From 858858ab488390094120efb894d5f1b3c654e097 Mon Sep 17 00:00:00 2001 From: Wind-Explorer Date: Tue, 10 Mar 2026 15:46:49 +0800 Subject: [PATCH] Rust service refactor: app data, session windows & client config pt 2 --- src-tauri/src/commands/app_state.rs | 3 +- src-tauri/src/commands/config.rs | 8 +- src-tauri/src/commands/dolls.rs | 4 +- src-tauri/src/commands/friends.rs | 2 +- src-tauri/src/commands/mod.rs | 5 +- src-tauri/src/init/lifecycle.rs | 8 +- src-tauri/src/lib.rs | 4 +- src-tauri/src/services/app_data/display.rs | 70 +++++ src-tauri/src/services/app_data/mod.rs | 5 + src-tauri/src/services/app_data/refresh.rs | 174 +++++++++++ .../mod.rs | 8 +- .../store.rs | 0 .../window.rs | 20 +- src-tauri/src/services/mod.rs | 19 +- src-tauri/src/services/session_windows.rs | 13 + src-tauri/src/services/ws/client.rs | 2 +- src-tauri/src/services/ws/doll.rs | 2 +- src-tauri/src/services/ws/friend.rs | 2 +- src-tauri/src/services/ws/refresh.rs | 2 +- src-tauri/src/state/mod.rs | 10 +- src-tauri/src/state/ui.rs | 290 ------------------ src/lib/bindings.ts | 4 +- .../+page.svelte | 0 23 files changed, 308 insertions(+), 347 deletions(-) create mode 100644 src-tauri/src/services/app_data/display.rs create mode 100644 src-tauri/src/services/app_data/mod.rs create mode 100644 src-tauri/src/services/app_data/refresh.rs rename src-tauri/src/services/{client_config_manager => client_config}/mod.rs (73%) rename src-tauri/src/services/{client_config_manager => client_config}/store.rs (100%) rename src-tauri/src/services/{client_config_manager => client_config}/window.rs (60%) create mode 100644 src-tauri/src/services/session_windows.rs delete mode 100644 src-tauri/src/state/ui.rs rename src/routes/{client-config-manager => client-config}/+page.svelte (100%) diff --git a/src-tauri/src/commands/app_state.rs b/src-tauri/src/commands/app_state.rs index cb33c73..cb26408 100644 --- a/src-tauri/src/commands/app_state.rs +++ b/src-tauri/src/commands/app_state.rs @@ -2,9 +2,10 @@ use crate::{ lock_r, models::app_data::UserData, services::{ + app_data::{init_app_data_scoped, AppDataRefreshScope}, friends, presence_modules::models::ModuleMetadata, sprite, }, - state::{init_app_data_scoped, AppDataRefreshScope, FDOLL}, + state::FDOLL, }; #[tauri::command] diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index ffbc56f..ea549e8 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -1,7 +1,7 @@ use crate::{ lock_w, - services::client_config_manager::{ - load_app_config, open_config_manager_window, save_app_config, AppConfig, + services::client_config::{ + load_app_config, open_config_window, save_app_config, AppConfig, }, state::FDOLL, }; @@ -29,6 +29,6 @@ pub fn save_client_config(config: AppConfig) -> Result<(), String> { #[tauri::command] #[specta::specta] -pub async fn open_client_config_manager() -> Result<(), String> { - open_config_manager_window().map_err(|e| e.to_string()) +pub async fn open_client_config() -> Result<(), String> { + open_config_window().map_err(|e| e.to_string()) } diff --git a/src-tauri/src/commands/dolls.rs b/src-tauri/src/commands/dolls.rs index 6da5e68..0b7fc04 100644 --- a/src-tauri/src/commands/dolls.rs +++ b/src-tauri/src/commands/dolls.rs @@ -1,11 +1,11 @@ use crate::{ + commands::{is_active_doll, refresh_app_data, refresh_app_data_conditionally}, models::dolls::{CreateDollDto, DollDto, UpdateDollDto}, remotes::{ dolls::DollsRemote, user::UserRemote, }, - state::AppDataRefreshScope, - commands::{refresh_app_data, refresh_app_data_conditionally, is_active_doll}, + services::app_data::AppDataRefreshScope, }; #[tauri::command] diff --git a/src-tauri/src/commands/friends.rs b/src-tauri/src/commands/friends.rs index 5653a9d..1da39b1 100644 --- a/src-tauri/src/commands/friends.rs +++ b/src-tauri/src/commands/friends.rs @@ -2,8 +2,8 @@ use crate::remotes::friends::FriendRemote; use crate::models::friends::{ FriendRequestResponseDto, FriendshipResponseDto, SendFriendRequestDto, UserBasicDto, }; -use crate::state::AppDataRefreshScope; use crate::commands::refresh_app_data; +use crate::services::app_data::AppDataRefreshScope; #[tauri::command] #[specta::specta] diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 32bad26..8c52e1d 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -9,7 +9,10 @@ pub mod petpet; pub mod sprite; use crate::lock_r; -use crate::state::{init_app_data_scoped, AppDataRefreshScope, FDOLL}; +use crate::{ + services::app_data::{init_app_data_scoped, AppDataRefreshScope}, + state::FDOLL, +}; use tauri::async_runtime; /// Helper to execute a mutation operation and refresh app data scopes in the background. diff --git a/src-tauri/src/init/lifecycle.rs b/src-tauri/src/init/lifecycle.rs index 1e11724..4200708 100644 --- a/src-tauri/src/init/lifecycle.rs +++ b/src-tauri/src/init/lifecycle.rs @@ -7,16 +7,14 @@ use crate::{ models::health::HealthError, remotes::health::HealthRemote, services::{ - close_all_windows, + app_data::{clear_app_data, init_app_data_scoped, AppDataRefreshScope}, health_manager::open_health_manager_window, health_monitor::{start_health_monitor, stop_health_monitor}, scene::open_scene_window, + session_windows::close_all_windows, ws::client::{clear_ws_client, establish_websocket_connection}, }, - state::{ - auth::{start_background_token_refresh, stop_background_token_refresh}, - clear_app_data, init_app_data_scoped, AppDataRefreshScope, - }, + state::auth::{start_background_token_refresh, stop_background_token_refresh}, system_tray::update_system_tray, }; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ab0a9d2..c17dbc8 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -11,7 +11,7 @@ use commands::app_state::{ refresh_app_data, }; use commands::auth::{change_password, login, logout_and_restart, register, reset_password}; -use commands::config::{get_client_config, open_client_config_manager, save_client_config}; +use commands::config::{get_client_config, open_client_config, save_client_config}; use commands::dolls::{ create_doll, delete_doll, get_doll, get_dolls, remove_active_doll, set_active_doll, update_doll, }; @@ -94,7 +94,7 @@ pub fn run() { retry_connection, get_client_config, save_client_config, - open_client_config_manager, + open_client_config, open_doll_editor_window, get_scene_interactive, set_scene_interactive, diff --git a/src-tauri/src/services/app_data/display.rs b/src-tauri/src/services/app_data/display.rs new file mode 100644 index 0000000..acf4882 --- /dev/null +++ b/src-tauri/src/services/app_data/display.rs @@ -0,0 +1,70 @@ +use crate::{get_app_handle, lock_w, state::FDOLL}; +use tracing::{info, warn}; + +pub fn update_display_dimensions_for_scene_state() { + let app_handle = get_app_handle(); + + let mut guard = lock_w!(FDOLL); + + let primary_monitor = { + let mut retry_count = 0; + let max_retries = 3; + loop { + match app_handle.primary_monitor() { + Ok(Some(monitor)) => { + info!("Primary monitor acquired for state initialization"); + break Some(monitor); + } + Ok(None) => { + retry_count += 1; + if retry_count >= max_retries { + warn!( + "No primary monitor found after {} retries during state init", + max_retries + ); + break None; + } + warn!( + "Primary monitor not available during state init, retrying... ({}/{})", + retry_count, max_retries + ); + std::thread::sleep(std::time::Duration::from_millis(100)); + } + Err(error) => { + retry_count += 1; + if retry_count >= max_retries { + warn!("Failed to get primary monitor during state init: {}", error); + break None; + } + warn!( + "Error getting primary monitor during state init, retrying... ({}/{}): {}", + retry_count, max_retries, error + ); + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } + } + }; + + if let Some(monitor) = primary_monitor { + let monitor_dimensions = monitor.size(); + let monitor_scale_factor = monitor.scale_factor(); + let logical_monitor_dimensions: tauri::LogicalSize = + monitor_dimensions.to_logical(monitor_scale_factor); + + guard.user_data.scene.display.screen_width = logical_monitor_dimensions.width; + guard.user_data.scene.display.screen_height = logical_monitor_dimensions.height; + guard.user_data.scene.display.monitor_scale_factor = monitor_scale_factor; + guard.user_data.scene.grid_size = 600; + + info!( + "Initialized global AppData with screen dimensions: {}x{}, scale: {}, grid: {}", + logical_monitor_dimensions.width, + logical_monitor_dimensions.height, + monitor_scale_factor, + guard.user_data.scene.grid_size + ); + } else { + warn!("Could not initialize screen dimensions in global state - no monitor found"); + } +} diff --git a/src-tauri/src/services/app_data/mod.rs b/src-tauri/src/services/app_data/mod.rs new file mode 100644 index 0000000..9ffd5e4 --- /dev/null +++ b/src-tauri/src/services/app_data/mod.rs @@ -0,0 +1,5 @@ +mod display; +mod refresh; + +pub use display::update_display_dimensions_for_scene_state; +pub use refresh::{clear_app_data, init_app_data_scoped, AppDataRefreshScope}; diff --git a/src-tauri/src/services/app_data/refresh.rs b/src-tauri/src/services/app_data/refresh.rs new file mode 100644 index 0000000..0252e66 --- /dev/null +++ b/src-tauri/src/services/app_data/refresh.rs @@ -0,0 +1,174 @@ +use std::{collections::HashSet, sync::LazyLock}; + +use tauri_plugin_dialog::MessageDialogBuilder; +use tauri_plugin_dialog::{DialogExt, MessageDialogKind}; +use tauri_specta::Event as _; +use tokio::sync::Mutex; +use tracing::warn; + +use crate::{ + get_app_handle, lock_r, lock_w, + remotes::{dolls::DollsRemote, friends::FriendRemote, user::UserRemote}, + services::{ + app_events::{ActiveDollSpriteChanged, AppDataRefreshed}, + friends, sprite, + }, + state::FDOLL, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AppDataRefreshScope { + All, + User, + Friends, + Dolls, +} + +static REFRESH_IN_FLIGHT: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); +static REFRESH_PENDING: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); + +pub async fn init_app_data_scoped(scope: AppDataRefreshScope) { + loop { + { + let mut in_flight = REFRESH_IN_FLIGHT.lock().await; + if in_flight.contains(&scope) { + let mut pending = REFRESH_PENDING.lock().await; + pending.insert(scope); + return; + } + in_flight.insert(scope); + } + + let result: Result<(), ()> = async { + let user_remote = UserRemote::new(); + let friend_remote = FriendRemote::new(); + let dolls_remote = DollsRemote::new(); + + if matches!(scope, AppDataRefreshScope::All | AppDataRefreshScope::User) { + match user_remote.get_user(None).await { + Ok(user) => { + let mut guard = lock_w!(FDOLL); + guard.user_data.user = Some(user); + } + Err(error) => { + warn!("Failed to fetch user profile: {}", error); + show_refresh_error_dialog( + "Network Error", + "Failed to fetch user profile. You may be offline.", + ); + return Err(()); + } + } + } + + if matches!(scope, AppDataRefreshScope::All | AppDataRefreshScope::Friends) { + match friend_remote.get_friends().await { + Ok(friends_list) => { + let mut guard = lock_w!(FDOLL); + guard.user_data.friends = Some(friends_list); + drop(guard); + friends::sync_from_app_data(); + } + Err(error) => { + warn!("Failed to fetch friends list: {}", error); + show_refresh_error_dialog( + "Network Error", + "Failed to fetch friends list. You may be offline.", + ); + return Err(()); + } + } + } + + if matches!(scope, AppDataRefreshScope::All | AppDataRefreshScope::Dolls) { + match dolls_remote.get_dolls().await { + Ok(dolls) => { + let mut guard = lock_w!(FDOLL); + guard.user_data.dolls = Some(dolls); + } + Err(error) => { + warn!("Failed to fetch dolls list: {}", error); + show_refresh_error_dialog( + "Network Error", + "Failed to fetch dolls list. You may be offline.", + ); + return Err(()); + } + } + } + + emit_refresh_events(scope); + + Ok(()) + } + .await; + + { + let mut in_flight = REFRESH_IN_FLIGHT.lock().await; + in_flight.remove(&scope); + } + + let rerun = { + let mut pending = REFRESH_PENDING.lock().await; + pending.remove(&scope) + }; + + if rerun { + continue; + } + + if result.is_err() { + return; + } + + break; + } +} + +pub fn clear_app_data() { + let mut guard = lock_w!(FDOLL); + guard.user_data.dolls = None; + guard.user_data.user = None; + guard.user_data.friends = None; + drop(guard); + friends::clear(); +} + +fn emit_refresh_events(scope: AppDataRefreshScope) { + let guard = lock_r!(FDOLL); + let app_data = guard.user_data.clone(); + drop(guard); + + if let Err(error) = AppDataRefreshed(app_data).emit(get_app_handle()) { + warn!("Failed to emit app-data-refreshed event: {}", error); + show_refresh_error_dialog( + "Sync Error", + "Could not broadcast refreshed data to the UI. Some data may be stale.", + ); + } + + if matches!( + scope, + AppDataRefreshScope::All | AppDataRefreshScope::User | AppDataRefreshScope::Dolls + ) { + match sprite::get_active_doll_sprite_base64() { + Ok(sprite_b64) => { + if let Err(error) = ActiveDollSpriteChanged(sprite_b64).emit(get_app_handle()) { + warn!("Failed to emit active-doll-sprite-changed event: {}", error); + } + } + Err(error) => { + warn!("Failed to generate active doll sprite: {}", error); + } + } + } +} + +fn show_refresh_error_dialog(title: &str, message: &str) { + let handle = get_app_handle(); + MessageDialogBuilder::new(handle.dialog().clone(), title, message) + .kind(MessageDialogKind::Error) + .show(|_| {}); +} diff --git a/src-tauri/src/services/client_config_manager/mod.rs b/src-tauri/src/services/client_config/mod.rs similarity index 73% rename from src-tauri/src/services/client_config_manager/mod.rs rename to src-tauri/src/services/client_config/mod.rs index d61f0a0..f4cc3a5 100644 --- a/src-tauri/src/services/client_config_manager/mod.rs +++ b/src-tauri/src/services/client_config/mod.rs @@ -6,7 +6,7 @@ use specta::Type; use thiserror::Error; pub use store::{load_app_config, save_app_config}; -pub use window::open_config_manager_window; +pub use window::open_config_window; #[derive(Default, Serialize, Deserialize, Clone, Debug, Type)] pub struct AppConfig { @@ -23,10 +23,10 @@ pub enum ClientConfigError { Parse(#[from] serde_json::Error), #[error("failed to run on main thread: {0}")] Dispatch(#[from] tauri::Error), - #[error("failed to build client config manager window: {0}")] + #[error("failed to build client config window: {0}")] Window(tauri::Error), - #[error("failed to show client config manager window: {0}")] + #[error("failed to show client config window: {0}")] ShowWindow(tauri::Error), } -pub static CLIENT_CONFIG_MANAGER_WINDOW_LABEL: &str = "client_config_manager"; +pub static CLIENT_CONFIG_WINDOW_LABEL: &str = "client_config"; diff --git a/src-tauri/src/services/client_config_manager/store.rs b/src-tauri/src/services/client_config/store.rs similarity index 100% rename from src-tauri/src/services/client_config_manager/store.rs rename to src-tauri/src/services/client_config/store.rs diff --git a/src-tauri/src/services/client_config_manager/window.rs b/src-tauri/src/services/client_config/window.rs similarity index 60% rename from src-tauri/src/services/client_config_manager/window.rs rename to src-tauri/src/services/client_config/window.rs index 116801e..6a65b73 100644 --- a/src-tauri/src/services/client_config_manager/window.rs +++ b/src-tauri/src/services/client_config/window.rs @@ -3,28 +3,28 @@ use tracing::error; use crate::get_app_handle; -use super::{ClientConfigError, CLIENT_CONFIG_MANAGER_WINDOW_LABEL}; +use super::{ClientConfigError, CLIENT_CONFIG_WINDOW_LABEL}; #[tauri::command] -pub fn open_config_manager_window() -> Result<(), ClientConfigError> { +pub fn open_config_window() -> Result<(), ClientConfigError> { let app_handle = get_app_handle(); - let existing_webview_window = app_handle.get_window(CLIENT_CONFIG_MANAGER_WINDOW_LABEL); + let existing_webview_window = app_handle.get_window(CLIENT_CONFIG_WINDOW_LABEL); if let Some(window) = existing_webview_window { if let Err(e) = window.show() { - error!("Failed to show client config manager window: {e}"); + error!("Failed to show client config window: {e}"); return Err(ClientConfigError::ShowWindow(e)); } if let Err(e) = window.set_focus() { - error!("Failed to focus client config manager window: {e}"); + error!("Failed to focus client config window: {e}"); } return Ok(()); } match tauri::WebviewWindowBuilder::new( app_handle, - CLIENT_CONFIG_MANAGER_WINDOW_LABEL, - tauri::WebviewUrl::App("/client-config-manager".into()), + CLIENT_CONFIG_WINDOW_LABEL, + tauri::WebviewUrl::App("/client-config".into()), ) .title("Advanced Configuration") .inner_size(300.0, 420.0) @@ -35,16 +35,16 @@ pub fn open_config_manager_window() -> Result<(), ClientConfigError> { { Ok(window) => { if let Err(e) = window.show() { - error!("Failed to show client config manager window: {}", e); + error!("Failed to show client config window: {}", e); return Err(ClientConfigError::ShowWindow(e)); } if let Err(e) = window.set_focus() { - error!("Failed to focus client config manager window: {e}"); + error!("Failed to focus client config window: {e}"); } Ok(()) } Err(e) => { - error!("Failed to build client config manager window: {}", e); + error!("Failed to build client config window: {}", e); Err(ClientConfigError::Window(e)) } } diff --git a/src-tauri/src/services/mod.rs b/src-tauri/src/services/mod.rs index 85d9fa7..ce95651 100644 --- a/src-tauri/src/services/mod.rs +++ b/src-tauri/src/services/mod.rs @@ -1,12 +1,8 @@ -use tauri::Manager; - -use crate::get_app_handle; -use tracing::warn; - +pub mod app_data; pub mod app_events; pub mod app_menu; pub mod auth; -pub mod client_config_manager; +pub mod client_config; pub mod cursor; pub mod doll_editor; pub mod friends; @@ -16,17 +12,8 @@ pub mod interaction; pub mod petpet; pub mod presence_modules; pub mod scene; +pub mod session_windows; pub mod sprite; pub mod sprite_recolor; pub mod welcome; pub mod ws; - -pub fn close_all_windows() { - let app_handle = get_app_handle(); - let webview_windows = app_handle.webview_windows(); - for window in webview_windows { - if let Err(err) = window.1.close() { - warn!("Failed to close window '{}': {}", window.0, err); - } - } -} diff --git a/src-tauri/src/services/session_windows.rs b/src-tauri/src/services/session_windows.rs new file mode 100644 index 0000000..a0c2bb5 --- /dev/null +++ b/src-tauri/src/services/session_windows.rs @@ -0,0 +1,13 @@ +use tauri::Manager; +use tracing::warn; + +use crate::get_app_handle; + +pub fn close_all_windows() { + let app_handle = get_app_handle(); + for (label, window) in app_handle.webview_windows() { + if let Err(error) = window.close() { + warn!("Failed to close window '{}': {}", label, error); + } + } +} diff --git a/src-tauri/src/services/ws/client.rs b/src-tauri/src/services/ws/client.rs index fa18e3e..6a8c911 100644 --- a/src-tauri/src/services/ws/client.rs +++ b/src-tauri/src/services/ws/client.rs @@ -7,7 +7,7 @@ use tracing::{error, info}; use crate::{ lock_r, lock_w, - services::{auth::get_access_token, client_config_manager::AppConfig}, + services::{auth::get_access_token, client_config::AppConfig}, state::FDOLL, }; diff --git a/src-tauri/src/services/ws/doll.rs b/src-tauri/src/services/ws/doll.rs index 5f9d30c..6f83259 100644 --- a/src-tauri/src/services/ws/doll.rs +++ b/src-tauri/src/services/ws/doll.rs @@ -5,7 +5,7 @@ use super::{refresh, utils}; /// Handler for doll.created event pub fn on_doll_created(payload: Payload, _socket: RawClient) { if utils::extract_text_value(payload, "doll.created").is_ok() { - refresh::refresh_app_data(crate::state::AppDataRefreshScope::Dolls); + refresh::refresh_app_data(crate::services::app_data::AppDataRefreshScope::Dolls); } } diff --git a/src-tauri/src/services/ws/friend.rs b/src-tauri/src/services/ws/friend.rs index 7a5b371..ce7b812 100644 --- a/src-tauri/src/services/ws/friend.rs +++ b/src-tauri/src/services/ws/friend.rs @@ -6,6 +6,7 @@ use crate::models::event_payloads::{ FriendRequestDeniedPayload, FriendRequestReceivedPayload, FriendUserStatusPayload, UnfriendedPayload, }; +use crate::services::app_data::AppDataRefreshScope; use crate::services::app_events::{ FriendActiveDollChanged, FriendDisconnected, FriendRequestAccepted, FriendRequestDenied, FriendRequestReceived, FriendUserStatusChanged, Unfriended, @@ -14,7 +15,6 @@ use crate::services::{ cursor::{normalized_to_absolute, CursorPositions}, friends, }; -use crate::state::AppDataRefreshScope; use super::{emitter, refresh, types::IncomingFriendCursorPayload, utils}; diff --git a/src-tauri/src/services/ws/refresh.rs b/src-tauri/src/services/ws/refresh.rs index c2840e3..27a6a93 100644 --- a/src-tauri/src/services/ws/refresh.rs +++ b/src-tauri/src/services/ws/refresh.rs @@ -1,6 +1,6 @@ use tauri::async_runtime; -use crate::state::{init_app_data_scoped, AppDataRefreshScope}; +use crate::services::app_data::{init_app_data_scoped, AppDataRefreshScope}; /// Refresh app data with the given scope pub fn refresh_app_data(scope: AppDataRefreshScope) { diff --git a/src-tauri/src/state/mod.rs b/src-tauri/src/state/mod.rs index 142f831..24bc2be 100644 --- a/src-tauri/src/state/mod.rs +++ b/src-tauri/src/state/mod.rs @@ -1,6 +1,8 @@ // in app-core/src/state.rs use crate::{ - lock_w, models::app_data::UserData, services::presence_modules::models::ModuleMetadata, + lock_w, + models::app_data::UserData, + services::{app_data::update_display_dimensions_for_scene_state, presence_modules::models::ModuleMetadata}, }; use std::sync::{Arc, LazyLock, RwLock}; use tauri::tray::TrayIcon; @@ -8,11 +10,9 @@ use tracing::info; pub mod auth; mod network; -mod ui; pub use auth::*; pub use network::*; -pub use ui::*; #[derive(Default)] pub struct Modules { @@ -22,7 +22,7 @@ pub struct Modules { #[derive(Default)] pub struct AppState { - pub app_config: crate::services::client_config_manager::AppConfig, + pub app_config: crate::services::client_config::AppConfig, pub network: NetworkState, pub auth: AuthState, pub user_data: UserData, @@ -41,7 +41,7 @@ pub fn init_app_state() { dotenvy::dotenv().ok(); { let mut guard = lock_w!(FDOLL); - guard.app_config = crate::services::client_config_manager::load_app_config(); + guard.app_config = crate::services::client_config::load_app_config(); guard.network = init_network_state(); guard.auth = init_auth_state(); guard.user_data = UserData::default(); diff --git a/src-tauri/src/state/ui.rs b/src-tauri/src/state/ui.rs deleted file mode 100644 index a19b150..0000000 --- a/src-tauri/src/state/ui.rs +++ /dev/null @@ -1,290 +0,0 @@ -use crate::{ - get_app_handle, lock_r, lock_w, - remotes::{dolls::DollsRemote, friends::FriendRemote, user::UserRemote}, - services::{ - app_events::{ActiveDollSpriteChanged, AppDataRefreshed}, - friends, sprite, - }, - state::FDOLL, -}; -use std::{collections::HashSet, sync::LazyLock}; -use tauri_specta::Event as _; -use tokio::sync::Mutex; -use tracing::{info, warn}; - -pub fn update_display_dimensions_for_scene_state() { - let app_handle = get_app_handle(); - - let mut guard = lock_w!(FDOLL); - - // Get primary monitor with retries - let primary_monitor = { - let mut retry_count = 0; - let max_retries = 3; - loop { - match app_handle.primary_monitor() { - Ok(Some(monitor)) => { - info!("Primary monitor acquired for state initialization"); - break Some(monitor); - } - Ok(None) => { - retry_count += 1; - if retry_count >= max_retries { - warn!( - "No primary monitor found after {} retries during state init", - max_retries - ); - break None; - } - warn!( - "Primary monitor not available during state init, retrying... ({}/{})", - retry_count, max_retries - ); - std::thread::sleep(std::time::Duration::from_millis(100)); - } - Err(e) => { - retry_count += 1; - if retry_count >= max_retries { - warn!("Failed to get primary monitor during state init: {}", e); - break None; - } - warn!( - "Error getting primary monitor during state init, retrying... ({}/{}): {}", - retry_count, max_retries, e - ); - std::thread::sleep(std::time::Duration::from_millis(100)); - } - } - } - }; - - if let Some(monitor) = primary_monitor { - let monitor_dimensions = monitor.size(); - let monitor_scale_factor = monitor.scale_factor(); - let logical_monitor_dimensions: tauri::LogicalSize = - monitor_dimensions.to_logical(monitor_scale_factor); - - guard.user_data.scene.display.screen_width = logical_monitor_dimensions.width; - guard.user_data.scene.display.screen_height = logical_monitor_dimensions.height; - guard.user_data.scene.display.monitor_scale_factor = monitor_scale_factor; - guard.user_data.scene.grid_size = 600; // Hardcoded grid size - - info!( - "Initialized global AppData with screen dimensions: {}x{}, scale: {}, grid: {}", - logical_monitor_dimensions.width, - logical_monitor_dimensions.height, - monitor_scale_factor, - guard.user_data.scene.grid_size - ); - } else { - warn!("Could not initialize screen dimensions in global state - no monitor found"); - } -} - -/// Defines which parts of AppData should be refreshed from the server -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum AppDataRefreshScope { - /// Refresh all data (user profile + friends list + dolls list) - All, - /// Refresh only user profile - User, - /// Refresh only friends list - Friends, - /// Refresh only dolls list - Dolls, -} - -static REFRESH_IN_FLIGHT: LazyLock>> = - LazyLock::new(|| Mutex::new(HashSet::new())); -static REFRESH_PENDING: LazyLock>> = - LazyLock::new(|| Mutex::new(HashSet::new())); - -/// Populate specific parts of app data from the server based on the scope. -/// -/// # Arguments -/// * `scope` - Determines which data to refresh (All, User, or Friends) -/// -/// # Examples -/// ``` -/// // Refresh only friends list when a friend request is accepted -/// init_app_data_scoped(AppDataRefreshScope::Friends).await; -/// -/// // Refresh only user profile when updating user settings -/// init_app_data_scoped(AppDataRefreshScope::User).await; -/// ``` -pub async fn init_app_data_scoped(scope: AppDataRefreshScope) { - loop { - // Deduplicate concurrent refreshes for the same scope - { - let mut in_flight = REFRESH_IN_FLIGHT.lock().await; - if in_flight.contains(&scope) { - let mut pending = REFRESH_PENDING.lock().await; - pending.insert(scope); - return; - } - in_flight.insert(scope); - } - - let result: Result<(), ()> = async { - let user_remote = UserRemote::new(); - let friend_remote = FriendRemote::new(); - let dolls_remote = DollsRemote::new(); - - // Fetch user profile if needed - if matches!(scope, AppDataRefreshScope::All | AppDataRefreshScope::User) { - match user_remote.get_user(None).await { - Ok(user) => { - let mut guard = lock_w!(crate::state::FDOLL); - guard.user_data.user = Some(user); - } - Err(e) => { - warn!("Failed to fetch user profile: {}", e); - use tauri_plugin_dialog::MessageDialogBuilder; - use tauri_plugin_dialog::{DialogExt, MessageDialogKind}; - - let handle = get_app_handle(); - MessageDialogBuilder::new( - handle.dialog().clone(), - "Network Error", - "Failed to fetch user profile. You may be offline.", - ) - .kind(MessageDialogKind::Error) - .show(|_| {}); - return Err(()); - } - } - } - - // Fetch friends list if needed - if matches!( - scope, - AppDataRefreshScope::All | AppDataRefreshScope::Friends - ) { - match friend_remote.get_friends().await { - Ok(friends) => { - let mut guard = lock_w!(crate::state::FDOLL); - guard.user_data.friends = Some(friends); - drop(guard); - friends::sync_from_app_data(); - } - Err(e) => { - warn!("Failed to fetch friends list: {}", e); - use tauri_plugin_dialog::MessageDialogBuilder; - use tauri_plugin_dialog::{DialogExt, MessageDialogKind}; - - let handle = get_app_handle(); - MessageDialogBuilder::new( - handle.dialog().clone(), - "Network Error", - "Failed to fetch friends list. You may be offline.", - ) - .kind(MessageDialogKind::Error) - .show(|_| {}); - return Err(()); - } - } - } - - // Fetch dolls list if needed - if matches!(scope, AppDataRefreshScope::All | AppDataRefreshScope::Dolls) { - match dolls_remote.get_dolls().await { - Ok(dolls) => { - let mut guard = lock_w!(crate::state::FDOLL); - guard.user_data.dolls = Some(dolls); - } - Err(e) => { - warn!("Failed to fetch dolls list: {}", e); - use tauri_plugin_dialog::MessageDialogBuilder; - use tauri_plugin_dialog::{DialogExt, MessageDialogKind}; - - let handle = get_app_handle(); - MessageDialogBuilder::new( - handle.dialog().clone(), - "Network Error", - "Failed to fetch dolls list. You may be offline.", - ) - .kind(MessageDialogKind::Error) - .show(|_| {}); - return Err(()); - } - } - } - - // Emit event regardless of partial success, frontend should handle nulls/empty states - { - let guard = lock_r!(crate::state::FDOLL); // Use read lock to get data - let app_data_clone = guard.user_data.clone(); - drop(guard); // Drop lock before emitting to prevent potential deadlocks - - if let Err(e) = AppDataRefreshed(app_data_clone).emit(get_app_handle()) { - warn!("Failed to emit app-data-refreshed event: {}", e); - use tauri_plugin_dialog::MessageDialogBuilder; - use tauri_plugin_dialog::{DialogExt, MessageDialogKind}; - - let handle = get_app_handle(); - MessageDialogBuilder::new( - handle.dialog().clone(), - "Sync Error", - "Could not broadcast refreshed data to the UI. Some data may be stale.", - ) - .kind(MessageDialogKind::Error) - .show(|_| {}); - } - - if matches!( - scope, - AppDataRefreshScope::All - | AppDataRefreshScope::User - | AppDataRefreshScope::Dolls - ) { - match sprite::get_active_doll_sprite_base64() { - Ok(sprite_b64) => { - if let Err(e) = - ActiveDollSpriteChanged(sprite_b64).emit(get_app_handle()) - { - warn!("Failed to emit active-doll-sprite-changed event: {}", e); - } - } - Err(e) => { - warn!("Failed to generate active doll sprite: {}", e); - } - } - } - } - - Ok(()) - } - .await; - - // Clear in-flight marker even on early exit - { - let mut in_flight = REFRESH_IN_FLIGHT.lock().await; - in_flight.remove(&scope); - } - - // If a refresh was queued while this one was running, run again - let rerun = { - let mut pending = REFRESH_PENDING.lock().await; - pending.remove(&scope) - }; - - if rerun { - continue; - } - - if result.is_err() { - return; - } - - break; - } -} - -pub fn clear_app_data() { - let mut guard = lock_w!(FDOLL); - guard.user_data.dolls = None; - guard.user_data.user = None; - guard.user_data.friends = None; - drop(guard); - friends::clear(); -} diff --git a/src/lib/bindings.ts b/src/lib/bindings.ts index 60597d4..4460634 100644 --- a/src/lib/bindings.ts +++ b/src/lib/bindings.ts @@ -89,8 +89,8 @@ async getClientConfig() : Promise { async saveClientConfig(config: AppConfig) : Promise { return await TAURI_INVOKE("save_client_config", { config }); }, -async openClientConfigManager() : Promise { - return await TAURI_INVOKE("open_client_config_manager"); +async openClientConfig() : Promise { + return await TAURI_INVOKE("open_client_config"); }, async openDollEditorWindow(dollId: string | null) : Promise { await TAURI_INVOKE("open_doll_editor_window", { dollId }); diff --git a/src/routes/client-config-manager/+page.svelte b/src/routes/client-config/+page.svelte similarity index 100% rename from src/routes/client-config-manager/+page.svelte rename to src/routes/client-config/+page.svelte