refactor: Improvements to src-tauri

Signed-off-by: quexeky <git@quexeky.dev>
This commit is contained in:
quexeky
2025-10-12 17:04:27 +11:00
parent 62a2561539
commit 5d22b883d5
16 changed files with 133 additions and 183 deletions

1
Cargo.lock generated
View File

@ -1380,6 +1380,7 @@ dependencies = [
"filetime",
"futures-core",
"futures-lite",
"games",
"gethostname",
"hex 0.4.3",
"http 1.3.1",

View File

@ -1,48 +1,8 @@
use database::{borrow_db_checked, borrow_db_mut_checked};
use database::borrow_db_checked;
use log::debug;
use tauri::AppHandle;
use tauri_plugin_autostart::ManagerExt;
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())?;
debug!("enabled autostart");
} else {
manager.disable().map_err(|e| e.to_string())?;
debug!("eisabled autostart");
}
// Store the state in DB
let mut db_handle = borrow_db_mut_checked();
db_handle.settings.autostart = enabled;
drop(db_handle);
Ok(())
}
pub fn get_autostart_enabled_logic(app: AppHandle) -> Result<bool, tauri_plugin_autostart::Error> {
// First check DB state
let db_handle = borrow_db_checked();
let db_state = db_handle.settings.autostart;
drop(db_handle);
// Get actual system state
let manager = app.autolaunch();
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()?;
} else {
manager.disable()?;
}
}
Ok(db_state)
}
// New function to sync state on startup
pub fn sync_autostart_on_startup(app: &AppHandle) -> Result<(), String> {
let db_handle = borrow_db_checked();

View File

@ -17,7 +17,7 @@ pub struct QueueUpdateEvent {
pub queue: Vec<QueueUpdateEventQueueData>,
}
#[derive(serde::Serialize, Clone)]
#[derive(Serialize, Clone)]
pub struct StatsUpdateEvent {
pub speed: usize,
pub time: usize,

View File

@ -1,8 +1,16 @@
#![feature(duration_millis_float)]
#![feature(nonpoison_mutex)]
#![feature(sync_nonpoison)]
use std::sync::{nonpoison::Mutex, LazyLock};
use crate::{download_manager_builder::DownloadManagerBuilder, download_manager_frontend::DownloadManager};
pub mod download_manager_builder;
pub mod download_manager_frontend;
pub mod downloadable;
pub mod util;
pub mod error;
pub mod frontend_updates;
pub mod frontend_updates;
pub static DOWNLOAD_MANAGER: LazyLock<Mutex<DownloadManager>> = LazyLock::new(|| todo!());

View File

@ -18,6 +18,12 @@ pub struct FetchGameStruct {
version: Option<GameVersion>,
}
impl FetchGameStruct {
pub fn new(game: Game, status: GameStatusWithTransient, version: Option<GameVersion>) -> Self {
Self { game, status, version }
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, Encode, Decode)]
#[serde(rename_all = "camelCase")]
pub struct Game {
@ -33,6 +39,11 @@ pub struct Game {
m_image_library_object_ids: Vec<String>,
m_image_carousel_object_ids: Vec<String>,
}
impl Game {
pub fn id(&self) -> &String {
&self.id
}
}
#[derive(serde::Serialize, Clone)]
pub struct GameUpdateEvent {
pub game_id: String,

View File

@ -100,7 +100,7 @@ impl ProcessManager<'_> {
}
}
fn get_log_dir(&self, game_id: String) -> PathBuf {
pub fn get_log_dir(&self, game_id: String) -> PathBuf {
self.log_output_dir.join(game_id)
}

View File

@ -6,8 +6,8 @@ use std::{
};
use bitcode::{Decode, DecodeOwned, Encode};
use database::{borrow_db_checked, Database};
use http::{header::{CONTENT_TYPE}, response::Builder as ResponseBuilder, Response};
use database::{Database, borrow_db_checked};
use http::{Response, header::CONTENT_TYPE, response::Builder as ResponseBuilder};
use crate::error::{CacheError, RemoteAccessError};
@ -15,7 +15,9 @@ use crate::error::{CacheError, RemoteAccessError};
macro_rules! offline {
($var:expr, $func1:expr, $func2:expr, $( $arg:expr ),* ) => {
async move { if $crate::borrow_db_checked().settings.force_offline || $crate::lock!($var).status == $crate::AppStatus::Offline {
async move {
if ::database::borrow_db_checked().settings.force_offline
|| ::utils::lock!($var).status == ::client::app_status::AppStatus::Offline {
$func2( $( $arg ), *).await
} else {
$func1( $( $arg ), *).await
@ -81,10 +83,7 @@ pub fn get_cached_object_db<D: DecodeOwned>(
pub fn clear_cached_object(key: &str) -> Result<(), RemoteAccessError> {
clear_cached_object_db(key, &borrow_db_checked())
}
pub fn clear_cached_object_db(
key: &str,
db: &Database,
) -> Result<(), RemoteAccessError> {
pub fn clear_cached_object_db(key: &str, db: &Database) -> Result<(), RemoteAccessError> {
delete_sync(&db.cache_dir, key).map_err(RemoteAccessError::Cache)?;
Ok(())
}
@ -103,9 +102,9 @@ impl ObjectCache {
}
}
impl TryFrom<Response<Vec<u8>>> for ObjectCache {
impl TryFrom<Response<Vec<u8>>> for ObjectCache {
type Error = CacheError;
fn try_from(value: Response<Vec<u8>>) -> Result<Self, Self::Error> {
Ok(ObjectCache {
content_type: value
@ -118,14 +117,15 @@ impl TryFrom<Response<Vec<u8>>> for ObjectCache {
body: value.body().clone(),
expiry: get_sys_time_in_secs() + 60 * 60 * 24,
})
}
}
impl TryFrom<ObjectCache> for Response<Vec<u8>> {
type Error = CacheError;
fn try_from(value: ObjectCache) -> Result<Self, Self::Error> {
let resp_builder = ResponseBuilder::new().header(CONTENT_TYPE, value.content_type);
resp_builder.body(value.body).map_err(CacheError::ConstructionError)
resp_builder
.body(value.body)
.map_err(CacheError::ConstructionError)
}
}
impl TryFrom<&ObjectCache> for Response<Vec<u8>> {
@ -133,6 +133,8 @@ impl TryFrom<&ObjectCache> for Response<Vec<u8>> {
fn try_from(value: &ObjectCache) -> Result<Self, Self::Error> {
let resp_builder = ResponseBuilder::new().header(CONTENT_TYPE, value.content_type.clone());
resp_builder.body(value.body.clone()).map_err(CacheError::ConstructionError)
resp_builder
.body(value.body.clone())
.map_err(CacheError::ConstructionError)
}
}

View File

@ -11,10 +11,14 @@ use serde::Deserialize;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct DropHealthcheck {
pub struct DropHealthcheck {
app_name: String,
}
impl DropHealthcheck {
pub fn app_name(&self) -> &String{
&self.app_name
}
}
static DROP_CERT_BUNDLE: LazyLock<Vec<Certificate>> = LazyLock::new(fetch_certificates);
pub static DROP_CLIENT_SYNC: LazyLock<reqwest::blocking::Client> = LazyLock::new(get_client_sync);
pub static DROP_CLIENT_ASYNC: LazyLock<reqwest::Client> = LazyLock::new(get_client_async);

View File

@ -80,11 +80,12 @@ bytes = "1.10.1"
# Workspaces
client = { path = "../client" }
client = { version = "0.1.0", path = "../client" }
database = { path = "../database" }
process = { path = "../process" }
remote = { path = "../remote" }
remote = { version = "0.1.0", path = "../remote" }
utils = { path = "../utils" }
games = { version = "0.1.0", path = "../games" }
[dependencies.dynfmt]
version = "0.1.5"

View File

@ -1,4 +1,10 @@
use crate::{lock, AppState};
use database::{borrow_db_checked, borrow_db_mut_checked};
use log::{debug, error};
use tauri::AppHandle;
use tauri_plugin_autostart::ManagerExt;
use utils::lock;
use crate::{AppState};
#[tauri::command]
pub fn fetch_state(
@ -31,10 +37,39 @@ pub fn cleanup_and_exit(app: &AppHandle, state: &tauri::State<'_, std::sync::Mut
#[tauri::command]
pub fn toggle_autostart(app: AppHandle, enabled: bool) -> Result<(), String> {
toggle_autostart_logic(app, enabled)
let manager = app.autolaunch();
if enabled {
manager.enable().map_err(|e| e.to_string())?;
debug!("enabled autostart");
} else {
manager.disable().map_err(|e| e.to_string())?;
debug!("eisabled autostart");
}
// Store the state in DB
let mut db_handle = borrow_db_mut_checked();
db_handle.settings.autostart = enabled;
Ok(())
}
#[tauri::command]
pub fn get_autostart_enabled(app: AppHandle) -> Result<bool, tauri_plugin_autostart::Error> {
get_autostart_enabled_logic(app)
let db_handle = borrow_db_checked();
let db_state = db_handle.settings.autostart;
drop(db_handle);
// Get actual system state
let manager = app.autolaunch();
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()?;
} else {
manager.disable()?;
}
}
Ok(db_state)
}

View File

@ -1,27 +0,0 @@
use std::{fmt::Display, io, sync::mpsc::SendError};
use serde_with::SerializeDisplay;
#[derive(SerializeDisplay)]
pub enum DownloadManagerError<T> {
IOError(io::Error),
SignalError(SendError<T>),
}
impl<T> Display for DownloadManagerError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DownloadManagerError::IOError(error) => write!(f, "{error}"),
DownloadManagerError::SignalError(send_error) => write!(f, "{send_error}"),
}
}
}
impl<T> From<SendError<T>> for DownloadManagerError<T> {
fn from(value: SendError<T>) -> Self {
DownloadManagerError::SignalError(value)
}
}
impl<T> From<io::Error> for DownloadManagerError<T> {
fn from(value: io::Error) -> Self {
DownloadManagerError::IOError(value)
}
}

View File

@ -1,7 +0,0 @@
pub mod application_download_error;
pub mod download_manager_error;
pub mod drop_server_error;
pub mod library_error;
pub mod process_error;
pub mod remote_access_error;
pub mod cache_error;

View File

@ -1,28 +1,15 @@
use std::sync::Mutex;
use database::{borrow_db_checked, borrow_db_mut_checked, GameDownloadStatus, GameVersion};
use games::{downloads::error::LibraryError, library::{get_current_meta, uninstall_game_logic, FetchGameStruct, Game}, state::{GameStatusManager, GameStatusWithTransient}};
use log::warn;
use process::PROCESS_MANAGER;
use remote::{auth::generate_authorization_header, cache::{cache_object, cache_object_db, get_cached_object, get_cached_object_db}, error::{DropServerError, RemoteAccessError}, offline, requests::generate_url, utils::DROP_CLIENT_ASYNC};
use tauri::AppHandle;
use utils::lock;
use crate::{
AppState,
database::{
db::borrow_db_checked,
models::data::GameVersion,
},
error::{library_error::LibraryError, remote_access_error::RemoteAccessError},
games::library::{
fetch_game_logic_offline, fetch_library_logic_offline, get_current_meta,
uninstall_game_logic,
},
offline,
};
use crate::AppState;
use super::{
library::{
FetchGameStruct, Game, fetch_game_logic, fetch_game_version_options_logic,
fetch_library_logic,
},
state::{GameStatusManager, GameStatusWithTransient},
};
#[tauri::command]
pub async fn fetch_library(
@ -72,18 +59,18 @@ pub async fn fetch_library_logic(
let mut db_handle = borrow_db_mut_checked();
for game in &games {
handle.games.insert(game.id.clone(), game.clone());
if !db_handle.applications.game_statuses.contains_key(&game.id) {
handle.games.insert(game.id().clone(), game.clone());
if !db_handle.applications.game_statuses.contains_key(game.id()) {
db_handle
.applications
.game_statuses
.insert(game.id.clone(), GameDownloadStatus::Remote {});
.insert(game.id().clone(), GameDownloadStatus::Remote {});
}
}
// Add games that are installed but no longer in library
for meta in db_handle.applications.installed_game_version.values() {
if games.iter().any(|e| e.id == meta.id) {
if games.iter().any(|e| *e.id() == meta.id) {
continue;
}
// We should always have a cache of the object
@ -120,7 +107,7 @@ pub async fn fetch_library_logic_offline(
&db_handle
.applications
.game_statuses
.get(&game.id)
.get(game.id())
.unwrap_or(&GameDownloadStatus::Remote {}),
GameDownloadStatus::Installed { .. } | GameDownloadStatus::SetupRequired { .. }
)
@ -152,11 +139,7 @@ pub async fn fetch_game_logic(
if let Some(game) = game {
let status = GameStatusManager::fetch_state(&id, &db_lock);
let data = FetchGameStruct {
game: game.clone(),
status,
version,
};
let data = FetchGameStruct::new(game.clone(), status, version);
cache_object_db(&id, game, &db_lock)?;
@ -205,11 +188,7 @@ pub async fn fetch_game_logic(
drop(db_handle);
let data = FetchGameStruct {
game: game.clone(),
status,
version,
};
let data = FetchGameStruct::new(game.clone(), status, version);
cache_object(&id, &game)?;
@ -238,10 +217,10 @@ pub async fn fetch_game_version_options_logic(
let data: Vec<GameVersion> = response.json().await?;
let state_lock = lock!(state);
let process_manager_lock = lock!(state_lock.process_manager);
let process_manager_lock = PROCESS_MANAGER.lock();
let data: Vec<GameVersion> = data
.into_iter()
.filter(|v| process_manager_lock.valid_platform(&v.platform, &state_lock))
.filter(|v| process_manager_lock.valid_platform(&v.platform))
.collect();
drop(process_manager_lock);
drop(state_lock);
@ -271,11 +250,7 @@ pub async fn fetch_game_logic_offline(
drop(db_handle);
Ok(FetchGameStruct {
game,
status,
version,
})
Ok(FetchGameStruct::new(game, status, version))
}
#[tauri::command]

View File

@ -3,12 +3,21 @@
#![feature(duration_constructors)]
#![feature(duration_millis_float)]
#![feature(iterator_try_collect)]
#![feature(nonpoison_mutex)]
#![deny(clippy::all)]
use std::collections::HashMap;
use ::client::{app_status::AppStatus, compat::CompatInfo, user::User};
use database::{borrow_db_checked, GameDownloadStatus};
use ::games::library::Game;
use ::remote::auth;
use serde::Serialize;
use tauri::AppHandle;
mod games;
mod client;
mod error;
mod process;
mod remote;
@ -19,13 +28,6 @@ pub struct AppState<'a> {
status: AppStatus,
user: Option<User>,
games: HashMap<String, Game>,
#[serde(skip_serializing)]
download_manager: Arc<DownloadManager>,
#[serde(skip_serializing)]
process_manager: Arc<Mutex<ProcessManager<'a>>>,
#[serde(skip_serializing)]
compat_info: Option<CompatInfo>,
}
async fn setup(handle: AppHandle) -> AppState<'static> {

View File

@ -1,6 +1,11 @@
use std::sync::Mutex;
use crate::{AppState, error::process_error::ProcessError, lock};
use process::{error::ProcessError, PROCESS_MANAGER};
use tauri::AppHandle;
use tauri_plugin_opener::OpenerExt;
use utils::lock;
use crate::AppState;
#[tauri::command]
pub fn launch_game(
@ -8,8 +13,7 @@ pub fn launch_game(
state: tauri::State<'_, Mutex<AppState>>,
) -> Result<(), ProcessError> {
let state_lock = lock!(state);
let mut process_manager_lock = lock!(state_lock.process_manager);
let process_manager_lock = PROCESS_MANAGER.lock();
//let meta = DownloadableMetadata {
// id,
// version: Some(version),
@ -30,11 +34,9 @@ pub fn launch_game(
#[tauri::command]
pub fn kill_game(
game_id: String,
state: tauri::State<'_, Mutex<AppState>>,
) -> Result<(), ProcessError> {
let state_lock = lock!(state);
let mut process_manager_lock = lock!(state_lock.process_manager);
process_manager_lock
PROCESS_MANAGER
.lock()
.kill_game(game_id)
.map_err(ProcessError::IOError)
}
@ -42,17 +44,13 @@ pub fn kill_game(
#[tauri::command]
pub fn open_process_logs(
game_id: String,
state: tauri::State<'_, Mutex<AppState>>,
app_handle: AppHandle
) -> Result<(), ProcessError> {
let state_lock = lock!(state);
let mut process_manager_lock = lock!(state_lock.process_manager);
let process_manager_lock = PROCESS_MANAGER.lock();
let dir = process_manager_lock.get_log_dir(game_id);
state
.handle()
app_handle
.opener()
.open_path(dir.display().to_string(), None::<&str>)
.map_err(ProcessError::OpenerError)?;
process_manager_lock.open_process_logs(game_id)
.map_err(ProcessError::OpenerError)
}

View File

@ -1,30 +1,17 @@
use std::sync::Mutex;
use std::{sync::Mutex, time::Duration};
use client::app_status::AppStatus;
use database::{borrow_db_checked, borrow_db_mut_checked};
use futures_lite::StreamExt;
use log::{debug, warn};
use remote::{auth::{auth_initiate_logic, generate_authorization_header}, cache::{cache_object, get_cached_object}, error::RemoteAccessError, requests::generate_url, setup, utils::{DropHealthcheck, DROP_CLIENT_ASYNC, DROP_CLIENT_WS_CLIENT}};
use reqwest_websocket::{Message, RequestBuilderExt};
use serde::Deserialize;
use tauri::{AppHandle, Emitter, Manager};
use url::Url;
use utils::{app_emit, lock, webbrowser_open::webbrowser_open};
use crate::{
AppState, AppStatus, app_emit,
database::db::{borrow_db_checked, borrow_db_mut_checked},
error::remote_access_error::RemoteAccessError,
lock,
remote::{
auth::generate_authorization_header,
requests::generate_url,
utils::{DROP_CLIENT_SYNC, DROP_CLIENT_WS_CLIENT},
},
utils::webbrowser_open::webbrowser_open,
};
use super::{
auth::{auth_initiate_logic, recieve_handshake, setup},
cache::{cache_object, get_cached_object},
utils::use_remote_logic,
};
use crate::{recieve_handshake, AppState};
#[tauri::command]
pub async fn use_remote(
@ -45,7 +32,7 @@ pub async fn use_remote(
let result: DropHealthcheck = response.json().await?;
if result.app_name != "Drop" {
if result.app_name() != "Drop" {
warn!("user entered drop endpoint that connected, but wasn't identified as Drop");
return Err(RemoteAccessError::InvalidEndpoint);
}
@ -77,7 +64,7 @@ pub fn gen_drop_url(path: String) -> Result<String, RemoteAccessError> {
pub fn fetch_drop_object(path: String) -> Result<Vec<u8>, RemoteAccessError> {
let _drop_url = gen_drop_url(path.clone())?;
let req = generate_url(&[&path], &[])?;
let req = DROP_CLIENT_SYNC
let req = remote::utils::DROP_CLIENT_SYNC
.get(req)
.header("Authorization", generate_authorization_header())
.send();