From 604d5b5884d150a54d3edcd659a49825d3c170dd Mon Sep 17 00:00:00 2001 From: quexeky <116044207+quexeky@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:44:57 +1100 Subject: [PATCH] Implement better error system and segregate errors and commands (#23) * chore: Progress on amend_settings command Signed-off-by: quexeky * chore(errors): Progress on better error handling with segragation of files * chore: Progress on amend_settings command Signed-off-by: quexeky * chore(commands): Separated commands under each subdirectory into respective commands.rs files Signed-off-by: quexeky * chore(errors): Almost all errors and commands have been segregated * chore(errors): Added drop server error Signed-off-by: quexeky * feat(core): Update to using nightly compiler Signed-off-by: quexeky * chore(errors): More progress on error handling Signed-off-by: quexeky * chore(errors): Implementing Try and FromResidual for UserValue Signed-off-by: quexeky * refactor(errors): Segregated errors and commands from code, and made commands return UserValue struct Signed-off-by: quexeky * fix(errors): Added missing files * chore(errors): Convert match statement to map_err * feat(settings): Implemented settings editing from UI * feat(errors): Clarified return values from retry_connect command * chore(errors): Moved autostart commands to autostart.rs * chore(process manager): Converted launch_process function for games to use game_id --------- Signed-off-by: quexeky --- pages/settings/downloads.vue | 1 + src-tauri/Cargo.lock | 23 +++ src-tauri/Cargo.toml | 2 + src-tauri/rust-toolchain.toml | 2 + src-tauri/src/autostart.rs | 23 +-- src-tauri/src/commands.rs | 16 +++ src-tauri/src/database/commands.rs | 83 +++++++++++ src-tauri/src/{ => database}/db.rs | 60 +------- src-tauri/src/database/debug.rs | 22 +++ src-tauri/src/database/mod.rs | 4 + src-tauri/src/database/settings.rs | 24 ++++ src-tauri/src/debug.rs | 26 ---- src-tauri/src/download_manager/commands.rs | 31 ++++ .../src/download_manager/download_manager.rs | 3 +- .../download_manager_builder.rs | 12 +- .../src/download_manager/downloadable.rs | 5 +- src-tauri/src/download_manager/mod.rs | 2 +- .../src/download_manager/progress_object.rs | 23 +-- .../application_download_error.rs | 15 +- src-tauri/src/error/drop_server_error.rs | 10 ++ src-tauri/src/error/library_error.rs | 16 +++ src-tauri/src/error/mod.rs | 7 + src-tauri/src/error/process_error.rs | 28 ++++ .../remote_access_error.rs} | 86 +---------- src-tauri/src/error/setup_error.rs | 14 ++ src-tauri/src/error/user_error.rs | 66 +++++++++ src-tauri/src/games/commands.rs | 57 ++++++++ src-tauri/src/games/downloads/commands.rs | 31 ++++ .../src/games/downloads/download_agent.rs | 32 +++-- .../src/games/downloads/download_commands.rs | 58 -------- .../src/games/downloads/download_logic.rs | 4 +- src-tauri/src/games/downloads/mod.rs | 2 +- src-tauri/src/games/library.rs | 84 +++-------- src-tauri/src/games/mod.rs | 1 + src-tauri/src/games/state.rs | 2 +- src-tauri/src/lib.rs | 69 +++++---- src-tauri/src/process/commands.rs | 44 ++++++ src-tauri/src/process/mod.rs | 2 +- src-tauri/src/process/process_commands.rs | 55 -------- src-tauri/src/process/process_manager.rs | 133 ++++++++++-------- src-tauri/src/{ => remote}/auth.rs | 79 +---------- src-tauri/src/remote/commands.rs | 78 ++++++++++ src-tauri/src/remote/mod.rs | 3 + src-tauri/src/remote/remote.rs | 49 +++++++ src-tauri/src/settings.rs | 35 ----- 45 files changed, 822 insertions(+), 600 deletions(-) create mode 100644 src-tauri/rust-toolchain.toml create mode 100644 src-tauri/src/commands.rs create mode 100644 src-tauri/src/database/commands.rs rename src-tauri/src/{ => database}/db.rs (77%) create mode 100644 src-tauri/src/database/debug.rs create mode 100644 src-tauri/src/database/mod.rs create mode 100644 src-tauri/src/database/settings.rs delete mode 100644 src-tauri/src/debug.rs create mode 100644 src-tauri/src/download_manager/commands.rs rename src-tauri/src/{download_manager => error}/application_download_error.rs (77%) create mode 100644 src-tauri/src/error/drop_server_error.rs create mode 100644 src-tauri/src/error/library_error.rs create mode 100644 src-tauri/src/error/mod.rs create mode 100644 src-tauri/src/error/process_error.rs rename src-tauri/src/{remote.rs => error/remote_access_error.rs} (54%) create mode 100644 src-tauri/src/error/setup_error.rs create mode 100644 src-tauri/src/error/user_error.rs create mode 100644 src-tauri/src/games/commands.rs create mode 100644 src-tauri/src/games/downloads/commands.rs delete mode 100644 src-tauri/src/games/downloads/download_commands.rs create mode 100644 src-tauri/src/process/commands.rs delete mode 100644 src-tauri/src/process/process_commands.rs rename src-tauri/src/{ => remote}/auth.rs (72%) create mode 100644 src-tauri/src/remote/commands.rs create mode 100644 src-tauri/src/remote/mod.rs create mode 100644 src-tauri/src/remote/remote.rs delete mode 100644 src-tauri/src/settings.rs diff --git a/pages/settings/downloads.vue b/pages/settings/downloads.vue index c5d0de3..f6492e7 100644 --- a/pages/settings/downloads.vue +++ b/pages/settings/downloads.vue @@ -280,5 +280,6 @@ async function deleteDirectory(index: number) { async function saveDownloadThreads() { //Would save download threads downloadThreads.value); + await invoke("amend_settings", { newSettings: { max_download_threads: downloadThreads.value } }) } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a676baf..0c8123b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1042,6 +1042,7 @@ dependencies = [ "log", "log4rs", "md5", + "merge-struct", "openssl", "parking_lot 0.12.3", "rayon", @@ -1051,6 +1052,7 @@ dependencies = [ "serde", "serde-binary", "serde_json", + "serde_merge", "serde_with", "shared_child", "slice-deque", @@ -2453,6 +2455,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "merge-struct" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d82012d21e24135b839b6b9bebd622b7ff0cb40071498bc2d066d3a6d04dd4a" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "mime" version = "0.3.17" @@ -3901,6 +3913,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_merge" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "606e91878516232ac3b16c12e063d4468d762f16d77e7aef14a1f2326c5f409b" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "serde_repr" version = "0.1.19" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c5d6237..c48f490 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -50,6 +50,8 @@ slice-deque = "0.3.0" derive_builder = "0.20.2" throttle_my_fn = "0.2.6" parking_lot = "0.12.3" +merge-struct = "0.1.0" +serde_merge = "0.1.3" [dependencies.tauri] version = "2.1.1" diff --git a/src-tauri/rust-toolchain.toml b/src-tauri/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/src-tauri/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/src-tauri/src/autostart.rs b/src-tauri/src/autostart.rs index aa41fcb..da0c1aa 100644 --- a/src-tauri/src/autostart.rs +++ b/src-tauri/src/autostart.rs @@ -3,8 +3,7 @@ use log::debug; use tauri::AppHandle; use tauri_plugin_autostart::ManagerExt; -#[tauri::command] -pub async fn toggle_autostart(app: AppHandle, enabled: bool) -> Result<(), String> { +pub fn toggle_autostart_logic(app: AppHandle, enabled: bool) -> Result<(), String> { let manager = app.autolaunch(); if enabled { manager.enable().map_err(|e| e.to_string())?; @@ -23,23 +22,22 @@ pub async fn toggle_autostart(app: AppHandle, enabled: bool) -> Result<(), Strin Ok(()) } -#[tauri::command] -pub async fn get_autostart_enabled(app: AppHandle) -> Result { +pub fn get_autostart_enabled_logic(app: AppHandle) -> Result { // First check DB state - let db_handle = DB.borrow_data().map_err(|e| e.to_string())?; + let db_handle = DB.borrow_data().unwrap(); let db_state = db_handle.settings.autostart; drop(db_handle); // Get actual system state let manager = app.autolaunch(); - let system_state = manager.is_enabled().map_err(|e| e.to_string())?; + let system_state = manager.is_enabled()?; // If they don't match, sync to DB state if db_state != system_state { if db_state { - manager.enable().map_err(|e| e.to_string())?; + manager.enable()?; } else { - manager.disable().map_err(|e| e.to_string())?; + manager.disable()?; } } @@ -67,3 +65,12 @@ pub fn sync_autostart_on_startup(app: &AppHandle) -> Result<(), String> { Ok(()) } +#[tauri::command] +pub fn toggle_autostart(app: AppHandle, enabled: bool) -> Result<(), String> { + toggle_autostart_logic(app, enabled) +} + +#[tauri::command] +pub fn get_autostart_enabled(app: AppHandle) -> Result { + get_autostart_enabled_logic(app) +} diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs new file mode 100644 index 0000000..a0b646e --- /dev/null +++ b/src-tauri/src/commands.rs @@ -0,0 +1,16 @@ +use tauri::AppHandle; + +use crate::{ + autostart::{get_autostart_enabled_logic, toggle_autostart_logic}, + AppState, +}; + +#[tauri::command] +pub fn fetch_state( + state: tauri::State<'_, std::sync::Mutex>>, +) -> Result { + let guard = state.lock().unwrap(); + let cloned_state = serde_json::to_string(&guard.clone()).map_err(|e| e.to_string())?; + drop(guard); + Ok(cloned_state) +} diff --git a/src-tauri/src/database/commands.rs b/src-tauri/src/database/commands.rs new file mode 100644 index 0000000..b254e00 --- /dev/null +++ b/src-tauri/src/database/commands.rs @@ -0,0 +1,83 @@ +use std::{ + fs::create_dir_all, + io::{Error, ErrorKind}, + path::{Path, PathBuf}, +}; + +use serde_json::Value; + +use crate::{database::settings::Settings, error::user_error::UserValue, DB}; + +use super::{db::DATA_ROOT_DIR, debug::SystemData}; + +// Will, in future, return disk/remaining size +// Just returns the directories that have been set up +#[tauri::command] +pub fn fetch_download_dir_stats() -> Vec { + let lock = DB.borrow_data().unwrap(); + lock.applications.install_dirs.clone() +} + +#[tauri::command] +pub fn delete_download_dir(index: usize) { + let mut lock = DB.borrow_data_mut().unwrap(); + lock.applications.install_dirs.remove(index); + drop(lock); + DB.save().unwrap(); +} + +#[tauri::command] +pub fn add_download_dir(new_dir: PathBuf) -> UserValue<(), Error> { + // Check the new directory is all good + let new_dir_path = Path::new(&new_dir); + if new_dir_path.exists() { + let dir_contents = new_dir_path.read_dir()?; + if dir_contents.count() != 0 { + return UserValue::Err(Error::new( + ErrorKind::DirectoryNotEmpty, + "Selected directory cannot contain any existing files", + )); + } + } else { + create_dir_all(new_dir_path)?; + } + + // Add it to the dictionary + let mut lock = DB.borrow_data_mut().unwrap(); + if lock.applications.install_dirs.contains(&new_dir) { + return UserValue::Err(Error::new( + ErrorKind::AlreadyExists, + "Selected directory already exists in database", + )); + } + lock.applications.install_dirs.push(new_dir); + drop(lock); + DB.save().unwrap(); + + UserValue::Ok(()) +} + +#[tauri::command] +pub fn update_settings(new_settings: Value) { + println!("{}", new_settings); + let mut db_lock = DB.borrow_data_mut().unwrap(); + let mut current_settings = serde_json::to_value(db_lock.settings.clone()).unwrap(); + for (key, value) in new_settings.as_object().unwrap() { + current_settings[key] = value.clone(); + } + println!("New settings unset: {}", ¤t_settings); + let new_settings: Settings = serde_json::from_value(current_settings).unwrap(); + db_lock.settings = new_settings; + println!("New Settings: {:?}", db_lock.settings); +} + +#[tauri::command] +pub fn fetch_system_data() -> SystemData { + let db_handle = DB.borrow_data().unwrap(); + SystemData::new( + db_handle.auth.as_ref().unwrap().client_id.clone(), + db_handle.base_url.clone(), + DATA_ROOT_DIR.lock().unwrap().to_string_lossy().to_string(), + std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()), + ) +} diff --git a/src-tauri/src/db.rs b/src-tauri/src/database/db.rs similarity index 77% rename from src-tauri/src/db.rs rename to src-tauri/src/database/db.rs index 4fcdb3d..376bcb0 100644 --- a/src-tauri/src/db.rs +++ b/src-tauri/src/database/db.rs @@ -15,10 +15,10 @@ use tauri::AppHandle; use url::Url; use crate::{ + database::settings::Settings, download_manager::downloadable_metadata::DownloadableMetadata, games::{library::push_game_update, state::GameStatusManager}, process::process_manager::Platform, - settings::Settings, DB, }; @@ -164,61 +164,6 @@ impl DatabaseImpls for DatabaseInterface { } } -#[tauri::command] -pub fn add_download_dir(new_dir: PathBuf) -> Result<(), String> { - // Check the new directory is all good - let new_dir_path = Path::new(&new_dir); - if new_dir_path.exists() { - let metadata = new_dir_path - .metadata() - .map_err(|e| format!("Unable to access file or directory: {}", e))?; - if !metadata.is_dir() { - return Err("Invalid path: not a directory".to_string()); - } - let dir_contents = new_dir_path - .read_dir() - .map_err(|e| format!("Unable to check directory contents: {}", e))?; - if dir_contents.count() != 0 { - return Err("Directory is not empty".to_string()); - } - } else { - create_dir_all(new_dir_path) - .map_err(|e| format!("Unable to create directories to path: {}", e))?; - } - - // Add it to the dictionary - let mut lock = DB.borrow_data_mut().unwrap(); - if lock.applications.install_dirs.contains(&new_dir) { - return Err("Download directory already used".to_string()); - } - lock.applications.install_dirs.push(new_dir); - drop(lock); - DB.save().unwrap(); - - Ok(()) -} - -#[tauri::command] -pub fn delete_download_dir(index: usize) -> Result<(), String> { - let mut lock = DB.borrow_data_mut().unwrap(); - lock.applications.install_dirs.remove(index); - drop(lock); - DB.save().unwrap(); - - Ok(()) -} - -// Will, in future, return disk/remaining size -// Just returns the directories that have been set up -#[tauri::command] -pub fn fetch_download_dir_stats() -> Result, String> { - let lock = DB.borrow_data().unwrap(); - let directories = lock.applications.install_dirs.clone(); - drop(lock); - - Ok(directories) -} - pub fn set_game_status, &DownloadableMetadata)>( app_handle: &AppHandle, meta: DownloadableMetadata, @@ -231,9 +176,8 @@ pub fn set_game_status, &Downloada let status = GameStatusManager::fetch_state(&meta.id); - push_game_update(app_handle, &meta, status); + push_game_update(app_handle, &meta.id, status); } - // TODO: Make the error relelvant rather than just assume that it's a Deserialize error fn handle_invalid_database( _e: RustbreakError, diff --git a/src-tauri/src/database/debug.rs b/src-tauri/src/database/debug.rs new file mode 100644 index 0000000..8547b73 --- /dev/null +++ b/src-tauri/src/database/debug.rs @@ -0,0 +1,22 @@ +use crate::{DATA_ROOT_DIR, DB}; +use serde::Serialize; + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SystemData { + client_id: String, + base_url: String, + data_dir: String, + log_level: String, +} + +impl SystemData { + pub fn new(client_id: String, base_url: String, data_dir: String, log_level: String) -> Self { + Self { + client_id, + base_url, + data_dir, + log_level, + } + } +} diff --git a/src-tauri/src/database/mod.rs b/src-tauri/src/database/mod.rs new file mode 100644 index 0000000..2a4255b --- /dev/null +++ b/src-tauri/src/database/mod.rs @@ -0,0 +1,4 @@ +pub mod commands; +pub mod db; +pub mod debug; +pub mod settings; diff --git a/src-tauri/src/database/settings.rs b/src-tauri/src/database/settings.rs new file mode 100644 index 0000000..b8c1742 --- /dev/null +++ b/src-tauri/src/database/settings.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Settings { + pub autostart: bool, + pub max_download_threads: usize, + // ... other settings ... +} +impl Default for Settings { + fn default() -> Self { + Self { + autostart: false, + max_download_threads: 4, + } + } +} +// Ideally use pointers instead of a macro to assign the settings +// fn deserialize_into(v: serde_json::Value, t: &mut T) -> Result<(), serde_json::Error> +// where T: for<'a> Deserialize<'a> +// { +// *t = serde_json::from_value(v)?; +// Ok(()) +// } diff --git a/src-tauri/src/debug.rs b/src-tauri/src/debug.rs deleted file mode 100644 index a575b71..0000000 --- a/src-tauri/src/debug.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::{DATA_ROOT_DIR, DB}; -use log::LevelFilter; -use serde::Serialize; - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct SystemData { - client_id: String, - base_url: String, - data_dir: String, - log_level: String, -} - -#[tauri::command] -pub fn fetch_system_data() -> Result { - let db_handle = DB.borrow_data().map_err(|e| e.to_string())?; - let system_data = SystemData { - client_id: db_handle.auth.as_ref().unwrap().client_id.clone(), - base_url: db_handle.base_url.clone(), - data_dir: DATA_ROOT_DIR.lock().unwrap().to_string_lossy().to_string(), - log_level: std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()), - }; - drop(db_handle); - - Ok(system_data) -} diff --git a/src-tauri/src/download_manager/commands.rs b/src-tauri/src/download_manager/commands.rs new file mode 100644 index 0000000..aa69aae --- /dev/null +++ b/src-tauri/src/download_manager/commands.rs @@ -0,0 +1,31 @@ +use std::sync::{mpsc::SendError, Arc, Mutex}; + +use crate::{download_manager::downloadable_metadata::DownloadableMetadata, AppState}; + +#[tauri::command] +pub fn pause_downloads(state: tauri::State<'_, Mutex>) { + state.lock().unwrap().download_manager.pause_downloads() +} + +#[tauri::command] +pub fn resume_downloads(state: tauri::State<'_, Mutex>) { + state.lock().unwrap().download_manager.resume_downloads() +} + +#[tauri::command] +pub fn move_download_in_queue( + state: tauri::State<'_, Mutex>, + old_index: usize, + new_index: usize, +) { + state + .lock() + .unwrap() + .download_manager + .rearrange(old_index, new_index) +} + +#[tauri::command] +pub fn cancel_game(state: tauri::State<'_, Mutex>, meta: DownloadableMetadata) { + state.lock().unwrap().download_manager.cancel(meta) +} diff --git a/src-tauri/src/download_manager/download_manager.rs b/src-tauri/src/download_manager/download_manager.rs index 9c7534e..8e5474a 100644 --- a/src-tauri/src/download_manager/download_manager.rs +++ b/src-tauri/src/download_manager/download_manager.rs @@ -12,8 +12,9 @@ use std::{ use log::info; use serde::Serialize; +use crate::error::application_download_error::ApplicationDownloadError; + use super::{ - application_download_error::ApplicationDownloadError, download_manager_builder::{CurrentProgressObject, DownloadAgent}, downloadable_metadata::DownloadableMetadata, queue::Queue, diff --git a/src-tauri/src/download_manager/download_manager_builder.rs b/src-tauri/src/download_manager/download_manager_builder.rs index 97dd0f6..4738730 100644 --- a/src-tauri/src/download_manager/download_manager_builder.rs +++ b/src-tauri/src/download_manager/download_manager_builder.rs @@ -7,13 +7,15 @@ use std::{ thread::{spawn, JoinHandle}, }; -use log::info; +use log::{debug, error, info}; use tauri::{AppHandle, Emitter}; -use crate::games::library::{QueueUpdateEvent, QueueUpdateEventQueueData, StatsUpdateEvent}; +use crate::{ + error::application_download_error::ApplicationDownloadError, + games::library::{QueueUpdateEvent, QueueUpdateEventQueueData, StatsUpdateEvent}, +}; use super::{ - application_download_error::ApplicationDownloadError, download_manager::{DownloadManager, DownloadManagerSignal, DownloadManagerStatus}, download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag}, downloadable::Downloadable, @@ -54,7 +56,7 @@ whichever download queue order is required. +----------------------------------------------------------------------------+ This download queue does not actually own any of the DownloadAgents. It is -simply a id-based reference system. The actual Agents are stored in the +simply an id-based reference system. The actual Agents are stored in the download_agent_registry HashMap, as ordering is no issue here. This is why appending or removing from the download_queue must be done via signals. @@ -239,6 +241,7 @@ impl DownloadManagerBuilder { match download_agent.download(&app_handle) { // Ok(true) is for completed and exited properly Ok(true) => { + debug!("download {:?} has completed", download_agent.metadata()); download_agent.on_complete(&app_handle); sender .send(DownloadManagerSignal::Completed(download_agent.metadata())) @@ -249,6 +252,7 @@ impl DownloadManagerBuilder { download_agent.on_incomplete(&app_handle); } Err(e) => { + error!("download {:?} has error {}", download_agent.metadata(), &e); download_agent.on_error(&app_handle, e.clone()); sender.send(DownloadManagerSignal::Error(e)).unwrap(); } diff --git a/src-tauri/src/download_manager/downloadable.rs b/src-tauri/src/download_manager/downloadable.rs index b063989..181b329 100644 --- a/src-tauri/src/download_manager/downloadable.rs +++ b/src-tauri/src/download_manager/downloadable.rs @@ -2,9 +2,10 @@ use std::sync::Arc; use tauri::AppHandle; +use crate::error::application_download_error::ApplicationDownloadError; + use super::{ - application_download_error::ApplicationDownloadError, download_manager::DownloadStatus, - download_thread_control_flag::DownloadThreadControl, + download_manager::DownloadStatus, download_thread_control_flag::DownloadThreadControl, downloadable_metadata::DownloadableMetadata, progress_object::ProgressObject, }; diff --git a/src-tauri/src/download_manager/mod.rs b/src-tauri/src/download_manager/mod.rs index 67ceb00..0d664dd 100644 --- a/src-tauri/src/download_manager/mod.rs +++ b/src-tauri/src/download_manager/mod.rs @@ -1,4 +1,4 @@ -pub mod application_download_error; +pub mod commands; pub mod download_manager; pub mod download_manager_builder; pub mod download_thread_control_flag; diff --git a/src-tauri/src/download_manager/progress_object.rs b/src-tauri/src/download_manager/progress_object.rs index 6dcd4a0..5d65207 100644 --- a/src-tauri/src/download_manager/progress_object.rs +++ b/src-tauri/src/download_manager/progress_object.rs @@ -138,19 +138,22 @@ impl ProgressObject { } } -#[throttle(10, Duration::from_secs(1))] +#[throttle(50, Duration::from_secs(1))] fn update_ui(progress_object: &ProgressObject, kilobytes_per_second: usize, time_remaining: usize) { - progress_object.sender - .send(DownloadManagerSignal::UpdateUIStats( - kilobytes_per_second, - time_remaining, - )) - .unwrap(); + progress_object + .sender + .send(DownloadManagerSignal::UpdateUIStats( + kilobytes_per_second, + time_remaining, + )) + .unwrap(); } -#[throttle(10, Duration::from_secs(1))] +#[throttle(50, Duration::from_secs(1))] fn update_queue(progress: &ProgressObject) { - progress.sender + progress + .sender .send(DownloadManagerSignal::UpdateUIQueue) .unwrap(); -} \ No newline at end of file +} + diff --git a/src-tauri/src/download_manager/application_download_error.rs b/src-tauri/src/error/application_download_error.rs similarity index 77% rename from src-tauri/src/download_manager/application_download_error.rs rename to src-tauri/src/error/application_download_error.rs index 33a99fa..fb6035a 100644 --- a/src-tauri/src/download_manager/application_download_error.rs +++ b/src-tauri/src/error/application_download_error.rs @@ -3,7 +3,7 @@ use std::{ io, }; -use crate::remote::RemoteAccessError; +use super::{remote_access_error::RemoteAccessError, setup_error::SetupError}; // TODO: Rename / separate from downloads #[derive(Debug, Clone)] @@ -28,16 +28,3 @@ impl Display for ApplicationDownloadError { } } } - -#[derive(Debug, Clone)] -pub enum SetupError { - Context, -} - -impl Display for SetupError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - SetupError::Context => write!(f, "Failed to generate contexts for download"), - } - } -} diff --git a/src-tauri/src/error/drop_server_error.rs b/src-tauri/src/error/drop_server_error.rs new file mode 100644 index 0000000..ab42263 --- /dev/null +++ b/src-tauri/src/error/drop_server_error.rs @@ -0,0 +1,10 @@ +use serde::Deserialize; + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DropServerError { + pub status_code: usize, + pub status_message: String, + pub message: String, + pub url: String, +} diff --git a/src-tauri/src/error/library_error.rs b/src-tauri/src/error/library_error.rs new file mode 100644 index 0000000..4957ae9 --- /dev/null +++ b/src-tauri/src/error/library_error.rs @@ -0,0 +1,16 @@ +use std::fmt::Display; + +pub enum LibraryError { + MetaNotFound(String), +} +impl Display for LibraryError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LibraryError::MetaNotFound(id) => write!( + f, + "Could not locate any installed version of game ID {} in the database", + id + ), + } + } +} diff --git a/src-tauri/src/error/mod.rs b/src-tauri/src/error/mod.rs new file mode 100644 index 0000000..a38a696 --- /dev/null +++ b/src-tauri/src/error/mod.rs @@ -0,0 +1,7 @@ +pub mod application_download_error; +pub mod drop_server_error; +pub mod library_error; +pub mod process_error; +pub mod remote_access_error; +pub mod setup_error; +pub mod user_error; diff --git a/src-tauri/src/error/process_error.rs b/src-tauri/src/error/process_error.rs new file mode 100644 index 0000000..8a105a7 --- /dev/null +++ b/src-tauri/src/error/process_error.rs @@ -0,0 +1,28 @@ +use std::{fmt::Display, io::Error}; + +pub enum ProcessError { + SetupRequired, + NotInstalled, + AlreadyRunning, + NotDownloaded, + InvalidID, + InvalidVersion, + IOError(Error), + InvalidPlatform, +} + +impl Display for ProcessError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + ProcessError::SetupRequired => "Game not set up", + ProcessError::NotInstalled => "Game not installed", + ProcessError::AlreadyRunning => "Game already running", + ProcessError::NotDownloaded => "Game not downloaded", + ProcessError::InvalidID => "Invalid Game ID", + ProcessError::InvalidVersion => "Invalid Game version", + ProcessError::IOError(error) => &error.to_string(), + ProcessError::InvalidPlatform => "This Game cannot be played on the current platform", + }; + write!(f, "{}", s) + } +} diff --git a/src-tauri/src/remote.rs b/src-tauri/src/error/remote_access_error.rs similarity index 54% rename from src-tauri/src/remote.rs rename to src-tauri/src/error/remote_access_error.rs index 1048df1..40740b2 100644 --- a/src-tauri/src/remote.rs +++ b/src-tauri/src/error/remote_access_error.rs @@ -1,15 +1,13 @@ use std::{ error::Error, fmt::{Display, Formatter}, - sync::{Arc, Mutex}, + sync::Arc, }; use http::StatusCode; -use log::{info, warn}; -use serde::Deserialize; -use url::{ParseError, Url}; +use url::ParseError; -use crate::{AppState, AppStatus, DB}; +use super::drop_server_error::DropServerError; #[derive(Debug, Clone)] pub enum RemoteAccessError { @@ -67,82 +65,4 @@ impl From for RemoteAccessError { RemoteAccessError::ParsingError(err) } } - impl std::error::Error for RemoteAccessError {} - -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct DropServerError { - pub status_code: usize, - pub status_message: String, - pub message: String, - pub url: String, -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct DropHealthcheck { - app_name: String, -} - -fn use_remote_logic<'a>( - url: String, - state: tauri::State<'_, Mutex>>, -) -> Result<(), RemoteAccessError> { - info!("connecting to url {}", url); - let base_url = Url::parse(&url)?; - - // Test Drop url - let test_endpoint = base_url.join("/api/v1")?; - let response = reqwest::blocking::get(test_endpoint.to_string())?; - - let result: DropHealthcheck = response.json()?; - - if result.app_name != "Drop" { - warn!("user entered drop endpoint that connected, but wasn't identified as Drop"); - return Err(RemoteAccessError::InvalidEndpoint); - } - - let mut app_state = state.lock().unwrap(); - app_state.status = AppStatus::SignedOut; - drop(app_state); - - let mut db_state = DB.borrow_data_mut().unwrap(); - db_state.base_url = base_url.to_string(); - drop(db_state); - - DB.save().unwrap(); - - Ok(()) -} - -#[tauri::command] -pub fn use_remote<'a>( - url: String, - state: tauri::State<'_, Mutex>>, -) -> Result<(), String> { - let result = use_remote_logic(url, state); - - if result.is_err() { - return Err(result.err().unwrap().to_string()); - } - - Ok(()) -} - -#[tauri::command] -pub fn gen_drop_url(path: String) -> Result { - let base_url = { - let handle = DB.borrow_data().unwrap(); - - if handle.base_url.is_empty() { - return Ok("".to_string()); - }; - - Url::parse(&handle.base_url).unwrap() - }; - - let url = base_url.join(&path).unwrap(); - - Ok(url.to_string()) -} diff --git a/src-tauri/src/error/setup_error.rs b/src-tauri/src/error/setup_error.rs new file mode 100644 index 0000000..bae7fea --- /dev/null +++ b/src-tauri/src/error/setup_error.rs @@ -0,0 +1,14 @@ +use std::fmt::{Display, Formatter}; + +#[derive(Debug, Clone)] +pub enum SetupError { + Context, +} + +impl Display for SetupError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + SetupError::Context => write!(f, "Failed to generate contexts for download"), + } + } +} diff --git a/src-tauri/src/error/user_error.rs b/src-tauri/src/error/user_error.rs new file mode 100644 index 0000000..bb019fc --- /dev/null +++ b/src-tauri/src/error/user_error.rs @@ -0,0 +1,66 @@ +use std::{ + fmt::Display, + ops::{FromResidual, Try}, +}; + +use serde::Serialize; + +pub enum UserValue +where + T: Serialize, + D: Display, +{ + Ok(T), + Err(D), +} +impl Serialize for UserValue { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + UserValue::Ok(data) => data.serialize(serializer), + UserValue::Err(err) => serializer.serialize_str(err.to_string().as_ref()), + } + } +} + +impl From> for UserValue { + fn from(value: Result) -> Self { + match value { + Ok(data) => UserValue::Ok(data), + Err(data) => UserValue::Err(data), + } + } +} + +impl Try for UserValue { + type Output = T; + + type Residual = D; + + fn from_output(output: Self::Output) -> Self { + Self::Ok(output) + } + + fn branch(self) -> std::ops::ControlFlow { + match self { + UserValue::Ok(data) => std::ops::ControlFlow::Continue(data), + UserValue::Err(e) => std::ops::ControlFlow::Break(e), + } + } +} +impl FromResidual for UserValue { + fn from_residual(residual: ::Residual) -> Self { + UserValue::Err(residual) + } +} +impl FromResidual> for UserValue { + fn from_residual(residual: Result) -> Self { + match residual { + Ok(_) => unreachable!(), + Err(e) => UserValue::Err(e), + } + } +} + diff --git a/src-tauri/src/games/commands.rs b/src-tauri/src/games/commands.rs new file mode 100644 index 0000000..beec0bc --- /dev/null +++ b/src-tauri/src/games/commands.rs @@ -0,0 +1,57 @@ +use std::sync::Mutex; + +use tauri::AppHandle; + +use crate::{ + error::{ + library_error::LibraryError, remote_access_error::RemoteAccessError, user_error::UserValue, + }, + games::library::{get_current_meta, uninstall_game_logic}, + AppState, +}; + +use super::{ + library::{ + fetch_game_logic, fetch_game_verion_options_logic, fetch_library_logic, FetchGameStruct, + Game, GameVersionOption, + }, + state::{GameStatusManager, GameStatusWithTransient}, +}; + +#[tauri::command] +pub fn fetch_library(app: AppHandle) -> UserValue, RemoteAccessError> { + fetch_library_logic(app).into() +} + +#[tauri::command] +pub fn fetch_game( + game_id: String, + app: tauri::AppHandle, +) -> UserValue { + fetch_game_logic(game_id, app).into() +} + +#[tauri::command] +pub fn fetch_game_status(id: String) -> GameStatusWithTransient { + GameStatusManager::fetch_state(&id) +} + +#[tauri::command] +pub fn uninstall_game(game_id: String, app_handle: AppHandle) -> UserValue<(), LibraryError> { + let meta = match get_current_meta(&game_id) { + Some(data) => data, + None => return UserValue::Err(LibraryError::MetaNotFound(game_id)), + }; + println!("{:?}", meta); + uninstall_game_logic(meta, &app_handle); + + UserValue::Ok(()) +} + +#[tauri::command] +pub fn fetch_game_verion_options( + game_id: String, + state: tauri::State<'_, Mutex>, +) -> UserValue, RemoteAccessError> { + fetch_game_verion_options_logic(game_id, state).into() +} diff --git a/src-tauri/src/games/downloads/commands.rs b/src-tauri/src/games/downloads/commands.rs new file mode 100644 index 0000000..d3c604a --- /dev/null +++ b/src-tauri/src/games/downloads/commands.rs @@ -0,0 +1,31 @@ +use std::sync::{mpsc::SendError, Arc, Mutex}; + +use crate::{ + download_manager::{download_manager::DownloadManagerSignal, downloadable::Downloadable}, + error::user_error::UserValue, + AppState, +}; + +use super::download_agent::GameDownloadAgent; + +#[tauri::command] +pub fn download_game( + game_id: String, + game_version: String, + install_dir: usize, + state: tauri::State<'_, Mutex>, +) -> UserValue<(), SendError> { + let sender = state.lock().unwrap().download_manager.get_sender(); + let game_download_agent = Arc::new(Box::new(GameDownloadAgent::new( + game_id, + game_version, + install_dir, + sender, + )) as Box); + state + .lock() + .unwrap() + .download_manager + .queue_download(game_download_agent) + .into() +} diff --git a/src-tauri/src/games/downloads/download_agent.rs b/src-tauri/src/games/downloads/download_agent.rs index a49b66c..a9da4c3 100644 --- a/src-tauri/src/games/downloads/download_agent.rs +++ b/src-tauri/src/games/downloads/download_agent.rs @@ -1,6 +1,7 @@ use crate::auth::generate_authorization_header; -use crate::db::{set_game_status, ApplicationTransientStatus, DatabaseImpls}; -use crate::download_manager::application_download_error::ApplicationDownloadError; +use crate::database::db::{ + set_game_status, ApplicationTransientStatus, DatabaseImpls, GameDownloadStatus, +}; use crate::download_manager::download_manager::{DownloadManagerSignal, DownloadStatus}; use crate::download_manager::download_thread_control_flag::{ DownloadThreadControl, DownloadThreadControlFlag, @@ -8,9 +9,10 @@ use crate::download_manager::download_thread_control_flag::{ use crate::download_manager::downloadable::Downloadable; use crate::download_manager::downloadable_metadata::{DownloadType, DownloadableMetadata}; use crate::download_manager::progress_object::{ProgressHandle, ProgressObject}; +use crate::error::application_download_error::ApplicationDownloadError; +use crate::error::remote_access_error::RemoteAccessError; use crate::games::downloads::manifest::{DropDownloadContext, DropManifest}; -use crate::games::library::{on_game_complete, push_game_update}; -use crate::remote::RemoteAccessError; +use crate::games::library::{on_game_complete, push_game_update, GameUpdateEvent}; use crate::DB; use log::{debug, error, info}; use rayon::ThreadPoolBuilder; @@ -100,7 +102,7 @@ impl GameDownloadAgent { let timer = Instant::now(); push_game_update( app_handle, - &self.metadata(), + &self.metadata().id, ( None, Some(ApplicationTransientStatus::Downloading { @@ -208,7 +210,8 @@ impl GameDownloadAgent { { let mut completed_contexts_lock = self.completed_contexts.lock().unwrap(); completed_contexts_lock.clear(); - completed_contexts_lock.extend(self.stored_manifest.get_completed_contexts()); + completed_contexts_lock + .extend_from_slice(&self.stored_manifest.get_completed_contexts()); } for (raw_path, chunk) in manifest { @@ -247,9 +250,12 @@ impl GameDownloadAgent { // TODO: Change return value on Err pub fn run(&self) -> Result { - info!("downloading game: {}", self.id); let max_download_threads = DB.borrow_data().unwrap().settings.max_download_threads; + info!( + "downloading game: {} with {} threads", + self.id, max_download_threads + ); let pool = ThreadPoolBuilder::new() .num_threads(max_download_threads) .build() @@ -401,8 +407,18 @@ impl Downloadable for GameDownloadAgent { .unwrap(); } - fn on_incomplete(&self, _app_handle: &tauri::AppHandle) { + fn on_incomplete(&self, app_handle: &tauri::AppHandle) { + let meta = self.metadata(); *self.status.lock().unwrap() = DownloadStatus::Queued; + app_handle + .emit( + &format!("update_game/{}", meta.id), + GameUpdateEvent { + game_id: meta.id.clone(), + status: (Some(GameDownloadStatus::Remote {}), None), + }, + ) + .unwrap(); } fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {} diff --git a/src-tauri/src/games/downloads/download_commands.rs b/src-tauri/src/games/downloads/download_commands.rs deleted file mode 100644 index c9ea576..0000000 --- a/src-tauri/src/games/downloads/download_commands.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use crate::{ - download_manager::{downloadable::Downloadable, downloadable_metadata::DownloadableMetadata}, - AppState, -}; - -use super::download_agent::GameDownloadAgent; - -#[tauri::command] -pub fn download_game( - game_id: String, - game_version: String, - install_dir: usize, - state: tauri::State<'_, Mutex>, -) -> Result<(), String> { - let sender = state.lock().unwrap().download_manager.get_sender(); - let game_download_agent = Arc::new(Box::new(GameDownloadAgent::new( - game_id, - game_version, - install_dir, - sender, - )) as Box); - state - .lock() - .unwrap() - .download_manager - .queue_download(game_download_agent) - .map_err(|_| "An error occurred while communicating with the download manager.".to_string()) -} - -#[tauri::command] -pub fn pause_game_downloads(state: tauri::State<'_, Mutex>) { - state.lock().unwrap().download_manager.pause_downloads() -} - -#[tauri::command] -pub fn resume_game_downloads(state: tauri::State<'_, Mutex>) { - state.lock().unwrap().download_manager.resume_downloads() -} - -#[tauri::command] -pub fn move_game_in_queue( - state: tauri::State<'_, Mutex>, - old_index: usize, - new_index: usize, -) { - state - .lock() - .unwrap() - .download_manager - .rearrange(old_index, new_index) -} - -#[tauri::command] -pub fn cancel_game(state: tauri::State<'_, Mutex>, meta: DownloadableMetadata) { - state.lock().unwrap().download_manager.cancel(meta) -} diff --git a/src-tauri/src/games/downloads/download_logic.rs b/src-tauri/src/games/downloads/download_logic.rs index 3c9ca60..87f057f 100644 --- a/src-tauri/src/games/downloads/download_logic.rs +++ b/src-tauri/src/games/downloads/download_logic.rs @@ -1,10 +1,10 @@ -use crate::download_manager::application_download_error::ApplicationDownloadError; use crate::download_manager::download_thread_control_flag::{ DownloadThreadControl, DownloadThreadControlFlag, }; use crate::download_manager::progress_object::ProgressHandle; +use crate::error::application_download_error::ApplicationDownloadError; +use crate::error::remote_access_error::RemoteAccessError; use crate::games::downloads::manifest::DropDownloadContext; -use crate::remote::RemoteAccessError; use log::{error, warn}; use md5::{Context, Digest}; use reqwest::blocking::{RequestBuilder, Response}; diff --git a/src-tauri/src/games/downloads/mod.rs b/src-tauri/src/games/downloads/mod.rs index 8709ca6..c9b3cd4 100644 --- a/src-tauri/src/games/downloads/mod.rs +++ b/src-tauri/src/games/downloads/mod.rs @@ -1,5 +1,5 @@ +pub mod commands; pub mod download_agent; -pub mod download_commands; mod download_logic; mod manifest; mod stored_manifest; diff --git a/src-tauri/src/games/library.rs b/src-tauri/src/games/library.rs index 33038b3..ab4cc9e 100644 --- a/src-tauri/src/games/library.rs +++ b/src-tauri/src/games/library.rs @@ -8,14 +8,15 @@ use tauri::Emitter; use tauri::{AppHandle, Manager}; use urlencoding::encode; -use crate::db::GameVersion; -use crate::db::{ApplicationTransientStatus, DatabaseImpls, GameDownloadStatus}; +use crate::database::db::GameVersion; +use crate::database::db::{ApplicationTransientStatus, DatabaseImpls, GameDownloadStatus}; use crate::download_manager::download_manager::DownloadStatus; use crate::download_manager::downloadable_metadata::DownloadableMetadata; +use crate::error::remote_access_error::RemoteAccessError; use crate::games::state::{GameStatusManager, GameStatusWithTransient}; use crate::process::process_manager::Platform; -use crate::remote::RemoteAccessError; -use crate::{auth::generate_authorization_header, AppState, DB}; +use crate::remote::auth::generate_authorization_header; +use crate::{AppState, DB}; #[derive(serde::Serialize)] pub struct FetchGameStruct { @@ -78,7 +79,7 @@ pub struct GameVersionOption { // total_size: usize, } -fn fetch_library_logic(app: AppHandle) -> Result, RemoteAccessError> { +pub fn fetch_library_logic(app: AppHandle) -> Result, RemoteAccessError> { let base_url = DB.fetch_base_url(); let library_url = base_url.join("/api/v1/client/user/library")?; @@ -118,12 +119,7 @@ fn fetch_library_logic(app: AppHandle) -> Result, RemoteAccessError> { Ok(games) } -#[tauri::command] -pub fn fetch_library(app: AppHandle) -> Result, String> { - fetch_library_logic(app).map_err(|e| e.to_string()) -} - -fn fetch_game_logic( +pub fn fetch_game_logic( id: String, app: tauri::AppHandle, ) -> Result { @@ -184,25 +180,7 @@ fn fetch_game_logic( Ok(data) } -#[tauri::command] -pub fn fetch_game(game_id: String, app: tauri::AppHandle) -> Result { - let result = fetch_game_logic(game_id, app); - - if result.is_err() { - return Err(result.err().unwrap().to_string()); - } - - Ok(result.unwrap()) -} - -#[tauri::command] -pub fn fetch_game_status(id: String) -> Result { - let status = GameStatusManager::fetch_state(&id); - - Ok(status) -} - -fn fetch_game_verion_options_logic( +pub fn fetch_game_verion_options_logic( game_id: String, state: tauri::State<'_, Mutex>, ) -> Result, RemoteAccessError> { @@ -238,19 +216,7 @@ fn fetch_game_verion_options_logic( Ok(data) } -#[tauri::command] -pub fn uninstall_game( - game_id: String, - app_handle: AppHandle, -) -> Result<(), String> { - let meta = get_current_meta(&game_id)?; - println!("{:?}", meta); - uninstall_game_logic(meta, &app_handle); - - Ok(()) -} - -fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) { +pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) { println!("Triggered uninstall for agent"); let mut db_handle = DB.borrow_data_mut().unwrap(); db_handle @@ -261,7 +227,7 @@ fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) { push_game_update( app_handle, - &meta, + &meta.id, (None, Some(ApplicationTransientStatus::Uninstalling {})), ); @@ -309,7 +275,7 @@ fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) { push_game_update( &app_handle, - &meta, + &meta.id, (Some(GameDownloadStatus::Remote {}), None), ); } @@ -317,25 +283,13 @@ fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) { } } -pub fn get_current_meta(game_id: &String) -> Result { - match DB - .borrow_data() +pub fn get_current_meta(game_id: &String) -> Option { + DB.borrow_data() .unwrap() .applications .installed_game_version .get(game_id) - { - Some(meta) => Ok(meta.clone()), - None => Err(String::from("Could not find installed version")), - } -} - -#[tauri::command] -pub fn fetch_game_verion_options( - game_id: String, - state: tauri::State<'_, Mutex>, -) -> Result, String> { - fetch_game_verion_options_logic(game_id, state).map_err(|e| e.to_string()) + .cloned() } pub fn on_game_complete( @@ -414,16 +368,12 @@ pub fn on_game_complete( Ok(()) } -pub fn push_game_update( - app_handle: &AppHandle, - meta: &DownloadableMetadata, - status: GameStatusWithTransient, -) { +pub fn push_game_update(app_handle: &AppHandle, game_id: &String, status: GameStatusWithTransient) { app_handle .emit( - &format!("update_game/{}", meta.id), + &format!("update_game/{}", game_id), GameUpdateEvent { - game_id: meta.id.clone(), + game_id: game_id.clone(), status, }, ) diff --git a/src-tauri/src/games/mod.rs b/src-tauri/src/games/mod.rs index d7b17f3..65c5c6b 100644 --- a/src-tauri/src/games/mod.rs +++ b/src-tauri/src/games/mod.rs @@ -1,3 +1,4 @@ +pub mod commands; pub mod downloads; pub mod library; pub mod state; diff --git a/src-tauri/src/games/state.rs b/src-tauri/src/games/state.rs index cd03439..29287db 100644 --- a/src-tauri/src/games/state.rs +++ b/src-tauri/src/games/state.rs @@ -1,5 +1,5 @@ use crate::{ - db::{ApplicationTransientStatus, GameDownloadStatus}, + database::db::{ApplicationTransientStatus, GameDownloadStatus}, DB, }; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index af980dd..1a3903e 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,35 +1,35 @@ -mod auth; -mod db; +#![feature(try_trait_v2)] + +mod database; mod games; mod autostart; mod cleanup; -mod debug; +mod commands; mod download_manager; +mod error; mod process; mod remote; -pub mod settings; -use crate::autostart::{get_autostart_enabled, toggle_autostart}; -use crate::db::DatabaseImpls; -use auth::{ - auth_initiate, generate_authorization_header, manual_recieve_handshake, recieve_handshake, - retry_connect, sign_out, -}; +use crate::database::db::DatabaseImpls; +use autostart::{get_autostart_enabled, toggle_autostart}; use cleanup::{cleanup_and_exit, quit}; -use db::{ - add_download_dir, delete_download_dir, fetch_download_dir_stats, DatabaseInterface, - GameDownloadStatus, DATA_ROOT_DIR, +use commands::fetch_state; +use database::commands::{ + add_download_dir, delete_download_dir, fetch_download_dir_stats, fetch_system_data, + update_settings, +}; +use database::db::{DatabaseInterface, GameDownloadStatus, DATA_ROOT_DIR}; +use download_manager::commands::{ + cancel_game, move_download_in_queue, pause_downloads, resume_downloads, }; -use debug::fetch_system_data; use download_manager::download_manager::DownloadManager; use download_manager::download_manager_builder::DownloadManagerBuilder; -use games::downloads::download_commands::{ - cancel_game, download_game, move_game_in_queue, pause_game_downloads, resume_game_downloads, -}; -use games::library::{ - fetch_game, fetch_game_status, fetch_game_verion_options, fetch_library, uninstall_game, Game, +use games::commands::{ + fetch_game, fetch_game_status, fetch_game_verion_options, fetch_library, uninstall_game, }; +use games::downloads::commands::download_game; +use games::library::Game; use http::Response; use http::{header::*, response::Builder as ResponseBuilder}; use log::{debug, info, warn, LevelFilter}; @@ -38,11 +38,13 @@ use log4rs::append::file::FileAppender; use log4rs::config::{Appender, Root}; use log4rs::encode::pattern::PatternEncoder; use log4rs::Config; -use process::process_commands::{kill_game, launch_game}; +use process::commands::{kill_game, launch_game}; use process::process_manager::ProcessManager; -use remote::{gen_drop_url, use_remote}; +use remote::auth::{self, generate_authorization_header, recieve_handshake}; +use remote::commands::{ + auth_initiate, gen_drop_url, manual_recieve_handshake, retry_connect, sign_out, use_remote, +}; use serde::{Deserialize, Serialize}; -use settings::amend_settings; use std::path::Path; use std::sync::Arc; use std::{ @@ -88,14 +90,6 @@ pub struct AppState<'a> { process_manager: Arc>>, } -#[tauri::command] -fn fetch_state(state: tauri::State<'_, Mutex>>) -> Result { - let guard = state.lock().unwrap(); - let cloned_state = serde_json::to_string(&guard.clone()).map_err(|e| e.to_string())?; - drop(guard); - Ok(cloned_state) -} - fn setup(handle: AppHandle) -> AppState<'static> { let logfile = FileAppender::builder() .encoder(Box::new(PatternEncoder::new("{d} | {l} | {f} - {m}{n}"))) @@ -139,6 +133,7 @@ fn setup(handle: AppHandle) -> AppState<'static> { debug!("Database is set up"); + // TODO: Account for possible failure let (app_status, user) = auth::setup().unwrap(); let db_handle = DB.borrow_data().unwrap(); @@ -147,8 +142,8 @@ fn setup(handle: AppHandle) -> AppState<'static> { drop(db_handle); for (game_id, status) in statuses.into_iter() { match status { - db::GameDownloadStatus::Remote {} => {} - db::GameDownloadStatus::SetupRequired { + database::db::GameDownloadStatus::Remote {} => {} + database::db::GameDownloadStatus::SetupRequired { version_name: _, install_dir, } => { @@ -157,7 +152,7 @@ fn setup(handle: AppHandle) -> AppState<'static> { missing_games.push(game_id); } } - db::GameDownloadStatus::Installed { + database::db::GameDownloadStatus::Installed { version_name: _, install_dir, } => { @@ -222,7 +217,7 @@ pub fn run() { quit, fetch_system_data, // User utils - amend_settings, + update_settings, // Auth auth_initiate, retry_connect, @@ -241,9 +236,9 @@ pub fn run() { fetch_game_verion_options, // Downloads download_game, - move_game_in_queue, - pause_game_downloads, - resume_game_downloads, + move_download_in_queue, + pause_downloads, + resume_downloads, cancel_game, uninstall_game, // Processes diff --git a/src-tauri/src/process/commands.rs b/src-tauri/src/process/commands.rs new file mode 100644 index 0000000..0d7492e --- /dev/null +++ b/src-tauri/src/process/commands.rs @@ -0,0 +1,44 @@ +use std::sync::Mutex; + +use crate::{ + error::{process_error::ProcessError, user_error::UserValue}, + AppState, DB, +}; + +#[tauri::command] +pub fn launch_game( + id: String, + state: tauri::State<'_, Mutex>, +) -> UserValue<(), ProcessError> { + let state_lock = state.lock().unwrap(); + let mut process_manager_lock = state_lock.process_manager.lock().unwrap(); + + //let meta = DownloadableMetadata { + // id, + // version: Some(version), + // download_type: DownloadType::Game, + //}; + + match process_manager_lock.launch_process(id) { + Ok(_) => {} + Err(e) => return UserValue::Err(e), + }; + + drop(process_manager_lock); + drop(state_lock); + + UserValue::Ok(()) +} + +#[tauri::command] +pub fn kill_game( + game_id: String, + state: tauri::State<'_, Mutex>, +) -> UserValue<(), ProcessError> { + let state_lock = state.lock().unwrap(); + let mut process_manager_lock = state_lock.process_manager.lock().unwrap(); + process_manager_lock + .kill_game(game_id) + .map_err(ProcessError::IOError) + .into() +} diff --git a/src-tauri/src/process/mod.rs b/src-tauri/src/process/mod.rs index 85692c9..6a1aed5 100644 --- a/src-tauri/src/process/mod.rs +++ b/src-tauri/src/process/mod.rs @@ -1,3 +1,3 @@ +pub mod commands; pub mod compat; -pub mod process_commands; pub mod process_manager; diff --git a/src-tauri/src/process/process_commands.rs b/src-tauri/src/process/process_commands.rs deleted file mode 100644 index 53bb11e..0000000 --- a/src-tauri/src/process/process_commands.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::sync::Mutex; - -use crate::{ - db::GameDownloadStatus, - download_manager::downloadable_metadata::{DownloadType, DownloadableMetadata}, - games::library::get_current_meta, - AppState, DB, -}; - -#[tauri::command] -pub fn launch_game(id: String, state: tauri::State<'_, Mutex>) -> Result<(), String> { - let state_lock = state.lock().unwrap(); - let mut process_manager_lock = state_lock.process_manager.lock().unwrap(); - - let version = match DB - .borrow_data() - .unwrap() - .applications - .game_statuses - .get(&id) - .cloned() - { - Some(GameDownloadStatus::Installed { - version_name, - .. - }) => version_name, - Some(GameDownloadStatus::SetupRequired { - .. - }) => return Err(String::from("Game setup still required")), - _ => return Err(String::from("Game not installed")), - }; - - let meta = DownloadableMetadata { - id, - version: Some(version), - download_type: DownloadType::Game, - }; - - process_manager_lock.launch_process(meta)?; - - drop(process_manager_lock); - drop(state_lock); - - Ok(()) -} - -#[tauri::command] -pub fn kill_game(game_id: String, state: tauri::State<'_, Mutex>) -> Result<(), String> { - let meta = get_current_meta(&game_id)?; - let state_lock = state.lock().unwrap(); - let mut process_manager_lock = state_lock.process_manager.lock().unwrap(); - process_manager_lock - .kill_game(meta) - .map_err(|x| x.to_string()) -} diff --git a/src-tauri/src/process/process_manager.rs b/src-tauri/src/process/process_manager.rs index 581b325..f53ea20 100644 --- a/src-tauri/src/process/process_manager.rs +++ b/src-tauri/src/process/process_manager.rs @@ -1,7 +1,7 @@ use std::{ collections::HashMap, fs::{File, OpenOptions}, - io, + io::{self, Error}, path::{Path, PathBuf}, process::{Child, Command, ExitStatus}, sync::{Arc, Mutex}, @@ -15,17 +15,17 @@ use tauri::{AppHandle, Manager}; use umu_wrapper_lib::command_builder::UmuCommandBuilder; use crate::{ - db::{ApplicationTransientStatus, GameDownloadStatus, DATA_ROOT_DIR}, - download_manager::downloadable_metadata::DownloadableMetadata, - games::library::push_game_update, - games::state::GameStatusManager, + database::db::{ApplicationTransientStatus, GameDownloadStatus, DATA_ROOT_DIR}, + download_manager::downloadable_metadata::{DownloadType, DownloadableMetadata}, + error::process_error::ProcessError, + games::{library::push_game_update, state::GameStatusManager}, AppState, DB, }; pub struct ProcessManager<'a> { current_platform: Platform, log_output_dir: PathBuf, - processes: HashMap>, + processes: HashMap>, app_handle: AppHandle, game_launchers: HashMap<(Platform, Platform), &'a (dyn ProcessHandler + Sync + Send + 'static)>, } @@ -82,8 +82,8 @@ impl ProcessManager<'_> { */ (absolute_exe, Vec::new()) } - pub fn kill_game(&mut self, meta: DownloadableMetadata) -> Result<(), io::Error> { - return match self.processes.get(&meta) { + pub fn kill_game(&mut self, game_id: String) -> Result<(), io::Error> { + return match self.processes.get(&game_id) { Some(child) => { child.kill()?; child.wait()?; @@ -96,24 +96,26 @@ impl ProcessManager<'_> { }; } - fn on_process_finish( - &mut self, - meta: DownloadableMetadata, - result: Result, - ) { - if !self.processes.contains_key(&meta) { + fn on_process_finish(&mut self, game_id: String, result: Result) { + if !self.processes.contains_key(&game_id) { warn!("process on_finish was called, but game_id is no longer valid. finished with result: {:?}", result); return; } - debug!("process for {:?} exited with {:?}", meta, result); + debug!("process for {:?} exited with {:?}", &game_id, result); - self.processes.remove(&meta); + self.processes.remove(&game_id); let mut db_handle = DB.borrow_data_mut().unwrap(); + let meta = db_handle + .applications + .installed_game_version + .get(&game_id) + .cloned() + .unwrap(); db_handle.applications.transient_statuses.remove(&meta); - let current_state = db_handle.applications.game_statuses.get(&meta.id).cloned(); + let current_state = db_handle.applications.game_statuses.get(&game_id).cloned(); if let Some(saved_state) = current_state { if let GameDownloadStatus::SetupRequired { version_name, @@ -123,7 +125,7 @@ impl ProcessManager<'_> { if let Ok(exit_code) = result { if exit_code.success() { db_handle.applications.game_statuses.insert( - meta.id.clone(), + game_id.clone(), GameDownloadStatus::Installed { version_name: version_name.to_string(), install_dir: install_dir.to_string(), @@ -135,9 +137,9 @@ impl ProcessManager<'_> { } drop(db_handle); - let status = GameStatusManager::fetch_state(&meta.id); + let status = GameStatusManager::fetch_state(&game_id); - push_game_update(&self.app_handle, &meta, status); + push_game_update(&self.app_handle, &game_id, status); // TODO better management } @@ -149,28 +151,48 @@ impl ProcessManager<'_> { .contains_key(&(current.clone(), platform.clone()))) } - pub fn launch_process(&mut self, meta: DownloadableMetadata) -> Result<(), String> { - if self.processes.contains_key(&meta) { - return Err("Game or setup is already running.".to_owned()); + pub fn launch_process(&mut self, game_id: String) -> Result<(), ProcessError> { + if self.processes.contains_key(&game_id) { + return Err(ProcessError::AlreadyRunning); } + let version = match DB + .borrow_data() + .unwrap() + .applications + .game_statuses + .get(&game_id) + .cloned() + { + Some(GameDownloadStatus::Installed { version_name, .. }) => version_name, + Some(GameDownloadStatus::SetupRequired { .. }) => { + return Err(ProcessError::SetupRequired).into() + } + _ => return Err(ProcessError::NotInstalled).into(), + }; + let meta = DownloadableMetadata { + id: game_id.clone(), + version: Some(version.clone()), + download_type: DownloadType::Game, + }; + let mut db_lock = DB.borrow_data_mut().unwrap(); debug!( "Launching process {:?} with games {:?}", - meta, db_lock.applications.game_versions + &game_id, db_lock.applications.game_versions ); let game_status = db_lock .applications .game_statuses - .get(&meta.id) - .ok_or("game not installed")?; + .get(&game_id) + .ok_or(ProcessError::NotInstalled)?; let status_metadata: Option<(&String, &String)> = match game_status { GameDownloadStatus::Installed { version_name, install_dir, - } => Some((version_name, install_dir)), + } => Some((&version_name, &install_dir)), GameDownloadStatus::SetupRequired { version_name, install_dir, @@ -179,7 +201,7 @@ impl ProcessManager<'_> { }; if status_metadata.is_none() { - return Err("game has not been downloaded.".to_owned()); + return Err(ProcessError::NotDownloaded); } let (version_name, install_dir) = status_metadata.unwrap(); @@ -187,10 +209,10 @@ impl ProcessManager<'_> { let game_version = db_lock .applications .game_versions - .get(&meta.id) - .ok_or("Invalid game ID".to_owned())? + .get(&game_id) + .ok_or(ProcessError::InvalidID)? .get(version_name) - .ok_or("Invalid version name".to_owned())?; + .ok_or(ProcessError::InvalidVersion)?; let raw_command: String = match game_status { GameDownloadStatus::Installed { @@ -222,11 +244,11 @@ impl ProcessManager<'_> { .create(true) .open(self.log_output_dir.join(format!( "{}-{}-{}.log", - meta.id.clone(), - meta.version.clone().unwrap_or_default(), + &game_id, + &version, current_time.timestamp() ))) - .map_err(|v| v.to_string())?; + .map_err(ProcessError::IOError)?; let error_file = OpenOptions::new() .write(true) @@ -235,11 +257,11 @@ impl ProcessManager<'_> { .create(true) .open(self.log_output_dir.join(format!( "{}-{}-{}-error.log", - meta.id.clone(), - meta.version.clone().unwrap_or_default(), + &game_id, + &version, current_time.timestamp() ))) - .map_err(|v| v.to_string())?; + .map_err(ProcessError::IOError)?; let current_platform = self.current_platform.clone(); let target_platform = game_version.platform.clone(); @@ -247,20 +269,21 @@ impl ProcessManager<'_> { let game_launcher = self .game_launchers .get(&(current_platform, target_platform)) - .ok_or("Invalid version for this platform.") - .map_err(|e| e.to_string())?; + .ok_or(ProcessError::InvalidPlatform)?; - let launch_process = game_launcher.launch_process( - &meta, - command.to_str().unwrap().to_owned(), - args, - target_current_dir, - log_file, - error_file, - )?; + let launch_process = game_launcher + .launch_process( + &meta, + command.to_str().unwrap().to_owned(), + args, + target_current_dir, + log_file, + error_file, + ) + .map_err(ProcessError::IOError)?; let launch_process_handle = - Arc::new(SharedChild::new(launch_process).map_err(|e| e.to_string())?); + Arc::new(SharedChild::new(launch_process).map_err(ProcessError::IOError)?); db_lock .applications @@ -269,7 +292,7 @@ impl ProcessManager<'_> { push_game_update( &self.app_handle, - &meta, + &meta.id, (None, Some(ApplicationTransientStatus::Running {})), ); @@ -284,7 +307,7 @@ impl ProcessManager<'_> { let app_state_handle = app_state.lock().unwrap(); let mut process_manager_handle = app_state_handle.process_manager.lock().unwrap(); - process_manager_handle.on_process_finish(wait_thread_game_id, result); + process_manager_handle.on_process_finish(wait_thread_game_id.id, result); // As everything goes out of scope, they should get dropped // But just to explicit about it @@ -292,7 +315,7 @@ impl ProcessManager<'_> { drop(app_state_handle); }); - self.processes.insert(meta, wait_thread_handle); + self.processes.insert(meta.id, wait_thread_handle); info!("finished spawning process"); @@ -315,7 +338,7 @@ pub trait ProcessHandler: Send + 'static { current_dir: &str, log_file: File, error_file: File, - ) -> Result; + ) -> Result; } struct NativeGameLauncher; @@ -328,14 +351,13 @@ impl ProcessHandler for NativeGameLauncher { current_dir: &str, log_file: File, error_file: File, - ) -> Result { + ) -> Result { Command::new(command) .current_dir(current_dir) .stdout(log_file) .stderr(error_file) .args(args) .spawn() - .map_err(|v| v.to_string()) } } @@ -350,12 +372,11 @@ impl ProcessHandler for UMULauncher { _current_dir: &str, _log_file: File, _error_file: File, - ) -> Result { + ) -> Result { UmuCommandBuilder::new(UMU_LAUNCHER_EXECUTABLE, command) .game_id(String::from("0")) .launch_args(args) .build() .spawn() - .map_err(|x| x.to_string()) } } diff --git a/src-tauri/src/auth.rs b/src-tauri/src/remote/auth.rs similarity index 72% rename from src-tauri/src/auth.rs rename to src-tauri/src/remote/auth.rs index 03ca9e3..afd0c9a 100644 --- a/src-tauri/src/auth.rs +++ b/src-tauri/src/remote/auth.rs @@ -8,8 +8,8 @@ use tauri::{AppHandle, Emitter, Manager}; use url::Url; use crate::{ - db::{DatabaseAuth, DatabaseImpls}, - remote::{DropServerError, RemoteAccessError}, + database::db::{DatabaseAuth, DatabaseImpls}, + error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError}, AppState, AppStatus, User, DB, }; @@ -73,7 +73,6 @@ pub fn fetch_user() -> Result { .get(endpoint.to_string()) .header("Authorization", header) .send()?; - if response.status() != 200 { let err: DropServerError = response.json().unwrap(); warn!("{:?}", err); @@ -85,9 +84,7 @@ pub fn fetch_user() -> Result { return Err(RemoteAccessError::InvalidResponse(err)); } - let user = response.json()?; - - Ok(user) + response.json::().map_err(|e| e.into()) } fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAccessError> { @@ -138,12 +135,6 @@ fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAc Ok(()) } -#[tauri::command] -pub fn manual_recieve_handshake(app: AppHandle, token: String) -> Result<(), String> { - recieve_handshake(app, format!("handshake/{}", token)); - Ok(()) -} - pub fn recieve_handshake(app: AppHandle, path: String) { // Tell the app we're processing app.emit("auth/processing", ()).unwrap(); @@ -158,7 +149,7 @@ pub fn recieve_handshake(app: AppHandle, path: String) { app.emit("auth/finished", ()).unwrap(); } -fn auth_initiate_wrapper() -> Result<(), RemoteAccessError> { +pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> { let base_url = { let db_lock = DB.borrow_data().unwrap(); Url::parse(&db_lock.base_url.clone())? @@ -189,71 +180,15 @@ fn auth_initiate_wrapper() -> Result<(), RemoteAccessError> { Ok(()) } -#[tauri::command] -pub fn auth_initiate() -> Result<(), String> { - let result = auth_initiate_wrapper(); - if result.is_err() { - return Err(result.err().unwrap().to_string()); - } - - Ok(()) -} - -#[tauri::command] -pub fn retry_connect(state: tauri::State<'_, Mutex>) -> Result<(), ()> { - let (app_status, user) = setup()?; - - let mut guard = state.lock().unwrap(); - guard.status = app_status; - guard.user = user; - drop(guard); - - Ok(()) -} - -pub fn setup() -> Result<(AppStatus, Option), ()> { +pub fn setup() -> Result<(AppStatus, Option), RemoteAccessError> { let data = DB.borrow_data().unwrap(); let auth = data.auth.clone(); drop(data); if auth.is_some() { - let user_result = fetch_user(); - if user_result.is_err() { - let error = user_result.err().unwrap(); - warn!("auth setup failed with: {}", error); - match error { - RemoteAccessError::FetchError(_) => { - return Ok((AppStatus::ServerUnavailable, None)) - } - _ => return Ok((AppStatus::SignedInNeedsReauth, None)), - } - } - return Ok((AppStatus::SignedIn, Some(user_result.unwrap()))); + let user_result = fetch_user()?; + return Ok((AppStatus::SignedIn, Some(user_result))); } Ok((AppStatus::SignedOut, None)) } - -#[tauri::command] -pub fn sign_out(app: AppHandle) -> Result<(), String> { - // Clear auth from database - { - let mut handle = DB.borrow_data_mut().unwrap(); - handle.auth = None; - drop(handle); - DB.save().unwrap(); - } - - // Update app state - { - let app_state = app.state::>(); - let mut app_state_handle = app_state.lock().unwrap(); - app_state_handle.status = AppStatus::SignedOut; - app_state_handle.user = None; - } - - // Emit event for frontend - app.emit("auth/signedout", ()).unwrap(); - - Ok(()) -} diff --git a/src-tauri/src/remote/commands.rs b/src-tauri/src/remote/commands.rs new file mode 100644 index 0000000..9dbfcb4 --- /dev/null +++ b/src-tauri/src/remote/commands.rs @@ -0,0 +1,78 @@ +use std::sync::Mutex; + +use tauri::{AppHandle, Emitter, Manager}; +use url::Url; + +use crate::{ + error::{remote_access_error::RemoteAccessError, user_error::UserValue}, + AppState, AppStatus, DB, +}; + +use super::{ + auth::{auth_initiate_logic, recieve_handshake, setup}, + remote::use_remote_logic, +}; + +#[tauri::command] +pub fn use_remote( + url: String, + state: tauri::State<'_, Mutex>>, +) -> UserValue<(), RemoteAccessError> { + UserValue::Ok(use_remote_logic(url, state)?) +} + +#[tauri::command] +pub fn gen_drop_url(path: String) -> UserValue { + let base_url = { + let handle = DB.borrow_data().unwrap(); + + Url::parse(&handle.base_url).map_err(RemoteAccessError::ParsingError)? + }; + + let url = base_url.join(&path).unwrap(); + + UserValue::Ok(url.to_string()) +} + +#[tauri::command] +pub fn sign_out(app: AppHandle) { + // Clear auth from database + { + let mut handle = DB.borrow_data_mut().unwrap(); + handle.auth = None; + drop(handle); + DB.save().unwrap(); + } + + // Update app state + { + let app_state = app.state::>(); + let mut app_state_handle = app_state.lock().unwrap(); + app_state_handle.status = AppStatus::SignedOut; + app_state_handle.user = None; + } + + // Emit event for frontend + app.emit("auth/signedout", ()).unwrap(); +} + +#[tauri::command] +pub fn retry_connect(state: tauri::State<'_, Mutex>) -> UserValue<(), RemoteAccessError> { + let (app_status, user) = setup()?; + + let mut guard = state.lock().unwrap(); + guard.status = app_status; + guard.user = user; + drop(guard); + UserValue::Ok(()) +} + +#[tauri::command] +pub fn auth_initiate() -> UserValue<(), RemoteAccessError> { + auth_initiate_logic().into() +} + +#[tauri::command] +pub fn manual_recieve_handshake(app: AppHandle, token: String) { + recieve_handshake(app, format!("handshake/{}", token)); +} diff --git a/src-tauri/src/remote/mod.rs b/src-tauri/src/remote/mod.rs new file mode 100644 index 0000000..29d8c02 --- /dev/null +++ b/src-tauri/src/remote/mod.rs @@ -0,0 +1,3 @@ +pub mod auth; +pub mod commands; +pub mod remote; diff --git a/src-tauri/src/remote/remote.rs b/src-tauri/src/remote/remote.rs new file mode 100644 index 0000000..7790982 --- /dev/null +++ b/src-tauri/src/remote/remote.rs @@ -0,0 +1,49 @@ +use std::{ + error::Error, + fmt::{Display, Formatter}, + sync::{Arc, Mutex}, +}; + +use http::StatusCode; +use log::{info, warn}; +use serde::Deserialize; +use url::{ParseError, Url}; + +use crate::{error::remote_access_error::RemoteAccessError, AppState, AppStatus, DB}; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct DropHealthcheck { + app_name: String, +} + +pub fn use_remote_logic( + url: String, + state: tauri::State<'_, Mutex>>, +) -> Result<(), RemoteAccessError> { + info!("connecting to url {}", url); + let base_url = Url::parse(&url)?; + + // Test Drop url + let test_endpoint = base_url.join("/api/v1")?; + let response = reqwest::blocking::get(test_endpoint.to_string())?; + + let result: DropHealthcheck = response.json()?; + + if result.app_name != "Drop" { + warn!("user entered drop endpoint that connected, but wasn't identified as Drop"); + return Err(RemoteAccessError::InvalidEndpoint); + } + + let mut app_state = state.lock().unwrap(); + app_state.status = AppStatus::SignedOut; + drop(app_state); + + let mut db_state = DB.borrow_data_mut().unwrap(); + db_state.base_url = base_url.to_string(); + drop(db_state); + + DB.save().unwrap(); + + Ok(()) +} diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs deleted file mode 100644 index bc62759..0000000 --- a/src-tauri/src/settings.rs +++ /dev/null @@ -1,35 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -use crate::DB; - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Settings { - pub autostart: bool, - pub max_download_threads: usize, - // ... other settings ... -} -impl Default for Settings { - fn default() -> Self { - Self { - autostart: false, - max_download_threads: 4 - } - } -} -fn deserialize_into(v: serde_json::Value, t: &mut T) -> Result<(), serde_json::Error> - where T: for<'a> Deserialize<'a> -{ - *t = serde_json::from_value(v)?; - Ok(()) -} - -#[tauri::command] -pub fn amend_settings(new_settings: Value) { - let db_lock = DB.borrow_data_mut().unwrap(); - let mut current_settings = db_lock.settings.clone(); - let e = deserialize_into(new_settings, &mut current_settings); - - println!("Amend status: {:?}", e); - println!("New settings: {:?}", current_settings); -} \ No newline at end of file