this repo has no description
1use crate::utils::CmprssOutput;
2use clap::Args;
3use indicatif::{HumanBytes, ProgressBar};
4use std::str::FromStr;
5
6#[derive(clap::ValueEnum, Clone, Copy, Debug, Default)]
7pub enum ProgressDisplay {
8 #[default]
9 Auto,
10 On,
11 Off,
12}
13
14#[derive(Debug, Clone, Copy)]
15pub struct ChunkSize {
16 pub size_in_bytes: usize,
17}
18
19impl Default for ChunkSize {
20 fn default() -> Self {
21 ChunkSize {
22 size_in_bytes: 8192,
23 }
24 }
25}
26
27impl FromStr for ChunkSize {
28 type Err = &'static str;
29
30 fn from_str(s: &str) -> Result<Self, Self::Err> {
31 // Try to parse s as just a number
32 if let Ok(num) = s.parse::<usize>() {
33 if num == 0 {
34 return Err("Invalid number");
35 }
36 return Ok(ChunkSize { size_in_bytes: num });
37 }
38 // Simplify so that we always assume base 2, regardless of whether we see
39 // 'kb' or 'kib'
40 let mut s = s.to_lowercase();
41 if s.ends_with("ib") {
42 s.truncate(s.len() - 2);
43 s.push('b');
44 };
45 let (num_str, unit) = s.split_at(s.len() - 2);
46 let num = num_str.parse::<usize>().map_err(|_| "Invalid number")?;
47
48 let size_in_bytes = match unit {
49 "kb" => num * 1024,
50 "mb" => num * 1024 * 1024,
51 "gb" => num * 1024 * 1024 * 1024,
52 _ => return Err("Invalid unit"),
53 };
54 if size_in_bytes == 0 {
55 return Err("Invalid number");
56 }
57
58 Ok(ChunkSize { size_in_bytes })
59 }
60}
61
62#[derive(Args, Debug, Default, Clone, Copy)]
63pub struct ProgressArgs {
64 /// Show progress.
65 #[arg(long, value_enum, default_value = "auto")]
66 pub progress: ProgressDisplay,
67
68 /// Chunk size to use during the copy when showing the progress bar.
69 #[arg(long, default_value = "8kib")]
70 pub chunk_size: ChunkSize,
71}
72
73/// Progress bar for the compress process
74pub struct Progress {
75 /// The progress bar
76 bar: ProgressBar,
77 /// The number of bytes read from the input
78 input_read: u64,
79 /// The number of bytes written to the output
80 output_written: u64,
81}
82
83/// Create a progress bar if necessary
84pub fn progress_bar(
85 input_size: Option<u64>,
86 progress: ProgressDisplay,
87 output: &CmprssOutput,
88) -> Option<Progress> {
89 match (progress, output) {
90 (ProgressDisplay::Auto, CmprssOutput::Pipe(_)) => None,
91 (ProgressDisplay::Off, _) => None,
92 (_, _) => Some(Progress::new(input_size)),
93 }
94}
95
96impl Progress {
97 /// Create a new progress bar
98 /// Draws to stderr by default
99 pub fn new(input_size: Option<u64>) -> Self {
100 let bar = match input_size {
101 Some(size) => ProgressBar::new(size),
102 None => ProgressBar::new_spinner(),
103 };
104 bar.set_style(
105 indicatif::ProgressStyle::default_bar()
106 .template("{spinner:.green} [{elapsed_precise}] ({eta}) [{bar:40.cyan/blue}] {bytes}/{total_bytes} => {msg}").unwrap()
107 .progress_chars("#>-"),
108 );
109 Progress {
110 bar,
111 input_read: 0,
112 output_written: 0,
113 }
114 }
115
116 /// Update the progress bar with the number of bytes read from the input
117 pub fn update_input(&mut self, bytes_read: u64) {
118 self.input_read = bytes_read;
119 self.bar.set_position(self.input_read);
120 }
121
122 /// Update the progress bar with the number of bytes written to the output
123 pub fn update_output(&mut self, bytes_written: u64) {
124 self.output_written = bytes_written;
125 self.bar
126 .set_message(HumanBytes(self.output_written).to_string());
127 }
128
129 /// Finish the progress bar
130 pub fn finish(&self) {
131 self.bar.finish();
132 }
133}