131 lines
3.6 KiB
Rust
131 lines
3.6 KiB
Rust
use std::fs::File;
|
|
use std::hash::Hasher;
|
|
use std::io::{Read, Write};
|
|
use std::path::{Path, PathBuf};
|
|
use thiserror::Error;
|
|
use twox_hash::XxHash64;
|
|
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<String>,
|
|
pub original_path: PathBuf,
|
|
files: Vec<GameFile>,
|
|
uuid: Uuid,
|
|
}
|
|
|
|
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<GameFile> = Vec::new();
|
|
/// let friendly_name = "Some Game".to_string();
|
|
/// let game_save_location = GameSaveLocation::new(path, files, Some(friendly_name));
|
|
/// ```
|
|
pub fn new<P>(path: P, files: Vec<GameFile>, friendly_name: Option<String>) -> Self
|
|
where
|
|
P: AsRef<Path>,
|
|
{
|
|
Self {
|
|
friendly_name,
|
|
original_path: path.as_ref().to_path_buf(),
|
|
files,
|
|
uuid: Uuid::new_v4(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
pub hash: u64,
|
|
}
|
|
|
|
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<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
|
|
let path = path.as_ref();
|
|
let file = File::open(path)?;
|
|
|
|
Ok(Self {
|
|
original_path: path.to_path_buf(),
|
|
hash: Self::calculate_hash(file)?,
|
|
})
|
|
}
|
|
|
|
fn calculate_hash(mut buf: impl Read) -> std::io::Result<u64> {
|
|
let mut hash_writer = HashWriter(XxHash64::with_seed(XXHASH64_SEED));
|
|
std::io::copy(&mut buf, &mut hash_writer)?;
|
|
Ok(hash_writer.0.finish())
|
|
}
|
|
}
|
|
|
|
/// The Error type for Interactions involving GameFiles
|
|
#[derive(Error, Debug)]
|
|
pub enum GameFileError {
|
|
#[error(transparent)]
|
|
IoError(#[from] std::io::Error),
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct BackupPath {
|
|
inner: Option<PathBuf>,
|
|
}
|
|
|
|
impl BackupPath {
|
|
fn new<P: AsRef<Path>>(path: P) -> Self {
|
|
Self {
|
|
inner: Some(path.as_ref().to_path_buf()),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct HashWriter<T: Hasher>(T);
|
|
|
|
impl<T: Hasher> Write for HashWriter<T> {
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
self.0.write(buf);
|
|
Ok(buf.len())
|
|
}
|
|
|
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
|
|
self.write(buf).map(|_| ())
|
|
}
|
|
}
|