ibdl_core/progress_bars/
mod.rs1use ibdl_common::{
2 tokio::{spawn, sync::mpsc::Receiver},
3 ImageBoards,
4};
5use indicatif::{
6 HumanBytes, MultiProgress, ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle,
7};
8use std::{
9 fmt::Write,
10 sync::{
11 atomic::{AtomicU64, AtomicUsize, Ordering},
12 Arc,
13 },
14 time::Duration,
15};
16
17const PROGRESS_CHARS: &str = "━━";
18
19struct BarTemplates {
20 pub main: &'static str,
21 pub download: &'static str,
22}
23
24impl BarTemplates {
25 #[inline]
27 pub fn new(imageboard: ImageBoards) -> Self {
28 match imageboard {
29 ImageBoards::E621 => Self {
30 main: "{spinner:.yellow.bold} {elapsed_precise:.bold} {wide_bar:.blue/white.dim} {percent:.bold} {pos:.yellow} (eta. {eta})",
31 download: "{spinner:.blue.bold} {bar:40.yellow/white.dim} {percent:.bold} | {byte_progress:21.blue} @ {bytes_per_sec:>13.yellow} (eta. {eta:<4.blue})",
32 },
33 ImageBoards::GelbooruV0_2 => Self {
34 main: "{spinner:.red.bold} {elapsed_precise:.bold} {wide_bar:.red/white.dim} {percent:.bold} {pos:.bold} (eta. {eta})",
35 download: "{spinner:.red.bold} {bar:40.red/white.dim} {percent:.bold} | {byte_progress:21.bold.green} @ {bytes_per_sec:>13.red} (eta. {eta:<4})",
36 },
37 _ => Self::default(),
38 }
39 }
40}
41
42impl Default for BarTemplates {
43 fn default() -> Self {
44 Self {
45 main: "{spinner:.green.bold} {elapsed_precise:.bold} {wide_bar:.green/white.dim} {percent:.bold} {pos:.green} (eta. {eta:.blue})",
46 download: "{spinner:.green.bold} {bar:40.green/white.dim} {percent:.bold} | {byte_progress:21.green} @ {bytes_per_sec:>13.red} (eta. {eta:<4.blue})",
47 }
48 }
49}
50
51#[derive(Debug, Clone)]
55pub struct ProgressCounter {
56 pub total_mtx: Arc<AtomicUsize>,
57 pub downloaded_mtx: Arc<AtomicU64>,
58 pub main: ProgressBar,
59 pub multi: MultiProgress,
60}
61
62impl ProgressCounter {
63 pub fn initialize(len: u64, imageboard: ImageBoards) -> Self {
67 let template = BarTemplates::new(imageboard);
68 let bar = ProgressBar::new(len).with_style(master_progress_style(&template));
69 bar.set_draw_target(ProgressDrawTarget::stderr());
70 bar.enable_steady_tick(Duration::from_millis(100));
71
72 let multi = MultiProgress::new();
74 let main = multi.add(bar);
75
76 Self {
77 main,
78 multi,
79 total_mtx: Default::default(),
80 downloaded_mtx: Default::default(),
81 }
82 }
83
84 pub fn initialize_custom_style(len: u64, style: ProgressStyle) -> Self {
86 let bar = ProgressBar::new(len).with_style(style);
87 bar.set_draw_target(ProgressDrawTarget::stderr());
88 bar.enable_steady_tick(Duration::from_millis(100));
89
90 let multi = MultiProgress::new();
92 let main = multi.add(bar);
93
94 Self {
95 main,
96 multi,
97 total_mtx: Default::default(),
98 downloaded_mtx: Default::default(),
99 }
100 }
101
102 pub fn add_download_bar(&self, len: u64, imageboard: ImageBoards) -> ProgressBar {
104 let template = BarTemplates::new(imageboard);
105 let bar = ProgressBar::new(len).with_style(download_progress_style(&template));
106 bar.set_draw_target(ProgressDrawTarget::stderr());
107
108 self.multi.add(bar)
109 }
110
111 pub fn add_download_custom_style(&self, len: u64, style: ProgressStyle) -> ProgressBar {
113 let bar = ProgressBar::new(len).with_style(style);
114 bar.set_draw_target(ProgressDrawTarget::stderr());
115
116 self.multi.add(bar)
117 }
118
119 pub async fn init_length_updater(&self, channel: Receiver<u64>) {
120 let mut channel = channel;
121 let cloned_bar = self.main.clone();
122 spawn(async move {
123 while let Some(delta_posts) = channel.recv().await {
124 cloned_bar.inc_length(delta_posts);
125 }
126 })
127 .await
128 .unwrap();
129 }
130
131 pub async fn init_download_counter(&self, channel: Receiver<bool>) {
132 let mut channel = channel;
133 let cloned_bar = self.main.clone();
134 let cloned_mtx = self.downloaded_mtx.clone();
135 spawn(async move {
136 while let Some(downloaded) = channel.recv().await {
137 if downloaded {
138 cloned_bar.inc(1);
139 cloned_mtx.fetch_add(1, Ordering::SeqCst);
140 }
141 }
142 });
143 }
144
145 pub fn increment_counters(&self, delta: u64) {
146 self.main.inc(delta);
147 self.total_mtx.fetch_add(delta as usize, Ordering::SeqCst);
148 }
149}
150
151fn master_progress_style(templates: &BarTemplates) -> ProgressStyle {
152 ProgressStyle::default_bar()
153 .template(templates.main)
154 .unwrap()
155 .with_key("pos", |state: &ProgressState, w: &mut dyn Write| {
156 write!(w, "{}/{}", state.pos(), state.len().unwrap()).unwrap();
157 })
158 .with_key("percent", |state: &ProgressState, w: &mut dyn Write| {
159 write!(w, "{:>3.0}%", state.fraction() * 100_f32).unwrap();
160 })
161 .with_key(
162 "files_sec",
163 |state: &ProgressState, w: &mut dyn Write| match state.per_sec() {
164 files_sec if files_sec.abs() < f64::EPSILON => write!(w, "0 files/s").unwrap(),
165 files_sec if files_sec < 1.0 => write!(w, "{:.2} s/file", 1.0 / files_sec).unwrap(),
166 files_sec => write!(w, "{:.2} files/s", files_sec).unwrap(),
167 },
168 )
169 .progress_chars(PROGRESS_CHARS)
170}
171
172fn download_progress_style(templates: &BarTemplates) -> ProgressStyle {
173 ProgressStyle::default_bar()
174 .template(templates.download)
175 .unwrap()
176 .with_key("percent", |state: &ProgressState, w: &mut dyn Write| {
177 write!(w, "{:>3.0}%", state.fraction() * 100_f32).unwrap();
178 })
179 .with_key(
180 "byte_progress",
181 |state: &ProgressState, w: &mut dyn Write| {
182 write!(
183 w,
184 "{}/{}",
185 HumanBytes(state.pos()),
186 HumanBytes(state.len().unwrap())
187 )
188 .unwrap();
189 },
190 )
191 .progress_chars(PROGRESS_CHARS)
192}