From e4ffec63a13f3449c6c3e5c2cf630a859933393c Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 3 Jul 2025 12:23:48 -0400 Subject: [PATCH] refactor: apply limit on the number of persisted sort names --- Cargo.lock | 11 +++++++++++ Cargo.toml | 5 +++-- src/app.rs | 51 +++++++++++++++++++++++++++++++++++++++++---------- src/config.rs | 5 +++-- src/tab.rs | 5 +++-- 5 files changed, 61 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70f3ba9..3edacc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1512,6 +1512,7 @@ dependencies = [ "notify-rust", "once_cell", "open", + "ordermap", "paste", "procfs", "recently-used-xbel", @@ -5326,6 +5327,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ordermap" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6bff06e4a5dc6416bead102d3e63c480dd852ffbb278bf8cfeb4966b329609" +dependencies = [ + "indexmap 2.10.0", + "serde", +] + [[package]] name = "os_pipe" version = "1.2.2" diff --git a/Cargo.toml b/Cargo.toml index 036ccfe..051bbab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,10 +50,11 @@ xdg = { version = "2.5.2", optional = true } # https://github.com/ebassi/xdg-mime-rs/pull/31 xdg-mime = { git = "https://github.com/ellieplayswow/xdg-mime-rs", branch = "feature/get-same-as" } # Compression -bzip2 = { version = "0.5", optional = true } #TODO: replace with pure Rust crate +bzip2 = { version = "0.5", optional = true } #TODO: replace with pure Rust crate flate2 = "1.0" tar = "0.4.43" -xz2 = { version = "0.1", optional = true } #TODO: replace with pure Rust crate +xz2 = { version = "0.1", optional = true } #TODO: replace with pure Rust crate +ordermap = { version = "0.5.8", features = ["serde"] } # Internationalization i18n-embed = { version = "0.15", features = [ "fluent-system", diff --git a/src/app.rs b/src/app.rs index 4ab0e73..d3849c8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -86,7 +86,9 @@ use crate::{ ReplaceResult, }, spawn_detached::spawn_detached, - tab::{self, HeadingOptions, ItemMetadata, Location, Tab, HOVER_DURATION}, + tab::{ + self, HeadingOptions, ItemMetadata, Location, Tab, HOVER_DURATION, SORT_OPTION_FALLBACK, + }, }; use crate::{config::State, dialog::DialogSettings}; @@ -3602,10 +3604,35 @@ impl Application for App { } } tab::Command::SetSort(location, heading_options, direction) => { - self.state - .sort_names - .insert(location, (heading_options, direction)); - if !self.must_save_sort_names { + let default_sort = tab::SORT_OPTION_FALLBACK + .get(&location) + .cloned() + .unwrap_or((HeadingOptions::Name, true)); + let changed = if default_sort == (heading_options, direction) { + self.state.sort_names.remove(&location).is_some() + } else { + // force reordering of inserted values so new settings are not dropped in the truncation step + _ = self.state.sort_names.remove(&location); + _ = self + .state + .sort_names + .insert(location, (heading_options, direction)) + .is_none_or(|old| old != (heading_options, direction)); + + const MAX_SORT_NAMES: usize = 999; + // TODO potentially configurable limit on max size? + if self.state.sort_names.len() > MAX_SORT_NAMES { + // truncate is not a good fit because it drops the items at the end, which are newest... + self.state.sort_names = self + .state + .sort_names + .split_off(self.state.sort_names.len() - MAX_SORT_NAMES); + } + + true + }; + + if !self.must_save_sort_names & changed { self.must_save_sort_names = true; return cosmic::Task::perform( async move { @@ -3635,10 +3662,12 @@ impl Application for App { if location == tab.location { tab.parent_item_opt = parent_item_opt; tab.set_items(items); + let location_str = location.to_string(); let sort = self .state .sort_names - .get(&location.to_string()) + .get(&location_str) + .or_else(|| SORT_OPTION_FALLBACK.get(&location_str)) .unwrap_or_else(|| &(HeadingOptions::Name, true)); tab.sort_name = sort.0; @@ -4237,10 +4266,12 @@ impl Application for App { Message::SaveSortNames => { self.must_save_sort_names = false; if let Some(state_handler) = self.state_handler.as_ref() { - if let Err(err) = state_handler.set::>( - "sort_names", - self.state.sort_names.clone(), - ) { + if let Err(err) = + state_handler.set::>( + "sort_names", + self.state.sort_names.clone(), + ) + { log::warn!("Failed to save sort names: {:?}", err); } } diff --git a/src/config.rs b/src/config.rs index eb978e3..b52c284 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,6 +7,7 @@ use cosmic::{ iced::Subscription, theme, Application, }; +use ordermap::OrderMap; use serde::{Deserialize, Serialize}; use crate::{ @@ -107,13 +108,13 @@ pub enum TypeToSearch { #[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(default)] pub struct State { - pub sort_names: HashMap, + pub sort_names: ordermap::OrderMap, } impl Default for State { fn default() -> Self { Self { - sort_names: HashMap::from_iter(dirs::download_dir().into_iter().map(|dir| { + sort_names: OrderMap::from_iter(dirs::download_dir().into_iter().map(|dir| { ( Location::Path(dir).normalize().to_string(), (HeadingOptions::Modified, false), diff --git a/src/tab.rs b/src/tab.rs index 63e058a..da6a546 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -45,6 +45,7 @@ use icu::datetime::{ }; use mime_guess::{mime, Mime}; use once_cell::sync::Lazy; +use ordermap::OrderMap; use serde::{Deserialize, Serialize}; use std::{ borrow::Cow, @@ -91,7 +92,7 @@ const THUMBNAIL_SIZE: u32 = (ICON_SIZE_GRID as u32) * (ICON_SCALE_MAX as u32); const DRAG_SCROLL_DISTANCE: f32 = 15.0; -static SORT_OPTION_FALLBACK: LazyLock> = +pub(crate) static SORT_OPTION_FALLBACK: LazyLock> = LazyLock::new(|| { HashMap::from_iter(dirs::download_dir().into_iter().map(|dir| { ( @@ -2366,7 +2367,7 @@ impl Tab { pub fn new( location: Location, config: TabConfig, - sorting_options: Option<&HashMap>, + sorting_options: Option<&OrderMap>, ) -> Self { let location_str = location.to_string(); let (sort_name, sort_direction) = sorting_options