shrupl/src/appstate.rs

223 lines
6.4 KiB
Rust
Raw Normal View History

2025-06-02 23:57:17 +00:00
use std::{
2025-06-05 01:13:54 +00:00
collections::VecDeque,
2025-06-02 23:57:17 +00:00
fs,
io::{self, Write},
path::{Path, PathBuf},
2025-06-05 21:36:41 +00:00
time::Duration,
2025-06-02 23:57:17 +00:00
};
2025-06-05 17:37:35 +00:00
use console::style;
use indicatif::{ProgressBar, ProgressStyle};
2025-06-05 01:44:39 +00:00
use log::{debug, trace};
2025-06-02 23:57:17 +00:00
use serde::{Deserialize, Serialize};
use super::{
cli::Cli,
file::{self, FileTrait},
sharry::{self, Client, ClientError, Uri},
2025-06-02 23:57:17 +00:00
};
#[derive(Serialize, Deserialize, Debug)]
pub struct AppState {
2025-06-04 21:02:35 +00:00
#[serde(skip)]
file_name: PathBuf,
2025-06-05 17:37:35 +00:00
#[serde(skip)]
progress: Option<ProgressBar>,
#[serde(skip)]
buffer: Vec<u8>,
2025-06-04 21:02:35 +00:00
uri: Uri,
alias_id: String,
share_id: String,
2025-06-05 01:13:54 +00:00
files: VecDeque<FileState>,
}
2025-06-04 21:02:35 +00:00
#[derive(Serialize, Deserialize, Debug)]
enum FileState {
C(file::Checked),
U(file::Uploading),
2025-06-02 23:57:17 +00:00
}
2025-06-05 17:37:35 +00:00
impl FileState {
fn file_name(&self) -> &str {
match self {
2025-06-06 23:42:18 +00:00
FileState::C(checked) => checked.get_name(),
FileState::U(uploading) => uploading.get_name(),
2025-06-05 17:37:35 +00:00
}
}
2025-06-05 22:08:24 +00:00
fn start_upload(
self,
http: &impl Client,
uri: &Uri,
alias_id: &str,
share_id: &str,
) -> sharry::Result<file::Uploading> {
2025-06-05 22:08:24 +00:00
match self {
FileState::C(checked) => {
let endpoint = &uri.endpoint(format!("alias/upload/{}/files/tus", share_id));
checked.start_upload(http, endpoint, alias_id)
}
2025-06-05 22:08:24 +00:00
FileState::U(uploading) => Ok(uploading),
}
}
2025-06-05 17:37:35 +00:00
}
2025-06-02 23:57:17 +00:00
impl AppState {
2025-06-04 21:23:21 +00:00
fn cache_dir() -> PathBuf {
let dir_name = dirs_next::cache_dir()
.expect("could not determine cache directory")
.join("shrupl");
2025-06-04 21:02:35 +00:00
2025-06-05 01:45:25 +00:00
trace!("cachedir: {:?}", dir_name.display());
2025-06-04 21:23:21 +00:00
dir_name
2025-06-04 21:02:35 +00:00
}
2025-06-04 21:23:21 +00:00
fn cache_file(args: &Cli) -> PathBuf {
let file_name = Self::cache_dir().join(format!("{}.json", args.get_hash()));
2025-06-04 21:02:35 +00:00
2025-06-05 01:45:25 +00:00
trace!("cachefile: {:?}", file_name.display());
2025-06-04 21:23:21 +00:00
file_name
2025-06-04 21:02:35 +00:00
}
2025-06-02 23:57:17 +00:00
fn load(file_name: impl AsRef<Path>) -> io::Result<Self> {
let content = fs::read_to_string(file_name)?;
serde_json::from_str(&content).map_err(io::Error::other)
2025-06-02 23:57:17 +00:00
}
2025-06-04 21:02:35 +00:00
pub fn try_resume(args: &Cli) -> Option<Self> {
2025-06-04 21:23:21 +00:00
let file_name = Self::cache_file(args);
2025-06-04 21:02:35 +00:00
Self::load(&file_name)
2025-06-05 01:45:25 +00:00
.inspect_err(|e| debug!("could not resume from {:?}: {e}", file_name.display()))
2025-06-04 21:02:35 +00:00
.map(|state| {
debug!("successfully loaded AppState");
Self {
file_name,
2025-06-05 17:37:35 +00:00
progress: None,
buffer: Vec::with_capacity(args.chunk_size),
uri: state.uri,
alias_id: state.alias_id,
share_id: state.share_id,
2025-06-04 21:02:35 +00:00
files: state.files,
}
})
.ok()
}
pub fn from_args(args: &Cli, http: &impl Client) -> sharry::Result<Self> {
2025-06-04 21:23:21 +00:00
let file_name = Self::cache_file(args);
2025-06-04 21:02:35 +00:00
let uri = args.get_uri();
let alias_id = args.alias.clone();
let share_id = http.share_create(
&uri.endpoint("alias/upload/new"),
&alias_id,
args.get_share_request(),
)?;
2025-06-04 21:02:35 +00:00
2025-06-05 01:13:54 +00:00
let files: VecDeque<_> = args.files.clone().into_iter().map(FileState::C).collect();
2025-06-04 21:02:35 +00:00
2025-06-05 01:55:30 +00:00
Ok(Self {
2025-06-04 21:02:35 +00:00
file_name,
2025-06-05 17:37:35 +00:00
progress: None,
buffer: Vec::with_capacity(args.chunk_size),
uri,
alias_id,
share_id,
2025-06-04 21:02:35 +00:00
files,
})
}
2025-06-05 17:37:35 +00:00
pub fn file_names(&self) -> Vec<&str> {
self.files.iter().map(FileState::file_name).collect()
}
pub fn upload_chunk(&mut self, http: &impl Client) -> sharry::Result<Option<()>> {
let mut uploading = if let Some(state) = self.files.pop_front() {
state
.start_upload(http, &self.uri, &self.alias_id, &self.share_id)
.unwrap() // HACK unwrap
2025-06-05 22:08:24 +00:00
} else {
return Ok(None);
2025-06-05 01:13:54 +00:00
};
debug!("{uploading} chunk {}", self.buffer.len());
2025-06-05 01:13:54 +00:00
2025-06-05 21:36:41 +00:00
// Initialize or fetch the existing ProgressBar in one call:
let bar = &*self.progress.get_or_insert_with(|| {
// Create a new bar with style
2025-06-06 23:42:18 +00:00
let bar = ProgressBar::new(uploading.get_size())
2025-06-05 21:36:41 +00:00
.with_style(
ProgressStyle::with_template(&format!(
concat!(
"{{msg:.yellow}}: {{bar:50.cyan/blue}} ",
"{{binary_bytes:.magenta}}{}{{binary_total_bytes:.magenta}} ",
"({{eta}})",
),
style("/").magenta(),
))
.unwrap(),
)
2025-06-06 23:42:18 +00:00
.with_message(uploading.get_name().to_owned())
.with_position(uploading.get_offset());
2025-06-05 21:36:41 +00:00
bar.enable_steady_tick(Duration::from_millis(100));
bar
});
2025-06-05 17:37:35 +00:00
let chunk = uploading
.read(&mut self.buffer)
.map_err(ClientError::req_err)?;
if chunk.get_length() == 0 {
return Err(ClientError::req_err("wtf"));
}
http.file_patch(
chunk.get_patch_uri(),
&self.alias_id,
chunk.get_offset(),
chunk.get_data(),
)?;
match uploading.check_eof() {
Ok(uploading) => {
bar.set_position(uploading.get_offset());
self.files.push_front(FileState::U(uploading));
2025-06-05 01:13:54 +00:00
Ok(Some(()))
}
Err(path) => {
2025-06-05 01:13:54 +00:00
debug!("Finished {:?}!", path.display());
2025-06-05 21:36:41 +00:00
bar.finish();
2025-06-05 17:37:35 +00:00
self.progress = None;
let endpoint = self
.uri
.endpoint(format!("alias/mail/notify/{}", self.share_id));
http.share_notify(&endpoint, &self.alias_id).unwrap(); // HACK unwrap
2025-06-05 11:20:27 +00:00
Ok(self.files.front().map(drop))
2025-06-05 01:13:54 +00:00
}
}
}
2025-06-04 21:02:35 +00:00
pub fn save(&self) -> io::Result<()> {
2025-06-04 21:23:21 +00:00
fs::create_dir_all(Self::cache_dir())?;
2025-06-04 21:02:35 +00:00
2025-06-02 23:57:17 +00:00
let json = serde_json::to_string_pretty(self).map_err(io::Error::other)?;
2025-06-04 21:02:35 +00:00
let mut file = fs::File::create(&self.file_name)?;
2025-06-02 23:57:17 +00:00
file.write_all(json.as_bytes())?;
2025-06-05 11:20:27 +00:00
trace!("updated {:?}", self.file_name.display());
2025-06-02 23:57:17 +00:00
Ok(())
}
2025-06-04 21:23:21 +00:00
pub fn clear(self) -> io::Result<()> {
2025-06-05 11:20:27 +00:00
fs::remove_file(&self.file_name)?;
2025-06-02 23:57:17 +00:00
2025-06-05 11:20:27 +00:00
trace!("removed {:?}", self.file_name.display());
2025-06-04 21:02:35 +00:00
Ok(())
2025-06-02 23:57:17 +00:00
}
}