shrupl/src/appstate.rs

176 lines
4.8 KiB
Rust

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<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()
}
impl AppState {
fn new(http: ureq::Agent, inner: CacheFile) -> Self {
Self {
progress: RefCell::new(None),
http,
inner,
}
}
pub fn try_resume(args: &Cli) -> Option<Self> {
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<Self> {
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<Option<Chunk<'t>>> {
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<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)?;
Ok(self.is_done())
}
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();
self.with_progressbar(true, ProgressBar::abandon);
}
pub fn rebuild_share(self, args: &Cli) -> sharry::Result<Self> {
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()
}
}