diff --git a/.drone.yml b/.drone.yml index 5b92aaa..cf58d01 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,5 +6,9 @@ steps: - name: cargo test image: rust:latest commands: +<<<<<<< HEAD +======= + - export DATABASE_URL=/tmp/save-sync.db +>>>>>>> dev - cargo build --all - cargo test --all \ No newline at end of file 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/client/src/archive.rs b/client/src/archive.rs index 13d480d..699efaa 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 { @@ -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 /// @@ -73,12 +73,21 @@ impl Archive { /// /// # Examples /// ``` +<<<<<<< HEAD /// # use crate::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) +======= + /// # 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) +>>>>>>> dev /// }; /// ``` pub fn track_game>(&mut self, path: P) -> Result<(), GameTrackError> { @@ -87,6 +96,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, @@ -149,7 +175,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 +196,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"); /// ``` 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..5a8476e --- /dev/null +++ b/src/db.rs @@ -0,0 +1,25 @@ +use diesel::prelude::*; +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(); + + // 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/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 { 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, +);