|
| 1 | +use std::{collections::HashMap, fmt, sync::Arc, time::Duration}; |
| 2 | + |
| 3 | +use arc_swap::ArcSwap; |
| 4 | +use tui::{MultiProgress, ProgressBar, ProgressStyle, Styled}; |
| 5 | + |
| 6 | +use crate::{output, repository}; |
| 7 | + |
| 8 | +/// Textual output to stdout / stderr |
| 9 | +#[derive(Debug, Clone, Default)] |
| 10 | +pub struct TuiOutput { |
| 11 | + progress: ProgressState, |
| 12 | +} |
| 13 | + |
| 14 | +impl output::Emitter for TuiOutput { |
| 15 | + fn emit(&self, event: &output::InternalEvent) { |
| 16 | + match event { |
| 17 | + output::InternalEvent::RepositoryManager(event) => self.emit_repository_manager(event), |
| 18 | + } |
| 19 | + } |
| 20 | +} |
| 21 | + |
| 22 | +impl TuiOutput { |
| 23 | + fn emit_repository_manager(&self, event: &repository::manager::OutputEvent) { |
| 24 | + match event { |
| 25 | + repository::manager::OutputEvent::RefreshStarted { .. } => { |
| 26 | + self.progress.multi_start(); |
| 27 | + } |
| 28 | + repository::manager::OutputEvent::RefreshRepoStarted(id) => { |
| 29 | + let id = id.to_string(); |
| 30 | + let pb = self.progress.multi_add_pb( |
| 31 | + &id, |
| 32 | + ProgressBar::new_spinner() |
| 33 | + .with_style( |
| 34 | + ProgressStyle::with_template(" {spinner} {wide_msg}") |
| 35 | + .unwrap() |
| 36 | + .tick_chars("--=≡■≡=--"), |
| 37 | + ) |
| 38 | + .with_message(format!("{} {id}", "Refreshing".blue())), |
| 39 | + ); |
| 40 | + pb.enable_steady_tick(Duration::from_millis(150)); |
| 41 | + } |
| 42 | + repository::manager::OutputEvent::RefreshRepoFinished(id) => { |
| 43 | + let id = id.to_string(); |
| 44 | + self.progress |
| 45 | + .multi_pb_println(&id, format_args!("{} {id}", "Refreshed".green())); |
| 46 | + self.progress.multi_remove_pb(&id); |
| 47 | + } |
| 48 | + repository::manager::OutputEvent::RefreshFinished { .. } => { |
| 49 | + self.progress.multi_finish(); |
| 50 | + } |
| 51 | + } |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +#[derive(Debug, Clone, Default)] |
| 56 | +struct ProgressState { |
| 57 | + // ArcSwap provides lock-free safe usage in sync & async environments |
| 58 | + mpb: Arc<ArcSwap<MultiProgress>>, |
| 59 | + pbs: Arc<ArcSwap<HashMap<String, ProgressBar>>>, |
| 60 | +} |
| 61 | + |
| 62 | +impl ProgressState { |
| 63 | + fn multi_start(&self) { |
| 64 | + self.mpb.store(Arc::new(MultiProgress::new())); |
| 65 | + self.pbs.store(Arc::new(HashMap::new())); |
| 66 | + } |
| 67 | + |
| 68 | + fn multi_add_pb(&self, id: &str, pb: ProgressBar) -> ProgressBar { |
| 69 | + let pb = self.mpb.load().add(pb); |
| 70 | + self.pbs.rcu(|pbs| { |
| 71 | + let mut pbs = (**pbs).clone(); |
| 72 | + pbs.insert(id.to_owned(), pb.clone()); |
| 73 | + Arc::new(pbs) |
| 74 | + }); |
| 75 | + pb |
| 76 | + } |
| 77 | + |
| 78 | + fn multi_pb_println(&self, id: &str, args: fmt::Arguments<'_>) { |
| 79 | + let pbs = self.pbs.load(); |
| 80 | + pbs.get(id).expect("pb exists").suspend(|| println!("{args}")); |
| 81 | + } |
| 82 | + |
| 83 | + fn multi_remove_pb(&self, id: &str) { |
| 84 | + self.pbs.rcu(|pbs| { |
| 85 | + let mut pbs = (**pbs).clone(); |
| 86 | + pbs.remove(id); |
| 87 | + Arc::new(pbs) |
| 88 | + }); |
| 89 | + } |
| 90 | + |
| 91 | + fn multi_finish(&self) { |
| 92 | + self.mpb.store(Arc::new(MultiProgress::new())); |
| 93 | + self.pbs.store(Arc::new(HashMap::new())); |
| 94 | + } |
| 95 | +} |
0 commit comments