use std::{cell::RefCell, fmt, io, time::Duration}; use console::style; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, warn}; use crate::{ cachefile::CacheFile, cli::Cli, file::{Chunk, FileTrait}, sharry::{self, Client}, }; pub struct AppState { progress: RefCell>, 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: RefCell::new(None), 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(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(), &args.alias, args.get_share_request())?; Ok(Self::new(http, CacheFile::from_args(args, share_id))) } fn with_progressbar(&self, drop_bar: bool, f: impl FnOnce(&ProgressBar)) { let Some(upl) = self.inner.peek_uploading() else { return; }; let mut slot = self.progress.borrow_mut(); if slot.is_none() { let bar = ProgressBar::no_length() .with_style( ProgressStyle::with_template(&format!( concat!( "{{bar:50.cyan/blue}} {{msg:.magenta}}: ", "{{binary_bytes:.yellow}}{}{{binary_total_bytes:.yellow}} ", "({{eta}})", ), style("/").magenta(), )) .expect("style template is not valid"), ) .with_message(upl.get_name().to_owned()); bar.enable_steady_tick(Duration::from_millis(100)); *slot = Some(bar); } let bar = slot.as_ref().expect("somehow the slot holds None"); bar.set_position(upl.get_offset()); // BUG in `indicatif` crate? // `set_position` does not force an immediate redraw, so we need to call `set_length` after bar.set_length(upl.get_size()); f(bar); if drop_bar { *slot = None; } } fn touch_progressbar(&self) { self.with_progressbar(false, |_| ()); } fn next_chunk<'t>(&mut self, buffer: &'t mut [u8]) -> sharry::Result>> { if self.inner.get_uploading(&self.http)?.is_none() { return Ok(None); } self.touch_progressbar(); let uploading = self .inner .get_uploading(&self.http)? .expect("we just checked!"); debug!("{uploading:?}"); let chunk = uploading.read(buffer)?; debug!("{chunk:?}"); Ok(Some(chunk)) } fn is_done(&mut self) -> bool { if let Some(path) = self.inner.check_eof() { debug!("Finished {:?}!", path.display()); self.with_progressbar(true, ProgressBar::finish); } else if self.inner.peek_uploading().is_some() { self.touch_progressbar(); return false; } self.inner.queue_empty() } pub fn upload_chunk(&mut self, buffer: &mut [u8]) -> sharry::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)?; Ok(self.is_done()) } 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.with_progressbar(true, ProgressBar::abandon); } pub fn rebuild_share(self, args: &Cli) -> sharry::Result { let share_id = self.http .share_create(&args.get_uri(), &args.alias, args.get_share_request())?; self.with_progressbar(true, ProgressBar::abandon); Ok(Self::new(self.http, CacheFile::from_args(args, share_id))) } pub fn save(&self) -> io::Result<()> { self.inner.save() } pub fn clear(self) -> io::Result<()> { self.inner.clear() } }