feat: improve cache, add ScaledDirectories lookup and parent lookup

This commit is contained in:
Paul Delafosse 2022-05-13 08:00:52 +02:00
parent bc903c1bcb
commit 73702f6049
3 changed files with 133 additions and 37 deletions

View file

@ -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<PathBuf> {
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<PathBuf> {
CACHE.get(theme, self.size, self.scale, self.name)
}
#[inline]
fn store(&self, theme: &str, icon: Option<PathBuf>) -> Option<PathBuf> {
icon.map(|icon| {
CACHE.insert(theme, self.size, self.scale, self.name, &icon);
icon
})
}
}

View file

@ -18,8 +18,8 @@ pub static THEMES: Lazy<BTreeMap<String, Theme>> =
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<PathBuf> {
self.match_size(size, scale)
.iter()
.find_map(|path| try_build_icon_path(name, path))
}
fn match_size(&self, size: u16, scale: u16) -> Vec<PathBuf> {
fn match_size(&self, size: u16, scale: u16) -> impl Iterator<Item = PathBuf> + '_ {
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<PathBuf> {
@ -53,8 +50,7 @@ impl Theme {
fn closest_match_size(&self, size: u16, scale: u16) -> Vec<PathBuf> {
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<BTreeMap<String, Theme>> {
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<P: AsRef<Path>>(path: P) -> Option<Self> {
let path = path.as_ref();

View file

@ -3,11 +3,15 @@ use crate::theme::Theme;
use ini::Properties;
impl Theme {
pub(super) fn get_all_directories(&self) -> Vec<Directory> {
pub(super) fn get_all_directories(&self) -> impl Iterator<Item = Directory> {
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",
]);
}
}