use std::{ collections::VecDeque, fs, io::{self, Write}, path::PathBuf, }; use log::trace; use serde::{Deserialize, Serialize}; use crate::{ cli::Cli, file::{self, Chunk}, sharry::{self, Client, Uri}, }; #[derive(Serialize, Deserialize, Debug)] enum FileState { C(file::Checked), U(file::Uploading), } impl FileState { fn start_upload( self, client: &impl sharry::Client, uri: &sharry::Uri, alias_id: &str, share_id: &str, ) -> sharry::Result { match self { FileState::C(checked) => checked.start_upload(client, uri, alias_id, share_id), FileState::U(uploading) => Ok(uploading), } } } #[derive(Serialize, Deserialize, Debug)] pub struct CacheFile { #[serde(skip)] file_name: PathBuf, uri: Uri, alias_id: String, share_id: String, files: VecDeque, } impl CacheFile { fn cache_dir() -> PathBuf { 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 } pub fn try_resume(args: &Cli) -> io::Result { 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 { file_name: Self::cache_file(args), uri: args.get_uri(), alias_id: args.alias.clone(), share_id, files: args.files.clone().into_iter().map(FileState::C).collect(), } } pub fn is_empty(&self) -> bool { self.files.is_empty() } pub fn pop_file(&mut self, client: &impl Client) -> Option { if let Some(state) = self.files.pop_front() { // HACK unwrap // TODO somehow retry Some( state .start_upload(client, &self.uri, &self.alias_id, &self.share_id) .unwrap(), ) } else { None } } pub fn push_file(&mut self, file: file::Uploading) { self.files.push_front(FileState::U(file)); } pub fn requeue_file(&mut self, file: file::Checked) { self.files.push_back(FileState::C(file)); } pub fn share_notify(&self, client: &impl Client) -> sharry::Result<()> { client.share_notify(&self.uri, &self.alias_id, &self.share_id) } pub fn file_patch(&self, client: &impl Client, chunk: &Chunk) -> sharry::Result<()> { client.file_patch(&self.uri, &self.alias_id, &self.share_id, chunk) } pub fn save(&self) -> io::Result<()> { let cache_dir = self.file_name.parent().ok_or_else(|| { io::Error::other(format!("orphan file {:?}", self.file_name.display())) })?; fs::create_dir_all(cache_dir)?; let json = serde_json::to_string_pretty(self).map_err(io::Error::other)?; let mut file = fs::File::create(&self.file_name)?; file.write_all(json.as_bytes())?; trace!("updated {:?}", self.file_name.display()); Ok(()) } pub fn clear(self) -> io::Result<()> { fs::remove_file(&self.file_name)?; trace!("removed {:?}", self.file_name.display()); Ok(()) } }