From 73702f6049da07bc0d7a934ed026525cf6acdd8b Mon Sep 17 00:00:00 2001 From: Paul Delafosse Date: Fri, 13 May 2022 08:00:52 +0200 Subject: [PATCH] feat: improve cache, add ScaledDirectories lookup and parent lookup --- src/lib.rs | 110 ++++++++++++++++++++++++++++++++++++++++----- src/theme/mod.rs | 26 +++-------- src/theme/parse.rs | 34 ++++++++++++-- 3 files changed, 133 insertions(+), 37 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e7bbbdd..876cbfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,22 @@ //! .find(); //! # } //!``` +//! **Cache:** +//! +//! If your application is going to repeat the same icon lookups multiple time +//! you can use the internal cache to improve performance. +//! +//! ```rust +//! # fn main() { +//! use freedesktop_icons::lookup; +//! +//! let icon = lookup("firefox") +//! .with_size(48) +//! .with_scale(2) +//! .with_theme("Arc") +//! .with_cache() +//! .find(); +//! # } use crate::cache::CACHE; use crate::theme::{try_build_icon_path, THEMES}; use std::path::PathBuf; @@ -41,9 +57,36 @@ use std::path::PathBuf; mod cache; mod theme; +/// Return the list of installed themes on the system +/// +/// ## Example +/// ```rust +/// # fn main() { +/// use freedesktop_icons::list_themes; +/// +/// let themes: Vec<&str> = list_themes(); +/// +/// assert_eq!(themes, vec![ +/// "Adwaita", "Arc", "Breeze Light", "HighContrast", "Papirus", "Papirus-Dark", +/// "Papirus-Light", "Breeze", "Breeze Dark", "Breeze", "ePapirus", "ePapirus-Dark", "Hicolor" +/// ]) +/// # } +pub fn list_themes() -> Vec<&'static str> { + THEMES + .values() + .map(|path| &path.index) + .filter_map(|index| { + index + .section(Some("Icon Theme")) + .and_then(|section| section.get("Name")) + }) + .collect() +} + /// The lookup builder struct, holding all the lookup query parameters. pub struct LookupBuilder<'a> { name: &'a str, + cache: bool, scale: u16, size: u16, theme: Option<&'a str>, @@ -110,6 +153,26 @@ impl<'a> LookupBuilder<'a> { self } + /// Store the result of the lookup in cache, subsequent + /// lookup will first try to retrieve get the cached icon. + /// This can drastically increase lookup performances for application + /// that repeat the same lookups, an application launcher for instance. + /// + /// ## Example + /// ```rust + /// # fn main() { + /// use freedesktop_icons::lookup; + /// + /// let icon = lookup("firefox") + /// .with_scale(2) + /// .with_cache() + /// .find(); + /// # } + pub fn with_cache(mut self) -> Self { + self.cache = true; + self + } + /// Execute the current lookup /// if no icon is found in the current theme fallback to /// `/usr/shar/hicolor` theme and then to `/usr/share/pixmaps`. @@ -132,6 +195,7 @@ impl<'a> LookupBuilder<'a> { fn new<'b: 'a>(name: &'b str) -> Self { Self { name, + cache: false, scale: 1, size: 24, theme: None, @@ -140,23 +204,45 @@ impl<'a> LookupBuilder<'a> { // Recursively lookup for icon in the given theme and its parents fn lookup_in_theme(&self, theme: &str) -> Option { - let cached = CACHE.get(theme, self.size, self.scale, self.name); - if cached.is_some() { - return cached; + // If cache is activated, attempt to get the icon there first + if self.cache { + let cached = self.cache_lookup(theme); + if cached.is_some() { + return cached; + } } + // Then lookup in the given theme THEMES.get(theme).and_then(|icon_theme| { - icon_theme + let icon = icon_theme .try_get_icon(self.name, self.size, self.scale) .or_else(|| { - icon_theme - .inherits() - .and_then(|parent| self.lookup_in_theme(parent)) - }) - .map(|icon| { - CACHE.insert(theme, self.size, self.scale, self.name, &icon); - icon - }) + // Fallback to the parent themes recursively + icon_theme.inherits().into_iter().find_map(|parent| { + THEMES.get(parent).and_then(|parent| { + parent.try_get_icon(self.name, self.size, self.scale) + }) + }) + }); + + if self.cache { + self.store(theme, icon) + } else { + icon + } + }) + } + + #[inline] + fn cache_lookup(&self, theme: &str) -> Option { + CACHE.get(theme, self.size, self.scale, self.name) + } + + #[inline] + fn store(&self, theme: &str, icon: Option) -> Option { + icon.map(|icon| { + CACHE.insert(theme, self.size, self.scale, self.name, &icon); + icon }) } } diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 84b0430..7e34a2c 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -18,8 +18,8 @@ pub static THEMES: Lazy> = Lazy::new(|| get_all_themes().expect("Failed to get theme paths")); pub struct Theme { - path: ThemePath, - index: Ini, + pub path: ThemePath, + pub index: Ini, } impl Theme { @@ -30,18 +30,15 @@ impl Theme { fn try_get_icon_exact_size(&self, name: &str, size: u16, scale: u16) -> Option { self.match_size(size, scale) - .iter() .find_map(|path| try_build_icon_path(name, path)) } - fn match_size(&self, size: u16, scale: u16) -> Vec { + fn match_size(&self, size: u16, scale: u16) -> impl Iterator + '_ { let dirs = self.get_all_directories(); - dirs.iter() - .filter(|directory| directory.match_size(size, scale)) + dirs.filter(move |directory| directory.match_size(size, scale)) .map(|dir| dir.name) .map(|dir| self.path().join(dir)) - .collect() } fn try_get_icon_closest_size(&self, name: &str, size: u16, scale: u16) -> Option { @@ -53,8 +50,7 @@ impl Theme { fn closest_match_size(&self, size: u16, scale: u16) -> Vec { let dirs = self.get_all_directories(); - dirs.iter() - .filter(|directory| directory.directory_size_distance(size, scale) < i16::MAX) + dirs.filter(|directory| directory.directory_size_distance(size, scale) < i16::MAX) .map(|dir| dir.name) .map(|dir| self.path().join(dir)) .collect() @@ -100,18 +96,6 @@ pub(super) fn get_all_themes() -> Result> { Ok(icon_themes) } -pub fn theme_names() -> Vec<&'static str> { - THEMES - .values() - .map(|path| &path.index) - .filter_map(|index| { - index - .section(Some("Icon Theme")) - .and_then(|section| section.get("Name")) - }) - .collect() -} - impl Theme { fn from_path>(path: P) -> Option { let path = path.as_ref(); diff --git a/src/theme/parse.rs b/src/theme/parse.rs index 55aa6e2..071e52c 100644 --- a/src/theme/parse.rs +++ b/src/theme/parse.rs @@ -3,11 +3,15 @@ use crate::theme::Theme; use ini::Properties; impl Theme { - pub(super) fn get_all_directories(&self) -> Vec { + pub(super) fn get_all_directories(&self) -> impl Iterator { self.directories() - .iter() + .into_iter() .filter_map(|name| self.get_directory(name)) - .collect() + .chain( + self.scaled_directories() + .into_iter() + .filter_map(|name| self.get_directory(name)), + ) } // TODO: use me @@ -22,9 +26,11 @@ impl Theme { self.index.section(Some("Icon Theme")) } - pub fn inherits(&self) -> Option<&str> { + pub fn inherits(&self) -> Vec<&str> { self.get_icon_theme_section() .and_then(|props| props.get("Inherits")) + .map(|parents| parents.split(',').collect()) + .unwrap_or(vec![]) } fn directories(&self) -> Vec<&str> { @@ -69,3 +75,23 @@ impl Theme { }) } } + +#[cfg(test)] +mod test { + use crate::THEMES; + use speculoos::prelude::*; + + #[test] + fn should_get_theme_parents() { + let theme = THEMES.get("Arc").unwrap(); + let parents = theme.inherits(); + assert_that!(parents).is_equal_to(vec![ + "Moka", + "Faba", + "elementary", + "Adwaita", + "gnome", + "hicolor", + ]); + } +}