scirs2_core/logging/progress/
multi.rs1use super::tracker::EnhancedProgressTracker;
7use std::collections::HashMap;
8
9pub struct MultiProgress {
11 trackers: HashMap<usize, EnhancedProgressTracker>,
13 next_id: usize,
15 show_all: bool,
17}
18
19impl MultiProgress {
20 pub fn new() -> Self {
22 Self {
23 trackers: HashMap::new(),
24 next_id: 0,
25 show_all: true,
26 }
27 }
28
29 pub fn new_single_view() -> Self {
31 Self {
32 trackers: HashMap::new(),
33 next_id: 0,
34 show_all: false,
35 }
36 }
37
38 pub fn add(&mut self, mut tracker: EnhancedProgressTracker) -> usize {
40 let id = self.next_id;
41 self.next_id += 1;
42
43 if !self.show_all {
45 tracker.hide();
46 }
47
48 self.trackers.insert(id, tracker);
49 id
50 }
51
52 pub fn get(&mut self, id: usize) -> Option<&mut EnhancedProgressTracker> {
54 self.trackers.get_mut(&id)
55 }
56
57 pub fn remove(&mut self, id: usize) -> Option<EnhancedProgressTracker> {
59 self.trackers.remove(&id)
60 }
61
62 pub fn start(&mut self, id: usize) {
64 if let Some(tracker) = self.trackers.get_mut(&id) {
65 tracker.start();
66 }
67 }
68
69 pub fn start_all(&mut self) {
71 for tracker in self.trackers.values_mut() {
72 tracker.start();
73 }
74 }
75
76 pub fn update(&mut self, id: usize, processed: u64) {
78 if let Some(tracker) = self.trackers.get_mut(&id) {
79 tracker.update(processed);
80 }
81
82 if !self.show_all {
83 self.render_overview();
84 }
85 }
86
87 pub fn increment(&mut self, id: usize, amount: u64) {
89 if let Some(tracker) = self.trackers.get_mut(&id) {
90 tracker.increment(amount);
91 }
92
93 if !self.show_all {
94 self.render_overview();
95 }
96 }
97
98 pub fn finish(&mut self, id: usize) {
100 if let Some(tracker) = self.trackers.get_mut(&id) {
101 tracker.finish();
102 }
103
104 if !self.show_all {
105 self.render_overview();
106 }
107 }
108
109 pub fn finish_all(&mut self) {
111 for tracker in self.trackers.values_mut() {
112 tracker.finish();
113 }
114 }
115
116 pub fn active_count(&self) -> usize {
118 self.trackers.len()
119 }
120
121 pub fn overall_progress(&self) -> f64 {
123 if self.trackers.is_empty() {
124 return 0.0;
125 }
126
127 let total_progress: f64 = self
128 .trackers
129 .values()
130 .map(|tracker| tracker.stats().percentage)
131 .sum();
132
133 total_progress / self.trackers.len() as f64
134 }
135
136 pub fn all_complete(&self) -> bool {
138 self.trackers.values().all(|tracker| {
139 let stats = tracker.stats();
140 stats.is_complete()
141 })
142 }
143
144 pub fn tracker_ids(&self) -> Vec<usize> {
146 self.trackers.keys().copied().collect()
147 }
148
149 pub fn clear_completed(&mut self) {
151 self.trackers.retain(|_, tracker| {
152 let stats = tracker.stats();
153 !stats.is_complete()
154 });
155 }
156
157 fn render_overview(&self) {
159 if self.show_all || self.trackers.is_empty() {
160 return;
161 }
162
163 print!("\r\x1b[K");
165
166 let overall = self.overall_progress();
168 print!("Overall: {overall:.1}% | ");
169
170 let active_trackers: Vec<_> = self
172 .trackers
173 .iter()
174 .filter(|(_, tracker)| {
175 let stats = tracker.stats();
176 !stats.is_complete()
177 })
178 .collect();
179
180 match active_trackers.len() {
181 1 => {
182 let (_, tracker) = active_trackers[0];
183 let stats = tracker.stats();
184 print!(
185 "{}: {:.1}% ({}/{})",
186 tracker.description, stats.percentage, stats.processed, stats.total
187 );
188 }
189 n if n > 1 => {
190 print!("{} active tasks", active_trackers.len());
191 }
192 _ => {
193 print!("All tasks complete");
194 }
195 }
196
197 use std::io::{self, Write};
198 let _ = io::stdout().flush();
199 }
200}
201
202impl Default for MultiProgress {
203 fn default() -> Self {
204 Self::new()
205 }
206}
207
208pub struct ProgressGroup {
210 name: String,
212 multi: MultiProgress,
214 started_at: Option<std::time::Instant>,
216}
217
218impl ProgressGroup {
219 pub fn new(name: &str) -> Self {
221 Self {
222 name: name.to_string(),
223 multi: MultiProgress::new(),
224 started_at: None,
225 }
226 }
227
228 pub fn add_tracker(&mut self, tracker: EnhancedProgressTracker) -> usize {
230 if self.started_at.is_none() {
231 self.started_at = Some(std::time::Instant::now());
232 }
233 self.multi.add(tracker)
234 }
235
236 pub fn start_all(&mut self) {
238 self.started_at = Some(std::time::Instant::now());
239 self.multi.start_all();
240
241 println!("Starting group: {}", self.name);
242 }
243
244 pub fn update(&mut self, id: usize, processed: u64) {
246 self.multi.update(id, processed);
247 }
248
249 pub fn finish_all(&mut self) {
251 self.multi.finish_all();
252
253 if let Some(started) = self.started_at {
254 let elapsed = started.elapsed();
255 println!(
256 "Group '{}' completed in {:.2}s",
257 self.name,
258 elapsed.as_secs_f64()
259 );
260 }
261 }
262
263 pub fn overall_progress(&self) -> f64 {
265 self.multi.overall_progress()
266 }
267
268 pub fn is_complete(&self) -> bool {
270 self.multi.all_complete()
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use crate::logging::progress::tracker::{ProgressBuilder, ProgressStyle};
278
279 #[test]
280 fn test_multi_progress_creation() {
281 let multi = MultiProgress::new();
282 assert_eq!(multi.active_count(), 0);
283 assert_eq!(multi.overall_progress(), 0.0);
284 }
285
286 #[test]
287 fn test_multi_progress_add_tracker() {
288 let mut multi = MultiProgress::new();
289
290 let tracker = ProgressBuilder::new("Test", 100)
291 .style(ProgressStyle::Bar)
292 .build();
293
294 let id = multi.add(tracker);
295 assert_eq!(id, 0);
296 assert_eq!(multi.active_count(), 1);
297 }
298
299 #[test]
300 fn test_multi_progress_overall_progress() {
301 let mut multi = MultiProgress::new();
302
303 let tracker1 = ProgressBuilder::new("Test 1", 100).build();
304 let tracker2 = ProgressBuilder::new("Test 2", 100).build();
305
306 let id1 = multi.add(tracker1);
307 let id2 = multi.add(tracker2);
308
309 multi.start_all();
310 multi.update(id1, 50); multi.update(id2, 25); let overall = multi.overall_progress();
315 assert!((overall - 37.5).abs() < 0.1);
316 }
317
318 #[test]
319 fn test_progress_group() {
320 let mut group = ProgressGroup::new("Test Group");
321
322 let tracker = ProgressBuilder::new("Task", 100).build();
323 let id = group.add_tracker(tracker);
324
325 assert_eq!(group.overall_progress(), 0.0);
326 assert!(!group.is_complete());
327 }
328}