From 32b7632258c022a3481b5478d1f8aed9899ce95e Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Mon, 1 Mar 2021 21:30:37 -0600 Subject: [PATCH 1/8] feat(db): add diesel and sqlite3 as dependencies --- .env.example | 3 ++- Cargo.toml | 2 ++ diesel.toml | 5 ++++ migrations/.gitkeep | 0 .../down.sql | 2 ++ .../up.sql | 8 +++++++ .../down.sql | 2 ++ .../up.sql | 7 ++++++ src/db.rs | 14 +++++++++++ src/lib.rs | 6 +++++ src/models.rs | 8 +++++++ src/schema.rs | 23 +++++++++++++++++++ 12 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 diesel.toml create mode 100644 migrations/.gitkeep create mode 100644 migrations/2021-03-02-022949_create_game_file_table/down.sql create mode 100644 migrations/2021-03-02-022949_create_game_file_table/up.sql create mode 100644 migrations/2021-03-02-025744_create_game_save_loc_table/down.sql create mode 100644 migrations/2021-03-02-025744_create_game_save_loc_table/up.sql create mode 100644 src/db.rs create mode 100644 src/models.rs create mode 100644 src/schema.rs diff --git a/.env.example b/.env.example index c0a1380..1f36879 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ -RUST_LOG = \ No newline at end of file +RUST_LOG= +DATABASE_URL= \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 1517063..3d642ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,5 +13,7 @@ members = ["server", "client"] uuid = { version = "^0.8", features = ["v4"] } twox-hash = "^1.6" thiserror = "^1.0" +diesel = { version = "^1.4", features = ["sqlite"] } +dotenv = "^0.15" [dev-dependencies] diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..92267c8 --- /dev/null +++ b/diesel.toml @@ -0,0 +1,5 @@ +# For documentation on how to configure this file, +# see diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" diff --git a/migrations/.gitkeep b/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/migrations/2021-03-02-022949_create_game_file_table/down.sql b/migrations/2021-03-02-022949_create_game_file_table/down.sql new file mode 100644 index 0000000..790e63a --- /dev/null +++ b/migrations/2021-03-02-022949_create_game_file_table/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE game_file \ No newline at end of file diff --git a/migrations/2021-03-02-022949_create_game_file_table/up.sql b/migrations/2021-03-02-022949_create_game_file_table/up.sql new file mode 100644 index 0000000..b4d4faa --- /dev/null +++ b/migrations/2021-03-02-022949_create_game_file_table/up.sql @@ -0,0 +1,8 @@ +-- Your SQL goes here +CREATE TABLE game_file ( + id INTEGER PRIMARY KEY, + original_path BLOB NOT NULL, -- TEXT assumes a Unicode Encoding + file_hash INTEGER NOT NULL, -- u64, but that's not + game_save_id INTEGER NOT NULL, + FOREIGN KEY (game_save_id) REFERENCES game_save_location (id) +) \ No newline at end of file diff --git a/migrations/2021-03-02-025744_create_game_save_loc_table/down.sql b/migrations/2021-03-02-025744_create_game_save_loc_table/down.sql new file mode 100644 index 0000000..503f787 --- /dev/null +++ b/migrations/2021-03-02-025744_create_game_save_loc_table/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE game_save_location \ No newline at end of file diff --git a/migrations/2021-03-02-025744_create_game_save_loc_table/up.sql b/migrations/2021-03-02-025744_create_game_save_loc_table/up.sql new file mode 100644 index 0000000..2efd290 --- /dev/null +++ b/migrations/2021-03-02-025744_create_game_save_loc_table/up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here +CREATE TABLE game_save_location ( + id INTEGER PRIMARY KEY, + friendly_name TEXT -- This can be null + original_PATH BLOB NOT NULL, + uuid BLOB NOT NULL +) \ No newline at end of file diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..c3f9a32 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,14 @@ +use diesel::prelude::*; +use diesel::sqlite::SqliteConnection; +use dotenv::dotenv; +use std::env; + +pub fn establish_connection() -> SqliteConnection { + dotenv().ok(); + + // TODO: Consider whether it is best practice to panic here + // or have establish_connection return a Result with a thiserror enum + + let db_url = env::var("DATABASE_URL").expect("$DATABASE_URL was not set"); + SqliteConnection::establish(&db_url).expect(&format!("Error connecting to {}", db_url)) +} diff --git a/src/lib.rs b/src/lib.rs index bfbe1ff..a94397d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,10 @@ +#[macro_use] +extern crate diesel; + +pub mod db; pub mod game; +pub mod models; +pub mod schema; #[cfg(test)] mod tests { diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..91ad98a --- /dev/null +++ b/src/models.rs @@ -0,0 +1,8 @@ +use std::path::PathBuf; + +#[derive(Queryable)] +pub struct GameFile { + pub id: i32, + pub original_path: PathBuf, + pub file_hash: u64, +} diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..549c02f --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,23 @@ +table! { + game_file (id) { + id -> Nullable, + original_path -> Binary, + file_hash -> Integer, + game_save_id -> Integer, + } +} + +table! { + game_save_location (id) { + id -> Nullable, + friendly_name -> Text, + uuid -> Binary, + } +} + +joinable!(game_file -> game_save_location (game_save_id)); + +allow_tables_to_appear_in_same_query!( + game_file, + game_save_location, +); From ab499bfddc1d258f4c0ebe66ddf441d168617d96 Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Mon, 1 Mar 2021 21:36:06 -0600 Subject: [PATCH 2/8] chore: add CI build config --- .drone.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .drone.yml diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..894c9fb --- /dev/null +++ b/.drone.yml @@ -0,0 +1,12 @@ +--- +kind: pipeline +type: docker +name: default +steps: +- name: cargo test + image: rust:latest + commands: + - apt-get update + - apt-get install -y libsqlite3-dev + - cargo build --verbose --all + - cargo test --verbose --all \ No newline at end of file From 5b21fac18607f8b81594d24ac3c26a65247e4157 Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Mon, 1 Mar 2021 21:38:23 -0600 Subject: [PATCH 3/8] chore: remove unnecessary commands from CI config --- .drone.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 894c9fb..94a2a4e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,7 +6,5 @@ steps: - name: cargo test image: rust:latest commands: - - apt-get update - - apt-get install -y libsqlite3-dev - cargo build --verbose --all - cargo test --verbose --all \ No newline at end of file From cf30601d9ff466efd64100d419c48bbc82a0b8ca Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Mon, 1 Mar 2021 21:49:32 -0600 Subject: [PATCH 4/8] fix(docs): ensure archive doctests pass --- client/src/archive.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/client/src/archive.rs b/client/src/archive.rs index 722673a..13d480d 100644 --- a/client/src/archive.rs +++ b/client/src/archive.rs @@ -74,8 +74,12 @@ impl Archive { /// # Examples /// ``` /// # use crate::client::archive::Archive; - /// let archive = Archive::try_default() - /// archive.track_game("/home/user/Documents/generic_company/generic_game/save_folder") + /// let mut archive = Archive::try_default().unwrap(); + /// let game_save_path = "/home/user/Documents/generic_company/generic_game/save_folder"; + /// match archive.track_game(game_save_path) { + /// Ok(_) => println!("Save Sync is now tracking {}", game_save_path), + /// Err(err) => eprintln!("Failed to track {}: {:?}", game_save_path, err) + /// }; /// ``` pub fn track_game>(&mut self, path: P) -> Result<(), GameTrackError> { let game_save_loc = self.get_game_save_files(path, None)?; @@ -150,7 +154,11 @@ impl Archive { /// let drop_res = archive.drop_game("/home/user/Documents/generic_company/generic_game/save_folder"); /// ``` pub fn drop_game>(&mut self, path: P) -> Result<(), GameDropError> { - unimplemented!() + 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 @@ -167,7 +175,13 @@ impl Archive { /// let drop_res = archive.drop_game("raging_loop"); /// ``` pub fn drop_game_with_friendly(&mut self, name: &str) -> Result<(), GameDropError> { - unimplemented!() + 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(()) } } From c42a8088782fb0031f896053ae5b9bd85c26d901 Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Mon, 1 Mar 2021 22:06:23 -0600 Subject: [PATCH 5/8] chore: remove --verbose from CI build & test config --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 94a2a4e..5b92aaa 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,5 +6,5 @@ steps: - name: cargo test image: rust:latest commands: - - cargo build --verbose --all - - cargo test --verbose --all \ No newline at end of file + - cargo build --all + - cargo test --all \ No newline at end of file From 1c666769947c3abfb3aa3fac5b16abddf8724934 Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Mon, 1 Mar 2021 22:22:06 -0600 Subject: [PATCH 6/8] chore(docs): remove unnecessary crate:: prefix crate:: prefix was being unnecessarily used when importing Archive in several doctests --- client/src/archive.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/archive.rs b/client/src/archive.rs index 13d480d..a35780a 100644 --- a/client/src/archive.rs +++ b/client/src/archive.rs @@ -20,7 +20,7 @@ impl Archive { /// /// # Examples /// ``` - /// # use crate::client::archive::Archive; + /// # use client::archive::Archive; /// let archive_res = Archive::try_default(); /// ``` pub fn try_default() -> Result { @@ -45,7 +45,7 @@ impl Archive { /// /// # Examples /// ``` - /// # use crate::client::archive::Archive; + /// # use 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 { @@ -73,7 +73,7 @@ impl Archive { /// /// # Examples /// ``` - /// # use crate::client::archive::Archive; + /// # use client::archive::Archive; /// let mut archive = Archive::try_default().unwrap(); /// let game_save_path = "/home/user/Documents/generic_company/generic_game/save_folder"; /// match archive.track_game(game_save_path) { @@ -149,7 +149,7 @@ impl Archive { /// /// # Examples /// ``` - /// # use crate::client::archive::Archive; + /// # 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"); /// ``` @@ -170,7 +170,7 @@ impl Archive { /// /// # Examples /// ``` - /// # use crate::client::archive::Archive; + /// # use client::archive::Archive; /// let mut archive = Archive::try_default().unwrap(); /// let drop_res = archive.drop_game("raging_loop"); /// ``` From 048ec310c7e0c9d39e6a9128f9937ac6a25d6808 Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Mon, 1 Mar 2021 22:23:20 -0600 Subject: [PATCH 7/8] feat(docs): document track_game_with_friendly --- client/src/archive.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/client/src/archive.rs b/client/src/archive.rs index a35780a..f365b91 100644 --- a/client/src/archive.rs +++ b/client/src/archive.rs @@ -64,7 +64,7 @@ impl Archive { } } - /// Adds a path and it's contents to the list of tracked game files + /// 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 /// @@ -75,10 +75,10 @@ impl Archive { /// ``` /// # use client::archive::Archive; /// let mut archive = Archive::try_default().unwrap(); - /// let game_save_path = "/home/user/Documents/generic_company/generic_game/save_folder"; - /// match archive.track_game(game_save_path) { - /// Ok(_) => println!("Save Sync is now tracking {}", game_save_path), - /// Err(err) => eprintln!("Failed to track {}: {:?}", game_save_path, err) + /// 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>(&mut self, path: P) -> Result<(), GameTrackError> { @@ -87,6 +87,23 @@ impl Archive { 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

(&mut self, path: P, name: &str) -> Result<(), GameTrackError> where P: AsRef, From ca934e370da68a24756812481ce486f20c8c5806 Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Mon, 1 Mar 2021 22:49:10 -0600 Subject: [PATCH 8/8] feat(docs): document public api in game.rs and db.rs --- .drone.yml | 1 + src/db.rs | 11 +++++++++++ src/game.rs | 44 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index 5b92aaa..f542cf3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,5 +6,6 @@ steps: - name: cargo test image: rust:latest commands: + - export DATABASE_URL=/tmp/save-sync.db - cargo build --all - cargo test --all \ No newline at end of file diff --git a/src/db.rs b/src/db.rs index c3f9a32..5a8476e 100644 --- a/src/db.rs +++ b/src/db.rs @@ -3,6 +3,17 @@ use diesel::sqlite::SqliteConnection; use dotenv::dotenv; use std::env; +/// Establishes a DB Connection with a Sqlite Database +/// +/// Will **panic** if: +/// * `$DATABASE_URL` is not set +/// * Save Sync fails to connect to the database at `$DATABASE_URL` +/// +/// # Examples +/// ``` +/// # use save_sync::db::establish_connection; +/// let connection = establish_connection(); +/// ``` pub fn establish_connection() -> SqliteConnection { dotenv().ok(); diff --git a/src/game.rs b/src/game.rs index f5b6687..6a37593 100644 --- a/src/game.rs +++ b/src/game.rs @@ -9,6 +9,7 @@ use uuid::Uuid; // TODO: Change this seed const XXHASH64_SEED: u64 = 1337; +/// GameSaveLocation represents a path that holds the files of a Game's saves. #[derive(Debug, Clone)] pub struct GameSaveLocation { pub friendly_name: Option, @@ -18,6 +19,21 @@ pub struct GameSaveLocation { } impl GameSaveLocation { + /// Constructs a GameSaveLocation + /// + /// # Arguments + /// * `path` - The path of the Game Save(s) + /// * `files` - A Vector containing GameFiles, which are used to track the hashes of game files + /// * `friendly_name` - A Friendly name for the Game Save + /// + /// # Examples + /// ``` + /// # use save_sync::game::{GameSaveLocation, GameFile}; + /// let path = "/home/user/Documents/some_company/some_game/saves"; + /// let files: Vec = Vec::new(); + /// let friendly_name = "Some Game".to_string(); + /// let game_save_location = GameSaveLocation::new(path, files, Some(friendly_name)); + /// ``` pub fn new

(path: P, files: Vec, friendly_name: Option) -> Self where P: AsRef, @@ -31,6 +47,10 @@ impl GameSaveLocation { } } +/// GameFile is the representation of a on-disk file inside of a GameSaveLocation +/// +/// This class keeps track of a Hash of the file, which allows Save Sync to identify when a +/// tracked file has changed #[derive(Debug, Clone)] pub struct GameFile { pub original_path: PathBuf, @@ -38,6 +58,23 @@ pub struct GameFile { } impl GameFile { + /// Constructs a new GameFile + /// + /// Will fail if: + /// * Save Sync is unable to open and read the contents of the file at `path` + /// + /// # Arguments + /// * `path` - The path of the game file + /// + /// # Examples + /// ``` + /// # use save_sync::game::GameFile; + /// let path = "/home/user/Documents/some_company/some_game/saves"; + /// match GameFile::new(path) { + /// Ok(_) => { /* Do something with the file */ } + /// Err(err) => { eprintln!("Error while attempting to calculate the hash of {}", path)} + /// }; + /// ``` pub fn new>(path: P) -> std::io::Result { let path = path.as_ref(); let file = File::open(path)?; @@ -55,6 +92,7 @@ impl GameFile { } } +/// The Error type for Interactions involving GameFiles #[derive(Error, Debug)] pub enum GameFileError { #[error(transparent)] @@ -62,20 +100,18 @@ pub enum GameFileError { } #[derive(Debug, Default)] -pub struct BackupPath { +struct BackupPath { inner: Option, } impl BackupPath { - pub fn new>(path: P) -> Self { + fn new>(path: P) -> Self { Self { inner: Some(path.as_ref().to_path_buf()), } } } -impl BackupPath {} - struct HashWriter(T); impl Write for HashWriter {