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},
|
2025-06-04 13:25:00 +00:00
|
|
|
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,
|
2025-06-06 15:21:49 +00:00
|
|
|
sharry::{Alias, ChunkState, FileChecked, FileUploading, Share, SharryFile, UploadError},
|
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>,
|
2025-06-04 21:02:35 +00:00
|
|
|
|
2025-06-02 23:57:17 +00:00
|
|
|
alias: Alias,
|
|
|
|
|
share: Share,
|
2025-06-05 01:13:54 +00:00
|
|
|
files: VecDeque<FileState>,
|
2025-06-04 13:25:00 +00:00
|
|
|
}
|
|
|
|
|
|
2025-06-04 21:02:35 +00:00
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
|
enum FileState {
|
|
|
|
|
C(FileChecked),
|
|
|
|
|
U(FileUploading),
|
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: &ureq::Agent,
|
|
|
|
|
alias: &Alias,
|
|
|
|
|
share: &Share,
|
|
|
|
|
) -> io::Result<FileUploading> {
|
|
|
|
|
match self {
|
|
|
|
|
FileState::C(checked) => checked.start_upload(http, alias, share),
|
|
|
|
|
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)?;
|
2025-06-04 13:25:00 +00:00
|
|
|
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,
|
2025-06-04 21:02:35 +00:00
|
|
|
alias: state.alias,
|
|
|
|
|
share: state.share,
|
|
|
|
|
files: state.files,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.ok()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 12:09:47 +00:00
|
|
|
pub fn from_args(args: &Cli, http: &ureq::Agent) -> Result<Self, ureq::Error> {
|
2025-06-04 21:23:21 +00:00
|
|
|
let file_name = Self::cache_file(args);
|
2025-06-04 21:02:35 +00:00
|
|
|
let alias = args.get_alias();
|
|
|
|
|
|
2025-06-05 12:09:47 +00:00
|
|
|
let share = Share::create(http, &alias, 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,
|
2025-06-04 21:02:35 +00:00
|
|
|
alias,
|
|
|
|
|
share,
|
|
|
|
|
files,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 17:37:35 +00:00
|
|
|
pub fn file_names(&self) -> Vec<&str> {
|
|
|
|
|
self.files.iter().map(FileState::file_name).collect()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 01:13:54 +00:00
|
|
|
pub fn upload_chunk(
|
|
|
|
|
&mut self,
|
|
|
|
|
http: &ureq::Agent,
|
|
|
|
|
chunk_size: usize,
|
|
|
|
|
) -> Result<Option<()>, UploadError> {
|
2025-06-05 22:08:24 +00:00
|
|
|
let uploading = if let Some(state) = self.files.pop_front() {
|
|
|
|
|
state.start_upload(http, &self.alias, &self.share).unwrap() // HACK unwrap
|
|
|
|
|
} else {
|
|
|
|
|
return Ok(None);
|
2025-06-05 01:13:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
debug!("{uploading} chunk {chunk_size}");
|
|
|
|
|
|
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
|
|
|
|
2025-06-05 01:13:54 +00:00
|
|
|
match uploading.upload_chunk(http, &self.alias, chunk_size) {
|
|
|
|
|
ChunkState::Ok(upl) => {
|
2025-06-06 23:42:18 +00:00
|
|
|
bar.set_position(upl.get_offset());
|
2025-06-05 01:13:54 +00:00
|
|
|
self.files.push_front(FileState::U(upl));
|
|
|
|
|
Ok(Some(()))
|
|
|
|
|
}
|
|
|
|
|
ChunkState::Err(upl, e) => {
|
|
|
|
|
self.files.push_front(FileState::U(upl));
|
|
|
|
|
Err(e)
|
|
|
|
|
}
|
|
|
|
|
ChunkState::Finished(path) => {
|
|
|
|
|
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;
|
2025-06-05 22:08:24 +00:00
|
|
|
self.share.notify(http, &self.alias).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
|
|
|
}
|
|
|
|
|
}
|