diff --git a/src/app.rs b/src/app.rs index 8658031..8cff8c3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -369,6 +369,7 @@ pub enum Message { PendingPauseAll(bool), PermanentlyDelete(Option), Preview(Option), + RescanRecents, RescanTrash, RemoveFromRecents(Option), Rename(Option), @@ -1246,6 +1247,23 @@ impl App { Task::batch(commands) } + /// Refresh all tabs that are opened in [`Location::Recents`]. + fn refresh_recents_tabs(&mut self) -> Task { + let commands: Vec<_> = self + .tab_model + .iter() + .filter_map(|entity| { + let tab = self.tab_model.data::(entity)?; + (tab.location == Location::Recents).then_some(entity) + }) + .collect(); + let commands: Vec<_> = commands + .into_iter() + .map(|entity| self.update_tab(entity, Location::Recents, None)) + .collect(); + Task::batch(commands) + } + fn rescan_recents(&mut self) -> Task { let mut needs_reload = Vec::new(); for entity in self.tab_model.iter() { @@ -3574,6 +3592,9 @@ impl Application for App { let paths = self.selected_paths(entity_opt); return self.operation(Operation::RemoveFromRecents { paths }); } + Message::RescanRecents => { + return self.refresh_recents_tabs(); + } Message::RescanTrash => { // Update trash icon if empty/full let maybe_entity = self.nav_model.iter().find(|&entity| { @@ -5788,6 +5809,12 @@ impl Application for App { struct WatcherSubscription; struct TrashWatcherSubscription; struct TimeSubscription; + #[cfg(all( + not(feature = "desktop-applet"), + not(target_os = "ios"), + not(target_os = "android") + ))] + struct RecentsWatcherSubscription; let mut subscriptions = vec![ event::listen_with(|event, status, window_id| match event { @@ -5989,6 +6016,73 @@ impl Application for App { } } + std::future::pending().await + }), + ), + #[cfg(all( + not(feature = "desktop-applet"), + not(target_os = "ios"), + not(target_os = "android") + ))] + Subscription::run_with_id( + TypeId::of::(), + stream::channel(1, |mut output| async move { + let Some(recents_path) = recently_used_xbel::dir() else { + log::warn!( + "failed to watch recents changes: .recently_used.xbel does not exist" + ); + return std::future::pending().await; + }; + + let watcher_res = new_debouncer( + time::Duration::from_millis(250), + Some(time::Duration::from_millis(250)), + move |event_res: notify_debouncer_full::DebounceEventResult| match event_res + { + Ok(events) => { + // Programs differ in how they modify the recents file so the + // rescan is triggered on any event but access. + if events.iter().any(|event| { + let kind = event.kind; + kind.is_create() + || kind.is_modify() + || kind.is_remove() + || kind.is_other() + }) { + if let Err(e) = futures::executor::block_on(async { + output.send(Message::RescanRecents).await + }) { + log::warn!("open recents tabs need to be updated but sending message failed: {e:?}"); + } + } + } + Err(e) => { + log::warn!("failed to watch recents file for changes: {e:?}") + } + }, + ); + + match watcher_res { + Ok(mut watcher) => { + if let Err(e) = watcher + .watcher() + .watch(&recents_path, notify::RecursiveMode::NonRecursive) + { + log::warn!( + "failed to add recents file `{}` to watcher: {}", + recents_path.display(), + e + ); + } + + // Don't drop the watcher. + std::future::pending::<()>().await; + } + Err(e) => { + log::warn!("failed to create new watcher for recents file: {e:?}") + } + } + std::future::pending().await }), ),