Initial Commit
This commit is contained in:
commit
6cf9a81ff9
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "save-sync"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Rekai Musuka <rekai@musuka.dev>"]
|
||||||
|
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]
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "client"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Rekai Musuka <rekai@musuka.dev>"]
|
||||||
|
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"
|
|
@ -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<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Self, ProjectDirError> {
|
||||||
|
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<P: AsRef<Path>>(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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<PathBuf, ArchiveError> {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod archive;
|
||||||
|
pub mod utils;
|
|
@ -0,0 +1,35 @@
|
||||||
|
use directories::ProjectDirs;
|
||||||
|
use std::path::Path;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub fn calc_file_hash(path: &Path) -> Option<u64> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn archive_directory(source: &Path, dest: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn archive_file(source: &Path, dest: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unarchive_directory(source: &Path, dest: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unarchive_file(source: &Path, dest: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_project_dirs() -> Result<ProjectDirs, ProjectDirError> {
|
||||||
|
ProjectDirs::from("dev", "musuka", "save-sync").ok_or(ProjectDirError::HomeNotSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn get_data_dir() -> Option<PathBuf> {}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ProjectDirError {
|
||||||
|
#[error("$HOME is not set")]
|
||||||
|
HomeNotSet,
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "server"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Rekai Musuka <rekai@musuka.dev>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
|
@ -0,0 +1,7 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(2 + 2, 4);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(2 + 2, 4);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue