diff --git a/benches/simple_lookup.rs b/benches/simple_lookup.rs index 8fd6c32..cbfb0b1 100644 --- a/benches/simple_lookup.rs +++ b/benches/simple_lookup.rs @@ -1,9 +1,8 @@ +use cosmic_freedesktop_icons::lookup; use criterion::{ - AxisScale, BenchmarkId, Criterion, PlotConfiguration, black_box, criterion_group, - criterion_main, + AxisScale, BenchmarkId, Criterion, PlotConfiguration, criterion_group, criterion_main, }; -use freedesktop_icons::lookup; -use gtk4::{IconLookupFlags, IconTheme, TextDirection}; +use std::hint::black_box; pub fn bench_lookups(c: &mut Criterion) { let mut group = c.benchmark_group("ComparisonsLookups"); @@ -11,21 +10,13 @@ pub fn bench_lookups(c: &mut Criterion) { group.plot_config(plot_config); let args = [ - "user-home", // (Best case) An icon that can be found in the current theme - "firefox", // An icon that can be found in the hicolor default theme - "archlinux-logo", // An icon that resides in /usr/share/pixmaps - "not-found", // (Worst case) An icon that does not exist + "user-home", // (Best case) An icon that can be found in the current theme + "firefox", // An icon that can be found in the hicolor default theme + "com.valvesoftware.Steam", // An icon that resides in /usr/share/pixmaps + "not-found", // (Worst case) An icon that does not exist ]; for arg in args { - group.bench_with_input(BenchmarkId::new("freedesktop-icons", arg), arg, |b, arg| { - b.iter(|| { - lookup(black_box(arg)) - .with_theme(black_box("Adwaita")) - .find() - }); - }); - group.bench_with_input( BenchmarkId::new("freedesktop-icons-cache", arg), arg, @@ -40,33 +31,6 @@ pub fn bench_lookups(c: &mut Criterion) { }); }, ); - - group.bench_with_input(BenchmarkId::new("linicon", arg), arg, |b, arg| { - b.iter(|| { - linicon::lookup_icon(black_box(arg)) - .from_theme(black_box("Adwaita")) - .with_scale(black_box(1)) - .with_size(black_box(24)) - .next() - }); - }); - - group.bench_with_input(BenchmarkId::new("gtk", arg), arg, |b, arg| { - gtk4::init().unwrap(); - let theme = IconTheme::new(); - b.iter(|| { - theme - .lookup_icon( - black_box(arg), - black_box(&[]), - black_box(24), - black_box(1), - black_box(TextDirection::None), - black_box(IconLookupFlags::empty()), - ) - .icon_name() - }); - }); } group.finish(); diff --git a/benches/tests.rs b/benches/tests.rs index 9c31812..5eaee3f 100644 --- a/benches/tests.rs +++ b/benches/tests.rs @@ -1,4 +1,4 @@ -use freedesktop_icons::lookup; +use cosmic_freedesktop_icons::lookup; use gtk4::{IconLookupFlags, IconTheme, TextDirection}; use speculoos::prelude::*; use std::path::PathBuf; diff --git a/src/cache.rs b/src/cache.rs index f5d7d19..cadf666 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,15 +1,17 @@ use std::collections::BTreeMap; use std::path::{Path, PathBuf}; -use std::sync::LazyLock; -use std::sync::Mutex; +use std::sync::{LazyLock, RwLock}; use std::time::Instant; pub(crate) static CACHE: LazyLock = LazyLock::new(Cache::default); -type IconMap = BTreeMap<(String, u16, u16), CacheEntry>; -type ThemeMap = BTreeMap; +type Theme = Box; +type Icon = Box; +type SizedMap = BTreeMap<(u16, u16), CacheEntry>; +type IconMap = BTreeMap; +type ThemeMap = BTreeMap; #[derive(Default)] -pub(crate) struct Cache(Mutex); +pub(crate) struct Cache(RwLock); #[derive(Debug, Clone, PartialEq)] pub enum CacheEntry { @@ -23,7 +25,7 @@ pub enum CacheEntry { impl Cache { pub fn clear(&self) { - self.0.lock().unwrap().clear(); + self.0.write().unwrap().clear(); } pub fn insert>( @@ -34,41 +36,38 @@ impl Cache { icon_name: &str, icon_path: &Option

, ) { - let mut theme_map = self.0.lock().unwrap(); + let mut inner = self.0.write().unwrap(); let entry = icon_path .as_ref() .map(|path| CacheEntry::Found(path.as_ref().to_path_buf())) .unwrap_or(CacheEntry::NotFound(Instant::now())); - match theme_map.get_mut(theme) { - Some(icon_map) => { - 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), entry); - theme_map.insert(theme.to_string(), icon_map); - } - } + inner + .entry(theme.into()) + .or_insert_with(IconMap::default) + .entry(icon_name.into()) + .or_insert_with(BTreeMap::default) + .insert((size, scale), entry); } pub fn get(&self, theme: &str, size: u16, scale: u16, icon_name: &str) -> CacheEntry { - let theme_map = self.0.lock().unwrap(); + let inner = self.0.read().unwrap(); - theme_map + inner .get(theme) - .map(|icon_map| icon_map.get(&(icon_name.to_string(), size, scale))) - .and_then(|path| path.cloned()) + .and_then(|icon_map| icon_map.get(icon_name)) + .and_then(|icon_map| icon_map.get(&(size, scale)).cloned()) .unwrap_or(CacheEntry::Unknown) } pub fn reset_none(&self) { - let mut theme_map = self.0.lock().unwrap(); - - for (_theme_name, theme) in theme_map.iter_mut() { - for (_icon_data, cached_icon) in theme.iter_mut() { - if matches!(cached_icon, CacheEntry::NotFound(_)) { - *cached_icon = CacheEntry::Unknown; + let mut inner = self.0.write().unwrap(); + for (_theme_name, theme) in inner.iter_mut() { + for (_, cached_icons) in theme.iter_mut() { + for (_, cached_icon) in cached_icons.iter_mut() { + if matches!(cached_icon, CacheEntry::NotFound(_)) { + *cached_icon = CacheEntry::Unknown; + } } } } diff --git a/src/lib.rs b/src/lib.rs index 9ebc3b0..551cc0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,11 +51,11 @@ //! .find(); //! # } //! ``` +use memmap2::Mmap; use theme::BASE_PATHS; use crate::cache::{CACHE, CacheEntry}; use crate::theme::{THEMES, Theme, try_build_icon_path}; -use std::collections::HashSet; use std::hash::{Hash, Hasher}; use std::io::BufRead; use std::path::PathBuf; @@ -84,8 +84,10 @@ pub fn list_themes() -> Vec { .flatten() .map(|path| &path.index) .filter_map(|index| { - let file = std::fs::File::open(index).ok()?; - let mut reader = std::io::BufReader::new(file); + let file = std::fs::File::open(index) + .and_then(|file| unsafe { Mmap::map(&file) }) + .ok()?; + let mut reader = std::io::Cursor::new(file.as_ref()); let mut line = String::new(); while let Ok(read) = reader.read_line(&mut line) { @@ -133,8 +135,10 @@ pub fn default_theme_gtk() -> Option { let name = name.trim().trim_matches('\''); THEMES.get(name).and_then(|themes| { themes.first().and_then(|path| { - let file = std::fs::File::open(&path.index).ok()?; - let mut reader = std::io::BufReader::new(file); + let file = std::fs::File::open(&path.index) + .and_then(|file| unsafe { Mmap::map(&file) }) + .ok()?; + let mut reader = std::io::Cursor::new(file.as_ref()); let mut line = String::new(); while let Ok(read) = reader.read_line(&mut line) { @@ -313,9 +317,9 @@ impl<'a> LookupBuilder<'a> { } // Records theme paths that have already been searched. - let searched_themes = &mut HashSet::new(); + let searched_themes = &mut Vec::new(); // Record themes whose inherits have been searched. - let search_inherits = &mut HashSet::new(); + let search_inherits = &mut Vec::new(); // Then lookup in the given theme THEMES @@ -332,14 +336,19 @@ impl<'a> LookupBuilder<'a> { self.search_theme_inherits(search_inherits, searched_themes, t) }) }) + // Search the cosmic icon theme + .or_else(|| self.search_inherited_theme(searched_themes, "Cosmic")) // Search the hicolor icon theme if it was not previously searched .or_else(|| self.search_inherited_theme(searched_themes, "hicolor")) + // GNOME applications may rely on the gnome theme + .or_else(|| self.search_inherited_theme(searched_themes, "gnome")) + // Ubuntu applications may require Yaru + .or_else(|| self.search_inherited_theme(searched_themes, "Yaru")) .or_else(|| { for theme_base_dir in BASE_PATHS.iter() { - if let Some(icon) = - try_build_icon_path(self.name, theme_base_dir, self.force_svg) - { - return Some(icon); + let mut path = theme_base_dir.clone(); + if try_build_icon_path(&mut path, self.name, self.force_svg) { + return Some(path); } } None @@ -347,7 +356,9 @@ impl<'a> LookupBuilder<'a> { .or_else(|| { let p = PathBuf::from(&self.name); if let (Some(name), Some(parent)) = (p.file_stem(), p.parent()) { - try_build_icon_path(&name.to_string_lossy(), parent, self.force_svg) + let mut path = parent.to_path_buf(); + try_build_icon_path(&mut path, &name.to_string_lossy(), self.force_svg) + .then_some(path) } else { None } @@ -383,7 +394,7 @@ impl<'a> LookupBuilder<'a> { } /// Search a theme by its path for a matching icon if not already searched. - fn search_theme(&self, searched_themes: &mut HashSet, theme: &Theme) -> Option { + fn search_theme(&self, searched_themes: &mut Vec, theme: &Theme) -> Option { // Store hash of the theme. let theme_hash = { let mut hasher = std::hash::DefaultHasher::new(); @@ -391,7 +402,8 @@ impl<'a> LookupBuilder<'a> { hasher.finish() }; - if searched_themes.insert(theme_hash) { + if let Err(pos) = searched_themes.binary_search(&theme_hash) { + searched_themes.insert(pos, theme_hash); return theme.try_get_icon(self.name, self.size, self.scale, self.force_svg); } @@ -401,8 +413,8 @@ impl<'a> LookupBuilder<'a> { // Search the inherits of a theme if not already searched. fn search_theme_inherits( &self, - search_inherits: &mut HashSet, - searched_themes: &mut HashSet, + search_inherits: &mut Vec, + searched_themes: &mut Vec, theme: &Theme, ) -> Option { // Store hash of the theme. @@ -412,7 +424,8 @@ impl<'a> LookupBuilder<'a> { hasher.finish() }; - if search_inherits.insert(theme_hash) { + if let Err(pos) = search_inherits.binary_search(&theme_hash) { + search_inherits.insert(pos, theme_hash); let Ok(file) = theme::read_ini_theme(&theme.index) else { return None; }; @@ -434,7 +447,7 @@ impl<'a> LookupBuilder<'a> { /// Search the inherits of a theme by its name if not already searched. fn search_inherited_theme( &self, - searched_themes: &mut HashSet, + searched_themes: &mut Vec, theme: &str, ) -> Option { THEMES @@ -490,7 +503,7 @@ mod test { #[test] fn cosmic_weather_storm_symbolic() { - let firefox = lookup("weather-storm-symbolic").with_theme("Cosmic").find(); + let firefox = lookup("weather-storm-symbolic").find(); asserting!("Is the cosmic icon theme installed?") .that(&firefox) @@ -502,10 +515,7 @@ mod test { #[test] fn cosmic_weather_storm_symbolic_force_svg() { - let firefox = lookup("weather-storm-symbolic") - .with_theme("Cosmic") - .force_svg() - .find(); + let firefox = lookup("weather-storm-symbolic").force_svg().find(); asserting!("Is the cosmic icon theme installed?") .that(&firefox) @@ -528,6 +538,46 @@ mod test { ); } + #[test] + fn vscode_pixmap() { + assert_eq!( + lookup("vscode").find(), + Some(PathBuf::from("/usr/share/pixmaps/vscode.png")), + "Is VS Code installed locally on the host?" + ); + } + + #[test] + fn libreoffice_startcenter() { + assert_eq!( + lookup("libreoffice-startcenter").find(), + Some(PathBuf::from( + "/usr/share/icons/hicolor/24x24/apps/libreoffice-startcenter.png" + )), + "Is libreoffice installed locally on the host?" + ); + } + + #[test] + fn gnome_advanced_network() { + assert_eq!( + lookup("preferences-system-network").find(), + Some(PathBuf::from( + "/usr/share/icons/gnome/24x24/categories/preferences-system-network.png" + )), + "Is the gnome icon theme installed?" + ); + } + + #[test] + fn ubuntu_additional_drivers() { + assert_eq!( + lookup("jockey").find(), + Some(PathBuf::from("/usr/share/icons/Yaru/24x24/apps/jockey.png")), + "Is the gnome icon theme installed?" + ); + } + #[test] #[cfg(feature = "local_tests")] fn theme_lookup() { diff --git a/src/theme/directories.rs b/src/theme/directories.rs index c437d31..33135a1 100644 --- a/src/theme/directories.rs +++ b/src/theme/directories.rs @@ -28,29 +28,29 @@ impl Directory<'_> { } pub fn directory_size_distance(&self, size: u16, scale: u16) -> i16 { - let scaled_size = self.size * self.scale; - let min_scaled_size = self.minsize * self.scale; - let max_scaled_size = self.maxsize * self.scale; let scale = scale as i16; - let size = size as i16; - let scaled_requested_size = size * scale; + let scaled_requested_size = size as i16 * scale; match self.type_ { - DirectoryType::Fixed => scaled_size - scaled_requested_size, + DirectoryType::Fixed => self.size * self.scale - scaled_requested_size, DirectoryType::Scalable => { + let min_scaled_size = self.minsize * self.scale; if scaled_requested_size < min_scaled_size { min_scaled_size - scaled_requested_size - } else if scaled_requested_size < max_scaled_size { - scaled_requested_size - max_scaled_size } else { - 0 + let max_scaled_size = self.maxsize * self.scale; + if scaled_requested_size < max_scaled_size { + scaled_requested_size - max_scaled_size + } else { + 0 + } } } DirectoryType::Threshold => { if scaled_requested_size < (self.size - self.threshold) * scale { - min_scaled_size - scaled_requested_size + self.minsize * self.scale - scaled_requested_size } else if scaled_requested_size > (self.size + self.threshold) * scale { - scaled_requested_size - max_scaled_size + scaled_requested_size - self.maxsize * self.scale } else { 0 } @@ -74,9 +74,9 @@ impl Default for DirectoryType { impl From<&str> for DirectoryType { fn from(value: &str) -> Self { - match value { - "Fixed" => DirectoryType::Fixed, - "Scalable" => DirectoryType::Scalable, + match value.as_bytes()[0] { + b'F' => DirectoryType::Fixed, + b'S' => DirectoryType::Scalable, _ => DirectoryType::Threshold, } } diff --git a/src/theme/error.rs b/src/theme/error.rs deleted file mode 100644 index 5ca693c..0000000 --- a/src/theme/error.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::io; -use std::path::PathBuf; -use thiserror::Error; - -#[derive(Error, Debug)] -pub(crate) enum ThemeError { - #[error("No 'index.theme' file for {0}")] - ThemeIndexNotFound(PathBuf), - #[error("IoError: {0}")] - IoError(#[from] io::Error), -} diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 5e0b1a8..7af9c80 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -1,18 +1,15 @@ -use crate::theme::error::ThemeError; use crate::theme::paths::ThemePath; use memmap2::Mmap; pub(crate) use paths::BASE_PATHS; use std::collections::BTreeMap; +use std::ops::ControlFlow; use std::path::{Path, PathBuf}; use std::sync::LazyLock; mod directories; -pub mod error; mod parse; mod paths; -type Result = std::result::Result; - pub static THEMES: LazyLock>> = LazyLock::new(get_all_themes); #[inline] @@ -50,8 +47,7 @@ impl Theme { scale: u16, force_svg: bool, ) -> Option { - self.match_size(file, size, scale) - .find_map(|path| try_build_icon_path(name, path, force_svg)) + self.try_fold_icon_path(self.match_size(file, size, scale), name, force_svg) } #[inline] @@ -60,12 +56,10 @@ impl Theme { file: &'a str, size: u16, scale: u16, - ) -> impl Iterator + 'a { - let dirs = self.get_all_directories(file); - - dirs.filter(move |directory| directory.match_size(size, scale)) + ) -> impl Iterator + 'a { + self.get_all_directories(file) + .filter(move |directory| directory.match_size(size, scale)) .map(|dir| dir.name) - .map(|dir| self.path().join(dir)) } #[inline] @@ -77,32 +71,59 @@ impl Theme { scale: u16, force_svg: bool, ) -> Option { - self.closest_match_size(file, size, scale) - .iter() - .find_map(|path| try_build_icon_path(name, path, force_svg)) + self.try_fold_icon_path(self.closest_match_size(file, size, scale), name, force_svg) } - fn closest_match_size(&self, file: &str, size: u16, scale: u16) -> Vec { - let dirs = self.get_all_directories(file); - - let mut dirs: Vec<_> = dirs - .filter_map(|directory| { - let distance = directory.directory_size_distance(size, scale); - if distance < i16::MAX { - Some((directory, distance.abs())) + fn try_fold_icon_path<'a>( + &self, + mut dir_names: impl Iterator, + name: &str, + force_svg: bool, + ) -> Option { + dir_names + .try_fold(self.path().clone(), move |mut path, dir_name| { + path.push(dir_name); + if try_build_icon_path(&mut path, name, force_svg) { + ControlFlow::Break(path) } else { - None + let components = dir_name + .as_bytes() + .iter() + .fold(2, |n, c| n + (*c == b'/') as u32) + as usize; + + for _ in 0..components { + path.pop(); + } + + ControlFlow::Continue(path) } }) - .collect(); + .break_value() + } - dirs.sort_by(|(_, a), (_, b)| a.cmp(b)); + fn closest_match_size<'a>( + &'a self, + file: &'a str, + size: u16, + scale: u16, + ) -> impl Iterator + 'a { + let dirs = self.get_all_directories(file); - dirs.iter() - .map(|(dir, _)| dir) - .map(|dir| dir.name) - .map(|dir| self.path().join(dir)) - .collect() + dirs.fold(Vec::<(&'a str, i16)>::new(), |mut sorted, directory| { + let distance = directory.directory_size_distance(size, scale); + if distance < i16::MAX { + let a = distance.abs(); + let pos = sorted + .binary_search_by(|(_, b)| b.cmp(&a)) + .unwrap_or_else(|pos| pos); + sorted.insert(pos, (directory.name, a)); + } + + sorted + }) + .into_iter() + .map(|(name, _)| name) } fn path(&self) -> &PathBuf { @@ -110,40 +131,27 @@ impl Theme { } } -pub(super) fn try_build_icon_path>( - name: &str, - path: P, - force_svg: bool, -) -> Option { +pub(super) fn try_build_icon_path<'a>(path: &'a mut PathBuf, name: &str, force_svg: bool) -> bool { + let mut name_buf = String::with_capacity(name.len() + 4); + name_buf.push_str(name); + path.push(name); if force_svg { - try_build_svg(name, path.as_ref()) - .or_else(|| try_build_png(name, path.as_ref())) - .or_else(|| try_build_xmp(name, path.as_ref())) + try_build_ext(path, &mut name_buf, name, ".svg") + || try_build_ext(path, &mut name_buf, name, ".png") + || try_build_ext(path, &mut name_buf, name, ".xmp") } else { - try_build_png(name, path.as_ref()) - .or_else(|| try_build_svg(name, path.as_ref())) - .or_else(|| try_build_xmp(name, path.as_ref())) + try_build_ext(path, &mut name_buf, name, ".png") + || try_build_ext(path, &mut name_buf, name, ".svg") + || try_build_ext(path, &mut name_buf, name, ".xmp") } } -fn try_build_svg>(name: &str, path: P) -> Option { - let path = path.as_ref(); - let svg = path.join(format!("{name}.svg")); - - if svg.exists() { Some(svg) } else { None } -} - -fn try_build_png>(name: &str, path: P) -> Option { - let path = path.as_ref(); - let png = path.join(format!("{name}.png")); - - if png.exists() { Some(png) } else { None } -} - -fn try_build_xmp>(name: &str, path: P) -> Option { - let path = path.as_ref(); - let xmp = path.join(format!("{name}.xmp")); - if xmp.exists() { Some(xmp) } else { None } +#[inline] +fn try_build_ext(path: &mut PathBuf, name_buf: &mut String, name: &str, ext: &'static str) -> bool { + name_buf.truncate(name.len()); + name_buf.push_str(ext); + path.set_file_name(&name_buf); + path.exists() } // Iter through the base paths and get all theme directories @@ -180,8 +188,9 @@ pub(super) fn get_all_themes() -> BTreeMap> { let name = entry.file_name(); let fallback_index = found_indices.get(&name); if let Some(theme) = Theme::from_path(entry.path(), fallback_index) { - let name = name.to_string_lossy().to_string(); - icon_themes.entry(name).or_default().push(theme); + if let Some(name) = name.to_str() { + icon_themes.entry(name.to_owned()).or_default().push(theme); + } } } @@ -190,24 +199,26 @@ pub(super) fn get_all_themes() -> BTreeMap> { impl Theme { pub(crate) fn from_path>(path: P, index: Option<&PathBuf>) -> Option { - let path = path.as_ref(); + let mut path = path.as_ref().to_path_buf(); + let is_dir = path.is_dir(); + path.push("index.theme"); + let local_index_exists = path.exists(); + let has_index = local_index_exists || index.is_some(); - let has_index = path.join("index.theme").exists() || index.is_some(); - - if !has_index || !path.is_dir() { + if !has_index || !is_dir { return None; } - let path = ThemePath(path.into()); - - match (index, path.index()) { - (Some(index), _) => Some(Theme { - path, - index: index.clone(), - }), - (None, Ok(index)) => Some(Theme { path, index }), - _ => None, - } + index + .cloned() + .or_else(|| local_index_exists.then_some(path.clone())) + .map(|index| Theme { + path: ThemePath({ + path.pop(); + path + }), + index, + }) } } diff --git a/src/theme/parse.rs b/src/theme/parse.rs index 64db96e..25787f4 100644 --- a/src/theme/parse.rs +++ b/src/theme/parse.rs @@ -105,27 +105,26 @@ impl Theme { }) } - pub fn inherits<'a>(&self, file: &'a str) -> Vec<&'a str> { + pub fn inherits<'a>(&self, file: &'a str) -> impl Iterator { icon_theme_section(file) .find(|&(key, _)| key == "Inherits") - .map(|(_, parents)| { + .into_iter() + .flat_map(|(_, parents)| { parents .split(',') // Filtering out 'hicolor' since we are going to fallback there anyway .filter(|parent| parent != &"hicolor") - .collect() }) - .unwrap_or_default() } } #[cfg(test)] +#[cfg(feature = "local_tests")] mod test { use crate::THEMES; use speculoos::prelude::*; #[test] - #[cfg(feature = "local_tests")] fn should_get_theme_parents() { for theme in THEMES.get("Arc").unwrap() { let file = crate::theme::read_ini_theme(&theme.index).ok().unwrap(); diff --git a/src/theme/paths.rs b/src/theme/paths.rs index c4581e6..c446c58 100644 --- a/src/theme/paths.rs +++ b/src/theme/paths.rs @@ -3,48 +3,35 @@ use std::path::PathBuf; use std::sync::LazyLock; use xdg::BaseDirectories; -use crate::theme; -use crate::theme::error::ThemeError; - pub(crate) static BASE_PATHS: LazyLock> = LazyLock::new(icon_theme_base_paths); /// Look in $HOME/.icons (for backwards compatibility), in $XDG_DATA_DIRS/icons, in $XDG_DATA_DIRS/pixmaps and in /usr/share/pixmaps (in that order). /// Paths that are not found are filtered out. fn icon_theme_base_paths() -> Vec { let base_dirs = BaseDirectories::new(); - let mut data_dirs: Vec<_> = base_dirs + + let data_dirs = base_dirs .get_data_dirs() .into_iter() - .flat_map(|p| [p.join("icons"), p.join("pixmaps")]) - .collect(); + .flat_map(|p| [p.join("icons"), p.join("pixmaps")]); - if let Some(data_home) = base_dirs.get_data_home() { - data_dirs.push(data_home.join("icons")); - data_dirs.push(data_home.join("pixmaps")); - } + let data_home_dirs = base_dirs + .get_data_home() + .into_iter() + .flat_map(|data_home| [data_home.join("icons"), data_home.join("pixmaps")].into_iter()); - match home_dir().map(|home| home.join(".icons")) { - Some(home_icon_dir) => data_dirs.push(home_icon_dir), - None => tracing::warn!("No $HOME directory found"), - } - data_dirs.into_iter().filter(|p| p.exists()).collect() + let home_dir = home_dir().into_iter().map(|home| home.join(".icons")); + + data_dirs + .chain(data_home_dirs) + .chain(home_dir) + .filter(|p| p.exists()) + .collect() } #[derive(Clone, Debug)] pub struct ThemePath(pub PathBuf); -impl ThemePath { - pub(super) fn index(&self) -> theme::Result { - let index = self.0.join("index.theme"); - - if !index.exists() { - return Err(ThemeError::ThemeIndexNotFound(index)); - } - - Ok(index) - } -} - #[cfg(test)] mod test { use crate::theme::paths::icon_theme_base_paths;