Skip to content

Commit

Permalink
Show a progress bar during downloading an image
Browse files Browse the repository at this point in the history
  • Loading branch information
nullpo-head committed Aug 22, 2021
1 parent 69e2f48 commit 7ea56a3
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 49 deletions.
4 changes: 3 additions & 1 deletion distrod/distrod/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ log = "0.4"
env_logger = "0.8"
anyhow = "1.0"
nix = "0.20.0"
indicatif = "0.16"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
scraper = "0.12"
reqwest = { version = "0.11", features = ["blocking", "json"] }
reqwest = { version = "0.11" }
tokio = { version = "1.10", features = ["rt", "rt-multi-thread", "macros"] }
chrono = "0.4"
xz2 = "0.1"
tar = "0.4"
Expand Down
26 changes: 13 additions & 13 deletions distrod/distrod/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use anyhow::{anyhow, bail, Context, Result};
use distro::Distro;
use libs::cli_ui::{choose_from_list, init_logger, prompt_path, LogLevel};
use libs::cli_ui::{build_progress_bar, choose_from_list, init_logger, prompt_path, LogLevel};
use libs::distrod_config::{self, DistrodConfig};
use libs::local_image::LocalDistroImage;
use libs::multifork::set_noninheritable_sig_ign;
use std::ffi::{CString, OsString};
use std::fs::File;
use std::io::{stdin, Read};
use std::io::{stdin, Cursor, Read};
use std::os::unix::prelude::OsStrExt;
use std::path::Path;
use structopt::StructOpt;
Expand All @@ -15,7 +15,8 @@ use xz2::read::XzDecoder;
use libs::command_alias::CommandAlias;
use libs::distro;
use libs::distro_image::{
self, DistroImage, DistroImageFetcher, DistroImageFetcherGen, DistroImageFile,
self, download_file_with_progress, DistroImage, DistroImageFetcher, DistroImageFetcherGen,
DistroImageFile,
};
use libs::lxd_image::LxdDistroImageList;
use libs::passwd::IdCredential;
Expand Down Expand Up @@ -64,7 +65,7 @@ pub struct ExecOpts {
#[structopt(short, long)]
user: Option<String>,

#[structopt(short, long)]
#[structopt(short = "i", long)]
uid: Option<u32>,

#[structopt(short, long)]
Expand Down Expand Up @@ -227,18 +228,20 @@ fn disable_wsl_exec_hook(_opts: DisableOpts) -> Result<()> {
Ok(())
}

fn create_distro(opts: CreateOpts) -> Result<()> {
#[tokio::main]
async fn create_distro(opts: CreateOpts) -> Result<()> {
let image = match opts.image_path {
None => {
let local_image_fetcher =
|| Ok(Box::new(LocalDistroImage::new(prompt_path)) as Box<dyn DistroImageFetcher>);
let lxd_image_fetcher =
|| Ok(Box::new(LxdDistroImageList::default()) as Box<dyn DistroImageFetcher>);
let fetchers = vec![
Box::new(local_image_fetcher) as Box<DistroImageFetcherGen>,
Box::new(lxd_image_fetcher) as Box<DistroImageFetcherGen>,
Box::new(local_image_fetcher) as DistroImageFetcherGen,
Box::new(lxd_image_fetcher) as DistroImageFetcherGen,
];
distro_image::fetch_image(fetchers, choose_from_list, 1)
.await
.with_context(|| "Failed to fetch the image list.")?
}
Some(path) => DistroImage {
Expand All @@ -255,13 +258,10 @@ fn create_distro(opts: CreateOpts) -> Result<()> {
) as Box<dyn Read>,
DistroImageFile::Url(url) => {
log::info!("Downloading '{}'...", url);
let client = reqwest::blocking::Client::builder().timeout(None).build()?;
let response = client
.get(&url)
.send()
.with_context(|| format!("Failed to download {}.", &url))?;
let mut bytes = vec![];
download_file_with_progress(&url, build_progress_bar, &mut bytes).await?;
log::info!("Download done.");
Box::new(std::io::Cursor::new(response.bytes()?)) as Box<dyn Read>
Box::new(Cursor::new(bytes)) as Box<dyn Read>
}
};

Expand Down
4 changes: 3 additions & 1 deletion distrod/distrod_wsl_launcher/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ anyhow = "1.0"
xz2 = "0.1"
tar = "0.4"
flate2 = "1.0"
indicatif = "0.16"
chrono = "0.4"
scraper = "0.12"
reqwest = { version = "0.11", features = ["blocking", "json"] }
reqwest = { version = "0.11" }
tokio = { version = "1.10", features = ["rt", "rt-multi-thread", "macros"] }
colored = "2"
tempfile = "3"
bytes = "1.0"
32 changes: 18 additions & 14 deletions distrod/distrod_wsl_launcher/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use anyhow::{bail, Context, Result};
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use libs::cli_ui::{self, LogLevel};
use indicatif::ProgressBar;
use libs::cli_ui::{self, build_progress_bar, LogLevel};
use libs::cli_ui::{init_logger, prompt_string};
use libs::distro_image::{self, DistroImageFetcher, DistroImageFetcherGen, DistroImageFile};
use libs::distro_image::{
self, download_file_with_progress, DistroImageFetcher, DistroImageFetcherGen, DistroImageFile,
};
use libs::distrod_config;
use libs::local_image::LocalDistroImage;
use libs::lxd_image::LxdDistroImageList;
Expand Down Expand Up @@ -137,7 +140,8 @@ fn config_distro(distro_name: &str, opts: ConfigOpts) -> Result<()> {
Ok(())
}

fn install_distro(distro_name: &str, opts: InstallOpts) -> Result<()> {
#[tokio::main]
async fn install_distro(distro_name: &str, opts: InstallOpts) -> Result<()> {
let wsl = WslApi::new()
.with_context(|| "Failed to retrieve WSL API. Have you enabled the WSL2 feature?")?;

Expand All @@ -158,7 +162,9 @@ You can install a local .tar.xz, or download an image from linuxcontainers.org.
BTW, you can run Systemd with distrod, so you can try LXC/LXD with distrod!
================================================================================="
);
let lxd_root_tarxz = fetch_distro_image().with_context(|| "Failed to fetch a distro image.")?;
let lxd_root_tarxz = fetch_distro_image()
.await
.with_context(|| "Failed to fetch a distro image.")?;
let lxd_tar = tar::Archive::new(XzDecoder::new(lxd_root_tarxz));

log::info!(
Expand Down Expand Up @@ -203,16 +209,17 @@ You can install a local .tar.xz, or download an image from linuxcontainers.org.
Ok(())
}

fn fetch_distro_image() -> Result<Box<dyn Read>> {
async fn fetch_distro_image() -> Result<Box<dyn Read>> {
let local_image_fetcher =
|| Ok(Box::new(LocalDistroImage::new(cli_ui::prompt_path)) as Box<dyn DistroImageFetcher>);
let lxd_image_fetcher =
|| Ok(Box::new(LxdDistroImageList::default()) as Box<dyn DistroImageFetcher>);
let fetchers = vec![
Box::new(local_image_fetcher) as Box<DistroImageFetcherGen>,
Box::new(lxd_image_fetcher) as Box<DistroImageFetcherGen>,
Box::new(local_image_fetcher) as DistroImageFetcherGen,
Box::new(lxd_image_fetcher) as DistroImageFetcherGen,
];
let image = distro_image::fetch_image(fetchers, cli_ui::choose_from_list, 1)
.await
.with_context(|| "Failed to fetch the image list.")?;
match image.image {
DistroImageFile::Local(path) => {
Expand All @@ -222,14 +229,10 @@ fn fetch_distro_image() -> Result<Box<dyn Read>> {
}
DistroImageFile::Url(url) => {
log::info!("Downloading '{}'...", url);
let client = reqwest::blocking::Client::builder().timeout(None).build()?;
let response = client
.get(&url)
.send()
.with_context(|| format!("Failed to download {}.", &url))?;
let bytes = response.bytes().with_context(|| "Download failed.")?;
let mut bytes = vec![];
download_file_with_progress(&url, build_progress_bar, &mut bytes).await?;
log::info!("Download done.");
Ok(Box::new(Cursor::new(bytes)))
Ok(Box::new(Cursor::new(bytes)) as Box<dyn Read>)
}
}
}
Expand Down Expand Up @@ -349,6 +352,7 @@ fn add_user(
),
true,
)?;
log::info!("Querying the generated uid. This may take some time depending on your machine.");
query_uid(wsl, distro_name, user_name, tmp_dir)
}

Expand Down
4 changes: 3 additions & 1 deletion distrod/libs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-trait = "0.1.51"
anyhow = "1.0"
chrono = "0.4"
colored = "2"
log = "0.4"
env_logger = "0.8"
scraper = "0.12"
reqwest = { version = "0.11", features = ["blocking", "json"] }
indicatif = "0.16"
reqwest = { version = "0.11" }
glob = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
8 changes: 8 additions & 0 deletions distrod/libs/src/cli_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,11 @@ pub fn prompt_string(message: &str, target_name: &str, default: Option<&str>) ->
choice = choice.trim_end().to_owned();
Ok(choice)
}

pub fn build_progress_bar(total_size: u64) -> indicatif::ProgressBar {
let bar = indicatif::ProgressBar::new(total_size);
bar.set_style(indicatif::ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})")
.progress_chars("#>-"));
bar
}
51 changes: 43 additions & 8 deletions distrod/libs/src/distro_image.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::ffi::OsString;

use anyhow::Result;
use anyhow::{Context, Result};
use async_trait::async_trait;

pub type ListChooseFn = fn(list: DistroImageList) -> Result<Box<dyn DistroImageFetcher>>;
pub type PromptPath = fn(message: &str, default: Option<&str>) -> Result<OsString>;

#[async_trait]
pub trait DistroImageFetcher {
fn get_name(&self) -> &str;
fn fetch(&self) -> Result<DistroImageList>;
async fn fetch(&self) -> Result<DistroImageList>;
}

pub enum DistroImageList {
Expand Down Expand Up @@ -37,10 +39,10 @@ pub enum DistroImageFile {
Url(String),
}

pub type DistroImageFetcherGen = dyn Fn() -> Result<Box<dyn DistroImageFetcher>>;
pub type DistroImageFetcherGen = Box<dyn Fn() -> Result<Box<dyn DistroImageFetcher>> + Sync>;

pub fn fetch_image(
fetchers: Vec<Box<DistroImageFetcherGen>>,
pub async fn fetch_image(
fetchers: Vec<DistroImageFetcherGen>,
choose_from_list: ListChooseFn,
default_index: usize,
) -> Result<DistroImage> {
Expand All @@ -49,7 +51,7 @@ pub fn fetch_image(
default_index,
}) as Box<dyn DistroImageFetcher>;
loop {
let fetched_image_list = distro_image_list.fetch()?;
let fetched_image_list = distro_image_list.fetch().await?;
match fetched_image_list {
DistroImageList::Fetcher(_, _, _) => {
distro_image_list = choose_from_list(fetched_image_list)?;
Expand All @@ -62,16 +64,17 @@ pub fn fetch_image(
}

struct DistroImageFetchersList {
fetchers: Vec<Box<DistroImageFetcherGen>>,
fetchers: Vec<DistroImageFetcherGen>,
default_index: usize,
}

#[async_trait]
impl DistroImageFetcher for DistroImageFetchersList {
fn get_name(&self) -> &str {
"Image candidates"
}

fn fetch(&self) -> Result<DistroImageList> {
async fn fetch(&self) -> Result<DistroImageList> {
let fetchers: Result<Vec<Box<_>>> = self.fetchers.iter().map(|f| f()).collect();
Ok(DistroImageList::Fetcher(
"the way to get a distro image".to_owned(),
Expand All @@ -80,3 +83,35 @@ impl DistroImageFetcher for DistroImageFetchersList {
))
}
}

pub async fn download_file_with_progress<F, W>(
url: &str,
progress_bar_builder: F,
out: &mut W,
) -> Result<()>
where
F: FnOnce(u64) -> indicatif::ProgressBar,
W: std::io::Write,
{
let client = reqwest::Client::builder().build()?;
let mut response = client
.get(url)
.send()
.await
.with_context(|| format!("Failed to download {}.", &url))?;
let total_size = response
.content_length()
.with_context(|| format!("Failed to get the content length of {}.", &url))?;

let progress_bar = progress_bar_builder(total_size);
let mut downloaded_size = 0;

while let Some(bytes) = response.chunk().await? {
out.write_all(&bytes)?;
downloaded_size = std::cmp::min(downloaded_size + bytes.len(), total_size as usize);
progress_bar.set_position(downloaded_size as u64);
}

progress_bar.finish();
Ok(())
}
4 changes: 3 additions & 1 deletion distrod/libs/src/local_image.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use async_trait::async_trait;
use std::path::{Path, PathBuf};

use crate::distro_image::{
Expand All @@ -15,12 +16,13 @@ impl LocalDistroImage {
}
}

#[async_trait]
impl DistroImageFetcher for LocalDistroImage {
fn get_name(&self) -> &str {
"Use a local tar.xz file"
}

fn fetch(&self) -> Result<DistroImageList> {
async fn fetch(&self) -> Result<DistroImageList> {
let mut path;
loop {
path = (self.prompt_path)("Please input the path to your .tar.xz image file.", None)?;
Expand Down
Loading

0 comments on commit 7ea56a3

Please sign in to comment.