Add progress indicators for the cli
This commit is contained in:
parent
217cd3e442
commit
04a1eed4e2
5 changed files with 84 additions and 7 deletions
50
Cargo.lock
generated
50
Cargo.lock
generated
|
@ -403,6 +403,21 @@ dependencies = [
|
||||||
"cache-padded",
|
"cache-padded",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"regex",
|
||||||
|
"terminal_size",
|
||||||
|
"unicode-width",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const_fn"
|
name = "const_fn"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
@ -577,6 +592,12 @@ dependencies = [
|
||||||
"dtoa",
|
"dtoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.26"
|
version = "0.8.26"
|
||||||
|
@ -960,6 +981,18 @@ dependencies = [
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indicatif"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"lazy_static",
|
||||||
|
"number_prefix",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "infer"
|
name = "infer"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
@ -1232,6 +1265,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "number_prefix"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.22.0"
|
version = "0.22.0"
|
||||||
|
@ -1278,6 +1317,7 @@ dependencies = [
|
||||||
"epub-builder",
|
"epub-builder",
|
||||||
"futures",
|
"futures",
|
||||||
"html5ever",
|
"html5ever",
|
||||||
|
"indicatif",
|
||||||
"kuchiki",
|
"kuchiki",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"md5",
|
"md5",
|
||||||
|
@ -1944,6 +1984,16 @@ dependencies = [
|
||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal_size"
|
||||||
|
version = "0.1.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
|
@ -17,6 +17,7 @@ clap = "2.33.3"
|
||||||
epub-builder = "0.4.8"
|
epub-builder = "0.4.8"
|
||||||
futures = "0.3.12"
|
futures = "0.3.12"
|
||||||
html5ever = "0.25.1"
|
html5ever = "0.25.1"
|
||||||
|
indicatif = "0.15.0"
|
||||||
kuchiki = "0.8.1"
|
kuchiki = "0.8.1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
|
|
14
src/epub.rs
14
src/epub.rs
|
@ -1,6 +1,7 @@
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
use epub_builder::{EpubBuilder, EpubContent, ZipLibrary};
|
use epub_builder::{EpubBuilder, EpubContent, ZipLibrary};
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::PaperoniError,
|
errors::PaperoniError,
|
||||||
|
@ -11,6 +12,12 @@ pub fn generate_epubs(
|
||||||
articles: Vec<Extractor>,
|
articles: Vec<Extractor>,
|
||||||
merged: Option<&String>,
|
merged: Option<&String>,
|
||||||
) -> Result<(), PaperoniError> {
|
) -> Result<(), PaperoniError> {
|
||||||
|
let bar = ProgressBar::new(articles.len() as u64);
|
||||||
|
let style = ProgressStyle::default_bar().template(
|
||||||
|
"{spinner:.cyan} [{elapsed_precise}] {bar:40.white} {:>8} epub {pos}/{len:7} {msg:.green}",
|
||||||
|
);
|
||||||
|
bar.set_style(style);
|
||||||
|
bar.set_message("Generating epubs");
|
||||||
match merged {
|
match merged {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
let mut epub = EpubBuilder::new(ZipLibrary::new()?)?;
|
let mut epub = EpubBuilder::new(ZipLibrary::new()?)?;
|
||||||
|
@ -33,6 +40,8 @@ pub fn generate_epubs(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
article.img_urls.iter().for_each(|img| {
|
article.img_urls.iter().for_each(|img| {
|
||||||
|
// TODO: Add error handling
|
||||||
|
bar.inc(1);
|
||||||
let mut file_path = std::env::temp_dir();
|
let mut file_path = std::env::temp_dir();
|
||||||
file_path.push(&img.0);
|
file_path.push(&img.0);
|
||||||
|
|
||||||
|
@ -48,6 +57,7 @@ pub fn generate_epubs(
|
||||||
});
|
});
|
||||||
let mut out_file = File::create(&name).unwrap();
|
let mut out_file = File::create(&name).unwrap();
|
||||||
epub.generate(&mut out_file)?;
|
epub.generate(&mut out_file)?;
|
||||||
|
bar.finish_with_message("Generated epub\n");
|
||||||
println!("Created {:?}", name);
|
println!("Created {:?}", name);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -79,8 +89,10 @@ pub fn generate_epubs(
|
||||||
epub.add_resource(file_path.file_name().unwrap(), img_buf, img.1.unwrap())?;
|
epub.add_resource(file_path.file_name().unwrap(), img_buf, img.1.unwrap())?;
|
||||||
}
|
}
|
||||||
epub.generate(&mut out_file)?;
|
epub.generate(&mut out_file)?;
|
||||||
println!("Created {:?}", file_name);
|
bar.inc(1);
|
||||||
|
// println!("Created {:?}", file_name);
|
||||||
}
|
}
|
||||||
|
bar.finish_with_message("Generated epubs\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
11
src/http.rs
11
src/http.rs
|
@ -1,6 +1,7 @@
|
||||||
use async_std::io::prelude::*;
|
use async_std::io::prelude::*;
|
||||||
use async_std::{fs::File, stream};
|
use async_std::{fs::File, stream};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use indicatif::ProgressBar;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{errors::ErrorKind, errors::PaperoniError, extractor::Extractor};
|
use crate::{errors::ErrorKind, errors::PaperoniError, extractor::Extractor};
|
||||||
|
@ -9,7 +10,7 @@ type HTMLResource = (String, String);
|
||||||
|
|
||||||
pub async fn fetch_html(url: &str) -> Result<HTMLResource, PaperoniError> {
|
pub async fn fetch_html(url: &str) -> Result<HTMLResource, PaperoniError> {
|
||||||
let client = surf::Client::new();
|
let client = surf::Client::new();
|
||||||
println!("Fetching...");
|
// println!("Fetching...");
|
||||||
|
|
||||||
let mut redirect_count: u8 = 0;
|
let mut redirect_count: u8 = 0;
|
||||||
let base_url = Url::parse(&url)?;
|
let base_url = Url::parse(&url)?;
|
||||||
|
@ -56,10 +57,12 @@ pub async fn fetch_html(url: &str) -> Result<HTMLResource, PaperoniError> {
|
||||||
pub async fn download_images(
|
pub async fn download_images(
|
||||||
extractor: &mut Extractor,
|
extractor: &mut Extractor,
|
||||||
article_origin: &Url,
|
article_origin: &Url,
|
||||||
|
bar: &ProgressBar,
|
||||||
) -> Result<(), Vec<PaperoniError>> {
|
) -> Result<(), Vec<PaperoniError>> {
|
||||||
if extractor.img_urls.len() > 0 {
|
if extractor.img_urls.len() > 0 {
|
||||||
println!("Downloading images...");
|
// println!("Downloading images...");
|
||||||
}
|
}
|
||||||
|
let img_count = extractor.img_urls.len();
|
||||||
|
|
||||||
let imgs_req_iter = extractor
|
let imgs_req_iter = extractor
|
||||||
.img_urls
|
.img_urls
|
||||||
|
@ -70,7 +73,9 @@ pub async fn download_images(
|
||||||
surf::Client::new().get(get_absolute_url(&url, article_origin)),
|
surf::Client::new().get(get_absolute_url(&url, article_origin)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.map(|(url, req)| async move {
|
.enumerate()
|
||||||
|
.map(|(img_idx, (url, req))| async move {
|
||||||
|
bar.set_message(format!("Downloading images [{}/{}]", img_idx + 1, img_count).as_str());
|
||||||
match req.await {
|
match req.await {
|
||||||
Ok(mut img_response) => {
|
Ok(mut img_response) => {
|
||||||
// let mut img_response = req.await.expect("Unable to retrieve image");
|
// let mut img_response = req.await.expect("Unable to retrieve image");
|
||||||
|
|
15
src/main.rs
15
src/main.rs
|
@ -4,6 +4,7 @@ extern crate lazy_static;
|
||||||
use async_std::stream;
|
use async_std::stream;
|
||||||
use async_std::task;
|
use async_std::task;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
|
@ -29,6 +30,12 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn download(app_config: AppConfig) {
|
fn download(app_config: AppConfig) {
|
||||||
|
let bar = ProgressBar::new(app_config.urls().len() as u64);
|
||||||
|
let style = ProgressStyle::default_bar().template(
|
||||||
|
"{spinner:.cyan} [{elapsed_precise}] {bar:40.white} {:>8} link {pos}/{len:7} {msg:.yellow/white}",
|
||||||
|
);
|
||||||
|
bar.set_style(style);
|
||||||
|
bar.enable_steady_tick(500);
|
||||||
let articles = task::block_on(async {
|
let articles = task::block_on(async {
|
||||||
let urls_iter = app_config.urls().iter().map(|url| fetch_html(url));
|
let urls_iter = app_config.urls().iter().map(|url| fetch_html(url));
|
||||||
let mut responses = stream::from_iter(urls_iter).buffered(app_config.max_conn());
|
let mut responses = stream::from_iter(urls_iter).buffered(app_config.max_conn());
|
||||||
|
@ -36,15 +43,15 @@ fn download(app_config: AppConfig) {
|
||||||
while let Some(fetch_result) = responses.next().await {
|
while let Some(fetch_result) = responses.next().await {
|
||||||
match fetch_result {
|
match fetch_result {
|
||||||
Ok((url, html)) => {
|
Ok((url, html)) => {
|
||||||
println!("Extracting");
|
// println!("Extracting");
|
||||||
let mut extractor = Extractor::from_html(&html);
|
let mut extractor = Extractor::from_html(&html);
|
||||||
|
bar.set_message("Extracting...");
|
||||||
extractor.extract_content(&url);
|
extractor.extract_content(&url);
|
||||||
|
|
||||||
if extractor.article().is_some() {
|
if extractor.article().is_some() {
|
||||||
extractor.extract_img_urls();
|
extractor.extract_img_urls();
|
||||||
|
|
||||||
if let Err(img_errors) =
|
if let Err(img_errors) =
|
||||||
download_images(&mut extractor, &Url::parse(&url).unwrap()).await
|
download_images(&mut extractor, &Url::parse(&url).unwrap(), &bar).await
|
||||||
{
|
{
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{} image{} failed to download for {}",
|
"{} image{} failed to download for {}",
|
||||||
|
@ -58,9 +65,11 @@ fn download(app_config: AppConfig) {
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("{}", e),
|
Err(e) => eprintln!("{}", e),
|
||||||
}
|
}
|
||||||
|
bar.inc(1);
|
||||||
}
|
}
|
||||||
articles
|
articles
|
||||||
});
|
});
|
||||||
|
bar.finish_with_message("Downloaded articles");
|
||||||
match generate_epubs(articles, app_config.merged()) {
|
match generate_epubs(articles, app_config.merged()) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => eprintln!("{}", e),
|
Err(e) => eprintln!("{}", e),
|
||||||
|
|
Reference in a new issue