use std::{ cell::{Ref, RefCell}, fmt, io, time::Duration, }; use console::style; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, warn}; use super::{ cachefile::CacheFile, cli::Cli, file::{self, FileTrait}, sharry::{self, Client, ClientError}, }; pub struct AppState { progress: RefCell>, buffer: Vec, 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(chunk_size: usize, http: ureq::Agent, inner: CacheFile) -> Self { Self { progress: None.into(), buffer: vec![0; chunk_size * 1024 * 1024], http, inner, } } pub fn try_resume(args: &Cli) -> Option { let inner = CacheFile::try_resume(args) .inspect_err(|e| debug!("could not resume from hash {:?}: {e}", args.get_hash())) .ok()?; Some(Self::new( args.chunk_size, new_http(args.get_timeout()), inner, )) } pub fn from_args(args: &Cli) -> sharry::Result { let http = new_http(args.get_timeout()); let share_id = http.share_create( &args.get_uri().endpoint("alias/upload/new"), &args.alias, args.get_share_request(), )?; Ok(Self::new( args.chunk_size, http, CacheFile::from_args(args, share_id), )) } fn get_or_create_progressbar(&self, uploading: &file::Uploading) -> Ref<'_, ProgressBar> { let mut slot = self.progress.borrow_mut(); if slot.is_none() { let bar = ProgressBar::new(uploading.get_size()) .with_style( ProgressStyle::with_template(&format!( concat!( "{{msg:.yellow}}: {{bar:50.cyan/blue}} ", "{{binary_bytes:.magenta}}{}{{binary_total_bytes:.magenta}} ", "({{eta}})", ), style("/").magenta(), )) .expect("style template is not valid"), ) .with_position(uploading.get_offset()) .with_message(uploading.get_name().to_owned()); bar.enable_steady_tick(Duration::from_millis(100)); *slot = Some(bar); } drop(slot); Ref::map(self.progress.borrow(), |opt| { opt.as_ref().expect("somehow the slot holds None") }) } fn finish_progressbar(&self) { let mut slot = self.progress.borrow_mut(); if let Some(bar) = slot.as_ref() { bar.finish(); *slot = None; } } pub fn upload_chunk(&mut self) -> sharry::Result { let Some(mut uploading) = self.inner.pop_file(&self.http) else { self.inner .share_notify(&self.http) .unwrap_or_else(|e| warn!("Failed to notify the share: {e}")); return Ok(true); }; debug!("{uploading:?}"); self.get_or_create_progressbar(&uploading); let chunk = uploading .read(&mut self.buffer) .map_err(ClientError::from)?; debug!("{chunk:?}"); self.http.file_patch( chunk.get_patch_uri(), self.inner.alias_id(), chunk.get_offset(), chunk.get_data(), )?; match uploading.check_eof() { Ok(uploading) => { let bar = self.get_or_create_progressbar(&uploading); bar.set_position(uploading.get_offset()); // BUG in `indicatif` crate? // `set_position` does not force immediate redraw, so we also call `inc_length` here bar.inc_length(0); drop(bar); self.inner.push_file(uploading); Ok(false) } Err(path) => { debug!("Finished {:?}!", path.display()); self.finish_progressbar(); Ok(self.inner.is_empty()) } } } pub fn file_names(&self) -> Vec<&str> { self.inner.file_names() } pub fn save(&self) -> io::Result<()> { self.inner.save() } pub fn clear(self) -> io::Result<()> { self.inner.clear() } }