Skip to content

Commit

Permalink
Merge pull request #2 from Diggsey/next
Browse files Browse the repository at this point in the history
Merge in download progress indicator changes
  • Loading branch information
brson committed Feb 28, 2016
2 parents 39e7949 + 286ddd1 commit 984a430
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 108 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ openssl = "0.7.2"
hyper = "0.7.0"
term = "0.2.11"
itertools = "0.4.1"
time = "0.1.34"
tempdir = "0.3.4"
libc = "0.2.0"

Expand Down
2 changes: 1 addition & 1 deletion rust-install/tests/dist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ fn update_from_dist(dist_server: &Url,
remove_extensions: remove.to_owned(),
};

manifestation.update(&manifest, changes, temp_cfg, notify_handler.clone())
manifestation.update("unknown", &manifest, changes, temp_cfg, notify_handler.clone())
}

fn make_manifest_url(dist_server: &Url, toolchain: &ToolchainDesc) -> Result<Url, Error> {
Expand Down
102 changes: 0 additions & 102 deletions src/cli_dl_display.rs

This file was deleted.

184 changes: 184 additions & 0 deletions src/download_tracker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
use std::collections::VecDeque;
use std::fmt;
use term;
use time::precise_time_s;
use multirust::Notification;
use rust_install::Notification as In;
use rust_install::utils::Notification as Un;

/// Keep track of this many past download amounts
const DOWNLOAD_TRACK_COUNT: usize = 5;

/// Tracks download progress and displays information about it to a terminal.
pub struct DownloadTracker {
/// Content-Length of the to-be downloaded object.
content_len: Option<u64>,
/// Total data downloaded in bytes.
total_downloaded: usize,
/// Data downloaded this second.
downloaded_this_sec: usize,
/// Keeps track of amount of data downloaded every last few secs.
/// Used for averaging the download speed.
downloaded_last_few_secs: VecDeque<usize>,
/// Time stamp of the last second
last_sec: Option<f64>,
/// How many seconds have elapsed since the download started
seconds_elapsed: u32,
/// The terminal we write the information to.
term: Box<term::StdoutTerminal>,
/// Whether we displayed progress for the download or not.
///
/// If the download is quick enough, we don't have time to
/// display the progress info.
/// In that case, we do not want to do some cleanup stuff we normally do.
displayed_progress: bool,
}

impl DownloadTracker {
/// Creates a new DownloadTracker.
pub fn new() -> Self {
DownloadTracker {
content_len: None,
total_downloaded: 0,
downloaded_this_sec: 0,
downloaded_last_few_secs: VecDeque::with_capacity(DOWNLOAD_TRACK_COUNT),
seconds_elapsed: 0,
last_sec: None,
term: term::stdout().expect("Failed to open terminal"),
displayed_progress: false,
}
}

pub fn handle_notification(&mut self, n: &Notification) -> bool {
match n {
&Notification::Install(In::Utils(Un::DownloadContentLengthReceived(content_len))) => {
self.content_length_received(content_len);
true
}
&Notification::Install(In::Utils(Un::DownloadDataReceived(len))) => {
if super::stderr_isatty() {
self.data_received(len);
}
true
}
&Notification::Install(In::Utils(Un::DownloadFinished)) => {
self.download_finished();
true
}
_ => false
}
}

/// Notifies self that Content-Length information has been received.
pub fn content_length_received(&mut self, content_len: u64) {
self.content_len = Some(content_len);
}
/// Notifies self that data of size `len` has been received.
pub fn data_received(&mut self, len: usize) {
self.total_downloaded += len;
self.downloaded_this_sec += len;

let current_time = precise_time_s();

match self.last_sec {
None => self.last_sec = Some(current_time),
Some(start) => {
let elapsed = current_time - start;
if elapsed >= 1.0 {
self.seconds_elapsed += 1;

self.display();
self.last_sec = Some(current_time);
if self.downloaded_last_few_secs.len() == DOWNLOAD_TRACK_COUNT {
self.downloaded_last_few_secs.pop_back();
}
self.downloaded_last_few_secs.push_front(self.downloaded_this_sec);
self.downloaded_this_sec = 0;
}
}
}
}
/// Notifies self that the download has finished.
pub fn download_finished(&mut self) {
if self.displayed_progress {
// Display the finished state
self.display();
let _ = writeln!(&mut self.term, "");
}
self.prepare_for_new_download();
}
/// Resets the state to be ready for a new download.
fn prepare_for_new_download(&mut self) {
self.content_len = None;
self.total_downloaded = 0;
self.downloaded_this_sec = 0;
self.downloaded_last_few_secs.clear();
self.seconds_elapsed = 0;
self.last_sec = None;
self.displayed_progress = false;
}
/// Display the tracked download information to the terminal.
fn display(&mut self) {
let total_h = HumanReadable(self.total_downloaded as u64);
let sum = self.downloaded_last_few_secs
.iter()
.fold(0usize, |a, &v| a + v);
let len = self.downloaded_last_few_secs.len();
let speed = if len > 0 {
(sum / len) as u64
} else {
0
};
let speed_h = HumanReadable(speed);

match self.content_len {
Some(content_len) => {
use std::borrow::Cow;

let percent = (self.total_downloaded as f64 / content_len as f64) * 100.;
let content_len_h = HumanReadable(content_len);
let remaining = content_len - self.total_downloaded as u64;
let eta = if speed > 0 {
Cow::Owned(format!("{}s", remaining / speed))
} else {
Cow::Borrowed("Unknown")
};
let _ = write!(&mut self.term,
"{} / {} ({:.2}%) {}/s ETA: {}",
total_h,
content_len_h,
percent,
speed_h,
eta);
}
None => {
let _ = write!(&mut self.term, "Total: {} Speed: {}/s", total_h, speed_h);
}
}
// delete_line() doesn't seem to clear the line properly.
// Instead, let's just print some whitespace to clear it.
let _ = write!(&mut self.term, " ");
let _ = self.term.flush();
let _ = self.term.carriage_return();
self.displayed_progress = true;
}
}

/// Human readable representation of data size in bytes
struct HumanReadable(u64);

impl fmt::Display for HumanReadable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
const KIB: f64 = 1024.0;
const MIB: f64 = 1048576.0;
let size = self.0 as f64;

if size >= MIB {
write!(f, "{:.2} MiB", size / MIB)
} else if size >= KIB {
write!(f, "{:.2} KiB", size / KIB)
} else {
write!(f, "{} B", size)
}
}
}
11 changes: 6 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ extern crate multirust;
extern crate term;
extern crate openssl;
extern crate itertools;
extern crate time;

#[cfg(windows)]
extern crate winapi;
Expand All @@ -31,16 +32,14 @@ use std::fmt;
use std::iter;
use std::thread;
use std::time::Duration;
use std::rc::Rc;
use multirust::*;
use rust_install::dist;
use openssl::crypto::hash::{Type, Hasher};
use itertools::Itertools;
use cli_dl_display::DownloadDisplayer;
use rust_install::notify::NotificationLevel;

mod cli;
mod cli_dl_display;
mod download_tracker;

macro_rules! warn {
( $ ( $ arg : tt ) * ) => ( $crate::warn_fmt ( format_args ! ( $ ( $ arg ) * ) ) )
Expand Down Expand Up @@ -106,13 +105,15 @@ fn info_fmt(args: fmt::Arguments) {
}

fn set_globals(m: Option<&ArgMatches>) -> Result<Cfg> {
use download_tracker::DownloadTracker;
use std::cell::RefCell;

let dl_display = Rc::new(DownloadDisplayer::new());
let download_tracker = RefCell::new(DownloadTracker::new());

// Base config
let verbose = m.map_or(false, |m| m.is_present("verbose"));
Cfg::from_env(shared_ntfy!(move |n: Notification| {
if dl_display.handle_notification(&n) {
if download_tracker.borrow_mut().handle_notification(&n) {
return;
}

Expand Down

0 comments on commit 984a430

Please sign in to comment.