typst/crates/typst-cli/src/download.rs
Andrew Voynov ac982f5856
Unified and fixed Duration formatting in the CLI (#4587)
Co-authored-by: PgBiel <9021226+PgBiel@users.noreply.github.com>
2024-09-10 13:49:37 +00:00

117 lines
3.6 KiB
Rust

use std::fmt::Display;
use std::io;
use std::io::Write;
use std::time::{Duration, Instant};
use codespan_reporting::term;
use codespan_reporting::term::termcolor::WriteColor;
use typst::utils::format;
use typst_kit::download::{DownloadState, Downloader, Progress};
use crate::terminal::{self, TermOut};
use crate::ARGS;
/// Prints download progress by writing `downloading {0}` followed by repeatedly
/// updating the last terminal line.
pub struct PrintDownload<T>(pub T);
impl<T: Display> Progress for PrintDownload<T> {
fn print_start(&mut self) {
// Print that a package downloading is happening.
let styles = term::Styles::default();
let mut out = terminal::out();
let _ = out.set_color(&styles.header_help);
let _ = write!(out, "downloading");
let _ = out.reset();
let _ = writeln!(out, " {}", self.0);
}
fn print_progress(&mut self, state: &DownloadState) {
let mut out = terminal::out();
let _ = out.clear_last_line();
let _ = display_download_progress(&mut out, state);
}
fn print_finish(&mut self, state: &DownloadState) {
let mut out = terminal::out();
let _ = display_download_progress(&mut out, state);
let _ = writeln!(out);
}
}
/// Returns a new downloader.
pub fn downloader() -> Downloader {
let user_agent = concat!("typst/", env!("CARGO_PKG_VERSION"));
match ARGS.cert.clone() {
Some(cert) => Downloader::with_path(user_agent, cert),
None => Downloader::new(user_agent),
}
}
/// Compile and format several download statistics and make and attempt at
/// displaying them on standard error.
pub fn display_download_progress(
out: &mut TermOut,
state: &DownloadState,
) -> io::Result<()> {
let sum: usize = state.bytes_per_second.iter().sum();
let len = state.bytes_per_second.len();
let speed = if len > 0 { sum / len } else { state.content_len.unwrap_or(0) };
let total_downloaded = as_bytes_unit(state.total_downloaded);
let speed_h = as_throughput_unit(speed);
let elapsed = format::time_starting_with_seconds(
&Instant::now().saturating_duration_since(state.start_time),
);
match state.content_len {
Some(content_len) => {
let percent = (state.total_downloaded as f64 / content_len as f64) * 100.;
let remaining = content_len - state.total_downloaded;
let download_size = as_bytes_unit(content_len);
let eta =
format::time_starting_with_seconds(&Duration::from_secs(if speed == 0 {
0
} else {
(remaining / speed) as u64
}));
writeln!(
out,
"{total_downloaded} / {download_size} ({percent:3.0} %) {speed_h} in {elapsed} ETA: {eta}",
)?;
}
None => writeln!(
out,
"Total downloaded: {total_downloaded} Speed: {speed_h} Elapsed: {elapsed}",
)?,
};
Ok(())
}
/// Format a given size as a unit of time. Setting `include_suffix` to true
/// appends a '/s' (per second) suffix.
fn as_bytes_unit(size: usize) -> String {
const KI: f64 = 1024.0;
const MI: f64 = KI * KI;
const GI: f64 = KI * KI * KI;
let size = size as f64;
if size >= GI {
format!("{:5.1} GiB", size / GI)
} else if size >= MI {
format!("{:5.1} MiB", size / MI)
} else if size >= KI {
format!("{:5.1} KiB", size / KI)
} else {
format!("{size:3} B")
}
}
fn as_throughput_unit(size: usize) -> String {
as_bytes_unit(size) + "/s"
}