Skip to content

Commit 57ec91e

Browse files
client: Print progress when downloading files
1 parent a7584b1 commit 57ec91e

File tree

1 file changed

+97
-17
lines changed

1 file changed

+97
-17
lines changed

crates/client/src/main.rs

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::{
55
io::{Write, stdout},
66
net::{TcpStream, ToSocketAddrs},
77
process,
8+
time::Instant,
89
};
910

1011
use http::{HttpMethod, HttpRequest, HttpResponse};
@@ -13,11 +14,89 @@ use config::ClientConfig;
1314

1415
use crate::config::HttpType;
1516

16-
fn open_file(fname: &str) -> Box<dyn Write> {
17-
Box::new(File::create(fname).unwrap_or_else(|_| {
17+
pub struct ProgressWriter<W: Write> {
18+
max: usize,
19+
current: usize,
20+
last_update: Instant,
21+
prev_current: usize,
22+
writer: W,
23+
}
24+
25+
impl<W: Write> ProgressWriter<W> {
26+
fn start(&self) {
27+
println!(
28+
"{perc:3} {progress:8} {total:^8} speed",
29+
perc = "%",
30+
progress = "Progress",
31+
total = "Total",
32+
);
33+
}
34+
35+
fn print_progress(&self) {
36+
let Self { max, current, .. } = self;
37+
38+
fn get_unit(bytes: usize) -> String {
39+
let kb = bytes / 1000;
40+
if kb >= 1000 {
41+
format!("{} M", kb / 1000)
42+
} else {
43+
format!("{kb} K")
44+
}
45+
}
46+
47+
let curr = get_unit(*current);
48+
let max = get_unit(*max);
49+
50+
if self.max > 0 {
51+
let percentage = 100 * self.current / self.max;
52+
print!("\x1b[2K\r{percentage:<3} {curr:^8} {max:^8} ");
53+
} else {
54+
print!("\x1b[2K\r{current}");
55+
}
56+
let kbps = (self.current - self.prev_current) / 1000;
57+
if kbps >= 1000 {
58+
print!("{} Mb/s", kbps / 1000);
59+
} else {
60+
print!("{kbps} Kb/s");
61+
}
62+
stdout().flush().unwrap();
63+
}
64+
}
65+
66+
impl<W: Write> Write for ProgressWriter<W> {
67+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
68+
let n = self.writer.write(buf)?;
69+
self.current += n;
70+
71+
let time = Instant::now().duration_since(self.last_update);
72+
if time.as_millis() >= 1000 {
73+
self.print_progress();
74+
self.last_update = Instant::now();
75+
self.prev_current = self.current;
76+
}
77+
78+
Ok(n)
79+
}
80+
81+
fn flush(&mut self) -> io::Result<()> {
82+
self.writer.flush()
83+
}
84+
}
85+
86+
fn open_file(fname: &str, max: usize) -> Box<dyn Write> {
87+
let file = File::create(fname).unwrap_or_else(|_| {
1888
eprintln!("Couldn't create file: {fname}");
1989
process::exit(1);
20-
}))
90+
});
91+
let p = Box::new(ProgressWriter {
92+
max,
93+
current: 0,
94+
prev_current: 0,
95+
writer: file,
96+
last_update: Instant::now(),
97+
});
98+
p.start();
99+
p
21100
}
22101

23102
#[cfg(not(feature = "tls"))]
@@ -68,20 +147,6 @@ pub fn main() -> http::Result<()> {
68147
let addr = format!("{}:{}", conf.host, conf.port);
69148
let addrs = addr.to_socket_addrs().unwrap().next().unwrap();
70149

71-
let mut out: Box<dyn Write> = match conf.out_file {
72-
config::OutFile::Stdout => Box::new(stdout()),
73-
config::OutFile::Filename(s) => open_file(&s),
74-
config::OutFile::GetFromUrl => {
75-
let fname = conf
76-
.url
77-
.split('/')
78-
.filter(|s| !s.is_empty())
79-
.next_back()
80-
.unwrap_or(&conf.host);
81-
open_file(fname)
82-
}
83-
};
84-
85150
let req = HttpRequest::builder()
86151
.method(conf.method)
87152
.url(conf.url.clone().into_boxed_str())
@@ -107,6 +172,21 @@ pub fn main() -> http::Result<()> {
107172
process::exit(1);
108173
});
109174

175+
let len = result.content_length();
176+
let mut out: Box<dyn Write> = match conf.out_file {
177+
config::OutFile::Stdout => Box::new(stdout()),
178+
config::OutFile::Filename(s) => open_file(&s, len),
179+
config::OutFile::GetFromUrl => {
180+
let fname = conf
181+
.url
182+
.split('/')
183+
.filter(|s| !s.is_empty())
184+
.next_back()
185+
.unwrap_or(&conf.host);
186+
open_file(fname, len)
187+
}
188+
};
189+
110190
if matches!(conf.method, HttpMethod::HEAD) {
111191
println!("Headers");
112192
for (k, v) in result.headers() {

0 commit comments

Comments
 (0)