Compare commits

...

4 commits

Author SHA1 Message Date
5b403ea129 Merge branch 'develop' into feature/rebuild_share 2025-06-24 00:23:09 +00:00
540953e4a9 notes 2025-06-24 00:18:36 +00:00
c734355ebb file names from args, not from appstate
- remove `AppState::file_names`, `CacheFile::file_names`, `FileState::file_name`
- add `Cli::file_names`
- clippy fix
2025-06-24 00:18:26 +00:00
393feec125 [wip] implement handling "missing share"
- rework `CacheFile` API
- fix `AppState` using new `CacheFile`
- fix `main` using new `AppState`
- logging enhancement
2025-06-24 00:11:24 +00:00
6 changed files with 89 additions and 118 deletions

View file

@ -53,3 +53,4 @@
- hashing - hashing
- store file hashes with all `file::*` variants - store file hashes with all `file::*` variants
- check hashes on "continue" - check hashes on "continue"
- CLI switch to skip hashing

View file

@ -1,8 +1,4 @@
use std::{ use std::{cell::RefCell, fmt, io, time::Duration};
cell::{Ref, RefCell},
fmt, io,
time::Duration,
};
use console::style; use console::style;
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
@ -11,7 +7,7 @@ use log::{debug, warn};
use crate::{ use crate::{
cachefile::CacheFile, cachefile::CacheFile,
cli::Cli, cli::Cli,
file::{self, Chunk, FileTrait}, file::{Chunk, FileTrait},
sharry::{self, Client}, sharry::{self, Client},
}; };
@ -61,10 +57,14 @@ impl AppState {
Ok(Self::new(http, CacheFile::from_args(args, share_id))) 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(); let mut slot = self.progress.borrow_mut();
if slot.is_none() { if slot.is_none() {
let bar = ProgressBar::new(uploading.get_size()) let bar = ProgressBar::no_length()
.with_style( .with_style(
ProgressStyle::with_template(&format!( ProgressStyle::with_template(&format!(
concat!( concat!(
@ -76,45 +76,44 @@ impl AppState {
)) ))
.expect("style template is not valid"), .expect("style template is not valid"),
) )
.with_position(uploading.get_offset()) .with_message(upl.get_name().to_owned());
.with_message(uploading.get_name().to_owned());
bar.enable_steady_tick(Duration::from_millis(100)); bar.enable_steady_tick(Duration::from_millis(100));
*slot = Some(bar); *slot = Some(bar);
} }
drop(slot);
Ref::map(self.progress.borrow(), |opt| { let bar = slot.as_ref().expect("somehow the slot holds None");
opt.as_ref().expect("somehow the slot holds None")
})
}
fn drop_progressbar(&self, f: impl FnOnce(&ProgressBar)) { bar.set_position(upl.get_offset());
let mut slot = self.progress.borrow_mut(); // BUG in `indicatif` crate?
if let Some(bar) = slot.as_ref() { // `set_position` does not force an immediate redraw, so we need to call `set_length` after
f(bar); bar.set_length(upl.get_size());
f(bar);
if drop_bar {
*slot = None; *slot = None;
} }
} }
fn next_chunk<'t>(&mut self, buffer: &'t mut [u8]) -> sharry::Result<Option<Chunk<'t>>> { fn touch_progressbar(&self) {
let Some(mut uploading) = self.inner.pop_uploading(&self.http)? else { self.with_progressbar(false, |_| ());
self.inner }
.share_notify(&self.http)
.unwrap_or_else(|e| warn!("Failed to notify the share: {e}"));
fn next_chunk<'t>(&mut self, buffer: &'t mut [u8]) -> sharry::Result<Option<Chunk<'t>>> {
if self.inner.get_uploading(&self.http)?.is_none() {
return Ok(None); return Ok(None);
}; }
self.touch_progressbar();
let uploading = self
.inner
.get_uploading(&self.http)?
.expect("we just checked!");
debug!("{uploading:?}"); debug!("{uploading:?}");
self.get_or_create_progressbar(&uploading); let chunk = uploading.read(buffer)?;
let chunk = {
let result = uploading.read(buffer);
self.inner.push_uploading(uploading);
result?
};
debug!("{chunk:?}"); debug!("{chunk:?}");
Ok(Some(chunk)) Ok(Some(chunk))
@ -123,13 +122,9 @@ impl AppState {
fn is_done(&mut self) -> bool { fn is_done(&mut self) -> bool {
if let Some(path) = self.inner.check_eof() { if let Some(path) = self.inner.check_eof() {
debug!("Finished {:?}!", path.display()); debug!("Finished {:?}!", path.display());
self.drop_progressbar(ProgressBar::finish); self.with_progressbar(true, ProgressBar::finish);
} else if let Some(uploading) = self.inner.peek_uploading() { } else if self.inner.peek_uploading().is_some() {
let bar = self.get_or_create_progressbar(uploading); self.touch_progressbar();
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);
return false; return false;
} }
@ -139,6 +134,10 @@ impl AppState {
pub fn upload_chunk(&mut self, buffer: &mut [u8]) -> sharry::Result<bool> { pub fn upload_chunk(&mut self, buffer: &mut [u8]) -> sharry::Result<bool> {
let Some(chunk) = self.next_chunk(buffer)? else { 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); return Ok(true);
}; };
@ -148,38 +147,14 @@ impl AppState {
} }
pub fn rewind_chunk(mut self) -> Option<Self> { pub fn rewind_chunk(mut self) -> Option<Self> {
let uploading = if let Some(state) = self.inner.pop_file() { self.inner = self.inner.rewind_chunk()?;
match state {
FileState::U(s) => s,
FileState::C(s) => {}
}
} else {
warn!("rewind_chunk called on empty queue");
return None;
};
let Some(FileState::U(uploading)) = self.inner.pop_file() else {
warn!("rewind_chunk called in invalid state");
return None;
};
self.inner.push_file(FileState::U(uploading.rewind()?));
Some(self) Some(self)
} }
pub fn requeue_file(mut self) -> Option<Self> { pub fn abort_upload(&mut self) {
let Some(uploading) = self.inner.pop_file(&self.http) else { self.inner.abort_upload();
warn!("requeue_file called on empty queue"); self.with_progressbar(true, ProgressBar::abandon);
return None;
};
let checked = uploading.abort();
self.inner.requeue_file(checked);
self.drop_progressbar(|pb| pb.abandon());
Some(self)
} }
pub fn rebuild_share(self, args: &Cli) -> sharry::Result<Self> { pub fn rebuild_share(self, args: &Cli) -> sharry::Result<Self> {
@ -187,13 +162,10 @@ impl AppState {
self.http self.http
.share_create(&args.get_uri(), &args.alias, args.get_share_request())?; .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))) Ok(Self::new(self.http, CacheFile::from_args(args, share_id)))
} }
pub fn file_names(&self) -> Vec<&str> {
self.inner.file_names()
}
pub fn save(&self) -> io::Result<()> { pub fn save(&self) -> io::Result<()> {
self.inner.save() self.inner.save()
} }

View file

@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
cli::Cli, cli::Cli,
file::{self, Chunk, FileTrait}, file::{self, Chunk},
sharry::{self, Client, Uri}, sharry::{self, Client, Uri},
}; };
@ -67,39 +67,26 @@ impl CacheFile {
} }
} }
pub fn file_names(&self) -> Vec<&str> {
let mut result = vec![];
if let Some(upl) = self.uploading.as_ref() {
result.push(upl.get_name());
}
self.files.iter().map(|chk| result.push(chk.get_name()));
result
}
pub fn queue_empty(&self) -> bool { pub fn queue_empty(&self) -> bool {
self.files.is_empty() self.files.is_empty()
} }
pub fn pop_uploading( pub fn get_uploading(
&mut self, &mut self,
client: &impl Client, client: &impl Client,
) -> sharry::Result<Option<file::Uploading>> { ) -> sharry::Result<Option<&mut file::Uploading>> {
if let Some(upl) = self.uploading.take() { if self.uploading.is_some() {
Ok(Some(upl)) Ok(self.uploading.as_mut())
} else if let Some(chk) = self.files.pop_front() { } else if let Some(chk) = self.files.pop_front() {
let upl = chk.start_upload(client, &self.uri, &self.alias_id, &self.share_id)?; let upl = chk.start_upload(client, &self.uri, &self.alias_id, &self.share_id)?;
Ok(Some(upl)) self.uploading.replace(upl);
Ok(self.uploading.as_mut())
} else { } else {
Ok(None) Ok(None)
} }
} }
pub fn push_uploading(&mut self, uploading: file::Uploading) {
self.uploading.replace(uploading);
}
pub fn peek_uploading(&self) -> Option<&file::Uploading> { pub fn peek_uploading(&self) -> Option<&file::Uploading> {
self.uploading.as_ref() self.uploading.as_ref()
} }
@ -107,7 +94,7 @@ impl CacheFile {
pub fn check_eof(&mut self) -> Option<PathBuf> { pub fn check_eof(&mut self) -> Option<PathBuf> {
if let Some(upl) = self.uploading.take() { if let Some(upl) = self.uploading.take() {
match upl.check_eof() { match upl.check_eof() {
Ok(upl) => self.push_uploading(upl), Ok(upl) => self.uploading = Some(upl),
Err(p) => return Some(p), Err(p) => return Some(p),
} }
} }
@ -115,9 +102,19 @@ impl CacheFile {
None None
} }
pub fn rewind_chunk(mut self) -> Option<Self> {
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) { pub fn abort_upload(&mut self) {
let Some(upl) = self.uploading.take() else { let Some(upl) = self.uploading.take() else {
panic!("abort called while not uploading"); panic!("abort_upload called while not uploading");
}; };
self.files.push_front(upl.abort()); self.files.push_front(upl.abort());

View file

@ -14,7 +14,7 @@ use clap::{
use log::LevelFilter; use log::LevelFilter;
use crate::{ use crate::{
file::Checked, file::{Checked, FileTrait},
sharry::{NewShareRequest, Uri}, sharry::{NewShareRequest, Uri},
}; };
@ -130,6 +130,10 @@ impl Cli {
} }
} }
pub fn file_names(&self) -> Vec<&str> {
self.files.iter().map(FileTrait::get_name).collect()
}
pub fn get_hash(&self) -> String { pub fn get_hash(&self) -> String {
let file_refs = { let file_refs = {
let mut refs: Vec<_> = self.files.iter().collect(); let mut refs: Vec<_> = self.files.iter().collect();

View file

@ -72,6 +72,8 @@ impl sharry::Client for ureq::Agent {
debug!("{res:?}"); debug!("{res:?}");
if res.success && (res.message == "Share created.") { if res.success && (res.message == "Share created.") {
trace!("new share id: {:?}", res.id);
Ok(res.id) Ok(res.id)
} else { } else {
Err(ClientError::response(format!("{res:?}"))) Err(ClientError::response(format!("{res:?}")))

View file

@ -73,7 +73,7 @@ fn main() {
info!("continuing with state: {state:#?}"); info!("continuing with state: {state:#?}");
let fns_magenta = output::style_all(&state.file_names(), |s| style(s).magenta()).join(", "); let fns_magenta = output::style_all(&args.file_names(), |s| style(s).magenta()).join(", ");
println!("{} is uploading: {fns_magenta}", *SHRUPL); println!("{} is uploading: {fns_magenta}", *SHRUPL);
@ -89,40 +89,35 @@ fn main() {
Err(e) => { Err(e) => {
Log::handle(&e); Log::handle(&e);
match e { if let ClientError::InvalidParameter(p) = e {
ClientError::InvalidParameter(p) => match p { match p {
// TODO Error 404: File not found // TODO Error 404: File not found
Parameter::FileID(fid) => { Parameter::FileID(fid) => {
// requeue file trace!("requeueing file {fid:?}");
let Some(s) = state.requeue_file() else {
Log::error("Failed to requeue file!");
};
trace!("File {fid:?} requeued"); state.abort_upload();
state = s;
} }
// TODO Error 404: Share not found // TODO Error 404: Share not found
Parameter::ShareID(sid) => { Parameter::ShareID(sid) => {
// requeue file trace!("rebuilding share {sid:?}");
// rebuild share
let Ok(s) = state.rebuild_share(&args) else { let Ok(s) = state.rebuild_share(&args) else {
Log::error("Failed to rebuild share!"); Log::error("Failed to rebuild share!");
}; };
trace!("Share {sid:?} rebuilt");
state = s; state = s;
} }
p => Log::error(format_args!("Unexpected {p}!")), p => Log::error(format_args!("Unexpected {p}!")),
},
_ => {
// retry chunk
let Some(s) = state.rewind_chunk() else {
Log::error("Failed to retry chunk!");
};
tries += 1;
trace!("State rewound, retrying last chunk (tries: {tries})");
state = s;
} }
} else {
// retry chunk
let Some(s) = state.rewind_chunk() else {
Log::error("Failed to retry chunk!");
};
tries += 1;
trace!("State rewound, retrying last chunk (tries: {tries})");
state = s;
} }
} }
Ok(false) => { Ok(false) => {