From 8f261a5dacdc0a94d072652dab31dc9fc883f630 Mon Sep 17 00:00:00 2001 From: quexeky Date: Tue, 13 May 2025 14:36:47 +1000 Subject: [PATCH] chore: Add extract() function Signed-off-by: quexeky --- src-tauri/src/cloud_saves/backup_manager.rs | 51 ++++---- src-tauri/src/cloud_saves/mod.rs | 1 - src-tauri/src/cloud_saves/parse.rs | 0 src-tauri/src/cloud_saves/placeholder.rs | 37 +++--- src-tauri/src/cloud_saves/resolver.rs | 131 ++++++++++++++++---- 5 files changed, 149 insertions(+), 71 deletions(-) delete mode 100644 src-tauri/src/cloud_saves/parse.rs diff --git a/src-tauri/src/cloud_saves/backup_manager.rs b/src-tauri/src/cloud_saves/backup_manager.rs index 8c9ceb6..522126c 100644 --- a/src-tauri/src/cloud_saves/backup_manager.rs +++ b/src-tauri/src/cloud_saves/backup_manager.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; use log::warn; -use crate::{database::db::GameVersion, error::backup_error::BackupError, process::process_manager::Platform}; +use crate::{database::db::{GameVersion, DATA_ROOT_DIR}, error::backup_error::BackupError, process::process_manager::Platform}; use super::path::CommonPath; @@ -45,11 +45,11 @@ impl BackupManager<'_> { } pub trait BackupHandler: Send + Sync { - fn root_translate(&self, path: &PathBuf, game: &GameVersion) -> Result; + fn root_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result { Ok(DATA_ROOT_DIR.lock().unwrap().join("games")) } fn game_translate(&self, _path: &PathBuf, game: &GameVersion) -> Result { Ok(PathBuf::from_str(&game.game_id).unwrap()) } fn base_translate(&self, path: &PathBuf, game: &GameVersion) -> Result { Ok(self.root_translate(path, game)?.join(self.game_translate(path, game)?)) } fn home_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result { let c = CommonPath::Home.get().ok_or(BackupError::NotFound); println!("{:?}", c); c } - fn store_user_id_translate(&self, path: &PathBuf, game: &GameVersion) -> Result; + fn store_user_id_translate(&self, _path: &PathBuf, game: &GameVersion) -> Result { PathBuf::from_str(&game.game_id).map_err(|_| BackupError::ParseError) } fn os_user_name_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result { Ok(PathBuf::from_str(&whoami::username()).unwrap()) } fn win_app_data_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result { warn!("Unexpected Windows Reference in Backup "); Err(BackupError::InvalidSystem) } fn win_local_app_data_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result { warn!("Unexpected Windows Reference in Backup "); Err(BackupError::InvalidSystem) } @@ -65,33 +65,38 @@ pub trait BackupHandler: Send + Sync { pub struct LinuxBackupManager {} impl BackupHandler for LinuxBackupManager { - fn root_translate(&self, path: &PathBuf, game: &GameVersion) -> Result { - println!("Root translate"); - PathBuf::from_str("~").map_err(|_| BackupError::ParseError) + fn xdg_config_translate(&self, _path: &PathBuf,_game: &GameVersion) -> Result { + Ok(CommonPath::Data.get().ok_or(BackupError::NotFound)?) } - - fn store_user_id_translate(&self, path: &PathBuf, game: &GameVersion) -> Result { - println!("Store user id translate"); - PathBuf::from_str("ID").map_err(|_| BackupError::ParseError) + fn xdg_data_translate(&self, _path: &PathBuf,_game: &GameVersion) -> Result { + Ok(CommonPath::Config.get().ok_or(BackupError::NotFound)?) } } pub struct WindowsBackupManager {} impl BackupHandler for WindowsBackupManager { - fn root_translate(&self, path: &PathBuf, game: &GameVersion) -> Result { - todo!() + fn win_app_data_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result { + Ok(CommonPath::Config.get().ok_or(BackupError::NotFound)?) } + fn win_local_app_data_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result { + Ok(CommonPath::DataLocal.get().ok_or(BackupError::NotFound)?) + } + fn win_local_app_data_low_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result { + Ok(CommonPath::DataLocalLow.get().ok_or(BackupError::NotFound)?) + } + fn win_dir_translate(&self, _path: &PathBuf,_game: &GameVersion) -> Result { + Ok(PathBuf::from_str("C:/Windows").unwrap()) + } + fn win_documents_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result { + Ok(CommonPath::Document.get().ok_or(BackupError::NotFound)?) + + } + fn win_program_data_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result { + Ok(PathBuf::from_str("C:/ProgramData").unwrap()) + } + fn win_public_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result { + Ok(CommonPath::Public.get().ok_or(BackupError::NotFound)?) - fn store_user_id_translate(&self, path: &PathBuf, game: &GameVersion) -> Result { - todo!() } } pub struct MacBackupManager {} -impl BackupHandler for MacBackupManager { - fn root_translate(&self, path: &PathBuf, game: &GameVersion) -> Result { - todo!() - } - - fn store_user_id_translate(&self, path: &PathBuf, game: &GameVersion) -> Result { - todo!() - } -} \ No newline at end of file +impl BackupHandler for MacBackupManager {} \ No newline at end of file diff --git a/src-tauri/src/cloud_saves/mod.rs b/src-tauri/src/cloud_saves/mod.rs index 0051a40..34bf4f2 100644 --- a/src-tauri/src/cloud_saves/mod.rs +++ b/src-tauri/src/cloud_saves/mod.rs @@ -3,6 +3,5 @@ pub mod metadata; pub mod resolver; pub mod placeholder; pub mod normalise; -pub mod parse; pub mod path; pub mod backup_manager; \ No newline at end of file diff --git a/src-tauri/src/cloud_saves/parse.rs b/src-tauri/src/cloud_saves/parse.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src-tauri/src/cloud_saves/placeholder.rs b/src-tauri/src/cloud_saves/placeholder.rs index 2d8c7ab..3224565 100644 --- a/src-tauri/src/cloud_saves/placeholder.rs +++ b/src-tauri/src/cloud_saves/placeholder.rs @@ -1,7 +1,5 @@ use std::sync::LazyLock; -use crate::process::process_manager::Platform; - pub const ALL: &[&str] = &[ ROOT, GAME, @@ -33,22 +31,21 @@ pub const AVOID_WILDCARDS: &[&str] = &[ XDG_CONFIG, ]; -pub const ROOT: &str = ""; -pub const GAME: &str = ""; -pub const BASE: &str = ""; -pub const HOME: &str = ""; -pub const STORE_USER_ID: &str = ""; -pub const OS_USER_NAME: &str = ""; -pub const WIN_APP_DATA: &str = ""; -pub const WIN_LOCAL_APP_DATA: &str = ""; -pub const WIN_LOCAL_APP_DATA_LOW: &str = ""; -pub const WIN_DOCUMENTS: &str = ""; -pub const WIN_PUBLIC: &str = ""; -pub const WIN_PROGRAM_DATA: &str = ""; -pub const WIN_DIR: &str = ""; -pub const XDG_DATA: &str = ""; -pub const XDG_CONFIG: &str = ""; -pub const SKIP: &str = ""; - -pub static OS_USERNAME: LazyLock = LazyLock::new(|| whoami::username()); +pub const ROOT: &str = ""; // a directory where games are installed (configured in backup tool) +pub const GAME: &str = ""; // an installDir (if defined) or the game's canonical name in the manifest +pub const BASE: &str = ""; // shorthand for / (unless overridden by store-specific rules) +pub const HOME: &str = ""; // current user's home directory in the OS (~) +pub const STORE_USER_ID: &str = ""; // a store-specific id from the manifest, corresponding to the root's store type +pub const OS_USER_NAME: &str = ""; // current user's ID in the game store +pub const WIN_APP_DATA: &str = ""; // current user's name in the OS +pub const WIN_LOCAL_APP_DATA: &str = ""; // %APPDATA% on Windows +pub const WIN_LOCAL_APP_DATA_LOW: &str = ""; // %LOCALAPPDATA% on Windows +pub const WIN_DOCUMENTS: &str = ""; // /AppData/LocalLow on Windows +pub const WIN_PUBLIC: &str = ""; // /Documents (f.k.a. /My Documents) or a localized equivalent on Windows +pub const WIN_PROGRAM_DATA: &str = ""; // %PUBLIC% on Windows +pub const WIN_DIR: &str = ""; // %PROGRAMDATA% on Windows +pub const XDG_DATA: &str = ""; // %WINDIR% on Windows +pub const XDG_CONFIG: &str = ""; // $XDG_DATA_HOME on Linux +pub const SKIP: &str = ""; // $XDG_CONFIG_HOME on Linux +pub static OS_USERNAME: LazyLock = LazyLock::new(|| whoami::username()); \ No newline at end of file diff --git a/src-tauri/src/cloud_saves/resolver.rs b/src-tauri/src/cloud_saves/resolver.rs index 3f27975..b8bbc5f 100644 --- a/src-tauri/src/cloud_saves/resolver.rs +++ b/src-tauri/src/cloud_saves/resolver.rs @@ -1,6 +1,12 @@ -use std::{fs::File, io::Write, path::{Component, PathBuf}}; +use std::{ + fs::{create_dir_all, File}, + io::{Read, Write}, + path::PathBuf, thread::{sleep}, time::Duration, +}; -use super::{backup_manager::BackupHandler, conditions::Condition, metadata::GameFile, placeholder::*}; +use super::{ + backup_manager::BackupHandler, conditions::Condition, metadata::GameFile, placeholder::*, +}; use log::warn; use rustix::path::Arg; use tempfile::tempfile; @@ -62,6 +68,64 @@ pub fn resolve(meta: &mut CloudSaveMetadata) -> File { tarball.into_inner().unwrap().finish().unwrap() } +pub fn extract(file: PathBuf) -> Result<(), BackupError> { + let tmpdir = tempfile::tempdir().unwrap(); + + // Reopen the file for reading + let file = File::open(file).unwrap(); + + let decompressor = zstd::Decoder::new(file).unwrap(); + let mut f = tar::Archive::new(decompressor); + f.unpack(tmpdir.path()).unwrap(); + + let path = tmpdir.path(); + + let mut manifest = File::open(path.join("metadata")).unwrap(); + + let mut manifest_slice = Vec::new(); + manifest.read_to_end(&mut manifest_slice).unwrap(); + + let manifest: CloudSaveMetadata = serde_json::from_slice(&manifest_slice).unwrap(); + + for file in manifest.files { + let current_path = path.join(file.id.as_ref().unwrap()); + + let manager = BackupManager::new(); + let os = match file + .conditions + .iter() + .find_map(|p| match p { + super::conditions::Condition::Os(os) => Some(os), + _ => None, + }) + .cloned() + { + Some(os) => os, + None => { + warn!( + "File {:?} could not be replaced up because it did not provide an OS", + &file + ); + continue; + } + }; + let handler = match manager.sources.get(&(manager.current_platform, os)) { + Some(h) => *h, + None => continue, + }; + + let new_path = parse_path(file.path.into(), handler, &manifest.game_version)?; + create_dir_all(&new_path.parent().unwrap()).unwrap(); + + println!("Current path {:?} copying to {:?}", ¤t_path, &new_path); + + + std::fs::copy(current_path, new_path).unwrap(); + } + + Ok(()) +} + pub fn parse_path( path: PathBuf, backup_handler: &dyn BackupHandler, @@ -71,23 +135,25 @@ pub fn parse_path( let mut s = PathBuf::new(); for component in path.components() { match component.as_str().unwrap() { - ROOT => { s.push(backup_handler.root_translate(&path, game)?)}, - GAME => { s.push(backup_handler.game_translate(&path, game)?)}, - BASE => { s.push(backup_handler.base_translate(&path, game)?)}, - HOME => { s.push(backup_handler.home_translate(&path, game)?)}, - STORE_USER_ID => { s.push(backup_handler.store_user_id_translate(&path, game)?)}, - OS_USER_NAME => { s.push(backup_handler.os_user_name_translate(&path, game)?)}, - WIN_APP_DATA => { s.push(backup_handler.win_app_data_translate(&path, game)?)}, - WIN_LOCAL_APP_DATA => { s.push(backup_handler.win_local_app_data_translate(&path, game)?)}, - WIN_LOCAL_APP_DATA_LOW => { s.push(backup_handler.win_local_app_data_low_translate(&path, game)?)}, - WIN_DOCUMENTS => { s.push(backup_handler.win_documents_translate(&path, game)?)}, - WIN_PUBLIC => { s.push(backup_handler.win_public_translate(&path, game)?)}, - WIN_PROGRAM_DATA => { s.push(backup_handler.win_program_data_translate(&path, game)?)}, - WIN_DIR => { s.push(backup_handler.win_dir_translate(&path, game)?)}, - XDG_DATA => { s.push(backup_handler.xdg_data_translate(&path, game)?)}, - XDG_CONFIG => { s.push(backup_handler.xdg_config_translate(&path, game)?)}, - SKIP => { }, - _ => s.push(PathBuf::from(component.as_os_str())) + ROOT => s.push(backup_handler.root_translate(&path, game)?), + GAME => s.push(backup_handler.game_translate(&path, game)?), + BASE => s.push(backup_handler.base_translate(&path, game)?), + HOME => s.push(backup_handler.home_translate(&path, game)?), + STORE_USER_ID => s.push(backup_handler.store_user_id_translate(&path, game)?), + OS_USER_NAME => s.push(backup_handler.os_user_name_translate(&path, game)?), + WIN_APP_DATA => s.push(backup_handler.win_app_data_translate(&path, game)?), + WIN_LOCAL_APP_DATA => s.push(backup_handler.win_local_app_data_translate(&path, game)?), + WIN_LOCAL_APP_DATA_LOW => { + s.push(backup_handler.win_local_app_data_low_translate(&path, game)?) + } + WIN_DOCUMENTS => s.push(backup_handler.win_documents_translate(&path, game)?), + WIN_PUBLIC => s.push(backup_handler.win_public_translate(&path, game)?), + WIN_PROGRAM_DATA => s.push(backup_handler.win_program_data_translate(&path, game)?), + WIN_DIR => s.push(backup_handler.win_dir_translate(&path, game)?), + XDG_DATA => s.push(backup_handler.xdg_data_translate(&path, game)?), + XDG_CONFIG => s.push(backup_handler.xdg_config_translate(&path, game)?), + SKIP => s.push(backup_handler.skip_translate(&path, game)?), + _ => s.push(PathBuf::from(component.as_os_str())), } } @@ -97,13 +163,22 @@ pub fn parse_path( pub fn test() { let mut meta = CloudSaveMetadata { - files: vec![GameFile { - path: String::from("/favicon.png"), - id: None, - data_type: super::metadata::DataType::File, - tags: Vec::new(), - conditions: vec![Condition::Os(Platform::Linux)], - }], + files: vec![ + GameFile { + path: String::from("/favicon.png"), + id: None, + data_type: super::metadata::DataType::File, + tags: Vec::new(), + conditions: vec![Condition::Os(Platform::Linux)], + }, + GameFile { + path: String::from("/quexeky.jpg"), + id: None, + data_type: super::metadata::DataType::File, + tags: Vec::new(), + conditions: vec![Condition::Os(Platform::Linux)], + }, + ], game_version: GameVersion { game_id: String::new(), version_name: String::new(), @@ -121,5 +196,7 @@ pub fn test() { }, save_id: String::from("aaaaaaa"), }; - let file = resolve(&mut meta); + //resolve(&mut meta); + + extract("save".into()).unwrap(); }