commit 6cf9a81ff94973ba6009ab652b9984313332cca6 Author: Rekai Musuka Date: Sat Feb 27 15:51:54 2021 -0600 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..acf47a9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "save-sync" +version = "0.1.0" +authors = ["Rekai Musuka "] +edition = "2018" + + +[workspace] +members = ["server", "client"] +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[dev-dependencies] diff --git a/client/Cargo.toml b/client/Cargo.toml new file mode 100644 index 0000000..afb67a7 --- /dev/null +++ b/client/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "client" +version = "0.1.0" +authors = ["Rekai Musuka "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +save-sync = { path = ".." } +thiserror = "^1.0" +walkdir = "^2.0" +directories = "^3.0" +log = "^0.4" + +[dev-dependencies] +vfs = "0.4.0" diff --git a/client/src/archive.rs b/client/src/archive.rs new file mode 100644 index 0000000..874c2d4 --- /dev/null +++ b/client/src/archive.rs @@ -0,0 +1,234 @@ +use crate::utils::{self, ProjectDirError}; +use log::{debug, info, trace, warn}; +use std::path::{Path, PathBuf}; +use thiserror::Error; +use walkdir::WalkDir; + +#[derive(Debug, Clone)] +pub struct Archive { + data_root: PathBuf, + config_root: PathBuf, + tracked_files: Vec, +} + +impl Archive { + /// Returns a Result, potentially containing a new instance of Archive + /// + /// This method infers the proper config and data user directories, + /// so having `$HOME` unset will cause the method to fail. + /// + /// # Examples + /// ``` + /// # use crate::client::archive::Archive; + /// let archive_res = Archive::try_default(); + /// ``` + pub fn try_default() -> Result { + let root = utils::get_project_dirs()?; + let data = root.data_dir().to_path_buf(); + let config = root.config_dir().to_path_buf(); + + debug!("Created default Archive with: {:?} and {:?}", data, config); + + Ok(Self { + data_root: data, + config_root: config, + tracked_files: Vec::new(), + }) + } + + /// Returns a new instance of Archive + /// + /// # Examples + /// ``` + /// # use crate::client::archive::Archive; + /// let archive = Archive::new("/home/user/.local/share/save-sync", "/home/user/.config/save-sync"); + /// ``` + pub fn new>(data_root: P, config_root: P) -> Self { + let data_root = data_root.as_ref(); + let config_root = config_root.as_ref(); + + debug!( + "Created new Archive with: {:?} and {:?}", + data_root, config_root + ); + + Self { + data_root: data_root.to_path_buf(), + config_root: config_root.to_path_buf(), + tracked_files: Vec::new(), + } + } + + /// Adds a file to the list of tracked files + /// + /// Will fail if: + /// * `path` is not a file + /// * `path` is already tracked + /// + /// # Examples + /// ``` + /// # use crate::client::archive::Archive; + /// let mut archive = Archive::try_default().unwrap(); + /// let track_result = archive.track_file("/home/user/Documents/game/0001.sav"); + /// ``` + pub fn track_file>(&mut self, path: P) -> Result<(), ArchiveAddError> { + let path = path.as_ref(); + + if path.is_file() { + if !self.tracked_files.iter().any(|buf| buf == path) { + self.tracked_files.push(path.to_path_buf()); + trace!("Added {:?} to list of tracked files", path); + Ok(()) + } else { + warn!("{:?} is already a tracked file", path); + Err(ArchiveAddError::AlreadyTracked(path.to_path_buf())) + } + } else { + warn!("{:?} was not tracked since it is not a file", path); + Err(ArchiveAddError::InvalidFilePath(path.to_path_buf())) + } + } + + /// Recursively adds all files in a directory to the list of tracked files + /// + /// Will fail if: + /// * `path` is not a directory + /// * The recursive directory search prematurely fails + /// + pub fn track_directory>(&mut self, path: &P) -> Result<(), ArchiveAddError> { + let path = path.as_ref(); + + if path.is_dir() { + for maybe_entry in WalkDir::new(path) { + match maybe_entry { + Ok(entry) => { + if let Err(add_error) = self.track_file(entry.path()) { + match add_error { + ArchiveAddError::AlreadyTracked(_) => {} + ArchiveAddError::InvalidFilePath(_) => {} + err => return Err(err), + } + } + } + Err(err) => { + warn!("WalkDir failed while recursively scanning {:?}", path); + return Err(ArchiveAddError::WalkDirError(err)); + } + }; + } + + Ok(()) + } else { + warn!( + "{:?} is not a directory, so the contents were ignored", + path + ); + Err(ArchiveAddError::InvalidDirectoryPath(path.to_path_buf())) + } + } + + /// Removes a file from the list of tracked files + /// + /// Will fail if: + /// * The file to be removed was not tracked + /// * the path provided is not a file + /// + /// # Examples + /// ``` + /// # use crate::client::archive::Archive; + /// let mut archive = Archive::try_default().unwrap(); + /// archive.track_file("/home/user/Documents/game/0001.sav").unwrap(); + /// archive.drop_file("/home/user/Documents/game/0001.sav").unwrap(); + /// ``` + pub fn drop_file>(&mut self, path: P) -> Result<(), ArchiveDropError> { + let path = path.as_ref(); + + if path.is_file() { + match self.tracked_files.iter().position(|buf| path == buf) { + Some(index) => { + self.tracked_files.remove(index); + Ok(()) + } + None => return Err(ArchiveDropError::FileNotTracked(path.to_path_buf())), + } + } else { + Err(ArchiveDropError::InvalidFilePath(path.to_path_buf())) + } + } +} + +impl Archive { + fn add_file(path: &Path) -> Result { + // Create Local Copy of file + unimplemented!() + } +} + +#[derive(Error, Debug)] +pub enum ArchiveError { + #[error(transparent)] + IOError(#[from] std::io::Error), +} + +#[derive(Error, Debug)] +enum LocalBackupError {} + +#[derive(Error, Debug)] +pub enum ArchiveDropError { + #[error("{0:?} was not a file")] + InvalidFilePath(PathBuf), + #[error("{0:?} was not a directory")] + InvalidDirectoryPath(PathBuf), + #[error("{0:?} is not tracked by save-sync")] + FileNotTracked(PathBuf), +} + +#[derive(Error, Debug)] +pub enum ArchiveAddError { + #[error("{0:?} was not a file")] + InvalidFilePath(PathBuf), + #[error("{0:?} was not a directory")] + InvalidDirectoryPath(PathBuf), + #[error("{0:?} is already tracked")] + AlreadyTracked(PathBuf), + #[error(transparent)] + WalkDirError(#[from] walkdir::Error), +} + +#[cfg(test)] +mod tests { + use super::*; + use vfs::{MemoryFS, VfsError, VfsPath}; + + const DATA_ROOT: &str = "/home/user/.local/share/save-sync"; + const CONFIG_ROOT: &str = "/home/user/.config/save-sync"; + + #[test] + fn try_default_works() { + let will_error = std::env::var("HOME").is_err(); + let archive = Archive::try_default(); + + assert_ne!(archive.is_ok(), will_error); + } + + #[test] + fn new_works() { + let archive = Archive::new(DATA_ROOT, CONFIG_ROOT); + + assert_eq!(archive.data_root, PathBuf::from(DATA_ROOT)); + assert_eq!(archive.config_root, PathBuf::from(CONFIG_ROOT)); + } + + #[test] + fn track_file_works() {} + + fn create_test_archive(vfs_root: VfsPath) -> Archive { + let data_root = vfs_root.join(DATA_ROOT).unwrap(); + let config_root = vfs_root.join(CONFIG_ROOT).unwrap(); + data_root.create_file().unwrap(); + config_root.create_file().unwrap(); + dbg!(&data_root); + + Archive::new(data_root, config_root) + } +} diff --git a/client/src/lib.rs b/client/src/lib.rs new file mode 100644 index 0000000..e270132 --- /dev/null +++ b/client/src/lib.rs @@ -0,0 +1,2 @@ +pub mod archive; +pub mod utils; diff --git a/client/src/utils.rs b/client/src/utils.rs new file mode 100644 index 0000000..19eb211 --- /dev/null +++ b/client/src/utils.rs @@ -0,0 +1,35 @@ +use directories::ProjectDirs; +use std::path::Path; +use thiserror::Error; + +pub fn calc_file_hash(path: &Path) -> Option { + unimplemented!() +} + +pub fn archive_directory(source: &Path, dest: &Path) -> Result<(), Box> { + unimplemented!() +} + +pub fn archive_file(source: &Path, dest: &Path) -> Result<(), Box> { + unimplemented!() +} + +pub fn unarchive_directory(source: &Path, dest: &Path) -> Result<(), Box> { + unimplemented!() +} + +pub fn unarchive_file(source: &Path, dest: &Path) -> Result<(), Box> { + unimplemented!() +} + +pub fn get_project_dirs() -> Result { + ProjectDirs::from("dev", "musuka", "save-sync").ok_or(ProjectDirError::HomeNotSet) +} + +// pub fn get_data_dir() -> Option {} + +#[derive(Error, Debug)] +pub enum ProjectDirError { + #[error("$HOME is not set")] + HomeNotSet, +} diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..ec2b192 --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "server" +version = "0.1.0" +authors = ["Rekai Musuka "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/server/src/lib.rs b/server/src/lib.rs new file mode 100644 index 0000000..31e1bb2 --- /dev/null +++ b/server/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..31e1bb2 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +}