shrupl/src/appstate.rs

225 lines
6.3 KiB
Rust
Raw Normal View History

2025-06-24 01:35:21 +00:00
use std::{fmt, io, time::Duration};
2025-06-02 23:57:17 +00:00
use indicatif::{ProgressBar, ProgressDrawTarget};
use log::{debug, info, warn};
2025-06-02 23:57:17 +00:00
use crate::{
cachefile::CacheFile,
2025-06-02 23:57:17 +00:00
cli::Cli,
error,
file::{Chunk, FileTrait},
output::new_progressbar,
sharry::Client,
2025-06-02 23:57:17 +00:00
};
pub struct AppState {
2025-06-24 01:35:21 +00:00
progress: Option<ProgressBar>,
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<Duration>) -> ureq::Agent {
ureq::Agent::config_builder()
.timeout_global(timeout)
.build()
.into()
}
2025-06-02 23:57:17 +00:00
impl AppState {
fn new(http: ureq::Agent, inner: CacheFile) -> Self {
Self {
2025-06-24 01:35:21 +00:00
progress: None,
http,
inner,
}
}
pub fn try_resume(args: &Cli) -> error::Result<Self> {
fn check_hash<'a>(file: &'a impl FileTrait<'a>, bar: &ProgressBar) -> error::Result<()> {
bar.set_message(format!("checking {:?}", file.get_name()));
match file.check_hash(|bytes| bar.inc(bytes)) {
Ok(true) => Ok(()),
Ok(false) => Err(error::Error::unknown(format!(
"Hash mismatch for file {:?}!",
file.get_name()
))),
Err(e) => Err(e.into()),
}
}
let inner = CacheFile::try_resume(args)?;
// TODO CLI switch begin
info!("Checking hashes for {inner:?}");
// BOOKMARK assumption: total file size < 2 EiB
let total_size = {
let upl_size = if let Some(upl) = inner.peek_uploading() {
upl.get_size()
} else {
0
};
upl_size + inner.queue().iter().map(|&f| f.get_size()).sum::<u64>()
};
let bar = new_progressbar();
bar.set_draw_target(ProgressDrawTarget::stderr());
bar.set_length(total_size);
bar.enable_steady_tick(Duration::from_millis(50));
if let Some(upl) = inner.peek_uploading() {
check_hash(upl, &bar)?;
}
for chk in inner.queue() {
check_hash(chk, &bar)?;
}
bar.finish_with_message("finished checking files");
// TODO CLI switch end
Ok(Self::new(new_http(args.get_timeout()), inner))
2025-06-04 21:02:35 +00:00
}
pub fn from_args(args: &Cli) -> error::Result<Self> {
// TODO CLI switch begin
let mut files = args.files.clone();
let bar = new_progressbar();
bar.set_draw_target(ProgressDrawTarget::stderr());
// BOOKMARK assumption: total file size < 2 EiB
bar.set_length(files.iter().map(FileTrait::get_size).sum());
bar.enable_steady_tick(Duration::from_millis(50));
2025-06-04 21:02:35 +00:00
for chk in &mut files {
bar.set_message(format!("hashing {:?}", chk.get_name()));
chk.hash(|bytes| bar.inc(bytes))?;
debug!("{chk:?}");
}
bar.finish_with_message("finished hashing files");
// TODO CLI switch end
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, files)))
2025-06-04 21:02:35 +00:00
}
2025-06-24 01:35:21 +00:00
fn with_progressbar(&mut self, f: impl FnOnce(&ProgressBar), drop_bar: bool) {
let bar = &*self.progress.get_or_insert_with(new_progressbar);
2025-06-24 01:11:11 +00:00
if let Some(upl) = self.inner.peek_uploading() {
if bar.length().is_none() {
bar.set_draw_target(ProgressDrawTarget::stderr());
2025-06-24 01:11:11 +00:00
bar.set_length(upl.get_size());
bar.set_message(upl.get_name().to_owned());
bar.enable_steady_tick(Duration::from_millis(100));
}
bar.set_position(upl.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);
}
f(bar);
if drop_bar {
2025-06-24 01:35:21 +00:00
self.progress = None;
}
}
2025-06-24 01:35:21 +00:00
fn touch_progressbar(&mut self) {
self.with_progressbar(|_| (), false);
}
fn drop_progressbar(&mut self, f: impl FnOnce(&ProgressBar)) {
self.with_progressbar(f, true);
}
fn next_chunk<'t>(&mut self, buffer: &'t mut [u8]) -> error::Result<Option<Chunk<'t>>> {
if self.inner.get_uploading(&self.http)?.is_none() {
return Ok(None);
}
self.touch_progressbar();
2025-06-05 17:37:35 +00:00
2025-06-24 02:05:27 +00:00
let uploading = self.inner.expect_uploading();
debug!("{uploading:?}");
let chunk = uploading.read(buffer)?;
debug!("{chunk:?}");
Ok(Some(chunk))
}
pub fn upload_chunk(&mut self, buffer: &mut [u8]) -> error::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);
};
self.inner.file_patch(&self.http, &chunk)?;
self.touch_progressbar();
if let Some(path) = self.inner.check_eof() {
debug!("Finished {:?}!", path.display());
self.drop_progressbar(ProgressBar::finish);
}
Ok(self.inner.peek_uploading().is_none() && self.inner.queue().is_empty())
2025-06-05 01:13:54 +00:00
}
2025-06-25 16:34:32 +00:00
#[must_use]
pub fn rewind_chunk(mut self) -> Option<Self> {
self.inner = self.inner.rewind_chunk()?;
Some(self)
}
pub fn abort_upload(&mut self) {
self.inner.abort_upload();
2025-06-24 01:35:21 +00:00
self.drop_progressbar(ProgressBar::abandon);
2025-06-18 19:40:34 +00:00
}
pub fn rebuild_share(self, args: &Cli) -> error::Result<Self> {
let share_id =
self.http
.share_create(&args.get_uri(), &args.alias, args.get_share_request())?;
let files = args.files.clone();
Ok(Self::new(
self.http,
CacheFile::from_args(args, share_id, files),
))
}
2025-06-04 21:02:35 +00:00
pub fn save(&self) -> io::Result<()> {
self.inner.save()
2025-06-02 23:57:17 +00:00
}
pub fn discard(self) -> io::Result<()> {
self.inner.discard()
2025-06-02 23:57:17 +00:00
}
pub fn clear_any(args: &Cli) {
CacheFile::clear_any(args);
2025-06-02 23:57:17 +00:00
}
}