This commit is contained in:
2022-06-13 08:44:16 +00:00
committed by GitHub
parent 960288e350
commit c8c3290f32
6 changed files with 140 additions and 4 deletions

View File

@@ -10,10 +10,16 @@ use crate::ext::io::ClearFile;
use crate::ext::replace::ReplaceWith2;
use crate::ext::rw_lock::GetRwLock;
use crate::ext::try_err::TryErr;
use crate::gettext;
use crate::utils::ask_need_overwrite;
use crate::utils::get_file_name_from_url;
use crate::webclient::WebClient;
use crate::webclient::ToHeaders;
use indicatif::ProgressBar;
use indicatif::ProgressStyle;
use indicatif::MultiProgress;
use reqwest::IntoUrl;
use std::borrow::Cow;
use std::collections::HashMap;
#[cfg(test)]
use std::fs::create_dir;
@@ -21,6 +27,7 @@ use std::fs::remove_file;
use std::io::Seek;
use std::io::SeekFrom;
use std::io::Write;
use std::ops::Deref;
use std::ops::DerefMut;
use std::path::Path;
use std::sync::Arc;
@@ -48,6 +55,10 @@ pub struct DownloaderInternal<T: Write + Seek + Send + Sync + ClearFile> {
pub tasks: RwLock<Vec<JoinHandle<Result<(), DownloaderError>>>>,
/// Whether to enable mulitple thread mode
multi: AtomicBool,
/// Whether to enable the progess bar.
progress_bar: AtomicBool,
/// The progress bar.
progress: RwLock<Option<ProgressBar>>,
}
impl DownloaderInternal<LocalFile> {
@@ -114,6 +125,8 @@ impl DownloaderInternal<LocalFile> {
status: RwLock::new(DownloaderStatus::Created),
tasks: RwLock::new(Vec::new()),
multi: AtomicBool::new(false),
progress_bar: AtomicBool::new(false),
progress: RwLock::new(None),
}))
}
}
@@ -134,11 +147,64 @@ impl <T: Write + Seek + Send + Sync + ClearFile> DownloaderInternal<T> {
Ok(())
}
/// Disable the progress bar
pub fn disable_progress_bar(&self) {
self.progress_bar.qstore(false);
self.progress.get_mut().take();
}
/// Enable the progress bar
/// * `style` - The style of the progress bar
/// * `mults` - The instance of [MultiProgress] if multiple progress bars are needed.
pub fn enable_progress_bar(&self, style: ProgressStyle, mults: Option<&MultiProgress>) {
let mut bar = ProgressBar::new(0).with_style(style);
match mults {
Some(bars) => {
bar = bars.add(bar);
}
None => { }
}
self.progress_bar.qstore(true);
self.progress.get_mut().replace(bar);
}
#[inline]
/// Returns true if the progress bar is enabled.
pub fn enabled_progress_bar(&self) -> bool {
self.progress_bar.qload()
}
#[inline]
/// Finishes the progress bar and sets a message
pub fn finish_progress_bar_with_message(&self, msg: impl Into<Cow<'static, str>>) {
match self.progress.get_ref().deref() {
Some(p) => { p.finish_with_message(msg) }
None => { }
}
}
#[inline]
/// Get the file name from url.
/// If not available, use `(Unknown)`
pub fn get_file_name(&self) -> String {
get_file_name_from_url(self.url.deref().clone()).unwrap_or(String::from(gettext("(Unknown)")))
}
#[inline]
/// Return the status of the downloader.
pub fn get_status(&self) -> DownloaderStatus {
self.status.get_ref().clone()
}
#[inline]
/// Advances the position of the progress bar by `delta`
pub fn inc_progress_bar(&self, delta: u64) {
match self.progress.get_ref().deref() {
Some(p) => { p.inc(delta) }
None => { }
}
}
#[inline]
/// Returns true if the downloader is created just now.
pub fn is_created(&self) -> bool {
@@ -151,6 +217,12 @@ impl <T: Write + Seek + Send + Sync + ClearFile> DownloaderInternal<T> {
*self.status.get_ref() == DownloaderStatus::Downloading
}
#[inline]
/// Returns true if the downloader is downloaded complete.
pub fn is_downloaded(&self) -> bool {
*self.status.get_ref() == DownloaderStatus::Downloaded
}
#[inline]
/// Returns true if is multiple thread mode.
pub fn is_multi_threads(&self) -> bool {
@@ -182,6 +254,33 @@ impl <T: Write + Seek + Send + Sync + ClearFile> DownloaderInternal<T> {
self.status.replace_with2(DownloaderStatus::Downloaded)
}
#[inline]
/// Sets the length of the progress bar
pub fn set_progress_bar_length(&self, length: u64) {
match self.progress.get_ref().deref() {
Some(p) => { p.set_length(length) }
None => { }
}
}
#[inline]
/// Sets the position of the progress bar
pub fn set_progress_bar_position(&self, pos: u64) {
match self.progress.get_ref().deref() {
Some(p) => { p.set_position(pos) }
None => { }
}
}
#[inline]
/// Sets the current message of the progress bar
pub fn set_progress_bar_message(&self, msg: impl Into<Cow<'static, str>>) {
match self.progress.get_ref().deref() {
Some(p) => { p.set_message(msg) }
None => { }
}
}
/// Write datas to the file.
/// * `data` - Data
pub fn write(&self, data: &[u8]) -> Result<(), DownloaderError> {
@@ -241,19 +340,31 @@ impl <T: Write + Seek + Send + Sync + ClearFile + 'static> Downloader<T> {
self.downloader.get_status()
}
/// Wait the downloader.
pub async fn join(&self) -> Result<(), DownloaderError> {
match self.task.get_mut().deref_mut() {
Some(v) => {
let re = v.await;
re.unwrap()
v.await?
}
None => { Ok(()) }
}
}
/// Disable progress bar
pub fn disable_progress_bar(&self) {
self.downloader.disable_progress_bar()
}
/// Enable the progress bar
/// * `style` - The style of the progress bar
/// * `mults` - The instance of [MultiProgress] if multiple progress bars are needed.
pub fn enable_progress_bar(&self, style: ProgressStyle, mults: Option<&MultiProgress>) {
self.downloader.enable_progress_bar(style, mults)
}
define_downloader_fn!(is_created, bool, "Returns true if the downloader is created just now.");
define_downloader_fn!(is_downloading, bool, "Returns true if the downloader is downloading now.");
define_downloader_fn!(is_multi_threads, bool, "Returns true if is multiple thread mode.");
define_downloader_fn!(is_downloaded, bool, "Returns true if the downloader is downloaded complete.");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
@@ -269,8 +380,10 @@ async fn test_downloader() {
match downloader {
DownloaderResult::Ok(v) => {
assert_eq!(v.is_created(), true);
v.disable_progress_bar();
v.download();
assert!(v.join().await.is_ok());
v.join().await.unwrap();
assert_eq!(v.is_downloaded(), true);
}
DownloaderResult::Canceled => { panic!("This should not happened.") }
}

View File

@@ -1,6 +1,7 @@
use crate::downloader::pd_file::PdFileError;
use http::status::StatusCode;
use tokio::time::error::Elapsed;
use tokio::task::JoinError;
#[derive(Debug, derive_more::From)]
pub enum DownloaderError {
@@ -10,6 +11,7 @@ pub enum DownloaderError {
String(String),
ErrorStatusCode(StatusCode),
Timeout(Elapsed),
JoinError(JoinError),
}
impl From<&str> for DownloaderError {

View File

@@ -7,8 +7,11 @@ use std::ops::Deref;
use std::path::Path;
use std::path::PathBuf;
/// A wrapper for [File], add support for clear file content.
pub struct LocalFile {
/// The file.
file: Option<File>,
/// The path of the file.
path: PathBuf,
}

View File

@@ -12,3 +12,4 @@ pub mod pd_file;
pub mod tasks;
pub use downloader::Downloader;
pub use error::DownloaderError;
pub use local_file::LocalFile;

View File

@@ -90,10 +90,16 @@ pub async fn create_download_tasks_simple<T: Seek + Write + Send + Sync + ClearF
match result.content_length() {
Some(len) => {
d.pd.set_file_size(len)?;
if d.enabled_progress_bar() {
d.set_progress_bar_length(len);
}
}
None => {}
}
}
if d.enabled_progress_bar() {
d.set_progress_bar_message(gettext("Downloading \"<loc>\".").replace("<loc>", d.get_file_name().as_str()));
}
handle_download(d, result).await
}
@@ -111,13 +117,20 @@ pub async fn handle_download<T: Seek + Write + Send + Sync + ClearFile>(d: Arc<D
match data {
Ok(data) => {
if !is_multi {
d.pd.inc(data.len() as u64)?;
let len = data.len() as u64;
d.pd.inc(len)?;
if d.enabled_progress_bar() {
d.inc_progress_bar(len);
}
}
d.write(&data)?;
}
Err(e) => {
if !is_multi {
d.pd.clear()?;
if d.enabled_progress_bar() {
d.set_progress_bar_position(0);
}
}
return Err(DownloaderError::from(e));
}
@@ -134,6 +147,9 @@ pub async fn handle_download<T: Seek + Write + Send + Sync + ClearFile>(d: Arc<D
Err(e) => {
if !is_multi {
d.pd.clear()?;
if d.enabled_progress_bar() {
d.set_progress_bar_position(0);
}
}
return Err(DownloaderError::from(e));
}

View File

@@ -45,6 +45,7 @@ pub fn ask_need_overwrite(path: &str) -> bool {
}
}
/// Get file name from url.
pub fn get_file_name_from_url<U: IntoUrl>(url: U) -> Option<String> {
let u = url.into_url();
if u.is_err() {