mod state; use log::LevelFilter; use state::{AppState, FrontendState}; use std::{path::PathBuf, str::FromStr}; use tauri_plugin_log::{Target, TargetKind}; #[tauri::command] fn get_state(app_state: tauri::State) -> FrontendState { log::debug!("get_state invoked"); app_state.snapshot() } #[tauri::command] fn tick(app_state: tauri::State) -> FrontendState { log::trace!("tick invoked"); app_state.tick() } #[tauri::command] fn create_alarm( time: String, label: Option, app_state: tauri::State, ) -> FrontendState { let has_label = label .as_ref() .map(|value| !value.trim().is_empty()) .unwrap_or(false); log::info!( "create_alarm invoked (time={time}, has_label={has_label})", time = time, has_label = has_label ); app_state.create_alarm(time, label) } #[tauri::command] fn toggle_alarm(id: String, app_state: tauri::State) -> FrontendState { log::info!("toggle_alarm invoked (id={id})", id = id); app_state.toggle_alarm(id) } #[tauri::command] fn delete_alarm(id: String, app_state: tauri::State) -> FrontendState { log::info!("delete_alarm invoked (id={id})", id = id); app_state.delete_alarm(id) } #[tauri::command] fn toggle_timer( input: Option, force_parse: bool, app_state: tauri::State, ) -> FrontendState { let has_input = input .as_ref() .map(|value| !value.trim().is_empty()) .unwrap_or(false); log::info!( "toggle_timer invoked (force_parse={force_parse}, has_input={has_input})", force_parse = force_parse, has_input = has_input ); app_state.toggle_timer(input, force_parse) } #[tauri::command] fn reset_timer(app_state: tauri::State) -> FrontendState { log::debug!("reset_timer invoked"); app_state.reset_timer() } #[tauri::command] fn toggle_stopwatch(app_state: tauri::State) -> FrontendState { log::info!("toggle_stopwatch invoked"); app_state.toggle_stopwatch() } #[tauri::command] fn add_lap(app_state: tauri::State) -> FrontendState { log::debug!("add_lap invoked"); app_state.add_lap() } #[tauri::command] fn reset_stopwatch(app_state: tauri::State) -> FrontendState { log::debug!("reset_stopwatch invoked"); app_state.reset_stopwatch() } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let context = tauri::generate_context!(); let storage_path = resolve_storage_path(context.config()); let storage_display = storage_path.display().to_string(); let app_state = AppState::new(storage_path); let log_level = resolve_log_level(); let log_plugin = tauri_plugin_log::Builder::new() .targets([ Target::new(TargetKind::Stdout), Target::new(TargetKind::Webview), Target::new(TargetKind::LogDir { file_name: None }), ]) .level(log_level) .build(); tauri::Builder::default() .manage(app_state) .plugin(log_plugin) .plugin(tauri_plugin_opener::init()) .invoke_handler(tauri::generate_handler![ get_state, tick, create_alarm, toggle_alarm, delete_alarm, toggle_timer, reset_timer, toggle_stopwatch, add_lap, reset_stopwatch ]) .setup(move |_| { log::info!("Tauclock backend ready; storage_path={}", storage_display); log::debug!("Backend log level set to {:?}", log_level); Ok(()) }) .run(context) .expect("error while running tauri application"); } fn resolve_log_level() -> LevelFilter { let env_value = std::env::var("TAUCLOCK_LOG_LEVEL") .or_else(|_| std::env::var("TAUCLOCK_LOG")) .ok(); if let Some(raw) = env_value { if let Ok(level) = LevelFilter::from_str(raw.trim()) { return level; } else { eprintln!( "[tauclock] Invalid TAUCLOCK_LOG_LEVEL '{}', falling back to default", raw ); } } if cfg!(debug_assertions) { LevelFilter::Debug } else { LevelFilter::Info } } fn resolve_storage_path(config: &tauri::Config) -> PathBuf { let mut dir = dirs::data_dir() .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))); let identifier = if config.identifier.is_empty() { "tauclock".to_string() } else { config.identifier.clone() }; dir.push(identifier); dir.join("alarms.json") }