From 50eb7ff716f5fe58e7519cdd93cb7e4435f8963c Mon Sep 17 00:00:00 2001 From: quexeky Date: Wed, 22 Oct 2025 18:55:55 +1100 Subject: [PATCH] chore: Starting migrating references to database applications, auth, and base_url Signed-off-by: quexeky --- src-tauri/Cargo.lock | 38 ++ src-tauri/Cargo.toml | 8 +- src-tauri/client/Cargo.toml | 1 + src-tauri/client/src/app_state.rs | 42 ++ src-tauri/client/src/compat.rs | 4 +- src-tauri/client/src/lib.rs | 3 +- src-tauri/cloud_saves/Cargo.toml | 1 + src-tauri/cloud_saves/src/backup_manager.rs | 2 +- src-tauri/database/Cargo.toml | 2 + src-tauri/database/src/db.rs | 14 +- src-tauri/database/src/interface.rs | 20 +- src-tauri/database/src/lib.rs | 2 +- src-tauri/database/src/models.rs | 373 ------------------ src-tauri/database/src/models/mod.rs | 110 ++++++ src-tauri/database/src/models/v1.rs | 201 ++++++++++ src-tauri/database/src/models/v2.rs | 111 ++++++ src-tauri/database/src/models/v3.rs | 32 ++ src-tauri/database/src/models/v4.rs | 43 ++ src-tauri/drop-consts/Cargo.toml | 7 + src-tauri/drop-consts/src/lib.rs | 16 + src-tauri/games/Cargo.toml | 5 +- src-tauri/games/src/collections/collection.rs | 5 +- .../games/src/downloads/download_agent.rs | 6 +- src-tauri/games/src/downloads/drop_data.rs | 3 +- src-tauri/games/src/library.rs | 24 +- src-tauri/games/src/scan.rs | 3 +- src-tauri/games/src/state.rs | 2 +- src-tauri/library/Cargo.toml | 17 + src-tauri/library/src/drop/drop.rs | 134 +++++++ src-tauri/library/src/drop/mod.rs | 1 + src-tauri/library/src/error.rs | 31 ++ src-tauri/library/src/lib.rs | 69 ++++ src-tauri/library/src/provider.rs | 16 + src-tauri/remote/src/auth.rs | 53 +-- src-tauri/remote/src/cache.rs | 2 +- src-tauri/remote/src/fetch_object.rs | 13 +- src-tauri/remote/src/requests.rs | 9 +- src-tauri/remote/src/server_proto.rs | 28 +- src-tauri/src/client.rs | 3 +- src-tauri/src/games.rs | 78 +--- src-tauri/src/lib.rs | 33 +- src-tauri/src/process.rs | 9 - src-tauri/src/remote.rs | 17 +- 43 files changed, 968 insertions(+), 623 deletions(-) delete mode 100644 src-tauri/database/src/models.rs create mode 100644 src-tauri/database/src/models/mod.rs create mode 100644 src-tauri/database/src/models/v1.rs create mode 100644 src-tauri/database/src/models/v2.rs create mode 100644 src-tauri/database/src/models/v3.rs create mode 100644 src-tauri/database/src/models/v4.rs create mode 100644 src-tauri/drop-consts/Cargo.toml create mode 100644 src-tauri/drop-consts/src/lib.rs create mode 100644 src-tauri/library/Cargo.toml create mode 100644 src-tauri/library/src/drop/drop.rs create mode 100644 src-tauri/library/src/drop/mod.rs create mode 100644 src-tauri/library/src/error.rs create mode 100644 src-tauri/library/src/lib.rs create mode 100644 src-tauri/library/src/provider.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 51278fa..3f65b64 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -785,6 +785,7 @@ version = "0.1.0" dependencies = [ "bitcode", "database", + "drop-consts", "log", "serde", "tauri", @@ -797,6 +798,7 @@ version = "0.1.0" dependencies = [ "database", "dirs 6.0.0", + "drop-consts", "log", "regex", "rustix 1.1.2", @@ -1065,8 +1067,10 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" name = "database" version = "0.1.0" dependencies = [ + "bitcode", "chrono", "dirs 6.0.0", + "drop-consts", "log", "native_model", "rustbreak", @@ -1414,6 +1418,13 @@ dependencies = [ "zstd", ] +[[package]] +name = "drop-consts" +version = "0.1.0" +dependencies = [ + "dirs 6.0.0", +] + [[package]] name = "droplet-rs" version = "0.7.3" @@ -1841,6 +1852,7 @@ dependencies = [ "boxcar", "database", "download_manager", + "drop-consts", "hex 0.4.3", "log", "md5 0.8.0", @@ -2739,6 +2751,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -2935,6 +2956,23 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "library" +version = "0.1.0" +dependencies = [ + "async-trait", + "client", + "database", + "futures", + "itertools", + "log", + "remote", + "serde", + "serde_with", + "tauri", + "url", +] + [[package]] name = "libredox" version = "0.1.10" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 4001a30..da341e8 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -65,7 +65,9 @@ whoami = "1.6.0" filetime = "0.2.25" walkdir = "2.5.0" known-folders = "1.2.0" -native_model = { version = "0.6.4", features = ["rmp_serde_1_3"], git = "https://github.com/Drop-OSS/native_model.git"} +native_model = { version = "0.6.4", features = [ + "rmp_serde_1_3", +], git = "https://github.com/Drop-OSS/native_model.git" } tauri-plugin-opener = "2.4.0" bitcode = "0.6.6" reqwest-websocket = "0.5.0" @@ -149,6 +151,8 @@ members = [ "cloud_saves", "download_manager", "games", + "library", + "drop-consts", ] -resolver = "3" \ No newline at end of file +resolver = "3" diff --git a/src-tauri/client/Cargo.toml b/src-tauri/client/Cargo.toml index 6e32523..18cf5c8 100644 --- a/src-tauri/client/Cargo.toml +++ b/src-tauri/client/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] bitcode = "0.6.7" database = { version = "0.1.0", path = "../database" } +drop-consts = { version = "0.1.0", path = "../drop-consts" } log = "0.4.28" serde = { version = "1.0.228", features = ["derive"] } tauri = "2.8.5" diff --git a/src-tauri/client/src/app_state.rs b/src-tauri/client/src/app_state.rs index e69de29..36e9aaf 100644 --- a/src-tauri/client/src/app_state.rs +++ b/src-tauri/client/src/app_state.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; + +use database::models::Game; +use serde::Serialize; + +use crate::{app_status::AppStatus, user::User}; + +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppState { + status: AppStatus, + user: Option, + games: HashMap, +} +impl AppState { + pub fn new(status: AppStatus, user: Option, games: HashMap) -> Self { + Self { + status, + user, + games, + } + } + + pub fn status(&self) -> &AppStatus { + &self.status + } + pub fn status_mut(&mut self) -> &mut AppStatus { + &mut self.status + } + pub fn games(&self) -> &HashMap { + &self.games + } + pub fn games_mut(&mut self) -> &mut HashMap { + &mut self.games + } + pub fn user(&self) -> &Option { + &self.user + } + pub fn user_mut(&mut self) -> &mut Option { + &mut self.user + } +} diff --git a/src-tauri/client/src/compat.rs b/src-tauri/client/src/compat.rs index 0bd04b6..f11993e 100644 --- a/src-tauri/client/src/compat.rs +++ b/src-tauri/client/src/compat.rs @@ -5,6 +5,7 @@ use std::{ sync::LazyLock, }; +use drop_consts::{UMU_BASE_LAUNCHER_EXECUTABLE, UMU_INSTALL_DIRS}; use log::info; pub static COMPAT_INFO: LazyLock> = LazyLock::new(create_new_compat_info); @@ -30,9 +31,6 @@ fn create_new_compat_info() -> Option { }) } -const UMU_BASE_LAUNCHER_EXECUTABLE: &str = "umu-run"; -const UMU_INSTALL_DIRS: [&str; 4] = ["/app/share", "/use/local/share", "/usr/share", "/opt"]; - fn get_umu_executable() -> Option { if check_executable_exists(UMU_BASE_LAUNCHER_EXECUTABLE) { return Some(PathBuf::from(UMU_BASE_LAUNCHER_EXECUTABLE)); diff --git a/src-tauri/client/src/lib.rs b/src-tauri/client/src/lib.rs index cb5afe9..7dab6c9 100644 --- a/src-tauri/client/src/lib.rs +++ b/src-tauri/client/src/lib.rs @@ -1,4 +1,5 @@ +pub mod app_state; pub mod app_status; pub mod autostart; pub mod compat; -pub mod user; +pub mod user; \ No newline at end of file diff --git a/src-tauri/cloud_saves/Cargo.toml b/src-tauri/cloud_saves/Cargo.toml index 806d1b0..f7e98b6 100644 --- a/src-tauri/cloud_saves/Cargo.toml +++ b/src-tauri/cloud_saves/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] database = { version = "0.1.0", path = "../database" } dirs = "6.0.0" +drop-consts = { version = "0.1.0", path = "../drop-consts" } log = "0.4.28" regex = "1.11.3" rustix = "1.1.2" diff --git a/src-tauri/cloud_saves/src/backup_manager.rs b/src-tauri/cloud_saves/src/backup_manager.rs index 3527672..d7e2c5a 100644 --- a/src-tauri/cloud_saves/src/backup_manager.rs +++ b/src-tauri/cloud_saves/src/backup_manager.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; #[cfg(target_os = "linux")] use database::platform::Platform; -use database::{GameVersion, db::DATA_ROOT_DIR}; +use database::{db::DATA_ROOT_DIR, GameVersion}; use log::warn; use crate::error::BackupError; diff --git a/src-tauri/database/Cargo.toml b/src-tauri/database/Cargo.toml index 9fbb499..6026c13 100644 --- a/src-tauri/database/Cargo.toml +++ b/src-tauri/database/Cargo.toml @@ -4,8 +4,10 @@ version = "0.1.0" edition = "2024" [dependencies] +bitcode = "0.6.7" chrono = "0.4.42" dirs = "6.0.0" +drop-consts = { version = "0.1.0", path = "../drop-consts" } log = "0.4.28" native_model = { version = "0.6.4", features = ["rmp_serde_1_3"], git = "https://github.com/Drop-OSS/native_model.git"} rustbreak = "2.0.0" diff --git a/src-tauri/database/src/db.rs b/src-tauri/database/src/db.rs index 313eb80..9b8f5a5 100644 --- a/src-tauri/database/src/db.rs +++ b/src-tauri/database/src/db.rs @@ -3,24 +3,18 @@ use std::{ sync::{Arc, LazyLock}, }; +use drop_consts::DATA_ROOT_PREFIX; use rustbreak::{DeSerError, DeSerializer}; use serde::{Serialize, de::DeserializeOwned}; -use crate::interface::{DatabaseImpls, DatabaseInterface}; +use crate::{interface::DatabaseImpls, models::DatabaseInterface}; pub static DB: LazyLock = LazyLock::new(DatabaseInterface::set_up_database); -#[cfg(not(debug_assertions))] -static DATA_ROOT_PREFIX: &str = "drop"; -#[cfg(debug_assertions)] -static DATA_ROOT_PREFIX: &str = "drop-debug"; - -pub static DATA_ROOT_DIR: LazyLock> = LazyLock::new(|| { - Arc::new( +pub static DATA_ROOT_DIR: LazyLock = LazyLock::new(|| { dirs::data_dir() .expect("Failed to get data dir") - .join(DATA_ROOT_PREFIX), - ) + .join(DATA_ROOT_PREFIX) }); // Custom JSON serializer to support everything we need diff --git a/src-tauri/database/src/interface.rs b/src-tauri/database/src/interface.rs index 969c93d..628e8ad 100644 --- a/src-tauri/database/src/interface.rs +++ b/src-tauri/database/src/interface.rs @@ -9,20 +9,14 @@ use std::{ use chrono::Utc; use log::{debug, error, info, warn}; use rustbreak::{PathDatabase, RustbreakError}; -use url::Url; use crate::{ - db::{DATA_ROOT_DIR, DB, DropDatabaseSerializer}, - models::data::Database, + db::{DropDatabaseSerializer, DATA_ROOT_DIR, DB}, + models::{Database, DatabaseInterface}, }; -pub type DatabaseInterface = - rustbreak::Database; - pub trait DatabaseImpls { fn set_up_database() -> DatabaseInterface; - fn database_is_set_up(&self) -> bool; - fn fetch_base_url(&self) -> Url; } impl DatabaseImpls for DatabaseInterface { fn set_up_database() -> DatabaseInterface { @@ -88,16 +82,6 @@ impl DatabaseImpls for DatabaseInterface { PathDatabase::create_at_path(db_path, default).expect("Database could not be created") } } - - fn database_is_set_up(&self) -> bool { - !borrow_db_checked().base_url.is_empty() - } - - fn fetch_base_url(&self) -> Url { - let handle = borrow_db_checked(); - Url::parse(&handle.base_url) - .unwrap_or_else(|_| panic!("Failed to parse base url {}", handle.base_url)) - } } // TODO: Make the error relelvant rather than just assume that it's a Deserialize error diff --git a/src-tauri/database/src/lib.rs b/src-tauri/database/src/lib.rs index df8b27e..8f76e3c 100644 --- a/src-tauri/database/src/lib.rs +++ b/src-tauri/database/src/lib.rs @@ -8,7 +8,7 @@ pub mod platform; pub use db::DB; pub use interface::{borrow_db_checked, borrow_db_mut_checked}; -pub use models::data::{ +pub use models::{ ApplicationTransientStatus, Database, DatabaseApplications, DatabaseAuth, DownloadType, DownloadableMetadata, GameDownloadStatus, GameVersion, Settings, }; diff --git a/src-tauri/database/src/models.rs b/src-tauri/database/src/models.rs deleted file mode 100644 index 7877886..0000000 --- a/src-tauri/database/src/models.rs +++ /dev/null @@ -1,373 +0,0 @@ -pub mod data { - use std::{hash::Hash, path::PathBuf}; - - use native_model::native_model; - use serde::{Deserialize, Serialize}; - - // NOTE: Within each version, you should NEVER use these types. - // Declare it using the actual version that it is from, i.e. v1::Settings rather than just Settings from here - - pub type GameVersion = v1::GameVersion; - pub type Database = v3::Database; - pub type Settings = v1::Settings; - pub type DatabaseAuth = v1::DatabaseAuth; - - pub type GameDownloadStatus = v2::GameDownloadStatus; - pub type ApplicationTransientStatus = v1::ApplicationTransientStatus; - /** - * Need to be universally accessible by the ID, and the version is just a couple sprinkles on top - */ - pub type DownloadableMetadata = v1::DownloadableMetadata; - pub type DownloadType = v1::DownloadType; - pub type DatabaseApplications = v2::DatabaseApplications; - // pub type DatabaseCompatInfo = v2::DatabaseCompatInfo; - - use std::collections::HashMap; - - impl PartialEq for DownloadableMetadata { - fn eq(&self, other: &Self) -> bool { - self.id == other.id && self.download_type == other.download_type - } - } - impl Hash for DownloadableMetadata { - fn hash(&self, state: &mut H) { - self.id.hash(state); - self.download_type.hash(state); - } - } - - mod v1 { - use serde_with::serde_as; - use std::{collections::HashMap, path::PathBuf}; - - use crate::platform::Platform; - - use super::{Deserialize, Serialize, native_model}; - - fn default_template() -> String { - "{}".to_owned() - } - - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] - #[serde(rename_all = "camelCase")] - #[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, - - pub platform: Platform, - - pub launch_command: String, - pub launch_args: Vec, - #[serde(default = "default_template")] - pub launch_command_template: String, - - pub setup_command: String, - pub setup_args: Vec, - #[serde(default = "default_template")] - pub setup_command_template: String, - - pub only_setup: bool, - - pub version_index: usize, - pub delta: bool, - - pub umu_id_override: Option, - } - - #[serde_as] - #[derive(Serialize, Clone, Deserialize, Default)] - #[serde(rename_all = "camelCase")] - #[native_model(id = 3, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] - pub struct DatabaseApplications { - pub install_dirs: Vec, - // Guaranteed to exist if the game also exists in the app state map - pub game_statuses: HashMap, - pub game_versions: HashMap>, - pub installed_game_version: HashMap, - - #[serde(skip)] - pub transient_statuses: HashMap, - } - - #[derive(Serialize, Deserialize, Clone, Debug)] - #[serde(rename_all = "camelCase")] - #[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, - pub force_offline: bool, // ... other settings ... - } - impl Default for Settings { - fn default() -> Self { - Self { - autostart: false, - max_download_threads: 4, - force_offline: false, - } - } - } - - // Strings are version names for a particular game - #[derive(Serialize, Clone, Deserialize)] - #[serde(tag = "type")] - #[native_model(id = 5, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] - pub enum GameDownloadStatus { - Remote {}, - SetupRequired { - version_name: String, - install_dir: String, - }, - Installed { - version_name: String, - install_dir: String, - }, - } - - // Stuff that shouldn't be synced to disk - #[derive(Clone, Serialize, Deserialize, Debug)] - pub enum ApplicationTransientStatus { - Queued { version_name: String }, - Downloading { version_name: String }, - Uninstalling {}, - Updating { version_name: String }, - Validating { version_name: String }, - Running {}, - } - - #[derive(serde::Serialize, Clone, Deserialize)] - #[native_model(id = 6, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] - pub struct DatabaseAuth { - pub private: String, - pub cert: String, - pub client_id: String, - pub web_token: Option, - } - - #[native_model(id = 8, version = 1)] - #[derive( - Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone, Copy, - )] - pub enum DownloadType { - Game, - Tool, - Dlc, - Mod, - } - - #[native_model(id = 7, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] - #[derive(Debug, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)] - #[serde(rename_all = "camelCase")] - pub struct DownloadableMetadata { - pub id: String, - pub version: Option, - pub download_type: DownloadType, - } - impl DownloadableMetadata { - pub fn new(id: String, version: Option, download_type: DownloadType) -> Self { - Self { - id, - version, - download_type, - } - } - } - - #[native_model(id = 1, version = 1)] - #[derive(Serialize, Deserialize, Clone, Default)] - pub struct Database { - #[serde(default)] - pub settings: Settings, - pub auth: Option, - pub base_url: String, - pub applications: DatabaseApplications, - pub prev_database: Option, - pub cache_dir: PathBuf, - } - } - - mod v2 { - use std::{collections::HashMap, path::PathBuf}; - - use serde_with::serde_as; - - use super::{Deserialize, Serialize, native_model, v1}; - - #[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde, from = v1::Database)] - #[derive(Serialize, Deserialize, Clone, Default)] - pub struct Database { - #[serde(default)] - pub settings: v1::Settings, - pub auth: Option, - pub base_url: String, - pub applications: v1::DatabaseApplications, - #[serde(skip)] - pub prev_database: Option, - pub cache_dir: PathBuf, - pub compat_info: Option, - } - - #[native_model(id = 9, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] - #[derive(Serialize, Deserialize, Clone, Default)] - - pub struct DatabaseCompatInfo { - pub umu_installed: bool, - } - - impl From for Database { - fn from(value: v1::Database) -> Self { - Self { - settings: value.settings, - auth: value.auth, - base_url: value.base_url, - applications: value.applications, - prev_database: value.prev_database, - cache_dir: value.cache_dir, - compat_info: None, - } - } - } - // Strings are version names for a particular game - #[derive(Serialize, Clone, Deserialize, Debug)] - #[serde(tag = "type")] - #[native_model(id = 5, version = 2, with = native_model::rmp_serde_1_3::RmpSerde, from = v1::GameDownloadStatus)] - pub enum GameDownloadStatus { - Remote {}, - SetupRequired { - version_name: String, - install_dir: String, - }, - Installed { - version_name: String, - install_dir: String, - }, - PartiallyInstalled { - version_name: String, - install_dir: String, - }, - } - impl From for GameDownloadStatus { - fn from(value: v1::GameDownloadStatus) -> Self { - match value { - v1::GameDownloadStatus::Remote {} => Self::Remote {}, - v1::GameDownloadStatus::SetupRequired { - version_name, - install_dir, - } => Self::SetupRequired { - version_name, - install_dir, - }, - v1::GameDownloadStatus::Installed { - version_name, - install_dir, - } => Self::Installed { - version_name, - install_dir, - }, - } - } - } - #[serde_as] - #[derive(Serialize, Clone, Deserialize, Default)] - #[serde(rename_all = "camelCase")] - #[native_model(id = 3, version = 2, with = native_model::rmp_serde_1_3::RmpSerde, from=v1::DatabaseApplications)] - pub struct DatabaseApplications { - pub install_dirs: Vec, - // Guaranteed to exist if the game also exists in the app state map - pub game_statuses: HashMap, - - pub game_versions: HashMap>, - pub installed_game_version: HashMap, - - #[serde(skip)] - pub transient_statuses: - HashMap, - } - impl From for DatabaseApplications { - fn from(value: v1::DatabaseApplications) -> Self { - Self { - game_statuses: value - .game_statuses - .into_iter() - .map(|x| (x.0, x.1.into())) - .collect::>(), - install_dirs: value.install_dirs, - game_versions: value.game_versions, - installed_game_version: value.installed_game_version, - transient_statuses: value.transient_statuses, - } - } - } - } - mod v3 { - use std::path::PathBuf; - - use super::{Deserialize, Serialize, native_model, v1, v2}; - #[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde, from = v2::Database)] - #[derive(Serialize, Deserialize, Clone, Default)] - pub struct Database { - #[serde(default)] - pub settings: v1::Settings, - pub auth: Option, - pub base_url: String, - pub applications: v2::DatabaseApplications, - #[serde(skip)] - pub prev_database: Option, - pub cache_dir: PathBuf, - pub compat_info: Option, - } - - impl From for Database { - fn from(value: v2::Database) -> Self { - Self { - settings: value.settings, - auth: value.auth, - base_url: value.base_url, - applications: value.applications.into(), - prev_database: value.prev_database, - cache_dir: value.cache_dir, - compat_info: None, - } - } - } - } - - impl Database { - pub fn new>( - games_base_dir: T, - prev_database: Option, - cache_dir: PathBuf, - ) -> Self { - Self { - applications: DatabaseApplications { - install_dirs: vec![games_base_dir.into()], - game_statuses: HashMap::new(), - game_versions: HashMap::new(), - installed_game_version: HashMap::new(), - transient_statuses: HashMap::new(), - }, - prev_database, - base_url: String::new(), - auth: None, - settings: Settings::default(), - cache_dir, - compat_info: None, - } - } - } - impl DatabaseAuth { - pub fn new( - private: String, - cert: String, - client_id: String, - web_token: Option, - ) -> Self { - Self { - private, - cert, - client_id, - web_token, - } - } - } -} diff --git a/src-tauri/database/src/models/mod.rs b/src-tauri/database/src/models/mod.rs new file mode 100644 index 0000000..a3b7721 --- /dev/null +++ b/src-tauri/database/src/models/mod.rs @@ -0,0 +1,110 @@ +mod v1; +mod v2; +mod v3; +mod v4; + +use std::{hash::Hash, path::PathBuf}; + +use native_model::native_model; +use serde::{Deserialize, Serialize}; + +use std::collections::HashMap; + +use crate::db::DropDatabaseSerializer; + + +// NOTE: Within each version, you should NEVER use these types. +// Declare it using the actual version that it is from, i.e. v1::Settings rather than just Settings from here + +pub type GameVersion = v1::GameVersion; +pub type Database = v4::Database; +pub type Settings = v1::Settings; +pub type DatabaseAuth = v1::DatabaseAuth; + +pub type GameDownloadStatus = v2::GameDownloadStatus; +pub type ApplicationTransientStatus = v1::ApplicationTransientStatus; +/** + * Need to be universally accessible by the ID, and the version is just a couple sprinkles on top + */ +pub type DownloadableMetadata = v1::DownloadableMetadata; +pub type DownloadType = v1::DownloadType; +pub type DatabaseApplications = v2::DatabaseApplications; +// pub type DatabaseCompatInfo = v2::DatabaseCompatInfo; + +pub type Game = v1::Game; + +impl Game { + pub fn id(&self) -> &String { + &self.id + } +} + +pub type DatabaseInterface = + rustbreak::Database; + +pub type LibraryMetadata = v1::LibraryMetadata; +pub type LibraryProviderMetadata = v1::LibraryProviderMetadata; +pub type ProviderType = v1::ProviderType; + +pub type Collection = v1::Collection; +pub type CollectionObject = v1::CollectionObject; + +impl PartialEq for DownloadableMetadata { + fn eq(&self, other: &Self) -> bool { + self.id == other.id && self.download_type == other.download_type + } +} +impl Hash for DownloadableMetadata { + fn hash(&self, state: &mut H) { + self.id.hash(state); + self.download_type.hash(state); + } +} +impl LibraryProviderMetadata { + pub fn provider(&self) -> &ProviderType { + &self.provider + } + pub fn id(&self) -> usize { + self.id + } + pub fn name(&self) -> &String { + &self.name + } +} + +impl Database { + pub fn new>( + games_base_dir: T, + prev_database: Option, + cache_dir: PathBuf, + ) -> Self { + Self { + prev_database, + settings: Settings::default(), + cache_dir, + compat_info: None, + library: v1::LibraryMetadata { providers: vec![] }, + } + } +} +impl DatabaseAuth { + pub fn new( + private: String, + cert: String, + client_id: String, + web_token: Option, + ) -> Self { + Self { + private, + cert, + client_id, + web_token, + } + } +} + +impl LibraryMetadata { + pub fn providers(&self) -> &Vec { + &self.providers + } +} \ No newline at end of file diff --git a/src-tauri/database/src/models/v1.rs b/src-tauri/database/src/models/v1.rs new file mode 100644 index 0000000..3a6e73e --- /dev/null +++ b/src-tauri/database/src/models/v1.rs @@ -0,0 +1,201 @@ +use bitcode::{Decode, Encode}; +use serde_with::serde_as; +use std::{collections::HashMap, path::PathBuf}; + +use crate::{models::v1, platform::Platform}; + +use super::{Deserialize, Serialize, native_model}; + +fn default_template() -> String { + "{}".to_owned() +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +#[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, + + pub platform: Platform, + + pub launch_command: String, + pub launch_args: Vec, + #[serde(default = "default_template")] + pub launch_command_template: String, + + pub setup_command: String, + pub setup_args: Vec, + #[serde(default = "default_template")] + pub setup_command_template: String, + + pub only_setup: bool, + + pub version_index: usize, + pub delta: bool, + + pub umu_id_override: Option, +} + +#[serde_as] +#[derive(Serialize, Clone, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +#[native_model(id = 3, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] +pub struct DatabaseApplications { + pub install_dirs: Vec, + // Guaranteed to exist if the game also exists in the app state map + pub game_statuses: HashMap, + pub game_versions: HashMap>, + pub installed_game_version: HashMap, + + #[serde(skip)] + pub transient_statuses: HashMap, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +#[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, + pub force_offline: bool, // ... other settings ... +} +impl Default for Settings { + fn default() -> Self { + Self { + autostart: false, + max_download_threads: 4, + force_offline: false, + } + } +} + +// Strings are version names for a particular game +#[derive(Serialize, Clone, Deserialize, Debug)] +#[serde(tag = "type")] +#[native_model(id = 5, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] +pub enum GameDownloadStatus { + Remote {}, + SetupRequired { + version_name: String, + install_dir: String, + }, + Installed { + version_name: String, + install_dir: String, + }, +} + +// Stuff that shouldn't be synced to disk +#[derive(Clone, Serialize, Deserialize, Debug)] +pub enum ApplicationTransientStatus { + Queued { version_name: String }, + Downloading { version_name: String }, + Uninstalling {}, + Updating { version_name: String }, + Validating { version_name: String }, + Running {}, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default, Encode, Decode)] +#[native_model(id = 6, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] +pub struct DatabaseAuth { + pub private: String, + pub cert: String, + pub client_id: String, + pub web_token: Option, +} + +#[native_model(id = 8, version = 1)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone, Copy)] +pub enum DownloadType { + Game, + Tool, + Dlc, + Mod, +} + +#[native_model(id = 7, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] +#[derive(Debug, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DownloadableMetadata { + pub id: String, + pub version: Option, + pub download_type: v1::DownloadType, +} +impl DownloadableMetadata { + pub fn new(id: String, version: Option, download_type: v1::DownloadType) -> Self { + Self { + id, + version, + download_type, + } + } +} + +#[native_model(id = 1, version = 1)] +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct Database { + #[serde(default)] + pub settings: Settings, + pub auth: Option, + pub base_url: String, + pub applications: v1::DatabaseApplications, + pub prev_database: Option, + pub cache_dir: PathBuf, +} +#[native_model(id = 15, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] +#[derive(Serialize, Deserialize, Debug, Clone, Encode, Decode, Default)] +pub struct LibraryMetadata { + pub(crate) providers: Vec +} + + +#[native_model(id = 11, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] +#[derive(Serialize, Deserialize, Debug, Clone, Encode, Decode)] +pub struct LibraryProviderMetadata { + pub(crate) id: usize, + pub(crate) name: String, + pub(crate) provider: v1::ProviderType +} +#[native_model(id = 10, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] +#[derive(Serialize, Deserialize, Debug, Clone, Encode, Decode)] +pub enum ProviderType { + Drop(v1::DatabaseAuth), +} +#[native_model(id = 12, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] +#[derive(Serialize, Deserialize, Debug, Clone, Encode, Decode)] +pub struct Game { + pub library_id: LibraryProviderMetadata, + pub(crate) id: String, + m_name: String, + m_short_description: String, + m_description: String, + // mDevelopers + // mPublishers + m_icon_object_id: String, + m_banner_object_id: String, + m_cover_object_id: String, + m_image_library_object_ids: Vec, + m_image_carousel_object_ids: Vec, +} + +#[native_model(id = 13, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +#[serde(rename_all = "camelCase")] +pub struct Collection { + id: String, + name: String, + is_default: bool, + user_id: String, + entries: Vec, +} + +#[native_model(id = 14, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct CollectionObject { + collection_id: String, + game_id: String, + game: Game, +} diff --git a/src-tauri/database/src/models/v2.rs b/src-tauri/database/src/models/v2.rs new file mode 100644 index 0000000..ff8897a --- /dev/null +++ b/src-tauri/database/src/models/v2.rs @@ -0,0 +1,111 @@ + use std::{collections::HashMap, path::PathBuf}; + + use serde_with::serde_as; + + use super::{Deserialize, Serialize, native_model, v1}; + + #[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde, from = v1::Database)] + #[derive(Serialize, Deserialize, Clone, Default)] + pub struct Database { + #[serde(default)] + pub settings: v1::Settings, + pub auth: Option, + pub base_url: String, + pub applications: v1::DatabaseApplications, + #[serde(skip)] + pub prev_database: Option, + pub cache_dir: PathBuf, + pub compat_info: Option, + } + + #[native_model(id = 9, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)] + #[derive(Serialize, Deserialize, Clone, Default)] + + pub struct DatabaseCompatInfo { + pub umu_installed: bool, + } + + impl From for Database { + fn from(value: v1::Database) -> Self { + Self { + settings: value.settings, + auth: value.auth, + base_url: value.base_url, + applications: value.applications, + prev_database: value.prev_database, + cache_dir: value.cache_dir, + compat_info: None, + } + } + } + // Strings are version names for a particular game + #[derive(Serialize, Clone, Deserialize, Debug)] + #[serde(tag = "type")] + #[native_model(id = 5, version = 2, with = native_model::rmp_serde_1_3::RmpSerde, from = v1::GameDownloadStatus)] + pub enum GameDownloadStatus { + Remote {}, + SetupRequired { + version_name: String, + install_dir: String, + }, + Installed { + version_name: String, + install_dir: String, + }, + PartiallyInstalled { + version_name: String, + install_dir: String, + }, + } + impl From for GameDownloadStatus { + fn from(value: v1::GameDownloadStatus) -> Self { + match value { + v1::GameDownloadStatus::Remote {} => Self::Remote {}, + v1::GameDownloadStatus::SetupRequired { + version_name, + install_dir, + } => Self::SetupRequired { + version_name, + install_dir, + }, + v1::GameDownloadStatus::Installed { + version_name, + install_dir, + } => Self::Installed { + version_name, + install_dir, + }, + } + } + } + #[serde_as] + #[derive(Serialize, Clone, Deserialize, Default)] + #[serde(rename_all = "camelCase")] + #[native_model(id = 3, version = 2, with = native_model::rmp_serde_1_3::RmpSerde, from=v1::DatabaseApplications)] + pub struct DatabaseApplications { + pub install_dirs: Vec, + // Guaranteed to exist if the game also exists in the app state map + pub game_statuses: HashMap, + + pub game_versions: HashMap>, + pub installed_game_version: HashMap, + + #[serde(skip)] + pub transient_statuses: + HashMap, + } + impl From for DatabaseApplications { + fn from(value: v1::DatabaseApplications) -> Self { + Self { + game_statuses: value + .game_statuses + .into_iter() + .map(|x| (x.0, x.1.into())) + .collect::>(), + install_dirs: value.install_dirs, + game_versions: value.game_versions, + installed_game_version: value.installed_game_version, + transient_statuses: value.transient_statuses, + } + } + } diff --git a/src-tauri/database/src/models/v3.rs b/src-tauri/database/src/models/v3.rs new file mode 100644 index 0000000..7728dcd --- /dev/null +++ b/src-tauri/database/src/models/v3.rs @@ -0,0 +1,32 @@ +use std::{collections::HashMap, path::PathBuf}; + +use serde_with::serde_as; + +use super::{Deserialize, Serialize, native_model, v1, v2}; +#[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde, from = v2::Database)] +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct Database { + #[serde(default)] + pub settings: v1::Settings, + pub auth: Option, + pub base_url: String, + pub applications: v2::DatabaseApplications, + #[serde(skip)] + pub prev_database: Option, + pub cache_dir: PathBuf, + pub compat_info: Option, +} + +impl From for Database { + fn from(value: v2::Database) -> Self { + Self { + settings: value.settings, + auth: value.auth, + base_url: value.base_url, + applications: value.applications.into(), + prev_database: value.prev_database, + cache_dir: value.cache_dir, + compat_info: None, + } + } +} \ No newline at end of file diff --git a/src-tauri/database/src/models/v4.rs b/src-tauri/database/src/models/v4.rs new file mode 100644 index 0000000..74f0d7e --- /dev/null +++ b/src-tauri/database/src/models/v4.rs @@ -0,0 +1,43 @@ +use std::path::PathBuf; + +use native_model::native_model; +use serde::{Deserialize, Serialize}; + +use crate::models::{ + v1::{self, LibraryMetadata}, + v2, v3, +}; + +#[native_model(id = 1, version = 4, with = native_model::rmp_serde_1_3::RmpSerde, from = v3::Database)] +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct Database { + #[serde(default)] + pub settings: v1::Settings, + #[serde(skip)] + pub prev_database: Option, + pub cache_dir: PathBuf, + pub compat_info: Option, + pub library: v1::LibraryMetadata, +} + +impl From for Database { + fn from(value: v3::Database) -> Self { + Self { + settings: value.settings, + prev_database: value.prev_database, + cache_dir: value.cache_dir, + compat_info: value.compat_info, + library: v1::LibraryMetadata { + providers: if let Some(auth) = value.auth { + vec![v1::LibraryProviderMetadata { + id: 0, + name: String::from("Default"), + provider: v1::ProviderType::Drop(auth), + }] + } else { + vec![] + }, + }, + } + } +} diff --git a/src-tauri/drop-consts/Cargo.toml b/src-tauri/drop-consts/Cargo.toml new file mode 100644 index 0000000..3485b2c --- /dev/null +++ b/src-tauri/drop-consts/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "drop-consts" +version = "0.1.0" +edition = "2024" + +[dependencies] +dirs = "6.0.0" diff --git a/src-tauri/drop-consts/src/lib.rs b/src-tauri/drop-consts/src/lib.rs new file mode 100644 index 0000000..d7be8aa --- /dev/null +++ b/src-tauri/drop-consts/src/lib.rs @@ -0,0 +1,16 @@ +use std::{path::PathBuf, sync::LazyLock}; + +#[cfg(not(debug_assertions))] +pub const DATA_ROOT_PREFIX: &str = "drop"; +#[cfg(debug_assertions)] +pub const DATA_ROOT_PREFIX: &str = "drop-debug"; + +pub const DROP_DATA_PATH: &str = ".dropdata"; + +pub const RETRY_COUNT: usize = 3; + +pub const TARGET_BUCKET_SIZE: usize = 63 * 1000 * 1000; +pub const MAX_FILES_PER_BUCKET: usize = (1024 / 4) - 1; + +pub const UMU_BASE_LAUNCHER_EXECUTABLE: &str = "umu-run"; +pub const UMU_INSTALL_DIRS: [&str; 4] = ["/app/share", "/use/local/share", "/usr/share", "/opt"]; diff --git a/src-tauri/games/Cargo.toml b/src-tauri/games/Cargo.toml index c50507b..1c85f81 100644 --- a/src-tauri/games/Cargo.toml +++ b/src-tauri/games/Cargo.toml @@ -22,5 +22,8 @@ sysinfo = "0.37.2" tauri = "2.8.5" throttle_my_fn = "0.2.6" utils = { version = "0.1.0", path = "../utils" } -native_model = { version = "0.6.4", features = ["rmp_serde_1_3"], git = "https://github.com/Drop-OSS/native_model.git"} +native_model = { version = "0.6.4", features = [ + "rmp_serde_1_3", +], git = "https://github.com/Drop-OSS/native_model.git" } serde_json = "1.0.145" +drop-consts = { version = "0.1.0", path = "../drop-consts" } diff --git a/src-tauri/games/src/collections/collection.rs b/src-tauri/games/src/collections/collection.rs index 2eef9b2..e4f6b9a 100644 --- a/src-tauri/games/src/collections/collection.rs +++ b/src-tauri/games/src/collections/collection.rs @@ -1,8 +1,7 @@ use bitcode::{Decode, Encode}; +use database::models::Game; use serde::{Deserialize, Serialize}; -use crate::library::Game; - pub type Collections = Vec; #[derive(Serialize, Deserialize, Debug, Clone, Default, Encode, Decode)] @@ -15,7 +14,7 @@ pub struct Collection { entries: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone, Default, Encode, Decode)] +#[derive(Serialize, Deserialize, Debug, Clone, Encode, Decode)] #[serde(rename_all = "camelCase")] pub struct CollectionObject { collection_id: String, diff --git a/src-tauri/games/src/downloads/download_agent.rs b/src-tauri/games/src/downloads/download_agent.rs index 1d54ca0..e6da73a 100644 --- a/src-tauri/games/src/downloads/download_agent.rs +++ b/src-tauri/games/src/downloads/download_agent.rs @@ -9,6 +9,7 @@ use download_manager::util::download_thread_control_flag::{ DownloadThreadControl, DownloadThreadControlFlag, }; use download_manager::util::progress_object::{ProgressHandle, ProgressObject}; +use drop_consts::{MAX_FILES_PER_BUCKET, RETRY_COUNT, TARGET_BUCKET_SIZE}; use log::{debug, error, info, warn}; use rayon::ThreadPoolBuilder; use remote::auth::generate_authorization_header; @@ -39,11 +40,6 @@ use crate::state::GameStatusManager; use super::download_logic::download_game_bucket; use super::drop_data::DropData; -static RETRY_COUNT: usize = 3; - -const TARGET_BUCKET_SIZE: usize = 63 * 1000 * 1000; -const MAX_FILES_PER_BUCKET: usize = (1024 / 4) - 1; - pub struct GameDownloadAgent { pub id: String, pub version: String, diff --git a/src-tauri/games/src/downloads/drop_data.rs b/src-tauri/games/src/downloads/drop_data.rs index 0c952cf..1e8ebc2 100644 --- a/src-tauri/games/src/downloads/drop_data.rs +++ b/src-tauri/games/src/downloads/drop_data.rs @@ -5,14 +5,13 @@ use std::{ path::{Path, PathBuf}, }; +use drop_consts::DROP_DATA_PATH; use log::error; use native_model::{Decode, Encode}; use utils::lock; pub type DropData = v1::DropData; -pub static DROP_DATA_PATH: &str = ".dropdata"; - pub mod v1 { use std::{collections::HashMap, path::PathBuf, sync::Mutex}; diff --git a/src-tauri/games/src/library.rs b/src-tauri/games/src/library.rs index c58d3c0..ff2bc18 100644 --- a/src-tauri/games/src/library.rs +++ b/src-tauri/games/src/library.rs @@ -1,7 +1,5 @@ -use bitcode::{Decode, Encode}; use database::{ - ApplicationTransientStatus, Database, DownloadableMetadata, GameDownloadStatus, GameVersion, - borrow_db_checked, borrow_db_mut_checked, + borrow_db_checked, borrow_db_mut_checked, models::Game, ApplicationTransientStatus, Database, DownloadableMetadata, GameDownloadStatus, GameVersion }; use log::{debug, error, warn}; use remote::{ @@ -33,26 +31,6 @@ impl FetchGameStruct { } } -#[derive(Serialize, Deserialize, Clone, Debug, Default, Encode, Decode)] -#[serde(rename_all = "camelCase")] -pub struct Game { - id: String, - m_name: String, - m_short_description: String, - m_description: String, - // mDevelopers - // mPublishers - m_icon_object_id: String, - m_banner_object_id: String, - m_cover_object_id: String, - m_image_library_object_ids: Vec, - m_image_carousel_object_ids: Vec, -} -impl Game { - pub fn id(&self) -> &String { - &self.id - } -} #[derive(serde::Serialize, Clone)] pub struct GameUpdateEvent { pub game_id: String, diff --git a/src-tauri/games/src/scan.rs b/src-tauri/games/src/scan.rs index 4a711fc..4b8f1cb 100644 --- a/src-tauri/games/src/scan.rs +++ b/src-tauri/games/src/scan.rs @@ -1,10 +1,11 @@ use std::fs; use database::{DownloadType, DownloadableMetadata, borrow_db_mut_checked}; +use drop_consts::DROP_DATA_PATH; use log::warn; use crate::{ - downloads::drop_data::{DROP_DATA_PATH, DropData}, + downloads::drop_data::DropData, library::set_partially_installed_db, }; diff --git a/src-tauri/games/src/state.rs b/src-tauri/games/src/state.rs index 08ed228..4a533a3 100644 --- a/src-tauri/games/src/state.rs +++ b/src-tauri/games/src/state.rs @@ -1,4 +1,4 @@ -use database::models::data::{ +use database::{ ApplicationTransientStatus, Database, DownloadType, DownloadableMetadata, GameDownloadStatus, }; diff --git a/src-tauri/library/Cargo.toml b/src-tauri/library/Cargo.toml new file mode 100644 index 0000000..88dd321 --- /dev/null +++ b/src-tauri/library/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "library" +version = "0.1.0" +edition = "2024" + +[dependencies] +async-trait = "0.1.89" +client = { version = "0.1.0", path = "../client" } +database = { version = "0.1.0", path = "../database" } +futures = "0.3.31" +itertools = "0.14.0" +log = "0.4.28" +remote = { version = "0.1.0", path = "../remote" } +serde = { version = "1.0.228", features = ["derive"] } +serde_with = "3.15.0" +tauri = "2.8.5" +url = "2.5.7" diff --git a/src-tauri/library/src/drop/drop.rs b/src-tauri/library/src/drop/drop.rs new file mode 100644 index 0000000..74486e9 --- /dev/null +++ b/src-tauri/library/src/drop/drop.rs @@ -0,0 +1,134 @@ +use std::sync::nonpoison::Mutex; + +use async_trait::async_trait; +use client::app_state::AppState; +use database::{ + DatabaseAuth, GameDownloadStatus, borrow_db_mut_checked, + models::{Collection, Game, LibraryProviderMetadata}, +}; +use log::warn; +use remote::{ + auth::generate_authorization_header, + cache::{cache_object, get_cached_object, get_cached_object_db}, + error::{DropServerError, RemoteAccessError}, + requests::generate_url, + utils::DROP_CLIENT_ASYNC, +}; +use url::Url; + +use crate::{error::LibraryError, provider::LibraryProvider}; + +pub struct DropLibraryProvider { + metadata: LibraryProviderMetadata, + auth: DatabaseAuth, + base_url: Url, +} + +impl DropLibraryProvider { + pub fn new(metadata: LibraryProviderMetadata, auth: DatabaseAuth, base_url: Url) -> Self { + Self { + metadata, + auth, + base_url, + } + } +} +#[async_trait] +impl LibraryProvider for DropLibraryProvider { + async fn get_library( + &self, + state: &tauri::State<'_, Mutex>, + ) -> Result, LibraryError> { + // let do_hard_refresh = hard_fresh.unwrap_or(false); + if + /* !do_hard_refresh &&*/ + let Ok(library) = get_cached_object("library") { + return Ok(library); + } + + let client = DROP_CLIENT_ASYNC.clone(); + let response = generate_url(&["/api/v1/client/user/library"], &[], self.base_url)?; + let response = client + .get(response) + .header("Authorization", generate_authorization_header(self.auth)) + .send() + .await + .map_err(|e| LibraryError::FetchError(RemoteAccessError::FetchError(e.into())))?; + + if response.status() != 200 { + let err = response.json().await.unwrap_or(DropServerError { + status_code: 500, + status_message: "Invalid response from server.".to_owned(), + }); + warn!("{err:?}"); + return Err(LibraryError::FetchError( + RemoteAccessError::InvalidResponse(err), + )); + } + + let mut games: Vec = response + .json() + .await + .map_err(|e| RemoteAccessError::FetchError(e.into()))?; + + let mut handle = state.lock(); + + let mut db_handle = borrow_db_mut_checked(); + + for game in &games { + handle.games_mut().insert(game.id().clone(), game.clone()); + if !db_handle.applications.game_statuses.contains_key(game.id()) { + db_handle + .applications + .game_statuses + .insert(game.id().clone(), GameDownloadStatus::Remote {}); + } + } + + // Add games that are installed but no longer in library + for meta in db_handle.applications.installed_game_version.values() { + if games.iter().any(|e| *e.id() == meta.id) { + continue; + } + // We should always have a cache of the object + // Pass db_handle because otherwise we get a gridlock + let game = match get_cached_object_db::(&meta.id.clone(), &db_handle) { + Ok(game) => game, + Err(err) => { + warn!( + "{} is installed, but encountered error fetching its error: {}.", + meta.id, err + ); + continue; + } + }; + games.push(game); + } + + drop(handle); + drop(db_handle); + cache_object("library", &games)?; + + Ok(games) + } + async fn get_collections(&self) -> Result, LibraryError> { + todo!() + } + + fn install(&mut self, game_id: String) { + todo!() + } + + fn uninstall(&mut self, game_id: String) { + todo!() + } + + fn metadata(&self) -> LibraryProviderMetadata { + todo!() + } +} + +async fn fetch_library_logic(state: &Mutex) -> Result, RemoteAccessError> {} +async fn fetch_library_logic_offline() -> Result, RemoteAccessError> { + todo!() +} diff --git a/src-tauri/library/src/drop/mod.rs b/src-tauri/library/src/drop/mod.rs new file mode 100644 index 0000000..c5c7fbb --- /dev/null +++ b/src-tauri/library/src/drop/mod.rs @@ -0,0 +1 @@ +pub mod drop; \ No newline at end of file diff --git a/src-tauri/library/src/error.rs b/src-tauri/library/src/error.rs new file mode 100644 index 0000000..11dd139 --- /dev/null +++ b/src-tauri/library/src/error.rs @@ -0,0 +1,31 @@ +use std::fmt::Display; + +use database::models::LibraryProviderMetadata; +use remote::error::RemoteAccessError; +use serde_with::SerializeDisplay; + +#[derive(Debug, SerializeDisplay)] +pub enum LibraryError { + ProviderConnection(ProviderError), + FetchError(RemoteAccessError) +} +#[derive(Debug, SerializeDisplay)] +pub struct ProviderError { + provider: LibraryProviderMetadata +} + +impl Display for LibraryError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} +impl Display for ProviderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} +impl From for LibraryError { + fn from(value: RemoteAccessError) -> Self { + LibraryError::FetchError(value) + } +} \ No newline at end of file diff --git a/src-tauri/library/src/lib.rs b/src-tauri/library/src/lib.rs new file mode 100644 index 0000000..ef0723f --- /dev/null +++ b/src-tauri/library/src/lib.rs @@ -0,0 +1,69 @@ +#![feature(nonpoison_mutex)] +#![feature(sync_nonpoison)] + +use std::sync::{LazyLock, nonpoison::Mutex}; + +use client::app_state::AppState; +use database::{borrow_db_checked, models::{Game, LibraryProviderMetadata, ProviderType}}; +use futures::{StreamExt, future::join_all}; +use itertools::Itertools; + +use crate::{drop::drop::DropLibraryProvider, error::LibraryError, provider::LibraryProvider}; + +pub mod drop; +pub mod error; +pub mod provider; + +pub static LIBRARY: LazyLock = LazyLock::new(Library::init); + +pub struct Library { + providers: Vec>, +} + +impl Library { + pub fn init() -> Self { + let metadata = borrow_db_checked(); + let library = &metadata.library; + let providers = library.providers().iter().map(|provider| { + Library::construct(provider) + }).collect(); + Self { + providers + } + } + fn construct(provider: &LibraryProviderMetadata) -> Box { + todo!() + } + pub async fn get_library( + &self, + state: &tauri::State<'_, Mutex>, + ) -> (Vec, Vec) { + let res = join_all( + self.providers + .iter() + .map(|provider| provider.get_library(state)), + ) + .await + .into_iter() + .fold( + (Vec::new(), Vec::new()), + |(mut acc_ok, mut acc_err), res| { + match res { + Ok(games) => acc_ok.extend(games), + Err(e) => acc_err.push(e), + }; + (acc_ok, acc_err) + }, + ); + res + } + pub fn add(&mut self, provider: LibraryProviderMetadata) { + let new_provider = Box::new(match provider.provider() { + ProviderType::Drop(_) => DropLibraryProvider::new(provider), + }); + self.providers.push(new_provider); + } + pub fn remove(&mut self, id: usize) { + self.providers.retain(|v| v.metadata().id() != id); + } +} diff --git a/src-tauri/library/src/provider.rs b/src-tauri/library/src/provider.rs new file mode 100644 index 0000000..7e9dfb8 --- /dev/null +++ b/src-tauri/library/src/provider.rs @@ -0,0 +1,16 @@ +use std::sync::nonpoison::Mutex; + +use async_trait::async_trait; +use client::app_state::AppState; +use database::models::{Collection, Game, LibraryProviderMetadata}; + +use crate::error::LibraryError; + +#[async_trait] +pub trait LibraryProvider: Sync + Send { + async fn get_library(&self, state: &tauri::State<'_, Mutex>) -> Result, LibraryError>; + async fn get_collections(&self) -> Result, LibraryError>; + fn install(&mut self, game_id: String); + fn uninstall(&mut self, game_id: String); + fn metadata(&self) -> LibraryProviderMetadata; +} \ No newline at end of file diff --git a/src-tauri/remote/src/auth.rs b/src-tauri/remote/src/auth.rs index 9f1ab98..41a0dff 100644 --- a/src-tauri/remote/src/auth.rs +++ b/src-tauri/remote/src/auth.rs @@ -60,22 +60,17 @@ impl From for DatabaseAuth { } } -pub fn generate_authorization_header() -> String { - let certs = { - let db = borrow_db_checked(); - db.auth.clone().expect("Authorisation not initialised") - }; - +pub fn generate_authorization_header(auth: DatabaseAuth) -> String { let nonce = Utc::now().timestamp_millis().to_string(); let signature = - sign_nonce(certs.private, nonce.clone()).expect("Failed to generate authorisation header"); + sign_nonce(auth.private, nonce.clone()).expect("Failed to generate authorisation header"); - format!("Nonce {} {} {}", certs.client_id, nonce, signature) + format!("Nonce {} {} {}", auth.client_id, nonce, signature) } -pub async fn fetch_user() -> Result { - let response = make_authenticated_get(generate_url(&["/api/v1/client/user"], &[])?).await?; +pub async fn fetch_user(auth: DatabaseAuth, base_url: Url) -> Result { + let response = make_authenticated_get(generate_url(&["/api/v1/client/user"], &[], base_url)?, auth).await?; if response.status() != 200 { let err: DropServerError = response.json().await?; warn!("{err:?}"); @@ -93,12 +88,7 @@ pub async fn fetch_user() -> Result { .map_err(std::convert::Into::into) } -pub fn auth_initiate_logic(mode: String) -> Result { - let base_url = { - let db_lock = borrow_db_checked(); - Url::parse(&db_lock.base_url.clone())? - }; - +pub fn auth_initiate_logic(mode: String, base_url: Url) -> Result { let hostname = gethostname(); let endpoint = base_url.join("/api/v1/client/auth/initiate")?; @@ -127,26 +117,17 @@ pub fn auth_initiate_logic(mode: String) -> Result { Ok(response) } -pub async fn setup() -> (AppStatus, Option) { - let auth = { - let data = borrow_db_checked(); - data.auth.clone() - }; - - if auth.is_some() { - let user_result = match fetch_user().await { - Ok(data) => data, - Err(RemoteAccessError::FetchError(_)) => { - let user = get_cached_object::("user").ok(); - return (AppStatus::Offline, user); - } - Err(_) => return (AppStatus::SignedInNeedsReauth, None), - }; - if let Err(e) = cache_object("user", &user_result) { - warn!("Could not cache user object with error {e}"); +pub async fn setup(auth: DatabaseAuth, base_url: Url) -> (AppStatus, Option) { + let user_result = match fetch_user(auth, base_url).await { + Ok(data) => data, + Err(RemoteAccessError::FetchError(_)) => { + let user = get_cached_object::("user").ok(); + return (AppStatus::Offline, user); } - return (AppStatus::SignedIn, Some(user_result)); + Err(_) => return (AppStatus::SignedInNeedsReauth, None), + }; + if let Err(e) = cache_object("user", &user_result) { + warn!("Could not cache user object with error {e}"); } - - (AppStatus::SignedOut, None) + return (AppStatus::SignedIn, Some(user_result)); } diff --git a/src-tauri/remote/src/cache.rs b/src-tauri/remote/src/cache.rs index 3a21a0a..c53d5fb 100644 --- a/src-tauri/remote/src/cache.rs +++ b/src-tauri/remote/src/cache.rs @@ -17,7 +17,7 @@ macro_rules! offline { async move { if ::database::borrow_db_checked().settings.force_offline - || $var.lock().status == ::client::app_status::AppStatus::Offline { + || *$var.lock().status() == ::client::app_status::AppStatus::Offline { $func2( $( $arg ), *).await } else { $func1( $( $arg ), *).await diff --git a/src-tauri/remote/src/fetch_object.rs b/src-tauri/remote/src/fetch_object.rs index e80a043..228ca38 100644 --- a/src-tauri/remote/src/fetch_object.rs +++ b/src-tauri/remote/src/fetch_object.rs @@ -1,7 +1,8 @@ -use database::{DB, interface::DatabaseImpls}; +use database::{DB, DatabaseAuth, interface::DatabaseImpls}; use http::{Response, header::CONTENT_TYPE, response::Builder as ResponseBuilder}; use log::{debug, warn}; use tauri::UriSchemeResponder; +use url::Url; use crate::{error::CacheError, utils::DROP_CLIENT_ASYNC}; @@ -10,8 +11,8 @@ use super::{ cache::{ObjectCache, cache_object, get_cached_object}, }; -pub async fn fetch_object_wrapper(request: http::Request>, responder: UriSchemeResponder) { - match fetch_object(request).await { +pub async fn fetch_object_wrapper(request: http::Request>, responder: UriSchemeResponder, auth: DatabaseAuth, base_url: Url) { + match fetch_object(request, auth, base_url).await { Ok(r) => responder.respond(r), Err(e) => { warn!("Cache error: {e}"); @@ -27,6 +28,8 @@ pub async fn fetch_object_wrapper(request: http::Request>, responder: Ur pub async fn fetch_object( request: http::Request>, + auth: DatabaseAuth, + base_url: Url ) -> Result>, CacheError> { // Drop leading / let object_id = &request.uri().path()[1..]; @@ -38,9 +41,9 @@ pub async fn fetch_object( return cache_result.try_into(); } - let header = generate_authorization_header(); + let header = generate_authorization_header(auth); let client = DROP_CLIENT_ASYNC.clone(); - let url = format!("{}api/v1/client/object/{object_id}", DB.fetch_base_url()); + let url = format!("{}api/v1/client/object/{object_id}", base_url); let response = client.get(url).header("Authorization", header).send().await; match response { diff --git a/src-tauri/remote/src/requests.rs b/src-tauri/remote/src/requests.rs index df74269..a19ddda 100644 --- a/src-tauri/remote/src/requests.rs +++ b/src-tauri/remote/src/requests.rs @@ -1,4 +1,4 @@ -use database::{DB, interface::DatabaseImpls}; +use database::{DB, DatabaseAuth, interface::DatabaseImpls}; use url::Url; use crate::{ @@ -8,8 +8,9 @@ use crate::{ pub fn generate_url>( path_components: &[T], query: &[(T, T)], + base_url: Url ) -> Result { - let mut base_url = DB.fetch_base_url(); + let mut base_url = base_url.clone(); for endpoint in path_components { base_url = base_url.join(endpoint.as_ref())?; } @@ -22,10 +23,10 @@ pub fn generate_url>( Ok(base_url) } -pub async fn make_authenticated_get(url: Url) -> Result { +pub async fn make_authenticated_get(url: Url, auth: DatabaseAuth) -> Result { DROP_CLIENT_ASYNC .get(url) - .header("Authorization", generate_authorization_header()) + .header("Authorization", generate_authorization_header(auth)) .send() .await } diff --git a/src-tauri/remote/src/server_proto.rs b/src-tauri/remote/src/server_proto.rs index 54334df..b65dfb8 100644 --- a/src-tauri/remote/src/server_proto.rs +++ b/src-tauri/remote/src/server_proto.rs @@ -1,9 +1,10 @@ use std::str::FromStr; -use database::borrow_db_checked; +use database::{DatabaseAuth, borrow_db_checked}; use http::{Request, Response, StatusCode, Uri, uri::PathAndQuery}; -use log::{error, warn}; +use log::warn; use tauri::UriSchemeResponder; +use url::Url; use utils::webbrowser_open::webbrowser_open; use crate::utils::DROP_CLIENT_SYNC; @@ -27,8 +28,8 @@ pub async fn handle_server_proto_offline( .expect("Failed to build error response for proto offline")) } -pub async fn handle_server_proto_wrapper(request: Request>, responder: UriSchemeResponder) { - match handle_server_proto(request).await { +pub async fn handle_server_proto_wrapper(request: Request>, responder: UriSchemeResponder, auth: DatabaseAuth, base_url: Url) { + match handle_server_proto(request, auth, base_url).await { Ok(r) => responder.respond(r), Err(e) => { warn!("Cache error: {e}"); @@ -42,23 +43,16 @@ pub async fn handle_server_proto_wrapper(request: Request>, responder: U } } -async fn handle_server_proto(request: Request>) -> Result>, StatusCode> { - let db_handle = borrow_db_checked(); - let auth = match db_handle.auth.as_ref() { - Some(auth) => auth, - None => { - error!("Could not find auth in database"); - return Err(StatusCode::UNAUTHORIZED); - } - }; +async fn handle_server_proto( + request: Request>, + auth: DatabaseAuth, + base_url: Url, +) -> Result>, StatusCode> { let web_token = match &auth.web_token { Some(token) => token, None => return Err(StatusCode::UNAUTHORIZED), }; - let remote_uri = db_handle - .base_url - .parse::() - .expect("Failed to parse base url"); + let remote_uri = base_url.as_str().parse::().expect("Failed to parse base url"); let path = request.uri().path(); diff --git a/src-tauri/src/client.rs b/src-tauri/src/client.rs index 919421d..e9e4a8f 100644 --- a/src-tauri/src/client.rs +++ b/src-tauri/src/client.rs @@ -1,5 +1,6 @@ use std::sync::nonpoison::Mutex; +use client::app_state::AppState; use database::{borrow_db_checked, borrow_db_mut_checked}; use download_manager::DOWNLOAD_MANAGER; use log::{debug, error}; @@ -7,8 +8,6 @@ use tauri::AppHandle; use tauri_plugin_autostart::ManagerExt; use tauri_plugin_opener::OpenerExt; -use crate::AppState; - #[tauri::command] pub fn fetch_state(state: tauri::State<'_, Mutex>) -> Result { let guard = state.lock(); diff --git a/src-tauri/src/games.rs b/src-tauri/src/games.rs index cd72666..bb55f65 100644 --- a/src-tauri/src/games.rs +++ b/src-tauri/src/games.rs @@ -1,9 +1,9 @@ use std::sync::nonpoison::Mutex; -use database::{GameDownloadStatus, GameVersion, borrow_db_checked, borrow_db_mut_checked}; +use database::{GameDownloadStatus, GameVersion, borrow_db_checked, borrow_db_mut_checked, models::Game}; use games::{ downloads::error::LibraryError, - library::{FetchGameStruct, FrontendGameOptions, Game, get_current_meta, uninstall_game_logic}, + library::{FetchGameStruct, FrontendGameOptions, get_current_meta, uninstall_game_logic}, state::{GameStatusManager, GameStatusWithTransient}, }; use log::warn; @@ -18,7 +18,7 @@ use remote::{ }; use tauri::AppHandle; -use crate::AppState; +use client::app_state::AppState; #[tauri::command] pub async fn fetch_library( @@ -38,75 +38,13 @@ pub async fn fetch_library( pub async fn fetch_library_logic( state: tauri::State<'_, Mutex>, hard_fresh: Option, -) -> Result, RemoteAccessError> { - let do_hard_refresh = hard_fresh.unwrap_or(false); - if !do_hard_refresh && let Ok(library) = get_cached_object("library") { - return Ok(library); - } - - let client = DROP_CLIENT_ASYNC.clone(); - let response = generate_url(&["/api/v1/client/user/library"], &[])?; - let response = client - .get(response) - .header("Authorization", generate_authorization_header()) - .send() - .await?; - - if response.status() != 200 { - let err = response.json().await.unwrap_or(DropServerError { - status_code: 500, - status_message: "Invalid response from server.".to_owned(), - }); - warn!("{err:?}"); - return Err(RemoteAccessError::InvalidResponse(err)); - } - - let mut games: Vec = response.json().await?; - - let mut handle = state.lock(); - - let mut db_handle = borrow_db_mut_checked(); - - for game in &games { - handle.games.insert(game.id().clone(), game.clone()); - if !db_handle.applications.game_statuses.contains_key(game.id()) { - db_handle - .applications - .game_statuses - .insert(game.id().clone(), GameDownloadStatus::Remote {}); - } - } - - // Add games that are installed but no longer in library - for meta in db_handle.applications.installed_game_version.values() { - if games.iter().any(|e| *e.id() == meta.id) { - continue; - } - // We should always have a cache of the object - // Pass db_handle because otherwise we get a gridlock - let game = match get_cached_object_db::(&meta.id.clone(), &db_handle) { - Ok(game) => game, - Err(err) => { - warn!( - "{} is installed, but encountered error fetching its error: {}.", - meta.id, err - ); - continue; - } - }; - games.push(game); - } - - drop(handle); - drop(db_handle); - cache_object("library", &games)?; - - Ok(games) +) -> (Vec, Vec) { + } pub async fn fetch_library_logic_offline( _state: tauri::State<'_, Mutex>, _hard_refresh: Option, -) -> Result, RemoteAccessError> { +) -> (Vec, Vec) { let mut games: Vec = get_cached_object("library")?; let db_handle = borrow_db_checked(); @@ -144,7 +82,7 @@ pub async fn fetch_game_logic( .cloned(), }; - let game = state_handle.games.get(&id); + let game = state_handle.games().get(&id); if let Some(game) = game { let status = GameStatusManager::fetch_state(&id, &db_lock); @@ -183,7 +121,7 @@ pub async fn fetch_game_logic( let game: Game = response.json().await?; let mut state_handle = state.lock(); - state_handle.games.insert(id.clone(), game.clone()); + state_handle.games_mut().insert(id.clone(), game.clone()); let mut db_handle = borrow_db_mut_checked(); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3230973..879d5b5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -12,9 +12,11 @@ use std::{ sync::nonpoison::Mutex, time::SystemTime, }; -use ::client::{app_status::AppStatus, autostart::sync_autostart_on_startup, user::User}; +use ::client::{ + app_state::AppState, app_status::AppStatus, autostart::sync_autostart_on_startup, +}; use ::download_manager::DownloadManagerWrapper; -use ::games::{library::Game, scan::scan_install_dirs}; +use ::games::scan::scan_install_dirs; use ::process::ProcessManagerWrapper; use ::remote::{ auth::{self, HandshakeRequestBody, HandshakeResponse, generate_authorization_header}, @@ -36,7 +38,6 @@ use log4rs::{ config::{Appender, Root}, encode::pattern::PatternEncoder, }; -use serde::Serialize; use tauri::{ AppHandle, Manager, RunEvent, WindowEvent, menu::{Menu, MenuItem, PredefinedMenuItem}, @@ -67,14 +68,6 @@ use process::*; use remote::*; use settings::*; -#[derive(Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct AppState { - status: AppStatus, - user: Option, - games: HashMap, -} - async fn setup(handle: AppHandle) -> AppState { let logfile = FileAppender::builder() .encoder(Box::new(PatternEncoder::new( @@ -117,11 +110,7 @@ async fn setup(handle: AppHandle) -> AppState { scan_install_dirs(); if !is_set_up { - return AppState { - status: AppStatus::NotConfigured, - user: None, - games, - }; + return AppState::new(AppStatus::NotConfigured, None, games); } debug!("database is set up"); @@ -179,11 +168,7 @@ async fn setup(handle: AppHandle) -> AppState { warn!("failed to sync autostart state: {e}"); } - AppState { - status: app_status, - user, - games, - } + AppState::new(app_status, user, games) } pub fn custom_panic_handler(e: &PanicHookInfo) -> Option<()> { @@ -473,14 +458,12 @@ pub async fn recieve_handshake(app: AppHandle, path: String) { let mut state_lock = app_state.lock(); - state_lock.status = app_status; - state_lock.user = user; + *state_lock.status_mut() = app_status; + *state_lock.user_mut() = user; let _ = clear_cached_object("collections"); let _ = clear_cached_object("library"); - drop(state_lock); - app_emit!(&app, "auth/finished", ()); } diff --git a/src-tauri/src/process.rs b/src-tauri/src/process.rs index fb5f153..67e47e1 100644 --- a/src-tauri/src/process.rs +++ b/src-tauri/src/process.rs @@ -1,17 +1,11 @@ -use std::sync::nonpoison::Mutex; - use process::{PROCESS_MANAGER, error::ProcessError}; use tauri::AppHandle; use tauri_plugin_opener::OpenerExt; -use crate::AppState; - #[tauri::command] pub fn launch_game( id: String, - state: tauri::State<'_, Mutex>, ) -> Result<(), ProcessError> { - let state_lock = state.lock(); let mut process_manager_lock = PROCESS_MANAGER.lock(); //let meta = DownloadableMetadata { // id, @@ -24,9 +18,6 @@ pub fn launch_game( Err(e) => return Err(e), } - drop(process_manager_lock); - drop(state_lock); - Ok(()) } diff --git a/src-tauri/src/remote.rs b/src-tauri/src/remote.rs index 2d4585f..af38c1d 100644 --- a/src-tauri/src/remote.rs +++ b/src-tauri/src/remote.rs @@ -1,6 +1,6 @@ use std::{sync::nonpoison::Mutex, time::Duration}; -use client::app_status::AppStatus; +use client::{app_state::AppState, app_status::AppStatus}; use database::{borrow_db_checked, borrow_db_mut_checked}; use futures_lite::StreamExt; use log::{debug, warn}; @@ -18,7 +18,7 @@ use tauri::{AppHandle, Manager}; use url::Url; use utils::{app_emit, webbrowser_open::webbrowser_open}; -use crate::{AppState, recieve_handshake}; +use crate::{recieve_handshake}; #[tauri::command] pub async fn use_remote( @@ -45,7 +45,7 @@ pub async fn use_remote( } let mut app_state = state.lock(); - app_state.status = AppStatus::SignedOut; + *app_state.status_mut() = AppStatus::SignedOut; drop(app_state); let mut db_state = borrow_db_mut_checked(); @@ -100,8 +100,8 @@ pub fn sign_out(app: AppHandle) { { let state = app.state::>(); let mut app_state_handle = state.lock(); - app_state_handle.status = AppStatus::SignedOut; - app_state_handle.user = None; + *app_state_handle.status_mut() = AppStatus::SignedOut; + *app_state_handle.user_mut() = None; } // Emit event for frontend @@ -112,10 +112,9 @@ pub fn sign_out(app: AppHandle) { pub async fn retry_connect(state: tauri::State<'_, Mutex>) -> Result<(), ()> { let (app_status, user) = setup().await; - let mut guard = state.lock(); - guard.status = app_status; - guard.user = user; - drop(guard); + let mut state_lock = state.lock(); + *state_lock.status_mut() = app_status; + *state_lock.user_mut() = user; Ok(()) }