diff --git a/pixstrip-gtk/src/app.rs b/pixstrip-gtk/src/app.rs index 1d26e53..99663f8 100644 --- a/pixstrip-gtk/src/app.rs +++ b/pixstrip-gtk/src/app.rs @@ -659,6 +659,114 @@ fn build_ui(app: &adw::Application) { } } } + + // Start watch folder monitoring for active folders + start_watch_folder_monitoring(&ui); +} + +fn start_watch_folder_monitoring(ui: &WizardUi) { + let config_store = pixstrip_core::storage::ConfigStore::new(); + let config = config_store.load().unwrap_or_default(); + + let active_folders: Vec<_> = config.watch_folders.iter() + .filter(|f| f.active && f.path.exists()) + .cloned() + .collect(); + + if active_folders.is_empty() { + return; + } + + let (tx, rx) = std::sync::mpsc::channel::(); + + // Start a watcher for each active folder + for folder in &active_folders { + let watcher = pixstrip_core::watcher::FolderWatcher::new(); + let folder_tx = tx.clone(); + if let Err(e) = watcher.start(folder, folder_tx) { + eprintln!("Failed to start watching {}: {}", folder.path.display(), e); + continue; + } + // Leak the watcher so it stays alive for the lifetime of the app + std::mem::forget(watcher); + } + + // Build a lookup from folder path to preset name + let folder_presets: std::collections::HashMap = active_folders + .iter() + .map(|f| (f.path.clone(), f.preset_name.clone())) + .collect(); + + let toast_overlay = ui.toast_overlay.clone(); + + // Poll the channel from the main loop + glib::timeout_add_local(std::time::Duration::from_millis(500), move || { + let mut batch: Vec<(std::path::PathBuf, String)> = Vec::new(); + + // Drain all pending events + while let Ok(event) = rx.try_recv() { + match event { + pixstrip_core::watcher::WatchEvent::NewImage(path) => { + // Find which watch folder this belongs to + for (folder_path, preset_name) in &folder_presets { + if path.starts_with(folder_path) { + batch.push((path.clone(), preset_name.clone())); + break; + } + } + } + pixstrip_core::watcher::WatchEvent::Error(e) => { + eprintln!("Watch folder error: {}", e); + } + } + } + + if !batch.is_empty() { + // Group by preset name and process + let preset_store = pixstrip_core::storage::PresetStore::new(); + let mut by_preset: std::collections::HashMap> = + std::collections::HashMap::new(); + for (path, preset) in batch { + by_preset.entry(preset).or_default().push(path); + } + + for (preset_name, files) in by_preset { + if let Ok(presets) = preset_store.list() { + if let Some(preset) = presets.iter().find(|p| p.name == preset_name) { + let count = files.len(); + let preset = preset.clone(); + std::thread::spawn(move || { + // Build output dir next to the first file + let output_dir = files.first() + .and_then(|f| f.parent()) + .map(|p| p.join("processed")) + .unwrap_or_else(|| std::path::PathBuf::from("processed")); + let input_dir = files.first() + .and_then(|f| f.parent()) + .unwrap_or_else(|| std::path::Path::new(".")) + .to_path_buf(); + let mut job = preset.to_job(&input_dir, &output_dir); + for file in &files { + job.add_source(file); + } + let executor = pixstrip_core::executor::PipelineExecutor::new(); + let _ = executor.execute(&job, |_| {}); + }); + + let toast = adw::Toast::new(&format!( + "Watch: processing {} new image{}", + count, + if count == 1 { "" } else { "s" } + )); + toast.set_timeout(3); + toast_overlay.add_toast(toast); + } + } + } + } + + glib::ControlFlow::Continue + }); } fn build_menu() -> gtk::gio::Menu {