cinc 0.1.0

Cloud sync replacement for games it
Documentation
use std::path::Path;

use chrono::Utc;
use filesystem::FilesystemStore;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use webdav::WebDavStore;

use crate::config::{BackendInfo, BackendTy, WebDavInfo};

pub mod filesystem;
pub mod webdav;

#[derive(Debug, Error)]
pub enum BackendError {
    #[error(transparent)]
    Io(#[from] std::io::Error),

    #[error(transparent)]
    FromUtf8(#[from] std::string::FromUtf8Error),

    #[error(transparent)]
    ChronoParse(#[from] chrono::ParseError),

    #[error(transparent)]
    Json(#[from] serde_json::Error),

    #[error(transparent)]
    Reqwuest(#[from] reqwest::Error),
}
type Result<T, E = BackendError> = std::result::Result<T, E>;

pub const SYNC_TIME_FILE: &str = "mod-meta.json";

#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct ModifiedMetadata {
    pub last_write_timestamp: chrono::DateTime<Utc>,
    pub last_write_hostname: String,
}

impl ModifiedMetadata {
    pub fn from_sys_info() -> Self {
        let last_write_timestamp = chrono::Local::now().to_utc();
        let last_write_hostname = gethostname::gethostname()
            .to_str()
            .expect("failed to convert hostname to string")
            .to_owned();
        Self {
            last_write_timestamp,
            last_write_hostname,
        }
    }
}

pub trait StorageBackend {
    fn write_file(&mut self, at: &Path, bytes: &[u8]) -> Result<()>;
    fn read_file(&self, at: &Path) -> Result<Vec<u8>>;
    fn exists(&self, f: &Path) -> Result<bool>;

    fn read_file_str(&self, at: &Path) -> Result<String> {
        Ok(String::from_utf8(self.read_file(at)?)?)
    }
    fn read_sync_time(&self) -> Result<Option<ModifiedMetadata>> {
        let sync_time_file = Path::new(SYNC_TIME_FILE);
        if !self.exists(sync_time_file)? {
            return Ok(None);
        }
        let f = self.read_file(sync_time_file)?;
        Ok(Some(serde_json::from_slice(&f)?))
    }

    fn write_sync_time(&mut self, metadata: &ModifiedMetadata) -> Result<()> {
        let data = serde_json::to_vec(metadata)?;
        self.write_file(Path::new(SYNC_TIME_FILE), &data)
    }
}
impl StorageBackend for Box<dyn StorageBackend> {
    fn write_file(&mut self, at: &Path, bytes: &[u8]) -> Result<()> {
        self.as_mut().write_file(at, bytes)
    }

    fn read_file(&self, at: &Path) -> Result<Vec<u8>> {
        self.as_ref().read_file(at)
    }

    fn exists(&self, f: &Path) -> Result<bool> {
        self.as_ref().exists(f)
    }
}

impl BackendInfo {
    pub fn to_backend(&self, game_name: &str) -> Result<Box<dyn StorageBackend>> {
        Ok(match &self.info {
            BackendTy::Filesystem { root } => Box::new(FilesystemStore::new(root.join(game_name))?),
            BackendTy::WebDav(web_dav_info) => Box::new(WebDavStore::new(WebDavInfo {
                root: web_dav_info.root.join(game_name),
                ..web_dav_info.to_owned()
            })),
        })
    }
}