diff --git a/src/appstate.rs b/src/appstate.rs index 927b37e..8223df8 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -1,8 +1,4 @@ -use std::{ - cell::{Ref, RefCell}, - fmt, io, - time::Duration, -}; +use std::{cell::RefCell, fmt, io, time::Duration}; use console::style; use indicatif::{ProgressBar, ProgressStyle}; @@ -11,7 +7,7 @@ use log::{debug, warn}; use crate::{ cachefile::CacheFile, cli::Cli, - file::{self, Chunk, FileTrait}, + file::{Chunk, FileTrait}, sharry::{self, Client}, }; @@ -61,10 +57,14 @@ impl AppState { Ok(Self::new(http, CacheFile::from_args(args, share_id))) } - fn get_or_create_progressbar(&self, uploading: &file::Uploading) -> Ref<'_, ProgressBar> { + 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::new(uploading.get_size()) + let bar = ProgressBar::no_length() .with_style( ProgressStyle::with_template(&format!( concat!( @@ -76,75 +76,68 @@ impl AppState { )) .expect("style template is not valid"), ) - .with_position(uploading.get_offset()) - .with_message(uploading.get_name().to_owned()); + .with_message(upl.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") - }) - } + let bar = slot.as_ref().expect("somehow the slot holds None"); - fn end_progressbar(&self, f: impl FnOnce(&ProgressBar)) { - let mut slot = self.progress.borrow_mut(); - if let Some(bar) = slot.as_ref() { - f(bar); + 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 next_chunk<'t>(&mut self, buffer: &'t mut [u8]) -> io::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}")); + 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:?}"); - self.get_or_create_progressbar(&uploading); - - let chunk_res = uploading.read(buffer); - self.inner.push_file(uploading); - - let chunk = chunk_res?; + let chunk = uploading.read(buffer)?; debug!("{chunk:?}"); Ok(Some(chunk)) } fn is_done(&mut self) -> bool { - let Some(uploading) = self.inner.pop_file(&self.http) else { - return true; - }; + 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(); - 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 an immediate redraw, so we also call `inc_length` here - bar.inc_length(0); - drop(bar); - - self.inner.push_file(uploading); - } - Err(path) => { - debug!("Finished {:?}!", path.display()); - self.end_progressbar(ProgressBar::finish); - } + return false; } - self.inner.is_empty() + 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); }; @@ -153,30 +146,24 @@ impl AppState { Ok(self.is_done()) } - pub fn rewind(mut self) -> Option { - let Some(uploading) = self.inner.pop_file(&self.http) else { - warn!("rewind called on empty queue"); - return None; - }; - - let uploading = uploading.rewind()?; - self.inner.push_file(uploading); + pub fn rewind_chunk(mut self) -> Option { + self.inner = self.inner.rewind_chunk()?; Some(self) } - pub fn requeue_file(mut self) -> Option { - let Some(uploading) = self.inner.pop_file(&self.http) else { - warn!("requeue_file called on empty queue"); - return None; - }; + pub fn abort_upload(&mut self) { + self.inner.abort_upload(); + self.with_progressbar(true, ProgressBar::abandon); + } - let checked = uploading.abort(); - self.inner.requeue_file(checked); + 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.end_progressbar(ProgressBar::abandon); - - Some(self) + self.with_progressbar(true, ProgressBar::abandon); + Ok(Self::new(self.http, CacheFile::from_args(args, share_id))) } pub fn save(&self) -> io::Result<()> { diff --git a/src/cachefile.rs b/src/cachefile.rs index 49879f5..897dfd7 100644 --- a/src/cachefile.rs +++ b/src/cachefile.rs @@ -14,27 +14,6 @@ use crate::{ sharry::{self, Client, Uri}, }; -#[derive(Serialize, Deserialize, Debug)] -enum FileState { - C(file::Checked), - U(file::Uploading), -} - -impl FileState { - fn start_upload( - self, - client: &impl sharry::Client, - uri: &sharry::Uri, - alias_id: &str, - share_id: &str, - ) -> sharry::Result { - match self { - FileState::C(checked) => checked.start_upload(client, uri, alias_id, share_id), - FileState::U(uploading) => Ok(uploading), - } - } -} - #[derive(Serialize, Deserialize, Debug)] pub struct CacheFile { #[serde(skip)] @@ -43,7 +22,9 @@ pub struct CacheFile { uri: Uri, alias_id: String, share_id: String, - files: VecDeque, + + uploading: Option, + files: VecDeque, } impl CacheFile { @@ -81,34 +62,62 @@ impl CacheFile { uri: args.get_uri(), alias_id: args.alias.clone(), share_id, - files: args.files.clone().into_iter().map(FileState::C).collect(), + uploading: None, + files: args.files.clone().into(), } } - pub fn is_empty(&self) -> bool { + pub fn queue_empty(&self) -> bool { self.files.is_empty() } - pub fn pop_file(&mut self, client: &impl Client) -> Option { - if let Some(state) = self.files.pop_front() { - // HACK unwrap - // TODO somehow retry - Some( - state - .start_upload(client, &self.uri, &self.alias_id, &self.share_id) - .unwrap(), - ) + pub fn get_uploading( + &mut self, + client: &impl Client, + ) -> sharry::Result> { + if self.uploading.is_some() { + Ok(self.uploading.as_mut()) + } else if let Some(chk) = self.files.pop_front() { + let upl = chk.start_upload(client, &self.uri, &self.alias_id, &self.share_id)?; + self.uploading.replace(upl); + + Ok(self.uploading.as_mut()) } else { - None + Ok(None) } } - pub fn push_file(&mut self, file: file::Uploading) { - self.files.push_front(FileState::U(file)); + pub fn peek_uploading(&self) -> Option<&file::Uploading> { + self.uploading.as_ref() } - pub fn requeue_file(&mut self, file: file::Checked) { - self.files.push_back(FileState::C(file)); + pub fn check_eof(&mut self) -> Option { + if let Some(upl) = self.uploading.take() { + match upl.check_eof() { + Ok(upl) => self.uploading = Some(upl), + Err(p) => return Some(p), + } + } + + None + } + + pub fn rewind_chunk(mut self) -> Option { + let Some(upl) = self.uploading.take() else { + panic!("rewind_chunk called while not uploading"); + }; + + self.uploading = Some(upl.rewind()?); + + Some(self) + } + + pub fn abort_upload(&mut self) { + let Some(upl) = self.uploading.take() else { + panic!("abort_upload called while not uploading"); + }; + + self.files.push_front(upl.abort()); } pub fn share_notify(&self, client: &impl Client) -> sharry::Result<()> { diff --git a/src/impl_ureq.rs b/src/impl_ureq.rs index 87d4c12..d8c50fd 100644 --- a/src/impl_ureq.rs +++ b/src/impl_ureq.rs @@ -72,6 +72,8 @@ impl sharry::Client for ureq::Agent { debug!("{res:?}"); if res.success && (res.message == "Share created.") { + trace!("new share id: {:?}", res.id); + Ok(res.id) } else { Err(ClientError::response(format!("{res:?}"))) diff --git a/src/main.rs b/src/main.rs index 69481a4..8d5c73c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,31 +88,35 @@ fn main() { match state.upload_chunk(&mut buffer) { Err(e) => { Log::handle(&e); - tries += 1; - if let ClientError::InvalidParameter(p) = e { match p { - // TODO Error 404: File not found - Parameter::FileID(fid) => { - // requeue file - let Some(s) = state.requeue_file() else { - Log::error("Failed to requeue file!"); - }; + if let ClientError::InvalidParameter(p) = e { + match p { + // TODO Error 404: File not found + Parameter::FileID(fid) => { + trace!("requeueing file {fid:?}"); - trace!("File {fid:?} requeued (tried: {tries})"); - state = s; + state.abort_upload(); + } + // TODO Error 404: Share not found + Parameter::ShareID(sid) => { + trace!("rebuilding share {sid:?}"); + + // rebuild share + let Ok(s) = state.rebuild_share(&args) else { + Log::error("Failed to rebuild share!"); + }; + state = s; + } + p => Log::error(format_args!("Unexpected {p}!")), } - // TODO Error 404: Share might have been deleted - Parameter::ShareID(sid) => { - Log::error(format_args!("404 sid: {sid}")); - } - p => Log::error(format_args!("Unexpected {p}!")), - } } else { + } else { // retry chunk - let Some(s) = state.rewind() else { + let Some(s) = state.rewind_chunk() else { Log::error("Failed to retry chunk!"); }; + tries += 1; - trace!("State rewound, retrying last chunk (tried: {tries})"); + trace!("State rewound, retrying last chunk (tries: {tries})"); state = s; } }