From fd61903130380f60ace156dbdfb074b0d9c1db04 Mon Sep 17 00:00:00 2001 From: quexeky Date: Sat, 21 Jun 2025 12:51:50 +1000 Subject: [PATCH] feat: Resume download button Also added DBWrite and DBRead structs to make database management easier Signed-off-by: quexeky --- components/GameStatusButton.vue | 6 + pages/library/[id]/index.vue | 11 + src-tauri/src/client/autostart.rs | 3 +- src-tauri/src/database/commands.rs | 7 +- src-tauri/src/database/db.rs | 71 ++++-- src-tauri/src/database/models.rs | 213 ++++++++++++++---- src-tauri/src/games/commands.rs | 6 +- src-tauri/src/games/downloads/commands.rs | 21 +- .../src/games/downloads/download_agent.rs | 69 +++++- src-tauri/src/games/library.rs | 19 +- src-tauri/src/lib.rs | 6 +- src-tauri/src/process/process_manager.rs | 9 +- src-tauri/src/remote/auth.rs | 6 +- src-tauri/src/remote/commands.rs | 4 +- src-tauri/src/remote/remote.rs | 5 +- types.ts | 2 + 16 files changed, 335 insertions(+), 123 deletions(-) diff --git a/components/GameStatusButton.vue b/components/GameStatusButton.vue index 9731721..b3edcb4 100644 --- a/components/GameStatusButton.vue +++ b/components/GameStatusButton.vue @@ -103,6 +103,7 @@ const emit = defineEmits<{ (e: "uninstall"): void; (e: "kill"): void; (e: "options"): void; + (e: "resume"): void }>(); const showDropdown = computed( @@ -128,6 +129,8 @@ const styles: { [key in GameStatusEnum]: string } = { "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700", [GameStatusEnum.Running]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700", + [GameStatusEnum.PartiallyInstalled]: + "bg-gray-600 text-white hover:bg-gray-500 focus-visible:outline-gray-600 hover:bg-gray-500" }; const buttonNames: { [key in GameStatusEnum]: string } = { @@ -139,6 +142,7 @@ const buttonNames: { [key in GameStatusEnum]: string } = { [GameStatusEnum.Updating]: "Updating", [GameStatusEnum.Uninstalling]: "Uninstalling", [GameStatusEnum.Running]: "Stop", + [GameStatusEnum.PartiallyInstalled]: "Resume" }; const buttonIcons: { [key in GameStatusEnum]: Component } = { @@ -150,6 +154,7 @@ const buttonIcons: { [key in GameStatusEnum]: Component } = { [GameStatusEnum.Updating]: ArrowDownTrayIcon, [GameStatusEnum.Uninstalling]: TrashIcon, [GameStatusEnum.Running]: PlayIcon, + [GameStatusEnum.PartiallyInstalled]: ArrowDownTrayIcon }; const buttonActions: { [key in GameStatusEnum]: () => void } = { @@ -161,5 +166,6 @@ const buttonActions: { [key in GameStatusEnum]: () => void } = { [GameStatusEnum.Updating]: () => emit("queue"), [GameStatusEnum.Uninstalling]: () => {}, [GameStatusEnum.Running]: () => emit("kill"), + [GameStatusEnum.PartiallyInstalled]: () => emit("resume") }; diff --git a/pages/library/[id]/index.vue b/pages/library/[id]/index.vue index 95f1304..d2a91f2 100644 --- a/pages/library/[id]/index.vue +++ b/pages/library/[id]/index.vue @@ -32,6 +32,7 @@ @uninstall="() => uninstall()" @kill="() => kill()" @options="() => (configureModalOpen = true)" + @resume="() => console.log('resume')" :status="status" /> Result<(), Strin let mut db_handle = borrow_db_mut_checked(); db_handle.settings.autostart = enabled; drop(db_handle); - save_db(); Ok(()) } diff --git a/src-tauri/src/database/commands.rs b/src-tauri/src/database/commands.rs index e7ccd5c..a83fd17 100644 --- a/src-tauri/src/database/commands.rs +++ b/src-tauri/src/database/commands.rs @@ -9,7 +9,7 @@ use serde_json::Value; use crate::{database::db::borrow_db_mut_checked, error::download_manager_error::DownloadManagerError}; use super::{ - db::{borrow_db_checked, save_db, DATA_ROOT_DIR}, + db::{borrow_db_checked, DATA_ROOT_DIR}, debug::SystemData, models::data::Settings, }; @@ -26,8 +26,6 @@ pub fn fetch_download_dir_stats() -> Vec { pub fn delete_download_dir(index: usize) { let mut lock = borrow_db_mut_checked(); lock.applications.install_dirs.remove(index); - drop(lock); - save_db(); } #[tauri::command] @@ -58,7 +56,6 @@ pub fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerError<()> } lock.applications.install_dirs.push(new_dir); drop(lock); - save_db(); Ok(()) } @@ -72,8 +69,6 @@ pub fn update_settings(new_settings: Value) { } let new_settings: Settings = serde_json::from_value(current_settings).unwrap(); db_lock.settings = new_settings; - drop(db_lock); - save_db(); } #[tauri::command] pub fn fetch_settings() -> Settings { diff --git a/src-tauri/src/database/db.rs b/src-tauri/src/database/db.rs index 3b72e36..8148346 100644 --- a/src-tauri/src/database/db.rs +++ b/src-tauri/src/database/db.rs @@ -1,7 +1,5 @@ use std::{ - fs::{self, create_dir_all}, - path::PathBuf, - sync::{LazyLock, Mutex, RwLockReadGuard, RwLockWriteGuard}, + fs::{self, create_dir_all}, mem::{replace, ManuallyDrop}, ops::{Deref, DerefMut}, path::PathBuf, sync::{LazyLock, Mutex, RwLockReadGuard, RwLockWriteGuard} }; use chrono::Utc; @@ -18,7 +16,6 @@ use super::models::data::Database; pub static DATA_ROOT_DIR: LazyLock> = LazyLock::new(|| Mutex::new(dirs::data_dir().unwrap().join("drop"))); - // Custom JSON serializer to support everything we need #[derive(Debug, Default, Clone)] pub struct DropDatabaseSerializer; @@ -27,15 +24,16 @@ impl DeSerializer for DropDatabaseSerializer { fn serialize(&self, val: &T) -> rustbreak::error::DeSerResult> { - native_model::rmp_serde_1_3::RmpSerde::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(&self, mut s: R) -> rustbreak::error::DeSerResult { let mut buf = Vec::new(); s.read_to_end(&mut buf) .map_err(|e| rustbreak::error::DeSerError::Other(e.into()))?; - let val = - native_model::rmp_serde_1_3::RmpSerde::decode(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) } } @@ -122,9 +120,48 @@ fn handle_invalid_database( PathDatabase::create_at_path(db_path, db).expect("Database could not be created") } -pub fn borrow_db_checked<'a>() -> RwLockReadGuard<'a, Database> { +// To automatically save the database upon drop +pub struct DBRead<'a>(RwLockReadGuard<'a, Database>); +pub struct DBWrite<'a>(ManuallyDrop>); +impl<'a> Deref for DBWrite<'a> { + type Target = RwLockWriteGuard<'a, Database>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl<'a> Deref for DBRead<'a> { + type Target = RwLockReadGuard<'a, Database>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl<'a> DerefMut for DBWrite<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl<'a> Drop for DBWrite<'a> { + fn drop(&mut self) { + + unsafe { + ManuallyDrop::drop(&mut self.0); + } + + match DB.save() { + Ok(_) => {} + Err(e) => { + error!("database failed to save with error {}", e); + panic!("database failed to save with error {}", e) + } + } + } +} +pub fn borrow_db_checked<'a>() -> DBRead<'a> { + match DB.borrow_data() { - Ok(data) => data, + Ok(data) => DBRead(data), Err(e) => { error!("database borrow failed with error {}", e); panic!("database borrow failed with error {}", e); @@ -132,22 +169,12 @@ pub fn borrow_db_checked<'a>() -> RwLockReadGuard<'a, Database> { } } -pub fn borrow_db_mut_checked<'a>() -> RwLockWriteGuard<'a, Database> { +pub fn borrow_db_mut_checked<'a>() -> DBWrite<'a> { match DB.borrow_data_mut() { - Ok(data) => data, + Ok(data) => DBWrite(ManuallyDrop::new(data)), Err(e) => { error!("database borrow mut failed with error {}", e); panic!("database borrow mut failed with error {}", e); } } -} - -pub fn save_db() { - match DB.save() { - Ok(_) => {} - Err(e) => { - error!("database failed to save with error {}", e); - panic!("database failed to save with error {}", e) - } - } -} +} \ No newline at end of file diff --git a/src-tauri/src/database/models.rs b/src-tauri/src/database/models.rs index 45a7f51..99192a5 100644 --- a/src-tauri/src/database/models.rs +++ b/src-tauri/src/database/models.rs @@ -1,19 +1,29 @@ +use crate::database::models::data::{Database, DatabaseCompatInfo}; + pub mod data { + use std::path::PathBuf; + use native_model::{native_model, Model}; use serde::{Deserialize, Serialize}; pub type GameVersion = v1::GameVersion; - pub type Database = v2::Database; + pub type Database = v3::Database; pub type Settings = v1::Settings; pub type DatabaseAuth = v1::DatabaseAuth; - pub type GameDownloadStatus = v1::GameDownloadStatus; + pub type GameDownloadStatus = v2::GameDownloadStatus; pub type ApplicationTransientStatus = v1::ApplicationTransientStatus; pub type DownloadableMetadata = v1::DownloadableMetadata; pub type DownloadType = v1::DownloadType; - pub type DatabaseApplications = v1::DatabaseApplications; + pub type DatabaseApplications = v2::DatabaseApplications; pub type DatabaseCompatInfo = v2::DatabaseCompatInfo; + use std::{collections::HashMap, process::Command}; + + use serde_with::serde_as; + + use crate::process::process_manager::UMU_LAUNCHER_EXECUTABLE; + pub mod v1 { use crate::process::process_manager::Platform; use serde_with::serde_as; @@ -102,7 +112,7 @@ pub mod data { } // Stuff that shouldn't be synced to disk - #[derive(Clone, Serialize, Deserialize)] + #[derive(Clone, Serialize, Deserialize, Debug)] pub enum ApplicationTransientStatus { Downloading { version_name: String }, Uninstalling {}, @@ -164,12 +174,126 @@ pub mod data { pub mod v2 { use std::{collections::HashMap, path::PathBuf, process::Command}; + use serde_with::serde_as; + use crate::process::process_manager::UMU_LAUNCHER_EXECUTABLE; use super::*; #[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)] #[derive(Serialize, Deserialize, Clone, Default)] + pub struct Database { + #[serde(default)] + pub settings: 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 = 8, version = 2, 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: crate::database::models::Database::create_new_compat_info(), + } + } + } + // 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)] + 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)] + 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::{collections::HashMap, path::PathBuf, process::Command}; + + use serde_with::serde_as; + + use crate::process::process_manager::UMU_LAUNCHER_EXECUTABLE; + + use super::*; + #[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde)] + #[derive(Serialize, Deserialize, Clone, Default)] pub struct Database { #[serde(default)] pub settings: Settings, @@ -181,55 +305,13 @@ pub mod data { pub cache_dir: PathBuf, pub compat_info: Option, } - - #[native_model(id = 8, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)] - #[derive(Serialize, Deserialize, Clone, Default)] - - pub struct DatabaseCompatInfo { - umu_installed: bool, - } - - impl Database { - fn create_new_compat_info() -> Option { - #[cfg(target_os = "windows")] - return None; - - let has_umu_installed = Command::new(UMU_LAUNCHER_EXECUTABLE).spawn().is_ok(); - Some(DatabaseCompatInfo { - umu_installed: has_umu_installed, - }) - } - - 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: "".to_owned(), - auth: None, - settings: Settings::default(), - cache_dir, - compat_info: Database::create_new_compat_info(), - } - } - } - - impl From for Database { - fn from(value: v1::Database) -> Self { + 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, + applications: value.applications.into(), prev_database: value.prev_database, cache_dir: value.cache_dir, compat_info: Database::create_new_compat_info(), @@ -237,4 +319,37 @@ pub mod data { } } } + impl Database { + fn create_new_compat_info() -> Option { + #[cfg(target_os = "windows")] + return None; + + let has_umu_installed = Command::new(UMU_LAUNCHER_EXECUTABLE).spawn().is_ok(); + Some(DatabaseCompatInfo { + umu_installed: has_umu_installed, + }) + } + + 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: "".to_owned(), + auth: None, + settings: Settings::default(), + cache_dir, + compat_info: Database::create_new_compat_info(), + } + } + } } diff --git a/src-tauri/src/games/commands.rs b/src-tauri/src/games/commands.rs index 8a95132..d494ec5 100644 --- a/src-tauri/src/games/commands.rs +++ b/src-tauri/src/games/commands.rs @@ -37,13 +37,15 @@ pub fn fetch_game( game_id: String, state: tauri::State<'_, Mutex>, ) -> Result { - offline!( + let res = offline!( state, fetch_game_logic, fetch_game_logic_offline, game_id, state - ) + ); + println!("Res: {:?}", &res); + res } #[tauri::command] diff --git a/src-tauri/src/games/downloads/commands.rs b/src-tauri/src/games/downloads/commands.rs index 76db029..a34239c 100644 --- a/src-tauri/src/games/downloads/commands.rs +++ b/src-tauri/src/games/downloads/commands.rs @@ -1,9 +1,7 @@ use std::sync::{Arc, Mutex}; use crate::{ - download_manager::{ - download_manager::DownloadManagerSignal, downloadable::Downloadable, - }, error::download_manager_error::DownloadManagerError, AppState + database::db::borrow_db_checked, download_manager::{download_manager::DownloadManagerSignal, downloadable::Downloadable}, error::download_manager_error::DownloadManagerError, AppState }; use super::download_agent::GameDownloadAgent; @@ -16,7 +14,7 @@ pub fn download_game( state: tauri::State<'_, Mutex>, ) -> Result<(), DownloadManagerError> { let sender = state.lock().unwrap().download_manager.get_sender(); - let game_download_agent = Arc::new(Box::new(GameDownloadAgent::new( + let game_download_agent = Arc::new(Box::new(GameDownloadAgent::new_from_index( game_id, game_version, install_dir, @@ -28,3 +26,18 @@ pub fn download_game( .download_manager .queue_download(game_download_agent)?) } + +#[tauri::command] +pub fn resume_download( + game_id: String, + version: String, + install_dir: String, + state: tauri::State<'_, Mutex>, +) -> Result<(), DownloadManagerError> { + let sender = state.lock().unwrap().download_manager.get_sender(); + let game_download_agent = Arc::new(Box::new(GameDownloadAgent::new( + game_id, version, install_dir.into(), sender + )) as Box); + Ok(state.lock().unwrap().download_manager.queue_download(game_download_agent)?) + +} diff --git a/src-tauri/src/games/downloads/download_agent.rs b/src-tauri/src/games/downloads/download_agent.rs index 275bac2..31c769b 100644 --- a/src-tauri/src/games/downloads/download_agent.rs +++ b/src-tauri/src/games/downloads/download_agent.rs @@ -1,5 +1,5 @@ use crate::auth::generate_authorization_header; -use crate::database::db::borrow_db_checked; +use crate::database::db::{borrow_db_checked, borrow_db_mut_checked}; use crate::database::models::data::{ ApplicationTransientStatus, DownloadType, DownloadableMetadata, GameDownloadStatus, }; @@ -19,11 +19,11 @@ use log::{debug, error, info}; use rayon::ThreadPoolBuilder; use slice_deque::SliceDeque; use std::fs::{create_dir_all, File, OpenOptions}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use std::time::Instant; -use tauri::{AppHandle, Emitter}; +use tauri::{AppHandle, Emitter, Manager}; #[cfg(target_os = "linux")] use rustix::fs::{fallocate, FallocateFlags}; @@ -45,19 +45,27 @@ pub struct GameDownloadAgent { } impl GameDownloadAgent { - pub fn new( + pub fn new_from_index( id: String, version: String, target_download_dir: usize, sender: Sender, ) -> Self { - // Don't run by default - let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop); - let db_lock = borrow_db_checked(); let base_dir = db_lock.applications.install_dirs[target_download_dir].clone(); drop(db_lock); + Self::new(id, version, base_dir, sender) + } + pub fn new( + id: String, + version: String, + base_dir: PathBuf, + sender: Sender, + ) -> Self { + // Don't run by default + let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop); + let base_dir_path = Path::new(&base_dir); let data_base_dir_path = base_dir_path.join(id.clone()); @@ -204,7 +212,12 @@ impl GameDownloadAgent { let container = path.parent().unwrap(); create_dir_all(container).unwrap(); - let file = OpenOptions::new().read(true).write(true).create(true).open(path.clone()).unwrap(); + let file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path.clone()) + .unwrap(); let mut running_offset = 0; for (index, length) in chunk.lengths.iter().enumerate() { @@ -260,7 +273,12 @@ impl GameDownloadAgent { let progress_handle = ProgressHandle::new(progress, self.progress.clone()); // If we've done this one already, skip it - if self.completed_contexts.lock().unwrap().contains(&context.checksum) { + if self + .completed_contexts + .lock() + .unwrap() + .contains(&context.checksum) + { progress_handle.skip(context.length); continue; } @@ -384,7 +402,7 @@ impl Downloadable for GameDownloadAgent { error!("error while managing download: {}", error); - let mut handle = DB.borrow_data_mut().unwrap(); + let mut handle = borrow_db_mut_checked(); handle .applications .transient_statuses @@ -404,12 +422,41 @@ impl Downloadable for GameDownloadAgent { fn on_incomplete(&self, app_handle: &tauri::AppHandle) { let meta = self.metadata(); *self.status.lock().unwrap() = DownloadStatus::Queued; + borrow_db_mut_checked().applications.game_statuses.insert( + self.id.clone(), + GameDownloadStatus::PartiallyInstalled { + version_name: self.version.clone(), + install_dir: self.stored_manifest.base_path.to_string_lossy().to_string(), + }, + ); + borrow_db_mut_checked() + .applications + .installed_game_version + .insert(self.id.clone(), self.metadata()); + borrow_db_mut_checked().applications.game_statuses.insert( + self.id.clone(), + GameDownloadStatus::PartiallyInstalled { + version_name: self.version.clone(), + install_dir: self.stored_manifest.base_path.to_string_lossy().to_string(), + }, + ); + app_handle .emit( &format!("update_game/{}", meta.id), GameUpdateEvent { game_id: meta.id.clone(), - status: (Some(GameDownloadStatus::Remote {}), None), + status: ( + Some(GameDownloadStatus::PartiallyInstalled { + version_name: self.version.clone(), + install_dir: self + .stored_manifest + .base_path + .to_string_lossy() + .to_string(), + }), + None, + ), version: None, }, ) diff --git a/src-tauri/src/games/library.rs b/src-tauri/src/games/library.rs index d62238a..d07ee63 100644 --- a/src-tauri/src/games/library.rs +++ b/src-tauri/src/games/library.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use tauri::Emitter; use tauri::AppHandle; -use crate::database::db::{borrow_db_checked, borrow_db_mut_checked, save_db}; +use crate::database::db::{borrow_db_checked, borrow_db_mut_checked}; use crate::database::models::data::{ ApplicationTransientStatus, DownloadableMetadata, GameDownloadStatus, GameVersion, }; @@ -18,9 +18,9 @@ use crate::games::state::{GameStatusManager, GameStatusWithTransient}; use crate::remote::auth::generate_authorization_header; use crate::remote::cache::{cache_object, get_cached_object, get_cached_object_db}; use crate::remote::requests::make_request; -use crate::{AppState, DB}; +use crate::AppState; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct FetchGameStruct { game: Game, status: GameStatusWithTransient, @@ -144,7 +144,7 @@ pub fn fetch_game_logic( ) -> Result { let mut state_handle = state.lock().unwrap(); - let handle = DB.borrow_data().unwrap(); + let handle = borrow_db_checked(); let metadata_option = handle.applications.installed_game_version.get(&id); let version = match metadata_option { @@ -220,7 +220,7 @@ pub fn fetch_game_logic_offline( id: String, _state: tauri::State<'_, Mutex>, ) -> Result { - let handle = DB.borrow_data().unwrap(); + let handle = borrow_db_checked(); let metadata_option = handle.applications.installed_game_version.get(&id); let version = match metadata_option { None => None, @@ -341,7 +341,6 @@ pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) .entry(meta.id.clone()) .and_modify(|e| *e = GameDownloadStatus::Remote {}); drop(db_handle); - save_db(); debug!("uninstalled game id {}", &meta.id); app_handle.emit("update_library", {}).unwrap(); @@ -401,10 +400,9 @@ pub fn on_game_complete( handle .applications .installed_game_version - .insert(meta.id.clone(), meta.clone()); + .insert(meta.id.clone(), meta.clone()); drop(handle); - save_db(); let status = if game_version.setup_command.is_empty() { GameDownloadStatus::Installed { @@ -424,7 +422,6 @@ pub fn on_game_complete( .game_statuses .insert(meta.id.clone(), status.clone()); drop(db_handle); - save_db(); app_handle .emit( &format!("update_game/{}", meta.id), @@ -468,7 +465,7 @@ pub fn update_game_configuration( game_id: String, options: FrontendGameOptions, ) -> Result<(), LibraryError> { - let mut handle = DB.borrow_data_mut().unwrap(); + let mut handle = borrow_db_mut_checked(); let installed_version = handle .applications .installed_game_version @@ -499,8 +496,6 @@ pub fn update_game_configuration( .unwrap() .insert(version.to_string(), existing_configuration); - drop(handle); - save_db(); Ok(()) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e0721c8..5462f01 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,7 +7,7 @@ mod error; mod process; mod remote; -use crate::database::db::DatabaseImpls; +use crate::{database::db::DatabaseImpls, games::downloads::commands::resume_download}; use client::{ autostart::{get_autostart_enabled, sync_autostart_on_startup, toggle_autostart}, cleanup::{cleanup_and_exit, quit}, @@ -156,6 +156,7 @@ fn setup(handle: AppHandle) -> AppState<'static> { for (game_id, status) in statuses.into_iter() { match status { GameDownloadStatus::Remote {} => {} + GameDownloadStatus::PartiallyInstalled { .. } => {}, GameDownloadStatus::SetupRequired { version_name: _, install_dir, @@ -173,7 +174,7 @@ fn setup(handle: AppHandle) -> AppState<'static> { if !install_dir_path.exists() { missing_games.push(game_id); } - } + }, } } @@ -259,6 +260,7 @@ pub fn run() { delete_game_in_collection, // Downloads download_game, + resume_download, move_download_in_queue, pause_downloads, resume_downloads, diff --git a/src-tauri/src/process/process_manager.rs b/src-tauri/src/process/process_manager.rs index c28ae1b..5646a06 100644 --- a/src-tauri/src/process/process_manager.rs +++ b/src-tauri/src/process/process_manager.rs @@ -193,6 +193,9 @@ impl ProcessManager<'_> { version_name, install_dir, } => (version_name, install_dir), + GameDownloadStatus::PartiallyInstalled { + version_name, install_dir + } => (version_name, install_dir), _ => return Err(ProcessError::NotDownloaded), }; @@ -248,7 +251,11 @@ impl ProcessManager<'_> { version_name: _, install_dir: _, } => (&game_version.setup_command, &game_version.setup_args), - GameDownloadStatus::Remote {} => unreachable!("nuh uh"), + GameDownloadStatus::PartiallyInstalled { + version_name, + install_dir + } => unreachable!("Game registered as 'Partially Installed'"), + GameDownloadStatus::Remote {} => unreachable!("Game registered as 'Remote'"), }; let launch = PathBuf::from_str(&install_dir).unwrap().join(launch); diff --git a/src-tauri/src/remote/auth.rs b/src-tauri/src/remote/auth.rs index fc242a1..a94f811 100644 --- a/src-tauri/src/remote/auth.rs +++ b/src-tauri/src/remote/auth.rs @@ -10,7 +10,7 @@ use url::Url; use crate::{ database::{ - db::{borrow_db_checked, borrow_db_mut_checked, save_db}, + db::{borrow_db_checked, borrow_db_mut_checked}, models::data::DatabaseAuth, }, error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError}, @@ -122,8 +122,6 @@ fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAc client_id: response_struct.id, web_token: None, // gets created later }); - drop(handle); - save_db(); } let web_token = { @@ -140,8 +138,6 @@ fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAc let mut handle = borrow_db_mut_checked(); let mut_auth = handle.auth.as_mut().unwrap(); mut_auth.web_token = Some(web_token); - drop(handle); - save_db(); Ok(()) } diff --git a/src-tauri/src/remote/commands.rs b/src-tauri/src/remote/commands.rs index 37039e6..613e2a3 100644 --- a/src-tauri/src/remote/commands.rs +++ b/src-tauri/src/remote/commands.rs @@ -6,7 +6,7 @@ use tauri::{AppHandle, Emitter, Manager}; use url::Url; use crate::{ - database::db::{borrow_db_checked, borrow_db_mut_checked, save_db}, + database::db::{borrow_db_checked, borrow_db_mut_checked}, error::remote_access_error::RemoteAccessError, remote::{auth::generate_authorization_header, requests::make_request}, AppState, AppStatus, @@ -65,8 +65,6 @@ pub fn sign_out(app: AppHandle) { { let mut handle = borrow_db_mut_checked(); handle.auth = None; - drop(handle); - save_db(); } // Update app state diff --git a/src-tauri/src/remote/remote.rs b/src-tauri/src/remote/remote.rs index 01f2c2b..cf340a9 100644 --- a/src-tauri/src/remote/remote.rs +++ b/src-tauri/src/remote/remote.rs @@ -5,7 +5,7 @@ use serde::Deserialize; use url::Url; use crate::{ - database::db::{borrow_db_mut_checked, save_db}, + database::db::{borrow_db_mut_checked}, error::remote_access_error::RemoteAccessError, AppState, AppStatus, }; @@ -40,9 +40,6 @@ pub fn use_remote_logic( let mut db_state = borrow_db_mut_checked(); db_state.base_url = base_url.to_string(); - drop(db_state); - - save_db(); Ok(()) } diff --git a/types.ts b/types.ts index 6d48825..57cd321 100644 --- a/types.ts +++ b/types.ts @@ -59,11 +59,13 @@ export enum GameStatusEnum { Uninstalling = "Uninstalling", SetupRequired = "SetupRequired", Running = "Running", + PartiallyInstalled = "PartiallyInstalled" } export type GameStatus = { type: GameStatusEnum; version_name?: string; + install_dir?: string; }; export enum DownloadableType {