diff --git a/src-tauri/src/commands/auth.rs b/src-tauri/src/commands/auth.rs index 808b626..f724bad 100644 --- a/src-tauri/src/commands/auth.rs +++ b/src-tauri/src/commands/auth.rs @@ -1,6 +1,8 @@ use tauri; use tracing; +use crate::init::lifecycle; + #[tauri::command] pub async fn logout_and_restart() -> Result<(), String> { crate::services::auth::logout_and_restart() @@ -18,7 +20,7 @@ pub fn start_auth_flow() -> Result<(), String> { // Close welcome window if it's still open crate::services::welcome::close_welcome_window(); tauri::async_runtime::spawn(async { - crate::startup::bootstrap().await; + lifecycle::handle_authentication_flow().await; }); }) .map_err(|e| e.to_string()) diff --git a/src-tauri/src/startup.rs b/src-tauri/src/init/lifecycle.rs similarity index 54% rename from src-tauri/src/startup.rs rename to src-tauri/src/init/lifecycle.rs index 770e13f..7bbf301 100644 --- a/src-tauri/src/startup.rs +++ b/src-tauri/src/init/lifecycle.rs @@ -1,77 +1,59 @@ use reqwest::StatusCode; use std::time::Duration; -use tokio::time::{sleep, Instant}; +use tokio::time::sleep; use tracing::{info, warn}; use crate::{ + init::startup::{initialize_app_data_and_connections, transition_to_main_interface}, + lock_w, models::health::HealthError, remotes::health::HealthRemote, services::{ - auth::{get_access_token, get_tokens}, - scene::{close_splash_window, open_scene_window, open_splash_window}, + active_app::init_active_app_changes_listener, + auth::get_tokens, + health_manager::show_health_manager_with_error, + scene::close_splash_window, welcome::open_welcome_window, - ws::init_ws_client, }, - state::init_app_data, - system_tray::update_system_tray, + state::FDOLL, + system_tray::{init_system_tray, update_system_tray}, }; -async fn init_ws_after_auth() { - const MAX_ATTEMPTS: u8 = 5; - const BACKOFF: Duration = Duration::from_millis(300); - - for _attempt in 1..=MAX_ATTEMPTS { - if get_access_token().await.is_some() { - init_ws_client().await; - return; - } - - sleep(BACKOFF).await; - } -} - -async fn construct_app() { - open_splash_window(); - - // Record start time for minimum splash duration - let start = Instant::now(); - - // Initialize app data first so we only start WebSocket after auth is fully available - init_app_data().await; - - // Initialize WebSocket client after we know auth is present - init_ws_after_auth().await; - - // Ensure splash stays visible for at least 3 seconds - let elapsed = start.elapsed(); - if elapsed < Duration::from_secs(3) { - sleep(Duration::from_secs(3) - elapsed).await; +/// Initializes and starts the core app lifecycle after initial setup. +/// +/// This function handles: +/// - System tray initialization and storage in app state +/// - Active app change listener setup +/// - Startup sequence execution with error handling +/// +/// # Errors +/// If the startup sequence fails, displays a health manager dialog +/// with the error details. +/// +/// # Example +/// ``` +/// // Called automatically during app setup in initialize_app_environment() +/// lifecycle::launch_core_services().await; +/// ``` +pub async fn launch_core_services() { + let tray = init_system_tray(); + { + let mut guard = lock_w!(FDOLL); + guard.tray = Some(tray); } - // Close splash and open main scene - close_splash_window(); - open_scene_window(); -} + // Begin listening for foreground app changes + init_active_app_changes_listener(); -pub async fn bootstrap() { - match get_tokens().await { - Some(_tokens) => { - info!("Tokens found in keyring - restoring user session"); - construct_app().await; - update_system_tray(true); - } - None => { - info!("No active session found - showing welcome first"); - open_welcome_window(); - close_splash_window(); - update_system_tray(false); - } + if let Err(err) = validate_environment_and_start_app().await { + tracing::warn!("Startup sequence encountered an error: {}", err); + show_health_manager_with_error(Some(err.to_string())); } } /// Perform checks for environment, network condition /// and handle situations where startup would not be appropriate. -pub async fn init_startup_sequence() -> Result<(), HealthError> { +pub async fn validate_environment_and_start_app() -> Result<(), HealthError> { let health_remote = HealthRemote::try_new()?; // simple retry loop to smooth transient network issues @@ -81,7 +63,7 @@ pub async fn init_startup_sequence() -> Result<(), HealthError> { for attempt in 1..=MAX_ATTEMPTS { match health_remote.get_health().await { Ok(_) => { - bootstrap().await; + handle_authentication_flow().await; return Ok(()); } Err(HealthError::NonOkStatus(status)) => { @@ -109,3 +91,21 @@ pub async fn init_startup_sequence() -> Result<(), HealthError> { StatusCode::SERVICE_UNAVAILABLE, )) } + +/// Handles authentication flow: checks for tokens and either restores session or shows welcome. +pub async fn handle_authentication_flow() { + match get_tokens().await { + Some(_tokens) => { + info!("Tokens found in keyring - restoring user session"); + let start = initialize_app_data_and_connections().await; + transition_to_main_interface(start).await; + update_system_tray(true); + } + None => { + info!("No active session found - showing welcome first"); + open_welcome_window(); + close_splash_window(); + update_system_tray(false); + } + } +} diff --git a/src-tauri/src/init/mod.rs b/src-tauri/src/init/mod.rs new file mode 100644 index 0000000..ef73da1 --- /dev/null +++ b/src-tauri/src/init/mod.rs @@ -0,0 +1,3 @@ +pub mod lifecycle; +pub mod startup; +pub mod tracing; diff --git a/src-tauri/src/init/startup.rs b/src-tauri/src/init/startup.rs new file mode 100644 index 0000000..4dd49d5 --- /dev/null +++ b/src-tauri/src/init/startup.rs @@ -0,0 +1,52 @@ +use std::time::Duration; +use tokio::time::{sleep, Instant}; + +use crate::{ + services::{ + auth::get_access_token, + scene::{close_splash_window, open_scene_window, open_splash_window}, + ws::init_ws_client, + }, + state::init_app_data, +}; + +async fn establish_websocket_connection() { + const MAX_ATTEMPTS: u8 = 5; + const BACKOFF: Duration = Duration::from_millis(300); + + for _attempt in 1..=MAX_ATTEMPTS { + if get_access_token().await.is_some() { + init_ws_client().await; + return; + } + + sleep(BACKOFF).await; + } +} + +pub async fn initialize_app_data_and_connections() -> Instant { + open_splash_window(); + + // Record start time for minimum splash duration + let start = Instant::now(); + + // Initialize app data first so we only start WebSocket after auth is fully available + init_app_data().await; + + // Initialize WebSocket client after we know auth is present + establish_websocket_connection().await; + + start +} + +pub async fn transition_to_main_interface(start: Instant) { + // Ensure splash stays visible for at least 3 seconds + let elapsed = start.elapsed(); + if elapsed < Duration::from_secs(3) { + sleep(Duration::from_secs(3) - elapsed).await; + } + + // Close splash and open main scene + close_splash_window(); + open_scene_window(); +} \ No newline at end of file diff --git a/src-tauri/src/init/tracing.rs b/src-tauri/src/init/tracing.rs new file mode 100644 index 0000000..c73a18e --- /dev/null +++ b/src-tauri/src/init/tracing.rs @@ -0,0 +1,49 @@ +use tauri::Manager; +use tracing_subscriber::util::SubscriberInitExt; + +use crate::get_app_handle; + +/// Initialize `tracing_subscriber` for logging to file & console +pub fn setup_logging() { + // Set up file appender + let app_handle = get_app_handle(); + let app_log_dir = app_handle + .path() + .app_log_dir() + .expect("Could not determine app log dir"); + + // Create the directory if it doesn't exist + if let Err(e) = std::fs::create_dir_all(&app_log_dir) { + eprintln!("Failed to create log directory: {}", e); + } + + let file_appender = tracing_appender::rolling::daily(&app_log_dir, "friendolls.log"); + let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); + + // Create a filter - adjust the level as needed (trace, debug, info, warn, error) + let filter = tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")); + + // Create a layer that writes to the file + let file_layer = tracing_subscriber::fmt::layer() + .with_target(false) + .with_thread_ids(false) + .with_file(true) + .with_line_number(true) + .with_writer(non_blocking); + + // Create a layer that writes to stdout (console) + let console_layer = tracing_subscriber::fmt::layer() + .with_target(false) + .with_thread_ids(false) + .with_file(true) + .with_line_number(true); + + // Combine both layers with filter + use tracing_subscriber::layer::SubscriberExt; + tracing_subscriber::registry() + .with(filter) + .with(file_layer) + .with(console_layer) + .init(); +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d55239e..c73647d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,7 +1,10 @@ -use crate::services::{ - cursor::start_cursor_tracking, - doll_editor::open_doll_editor_window, - scene::{open_splash_window, set_pet_menu_state, set_scene_interactive}, +use crate::{ + init::tracing::setup_logging, + services::{ + cursor::start_cursor_tracking, + doll_editor::open_doll_editor_window, + scene::{open_splash_window, set_pet_menu_state, set_scene_interactive}, + }, }; use commands::app::{quit_app, restart_app}; use commands::app_data::{get_app_data, refresh_app_data}; @@ -18,22 +21,18 @@ use commands::interaction::send_interaction_cmd; use commands::sprite::recolor_gif_base64; use commands::user_status::send_user_status_cmd; use tauri::async_runtime; -use tauri::Manager; -use tracing_subscriber::{self, util::SubscriberInitExt}; static APP_HANDLE: std::sync::OnceLock> = std::sync::OnceLock::new(); mod commands; -mod lifecycle; +mod init; mod models; mod remotes; mod services; -mod startup; mod state; mod system_tray; mod utilities; - /// Tauri app handle pub fn get_app_handle<'a>() -> &'a tauri::AppHandle { APP_HANDLE @@ -41,55 +40,11 @@ pub fn get_app_handle<'a>() -> &'a tauri::AppHandle { .expect("get_app_handle called but app is still not initialized") } -fn setup_fdoll() -> Result<(), tauri::Error> { - // Initialize tracing subscriber for logging - - // Set up file appender - let app_handle = get_app_handle(); - let app_log_dir = app_handle - .path() - .app_log_dir() - .expect("Could not determine app log dir"); - - // Create the directory if it doesn't exist - if let Err(e) = std::fs::create_dir_all(&app_log_dir) { - eprintln!("Failed to create log directory: {}", e); - } - - let file_appender = tracing_appender::rolling::daily(&app_log_dir, "friendolls.log"); - let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); - - // Create a filter - adjust the level as needed (trace, debug, info, warn, error) - let filter = tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")); - - // Create a layer that writes to the file - let file_layer = tracing_subscriber::fmt::layer() - .with_target(false) - .with_thread_ids(false) - .with_file(true) - .with_line_number(true) - .with_writer(non_blocking); - - // Create a layer that writes to stdout (console) - let console_layer = tracing_subscriber::fmt::layer() - .with_target(false) - .with_thread_ids(false) - .with_file(true) - .with_line_number(true); - - // Combine both layers with filter - use tracing_subscriber::layer::SubscriberExt; - tracing_subscriber::registry() - .with(filter) - .with(file_layer) - .with(console_layer) - .init(); - +fn initialize_app_environment() -> Result<(), tauri::Error> { + setup_logging(); open_splash_window(); - - state::init_fdoll_state(Some(_guard)); - async_runtime::spawn(async move { lifecycle::start_fdoll().await }); + state::init_fdoll_state(); + async_runtime::spawn(async move { init::lifecycle::launch_core_services().await }); Ok(()) } @@ -147,7 +102,7 @@ pub fn run() { APP_HANDLE .set(app.handle().to_owned()) .expect("Failed to init app handle."); - setup_fdoll().expect("Failed to setup app."); + initialize_app_environment().expect("Failed to setup app."); Ok(()) }) .build(tauri::generate_context!()) diff --git a/src-tauri/src/lifecycle.rs b/src-tauri/src/lifecycle.rs deleted file mode 100644 index 61d7ff7..0000000 --- a/src-tauri/src/lifecycle.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::{ - lock_w, - services::{ - active_app::init_active_app_changes_listener, - health_manager::show_health_manager_with_error, - }, - startup::init_startup_sequence, - state::FDOLL, - system_tray::init_system_tray, -}; - -/// Initializes and starts the core app lifecycle after initial setup. -/// -/// This function handles: -/// - System tray initialization and storage in app state -/// - Active app change listener setup -/// - Startup sequence execution with error handling -/// -/// # Errors -/// If the startup sequence fails, displays a health manager dialog -/// with the error details. -/// -/// # Example -/// ``` -/// // Called automatically during app setup in setup_fdoll() -/// lifecycle::start_fdoll().await; -/// ``` -pub async fn start_fdoll() { - let tray = init_system_tray(); - { - let mut guard = lock_w!(FDOLL); - guard.tray = Some(tray); - } - - // Begin listening for foreground app changes - init_active_app_changes_listener(); - - if let Err(err) = init_startup_sequence().await { - tracing::warn!("Startup sequence encountered an error: {}", err); - show_health_manager_with_error(Some(err.to_string())); - } -} diff --git a/src-tauri/src/state/mod.rs b/src-tauri/src/state/mod.rs index c6783b4..9279fba 100644 --- a/src-tauri/src/state/mod.rs +++ b/src-tauri/src/state/mod.rs @@ -1,17 +1,15 @@ // in app-core/src/state.rs -use crate::{lock_w}; -use std::{ - sync::{Arc, LazyLock, RwLock}, -}; +use crate::lock_w; +use std::sync::{Arc, LazyLock, RwLock}; use tauri::tray::TrayIcon; use tracing::info; -mod network; mod auth; +mod network; mod ui; -pub use network::*; pub use auth::*; +pub use network::*; pub use ui::*; #[derive(Default)] @@ -20,7 +18,6 @@ pub struct AppState { pub network: NetworkState, pub auth: AuthState, pub ui: UiState, - pub tracing_guard: Option, pub tray: Option, } @@ -29,11 +26,10 @@ pub struct AppState { pub static FDOLL: LazyLock>> = LazyLock::new(|| Arc::new(RwLock::new(AppState::default()))); -pub fn init_fdoll_state(tracing_guard: Option) { +pub fn init_fdoll_state() { { let mut guard = lock_w!(FDOLL); dotenvy::dotenv().ok(); - guard.tracing_guard = tracing_guard; guard.app_config = crate::services::client_config_manager::load_app_config(); guard.network = init_network_state(); guard.auth = init_auth_state();