improved caching for icons

This commit is contained in:
Daniel Bulant 2025-09-26 13:16:49 +02:00
parent 49f93a9c3e
commit 5d382f8110
No known key found for this signature in database
2 changed files with 71 additions and 26 deletions

View file

@ -1,18 +1,24 @@
use std::path::{Path, PathBuf}; use std::{
ffi::OsString,
path::{Path, PathBuf},
};
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct IconFile { pub struct IconFile {
pub path: PathBuf, pub path: PathBuf,
pub name: OsString,
pub file_type: FileType, pub file_type: FileType,
} }
impl IconFile { impl IconFile {
pub fn from_path(path: &Path) -> Option<IconFile> { pub fn from_path(path: &Path) -> Option<IconFile> {
let file_type = FileType::from_path_ext(path)?; let file_type = FileType::from_path_ext(path)?;
let name = path.file_stem()?.to_os_string();
Some(IconFile { Some(IconFile {
path: path.to_owned(), path: path.to_owned(),
file_type, file_type,
name,
}) })
} }
} }

View file

@ -5,7 +5,7 @@ use freedesktop_entry_parser::low_level::{EntryIter, SectionBytes};
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::{Arc, RwLock};
/// Main struct to locate icon files. /// Main struct to locate icon files.
/// ///
@ -84,6 +84,16 @@ impl Icons {
pub fn find_standalone_icon(&self, icon_name: &str) -> Option<IconFile> { pub fn find_standalone_icon(&self, icon_name: &str) -> Option<IconFile> {
self.standalone_icons.get(icon_name).cloned() self.standalone_icons.get(icon_name).cloned()
} }
/// Reset all caches in all loaded themes.
///
/// This is useful if you know that icons have changed on disk and want to force a reload,
/// or when benchmarking.
pub fn reset_cache(&self) {
for theme in self.themes.values() {
theme.reset_cache();
}
}
} }
impl Default for Icons { impl Default for Icons {
@ -111,11 +121,12 @@ impl Theme {
}) })
} }
pub fn reset_cache(&self) {
self.info.index.reset_cache();
}
// find an icon in this theme only, not checking parents. // find an icon in this theme only, not checking parents.
fn find_icon_here(&self, icon_name: &str, size: u32, scale: u32) -> Option<IconFile> { fn find_icon_here(&self, icon_name: &str, size: u32, scale: u32) -> Option<IconFile> {
const EXTENSIONS: [&str; 3] = ["png", "xmp", "svg"];
let file_names = EXTENSIONS.map(|ext| format!("{icon_name}.{ext}"));
let base_dirs = &self.info.base_dirs; let base_dirs = &self.info.base_dirs;
let sub_dirs = &self.info.index.directories; let sub_dirs = &self.info.index.directories;
@ -126,17 +137,9 @@ impl Theme {
for base_dir in base_dirs { for base_dir in base_dirs {
for sub_dir in exact_sub_dirs.clone() { for sub_dir in exact_sub_dirs.clone() {
for file_name in &file_names { if let Some(file) = sub_dir.lookup_icon(base_dir, icon_name) {
let path = base_dir // exact match!
.join(sub_dir.directory_name.as_str()) return Some(file);
.join(file_name);
if path.exists() {
if let Some(file) = IconFile::from_path(&path) {
// exact match!
return Some(file);
}
}
} }
} }
} }
@ -152,16 +155,9 @@ impl Theme {
let distance = sub_dir.size_distance(size, scale); let distance = sub_dir.size_distance(size, scale);
if distance < min_dist { if distance < min_dist {
for file_name in &file_names { if let Some(file) = sub_dir.lookup_icon(base_dir, icon_name) {
let path = base_dir min_dist = distance;
.join(sub_dir.directory_name.as_str()) best_icon = Some(file);
.join(file_name);
if path.exists() {
if let Some(file) = IconFile::from_path(&path) {
min_dist = distance;
best_icon = Some(file);
}
}
} }
} }
} }
@ -233,6 +229,12 @@ impl ThemeIndex {
Ok(index) Ok(index)
} }
pub fn reset_cache(&self) {
for dir in &self.directories {
dir.reset_cache();
}
}
pub fn parse(bytes: &[u8]) -> Result<Self, ThemeParseError> { pub fn parse(bytes: &[u8]) -> Result<Self, ThemeParseError> {
let mut entry: EntryIter = freedesktop_entry_parser::low_level::parse_entry(bytes); let mut entry: EntryIter = freedesktop_entry_parser::low_level::parse_entry(bytes);
@ -310,6 +312,9 @@ pub struct DirectoryIndex {
pub max_size: u32, pub max_size: u32,
pub min_size: u32, pub min_size: u32,
pub threshold: u32, pub threshold: u32,
/// Used as cache during icon lookups
pub known_icons: RwLock<Vec<IconFile>>,
pub known_basedirs: RwLock<Vec<PathBuf>>,
// pub additional_values: HashMap<String, String>, // pub additional_values: HashMap<String, String>,
} }
@ -353,9 +358,43 @@ impl DirectoryIndex {
max_size, max_size,
min_size, min_size,
threshold, threshold,
known_icons: Default::default(),
known_basedirs: Default::default(),
}) })
} }
fn reset_cache(&self) {
self.known_icons.write().unwrap().clear();
self.known_basedirs.write().unwrap().clear();
}
fn load_cache(&self, base_dir: &PathBuf) {
if self.known_basedirs.read().unwrap().contains(base_dir) {
return; // already loaded for this base dir
}
self.known_basedirs.write().unwrap().push(base_dir.clone());
let dir = base_dir.join(&self.directory_name);
if dir.is_dir() {
let icons = &mut *self.known_icons.write().unwrap();
for entry in dir.read_dir().unwrap() {
let entry = entry.unwrap();
if entry.file_type().unwrap().is_file() {
if let Some(icon) = IconFile::from_path(&entry.path()) {
icons.push(icon);
}
}
}
}
}
fn lookup_icon(&self, base_dir: &PathBuf, icon_name: &str) -> Option<IconFile> {
self.load_cache(base_dir);
let icons = self.known_icons.read().unwrap();
icons.iter().find(|ico| ico.name == icon_name).cloned()
}
fn size_distance(&self, icon_size: u32, icon_scale: u32) -> u32 { fn size_distance(&self, icon_size: u32, icon_scale: u32) -> u32 {
let size = icon_size * icon_scale; let size = icon_size * icon_scale;