chore: work on project structure
This commit is contained in:
@@ -12,6 +12,8 @@ thiserror = "^1.0"
|
||||
walkdir = "^2.0"
|
||||
directories = "^3.0"
|
||||
log = "^0.4"
|
||||
clap = "^2.33"
|
||||
env_logger = "^0.8"
|
||||
dotenv = "^0.15"
|
||||
|
||||
[dev-dependencies]
|
||||
vfs = "0.4.0"
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use crate::utils::{self, ProjectDirError};
|
||||
use log::{debug, info, trace, warn};
|
||||
use save_sync::game::{GameFile, GameSaveLocation};
|
||||
use std::path::{Path, PathBuf};
|
||||
use thiserror::Error;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Archive {
|
||||
tracked_games: Vec<GameSaveLocation>,
|
||||
data_root: PathBuf,
|
||||
config_root: PathBuf,
|
||||
tracked_files: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Archive {
|
||||
@@ -23,20 +24,24 @@ impl 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();
|
||||
let user = utils::get_project_dirs()?;
|
||||
let data = user.data_dir().to_path_buf();
|
||||
let config = user.config_dir().to_path_buf();
|
||||
|
||||
debug!("Created default Archive with: {:?} and {:?}", data, config);
|
||||
|
||||
Ok(Self {
|
||||
tracked_games: Vec::new(),
|
||||
data_root: data,
|
||||
config_root: config,
|
||||
tracked_files: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a new instance of Archive
|
||||
/// Creates a new instance of Archive
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data_root` - Path to the user application data folder
|
||||
/// * `config_root` - Path to the user configuration data folder.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
@@ -53,149 +58,126 @@ impl Archive {
|
||||
);
|
||||
|
||||
Self {
|
||||
tracked_games: Vec::new(),
|
||||
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
|
||||
/// Adds a path and it's contents to the list of tracked game files
|
||||
///
|
||||
/// Will fail if:
|
||||
/// * `path` is not a file
|
||||
/// * `path` is already tracked
|
||||
/// TODO: Add note here about how GameSaveLocation, GameFile and tracking individual files rather than directories work
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `path` - The path which will be tracked along with any children it may have
|
||||
///
|
||||
/// # 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");
|
||||
/// let archive = Archive::try_default()
|
||||
/// archive.track_game("/home/user/Documents/generic_company/generic_game/save_folder")
|
||||
/// ```
|
||||
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()))
|
||||
}
|
||||
pub fn track_game<P: AsRef<Path>>(&mut self, path: P) -> Result<(), GameTrackError> {
|
||||
let game_save_loc = self.get_game_save_files(path, None)?;
|
||||
self.tracked_games.push(game_save_loc);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
pub fn track_game_with_friendly<P>(&mut self, path: P, name: &str) -> Result<(), GameTrackError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let game_save_loc = self.get_game_save_files(path, Some(name))?;
|
||||
self.tracked_games.push(game_save_loc);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_game_save_files<P>(
|
||||
&mut self,
|
||||
path: P,
|
||||
friendly_name: Option<&str>,
|
||||
) -> Result<GameSaveLocation, GameTrackError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
use GameTrackError::*;
|
||||
|
||||
let path = path.as_ref();
|
||||
let mut game_files: Vec<GameFile> = Vec::new();
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
let game_file = GameFile::new(entry.path())?;
|
||||
game_files.push(game_file);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("WalkDir failed while recursively scanning {:?}", path);
|
||||
return Err(ArchiveAddError::WalkDirError(err));
|
||||
let io_err: std::io::Error = err.into();
|
||||
return Err(io_err.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else if path.is_file() {
|
||||
// We've been requested to track an individual file.
|
||||
todo!("Implement the ability to track a single file instead of a directory")
|
||||
} else {
|
||||
warn!(
|
||||
"{:?} is not a directory, so the contents were ignored",
|
||||
path
|
||||
);
|
||||
Err(ArchiveAddError::InvalidDirectoryPath(path.to_path_buf()))
|
||||
return Err(UnknownFileSystemObject(path.to_path_buf()));
|
||||
}
|
||||
|
||||
// FIXME: There most likely is a function that does this (check clippy)
|
||||
let friendly_name = friendly_name.map(|s| s.to_owned());
|
||||
Ok(GameSaveLocation::new(game_files, friendly_name))
|
||||
}
|
||||
|
||||
/// Removes a file from the list of tracked files
|
||||
/// Removes a game from the list of traked games
|
||||
///
|
||||
/// Will fail if:
|
||||
/// * The file to be removed was not tracked
|
||||
/// * the path provided is not a file
|
||||
/// * The path provided isn't associated with any game
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `path` - The path of the tracked game save location which will be dropped
|
||||
///
|
||||
/// # 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();
|
||||
/// let drop_res = archive.drop_game("/home/user/Documents/generic_company/generic_game/save_folder");
|
||||
/// ```
|
||||
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()))
|
||||
}
|
||||
pub fn drop_game<P: AsRef<Path>>(&mut self, path: P) -> Result<(), GameDropError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Archive {
|
||||
fn add_file(path: &Path) -> Result<PathBuf, ArchiveError> {
|
||||
// Create Local Copy of file
|
||||
/// Removes a game from the list of tracked games using the game's friendly name
|
||||
///
|
||||
/// Otherwise, is identical to [`Archive::drop_game`]
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `name` - The friendly name of the tracked game save location which will be dropped
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use crate::client::archive::Archive;
|
||||
/// let mut archive = Archive::try_default().unwrap();
|
||||
/// let drop_res = archive.drop_game("raging_loop");
|
||||
/// ```
|
||||
pub fn drop_game_with_friendly(&mut self, name: &str) -> Result<(), GameDropError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ArchiveError {
|
||||
pub enum GameTrackError {
|
||||
#[error(transparent)]
|
||||
IOError(#[from] std::io::Error),
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("{0:?} is not a supported inode type (File System Object)")]
|
||||
UnknownFileSystemObject(PathBuf), // FIXME: Is there a better name for this?
|
||||
}
|
||||
|
||||
#[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::*;
|
||||
pub enum GameDropError {
|
||||
#[error("Unable to find Game with the name {0}")]
|
||||
UnknownFriendlyName(String),
|
||||
#[error("Unable to find game with the path {0:?}")]
|
||||
UnknownPath(PathBuf),
|
||||
}
|
||||
|
||||
60
client/src/main.rs
Normal file
60
client/src/main.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use clap::{crate_authors, crate_description, crate_version, ArgMatches};
|
||||
use clap::{App, Arg, SubCommand};
|
||||
use client::archive::Archive;
|
||||
use dotenv::dotenv;
|
||||
use log::{debug, info};
|
||||
use std::path::Path;
|
||||
fn main() {
|
||||
dotenv().ok();
|
||||
env_logger::init();
|
||||
|
||||
let app = App::new("Save Sync")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.about(crate_description!());
|
||||
let m = app
|
||||
.subcommand(
|
||||
SubCommand::with_name("track")
|
||||
.arg(
|
||||
Arg::with_name("path")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.index(1)
|
||||
.help("The file / directory which will be tracked"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("friendly")
|
||||
.short("f")
|
||||
.long("friendly")
|
||||
.value_name("NAME")
|
||||
.takes_value(true)
|
||||
.help("A friendly name for a tracked file / directory"),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
match m.subcommand() {
|
||||
("track", Some(sub_m)) => track_path(sub_m),
|
||||
_ => eprintln!("No valid subcommand / argument provided"),
|
||||
}
|
||||
}
|
||||
|
||||
fn track_path(matches: &ArgMatches) {
|
||||
let path = Path::new(matches.value_of("path").unwrap());
|
||||
let mut archive = Archive::try_default().expect("Failed to create an Archive struct");
|
||||
|
||||
if let Some(f_name) = matches.value_of("friendly") {
|
||||
info!("Name {} present for {:?}", f_name, path);
|
||||
archive
|
||||
.track_game_with_friendly(path, f_name)
|
||||
.expect("Archive failed to track Game Save Location")
|
||||
} else {
|
||||
info!("No friendly name present for {:?}", path);
|
||||
archive
|
||||
.track_game(path)
|
||||
.expect("Archive failed to track Game Save Location");
|
||||
}
|
||||
|
||||
info!("Now Tracking: {:?}", path);
|
||||
}
|
||||
Reference in New Issue
Block a user