save-sync/client/src/archive.rs

220 lines
7.5 KiB
Rust

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,
}
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 client::archive::Archive;
/// let archive_res = Archive::try_default();
/// ```
pub fn try_default() -> Result<Self, ProjectDirError> {
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,
})
}
/// 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
/// ```
/// # use 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 {
tracked_games: Vec::new(),
data_root: data_root.to_path_buf(),
config_root: config_root.to_path_buf(),
}
}
/// Adds a path and its contents to the list of tracked game files
///
/// 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 client::archive::Archive;
/// let mut archive = Archive::try_default().unwrap();
/// let save_path = "/home/user/Documents/generic_company/generic_game/save_folder";
/// match archive.track_game(save_path) {
/// Ok(_) => println!("Save Sync is now tracking {}", save_path),
/// Err(err) => eprintln!("Failed to track {}: {:?}", save_path, err)
/// };
/// ```
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(())
}
/// Adds a path and its contents to the list of tracked game files
///
/// TODO: Add similar note to the one in [`track_game`]
///
/// # Arguments
/// * `path` - The path which will be tracked along with any children it may have
///
/// # Examples
/// ```
/// # use client::archive::Archive;
/// let mut archive = Archive::try_default().unwrap();
/// let save_path = "/home/user/Documents/generic_company/generic_game/save_folder";
/// let friendly_name = "Generic Game";
/// match archive.track_game_with_friendly(save_path, friendly_name) {
/// Ok(_) => println!("Save Sync is now tracking {}", friendly_name),
/// Err(err) => eprintln!("Save Sync failed to start tracking {}", friendly_name)
/// };
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 entry.path().is_file() {
let game_file = GameFile::new(entry.path())?;
game_files.push(game_file);
}
// FIXME: WalkDir will also return the paths of files. DO we want to track these?
// if so, how will be do that?
}
Err(err) => {
let io_err: std::io::Error = err.into();
return Err(io_err.into());
}
}
}
} 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 {
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(path, game_files, friendly_name))
}
/// Removes a game from the list of tracked games
///
/// Will fail if:
/// * 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 client::archive::Archive;
/// let mut archive = Archive::try_default().unwrap();
/// let drop_res = archive.drop_game("/home/user/Documents/generic_company/generic_game/save_folder");
/// ```
pub fn drop_game<P: AsRef<Path>>(&mut self, path: P) -> Result<(), GameDropError> {
self.tracked_games
.retain(|game| game.original_path != path.as_ref());
// TODO: Remove backup copy of game save location on disk
Ok(())
}
/// 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 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> {
self.tracked_games.retain(|game| match &game.friendly_name {
Some(f_name) => f_name != name,
None => false,
});
// TODO: Remove backup copy of game save location on disk
Ok(())
}
}
#[derive(Error, Debug)]
pub enum GameTrackError {
#[error(transparent)]
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)]
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),
}