1use std::sync::Arc;
21use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
22use std::time::{Duration, Instant};
23
24pub const BYTES_KIB: u64 = 1024;
26pub const BYTES_MIB: u64 = 1024 * BYTES_KIB;
28pub const BYTES_GIB: u64 = 1024 * BYTES_MIB;
30
31const BYTES_KB: f64 = 1024.0;
33const BYTES_MB: f64 = BYTES_KB * 1024.0;
34const BYTES_GB: f64 = BYTES_MB * 1024.0;
35
36pub trait ProgressReporter: Send {
44 fn on_total(&mut self, total_bytes: u64) {
48 let _ = total_bytes;
49 }
50
51 fn on_progress(&mut self, bytes_processed: u64, total_bytes: u64) -> bool {
55 let _ = (bytes_processed, total_bytes);
56 true
57 }
58
59 fn on_ratio(&mut self, input_bytes: u64, output_bytes: u64) {
63 let _ = (input_bytes, output_bytes);
64 }
65
66 fn on_entry_start(&mut self, entry_name: &str, size: u64) {
70 let _ = (entry_name, size);
71 }
72
73 fn on_entry_complete(&mut self, entry_name: &str, success: bool) {
77 let _ = (entry_name, success);
78 }
79
80 fn on_password_needed(&mut self) -> Option<String> {
84 None
85 }
86
87 fn on_warning(&mut self, message: &str) {
89 let _ = message;
90 }
91
92 fn should_cancel(&self) -> bool {
99 false
100 }
101}
102
103#[derive(Debug, Clone)]
105pub struct ProgressState {
106 pub total_bytes: u64,
108 pub processed_bytes: u64,
110 pub packed_bytes: u64,
112 pub current_entry: Option<String>,
114 pub entries_processed: usize,
116 pub entries_total: usize,
118 pub start_time: Instant,
120 pub last_update: Instant,
122}
123
124impl Default for ProgressState {
125 fn default() -> Self {
126 let now = Instant::now();
127 Self {
128 total_bytes: 0,
129 processed_bytes: 0,
130 packed_bytes: 0,
131 current_entry: None,
132 entries_processed: 0,
133 entries_total: 0,
134 start_time: now,
135 last_update: now,
136 }
137 }
138}
139
140impl ProgressState {
141 pub fn new() -> Self {
143 Self::default()
144 }
145
146 pub fn percentage(&self) -> f64 {
148 if self.total_bytes == 0 {
149 0.0
150 } else {
151 (self.processed_bytes as f64 / self.total_bytes as f64) * 100.0
152 }
153 }
154
155 pub fn compression_ratio(&self) -> f64 {
157 if self.processed_bytes == 0 {
158 1.0
159 } else {
160 self.packed_bytes as f64 / self.processed_bytes as f64
161 }
162 }
163
164 pub fn elapsed(&self) -> Duration {
166 self.start_time.elapsed()
167 }
168
169 pub fn bytes_per_second(&self) -> f64 {
171 let elapsed = self.elapsed().as_secs_f64();
172 if elapsed < 0.001 {
173 0.0
174 } else {
175 self.processed_bytes as f64 / elapsed
176 }
177 }
178
179 pub fn eta(&self) -> Option<Duration> {
181 let rate = self.bytes_per_second();
182 if rate < 1.0 || self.processed_bytes >= self.total_bytes {
183 return None;
184 }
185 let remaining = self.total_bytes - self.processed_bytes;
186 let seconds = remaining as f64 / rate;
187 Some(Duration::from_secs_f64(seconds))
188 }
189
190 pub fn format_rate(&self) -> String {
192 let rate = self.bytes_per_second();
193 format_bytes_per_second_iec(rate)
194 }
195
196 pub fn format_eta(&self) -> String {
198 match self.eta() {
199 Some(duration) => format_duration(duration),
200 None => "unknown".to_string(),
201 }
202 }
203}
204
205#[derive(Debug, Default, Clone)]
207pub struct NoProgress;
208
209impl ProgressReporter for NoProgress {}
210
211#[derive(Debug, Default, Clone)]
213pub struct StatisticsProgress {
214 pub state: ProgressState,
216 pub cancelled: bool,
218 pub warnings: Vec<String>,
220}
221
222impl StatisticsProgress {
223 pub fn new() -> Self {
225 Self::default()
226 }
227
228 pub fn state(&self) -> &ProgressState {
230 &self.state
231 }
232}
233
234impl ProgressReporter for StatisticsProgress {
235 fn on_total(&mut self, total_bytes: u64) {
236 self.state.total_bytes = total_bytes;
237 }
238
239 fn on_progress(&mut self, bytes_processed: u64, _total_bytes: u64) -> bool {
240 self.state.processed_bytes = bytes_processed;
241 self.state.last_update = Instant::now();
242 !self.cancelled
243 }
244
245 fn on_ratio(&mut self, _input_bytes: u64, output_bytes: u64) {
246 self.state.packed_bytes = output_bytes;
247 }
248
249 fn on_entry_start(&mut self, entry_name: &str, _size: u64) {
250 self.state.current_entry = Some(entry_name.to_string());
251 }
252
253 fn on_entry_complete(&mut self, _entry_name: &str, _success: bool) {
254 self.state.entries_processed += 1;
255 self.state.current_entry = None;
256 }
257
258 fn on_warning(&mut self, message: &str) {
259 self.warnings.push(message.to_string());
260 }
261
262 fn should_cancel(&self) -> bool {
263 self.cancelled
264 }
265}
266
267pub struct ThrottledProgress<P> {
271 inner: P,
272 min_interval: Duration,
273 last_callback: Instant,
274 last_bytes: u64,
275}
276
277impl<P: ProgressReporter> ThrottledProgress<P> {
278 pub fn new(inner: P, min_interval: Duration) -> Self {
282 Self {
283 inner,
284 min_interval,
285 last_callback: Instant::now(),
286 last_bytes: 0,
287 }
288 }
289
290 pub fn default_interval(inner: P) -> Self {
292 Self::new(inner, Duration::from_millis(100))
293 }
294
295 pub fn into_inner(self) -> P {
297 self.inner
298 }
299}
300
301impl<P: ProgressReporter> ProgressReporter for ThrottledProgress<P> {
302 fn on_total(&mut self, total_bytes: u64) {
303 self.inner.on_total(total_bytes);
304 }
305
306 fn on_progress(&mut self, bytes_processed: u64, total_bytes: u64) -> bool {
307 let now = Instant::now();
308 let elapsed = now.duration_since(self.last_callback);
309
310 if bytes_processed >= total_bytes || elapsed >= self.min_interval {
312 self.last_callback = now;
313 self.last_bytes = bytes_processed;
314 self.inner.on_progress(bytes_processed, total_bytes)
315 } else {
316 true
317 }
318 }
319
320 fn on_ratio(&mut self, input_bytes: u64, output_bytes: u64) {
321 self.inner.on_ratio(input_bytes, output_bytes);
322 }
323
324 fn on_entry_start(&mut self, entry_name: &str, size: u64) {
325 self.inner.on_entry_start(entry_name, size);
326 }
327
328 fn on_entry_complete(&mut self, entry_name: &str, success: bool) {
329 self.inner.on_entry_complete(entry_name, success);
330 }
331
332 fn on_password_needed(&mut self) -> Option<String> {
333 self.inner.on_password_needed()
334 }
335
336 fn on_warning(&mut self, message: &str) {
337 self.inner.on_warning(message);
338 }
339
340 fn should_cancel(&self) -> bool {
341 self.inner.should_cancel()
342 }
343}
344
345#[derive(Debug)]
349pub struct AtomicProgress {
350 total_bytes: AtomicU64,
351 processed_bytes: AtomicU64,
352 packed_bytes: AtomicU64,
353 cancelled: AtomicBool,
354 start_time: Instant,
355}
356
357impl Default for AtomicProgress {
358 fn default() -> Self {
359 Self::new()
360 }
361}
362
363impl AtomicProgress {
364 pub fn new() -> Self {
366 Self {
367 total_bytes: AtomicU64::new(0),
368 processed_bytes: AtomicU64::new(0),
369 packed_bytes: AtomicU64::new(0),
370 cancelled: AtomicBool::new(false),
371 start_time: Instant::now(),
372 }
373 }
374
375 pub fn shared() -> Arc<Self> {
377 Arc::new(Self::new())
378 }
379
380 pub fn total_bytes(&self) -> u64 {
382 self.total_bytes.load(Ordering::Relaxed)
383 }
384
385 pub fn processed_bytes(&self) -> u64 {
387 self.processed_bytes.load(Ordering::Relaxed)
388 }
389
390 pub fn packed_bytes(&self) -> u64 {
392 self.packed_bytes.load(Ordering::Relaxed)
393 }
394
395 pub fn is_cancelled(&self) -> bool {
397 self.cancelled.load(Ordering::Relaxed)
398 }
399
400 pub fn cancel(&self) {
402 self.cancelled.store(true, Ordering::Relaxed);
403 }
404
405 pub fn percentage(&self) -> f64 {
407 let total = self.total_bytes();
408 if total == 0 {
409 0.0
410 } else {
411 (self.processed_bytes() as f64 / total as f64) * 100.0
412 }
413 }
414
415 pub fn elapsed(&self) -> Duration {
417 self.start_time.elapsed()
418 }
419
420 pub fn bytes_per_second(&self) -> f64 {
422 let elapsed = self.elapsed().as_secs_f64();
423 if elapsed < 0.001 {
424 0.0
425 } else {
426 self.processed_bytes() as f64 / elapsed
427 }
428 }
429}
430
431impl ProgressReporter for AtomicProgress {
432 fn on_total(&mut self, total_bytes: u64) {
433 self.total_bytes.store(total_bytes, Ordering::Relaxed);
434 }
435
436 fn on_progress(&mut self, bytes_processed: u64, _total_bytes: u64) -> bool {
437 self.processed_bytes
438 .store(bytes_processed, Ordering::Relaxed);
439 !self.is_cancelled()
440 }
441
442 fn on_ratio(&mut self, _input_bytes: u64, output_bytes: u64) {
443 self.packed_bytes.store(output_bytes, Ordering::Relaxed);
444 }
445
446 fn should_cancel(&self) -> bool {
447 self.is_cancelled()
448 }
449}
450
451impl ProgressReporter for Arc<AtomicProgress> {
453 fn on_total(&mut self, total_bytes: u64) {
454 self.total_bytes.store(total_bytes, Ordering::Relaxed);
455 }
456
457 fn on_progress(&mut self, bytes_processed: u64, _total_bytes: u64) -> bool {
458 self.processed_bytes
459 .store(bytes_processed, Ordering::Relaxed);
460 !self.is_cancelled()
461 }
462
463 fn on_ratio(&mut self, _input_bytes: u64, output_bytes: u64) {
464 self.packed_bytes.store(output_bytes, Ordering::Relaxed);
465 }
466
467 fn should_cancel(&self) -> bool {
468 self.is_cancelled()
469 }
470}
471
472pub struct ClosureProgress<F> {
474 callback: F,
475}
476
477impl<F> ClosureProgress<F>
478where
479 F: FnMut(u64, u64) -> bool + Send,
480{
481 pub fn new(callback: F) -> Self {
486 Self { callback }
487 }
488}
489
490impl<F> ProgressReporter for ClosureProgress<F>
491where
492 F: FnMut(u64, u64) -> bool + Send,
493{
494 fn on_progress(&mut self, bytes_processed: u64, total_bytes: u64) -> bool {
495 (self.callback)(bytes_processed, total_bytes)
496 }
497}
498
499pub fn progress_fn<F>(f: F) -> ClosureProgress<F>
501where
502 F: FnMut(u64, u64) -> bool + Send,
503{
504 ClosureProgress::new(f)
505}
506
507pub fn format_bytes_per_second_iec(rate: f64) -> String {
511 if rate < BYTES_KB {
512 format!("{:.0} B/s", rate)
513 } else if rate < BYTES_MB {
514 format!("{:.1} KiB/s", rate / BYTES_KB)
515 } else if rate < BYTES_GB {
516 format!("{:.1} MiB/s", rate / BYTES_MB)
517 } else {
518 format!("{:.1} GiB/s", rate / BYTES_GB)
519 }
520}
521
522pub fn format_duration(duration: Duration) -> String {
524 let secs = duration.as_secs();
525 if secs < 60 {
526 format!("{}s", secs)
527 } else if secs < 3600 {
528 format!("{}m {}s", secs / 60, secs % 60)
529 } else {
530 format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
531 }
532}
533
534pub fn format_bytes_iec(bytes: u64) -> String {
551 let bytes_f64 = bytes as f64;
552 if bytes_f64 < BYTES_KB {
553 format!("{} B", bytes)
554 } else if bytes_f64 < BYTES_MB {
555 format!("{:.1} KiB", bytes_f64 / BYTES_KB)
556 } else if bytes_f64 < BYTES_GB {
557 format!("{:.1} MiB", bytes_f64 / BYTES_MB)
558 } else {
559 format!("{:.1} GiB", bytes_f64 / BYTES_GB)
560 }
561}
562
563pub fn format_bytes_iec_usize(bytes: usize) -> String {
568 format_bytes_iec(bytes as u64)
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574
575 #[test]
576 fn test_progress_state_percentage() {
577 let mut state = ProgressState::new();
578 state.total_bytes = 100;
579 state.processed_bytes = 25;
580 assert!((state.percentage() - 25.0).abs() < 0.001);
581 }
582
583 #[test]
584 fn test_progress_state_compression_ratio() {
585 let mut state = ProgressState::new();
586 state.processed_bytes = 1000;
587 state.packed_bytes = 500;
588 assert!((state.compression_ratio() - 0.5).abs() < 0.001);
589 }
590
591 #[test]
592 fn test_no_progress() {
593 let mut progress = NoProgress;
594 assert!(progress.on_progress(50, 100));
595 }
596
597 #[test]
598 fn test_statistics_progress() {
599 let mut progress = StatisticsProgress::new();
600 progress.on_total(1000);
601 progress.on_entry_start("test.txt", 500);
602 progress.on_progress(250, 1000);
603 progress.on_entry_complete("test.txt", true);
604
605 assert_eq!(progress.state().total_bytes, 1000);
606 assert_eq!(progress.state().processed_bytes, 250);
607 assert_eq!(progress.state().entries_processed, 1);
608 }
609
610 #[test]
611 fn test_throttled_progress() {
612 let inner = StatisticsProgress::new();
613 let mut throttled = ThrottledProgress::new(inner, Duration::from_millis(10));
614
615 throttled.on_total(100);
616 assert!(throttled.on_progress(10, 100));
617
618 assert!(throttled.on_progress(20, 100));
620
621 std::thread::sleep(Duration::from_millis(15));
623 assert!(throttled.on_progress(30, 100));
624 }
625
626 #[test]
627 fn test_atomic_progress() {
628 let progress = AtomicProgress::shared();
629 let mut reporter: Arc<AtomicProgress> = Arc::clone(&progress);
630
631 reporter.on_total(1000);
632 reporter.on_progress(500, 1000);
633
634 assert_eq!(progress.total_bytes(), 1000);
635 assert_eq!(progress.processed_bytes(), 500);
636 assert!((progress.percentage() - 50.0).abs() < 0.001);
637
638 progress.cancel();
639 assert!(!reporter.on_progress(600, 1000));
640 }
641
642 #[test]
643 fn test_closure_progress() {
644 let mut count = 0;
645 let mut progress = progress_fn(|bytes, total| {
646 count += 1;
647 bytes < total
648 });
649
650 assert!(progress.on_progress(50, 100));
651 assert!(progress.on_progress(99, 100));
652 assert!(!progress.on_progress(100, 100));
653 assert_eq!(count, 3);
654 }
655
656 #[test]
657 fn test_format_bytes_per_second() {
658 assert_eq!(format_bytes_per_second_iec(500.0), "500 B/s");
660 assert_eq!(format_bytes_per_second_iec(1500.0), "1.5 KiB/s");
661 assert_eq!(format_bytes_per_second_iec(1500.0 * 1024.0), "1.5 MiB/s");
662 assert_eq!(
663 format_bytes_per_second_iec(1500.0 * 1024.0 * 1024.0),
664 "1.5 GiB/s"
665 );
666 }
667
668 #[test]
669 fn test_format_duration() {
670 assert_eq!(format_duration(Duration::from_secs(45)), "45s");
671 assert_eq!(format_duration(Duration::from_secs(90)), "1m 30s");
672 assert_eq!(format_duration(Duration::from_secs(3700)), "1h 1m");
673 }
674
675 #[test]
676 fn test_format_bytes() {
677 assert_eq!(format_bytes_iec(500), "500 B");
679 assert_eq!(format_bytes_iec(1500), "1.5 KiB");
680 assert_eq!(format_bytes_iec(1500 * 1024), "1.5 MiB");
681 assert_eq!(format_bytes_iec(1500 * 1024 * 1024), "1.5 GiB");
682 }
683
684 #[test]
685 fn test_progress_state_empty() {
686 let state = ProgressState::new();
687 assert_eq!(state.percentage(), 0.0);
688 assert_eq!(state.compression_ratio(), 1.0);
689 }
690
691 #[test]
692 fn test_statistics_cancellation() {
693 let mut progress = StatisticsProgress::new();
694 assert!(progress.on_progress(50, 100));
695
696 progress.cancelled = true;
697 assert!(!progress.on_progress(75, 100));
698 }
699}