From 2128cd23005cebd633bb9ea8c816462419ca686f Mon Sep 17 00:00:00 2001 From: Ultradesu Date: Thu, 23 Jul 2026 14:23:22 +0300 Subject: [PATCH] Fixed artist page sorting --- Cargo.lock | 177 +++++++++----------------------------- src/app/state.rs | 98 +++++++++++++++++++-- src/federation/catalog.rs | 38 +++++++- 3 files changed, 164 insertions(+), 149 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47955db..b572fc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -933,6 +933,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", + "rand_core 0.10.1", ] [[package]] @@ -965,9 +966,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "5.0.0-rc.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f359e08ca85e7bd759e1fd933ff2bccd81864c60a8fba0e259c7f822b0924bf" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -992,38 +993,14 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - [[package]] name = "darling" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.119", + "darling_core", + "darling_macro", ] [[package]] @@ -1039,24 +1016,13 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.119", -] - [[package]] name = "darling_macro" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core 0.23.0", + "darling_core", "quote", "syn 2.0.119", ] @@ -1127,37 +1093,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn 2.0.119", -] - [[package]] name = "derive_more" version = "2.1.1" @@ -1296,9 +1231,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "3.0.0-rc.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011170fe4f04665565b4110afef66774fe9ffff278f3eb5b81cc73d26e27d60" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" dependencies = [ "curve25519-dalek", "ed25519", @@ -1493,7 +1428,7 @@ dependencies = [ [[package]] name = "federation-net" version = "0.1.0" -source = "git+https://gt.hexor.cy/ab/frid.git#8ee1db9cf89ea604c6b8a8c3fe089a714c1e321f" +source = "git+https://gt.hexor.cy/ab/frid.git#daea24042c01e8a69e7e9a90fc7784dade337bcc" dependencies = [ "blake3", "data-encoding", @@ -2375,7 +2310,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ - "darling 0.23.0", + "darling", "indoc", "proc-macro2", "quote", @@ -2426,9 +2361,9 @@ dependencies = [ [[package]] name = "iroh" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fca9b4b462c343ff88fc0af4096c186f939b602a0bc08723536ef2c31c93971" +checksum = "460de6bc52163b41b1646931f2897e5ab986f0966ade444467fec25024751a72" dependencies = [ "backon", "blake3", @@ -2477,9 +2412,9 @@ dependencies = [ [[package]] name = "iroh-base" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830a582cd54410dc1aa71d4786a82c3297d7b0165accd8b6dbbb3b240b48140d" +checksum = "6be73e16ee21c923aca9b3121aaa0db936f7c7ecc156ff47b8dac944c68d59a8" dependencies = [ "curve25519-dalek", "data-encoding", @@ -2496,9 +2431,9 @@ dependencies = [ [[package]] name = "iroh-dns" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516e4eedc38e33ab69a6bd325520332dc3d67b25454e2d590ebb84a25240dd9a" +checksum = "46f6a9b39d18e6345f5c151afd299f2488e2cb5c520fe41b107b6bd3dc4c3349" dependencies = [ "arc-swap", "cfg_aliases", @@ -2547,9 +2482,9 @@ dependencies = [ [[package]] name = "iroh-relay" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8149bb6a57126225a07d6928846d82dcedfd24ea0f863ef7b2eb475e1d726354" +checksum = "24bd586cf927f7b700f56ec3639b53cb5fa901ce284784051ff71092bfbf8193" dependencies = [ "blake3", "bytes", @@ -2586,7 +2521,6 @@ dependencies = [ "tokio-websockets", "tracing", "url", - "vergen-gitcl", "webpki-roots", "ws_stream_wasm", ] @@ -2730,9 +2664,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "22053b6a34f84abc97f9129e61334f40174659a1b9bd18c970b83db6a9a6348b" [[package]] name = "libm" @@ -3027,7 +2961,7 @@ dependencies = [ [[package]] name = "music-dht" version = "0.1.0" -source = "git+https://gt.hexor.cy/ab/frid.git#8ee1db9cf89ea604c6b8a8c3fe089a714c1e321f" +source = "git+https://gt.hexor.cy/ab/frid.git#daea24042c01e8a69e7e9a90fc7784dade337bcc" dependencies = [ "async-trait", "blake3", @@ -3275,9 +3209,9 @@ dependencies = [ [[package]] name = "noq" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf95190af1bd4a00a10e8255ca0c8ddd9e9a9f5e79151d7a7eb6d56aff5dc89" +checksum = "e11803df44ac03a30988d61585ea50885d5428e42da944fe1e498799da7886a2" dependencies = [ "bytes", "cfg_aliases", @@ -3297,9 +3231,9 @@ dependencies = [ [[package]] name = "noq-proto" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa6c890013591e709a3e45dd53501351b7e27e7ff3c7e9fc3dce43e300e7e9d3" +checksum = "334c3c9833f7b2c573cceb9896ddc7aaeb58c8807cbb63211b24d1fe88bf866e" dependencies = [ "aes-gcm", "bytes", @@ -3324,9 +3258,9 @@ dependencies = [ [[package]] name = "noq-udp" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3137a52df66c20090a889828d1c655f21f52294cba64e5c4fbb04fc83eee7c8e" +checksum = "bde7a5d5102f1cff03d482240f0ed20551661f63663620f4b26112ed751165e9" dependencies = [ "cfg_aliases", "libc", @@ -4836,6 +4770,9 @@ name = "signature" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "rand_core 0.10.1", +] [[package]] name = "simd-adler32" @@ -5454,9 +5391,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.53.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -5503,14 +5440,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", "futures-util", + "libc", "pin-project-lite", "tokio", ] @@ -5851,43 +5789,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vergen" -version = "9.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b849a1f6d8639e8de261e81ee0fc881e3e3620db1af9f2e0da015d4382ceaf75" -dependencies = [ - "anyhow", - "derive_builder", - "rustversion", - "vergen-lib", -] - -[[package]] -name = "vergen-gitcl" -version = "9.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ff3b5300a085d6bcd8fc96a507f706a28ae3814693236c9b409db71a1d15b9" -dependencies = [ - "anyhow", - "derive_builder", - "rustversion", - "time", - "vergen", - "vergen-lib", -] - -[[package]] -name = "vergen-lib" -version = "9.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b34a29ba7e9c59e62f229ae1932fb1b8fb8a6fdcc99215a641913f5f5a59a569" -dependencies = [ - "anyhow", - "derive_builder", - "rustversion", -] - [[package]] name = "version_check" version = "0.9.5" @@ -6697,18 +6598,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", diff --git a/src/app/state.rs b/src/app/state.rs index 416a7db..fe45160 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -121,17 +121,23 @@ impl Default for GlobalTab { } /// Releases of an artist in display order: grouped by type (albums, singles, -/// EPs, compilations, then anything else), keeping server order within a -/// group. Returns (group label, indices into the original slice). Cursor -/// positions use this flattened order, so update() and ui must both go -/// through here. +/// EPs, compilations, then anything else), newest first within each group. +/// Returns (group label, indices into the original slice). Cursor positions +/// use this flattened order, so update() and ui must both go through here. pub fn release_groups(releases: &[ReleaseCard]) -> Vec<(&'static str, Vec)> { - release_type_groups(releases, |release| &release.release_type) + release_type_groups( + releases, + |release| &release.release_type, + |release| release.year, + |release| &release.title, + ) } fn release_type_groups( items: &[T], release_type: impl Fn(&T) -> &str, + release_year: impl Fn(&T) -> Option, + release_title: impl Fn(&T) -> &str, ) -> Vec<(&'static str, Vec)> { const GROUPS: [(&str, &str); 4] = [ ("album", "Albums"), @@ -141,24 +147,40 @@ fn release_type_groups( ]; let mut groups: Vec<(&'static str, Vec)> = Vec::new(); for (kind, label) in GROUPS { - let indices: Vec = items + let mut indices: Vec = items .iter() .enumerate() .filter(|(_, item)| release_type(item).eq_ignore_ascii_case(kind)) .map(|(i, _)| i) .collect(); + sort_release_indices(&mut indices, items, &release_year, &release_title); if !indices.is_empty() { groups.push((label, indices)); } } let known: Vec = groups.iter().flat_map(|(_, v)| v.iter().copied()).collect(); - let other: Vec = (0..items.len()).filter(|i| !known.contains(i)).collect(); + let mut other: Vec = (0..items.len()).filter(|i| !known.contains(i)).collect(); + sort_release_indices(&mut other, items, &release_year, &release_title); if !other.is_empty() { groups.push(("Other", other)); } groups } +fn sort_release_indices( + indices: &mut [usize], + items: &[T], + release_year: &impl Fn(&T) -> Option, + release_title: &impl Fn(&T) -> &str, +) { + indices.sort_by(|&left, &right| { + release_year(&items[right]) + .unwrap_or(i32::MIN) + .cmp(&release_year(&items[left]).unwrap_or(i32::MIN)) + .then_with(|| release_title(&items[left]).cmp(release_title(&items[right]))) + }); +} + /// Flattened display order of releases (concatenated groups). pub fn release_display_order(releases: &[ReleaseCard]) -> Vec { release_groups(releases) @@ -177,7 +199,12 @@ pub fn release_rows(releases: &[ReleaseCard], columns: usize) -> Vec> pub fn fed_release_groups( releases: &[crate::federation::FedRelease], ) -> Vec<(&'static str, Vec)> { - release_type_groups(releases, |release| &release.release_type) + release_type_groups( + releases, + |release| &release.release_type, + |release| release.year, + |release| &release.title, + ) } /// Flattened display order of federated releases (concatenated groups). @@ -212,6 +239,61 @@ fn grouped_release_rows( rows } +#[cfg(test)] +mod tests { + use super::*; + + fn release(id: i64, title: &str, release_type: &str, year: Option) -> ReleaseCard { + ReleaseCard { + id, + title: title.to_string(), + release_type: release_type.to_string(), + year, + cover_path: None, + track_count: 1, + } + } + + fn fed_release( + title: &str, + release_type: &str, + year: Option, + ) -> crate::federation::FedRelease { + crate::federation::FedRelease { + title: title.to_string(), + release_type: release_type.to_string(), + year, + ..Default::default() + } + } + + #[test] + fn release_display_order_is_newest_first_within_each_type() { + let releases = vec![ + release(1, "Old Album", "album", Some(1991)), + release(2, "New Single", "single", Some(2024)), + release(3, "New Album", "album", Some(2020)), + release(4, "Undated Album", "album", None), + release(5, "Old Single", "single", Some(1999)), + ]; + + assert_eq!(release_display_order(&releases), vec![2, 0, 3, 1, 4]); + } + + #[test] + fn fed_release_display_order_is_newest_first_within_each_type() { + let releases = vec![ + fed_release("Old Album", "album", Some(1991)), + fed_release("New Single", "single", Some(2024)), + fed_release("New Album", "album", Some(2020)), + fed_release("Undated Album", "album", None), + fed_release("Old Single", "single", Some(1999)), + ]; + + assert_eq!(fed_release_display_order(&releases), vec![2, 0, 3, 1, 4]); + } +} + /// The virtual Likes playlist id (`kind == "likes"`). pub use crate::library::LIKES_PLAYLIST_ID; diff --git a/src/federation/catalog.rs b/src/federation/catalog.rs index 9f23ce2..0f163b4 100644 --- a/src/federation/catalog.rs +++ b/src/federation/catalog.rs @@ -605,9 +605,9 @@ pub fn merge_catalogs(name: &str, catalogs: Vec<(String, CatalogArtist)>) -> Fed .then_with(|| a.track.title.cmp(&b.track.title)) }); releases.sort_by(|a, b| { - a.year - .unwrap_or(i32::MAX) - .cmp(&b.year.unwrap_or(i32::MAX)) + b.year + .unwrap_or(i32::MIN) + .cmp(&a.year.unwrap_or(i32::MIN)) .then_with(|| a.title.cmp(&b.title)) }); @@ -754,4 +754,36 @@ mod tests { assert_eq!(card.appears_on[0].track.artists, vec!["Host"]); assert_eq!(card.appears_on[0].track.featured_artists, vec!["Guest"]); } + + #[test] + fn merge_catalogs_sorts_releases_newest_first() { + let release = |title: &str, year: Option, item: &str| CatalogRelease { + title: title.into(), + release_type: "album".into(), + year, + tracks: vec![track("Song", 1, item)], + }; + let card = merge_catalogs( + "Metallica", + vec![( + "peer-a".to_string(), + CatalogArtist { + name: "Metallica".into(), + releases: vec![ + release("Old Album", Some(1991), "a1"), + release("New Album", Some(2024), "a2"), + release("Undated Album", None, "a3"), + ], + appears_on: Vec::new(), + }, + )], + ); + + let titles: Vec<&str> = card + .releases + .iter() + .map(|release| release.title.as_str()) + .collect(); + assert_eq!(titles, vec!["New Album", "Old Album", "Undated Album"]); + } }