#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use std::sync::Mutex; use tauri::Manager; use tutorialdock_lib::{commands, ffmpeg, fonts, library, prefs, video_protocol, AppPaths}; fn main() { // 1. Resolve exe directory for portability. let exe_dir = std::env::current_exe() .expect("cannot resolve exe path") .parent() .expect("exe has no parent") .to_path_buf(); let state_dir = exe_dir.join("state"); // 2. Create all subdirectories. let paths = AppPaths { exe_dir: exe_dir.clone(), state_dir: state_dir.clone(), fonts_dir: state_dir.join("fonts"), fa_dir: state_dir.join("fontawesome"), subs_dir: state_dir.join("subtitles"), }; std::fs::create_dir_all(&paths.fonts_dir).ok(); std::fs::create_dir_all(&paths.fa_dir.join("webfonts")).ok(); std::fs::create_dir_all(&paths.subs_dir).ok(); // 3. Set WebView2 user data folder for portability (no AppData usage). let webview_profile = state_dir.join("webview_profile"); std::fs::create_dir_all(&webview_profile).ok(); unsafe { std::env::set_var("WEBVIEW2_USER_DATA_FOLDER", &webview_profile); } // 4. Load preferences. let prefs_data = prefs::Prefs::load(&state_dir); // 5. Initialize library (empty - loaded from last folder or frontend action). let mut lib = library::Library::new(); // Discover ffmpeg/ffprobe. let ff_paths = ffmpeg::discover(&paths.exe_dir, &paths.state_dir); lib.ffprobe = ff_paths.ffprobe; lib.ffmpeg = ff_paths.ffmpeg; // Restore last folder if it exists. if let Some(ref last_path) = prefs_data.last_folder_path { if std::path::Path::new(last_path).is_dir() { let _ = lib.set_root(last_path, &state_dir); } } // 6. Build Tauri app. let builder = tauri::Builder::default() .plugin(tauri_plugin_dialog::init()) .manage(Mutex::new(lib)) .manage(Mutex::new(prefs_data)) .manage(paths) .invoke_handler(tauri::generate_handler![ commands::select_folder, commands::open_folder_path, commands::get_recents, commands::remove_recent, commands::get_library, commands::set_current, commands::tick_progress, commands::set_folder_volume, commands::set_folder_autoplay, commands::set_folder_rate, commands::set_order, commands::start_duration_scan, commands::get_prefs, commands::set_prefs, commands::set_always_on_top, commands::save_window_state, commands::get_note, commands::set_note, commands::get_current_video_meta, commands::get_current_subtitle, commands::get_embedded_subtitles, commands::extract_embedded_subtitle, commands::get_available_subtitles, commands::load_sidecar_subtitle, commands::choose_subtitle_file, commands::reset_watch_progress, ]); // Register custom protocol for video/subtitle/font serving. let builder = video_protocol::register_protocol(builder); // Configure window from saved prefs and launch. builder .setup(|app| { let prefs_state = app.state::>(); let p = prefs_state.lock().unwrap(); let win = app.get_webview_window("main").unwrap(); if let Some(x) = p.window.x { if let Some(y) = p.window.y { let _ = win.set_position(tauri::Position::Physical( tauri::PhysicalPosition::new(x, y), )); } } let _ = win.set_size(tauri::Size::Physical(tauri::PhysicalSize::new( p.window.width as u32, p.window.height as u32, ))); let _ = win.set_always_on_top(p.always_on_top); drop(p); // Spawn async font caching (silent, non-blocking). let app_paths = app.state::(); let fonts_dir = app_paths.fonts_dir.clone(); let fa_dir = app_paths.fa_dir.clone(); tauri::async_runtime::spawn(async move { let _ = fonts::ensure_google_fonts_local(&fonts_dir).await; let _ = fonts::ensure_fontawesome_local(&fa_dir).await; }); Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); }