mod commands; mod news; mod state; mod sync; use std::sync::Mutex; use tauri::Manager; use state::AppState; /// Write a line to the platform log. /// /// On Android this reaches `adb logcat` under the tag `RustStdoutStderr`: tao's /// Android glue `dup2`s stdout/stderr into a pipe and forwards each line to the /// system log, so a plain `eprintln!` is all that's needed and no logging /// dependency is warranted. On desktop it's just stderr. /// /// This matters because Tauri command failures are *returned* to the WebView as /// values, not raised — so without logging them explicitly they never appear in /// logcat at all, and the UI only ever shows a truncated version. #[macro_export] macro_rules! mlog { ($($arg:tt)*) => { eprintln!("[mtgc] {}", format_args!($($arg)*)) }; } /// Log a command failure (with its full, untruncated message) and pass the /// result through unchanged. pub(crate) fn logged(what: &str, result: Result) -> Result { if let Err(e) = &result { crate::mlog!("{what} failed: {e}"); } result } /// Register the Kotlin-side `SyncPlugin` and stash its handle in app state, so /// the sync commands can call across to SAF. See `sync.rs` for why library sync /// goes through a Java plugin rather than plain `std::fs`. #[cfg(target_os = "android")] fn sync_plugin() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new("mtgc-sync") .setup(|app, api| { let handle = api.register_android_plugin("com.garrettmills.mtgc", "SyncPlugin")?; app.manage(sync::SyncBridge(handle)); Ok(()) }) .build() } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let builder = tauri::Builder::default().plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_opener::init()); #[cfg(target_os = "android")] let builder = builder.plugin(sync_plugin()); builder .setup(|app| { let data_dir = app .path() .app_data_dir() .expect("resolve app data dir"); std::fs::create_dir_all(&data_dir).ok(); mlog!("data dir: {}", data_dir.display()); app.manage(Mutex::new(AppState::new(data_dir))); Ok(()) }) .invoke_handler(tauri::generate_handler![ commands::get_status, commands::update_card_data, commands::cancel_update_card_data, commands::import_reference_db, sync::sync_status, sync::sync_pick_folder, sync::sync_set_document, sync::sync_list_documents, sync::sync_set_document_name, sync::sync_forget_document, sync::sync_now, sync::convert_legacy_library, news::get_news, news::get_set_cards, commands::search_cards, commands::add_owned, commands::set_owned, commands::set_location, commands::search_library, commands::search_reference, commands::get_library_stats, commands::get_library_facets, commands::get_reference_facets, commands::list_decks, commands::create_deck, commands::delete_deck, commands::rename_deck, commands::duplicate_deck, commands::set_deck_format, commands::set_deck_commander, commands::set_deck_card, commands::deck_contents, commands::suggest_cards, commands::card_decks, commands::export_deck, commands::import_deck, commands::export_library_cards, commands::import_library_cards, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }