From fda9b6451c5f419ff135920bc9c953ccd3398a6c Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 15 Jan 2025 04:15:26 +0100 Subject: [PATCH] fix: high CPU usage when desktop files are accessed --- Cargo.lock | 26 +++++- Cargo.toml | 2 +- src/subscriptions/desktop_files.rs | 131 ++++++++++------------------- 3 files changed, 70 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5f5750..8fcbae2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1068,7 +1068,7 @@ dependencies = [ "cosmic-app-list-config", "cosmic-freedesktop-icons", "current_locale", - "freedesktop-desktop-entry", + "freedesktop-desktop-entry 0.7.5", "futures", "glob", "i18n-embed", @@ -1940,6 +1940,22 @@ dependencies = [ "xdg", ] +[[package]] +name = "freedesktop-desktop-entry" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83c9c25bc7e0ff18c6fee324db310497622be235fd45c0f7347ab81981a941e" +dependencies = [ + "dirs 5.0.1", + "gettext-rs", + "log", + "memchr", + "strsim 0.11.1", + "textdistance", + "thiserror", + "xdg", +] + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -3116,7 +3132,7 @@ dependencies = [ "cosmic-theme", "css-color", "derive_setters", - "freedesktop-desktop-entry", + "freedesktop-desktop-entry 0.5.2", "iced", "iced_accessibility", "iced_core", @@ -5020,6 +5036,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textdistance" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa672c55ab69f787dbc9126cc387dbe57fdd595f585e4524cf89018fa44ab819" + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index b80411f..12d3715 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ i18n-embed = { version = "0.14.1", features = [ i18n-embed-fl = "0.8.0" rust-embed = "8.4.0" glob = "0.3.0" -freedesktop-desktop-entry = "0.5.2" +freedesktop-desktop-entry = "0.7.5" shlex = "1.1.0" serde = { version = "1.0.134", features = ["derive"] } ron = "0.8.0" diff --git a/src/subscriptions/desktop_files.rs b/src/subscriptions/desktop_files.rs index 3d7366a..dff6f87 100644 --- a/src/subscriptions/desktop_files.rs +++ b/src/subscriptions/desktop_files.rs @@ -1,100 +1,59 @@ -use cosmic::iced::Subscription; -use futures::stream; -use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; -use std::{fmt::Debug, hash::Hash}; -use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; - -#[derive(Debug)] -pub enum State { - Ready, - Waiting { - watcher: RecommendedWatcher, - rx: UnboundedReceiver>, - }, - Finished, -} +use cosmic::{ + iced::{stream, Subscription}, + iced_futures::futures::{self, SinkExt}, +}; +use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; +use std::fmt::Debug; +use std::hash::Hash; +use tokio::sync::mpsc; #[derive(Debug, Clone, Copy)] -pub enum DesktopFileEvent { +pub enum Event { Changed, } pub fn desktop_files( id: I, -) -> cosmic::iced::Subscription<(I, DesktopFileEvent)> { +) -> cosmic::iced::Subscription { Subscription::run_with_id( id, - stream::unfold(State::Ready, move |mut state| async move { - let (event, new_state) = start_watching(id, state).await; - state = new_state; - if let Some(event) = event { - return Some((event, state)); - } else { - None - } - }), - ) -} - -async fn start_watching(id: I, state: State) -> (Option<(I, DesktopFileEvent)>, State) { - match state { - State::Ready => { - let paths = freedesktop_desktop_entry::default_paths(); - // TODO log errors - if let Ok((mut watcher, rx)) = async_watcher() { - for path in paths { + stream::channel(50, move |mut output| async move { + let handle = tokio::runtime::Handle::current(); + let (tx, mut rx) = mpsc::channel(4); + let mut last_update = std::time::Instant::now(); + + // Automatically select the best implementation for your platform. + // You can also access each implementation directly e.g. INotifyWatcher. + let watcher = RecommendedWatcher::new( + move |res: Result| { + if let Ok(event) = res { + match event.kind { + EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) => { + let now = std::time::Instant::now(); + if now.duration_since(last_update).as_secs() > 3 { + _ = handle.block_on(tx.send(())); + last_update = now; + } + } + + _ => (), + } + } + }, + Config::default(), + ); + + if let Ok(mut watcher) = watcher { + for path in freedesktop_desktop_entry::default_paths() { let _ = watcher.watch(path.as_ref(), RecursiveMode::Recursive); } - ( - Some((id, DesktopFileEvent::Changed)), - State::Waiting { watcher, rx }, - ) - } else { - (None, State::Finished) - } - } - State::Waiting { watcher, rx } => { - if let Some(rx) = async_watch(rx).await { - ( - Some((id, DesktopFileEvent::Changed)), - State::Waiting { watcher, rx }, - ) - } else { - (None, State::Finished) - } - } - State::Finished => cosmic::iced::futures::future::pending().await, - } -} - -fn async_watcher() -> notify::Result<(RecommendedWatcher, UnboundedReceiver>)> -{ - let (tx, rx) = unbounded_channel(); - - // Automatically select the best implementation for your platform. - // You can also access each implementation directly e.g. INotifyWatcher. - let watcher = RecommendedWatcher::new( - move |res| { - futures::executor::block_on(async { - let _ = tx.send(res); - }) - }, - Config::default(), - )?; - - Ok((watcher, rx)) -} -async fn async_watch( - mut rx: UnboundedReceiver>, -) -> Option>> { - // TODO log errors - if let Some(res) = rx.recv().await { - match res { - Ok(_) => return Some(rx), - Err(_) => return None, - } - } + while rx.recv().await.is_some() { + _ = output.send(Event::Changed).await; + } + } - None + futures::future::pending().await + }), + ) }