mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-12 07:42:44 +10:00
Compare commits
9 Commits
v0.3.0-rc-
...
49-bug-bro
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f75bedcae | |||
| 86bce1c68d | |||
| 924e4e334c | |||
| 065eb2356a | |||
| 689e9ad890 | |||
| 7c35ed73aa | |||
| 8f261a5dac | |||
| 67b6f2aa2e | |||
| 11e2b3fe8a |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -51,7 +51,7 @@ jobs:
|
||||
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libgtk2.0-dev libsoup3.0-dev
|
||||
# webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2.
|
||||
# You can remove the one that doesn't apply to your app to speed up the workflow a bit.
|
||||
|
||||
|
||||
69
.github/workflows/test.yml
vendored
Normal file
69
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
name: 'test'
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
|
||||
# This can be used to automatically publish nightlies at UTC nighttime
|
||||
# schedule:
|
||||
# - cron: "0 2 * * *" # run at 2 AM UTC
|
||||
|
||||
# This workflow will trigger on each push to the `release` branch to create or update a GitHub release, build your app, and upload the artifacts to the release.
|
||||
|
||||
jobs:
|
||||
publish-tauri:
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: 'macos-latest' # for Arm based macs (M1 and above).
|
||||
args: '--target aarch64-apple-darwin'
|
||||
- platform: 'macos-latest' # for Intel based macs.
|
||||
args: '--target x86_64-apple-darwin'
|
||||
- platform: 'ubuntu-24.04' # for Tauri v1 you could replace this with ubuntu-20.04.
|
||||
args: ''
|
||||
- platform: 'ubuntu-24.04-arm'
|
||||
args: '--target aarch64-unknown-linux-gnu'
|
||||
- platform: 'windows-latest'
|
||||
args: ''
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: install Rust nightly
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
|
||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libgtk2.0-dev libsoup3.0-dev
|
||||
# webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2.
|
||||
# You can remove the one that doesn't apply to your app to speed up the workflow a bit.
|
||||
|
||||
- name: install frontend dependencies
|
||||
run: yarn install # change this to npm, pnpm or bun depending on which one you use.
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
|
||||
releaseName: 'Auto testing release'
|
||||
releaseBody: 'See the assets to download this version and install. This release was created automatically.'
|
||||
releaseDraft: false
|
||||
prerelease: true
|
||||
args: ${{ matrix.args }}
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "drop-app",
|
||||
"private": true,
|
||||
"version": "0.3.0-rc-2",
|
||||
"version": "0.3.0-rc-3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
|
||||
582
src-tauri/Cargo.lock
generated
582
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "drop-app"
|
||||
version = "0.3.0-rc-2"
|
||||
version = "0.3.0-rc-3"
|
||||
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
||||
authors = ["Drop OSS"]
|
||||
edition = "2021"
|
||||
@ -27,7 +27,6 @@ tauri-plugin-shell = "2.2.1"
|
||||
serde_json = "1"
|
||||
serde-binary = "0.5.0"
|
||||
rayon = "1.10.0"
|
||||
directories = "5.0.1"
|
||||
webbrowser = "1.0.2"
|
||||
url = "2.5.2"
|
||||
tauri-plugin-deep-link = "2"
|
||||
@ -55,6 +54,18 @@ reqwest-middleware-cache = "0.1.1"
|
||||
deranged = "=0.4.0"
|
||||
droplet-rs = "0.7.3"
|
||||
gethostname = "1.0.1"
|
||||
zstd = "0.13.3"
|
||||
tar = "0.4.44"
|
||||
rand = "0.9.1"
|
||||
regex = "1.11.1"
|
||||
tempfile = "3.19.1"
|
||||
schemars = "0.8.22"
|
||||
sha1 = "0.10.6"
|
||||
dirs = "6.0.0"
|
||||
whoami = "1.6.0"
|
||||
filetime = "0.2.25"
|
||||
walkdir = "2.5.0"
|
||||
known-folders = "1.2.0"
|
||||
native_model = { version = "0.6.1", features = ["rmp_serde_1_3"] }
|
||||
# tailscale = { path = "./tailscale" }
|
||||
|
||||
@ -80,11 +91,7 @@ features = ["fs"]
|
||||
|
||||
[dependencies.uuid]
|
||||
version = "1.10.0"
|
||||
features = [
|
||||
"v4", # Lets you generate random UUIDs
|
||||
"fast-rng", # Use a faster (but still sufficiently random) RNG
|
||||
"macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
|
||||
]
|
||||
features = ["v4", "fast-rng", "macro-diagnostics"]
|
||||
|
||||
[dependencies.rustbreak]
|
||||
version = "2"
|
||||
|
||||
102
src-tauri/src/cloud_saves/backup_manager.rs
Normal file
102
src-tauri/src/cloud_saves/backup_manager.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use std::{collections::HashMap, path::PathBuf, str::FromStr};
|
||||
|
||||
use log::warn;
|
||||
|
||||
use crate::{database::db::{GameVersion, DATA_ROOT_DIR}, error::backup_error::BackupError, process::process_manager::Platform};
|
||||
|
||||
use super::path::CommonPath;
|
||||
|
||||
pub struct BackupManager<'a> {
|
||||
pub current_platform: Platform,
|
||||
pub sources: HashMap<(Platform, Platform), &'a (dyn BackupHandler + Sync + Send)>,
|
||||
}
|
||||
|
||||
impl BackupManager<'_> {
|
||||
pub fn new() -> Self {
|
||||
BackupManager {
|
||||
#[cfg(target_os = "windows")]
|
||||
current_platform: Platform::Windows,
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
current_platform: Platform::MacOs,
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
current_platform: Platform::Linux,
|
||||
|
||||
sources: HashMap::from([
|
||||
// Current platform to target platform
|
||||
(
|
||||
(Platform::Windows, Platform::Windows),
|
||||
&WindowsBackupManager {} as &(dyn BackupHandler + Sync + Send),
|
||||
),
|
||||
(
|
||||
(Platform::Linux, Platform::Linux),
|
||||
&LinuxBackupManager {} as &(dyn BackupHandler + Sync + Send),
|
||||
),
|
||||
(
|
||||
(Platform::MacOs, Platform::MacOs),
|
||||
&MacBackupManager {} as &(dyn BackupHandler + Sync + Send),
|
||||
),
|
||||
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub trait BackupHandler: Send + Sync {
|
||||
fn root_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> { Ok(DATA_ROOT_DIR.lock().unwrap().join("games")) }
|
||||
fn game_translate(&self, _path: &PathBuf, game: &GameVersion) -> Result<PathBuf, BackupError> { Ok(PathBuf::from_str(&game.game_id).unwrap()) }
|
||||
fn base_translate(&self, path: &PathBuf, game: &GameVersion) -> Result<PathBuf, BackupError> { Ok(self.root_translate(path, game)?.join(self.game_translate(path, game)?)) }
|
||||
fn home_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> { let c = CommonPath::Home.get().ok_or(BackupError::NotFound); println!("{:?}", c); c }
|
||||
fn store_user_id_translate(&self, _path: &PathBuf, game: &GameVersion) -> Result<PathBuf, BackupError> { PathBuf::from_str(&game.game_id).map_err(|_| BackupError::ParseError) }
|
||||
fn os_user_name_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> { Ok(PathBuf::from_str(&whoami::username()).unwrap()) }
|
||||
fn win_app_data_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> { warn!("Unexpected Windows Reference in Backup <winAppData>"); Err(BackupError::InvalidSystem) }
|
||||
fn win_local_app_data_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> { warn!("Unexpected Windows Reference in Backup <winLocalAppData>"); Err(BackupError::InvalidSystem) }
|
||||
fn win_local_app_data_low_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> { warn!("Unexpected Windows Reference in Backup <winLocalAppDataLow>"); Err(BackupError::InvalidSystem) }
|
||||
fn win_documents_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> { warn!("Unexpected Windows Reference in Backup <winDocuments>"); Err(BackupError::InvalidSystem) }
|
||||
fn win_public_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> { warn!("Unexpected Windows Reference in Backup <winPublic>"); Err(BackupError::InvalidSystem) }
|
||||
fn win_program_data_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> { warn!("Unexpected Windows Reference in Backup <winProgramData>"); Err(BackupError::InvalidSystem) }
|
||||
fn win_dir_translate(&self, _path: &PathBuf,_game: &GameVersion) -> Result<PathBuf, BackupError> { warn!("Unexpected Windows Reference in Backup <winDir>"); Err(BackupError::InvalidSystem) }
|
||||
fn xdg_data_translate(&self, _path: &PathBuf,_game: &GameVersion) -> Result<PathBuf, BackupError> { warn!("Unexpected XDG Reference in Backup <xdgData>"); Err(BackupError::InvalidSystem) }
|
||||
fn xdg_config_translate(&self, _path: &PathBuf,_game: &GameVersion) -> Result<PathBuf, BackupError> { warn!("Unexpected XDG Reference in Backup <xdgConfig>"); Err(BackupError::InvalidSystem) }
|
||||
fn skip_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> { Ok(PathBuf::new()) }
|
||||
}
|
||||
|
||||
pub struct LinuxBackupManager {}
|
||||
impl BackupHandler for LinuxBackupManager {
|
||||
fn xdg_config_translate(&self, _path: &PathBuf,_game: &GameVersion) -> Result<PathBuf, BackupError> {
|
||||
Ok(CommonPath::Data.get().ok_or(BackupError::NotFound)?)
|
||||
}
|
||||
fn xdg_data_translate(&self, _path: &PathBuf,_game: &GameVersion) -> Result<PathBuf, BackupError> {
|
||||
Ok(CommonPath::Config.get().ok_or(BackupError::NotFound)?)
|
||||
}
|
||||
}
|
||||
pub struct WindowsBackupManager {}
|
||||
impl BackupHandler for WindowsBackupManager {
|
||||
fn win_app_data_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> {
|
||||
Ok(CommonPath::Config.get().ok_or(BackupError::NotFound)?)
|
||||
}
|
||||
fn win_local_app_data_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> {
|
||||
Ok(CommonPath::DataLocal.get().ok_or(BackupError::NotFound)?)
|
||||
}
|
||||
fn win_local_app_data_low_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> {
|
||||
Ok(CommonPath::DataLocalLow.get().ok_or(BackupError::NotFound)?)
|
||||
}
|
||||
fn win_dir_translate(&self, _path: &PathBuf,_game: &GameVersion) -> Result<PathBuf, BackupError> {
|
||||
Ok(PathBuf::from_str("C:/Windows").unwrap())
|
||||
}
|
||||
fn win_documents_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> {
|
||||
Ok(CommonPath::Document.get().ok_or(BackupError::NotFound)?)
|
||||
|
||||
}
|
||||
fn win_program_data_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> {
|
||||
Ok(PathBuf::from_str("C:/ProgramData").unwrap())
|
||||
}
|
||||
fn win_public_translate(&self, _path: &PathBuf, _game: &GameVersion) -> Result<PathBuf, BackupError> {
|
||||
Ok(CommonPath::Public.get().ok_or(BackupError::NotFound)?)
|
||||
|
||||
}
|
||||
}
|
||||
pub struct MacBackupManager {}
|
||||
impl BackupHandler for MacBackupManager {}
|
||||
6
src-tauri/src/cloud_saves/conditions.rs
Normal file
6
src-tauri/src/cloud_saves/conditions.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use crate::process::process_manager::Platform;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Condition {
|
||||
Os(Platform)
|
||||
}
|
||||
35
src-tauri/src/cloud_saves/metadata.rs
Normal file
35
src-tauri/src/cloud_saves/metadata.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use crate::database::db::GameVersion;
|
||||
|
||||
use super::conditions::{Condition};
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct CloudSaveMetadata {
|
||||
pub files: Vec<GameFile>,
|
||||
pub game_version: GameVersion,
|
||||
pub save_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GameFile {
|
||||
pub path: String,
|
||||
pub id: Option<String>,
|
||||
pub data_type: DataType,
|
||||
pub tags: Vec<Tag>,
|
||||
pub conditions: Vec<Condition>
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DataType {
|
||||
Registry,
|
||||
File,
|
||||
Other
|
||||
}
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Tag {
|
||||
Config,
|
||||
Save,
|
||||
#[default]
|
||||
#[serde(other)]
|
||||
Other,
|
||||
}
|
||||
7
src-tauri/src/cloud_saves/mod.rs
Normal file
7
src-tauri/src/cloud_saves/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub mod conditions;
|
||||
pub mod metadata;
|
||||
pub mod resolver;
|
||||
pub mod placeholder;
|
||||
pub mod normalise;
|
||||
pub mod path;
|
||||
pub mod backup_manager;
|
||||
162
src-tauri/src/cloud_saves/normalise.rs
Normal file
162
src-tauri/src/cloud_saves/normalise.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use regex::Regex;
|
||||
use crate::process::process_manager::Platform;
|
||||
|
||||
use super::placeholder::*;
|
||||
|
||||
|
||||
pub fn normalize(path: &str, os: Platform) -> String {
|
||||
let mut path = path.trim().trim_end_matches(['/', '\\']).replace('\\', "/");
|
||||
|
||||
if path == "~" || path.starts_with("~/") {
|
||||
path = path.replacen('~', HOME, 1);
|
||||
}
|
||||
|
||||
static CONSECUTIVE_SLASHES: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"/{2,}").unwrap());
|
||||
static UNNECESSARY_DOUBLE_STAR_1: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"([^/*])\*{2,}").unwrap());
|
||||
static UNNECESSARY_DOUBLE_STAR_2: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\*{2,}([^/*])").unwrap());
|
||||
static ENDING_WILDCARD: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(/\*)+$").unwrap());
|
||||
static ENDING_DOT: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(/\.)$").unwrap());
|
||||
static INTERMEDIATE_DOT: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(/\./)").unwrap());
|
||||
static BLANK_SEGMENT: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(/\s+/)").unwrap());
|
||||
static APP_DATA: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?i)%appdata%").unwrap());
|
||||
static APP_DATA_ROAMING: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?i)%userprofile%/AppData/Roaming").unwrap());
|
||||
static APP_DATA_LOCAL: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?i)%localappdata%").unwrap());
|
||||
static APP_DATA_LOCAL_2: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?i)%userprofile%/AppData/Local/").unwrap());
|
||||
static USER_PROFILE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?i)%userprofile%").unwrap());
|
||||
static DOCUMENTS: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?i)%userprofile%/Documents").unwrap());
|
||||
|
||||
for (pattern, replacement) in [
|
||||
(&CONSECUTIVE_SLASHES, "/"),
|
||||
(&UNNECESSARY_DOUBLE_STAR_1, "${1}*"),
|
||||
(&UNNECESSARY_DOUBLE_STAR_2, "*${1}"),
|
||||
(&ENDING_WILDCARD, ""),
|
||||
(&ENDING_DOT, ""),
|
||||
(&INTERMEDIATE_DOT, "/"),
|
||||
(&BLANK_SEGMENT, "/"),
|
||||
(&APP_DATA, WIN_APP_DATA),
|
||||
(&APP_DATA_ROAMING, WIN_APP_DATA),
|
||||
(&APP_DATA_LOCAL, WIN_LOCAL_APP_DATA),
|
||||
(&APP_DATA_LOCAL_2, &format!("{}/", WIN_LOCAL_APP_DATA)),
|
||||
(&USER_PROFILE, HOME),
|
||||
(&DOCUMENTS, WIN_DOCUMENTS),
|
||||
] {
|
||||
path = pattern.replace_all(&path, replacement).to_string();
|
||||
}
|
||||
|
||||
if os == Platform::Windows {
|
||||
let documents_2: Regex = Regex::new(r"(?i)<home>/Documents").unwrap();
|
||||
|
||||
#[allow(clippy::single_element_loop)]
|
||||
for (pattern, replacement) in [(&documents_2, WIN_DOCUMENTS)] {
|
||||
path = pattern.replace_all(&path, replacement).to_string();
|
||||
}
|
||||
}
|
||||
|
||||
for (pattern, replacement) in [
|
||||
("{64BitSteamID}", STORE_USER_ID),
|
||||
("{Steam3AccountID}", STORE_USER_ID),
|
||||
] {
|
||||
path = path.replace(pattern, replacement);
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
fn too_broad(path: &str) -> bool {
|
||||
println!("Path: {}", path);
|
||||
use {BASE, HOME, ROOT, STORE_USER_ID, WIN_APP_DATA, WIN_DIR, WIN_DOCUMENTS, XDG_CONFIG, XDG_DATA};
|
||||
|
||||
let path_lower = path.to_lowercase();
|
||||
|
||||
for item in ALL {
|
||||
if path == *item {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for item in AVOID_WILDCARDS {
|
||||
if path.starts_with(&format!("{}/*", item)) || path.starts_with(&format!("{}/{}", item, STORE_USER_ID)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// These paths are present whether or not the game is installed.
|
||||
// If possible, they should be narrowed down on the wiki.
|
||||
for item in [
|
||||
format!("{}/{}", BASE, STORE_USER_ID), // because `<storeUserId>` is handled as `*`
|
||||
format!("{}/Documents", HOME),
|
||||
format!("{}/Saved Games", HOME),
|
||||
format!("{}/AppData", HOME),
|
||||
format!("{}/AppData/Local", HOME),
|
||||
format!("{}/AppData/Local/Packages", HOME),
|
||||
format!("{}/AppData/LocalLow", HOME),
|
||||
format!("{}/AppData/Roaming", HOME),
|
||||
format!("{}/Documents/My Games", HOME),
|
||||
format!("{}/Library/Application Support", HOME),
|
||||
format!("{}/Library/Application Support/UserData", HOME),
|
||||
format!("{}/Library/Preferences", HOME),
|
||||
format!("{}/.renpy", HOME),
|
||||
format!("{}/.renpy/persistent", HOME),
|
||||
format!("{}/Library", HOME),
|
||||
format!("{}/Library/RenPy", HOME),
|
||||
format!("{}/Telltale Games", HOME),
|
||||
format!("{}/config", ROOT),
|
||||
format!("{}/MMFApplications", WIN_APP_DATA),
|
||||
format!("{}/RenPy", WIN_APP_DATA),
|
||||
format!("{}/RenPy/persistent", WIN_APP_DATA),
|
||||
format!("{}/win.ini", WIN_DIR),
|
||||
format!("{}/SysWOW64", WIN_DIR),
|
||||
format!("{}/My Games", WIN_DOCUMENTS),
|
||||
format!("{}/Telltale Games", WIN_DOCUMENTS),
|
||||
format!("{}/unity3d", XDG_CONFIG),
|
||||
format!("{}/unity3d", XDG_DATA),
|
||||
"C:/Program Files".to_string(),
|
||||
"C:/Program Files (x86)".to_string(),
|
||||
] {
|
||||
let item = item.to_lowercase();
|
||||
if path_lower == item
|
||||
|| path_lower.starts_with(&format!("{}/*", item))
|
||||
|| path_lower.starts_with(&format!("{}/{}", item, STORE_USER_ID.to_lowercase()))
|
||||
|| path_lower.starts_with(&format!("{}/savesdir", item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Drive letters:
|
||||
let drives: Regex = Regex::new(r"^[a-zA-Z]:$").unwrap();
|
||||
if drives.is_match(path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Colon not for a drive letter
|
||||
if path.get(2..).is_some_and(|path| path.contains(':')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Root:
|
||||
if path == "/" {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Relative path wildcard:
|
||||
if path.starts_with('*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn usable(path: &str) -> bool {
|
||||
let unprintable: Regex = Regex::new(r"(\p{Cc}|\p{Cf})").unwrap();
|
||||
|
||||
!path.is_empty()
|
||||
&& !path.contains("{{")
|
||||
&& !path.starts_with("./")
|
||||
&& !path.starts_with("../")
|
||||
&& !too_broad(path)
|
||||
&& !unprintable.is_match(path)
|
||||
}
|
||||
48
src-tauri/src/cloud_saves/path.rs
Normal file
48
src-tauri/src/cloud_saves/path.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use std::{path::PathBuf, sync::LazyLock};
|
||||
|
||||
pub enum CommonPath {
|
||||
Config,
|
||||
Data,
|
||||
DataLocal,
|
||||
DataLocalLow,
|
||||
Document,
|
||||
Home,
|
||||
Public,
|
||||
SavedGames,
|
||||
}
|
||||
|
||||
impl CommonPath {
|
||||
pub fn get(&self) -> Option<PathBuf> {
|
||||
static CONFIG: LazyLock<Option<PathBuf>> = LazyLock::new(|| dirs::config_dir());
|
||||
static DATA: LazyLock<Option<PathBuf>> = LazyLock::new(|| dirs::data_dir());
|
||||
static DATA_LOCAL: LazyLock<Option<PathBuf>> = LazyLock::new(|| dirs::data_local_dir());
|
||||
static DOCUMENT: LazyLock<Option<PathBuf>> = LazyLock::new(|| dirs::document_dir());
|
||||
static HOME: LazyLock<Option<PathBuf>> = LazyLock::new(|| dirs::home_dir());
|
||||
static PUBLIC: LazyLock<Option<PathBuf>> = LazyLock::new(|| dirs::public_dir());
|
||||
|
||||
#[cfg(windows)]
|
||||
static DATA_LOCAL_LOW: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
|
||||
known_folders::get_known_folder_path(known_folders::KnownFolder::LocalAppDataLow)
|
||||
});
|
||||
#[cfg(not(windows))]
|
||||
static DATA_LOCAL_LOW: Option<PathBuf> = None;
|
||||
|
||||
#[cfg(windows)]
|
||||
static SAVED_GAMES: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
|
||||
known_folders::get_known_folder_path(known_folders::KnownFolder::SavedGames)
|
||||
});
|
||||
#[cfg(not(windows))]
|
||||
static SAVED_GAMES: Option<PathBuf> = None;
|
||||
|
||||
match self {
|
||||
Self::Config => CONFIG.clone(),
|
||||
Self::Data => DATA.clone(),
|
||||
Self::DataLocal => DATA_LOCAL.clone(),
|
||||
Self::DataLocalLow => DATA_LOCAL_LOW.clone(),
|
||||
Self::Document => DOCUMENT.clone(),
|
||||
Self::Home => HOME.clone(),
|
||||
Self::Public => PUBLIC.clone(),
|
||||
Self::SavedGames => SAVED_GAMES.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src-tauri/src/cloud_saves/placeholder.rs
Normal file
51
src-tauri/src/cloud_saves/placeholder.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub const ALL: &[&str] = &[
|
||||
ROOT,
|
||||
GAME,
|
||||
BASE,
|
||||
HOME,
|
||||
STORE_USER_ID,
|
||||
OS_USER_NAME,
|
||||
WIN_APP_DATA,
|
||||
WIN_LOCAL_APP_DATA,
|
||||
WIN_DOCUMENTS,
|
||||
WIN_PUBLIC,
|
||||
WIN_PROGRAM_DATA,
|
||||
WIN_DIR,
|
||||
XDG_DATA,
|
||||
XDG_CONFIG,
|
||||
];
|
||||
|
||||
/// These are paths where `<placeholder>/*/` is suspicious.
|
||||
pub const AVOID_WILDCARDS: &[&str] = &[
|
||||
ROOT,
|
||||
HOME,
|
||||
WIN_APP_DATA,
|
||||
WIN_LOCAL_APP_DATA,
|
||||
WIN_DOCUMENTS,
|
||||
WIN_PUBLIC,
|
||||
WIN_PROGRAM_DATA,
|
||||
WIN_DIR,
|
||||
XDG_DATA,
|
||||
XDG_CONFIG,
|
||||
];
|
||||
|
||||
pub const ROOT: &str = "<root>"; // a directory where games are installed (configured in backup tool)
|
||||
pub const GAME: &str = "<game>"; // an installDir (if defined) or the game's canonical name in the manifest
|
||||
pub const BASE: &str = "<base>"; // shorthand for <root>/<game> (unless overridden by store-specific rules)
|
||||
pub const HOME: &str = "<home>"; // current user's home directory in the OS (~)
|
||||
pub const STORE_USER_ID: &str = "<storeUserId>"; // a store-specific id from the manifest, corresponding to the root's store type
|
||||
pub const OS_USER_NAME: &str = "<osUserName>"; // current user's ID in the game store
|
||||
pub const WIN_APP_DATA: &str = "<winAppData>"; // current user's name in the OS
|
||||
pub const WIN_LOCAL_APP_DATA: &str = "<winLocalAppData>"; // %APPDATA% on Windows
|
||||
pub const WIN_LOCAL_APP_DATA_LOW: &str = "<winLocalAppDataLow>"; // %LOCALAPPDATA% on Windows
|
||||
pub const WIN_DOCUMENTS: &str = "<winDocuments>"; // <home>/AppData/LocalLow on Windows
|
||||
pub const WIN_PUBLIC: &str = "<winPublic>"; // <home>/Documents (f.k.a. <home>/My Documents) or a localized equivalent on Windows
|
||||
pub const WIN_PROGRAM_DATA: &str = "<winProgramData>"; // %PUBLIC% on Windows
|
||||
pub const WIN_DIR: &str = "<winDir>"; // %PROGRAMDATA% on Windows
|
||||
pub const XDG_DATA: &str = "<xdgData>"; // %WINDIR% on Windows
|
||||
pub const XDG_CONFIG: &str = "<xdgConfig>"; // $XDG_DATA_HOME on Linux
|
||||
pub const SKIP: &str = "<skip>"; // $XDG_CONFIG_HOME on Linux
|
||||
|
||||
pub static OS_USERNAME: LazyLock<String> = LazyLock::new(|| whoami::username());
|
||||
262
src-tauri/src/cloud_saves/resolver.rs
Normal file
262
src-tauri/src/cloud_saves/resolver.rs
Normal file
@ -0,0 +1,262 @@
|
||||
use std::{
|
||||
fs::{self, create_dir_all, File},
|
||||
io::{self, ErrorKind, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use super::{
|
||||
backup_manager::BackupHandler, conditions::Condition, metadata::GameFile, placeholder::*,
|
||||
};
|
||||
use log::{debug, warn};
|
||||
use rustix::path::Arg;
|
||||
use tempfile::tempfile;
|
||||
|
||||
use crate::{
|
||||
database::db::GameVersion, error::backup_error::BackupError, process::process_manager::Platform,
|
||||
};
|
||||
|
||||
use super::{backup_manager::BackupManager, metadata::CloudSaveMetadata, normalise::normalize};
|
||||
|
||||
pub fn resolve(meta: &mut CloudSaveMetadata) -> File {
|
||||
let f = File::create_new("save").unwrap();
|
||||
let compressor = zstd::Encoder::new(f, 22).unwrap();
|
||||
let mut tarball = tar::Builder::new(compressor);
|
||||
let manager = BackupManager::new();
|
||||
for file in meta.files.iter_mut() {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
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 backed 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 t_path = PathBuf::from(normalize(&file.path, os));
|
||||
println!("{:?}", &t_path);
|
||||
let path = parse_path(t_path, handler, &meta.game_version).unwrap();
|
||||
let f = std::fs::metadata(&path).unwrap(); // TODO: Fix unwrap here
|
||||
if f.is_dir() {
|
||||
tarball.append_dir_all(&id, path).unwrap();
|
||||
} else if f.is_file() {
|
||||
tarball
|
||||
.append_file(&id, &mut File::open(path).unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
file.id = Some(id);
|
||||
}
|
||||
let binding = serde_json::to_string(meta).unwrap();
|
||||
let serialized = binding.as_bytes();
|
||||
let mut file = tempfile().unwrap();
|
||||
file.write(serialized).unwrap();
|
||||
tarball.append_file("metadata", &mut file).unwrap();
|
||||
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
|
||||
);
|
||||
|
||||
copy_item(current_path, new_path).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn copy_item<P: AsRef<Path>>(src: P, dest: P) -> io::Result<()> {
|
||||
let src_path = src.as_ref();
|
||||
let dest_path = dest.as_ref();
|
||||
|
||||
let metadata = fs::metadata(&src_path)?;
|
||||
|
||||
if metadata.is_file() {
|
||||
// Ensure the parent directory of the destination exists for a file copy
|
||||
if let Some(parent) = dest_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
fs::copy(&src_path, &dest_path)?;
|
||||
} else if metadata.is_dir() {
|
||||
// For directories, we call the recursive helper function.
|
||||
// The destination for the recursive copy is the `dest_path` itself.
|
||||
copy_dir_recursive(&src_path, &dest_path)?;
|
||||
} else {
|
||||
// Handle other file types like symlinks if necessary,
|
||||
// for now, return an error or skip.
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Source {:?} is neither a file nor a directory", src_path),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
|
||||
fs::create_dir_all(&dest)?;
|
||||
|
||||
for entry in fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let entry_path = entry.path();
|
||||
let entry_file_name = match entry_path.file_name() {
|
||||
Some(name) => name,
|
||||
None => continue, // Skip if somehow there's no file name
|
||||
};
|
||||
let dest_entry_path = dest.join(entry_file_name);
|
||||
let metadata = entry.metadata()?;
|
||||
|
||||
if metadata.is_file() {
|
||||
debug!(
|
||||
"Writing file {} to {}",
|
||||
entry_path.display(),
|
||||
dest_entry_path.display()
|
||||
);
|
||||
fs::copy(&entry_path, &dest_entry_path)?;
|
||||
} else if metadata.is_dir() {
|
||||
copy_dir_recursive(&entry_path, &dest_entry_path)?;
|
||||
}
|
||||
// Ignore other types like symlinks for this basic implementation
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parse_path(
|
||||
path: PathBuf,
|
||||
backup_handler: &dyn BackupHandler,
|
||||
game: &GameVersion,
|
||||
) -> Result<PathBuf, BackupError> {
|
||||
println!("Parsing: {:?}", &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(backup_handler.skip_translate(&path, game)?),
|
||||
_ => s.push(PathBuf::from(component.as_os_str())),
|
||||
}
|
||||
}
|
||||
|
||||
println!("Final line: {:?}", &s);
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn test() {
|
||||
let mut meta = CloudSaveMetadata {
|
||||
files: vec![
|
||||
GameFile {
|
||||
path: String::from("<home>/favicon.png"),
|
||||
id: None,
|
||||
data_type: super::metadata::DataType::File,
|
||||
tags: Vec::new(),
|
||||
conditions: vec![Condition::Os(Platform::Linux)],
|
||||
},
|
||||
GameFile {
|
||||
path: String::from("<home>/Documents/Pixel Art"),
|
||||
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(),
|
||||
platform: Platform::Linux,
|
||||
launch_command: String::new(),
|
||||
launch_args: Vec::new(),
|
||||
launch_command_template: String::new(),
|
||||
setup_command: String::new(),
|
||||
setup_args: Vec::new(),
|
||||
setup_command_template: String::new(),
|
||||
only_setup: true,
|
||||
version_index: 0,
|
||||
delta: false,
|
||||
umu_id_override: None,
|
||||
},
|
||||
save_id: String::from("aaaaaaa"),
|
||||
};
|
||||
//resolve(&mut meta);
|
||||
|
||||
extract("save".into()).unwrap();
|
||||
}
|
||||
0
src-tauri/src/cloud_saves/strict_path.rs
Normal file
0
src-tauri/src/cloud_saves/strict_path.rs
Normal file
@ -5,8 +5,8 @@ use std::{
|
||||
};
|
||||
|
||||
use chrono::Utc;
|
||||
use directories::BaseDirs;
|
||||
use log::{debug, error, info};
|
||||
use log::{debug, error, info, warn};
|
||||
use native_model::{Decode, Encode};
|
||||
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use url::Url;
|
||||
@ -16,7 +16,8 @@ use crate::DB;
|
||||
use super::models::data::Database;
|
||||
|
||||
pub static DATA_ROOT_DIR: LazyLock<Mutex<PathBuf>> =
|
||||
LazyLock::new(|| Mutex::new(BaseDirs::new().unwrap().data_dir().join("drop")));
|
||||
LazyLock::new(|| Mutex::new(dirs::data_dir().unwrap().join("drop")));
|
||||
|
||||
|
||||
// Custom JSON serializer to support everything we need
|
||||
#[derive(Debug, Default, Clone)]
|
||||
@ -26,15 +27,15 @@ impl<T: native_model::Model + Serialize + DeserializeOwned> DeSerializer<T>
|
||||
for DropDatabaseSerializer
|
||||
{
|
||||
fn serialize(&self, val: &T) -> rustbreak::error::DeSerResult<Vec<u8>> {
|
||||
native_model::encode(val).map_err(|e| DeSerError::Internal(e.to_string()))
|
||||
native_model::rmp_serde_1_3::RmpSerde::encode(val).map_err(|e| DeSerError::Internal(e.to_string()))
|
||||
}
|
||||
|
||||
fn deserialize<R: std::io::Read>(&self, mut s: R) -> rustbreak::error::DeSerResult<T> {
|
||||
let mut buf = Vec::new();
|
||||
s.read_to_end(&mut buf)
|
||||
.map_err(|e| rustbreak::error::DeSerError::Other(e.into()))?;
|
||||
let (val, _version) =
|
||||
native_model::decode::<T>(buf).map_err(|e| DeSerError::Internal(e.to_string()))?;
|
||||
let val =
|
||||
native_model::rmp_serde_1_3::RmpSerde::decode(buf).map_err(|e| DeSerError::Internal(e.to_string()))?;
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
@ -99,6 +100,7 @@ fn handle_invalid_database(
|
||||
games_base_dir: PathBuf,
|
||||
cache_dir: PathBuf,
|
||||
) -> rustbreak::Database<Database, rustbreak::backend::PathBackend, DropDatabaseSerializer> {
|
||||
warn!("{}", _e);
|
||||
let new_path = {
|
||||
let time = Utc::now().timestamp();
|
||||
let mut base = db_path.clone();
|
||||
|
||||
@ -27,7 +27,7 @@ pub mod data {
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[native_model(id = 2, version = 1)]
|
||||
#[native_model(id = 2, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
pub struct GameVersion {
|
||||
pub game_id: String,
|
||||
pub version_name: String,
|
||||
@ -69,7 +69,7 @@ pub mod data {
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[native_model(id = 4, version = 1)]
|
||||
#[native_model(id = 4, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
pub struct Settings {
|
||||
pub autostart: bool,
|
||||
pub max_download_threads: usize,
|
||||
@ -88,7 +88,7 @@ pub mod data {
|
||||
// Strings are version names for a particular game
|
||||
#[derive(Serialize, Clone, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
#[native_model(id = 5, version = 1)]
|
||||
#[native_model(id = 5, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
pub enum GameDownloadStatus {
|
||||
Remote {},
|
||||
SetupRequired {
|
||||
@ -111,7 +111,7 @@ pub mod data {
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Clone, Deserialize)]
|
||||
#[native_model(id = 6, version = 1)]
|
||||
#[native_model(id = 6, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
pub struct DatabaseAuth {
|
||||
pub private: String,
|
||||
pub cert: String,
|
||||
@ -130,7 +130,7 @@ pub mod data {
|
||||
Mod,
|
||||
}
|
||||
|
||||
#[native_model(id = 7, version = 1)]
|
||||
#[native_model(id = 7, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadableMetadata {
|
||||
@ -168,7 +168,7 @@ pub mod data {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[native_model(id = 1, version = 2)]
|
||||
#[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||
pub struct Database {
|
||||
#[serde(default)]
|
||||
@ -176,12 +176,13 @@ pub mod data {
|
||||
pub auth: Option<DatabaseAuth>,
|
||||
pub base_url: String,
|
||||
pub applications: DatabaseApplications,
|
||||
#[serde(skip)]
|
||||
pub prev_database: Option<PathBuf>,
|
||||
pub cache_dir: PathBuf,
|
||||
pub compat_info: Option<DatabaseCompatInfo>,
|
||||
}
|
||||
|
||||
#[native_model(id = 8, version = 2)]
|
||||
#[native_model(id = 8, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||
|
||||
pub struct DatabaseCompatInfo {
|
||||
|
||||
21
src-tauri/src/error/backup_error.rs
Normal file
21
src-tauri/src/error/backup_error.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde_with::SerializeDisplay;
|
||||
|
||||
#[derive(Debug, SerializeDisplay, Clone, Copy)]
|
||||
pub enum BackupError {
|
||||
InvalidSystem,
|
||||
NotFound,
|
||||
ParseError
|
||||
}
|
||||
|
||||
impl Display for BackupError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
BackupError::InvalidSystem => "Attempted to generate path for invalid system",
|
||||
BackupError::NotFound => "Could not generate or find path",
|
||||
BackupError::ParseError => "Failed to parse path",
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
@ -5,3 +5,4 @@ pub mod library_error;
|
||||
pub mod process_error;
|
||||
pub mod remote_access_error;
|
||||
pub mod setup_error;
|
||||
pub mod backup_error;
|
||||
@ -328,13 +328,56 @@ impl ProcessManager<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, Hash, PartialEq, Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Eq, Hash, PartialEq, Serialize, Deserialize, Clone, Copy, Debug)]
|
||||
pub enum Platform {
|
||||
Windows,
|
||||
Linux,
|
||||
MacOs,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
const WINDOWS: bool = cfg!(target_os = "windows");
|
||||
const MAC: bool = cfg!(target_os = "macos");
|
||||
const LINUX: bool = cfg!(target_os = "linux");
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const HOST: Platform = Self::Windows;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const HOST: Platform = Self::MacOs;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub const HOST: Platform = Self::Linux;
|
||||
|
||||
|
||||
|
||||
pub fn is_case_sensitive(&self) -> bool {
|
||||
match self {
|
||||
Self::Windows | Self::MacOs => false,
|
||||
Self::Linux => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Platform {
|
||||
fn from(value: &str) -> Self {
|
||||
match value.to_lowercase().trim() {
|
||||
"windows" => Self::Windows,
|
||||
"linux" => Self::Linux,
|
||||
"mac" | "macos" => Self::MacOs,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<whoami::Platform> for Platform {
|
||||
fn from(value: whoami::Platform) -> Self {
|
||||
match value {
|
||||
whoami::Platform::Windows => Platform::Windows,
|
||||
whoami::Platform::Linux => Platform::Linux,
|
||||
whoami::Platform::MacOS => Platform::MacOs,
|
||||
_ => unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ProcessHandler: Send + 'static {
|
||||
fn create_launch_process(
|
||||
&self,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2.0.0",
|
||||
"productName": "Drop Desktop Client",
|
||||
"version": "0.3.0-rc-2",
|
||||
"version": "0.3.0-rc-3",
|
||||
"identifier": "dev.drop.app",
|
||||
"build": {
|
||||
"beforeDevCommand": "yarn dev --port 1432",
|
||||
|
||||
Reference in New Issue
Block a user