use std::{fmt, io, time::Duration}; use indicatif::{ProgressBar, ProgressDrawTarget}; use log::{debug, info, warn}; use crate::{ cachefile::CacheFile, cli::Cli, error, file::{Chunk, FileTrait}, output::new_progressbar, sharry::Client, }; pub struct AppState { progress: Option, http: ureq::Agent, inner: CacheFile, } impl fmt::Debug for AppState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("AppState") .field("inner", &self.inner) .finish_non_exhaustive() } } fn new_http(timeout: Option) -> ureq::Agent { ureq::Agent::config_builder() .timeout_global(timeout) .build() .into() } impl AppState { fn new(http: ureq::Agent, inner: CacheFile) -> Self { Self { progress: None, http, inner, } } pub fn try_resume(args: &Cli) -> error::Result { fn check_hash<'a>(file: &'a impl FileTrait<'a>, bar: &ProgressBar) -> error::Result<()> { bar.set_message(format!("checking {:?}", file.get_name())); match file.check_hash(|bytes| bar.inc(bytes)) { Ok(true) => Ok(()), Ok(false) => Err(error::Error::unknown(format!( "Hash mismatch for file {:?}!", file.get_name() ))), Err(e) => Err(e.into()), } } let inner = CacheFile::try_resume(args)?; // TODO CLI switch begin info!("Checking hashes for {inner:?}"); // BOOKMARK assumption: total file size < 2 EiB let total_size = { let upl_size = if let Some(upl) = inner.peek_uploading() { upl.get_size() } else { 0 }; upl_size + inner.queue().iter().map(|&f| f.get_size()).sum::() }; let bar = new_progressbar(); bar.set_draw_target(ProgressDrawTarget::stderr()); bar.set_length(total_size); bar.enable_steady_tick(Duration::from_millis(50)); if let Some(upl) = inner.peek_uploading() { check_hash(upl, &bar)?; } for chk in inner.queue() { check_hash(chk, &bar)?; } bar.finish_with_message("finished checking files"); // TODO CLI switch end Ok(Self::new(new_http(args.get_timeout()), inner)) } pub fn from_args(args: &Cli) -> error::Result { // TODO CLI switch begin let mut files = args.files.clone(); let bar = new_progressbar(); bar.set_draw_target(ProgressDrawTarget::stderr()); // BOOKMARK assumption: total file size < 2 EiB bar.set_length(files.iter().map(FileTrait::get_size).sum()); bar.enable_steady_tick(Duration::from_millis(50)); for chk in &mut files { bar.set_message(format!("hashing {:?}", chk.get_name())); chk.hash(|bytes| bar.inc(bytes))?; debug!("{chk:?}"); } bar.finish_with_message("finished hashing files"); // TODO CLI switch end let http = new_http(args.get_timeout()); let share_id = http.share_create(&args.get_uri(), &args.alias, args.get_share_request())?; Ok(Self::new(http, CacheFile::from_args(args, share_id, files))) } fn with_progressbar(&mut self, f: impl FnOnce(&ProgressBar), drop_bar: bool) { let bar = &*self.progress.get_or_insert_with(new_progressbar); if let Some(upl) = self.inner.peek_uploading() { if bar.length().is_none() { bar.set_draw_target(ProgressDrawTarget::stderr()); bar.set_length(upl.get_size()); bar.set_message(upl.get_name().to_owned()); bar.enable_steady_tick(Duration::from_millis(100)); } bar.set_position(upl.get_offset()); // BUG in `indicatif` crate? // `set_position` does not force an immediate redraw, so we also call `inc_length` here bar.inc_length(0); } f(bar); if drop_bar { self.progress = None; } } fn touch_progressbar(&mut self) { self.with_progressbar(|_| (), false); } fn drop_progressbar(&mut self, f: impl FnOnce(&ProgressBar)) { self.with_progressbar(f, true); } fn next_chunk<'t>(&mut self, buffer: &'t mut [u8]) -> error::Result>> { if self.inner.get_uploading(&self.http)?.is_none() { return Ok(None); } self.touch_progressbar(); let uploading = self.inner.expect_uploading(); debug!("{uploading:?}"); let chunk = uploading.read(buffer)?; debug!("{chunk:?}"); Ok(Some(chunk)) } pub fn upload_chunk(&mut self, buffer: &mut [u8]) -> error::Result { let Some(chunk) = self.next_chunk(buffer)? else { self.inner .share_notify(&self.http) .unwrap_or_else(|e| warn!("Failed to notify the share: {e}")); return Ok(true); }; self.inner.file_patch(&self.http, &chunk)?; self.touch_progressbar(); if let Some(path) = self.inner.check_eof() { debug!("Finished {:?}!", path.display()); self.drop_progressbar(ProgressBar::finish); } Ok(self.inner.peek_uploading().is_none() && self.inner.queue().is_empty()) } #[must_use] pub fn rewind_chunk(mut self) -> Option { self.inner = self.inner.rewind_chunk()?; Some(self) } pub fn abort_upload(&mut self) { self.inner.abort_upload(); self.drop_progressbar(ProgressBar::abandon); } pub fn rebuild_share(self, args: &Cli) -> error::Result { let share_id = self.http .share_create(&args.get_uri(), &args.alias, args.get_share_request())?; let files = args.files.clone(); Ok(Self::new( self.http, CacheFile::from_args(args, share_id, files), )) } pub fn save(&self) -> io::Result<()> { self.inner.save() } pub fn discard(self) -> io::Result<()> { self.inner.discard() } pub fn clear_any(args: &Cli) { CacheFile::clear_any(args); } }