feat: Implement list info, and drop commands
Save Sync now has persistent storage. Currently, you can add new Saves, Remove them, and get info about them. The part of CRUD that is remaining is Update. Documentation needs to be written, a lot of the public API changed.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use crate::utils::{self, ProjectDirError};
|
||||
use log::{debug, info, trace, warn};
|
||||
use save_sync::db::{establish_connection, query::GameSaveQuery, Database};
|
||||
use save_sync::game::{GameFile, GameSaveLocation};
|
||||
use std::path::{Path, PathBuf};
|
||||
use thiserror::Error;
|
||||
@@ -63,7 +64,9 @@ impl Archive {
|
||||
config_root: config_root.to_path_buf(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Archive {
|
||||
/// 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
|
||||
@@ -74,7 +77,7 @@ impl Archive {
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use client::archive::Archive;
|
||||
/// let mut archive = Archive::try_default().unwrap();
|
||||
/// let mut archive = Archive::try_default().expect("Failed to create an Archive");
|
||||
/// 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),
|
||||
@@ -83,6 +86,9 @@ impl Archive {
|
||||
/// ```
|
||||
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::write_game_save(&game_save_loc);
|
||||
|
||||
self.tracked_games.push(game_save_loc);
|
||||
Ok(())
|
||||
}
|
||||
@@ -97,7 +103,7 @@ impl Archive {
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use client::archive::Archive;
|
||||
/// let mut archive = Archive::try_default().unwrap();
|
||||
/// let mut archive = Archive::try_default().expect("Failed to create an Archive");
|
||||
/// 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) {
|
||||
@@ -109,6 +115,9 @@ impl Archive {
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let game_save_loc = self.get_game_save_files(path, Some(name))?;
|
||||
|
||||
Self::write_game_save(&game_save_loc);
|
||||
|
||||
self.tracked_games.push(game_save_loc);
|
||||
Ok(())
|
||||
}
|
||||
@@ -155,50 +164,50 @@ impl Archive {
|
||||
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(())
|
||||
impl Archive {
|
||||
pub fn drop_game<P: AsRef<Path>>(path: P) -> Result<Option<()>, GameDropError> {
|
||||
let conn = establish_connection();
|
||||
let query = GameSaveQuery::Path(path.as_ref());
|
||||
Ok(Database::drop_game_save(&conn, query)?)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
});
|
||||
pub fn drop_game_with_friendly(name: &str) -> Result<Option<()>, GameDropError> {
|
||||
let conn = establish_connection();
|
||||
let query = GameSaveQuery::FriendlyName(name);
|
||||
Ok(Database::drop_game_save(&conn, query)?)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove backup copy of game save location on disk
|
||||
Ok(())
|
||||
impl Archive {
|
||||
pub fn get_game<P>(path: P) -> Result<Option<GameSaveLocation>, GameGetError>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let conn = establish_connection();
|
||||
let query = GameSaveQuery::Path(path.as_ref());
|
||||
Ok(Database::get_game_save(&conn, query)?)
|
||||
}
|
||||
|
||||
pub fn get_game_with_friendly(name: &str) -> Result<Option<GameSaveLocation>, GameGetError> {
|
||||
let conn = establish_connection();
|
||||
let query = GameSaveQuery::FriendlyName(name);
|
||||
Ok(Database::get_game_save(&conn, query)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Archive {
|
||||
pub fn get_all_games() -> Result<Option<Vec<GameSaveLocation>>, GameGetError> {
|
||||
let conn = establish_connection();
|
||||
Ok(Database::get_all_game_saves(&conn)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Archive {
|
||||
fn write_game_save(game_save_loc: &GameSaveLocation) -> Result<(), GameTrackError> {
|
||||
let conn = establish_connection();
|
||||
Ok(Database::write_game_save(&conn, &game_save_loc)?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +217,8 @@ pub enum GameTrackError {
|
||||
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?
|
||||
#[error(transparent)]
|
||||
DatabaseError(#[from] save_sync::db::DatabaseError),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -216,4 +227,12 @@ pub enum GameDropError {
|
||||
UnknownFriendlyName(String),
|
||||
#[error("Unable to find game with the path {0:?}")]
|
||||
UnknownPath(PathBuf),
|
||||
#[error(transparent)]
|
||||
DatabaseError(#[from] save_sync::db::DatabaseError),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum GameGetError {
|
||||
#[error(transparent)]
|
||||
DatabaseError(#[from] save_sync::db::DatabaseError),
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ use clap::{App, Arg, SubCommand};
|
||||
use client::archive::Archive;
|
||||
use dotenv::dotenv;
|
||||
use log::{debug, info};
|
||||
use save_sync::db::{establish_connection, Database};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
dotenv().ok();
|
||||
env_logger::init();
|
||||
@@ -32,29 +34,135 @@ fn main() {
|
||||
.help("A friendly name for a tracked file / directory"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("info")
|
||||
.arg(
|
||||
Arg::with_name("path")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.required_unless("friendly")
|
||||
.index(1)
|
||||
.help("The path of the game save"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("friendly")
|
||||
.long("friendly")
|
||||
.short("f")
|
||||
.value_name("NAME")
|
||||
.takes_value(true)
|
||||
.help("The friendly name of the game save"),
|
||||
),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("list"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("drop")
|
||||
.arg(
|
||||
Arg::with_name("path")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.required_unless("friendly")
|
||||
.index(1)
|
||||
.help("The path of the files you no longer want to track"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("friendly")
|
||||
.long("friendly")
|
||||
.short("f")
|
||||
.value_name("NAME")
|
||||
.takes_value(true)
|
||||
.help("The friendly name of the game save you no longer want tracked."),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
match m.subcommand() {
|
||||
("track", Some(sub_m)) => track_path(sub_m),
|
||||
("track", Some(sub_m)) => track_save(sub_m),
|
||||
("drop", Some(sub_m)) => drop_save(sub_m),
|
||||
("info", Some(sub_m)) => tracked_save_info(sub_m),
|
||||
("list", Some(sub_m)) => list_tracked_saves(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");
|
||||
fn track_save(matches: &ArgMatches) {
|
||||
let path_str = matches
|
||||
.value_of("path")
|
||||
.expect("The path argument was not set despite it being required");
|
||||
let mut archive = Archive::try_default().expect("Failed to create a new Archive");
|
||||
|
||||
if let Some(f_name) = matches.value_of("friendly") {
|
||||
info!("Name {} present for {:?}", f_name, path);
|
||||
info!("Name {} present for {:?}", f_name, path_str);
|
||||
archive
|
||||
.track_game_with_friendly(path, f_name)
|
||||
.track_game_with_friendly(path_str, f_name)
|
||||
.expect("Archive failed to track Game Save Location")
|
||||
} else {
|
||||
info!("No friendly name present for {:?}", path);
|
||||
info!("No friendly name present for {:?}", path_str);
|
||||
archive
|
||||
.track_game(path)
|
||||
.track_game(path_str)
|
||||
.expect("Archive failed to track Game Save Location");
|
||||
}
|
||||
|
||||
info!("Now Tracking: {:?}", path);
|
||||
info!("Now Tracking: {:?}", path_str);
|
||||
}
|
||||
|
||||
fn tracked_save_info(matches: &ArgMatches) {
|
||||
let maybe_game = if let Some(f_name) = matches.value_of("friendly") {
|
||||
Archive::get_game_with_friendly(f_name).expect("Failed to get game save info from Archive")
|
||||
} else {
|
||||
// There is guaranteed to be a path given by the user
|
||||
let path_str = matches
|
||||
.value_of("path")
|
||||
.expect("The path argument was not set despite it being required");
|
||||
Archive::get_game(path_str).expect("Failed to get game save info from Archive")
|
||||
};
|
||||
|
||||
let game = maybe_game.expect("No tracked game save found");
|
||||
|
||||
if let Some(name) = game.friendly_name {
|
||||
println!("Friendly Name: {}", name);
|
||||
} else {
|
||||
println!("Friendly Name: None");
|
||||
}
|
||||
|
||||
println!("Original Path: {:?}", game.original_path);
|
||||
println!("UUID: {:?}", game.uuid);
|
||||
println!("---\nFiles:");
|
||||
|
||||
for file in game.files {
|
||||
println!("Path: {:?}", file.original_path);
|
||||
println!("Hash: {}", file.hash);
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
fn list_tracked_saves(_matches: &ArgMatches) {
|
||||
let games = Archive::get_all_games()
|
||||
.expect("Failed to get all Games from the Archive")
|
||||
.expect("There are no tracked Games");
|
||||
|
||||
for game in games {
|
||||
if let Some(name) = game.friendly_name {
|
||||
print!("[{}] ", name);
|
||||
}
|
||||
println!("{:?}", game.original_path);
|
||||
println!("UUID: {:?}", game.uuid);
|
||||
println!("---");
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_save(matches: &ArgMatches) {
|
||||
let maybe_compromised = if let Some(name) = matches.value_of("friendly") {
|
||||
Archive::drop_game_with_friendly(name).expect("Archive failed to delete from database")
|
||||
} else {
|
||||
let path_str = matches
|
||||
.value_of("path")
|
||||
.expect("The path argument was not set despite it being required");
|
||||
|
||||
Archive::drop_game(path_str).expect("Archive failed to delete from database")
|
||||
};
|
||||
|
||||
match maybe_compromised {
|
||||
Some(()) => println!("Game successfully dropped from the list"),
|
||||
None => panic!("Database Invariant broken. Database is corrupted."),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user