feat: use CacheEntry enum to abort previously failed lookups
This commit is contained in:
parent
058625b249
commit
ab3d5a67f2
5 changed files with 115 additions and 64 deletions
38
src/cache.rs
38
src/cache.rs
|
|
@ -4,40 +4,56 @@ use std::path::{Path, PathBuf};
|
|||
use std::sync::Mutex;
|
||||
|
||||
pub(crate) static CACHE: Lazy<Cache> = Lazy::new(Cache::default);
|
||||
type IconMap = BTreeMap<(String, u16, u16), PathBuf>;
|
||||
type IconMap = BTreeMap<(String, u16, u16), CacheEntry>;
|
||||
type ThemeMap = BTreeMap<String, IconMap>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Cache(Mutex<ThemeMap>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CacheEntry {
|
||||
// We already looked for this and nothing was found, indicates we should not try to perform a lookup.
|
||||
NotFound,
|
||||
// We have this entry.
|
||||
Found(PathBuf),
|
||||
// We don't know this entry yet, indicate we should perform a lookup.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn insert(&self, theme: &str, size: u16, scale: u16, icon_name: &str, icon_path: &Path) {
|
||||
pub fn insert<P: AsRef<Path>>(
|
||||
&self,
|
||||
theme: &str,
|
||||
size: u16,
|
||||
scale: u16,
|
||||
icon_name: &str,
|
||||
icon_path: &Option<P>,
|
||||
) {
|
||||
let mut theme_map = self.0.lock().unwrap();
|
||||
let entry = icon_path
|
||||
.as_ref()
|
||||
.map(|path| CacheEntry::Found(path.as_ref().to_path_buf()))
|
||||
.unwrap_or(CacheEntry::NotFound);
|
||||
|
||||
match theme_map.get_mut(theme) {
|
||||
Some(icon_map) => {
|
||||
icon_map.insert(
|
||||
(icon_name.to_string(), size, scale),
|
||||
icon_path.to_path_buf(),
|
||||
);
|
||||
icon_map.insert((icon_name.to_string(), size, scale), entry);
|
||||
}
|
||||
None => {
|
||||
let mut icon_map = BTreeMap::new();
|
||||
icon_map.insert(
|
||||
(icon_name.to_string(), size, scale),
|
||||
icon_path.to_path_buf(),
|
||||
);
|
||||
icon_map.insert((icon_name.to_string(), size, scale), entry);
|
||||
theme_map.insert(theme.to_string(), icon_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, theme: &str, size: u16, scale: u16, icon_name: &str) -> Option<PathBuf> {
|
||||
pub fn get(&self, theme: &str, size: u16, scale: u16, icon_name: &str) -> CacheEntry {
|
||||
let theme_map = self.0.lock().unwrap();
|
||||
|
||||
theme_map
|
||||
.get(theme)
|
||||
.map(|icon_map| icon_map.get(&(icon_name.to_string(), size, scale)))
|
||||
.and_then(|path| path.cloned())
|
||||
.unwrap_or(CacheEntry::Unknown)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
111
src/lib.rs
111
src/lib.rs
|
|
@ -50,7 +50,7 @@
|
|||
//! .with_cache()
|
||||
//! .find();
|
||||
//! # }
|
||||
use crate::cache::CACHE;
|
||||
use crate::cache::{CacheEntry, CACHE};
|
||||
use crate::theme::{try_build_icon_path, THEMES};
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ pub struct LookupBuilder<'a> {
|
|||
cache: bool,
|
||||
scale: u16,
|
||||
size: u16,
|
||||
theme: Option<&'a str>,
|
||||
theme: &'a str,
|
||||
}
|
||||
|
||||
/// Build an icon lookup for the given icon name.
|
||||
|
|
@ -149,7 +149,7 @@ impl<'a> LookupBuilder<'a> {
|
|||
/// .find();
|
||||
/// # }
|
||||
pub fn with_theme<'b: 'a>(mut self, theme: &'b str) -> Self {
|
||||
self.theme = Some(theme);
|
||||
self.theme = theme;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -178,18 +178,7 @@ impl<'a> LookupBuilder<'a> {
|
|||
/// `/usr/shar/hicolor` theme and then to `/usr/share/pixmaps`.
|
||||
pub fn find(self) -> Option<PathBuf> {
|
||||
// Lookup for an icon in the given theme and fallback to 'hicolor' default theme
|
||||
let icon = self
|
||||
.theme
|
||||
.and_then(|theme| self.lookup_in_theme(theme))
|
||||
.or_else(|| self.lookup_in_theme("hicolor"));
|
||||
|
||||
// Return the icon if found
|
||||
if icon.is_some() {
|
||||
return icon;
|
||||
};
|
||||
|
||||
// Last chance
|
||||
try_build_icon_path(self.name, "/usr/share/pixmaps")
|
||||
self.lookup_in_theme()
|
||||
}
|
||||
|
||||
fn new<'b: 'a>(name: &'b str) -> Self {
|
||||
|
|
@ -198,22 +187,29 @@ impl<'a> LookupBuilder<'a> {
|
|||
cache: false,
|
||||
scale: 1,
|
||||
size: 24,
|
||||
theme: None,
|
||||
theme: "hicolor",
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively lookup for icon in the given theme and its parents
|
||||
fn lookup_in_theme(&self, theme: &str) -> Option<PathBuf> {
|
||||
fn lookup_in_theme(&self) -> Option<PathBuf> {
|
||||
// If cache is activated, attempt to get the icon there first
|
||||
// If the icon was previously search but not found, we return
|
||||
// `None` early, otherwise, attempt to perform a lookup
|
||||
if self.cache {
|
||||
let cached = self.cache_lookup(theme);
|
||||
if cached.is_some() {
|
||||
return cached;
|
||||
}
|
||||
match self.cache_lookup(self.theme) {
|
||||
CacheEntry::Found(icon) => {
|
||||
return Some(icon);
|
||||
}
|
||||
CacheEntry::NotFound => {
|
||||
return None;
|
||||
}
|
||||
CacheEntry::Unknown => {}
|
||||
};
|
||||
}
|
||||
|
||||
// Then lookup in the given theme
|
||||
THEMES.get(theme).and_then(|icon_theme| {
|
||||
THEMES.get(self.theme).and_then(|icon_theme| {
|
||||
let icon = icon_theme
|
||||
.try_get_icon(self.name, self.size, self.scale)
|
||||
.or_else(|| {
|
||||
|
|
@ -223,10 +219,18 @@ impl<'a> LookupBuilder<'a> {
|
|||
parent.try_get_icon(self.name, self.size, self.scale)
|
||||
})
|
||||
})
|
||||
});
|
||||
})
|
||||
.or_else(|| {
|
||||
THEMES
|
||||
.get("hicolor")
|
||||
// Fallback to 'hicolor'
|
||||
.and_then(|hicolor| hicolor.try_get_icon(self.name, self.size, self.scale))
|
||||
})
|
||||
// Last chance, try to find the icon in "/usr/share/pixmaps"
|
||||
.or_else(|| try_build_icon_path(self.name, "/usr/share/pixmaps"));
|
||||
|
||||
if self.cache {
|
||||
self.store(theme, icon)
|
||||
self.store(self.theme, icon)
|
||||
} else {
|
||||
icon
|
||||
}
|
||||
|
|
@ -234,22 +238,22 @@ impl<'a> LookupBuilder<'a> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn cache_lookup(&self, theme: &str) -> Option<PathBuf> {
|
||||
fn cache_lookup(&self, theme: &str) -> CacheEntry {
|
||||
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
|
||||
})
|
||||
CACHE.insert(theme, self.size, self.scale, self.name, &icon);
|
||||
icon
|
||||
}
|
||||
}
|
||||
|
||||
// WARNING: these test are highly dependent on your installed icon-themes.
|
||||
// If you want to run them, make sure you have 'Papirus' and 'Arc' icon-themes installed.
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::lookup;
|
||||
use crate::{lookup, CacheEntry, CACHE};
|
||||
use speculoos::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -266,7 +270,19 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn should_fallback() {
|
||||
fn theme_lookup() {
|
||||
let firefox = lookup("firefox").with_theme("Papirus").find();
|
||||
|
||||
asserting!("Lookup with no parameters should return an existing icon")
|
||||
.that(&firefox)
|
||||
.is_some()
|
||||
.is_equal_to(PathBuf::from(
|
||||
"/usr/share/icons/Papirus/24x24/apps/firefox.svg",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fallback_to_parent_theme() {
|
||||
let icon = lookup("video-single-display-symbolic")
|
||||
.with_theme("Arc")
|
||||
.find();
|
||||
|
|
@ -279,6 +295,20 @@ mod test {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fallback_to_pixmaps_utlimately() {
|
||||
let archlinux_logo = lookup("archlinux-logo")
|
||||
.with_size(16)
|
||||
.with_scale(1)
|
||||
.with_theme("Papirus")
|
||||
.find();
|
||||
|
||||
asserting!("When lookup fail in theme, icon should be found in '/usr/share/pixmaps'")
|
||||
.that(&archlinux_logo)
|
||||
.is_some()
|
||||
.is_equal_to(PathBuf::from("/usr/share/pixmaps/archlinux-logo.png"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compare_to_linincon_with_theme() {
|
||||
let lin_wireshark = linicon::lookup_icon("wireshark")
|
||||
|
|
@ -300,16 +330,15 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn should_fallback_to_pixmaps_utlimately() {
|
||||
let archlinux_logo = lookup("archlinux-logo")
|
||||
.with_size(16)
|
||||
.with_scale(1)
|
||||
.with_theme("Papirus")
|
||||
.find();
|
||||
fn should_not_attempt_to_lookup_a_not_found_cached_icon() {
|
||||
let not_found = lookup("not-found").with_cache().find();
|
||||
|
||||
asserting!("When lookup fail in theme, icon should be found in '/usr/share/pixmaps'")
|
||||
.that(&archlinux_logo)
|
||||
.is_some()
|
||||
.is_equal_to(PathBuf::from("/usr/share/pixmaps/archlinux-logo.png"));
|
||||
assert_that!(not_found).is_none();
|
||||
|
||||
let expected_cache_result = CACHE.get("hicolor", 24, 1, "not-found");
|
||||
|
||||
asserting!("When lookup fails a first time, subsequent attempts should fail from cache")
|
||||
.that(&expected_cache_result)
|
||||
.is_equal_to(CacheEntry::NotFound);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,13 @@ impl Theme {
|
|||
pub fn inherits(&self) -> Vec<&str> {
|
||||
self.get_icon_theme_section()
|
||||
.and_then(|props| props.get("Inherits"))
|
||||
.map(|parents| parents.split(',').collect())
|
||||
.map(|parents| {
|
||||
parents
|
||||
.split(',')
|
||||
// Filtering out 'hicolor' since we are going to fallback there anyway
|
||||
.filter(|parent| parent != &"hicolor")
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
|
@ -84,13 +90,9 @@ mod 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",
|
||||
]);
|
||||
|
||||
assert_that!(parents).does_not_contain("hicolor");
|
||||
|
||||
assert_that!(parents).is_equal_to(vec!["Moka", "Faba", "elementary", "Adwaita", "gnome"]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue