//! Tauri app builder and command registration pub mod commands; pub mod state; pub mod sync_scheduler; pub mod sync_service; #[cfg(mobile)] #[tauri::mobile_entry_point] fn mobile_main() { build_app() .run(tauri::generate_context!()) .expect("error while running tauri application"); } use state::AppState; use std::sync::Arc; use tauri::Manager; #[cfg(not(any(target_os = "ios", target_os = "android")))] use tauri::Emitter; /// Build the Tauri app with all commands registered. pub fn build_app() -> tauri::Builder { let builder = tauri::Builder::default(); // Desktop-only plugins #[cfg(not(any(target_os = "ios", target_os = "android")))] let builder = builder .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_window_state::Builder::new().build()) .plugin(tauri_plugin_updater::Builder::new().build()); builder .plugin(tauri_plugin_dialog::init()) .setup(|app| { let app_handle = app.handle().clone(); tauri::async_runtime::block_on(async move { let state = match AppState::new(&app_handle).await { Ok(s) => s, Err(e) => { tracing::error!(error = %e, "Fatal: failed to initialize app state"); eprintln!("Fatal: failed to initialize app state: {e}"); return Err(e); } }; let state = Arc::new(state); app_handle.manage(state.clone()); // Start background auto-fetch state::spawn_auto_fetch(app_handle.clone(), state.clone()); // Start background stale-item cleanup state::spawn_stale_cleanup(state.clone()); // Start sync scheduler let sync_handle = sync_scheduler::start_sync_scheduler(app_handle); state.set_sync_scheduler_handle(sync_handle); Ok(()) }) .map_err(|e: String| -> Box { e.into() })?; // Check for OTA updates after a short delay (desktop only) #[cfg(not(any(target_os = "ios", target_os = "android")))] { let update_handle = app.handle().clone(); tauri::async_runtime::spawn(async move { tokio::time::sleep(std::time::Duration::from_secs(10)).await; check_for_updates(update_handle).await; }); } Ok(()) }) .invoke_handler(tauri::generate_handler![ commands::list_sources, commands::list_items, commands::get_item, commands::mark_item_read, commands::mark_item_unread, commands::star_item, commands::unstar_item, commands::get_unread_count, commands::mark_all_read, commands::list_plugins, commands::get_plugin_schema, commands::get_feeds_by_busser, commands::get_feed, commands::create_feed, commands::update_feed, commands::delete_feed, commands::delete_feeds_by_busser, commands::fetch_all, commands::reset_circuit_breaker, commands::set_feed_tags, commands::list_all_tags, commands::export_opml, commands::import_opml, commands::list_themes, commands::get_theme, commands::get_custom_themes_dir, commands::import_theme, commands::export_theme, commands::sync_get_tiers, commands::sync_status, commands::sync_start_auth, commands::sync_complete_auth, commands::sync_disconnect, commands::sync_now, commands::sync_setup_encryption_new, commands::sync_setup_encryption_existing, commands::sync_update_settings, commands::sync_subscription_status, commands::sync_subscribe, commands::get_config, commands::set_config, commands::list_query_feeds, commands::create_query_feed, commands::update_query_feed, commands::delete_query_feed, commands::extract_reader_view, commands::download_and_open, commands::list_bookmarks, commands::create_bookmark, commands::create_bookmark_from_item, commands::update_bookmark, commands::delete_bookmark, commands::set_bookmark_tags, commands::list_bookmark_tags, commands::is_bookmarked, commands::get_bookmark_count, commands::export_bookmark_html, ]) } /// Check for OTA updates and emit an event to the frontend if one is available. #[cfg(not(any(target_os = "ios", target_os = "android")))] async fn check_for_updates(app: tauri::AppHandle) { use tauri_plugin_updater::UpdaterExt; let updater = match app.updater() { Ok(u) => u, Err(e) => { tracing::warn!("Failed to initialize updater: {e}"); return; } }; match updater.check().await { Ok(Some(update)) => { tracing::info!("Update available: v{}", update.version); let _ = app.emit( "update-available", serde_json::json!({ "version": update.version, "body": update.body.unwrap_or_default(), }), ); } Ok(None) => { tracing::info!("App is up to date"); } Err(e) => { tracing::warn!("Update check failed: {e}"); } } }