Compare commits

...

2 commits

Author SHA1 Message Date
2edc690331 [wip] impl Client for ureq::Agent
- clippy fix
2025-06-12 16:25:15 +00:00
ed10f269c8 [wip] impl Client for ureq::Agent
- wip: factor out `SavedState`
2025-06-12 16:24:17 +00:00
3 changed files with 70 additions and 85 deletions

View file

@ -1,53 +1,33 @@
use std::{fmt, io, path::PathBuf, time::Duration}; use std::{fmt, io, time::Duration};
use console::style; use console::style;
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, trace}; use log::debug;
use serde::{Deserialize, Serialize};
use super::{ use super::{
cachefile::CacheFile,
cli::Cli, cli::Cli,
file::FileTrait, file::FileTrait,
savedstate::SavedState,
sharry::{self, Client, ClientError}, sharry::{self, Client, ClientError},
}; };
#[derive(Serialize, Deserialize)]
pub struct AppState { pub struct AppState {
#[serde(skip)]
progress: Option<ProgressBar>, progress: Option<ProgressBar>,
#[serde(skip)]
buffer: Vec<u8>, buffer: Vec<u8>,
inner: SavedState, inner: CacheFile,
} }
impl fmt::Debug for AppState { impl fmt::Debug for AppState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AppState") f.debug_struct("AppState")
.field("inner", &self.inner) .field("inner", &self.inner)
.finish() .finish_non_exhaustive()
} }
} }
impl AppState { impl AppState {
fn cache_dir() -> PathBuf { fn new(chunk_size: usize, inner: CacheFile) -> Self {
let dir_name = dirs_next::cache_dir()
.expect("could not determine cache directory")
.join("shrupl");
trace!("cachedir: {:?}", dir_name.display());
dir_name
}
fn cache_file(args: &Cli) -> PathBuf {
let file_name = Self::cache_dir().join(format!("{}.json", args.get_hash()));
trace!("cachefile: {:?}", file_name.display());
file_name
}
fn new(chunk_size: usize, inner: SavedState) -> Self {
Self { Self {
progress: None, progress: None,
buffer: vec![0; chunk_size * 1024 * 1024], buffer: vec![0; chunk_size * 1024 * 1024],
@ -56,38 +36,26 @@ impl AppState {
} }
pub fn try_resume(args: &Cli) -> Option<Self> { pub fn try_resume(args: &Cli) -> Option<Self> {
let file_name = Self::cache_file(args); let inner = CacheFile::try_resume(args)
let inner = SavedState::load(&file_name) .inspect_err(|e| debug!("could not resume from hash {:?}: {e}", args.get_hash()))
.inspect_err(|e| debug!("could not resume from {:?}: {e}", file_name.display()))
.ok()?; .ok()?;
Some(Self::new(args.chunk_size, inner)) Some(Self::new(args.chunk_size, inner))
} }
pub fn from_args(args: &Cli, http: &impl Client) -> sharry::Result<Self> { pub fn from_args(args: &Cli, http: &impl Client) -> sharry::Result<Self> {
let uri = args.get_uri();
let share_id = http.share_create( let share_id = http.share_create(
&uri.endpoint("alias/upload/new"), &args.get_uri().endpoint("alias/upload/new"),
&args.alias, &args.alias,
args.get_share_request(), args.get_share_request(),
)?; )?;
Ok(Self::new( Ok(Self::new(
args.chunk_size, args.chunk_size,
SavedState::new( CacheFile::from_args(args, share_id),
Self::cache_file(&args),
uri,
args.alias.clone(),
share_id,
&args.files,
),
)) ))
} }
pub fn file_names(&self) -> Vec<&str> {
self.inner.file_names()
}
pub fn upload_chunk(&mut self, http: &impl Client) -> sharry::Result<Option<()>> { pub fn upload_chunk(&mut self, http: &impl Client) -> sharry::Result<Option<()>> {
let Some(mut uploading) = self.inner.pop_file(http) else { let Some(mut uploading) = self.inner.pop_file(http) else {
return Ok(None); return Ok(None);
@ -126,7 +94,7 @@ impl AppState {
http.file_patch( http.file_patch(
chunk.get_patch_uri(), chunk.get_patch_uri(),
&self.alias_id, self.inner.alias_id(),
chunk.get_offset(), chunk.get_offset(),
chunk.get_data(), chunk.get_data(),
)?; )?;
@ -142,16 +110,17 @@ impl AppState {
bar.finish(); bar.finish();
self.progress = None; self.progress = None;
let endpoint = self self.inner.share_notify(http).unwrap(); // HACK unwrap
.uri
.endpoint(format!("alias/mail/notify/{}", self.share_id));
http.share_notify(&endpoint, &self.alias_id).unwrap(); // HACK unwrap
Ok(self.inner.has_file().then_some(())) Ok(self.inner.has_file().then_some(()))
} }
} }
} }
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

@ -2,13 +2,14 @@ use std::{
collections::VecDeque, collections::VecDeque,
fs, fs,
io::{self, Write}, io::{self, Write},
path::{Path, PathBuf}, path::PathBuf,
}; };
use log::trace; use log::trace;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{ use super::{
cli::Cli,
file::{self, FileTrait}, file::{self, FileTrait},
sharry::{self, Client, Uri}, sharry::{self, Client, Uri},
}; };
@ -30,22 +31,18 @@ impl FileState {
fn start_upload( fn start_upload(
self, self,
http: &impl Client, http: &impl Client,
uri: &Uri, endpoint: &str,
alias_id: &str, alias_id: &str,
share_id: &str,
) -> sharry::Result<file::Uploading> { ) -> sharry::Result<file::Uploading> {
match self { match self {
FileState::C(checked) => { FileState::C(checked) => checked.start_upload(http, endpoint, alias_id),
let endpoint = &uri.endpoint(format!("alias/upload/{share_id}/files/tus"));
checked.start_upload(http, endpoint, alias_id)
}
FileState::U(uploading) => Ok(uploading), FileState::U(uploading) => Ok(uploading),
} }
} }
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct SavedState { pub struct CacheFile {
#[serde(skip)] #[serde(skip)]
file_name: PathBuf, file_name: PathBuf,
@ -55,35 +52,47 @@ pub struct SavedState {
files: VecDeque<FileState>, files: VecDeque<FileState>,
} }
impl SavedState { impl CacheFile {
pub fn new( fn cache_dir() -> PathBuf {
file_name: PathBuf, let dir_name = dirs_next::cache_dir()
uri: Uri, .expect("could not determine cache directory")
alias_id: String, .join("shrupl");
share_id: String,
files: &Vec<file::Checked>, trace!("cachedir: {:?}", dir_name.display());
) -> Self { dir_name
}
fn cache_file(args: &Cli) -> PathBuf {
let file_name = Self::cache_dir().join(format!("{}.json", args.get_hash()));
trace!("cachefile: {:?}", file_name.display());
file_name
}
pub fn try_resume(args: &Cli) -> io::Result<Self> {
let file_name = Self::cache_file(args);
let state: Self = {
let file = fs::File::open(&file_name)?;
let reader = io::BufReader::new(file);
serde_json::from_reader(reader).map_err(io::Error::other)?
};
Ok(Self { file_name, ..state })
}
pub fn from_args(args: &Cli, share_id: String) -> Self {
Self { Self {
file_name, file_name: Self::cache_file(args),
uri, uri: args.get_uri(),
alias_id, alias_id: args.alias.clone(),
share_id, share_id,
files: files.clone().into_iter().map(FileState::C).collect(), files: args.files.clone().into_iter().map(FileState::C).collect(),
} }
} }
pub fn load(file_name: &Path) -> io::Result<Self> { pub fn alias_id(&self) -> &str {
let file = fs::File::open(file_name)?; &self.alias_id
let state: Self =
serde_json::from_reader(io::BufReader::new(file)).map_err(io::Error::other)?;
Ok(Self {
file_name: file_name.to_owned(),
uri: state.uri,
alias_id: state.alias_id,
share_id: state.share_id,
files: state.files,
})
} }
pub fn file_names(&self) -> Vec<&str> { pub fn file_names(&self) -> Vec<&str> {
@ -96,11 +105,10 @@ impl SavedState {
pub fn pop_file(&mut self, http: &impl Client) -> Option<file::Uploading> { pub fn pop_file(&mut self, http: &impl Client) -> Option<file::Uploading> {
if let Some(state) = self.files.pop_front() { if let Some(state) = self.files.pop_front() {
Some( let endpoint = self
state .uri
.start_upload(http, &self.uri, &self.alias_id, &self.share_id) .endpoint(format!("alias/upload/{}/files/tus", self.share_id));
.unwrap(), Some(state.start_upload(http, &endpoint, &self.alias_id).unwrap()) // HACK unwrap
) // HACK unwrap
} else { } else {
None None
} }
@ -110,6 +118,14 @@ impl SavedState {
self.files.push_front(FileState::U(file)); self.files.push_front(FileState::U(file));
} }
pub fn share_notify(&self, http: &impl Client) -> sharry::Result<()> {
let endpoint = self
.uri
.endpoint(format!("alias/mail/notify/{}", self.share_id));
http.share_notify(&endpoint, &self.alias_id)
}
pub fn save(&self) -> io::Result<()> { pub fn save(&self) -> io::Result<()> {
let cache_dir = self.file_name.parent().ok_or_else(|| { let cache_dir = self.file_name.parent().ok_or_else(|| {
io::Error::other(format!("orphan file {:?}", self.file_name.display())) io::Error::other(format!("orphan file {:?}", self.file_name.display()))

View file

@ -1,7 +1,7 @@
mod appstate; mod appstate;
mod cachefile;
mod cli; mod cli;
mod file; mod file;
mod savedstate;
mod sharry; mod sharry;
use std::{ use std::{