Compare commits
No commits in common. "5b403ea12997d2241e12a16278cda27d28cb0e9a" and "df055fc4e9619a241c89b4a3362bc7bf2452fbb5" have entirely different histories.
5b403ea129
...
df055fc4e9
6 changed files with 118 additions and 89 deletions
1
notes.md
1
notes.md
|
|
@ -53,4 +53,3 @@
|
|||
- hashing
|
||||
- store file hashes with all `file::*` variants
|
||||
- check hashes on "continue"
|
||||
- CLI switch to skip hashing
|
||||
|
|
|
|||
116
src/appstate.rs
116
src/appstate.rs
|
|
@ -1,4 +1,8 @@
|
|||
use std::{cell::RefCell, fmt, io, time::Duration};
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
fmt, io,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use console::style;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
|
|
@ -7,7 +11,7 @@ use log::{debug, warn};
|
|||
use crate::{
|
||||
cachefile::CacheFile,
|
||||
cli::Cli,
|
||||
file::{Chunk, FileTrait},
|
||||
file::{self, Chunk, FileTrait},
|
||||
sharry::{self, Client},
|
||||
};
|
||||
|
||||
|
|
@ -57,14 +61,10 @@ impl AppState {
|
|||
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;
|
||||
};
|
||||
|
||||
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::no_length()
|
||||
let bar = ProgressBar::new(uploading.get_size())
|
||||
.with_style(
|
||||
ProgressStyle::with_template(&format!(
|
||||
concat!(
|
||||
|
|
@ -76,44 +76,45 @@ impl AppState {
|
|||
))
|
||||
.expect("style template is not valid"),
|
||||
)
|
||||
.with_message(upl.get_name().to_owned());
|
||||
.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);
|
||||
|
||||
let bar = slot.as_ref().expect("somehow the slot holds None");
|
||||
Ref::map(self.progress.borrow(), |opt| {
|
||||
opt.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 {
|
||||
fn drop_progressbar(&self, f: impl FnOnce(&ProgressBar)) {
|
||||
let mut slot = self.progress.borrow_mut();
|
||||
if let Some(bar) = slot.as_ref() {
|
||||
f(bar);
|
||||
*slot = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn touch_progressbar(&self) {
|
||||
self.with_progressbar(false, |_| ());
|
||||
}
|
||||
|
||||
fn next_chunk<'t>(&mut self, buffer: &'t mut [u8]) -> sharry::Result<Option<Chunk<'t>>> {
|
||||
if self.inner.get_uploading(&self.http)?.is_none() {
|
||||
let Some(mut uploading) = self.inner.pop_uploading(&self.http)? else {
|
||||
self.inner
|
||||
.share_notify(&self.http)
|
||||
.unwrap_or_else(|e| warn!("Failed to notify the share: {e}"));
|
||||
|
||||
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)?;
|
||||
self.get_or_create_progressbar(&uploading);
|
||||
|
||||
let chunk = {
|
||||
let result = uploading.read(buffer);
|
||||
self.inner.push_uploading(uploading);
|
||||
|
||||
result?
|
||||
};
|
||||
debug!("{chunk:?}");
|
||||
|
||||
Ok(Some(chunk))
|
||||
|
|
@ -122,9 +123,13 @@ impl AppState {
|
|||
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();
|
||||
self.drop_progressbar(ProgressBar::finish);
|
||||
} else if let Some(uploading) = self.inner.peek_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);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -134,10 +139,6 @@ impl AppState {
|
|||
|
||||
pub fn upload_chunk(&mut self, buffer: &mut [u8]) -> sharry::Result<bool> {
|
||||
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);
|
||||
};
|
||||
|
||||
|
|
@ -147,14 +148,38 @@ impl AppState {
|
|||
}
|
||||
|
||||
pub fn rewind_chunk(mut self) -> Option<Self> {
|
||||
self.inner = self.inner.rewind_chunk()?;
|
||||
let uploading = if let Some(state) = self.inner.pop_file() {
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn abort_upload(&mut self) {
|
||||
self.inner.abort_upload();
|
||||
self.with_progressbar(true, ProgressBar::abandon);
|
||||
pub fn requeue_file(mut self) -> Option<Self> {
|
||||
let Some(uploading) = self.inner.pop_file(&self.http) else {
|
||||
warn!("requeue_file called on empty queue");
|
||||
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> {
|
||||
|
|
@ -162,10 +187,13 @@ impl AppState {
|
|||
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 file_names(&self) -> Vec<&str> {
|
||||
self.inner.file_names()
|
||||
}
|
||||
|
||||
pub fn save(&self) -> io::Result<()> {
|
||||
self.inner.save()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::{
|
||||
cli::Cli,
|
||||
file::{self, Chunk},
|
||||
file::{self, Chunk, FileTrait},
|
||||
sharry::{self, Client, Uri},
|
||||
};
|
||||
|
||||
|
|
@ -67,26 +67,39 @@ 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 {
|
||||
self.files.is_empty()
|
||||
}
|
||||
|
||||
pub fn get_uploading(
|
||||
pub fn pop_uploading(
|
||||
&mut self,
|
||||
client: &impl Client,
|
||||
) -> sharry::Result<Option<&mut file::Uploading>> {
|
||||
if self.uploading.is_some() {
|
||||
Ok(self.uploading.as_mut())
|
||||
) -> sharry::Result<Option<file::Uploading>> {
|
||||
if let Some(upl) = self.uploading.take() {
|
||||
Ok(Some(upl))
|
||||
} 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())
|
||||
Ok(Some(upl))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_uploading(&mut self, uploading: file::Uploading) {
|
||||
self.uploading.replace(uploading);
|
||||
}
|
||||
|
||||
pub fn peek_uploading(&self) -> Option<&file::Uploading> {
|
||||
self.uploading.as_ref()
|
||||
}
|
||||
|
|
@ -94,7 +107,7 @@ impl CacheFile {
|
|||
pub fn check_eof(&mut self) -> Option<PathBuf> {
|
||||
if let Some(upl) = self.uploading.take() {
|
||||
match upl.check_eof() {
|
||||
Ok(upl) => self.uploading = Some(upl),
|
||||
Ok(upl) => self.push_uploading(upl),
|
||||
Err(p) => return Some(p),
|
||||
}
|
||||
}
|
||||
|
|
@ -102,19 +115,9 @@ impl CacheFile {
|
|||
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) {
|
||||
let Some(upl) = self.uploading.take() else {
|
||||
panic!("abort_upload called while not uploading");
|
||||
panic!("abort called while not uploading");
|
||||
};
|
||||
|
||||
self.files.push_front(upl.abort());
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use clap::{
|
|||
use log::LevelFilter;
|
||||
|
||||
use crate::{
|
||||
file::{Checked, FileTrait},
|
||||
file::Checked,
|
||||
sharry::{NewShareRequest, Uri},
|
||||
};
|
||||
|
||||
|
|
@ -130,10 +130,6 @@ impl Cli {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn file_names(&self) -> Vec<&str> {
|
||||
self.files.iter().map(FileTrait::get_name).collect()
|
||||
}
|
||||
|
||||
pub fn get_hash(&self) -> String {
|
||||
let file_refs = {
|
||||
let mut refs: Vec<_> = self.files.iter().collect();
|
||||
|
|
|
|||
|
|
@ -72,8 +72,6 @@ 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:?}")))
|
||||
|
|
|
|||
39
src/main.rs
39
src/main.rs
|
|
@ -73,7 +73,7 @@ fn main() {
|
|||
|
||||
info!("continuing with state: {state:#?}");
|
||||
|
||||
let fns_magenta = output::style_all(&args.file_names(), |s| style(s).magenta()).join(", ");
|
||||
let fns_magenta = output::style_all(&state.file_names(), |s| style(s).magenta()).join(", ");
|
||||
|
||||
println!("{} is uploading: {fns_magenta}", *SHRUPL);
|
||||
|
||||
|
|
@ -89,35 +89,40 @@ fn main() {
|
|||
Err(e) => {
|
||||
Log::handle(&e);
|
||||
|
||||
if let ClientError::InvalidParameter(p) = e {
|
||||
match p {
|
||||
match e {
|
||||
ClientError::InvalidParameter(p) => match p {
|
||||
// TODO Error 404: File not found
|
||||
Parameter::FileID(fid) => {
|
||||
trace!("requeueing file {fid:?}");
|
||||
// requeue file
|
||||
let Some(s) = state.requeue_file() else {
|
||||
Log::error("Failed to requeue file!");
|
||||
};
|
||||
|
||||
state.abort_upload();
|
||||
trace!("File {fid:?} requeued");
|
||||
state = s;
|
||||
}
|
||||
// TODO Error 404: Share not found
|
||||
Parameter::ShareID(sid) => {
|
||||
trace!("rebuilding share {sid:?}");
|
||||
|
||||
// rebuild share
|
||||
// requeue file
|
||||
let Ok(s) = state.rebuild_share(&args) else {
|
||||
Log::error("Failed to rebuild share!");
|
||||
};
|
||||
|
||||
trace!("Share {sid:?} rebuilt");
|
||||
state = s;
|
||||
}
|
||||
p => Log::error(format_args!("Unexpected {p}!")),
|
||||
}
|
||||
} else {
|
||||
// retry chunk
|
||||
let Some(s) = state.rewind_chunk() else {
|
||||
Log::error("Failed to retry chunk!");
|
||||
};
|
||||
tries += 1;
|
||||
},
|
||||
_ => {
|
||||
// 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;
|
||||
trace!("State rewound, retrying last chunk (tries: {tries})");
|
||||
state = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue