From 1098013703cb5cdac36ee8d854165590fbf2f771 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sat, 2 Mar 2024 09:32:34 +0100 Subject: [PATCH 01/12] Expand album date for sorting --- src/core/collection/album.rs | 67 +++++- src/core/collection/track.rs | 16 +- src/core/library/beets/mod.rs | 68 ++++-- src/core/library/beets/testmod.rs | 132 +++++++++-- src/core/library/mod.rs | 9 +- src/core/library/testmod.rs | 93 ++++++-- src/core/musichoard/musichoard.rs | 23 +- src/core/testmod.rs | 4 +- src/tests.rs | 148 +++++++----- src/tui/testmod.rs | 4 +- src/tui/ui.rs | 10 +- tests/library/testmod.rs | 325 +++++++++++++++++++------- tests/testlib.rs | 372 ++++++++++++++++-------------- 13 files changed, 877 insertions(+), 394 deletions(-) diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index 7a35591..492e320 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -15,10 +15,56 @@ pub struct Album { /// The album identifier. #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct AlbumId { - pub year: u32, + pub date: AlbumDate, pub title: String, } +// There are crates for handling dates, but we don't need much complexity beyond year-month-day. +#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub struct AlbumDate { + pub year: u32, + pub month: AlbumMonth, + pub day: u8, +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub enum AlbumMonth { + None = 0, + January = 1, + February = 2, + March = 3, + April = 4, + May = 5, + June = 6, + July = 7, + August = 8, + September = 9, + October = 10, + November = 11, + December = 12, +} + +impl From for AlbumMonth { + fn from(value: u8) -> Self { + match value { + 1 => AlbumMonth::January, + 2 => AlbumMonth::February, + 3 => AlbumMonth::March, + 4 => AlbumMonth::April, + 5 => AlbumMonth::May, + 6 => AlbumMonth::June, + 7 => AlbumMonth::July, + 8 => AlbumMonth::August, + 9 => AlbumMonth::September, + 10 => AlbumMonth::October, + 11 => AlbumMonth::November, + 12 => AlbumMonth::December, + _ => AlbumMonth::None, + } + } +} + impl Album { pub fn get_sort_key(&self) -> &AlbumId { &self.id @@ -51,6 +97,25 @@ mod tests { use super::*; + #[test] + fn album_month() { + assert_eq!(>::into(0), AlbumMonth::None); + assert_eq!(>::into(1), AlbumMonth::January); + assert_eq!(>::into(2), AlbumMonth::February); + assert_eq!(>::into(3), AlbumMonth::March); + assert_eq!(>::into(4), AlbumMonth::April); + assert_eq!(>::into(5), AlbumMonth::May); + assert_eq!(>::into(6), AlbumMonth::June); + assert_eq!(>::into(7), AlbumMonth::July); + assert_eq!(>::into(8), AlbumMonth::August); + assert_eq!(>::into(9), AlbumMonth::September); + assert_eq!(>::into(10), AlbumMonth::October); + assert_eq!(>::into(11), AlbumMonth::November); + assert_eq!(>::into(12), AlbumMonth::December); + assert_eq!(>::into(13), AlbumMonth::None); + assert_eq!(>::into(255), AlbumMonth::None); + } + #[test] fn merge_album_no_overlap() { let left = FULL_COLLECTION[0].albums[0].to_owned(); diff --git a/src/core/collection/track.rs b/src/core/collection/track.rs index 5853de1..2aa5aa1 100644 --- a/src/core/collection/track.rs +++ b/src/core/collection/track.rs @@ -5,7 +5,7 @@ use crate::core::collection::merge::Merge; pub struct Track { pub id: TrackId, pub artist: Vec, - pub quality: Quality, + pub quality: TrackQuality, } /// The track identifier. @@ -17,8 +17,8 @@ pub struct TrackId { /// The track quality. Combines format and bitrate information. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Quality { - pub format: Format, +pub struct TrackQuality { + pub format: TrackFormat, pub bitrate: u32, } @@ -30,7 +30,7 @@ impl Track { /// The track file format. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum Format { +pub enum TrackFormat { Flac, Mp3, } @@ -65,16 +65,16 @@ mod tests { title: String::from("a title"), }, artist: vec![String::from("left artist")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1411, }, }; let right = Track { id: left.id.clone(), artist: vec![String::from("right artist")], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 320, }, }; diff --git a/src/core/library/beets/mod.rs b/src/core/library/beets/mod.rs index 748a3c6..53a1ac1 100644 --- a/src/core/library/beets/mod.rs +++ b/src/core/library/beets/mod.rs @@ -7,7 +7,7 @@ pub mod executor; use mockall::automock; use crate::core::{ - collection::track::Format, + collection::track::TrackFormat, library::{Error, Field, ILibrary, Item, Query}, }; @@ -27,6 +27,10 @@ const LIST_FORMAT_ARG: &str = concat!( list_format_separator!(), "$year", list_format_separator!(), + "$month", + list_format_separator!(), + "$day", + list_format_separator!(), "$album", list_format_separator!(), "$track", @@ -42,6 +46,21 @@ const LIST_FORMAT_ARG: &str = concat!( const TRACK_FORMAT_FLAC: &str = "FLAC"; const TRACK_FORMAT_MP3: &str = "MP3"; +fn format_to_str(format: &TrackFormat) -> &'static str { + match format { + TrackFormat::Flac => TRACK_FORMAT_FLAC, + TrackFormat::Mp3 => TRACK_FORMAT_MP3, + } +} + +fn str_to_format(format: &str) -> Option { + match format { + TRACK_FORMAT_FLAC => Some(TrackFormat::Flac), + TRACK_FORMAT_MP3 => Some(TrackFormat::Mp3), + _ => None, + } +} + trait ToBeetsArg { fn to_arg(&self, include: bool) -> String; } @@ -57,10 +76,13 @@ impl ToBeetsArg for Field { Field::AlbumArtist(ref s) => format!("{negate}albumartist:{s}"), Field::AlbumArtistSort(ref s) => format!("{negate}albumartist_sort:{s}"), Field::AlbumYear(ref u) => format!("{negate}year:{u}"), + Field::AlbumMonth(ref e) => format!("{negate}month:{}", *e as u8), + Field::AlbumDay(ref u) => format!("{negate}day:{u}"), Field::AlbumTitle(ref s) => format!("{negate}album:{s}"), Field::TrackNumber(ref u) => format!("{negate}track:{u}"), Field::TrackTitle(ref s) => format!("{negate}title:{s}"), Field::TrackArtist(ref v) => format!("{negate}artist:{}", v.join("; ")), + Field::TrackFormat(ref f) => format!("{negate}format:{}", format_to_str(f)), Field::All(ref s) => format!("{negate}{s}"), } } @@ -127,36 +149,38 @@ impl ILibraryPrivate for BeetsLibrary { } let split: Vec<&str> = line.split(LIST_FORMAT_SEPARATOR).collect(); - if split.len() != 9 { + if split.len() != 11 { return Err(Error::Invalid(line.to_string())); } let album_artist = split[0].to_string(); - let album_artist_sort = if !split[1].is_empty() { - Some(split[1].to_string()) - } else { - None + let album_artist_sort = match !split[1].is_empty() { + true => Some(split[1].to_string()), + false => None, }; let album_year = split[2].parse::()?; - let album_title = split[3].to_string(); - let track_number = split[4].parse::()?; - let track_title = split[5].to_string(); - let track_artist = split[6] + let album_month = split[3].parse::()?.into(); + let album_day = split[4].parse::()?; + let album_title = split[5].to_string(); + let track_number = split[6].parse::()?; + let track_title = split[7].to_string(); + let track_artist = split[8] .to_string() .split("; ") .map(|s| s.to_owned()) .collect(); - let track_format = match split[7].to_string().as_str() { - TRACK_FORMAT_FLAC => Format::Flac, - TRACK_FORMAT_MP3 => Format::Mp3, - _ => return Err(Error::Invalid(line.to_string())), + let track_format = match str_to_format(split[9].to_string().as_str()) { + Some(format) => format, + None => return Err(Error::Invalid(line.to_string())), }; - let track_bitrate = split[8].trim_end_matches("kbps").parse::()?; + let track_bitrate = split[10].trim_end_matches("kbps").parse::()?; items.push(Item { album_artist, album_artist_sort, album_year, + album_month, + album_day, album_title, track_number, track_title, @@ -177,7 +201,7 @@ mod testmod; mod tests { use mockall::predicate; - use crate::core::library::testmod::LIBRARY_ITEMS; + use crate::{core::library::testmod::LIBRARY_ITEMS, collection::album::AlbumMonth}; use super::*; use testmod::LIBRARY_BEETS; @@ -191,6 +215,7 @@ mod tests { String::from("some.artist.1"), String::from("some.artist.2"), ])) + .exclude(Field::TrackFormat(TrackFormat::Mp3)) .exclude(Field::All(String::from("some.all"))) .to_args(); query.sort(); @@ -199,6 +224,7 @@ mod tests { query, vec![ String::from("^album:some.album"), + String::from("^format:MP3"), String::from("^some.all"), String::from("artist:some.artist.1; some.artist.2"), String::from("track:5"), @@ -209,7 +235,10 @@ mod tests { .exclude(Field::AlbumArtist(String::from("some.albumartist"))) .exclude(Field::AlbumArtistSort(String::from("some.albumartist"))) .include(Field::AlbumYear(3030)) + .include(Field::AlbumMonth(AlbumMonth::April)) + .include(Field::AlbumDay(6)) .include(Field::TrackTitle(String::from("some.track"))) + .include(Field::TrackFormat(TrackFormat::Flac)) .exclude(Field::TrackArtist(vec![ String::from("some.artist.1"), String::from("some.artist.2"), @@ -223,6 +252,9 @@ mod tests { String::from("^albumartist:some.albumartist"), String::from("^albumartist_sort:some.albumartist"), String::from("^artist:some.artist.1; some.artist.2"), + String::from("day:6"), + String::from("format:FLAC"), + String::from("month:4"), String::from("title:some.track"), String::from("year:3030"), ] @@ -335,8 +367,8 @@ mod tests { .split(LIST_FORMAT_SEPARATOR) .map(|s| s.to_owned()) .collect::>(); - invalid_string[7].clear(); - invalid_string[7].push_str("invalid format"); + invalid_string[9].clear(); + invalid_string[9].push_str("invalid format"); let invalid_string = invalid_string.join(LIST_FORMAT_SEPARATOR); output[2] = invalid_string.clone(); let result = Ok(output); diff --git a/src/core/library/beets/testmod.rs b/src/core/library/beets/testmod.rs index e4f30f9..b0fc10a 100644 --- a/src/core/library/beets/testmod.rs +++ b/src/core/library/beets/testmod.rs @@ -2,27 +2,115 @@ use once_cell::sync::Lazy; pub static LIBRARY_BEETS: Lazy> = Lazy::new(|| -> Vec { vec![ - String::from("Album_Artist ‘A’ -*^- -*^- 1998 -*^- album_title a.a -*^- 1 -*^- track a.a.1 -*^- artist a.a.1 -*^- FLAC -*^- 992"), - String::from("Album_Artist ‘A’ -*^- -*^- 1998 -*^- album_title a.a -*^- 2 -*^- track a.a.2 -*^- artist a.a.2.1; artist a.a.2.2 -*^- MP3 -*^- 320"), - String::from("Album_Artist ‘A’ -*^- -*^- 1998 -*^- album_title a.a -*^- 3 -*^- track a.a.3 -*^- artist a.a.3 -*^- FLAC -*^- 1061"), - String::from("Album_Artist ‘A’ -*^- -*^- 1998 -*^- album_title a.a -*^- 4 -*^- track a.a.4 -*^- artist a.a.4 -*^- FLAC -*^- 1042"), - String::from("Album_Artist ‘A’ -*^- -*^- 2015 -*^- album_title a.b -*^- 1 -*^- track a.b.1 -*^- artist a.b.1 -*^- FLAC -*^- 1004"), - String::from("Album_Artist ‘A’ -*^- -*^- 2015 -*^- album_title a.b -*^- 2 -*^- track a.b.2 -*^- artist a.b.2 -*^- FLAC -*^- 1077"), - String::from("Album_Artist ‘B’ -*^- -*^- 2003 -*^- album_title b.a -*^- 1 -*^- track b.a.1 -*^- artist b.a.1 -*^- MP3 -*^- 190"), - String::from("Album_Artist ‘B’ -*^- -*^- 2003 -*^- album_title b.a -*^- 2 -*^- track b.a.2 -*^- artist b.a.2.1; artist b.a.2.2 -*^- MP3 -*^- 120"), - String::from("Album_Artist ‘B’ -*^- -*^- 2008 -*^- album_title b.b -*^- 1 -*^- track b.b.1 -*^- artist b.b.1 -*^- FLAC -*^- 1077"), - String::from("Album_Artist ‘B’ -*^- -*^- 2008 -*^- album_title b.b -*^- 2 -*^- track b.b.2 -*^- artist b.b.2.1; artist b.b.2.2 -*^- MP3 -*^- 320"), - String::from("Album_Artist ‘B’ -*^- -*^- 2009 -*^- album_title b.c -*^- 1 -*^- track b.c.1 -*^- artist b.c.1 -*^- MP3 -*^- 190"), - String::from("Album_Artist ‘B’ -*^- -*^- 2009 -*^- album_title b.c -*^- 2 -*^- track b.c.2 -*^- artist b.c.2.1; artist b.c.2.2 -*^- MP3 -*^- 120"), - String::from("Album_Artist ‘B’ -*^- -*^- 2015 -*^- album_title b.d -*^- 1 -*^- track b.d.1 -*^- artist b.d.1 -*^- MP3 -*^- 190"), - String::from("Album_Artist ‘B’ -*^- -*^- 2015 -*^- album_title b.d -*^- 2 -*^- track b.d.2 -*^- artist b.d.2.1; artist b.d.2.2 -*^- MP3 -*^- 120"), - String::from("The Album_Artist ‘C’ -*^- Album_Artist ‘C’, The -*^- 1985 -*^- album_title c.a -*^- 1 -*^- track c.a.1 -*^- artist c.a.1 -*^- MP3 -*^- 320"), - String::from("The Album_Artist ‘C’ -*^- Album_Artist ‘C’, The -*^- 1985 -*^- album_title c.a -*^- 2 -*^- track c.a.2 -*^- artist c.a.2.1; artist c.a.2.2 -*^- MP3 -*^- 120"), - String::from("The Album_Artist ‘C’ -*^- Album_Artist ‘C’, The -*^- 2018 -*^- album_title c.b -*^- 1 -*^- track c.b.1 -*^- artist c.b.1 -*^- FLAC -*^- 1041"), - String::from("The Album_Artist ‘C’ -*^- Album_Artist ‘C’, The -*^- 2018 -*^- album_title c.b -*^- 2 -*^- track c.b.2 -*^- artist c.b.2.1; artist c.b.2.2 -*^- FLAC -*^- 756"), - String::from("Album_Artist ‘D’ -*^- -*^- 1995 -*^- album_title d.a -*^- 1 -*^- track d.a.1 -*^- artist d.a.1 -*^- MP3 -*^- 120"), - String::from("Album_Artist ‘D’ -*^- -*^- 1995 -*^- album_title d.a -*^- 2 -*^- track d.a.2 -*^- artist d.a.2.1; artist d.a.2.2 -*^- MP3 -*^- 120"), - String::from("Album_Artist ‘D’ -*^- -*^- 2028 -*^- album_title d.b -*^- 1 -*^- track d.b.1 -*^- artist d.b.1 -*^- FLAC -*^- 841"), - String::from("Album_Artist ‘D’ -*^- -*^- 2028 -*^- album_title d.b -*^- 2 -*^- track d.b.2 -*^- artist d.b.2.1; artist d.b.2.2 -*^- FLAC -*^- 756") + String::from( + "Album_Artist ‘A’ -*^- -*^- \ + 1998 -*^- 00 -*^- 00 -*^- album_title a.a -*^- \ + 1 -*^- track a.a.1 -*^- artist a.a.1 -*^- FLAC -*^- 992", + ), + String::from( + "Album_Artist ‘A’ -*^- -*^- \ + 1998 -*^- 00 -*^- 00 -*^- album_title a.a -*^- \ + 2 -*^- track a.a.2 -*^- artist a.a.2.1; artist a.a.2.2 -*^- MP3 -*^- 320", + ), + String::from( + "Album_Artist ‘A’ -*^- -*^- \ + 1998 -*^- 00 -*^- 00 -*^- album_title a.a -*^- \ + 3 -*^- track a.a.3 -*^- artist a.a.3 -*^- FLAC -*^- 1061", + ), + String::from( + "Album_Artist ‘A’ -*^- -*^- \ + 1998 -*^- 00 -*^- 00 -*^- album_title a.a -*^- \ + 4 -*^- track a.a.4 -*^- artist a.a.4 -*^- FLAC -*^- 1042", + ), + String::from( + "Album_Artist ‘A’ -*^- -*^- \ + 2015 -*^- 04 -*^- 00 -*^- album_title a.b -*^- \ + 1 -*^- track a.b.1 -*^- artist a.b.1 -*^- FLAC -*^- 1004", + ), + String::from( + "Album_Artist ‘A’ -*^- -*^- \ + 2015 -*^- 04 -*^- 00 -*^- album_title a.b -*^- \ + 2 -*^- track a.b.2 -*^- artist a.b.2 -*^- FLAC -*^- 1077", + ), + String::from( + "Album_Artist ‘B’ -*^- -*^- \ + 2003 -*^- 06 -*^- 06 -*^- album_title b.a -*^- \ + 1 -*^- track b.a.1 -*^- artist b.a.1 -*^- MP3 -*^- 190", + ), + String::from( + "Album_Artist ‘B’ -*^- -*^- \ + 2003 -*^- 06 -*^- 06 -*^- album_title b.a -*^- \ + 2 -*^- track b.a.2 -*^- artist b.a.2.1; artist b.a.2.2 -*^- MP3 -*^- 120", + ), + String::from( + "Album_Artist ‘B’ -*^- -*^- \ + 2008 -*^- 00 -*^- 00 -*^- album_title b.b -*^- \ + 1 -*^- track b.b.1 -*^- artist b.b.1 -*^- FLAC -*^- 1077", + ), + String::from( + "Album_Artist ‘B’ -*^- -*^- \ + 2008 -*^- 00 -*^- 00 -*^- album_title b.b -*^- \ + 2 -*^- track b.b.2 -*^- artist b.b.2.1; artist b.b.2.2 -*^- MP3 -*^- 320", + ), + String::from( + "Album_Artist ‘B’ -*^- -*^- \ + 2009 -*^- 00 -*^- 00 -*^- album_title b.c -*^- \ + 1 -*^- track b.c.1 -*^- artist b.c.1 -*^- MP3 -*^- 190", + ), + String::from( + "Album_Artist ‘B’ -*^- -*^- \ + 2009 -*^- 00 -*^- 00 -*^- album_title b.c -*^- \ + 2 -*^- track b.c.2 -*^- artist b.c.2.1; artist b.c.2.2 -*^- MP3 -*^- 120", + ), + String::from( + "Album_Artist ‘B’ -*^- -*^- \ + 2015 -*^- 00 -*^- 00 -*^- album_title b.d -*^- \ + 1 -*^- track b.d.1 -*^- artist b.d.1 -*^- MP3 -*^- 190", + ), + String::from( + "Album_Artist ‘B’ -*^- -*^- \ + 2015 -*^- 00 -*^- 00 -*^- album_title b.d -*^- \ + 2 -*^- track b.d.2 -*^- artist b.d.2.1; artist b.d.2.2 -*^- MP3 -*^- 120", + ), + String::from( + "The Album_Artist ‘C’ -*^- Album_Artist ‘C’, The -*^- \ + 1985 -*^- 00 -*^- 00 -*^- album_title c.a -*^- \ + 1 -*^- track c.a.1 -*^- artist c.a.1 -*^- MP3 -*^- 320", + ), + String::from( + "The Album_Artist ‘C’ -*^- Album_Artist ‘C’, The -*^- \ + 1985 -*^- 00 -*^- 00 -*^- album_title c.a -*^- \ + 2 -*^- track c.a.2 -*^- artist c.a.2.1; artist c.a.2.2 -*^- MP3 -*^- 120", + ), + String::from( + "The Album_Artist ‘C’ -*^- Album_Artist ‘C’, The -*^- \ + 2018 -*^- 00 -*^- 00 -*^- album_title c.b -*^- \ + 1 -*^- track c.b.1 -*^- artist c.b.1 -*^- FLAC -*^- 1041", + ), + String::from( + "The Album_Artist ‘C’ -*^- Album_Artist ‘C’, The -*^- \ + 2018 -*^- 00 -*^- 00 -*^- album_title c.b -*^- \ + 2 -*^- track c.b.2 -*^- artist c.b.2.1; artist c.b.2.2 -*^- FLAC -*^- 756", + ), + String::from( + "Album_Artist ‘D’ -*^- -*^- \ + 1995 -*^- 00 -*^- 00 -*^- album_title d.a -*^- \ + 1 -*^- track d.a.1 -*^- artist d.a.1 -*^- MP3 -*^- 120", + ), + String::from( + "Album_Artist ‘D’ -*^- -*^- \ + 1995 -*^- 00 -*^- 00 -*^- album_title d.a -*^- \ + 2 -*^- track d.a.2 -*^- artist d.a.2.1; artist d.a.2.2 -*^- MP3 -*^- 120", + ), + String::from( + "Album_Artist ‘D’ -*^- -*^- \ + 2028 -*^- 00 -*^- 00 -*^- album_title d.b -*^- \ + 1 -*^- track d.b.1 -*^- artist d.b.1 -*^- FLAC -*^- 841", + ), + String::from( + "Album_Artist ‘D’ -*^- -*^- \ + 2028 -*^- 00 -*^- 00 -*^- album_title d.b -*^- \ + 2 -*^- track d.b.2 -*^- artist d.b.2.1; artist d.b.2.2 -*^- FLAC -*^- 756", + ), ] }); diff --git a/src/core/library/mod.rs b/src/core/library/mod.rs index 6e0778a..7797c07 100644 --- a/src/core/library/mod.rs +++ b/src/core/library/mod.rs @@ -8,7 +8,7 @@ use std::{collections::HashSet, fmt, num::ParseIntError, str::Utf8Error}; #[cfg(test)] use mockall::automock; -use crate::core::collection::track::Format; +use crate::core::collection::{album::AlbumMonth, track::TrackFormat}; /// Trait for interacting with the music library. #[cfg_attr(test, automock)] @@ -32,11 +32,13 @@ pub struct Item { pub album_artist: String, pub album_artist_sort: Option, pub album_year: u32, + pub album_month: AlbumMonth, + pub album_day: u8, pub album_title: String, pub track_number: u32, pub track_title: String, pub track_artist: Vec, - pub track_format: Format, + pub track_format: TrackFormat, pub track_bitrate: u32, } @@ -46,10 +48,13 @@ pub enum Field { AlbumArtist(String), AlbumArtistSort(String), AlbumYear(u32), + AlbumMonth(AlbumMonth), + AlbumDay(u8), AlbumTitle(String), TrackNumber(u32), TrackTitle(String), TrackArtist(Vec), + TrackFormat(TrackFormat), All(String), } diff --git a/src/core/library/testmod.rs b/src/core/library/testmod.rs index af2ed68..09e56b8 100644 --- a/src/core/library/testmod.rs +++ b/src/core/library/testmod.rs @@ -1,6 +1,9 @@ use once_cell::sync::Lazy; -use crate::core::{collection::track::Format, library::Item}; +use crate::core::{ + collection::{album::AlbumMonth, track::TrackFormat}, + library::Item, +}; pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { vec![ @@ -8,17 +11,21 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { album_artist: String::from("Album_Artist ‘A’"), album_artist_sort: None, album_year: 1998, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title a.a"), track_number: 1, track_title: String::from("track a.a.1"), track_artist: vec![String::from("artist a.a.1")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 992, }, Item { album_artist: String::from("Album_Artist ‘A’"), album_artist_sort: None, album_year: 1998, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title a.a"), track_number: 2, track_title: String::from("track a.a.2"), @@ -26,68 +33,80 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { String::from("artist a.a.2.1"), String::from("artist a.a.2.2"), ], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 320, }, Item { album_artist: String::from("Album_Artist ‘A’"), album_artist_sort: None, album_year: 1998, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title a.a"), track_number: 3, track_title: String::from("track a.a.3"), track_artist: vec![String::from("artist a.a.3")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1061, }, Item { album_artist: String::from("Album_Artist ‘A’"), album_artist_sort: None, album_year: 1998, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title a.a"), track_number: 4, track_title: String::from("track a.a.4"), track_artist: vec![String::from("artist a.a.4")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1042, }, Item { album_artist: String::from("Album_Artist ‘A’"), album_artist_sort: None, album_year: 2015, + album_month: AlbumMonth::April, + album_day: 0, album_title: String::from("album_title a.b"), track_number: 1, track_title: String::from("track a.b.1"), track_artist: vec![String::from("artist a.b.1")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1004, }, Item { album_artist: String::from("Album_Artist ‘A’"), album_artist_sort: None, album_year: 2015, + album_month: AlbumMonth::April, + album_day: 0, album_title: String::from("album_title a.b"), track_number: 2, track_title: String::from("track a.b.2"), track_artist: vec![String::from("artist a.b.2")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1077, }, Item { album_artist: String::from("Album_Artist ‘B’"), album_artist_sort: None, album_year: 2003, + album_month: AlbumMonth::June, + album_day: 6, album_title: String::from("album_title b.a"), track_number: 1, track_title: String::from("track b.a.1"), track_artist: vec![String::from("artist b.a.1")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 190, }, Item { album_artist: String::from("Album_Artist ‘B’"), album_artist_sort: None, album_year: 2003, + album_month: AlbumMonth::June, + album_day: 6, album_title: String::from("album_title b.a"), track_number: 2, track_title: String::from("track b.a.2"), @@ -95,24 +114,28 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { String::from("artist b.a.2.1"), String::from("artist b.a.2.2"), ], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 120, }, Item { album_artist: String::from("Album_Artist ‘B’"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title b.b"), track_number: 1, track_title: String::from("track b.b.1"), track_artist: vec![String::from("artist b.b.1")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1077, }, Item { album_artist: String::from("Album_Artist ‘B’"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title b.b"), track_number: 2, track_title: String::from("track b.b.2"), @@ -120,24 +143,28 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { String::from("artist b.b.2.1"), String::from("artist b.b.2.2"), ], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 320, }, Item { album_artist: String::from("Album_Artist ‘B’"), album_artist_sort: None, album_year: 2009, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title b.c"), track_number: 1, track_title: String::from("track b.c.1"), track_artist: vec![String::from("artist b.c.1")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 190, }, Item { album_artist: String::from("Album_Artist ‘B’"), album_artist_sort: None, album_year: 2009, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title b.c"), track_number: 2, track_title: String::from("track b.c.2"), @@ -145,24 +172,28 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { String::from("artist b.c.2.1"), String::from("artist b.c.2.2"), ], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 120, }, Item { album_artist: String::from("Album_Artist ‘B’"), album_artist_sort: None, album_year: 2015, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title b.d"), track_number: 1, track_title: String::from("track b.d.1"), track_artist: vec![String::from("artist b.d.1")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 190, }, Item { album_artist: String::from("Album_Artist ‘B’"), album_artist_sort: None, album_year: 2015, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title b.d"), track_number: 2, track_title: String::from("track b.d.2"), @@ -170,24 +201,28 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { String::from("artist b.d.2.1"), String::from("artist b.d.2.2"), ], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 120, }, Item { album_artist: String::from("The Album_Artist ‘C’"), album_artist_sort: Some(String::from("Album_Artist ‘C’, The")), album_year: 1985, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title c.a"), track_number: 1, track_title: String::from("track c.a.1"), track_artist: vec![String::from("artist c.a.1")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 320, }, Item { album_artist: String::from("The Album_Artist ‘C’"), album_artist_sort: Some(String::from("Album_Artist ‘C’, The")), album_year: 1985, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title c.a"), track_number: 2, track_title: String::from("track c.a.2"), @@ -195,24 +230,28 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { String::from("artist c.a.2.1"), String::from("artist c.a.2.2"), ], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 120, }, Item { album_artist: String::from("The Album_Artist ‘C’"), album_artist_sort: Some(String::from("Album_Artist ‘C’, The")), album_year: 2018, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title c.b"), track_number: 1, track_title: String::from("track c.b.1"), track_artist: vec![String::from("artist c.b.1")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1041, }, Item { album_artist: String::from("The Album_Artist ‘C’"), album_artist_sort: Some(String::from("Album_Artist ‘C’, The")), album_year: 2018, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title c.b"), track_number: 2, track_title: String::from("track c.b.2"), @@ -220,24 +259,28 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { String::from("artist c.b.2.1"), String::from("artist c.b.2.2"), ], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 756, }, Item { album_artist: String::from("Album_Artist ‘D’"), album_artist_sort: None, album_year: 1995, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title d.a"), track_number: 1, track_title: String::from("track d.a.1"), track_artist: vec![String::from("artist d.a.1")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 120, }, Item { album_artist: String::from("Album_Artist ‘D’"), album_artist_sort: None, album_year: 1995, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title d.a"), track_number: 2, track_title: String::from("track d.a.2"), @@ -245,24 +288,28 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { String::from("artist d.a.2.1"), String::from("artist d.a.2.2"), ], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 120, }, Item { album_artist: String::from("Album_Artist ‘D’"), album_artist_sort: None, album_year: 2028, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title d.b"), track_number: 1, track_title: String::from("track d.b.1"), track_artist: vec![String::from("artist d.b.1")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 841, }, Item { album_artist: String::from("Album_Artist ‘D’"), album_artist_sort: None, album_year: 2028, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("album_title d.b"), track_number: 2, track_title: String::from("track d.b.2"), @@ -270,7 +317,7 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { String::from("artist d.b.2.1"), String::from("artist d.b.2.2"), ], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 756, }, ] diff --git a/src/core/musichoard/musichoard.rs b/src/core/musichoard/musichoard.rs index 266502a..2cd0ad5 100644 --- a/src/core/musichoard/musichoard.rs +++ b/src/core/musichoard/musichoard.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use crate::core::{ collection::{ - album::{Album, AlbumId}, + album::{Album, AlbumDate, AlbumId}, artist::{Artist, ArtistId}, - track::{Quality, Track, TrackId}, + track::{Track, TrackId, TrackQuality}, Collection, Merge, }, database::IDatabase, @@ -99,7 +99,11 @@ impl MusicHoard { let artist_sort = item.album_artist_sort.map(|s| ArtistId { name: s }); let album_id = AlbumId { - year: item.album_year, + date: AlbumDate { + year: item.album_year, + month: item.album_month, + day: item.album_day, + }, title: item.album_title, }; @@ -109,7 +113,7 @@ impl MusicHoard { title: item.track_title, }, artist: item.track_artist, - quality: Quality { + quality: TrackQuality { format: item.track_format, bitrate: item.track_bitrate, }, @@ -887,7 +891,7 @@ mod tests { } #[test] - fn rescan_library_album_title_year_clash() { + fn rescan_library_album_title_date_clash() { let mut library = MockILibrary::new(); let mut expected = LIBRARY_COLLECTION.to_owned(); @@ -896,9 +900,14 @@ mod tests { let mut items = LIBRARY_ITEMS.to_owned(); for item in items.iter_mut().filter(|it| { - (it.album_year == removed_album_id.year) && (it.album_title == removed_album_id.title) + (it.album_year == removed_album_id.date.year) + && (it.album_month == removed_album_id.date.month) + && (it.album_day == removed_album_id.date.day) + && (it.album_title == removed_album_id.title) }) { - item.album_year = clashed_album_id.year; + item.album_year = clashed_album_id.date.year; + item.album_month = clashed_album_id.date.month; + item.album_day = clashed_album_id.date.day; item.album_title = clashed_album_id.title.clone(); } diff --git a/src/core/testmod.rs b/src/core/testmod.rs index 2f10f36..971587b 100644 --- a/src/core/testmod.rs +++ b/src/core/testmod.rs @@ -2,9 +2,9 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use crate::core::collection::{ - album::{Album, AlbumId}, + album::{Album, AlbumDate, AlbumId, AlbumMonth}, artist::{Artist, ArtistId, MusicBrainz}, - track::{Format, Quality, Track, TrackId}, + track::{Track, TrackFormat, TrackId, TrackQuality}, }; use crate::tests::*; diff --git a/src/tests.rs b/src/tests.rs index bbb4de1..058c808 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -11,7 +11,11 @@ macro_rules! library_collection { albums: vec![ Album { id: AlbumId { - year: 1998, + date: AlbumDate { + year: 1998, + month: AlbumMonth::None, + day: 0, + }, title: "album_title a.a".to_string(), }, tracks: vec![ @@ -21,8 +25,8 @@ macro_rules! library_collection { title: "track a.a.1".to_string(), }, artist: vec!["artist a.a.1".to_string()], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 992, }, }, @@ -35,8 +39,8 @@ macro_rules! library_collection { "artist a.a.2.1".to_string(), "artist a.a.2.2".to_string(), ], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 320, }, }, @@ -46,8 +50,8 @@ macro_rules! library_collection { title: "track a.a.3".to_string(), }, artist: vec!["artist a.a.3".to_string()], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1061, }, }, @@ -57,8 +61,8 @@ macro_rules! library_collection { title: "track a.a.4".to_string(), }, artist: vec!["artist a.a.4".to_string()], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1042, }, }, @@ -66,7 +70,11 @@ macro_rules! library_collection { }, Album { id: AlbumId { - year: 2015, + date: AlbumDate { + year: 2015, + month: AlbumMonth::April, + day: 0, + }, title: "album_title a.b".to_string(), }, tracks: vec![ @@ -76,8 +84,8 @@ macro_rules! library_collection { title: "track a.b.1".to_string(), }, artist: vec!["artist a.b.1".to_string()], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1004, }, }, @@ -87,8 +95,8 @@ macro_rules! library_collection { title: "track a.b.2".to_string(), }, artist: vec!["artist a.b.2".to_string()], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1077, }, }, @@ -106,7 +114,11 @@ macro_rules! library_collection { albums: vec![ Album { id: AlbumId { - year: 2003, + date: AlbumDate { + year: 2003, + month: AlbumMonth::June, + day: 6, + }, title: "album_title b.a".to_string(), }, tracks: vec![ @@ -116,8 +128,8 @@ macro_rules! library_collection { title: "track b.a.1".to_string(), }, artist: vec!["artist b.a.1".to_string()], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 190, }, }, @@ -130,8 +142,8 @@ macro_rules! library_collection { "artist b.a.2.1".to_string(), "artist b.a.2.2".to_string(), ], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 120, }, }, @@ -139,7 +151,11 @@ macro_rules! library_collection { }, Album { id: AlbumId { - year: 2008, + date: AlbumDate { + year: 2008, + month: AlbumMonth::None, + day: 0, + }, title: "album_title b.b".to_string(), }, tracks: vec![ @@ -149,8 +165,8 @@ macro_rules! library_collection { title: "track b.b.1".to_string(), }, artist: vec!["artist b.b.1".to_string()], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1077, }, }, @@ -163,8 +179,8 @@ macro_rules! library_collection { "artist b.b.2.1".to_string(), "artist b.b.2.2".to_string(), ], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 320, }, }, @@ -172,7 +188,11 @@ macro_rules! library_collection { }, Album { id: AlbumId { - year: 2009, + date: AlbumDate { + year: 2009, + month: AlbumMonth::None, + day: 0, + }, title: "album_title b.c".to_string(), }, tracks: vec![ @@ -182,8 +202,8 @@ macro_rules! library_collection { title: "track b.c.1".to_string(), }, artist: vec!["artist b.c.1".to_string()], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 190, }, }, @@ -196,8 +216,8 @@ macro_rules! library_collection { "artist b.c.2.1".to_string(), "artist b.c.2.2".to_string(), ], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 120, }, }, @@ -205,7 +225,11 @@ macro_rules! library_collection { }, Album { id: AlbumId { - year: 2015, + date: AlbumDate { + year: 2015, + month: AlbumMonth::None, + day: 0, + }, title: "album_title b.d".to_string(), }, tracks: vec![ @@ -215,8 +239,8 @@ macro_rules! library_collection { title: "track b.d.1".to_string(), }, artist: vec!["artist b.d.1".to_string()], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 190, }, }, @@ -229,8 +253,8 @@ macro_rules! library_collection { "artist b.d.2.1".to_string(), "artist b.d.2.2".to_string(), ], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 120, }, }, @@ -250,7 +274,11 @@ macro_rules! library_collection { albums: vec![ Album { id: AlbumId { - year: 1985, + date: AlbumDate { + year: 1985, + month: AlbumMonth::None, + day: 0, + }, title: "album_title c.a".to_string(), }, tracks: vec![ @@ -260,8 +288,8 @@ macro_rules! library_collection { title: "track c.a.1".to_string(), }, artist: vec!["artist c.a.1".to_string()], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 320, }, }, @@ -274,8 +302,8 @@ macro_rules! library_collection { "artist c.a.2.1".to_string(), "artist c.a.2.2".to_string(), ], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 120, }, }, @@ -283,7 +311,11 @@ macro_rules! library_collection { }, Album { id: AlbumId { - year: 2018, + date: AlbumDate { + year: 2018, + month: AlbumMonth::None, + day: 0, + }, title: "album_title c.b".to_string(), }, tracks: vec![ @@ -293,8 +325,8 @@ macro_rules! library_collection { title: "track c.b.1".to_string(), }, artist: vec!["artist c.b.1".to_string()], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1041, }, }, @@ -307,8 +339,8 @@ macro_rules! library_collection { "artist c.b.2.1".to_string(), "artist c.b.2.2".to_string(), ], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 756, }, }, @@ -326,7 +358,11 @@ macro_rules! library_collection { albums: vec![ Album { id: AlbumId { - year: 1995, + date: AlbumDate { + year: 1995, + month: AlbumMonth::None, + day: 0, + }, title: "album_title d.a".to_string(), }, tracks: vec![ @@ -336,8 +372,8 @@ macro_rules! library_collection { title: "track d.a.1".to_string(), }, artist: vec!["artist d.a.1".to_string()], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 120, }, }, @@ -350,8 +386,8 @@ macro_rules! library_collection { "artist d.a.2.1".to_string(), "artist d.a.2.2".to_string(), ], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 120, }, }, @@ -359,7 +395,11 @@ macro_rules! library_collection { }, Album { id: AlbumId { - year: 2028, + date: AlbumDate { + year: 2028, + month: AlbumMonth::None, + day: 0, + }, title: "album_title d.b".to_string(), }, tracks: vec![ @@ -369,8 +409,8 @@ macro_rules! library_collection { title: "track d.b.1".to_string(), }, artist: vec!["artist d.b.1".to_string()], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 841, }, }, @@ -383,8 +423,8 @@ macro_rules! library_collection { "artist d.b.2.1".to_string(), "artist d.b.2.2".to_string(), ], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 756, }, }, diff --git a/src/tui/testmod.rs b/src/tui/testmod.rs index 7054f71..7a3bc37 100644 --- a/src/tui/testmod.rs +++ b/src/tui/testmod.rs @@ -1,7 +1,7 @@ use musichoard::collection::{ - album::{Album, AlbumId}, + album::{Album, AlbumDate, AlbumId, AlbumMonth}, artist::{Artist, ArtistId, MusicBrainz}, - track::{Format, Quality, Track, TrackId}, + track::{Track, TrackFormat, TrackId, TrackQuality}, }; use once_cell::sync::Lazy; use std::collections::HashMap; diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 5f6b467..3a151b8 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use musichoard::collection::{ album::Album, artist::Artist, - track::{Format, Track}, + track::{Track, TrackFormat}, Collection, }; use ratatui::{ @@ -289,7 +289,9 @@ impl<'a, 'b> AlbumState<'a, 'b> { "Title: {}\n\ Year: {}", album.map(|a| a.id.title.as_str()).unwrap_or(""), - album.map(|a| a.id.year.to_string()).unwrap_or_default(), + album + .map(|a| a.id.date.year.to_string()) + .unwrap_or_default(), )); AlbumState { @@ -328,8 +330,8 @@ impl<'a, 'b> TrackState<'a, 'b> { track.map(|t| t.artist.join("; ")).unwrap_or_default(), track .map(|t| match t.quality.format { - Format::Flac => "FLAC".to_string(), - Format::Mp3 => format!("MP3 {}kbps", t.quality.bitrate), + TrackFormat::Flac => "FLAC".to_string(), + TrackFormat::Mp3 => format!("MP3 {}kbps", t.quality.bitrate), }) .unwrap_or_default(), )); diff --git a/tests/library/testmod.rs b/tests/library/testmod.rs index bfd1658..cb85c50 100644 --- a/tests/library/testmod.rs +++ b/tests/library/testmod.rs @@ -1,6 +1,9 @@ use once_cell::sync::Lazy; -use musichoard::{collection::track::Format, library::Item}; +use musichoard::{ + collection::{album::AlbumMonth, track::TrackFormat}, + library::Item, +}; pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { vec![ @@ -8,880 +11,1040 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 1, track_title: String::from("Az’"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 992, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 2, track_title: String::from("Arkaim"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1061, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 3, track_title: String::from("Bol’no mne"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1004, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 4, track_title: String::from("Leshiy"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1077, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 5, track_title: String::from("Zakliatie"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1041, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 6, track_title: String::from("Predok"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 756, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 7, track_title: String::from("Nikogda"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1059, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 8, track_title: String::from("Tam za tumanami"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1023, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 9, track_title: String::from("Potomok"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 838, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 10, track_title: String::from("Slovo"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1028, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 11, track_title: String::from("Odna"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 991, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 12, track_title: String::from("Vo moiom sadochke…"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 919, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 13, track_title: String::from("Stenka na stenku"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1039, }, Item { album_artist: String::from("Аркона"), album_artist_sort: Some(String::from("Arkona")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slovo"), track_number: 14, track_title: String::from("Zimushka"), track_artist: vec![String::from("Аркона")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 974, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2004, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Vên [re‐recorded]"), track_number: 1, track_title: String::from("Verja Urit an Bitus"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 961, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2004, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Vên [re‐recorded]"), track_number: 2, track_title: String::from("Uis Elveti"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1067, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2004, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Vên [re‐recorded]"), track_number: 3, track_title: String::from("Ôrô"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 933, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2004, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Vên [re‐recorded]"), track_number: 4, track_title: String::from("Lament"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1083, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2004, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Vên [re‐recorded]"), track_number: 5, track_title: String::from("Druid"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1073, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2004, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Vên [re‐recorded]"), track_number: 6, track_title: String::from("Jêzaïg"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1002, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 1, track_title: String::from("Samon"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 953, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 2, track_title: String::from("Primordial Breath"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1103, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 3, track_title: String::from("Inis Mona"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1117, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 4, track_title: String::from("Gray Sublime Archon"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1092, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 5, track_title: String::from("Anagantios"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 923, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 6, track_title: String::from("Bloodstained Ground"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1098, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 7, track_title: String::from("The Somber Lay"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1068, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 8, track_title: String::from("Slanias Song"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1098, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 9, track_title: String::from("Giamonios"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 825, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 10, track_title: String::from("Tarvos"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1115, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 11, track_title: String::from("Calling the Rain"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1096, }, Item { album_artist: String::from("Eluveitie"), album_artist_sort: None, album_year: 2008, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Slania"), track_number: 12, track_title: String::from("Elembivos"), track_artist: vec![String::from("Eluveitie")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1059, }, Item { album_artist: String::from("Frontside"), album_artist_sort: None, album_year: 2001, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), track_number: 1, track_title: String::from("Intro = Chaos"), track_artist: vec![String::from("Frontside")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1024, }, Item { album_artist: String::from("Frontside"), album_artist_sort: None, album_year: 2001, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), track_number: 2, track_title: String::from("Modlitwa"), track_artist: vec![String::from("Frontside")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1073, }, Item { album_artist: String::from("Frontside"), album_artist_sort: None, album_year: 2001, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), track_number: 3, track_title: String::from("Długa droga z piekła"), track_artist: vec![String::from("Frontside")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1058, }, Item { album_artist: String::from("Frontside"), album_artist_sort: None, album_year: 2001, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), track_number: 4, track_title: String::from("Synowie ognia"), track_artist: vec![String::from("Frontside")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1066, }, Item { album_artist: String::from("Frontside"), album_artist_sort: None, album_year: 2001, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), track_number: 5, track_title: String::from("1902"), track_artist: vec![String::from("Frontside")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1074, }, Item { album_artist: String::from("Frontside"), album_artist_sort: None, album_year: 2001, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), track_number: 6, track_title: String::from("Krew za krew"), track_artist: vec![String::from("Frontside")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1080, }, Item { album_artist: String::from("Frontside"), album_artist_sort: None, album_year: 2001, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), track_number: 7, track_title: String::from("Kulminacja"), track_artist: vec![String::from("Frontside")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 992, }, Item { album_artist: String::from("Frontside"), album_artist_sort: None, album_year: 2001, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), track_number: 8, track_title: String::from("Judasz"), track_artist: vec![String::from("Frontside")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1018, }, Item { album_artist: String::from("Frontside"), album_artist_sort: None, album_year: 2001, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), track_number: 9, track_title: String::from("Więzy"), track_artist: vec![String::from("Frontside")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1077, }, Item { album_artist: String::from("Frontside"), album_artist_sort: None, album_year: 2001, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), track_number: 10, track_title: String::from("Zagubione dusze"), track_artist: vec![String::from("Frontside")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1033, }, Item { album_artist: String::from("Frontside"), album_artist_sort: None, album_year: 2001, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), track_number: 11, track_title: String::from("Linia życia"), track_artist: vec![String::from("Frontside")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 987, }, Item { album_artist: String::from("Heaven’s Basement"), album_artist_sort: None, album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Paper Plague"), track_number: 0, track_title: String::from("Paper Plague"), track_artist: vec![String::from("Heaven’s Basement")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 320, }, Item { album_artist: String::from("Heaven’s Basement"), album_artist_sort: Some(String::from("Heaven’s Basement")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Unbreakable"), track_number: 1, track_title: String::from("Unbreakable"), track_artist: vec![String::from("Heaven’s Basement")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 208, }, Item { album_artist: String::from("Heaven’s Basement"), album_artist_sort: Some(String::from("Heaven’s Basement")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Unbreakable"), track_number: 2, track_title: String::from("Guilt Trips and Sins"), track_artist: vec![String::from("Heaven’s Basement")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 205, }, Item { album_artist: String::from("Heaven’s Basement"), album_artist_sort: Some(String::from("Heaven’s Basement")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Unbreakable"), track_number: 3, track_title: String::from("The Long Goodbye"), track_artist: vec![String::from("Heaven’s Basement")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 227, }, Item { album_artist: String::from("Heaven’s Basement"), album_artist_sort: Some(String::from("Heaven’s Basement")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Unbreakable"), track_number: 4, track_title: String::from("Close Encounters"), track_artist: vec![String::from("Heaven’s Basement")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 213, }, Item { album_artist: String::from("Heaven’s Basement"), album_artist_sort: Some(String::from("Heaven’s Basement")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Unbreakable"), track_number: 5, track_title: String::from("Paranoia"), track_artist: vec![String::from("Heaven’s Basement")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 218, }, Item { album_artist: String::from("Heaven’s Basement"), album_artist_sort: Some(String::from("Heaven’s Basement")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Unbreakable"), track_number: 6, track_title: String::from("Let Me Out of Here"), track_artist: vec![String::from("Heaven’s Basement")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 207, }, Item { album_artist: String::from("Heaven’s Basement"), album_artist_sort: Some(String::from("Heaven’s Basement")), album_year: 2011, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Unbreakable"), track_number: 7, track_title: String::from("Leeches"), track_artist: vec![String::from("Heaven’s Basement")], - track_format: Format::Mp3, + track_format: TrackFormat::Mp3, track_bitrate: 225, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1984, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Ride the Lightning"), track_number: 1, track_title: String::from("Fight Fire with Fire"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 954, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1984, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Ride the Lightning"), track_number: 2, track_title: String::from("Ride the Lightning"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 951, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1984, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Ride the Lightning"), track_number: 3, track_title: String::from("For Whom the Bell Tolls"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 889, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1984, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Ride the Lightning"), track_number: 4, track_title: String::from("Fade to Black"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 939, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1984, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Ride the Lightning"), track_number: 5, track_title: String::from("Trapped under Ice"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 955, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1984, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Ride the Lightning"), track_number: 6, track_title: String::from("Escape"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 941, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1984, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Ride the Lightning"), track_number: 7, track_title: String::from("Creeping Death"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 958, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1984, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("Ride the Lightning"), track_number: 8, track_title: String::from("The Call of Ktulu"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 888, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 1, track_title: String::from("The Ecstasy of Gold"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 875, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 2, track_title: String::from("The Call of Ktulu"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1030, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 3, track_title: String::from("Master of Puppets"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1082, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 4, track_title: String::from("Of Wolf and Man"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1115, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 5, track_title: String::from("The Thing That Should Not Be"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1029, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 6, track_title: String::from("Fuel"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1057, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 7, track_title: String::from("The Memory Remains"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1080, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 8, track_title: String::from("No Leaf Clover"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1004, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 9, track_title: String::from("Hero of the Day"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 962, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 10, track_title: String::from("Devil’s Dance"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1076, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 11, track_title: String::from("Bleeding Me"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 993, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 12, track_title: String::from("Nothing Else Matters"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 875, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 13, track_title: String::from("Until It Sleeps"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1038, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 14, track_title: String::from("For Whom the Bell Tolls"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1072, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 15, track_title: String::from("−Human"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1029, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 16, track_title: String::from("Wherever I May Roam"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1035, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 17, track_title: String::from("Outlaw Torn"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1042, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 18, track_title: String::from("Sad but True"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1082, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 19, track_title: String::from("One"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 1017, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 20, track_title: String::from("Enter Sandman"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 993, }, Item { album_artist: String::from("Metallica"), album_artist_sort: None, album_year: 1999, + album_month: AlbumMonth::None, + album_day: 0, album_title: String::from("S&M"), track_number: 21, track_title: String::from("Battery"), track_artist: vec![String::from("Metallica")], - track_format: Format::Flac, + track_format: TrackFormat::Flac, track_bitrate: 967, }, ] diff --git a/tests/testlib.rs b/tests/testlib.rs index ef4821e..3cf1b98 100644 --- a/tests/testlib.rs +++ b/tests/testlib.rs @@ -2,9 +2,9 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use musichoard::collection::{ - album::{Album, AlbumId}, + album::{Album, AlbumDate, AlbumId, AlbumMonth}, artist::{Artist, ArtistId, MusicBrainz}, - track::{Format, Quality, Track, TrackId}, + track::{Track, TrackFormat, TrackId, TrackQuality}, Collection, }; @@ -33,7 +33,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { ]), albums: vec![Album { id: AlbumId { - year: 2011, + date: AlbumDate{ + year: 2011, + month: AlbumMonth::None, + day: 0, + }, title: String::from("Slovo"), }, tracks: vec![ @@ -43,8 +47,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Az’"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 992, }, }, @@ -54,8 +58,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Arkaim"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1061, }, }, @@ -65,8 +69,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Bol’no mne"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1004, }, }, @@ -76,8 +80,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Leshiy"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1077, }, }, @@ -87,8 +91,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Zakliatie"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1041, }, }, @@ -98,8 +102,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Predok"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 756, }, }, @@ -109,8 +113,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Nikogda"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1059, }, }, @@ -120,8 +124,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Tam za tumanami"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1023, }, }, @@ -131,8 +135,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Potomok"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 838, }, }, @@ -142,8 +146,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Slovo"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1028, }, }, @@ -153,8 +157,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Odna"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 991, }, }, @@ -164,8 +168,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Vo moiom sadochke…"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 919, }, }, @@ -175,8 +179,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Stenka na stenku"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1039, }, }, @@ -186,8 +190,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Zimushka"), }, artist: vec![String::from("Аркона")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 974, }, }, @@ -213,7 +217,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { albums: vec![ Album { id: AlbumId { - year: 2004, + date: AlbumDate{ + year: 2004, + month: AlbumMonth::None, + day: 0, + }, title: String::from("Vên [re‐recorded]"), }, tracks: vec![ @@ -223,8 +231,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Verja Urit an Bitus"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 961, }, }, @@ -234,8 +242,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Uis Elveti"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1067, }, }, @@ -245,8 +253,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Ôrô"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 933, }, }, @@ -256,8 +264,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Lament"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1083, }, }, @@ -267,8 +275,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Druid"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1073, }, }, @@ -278,8 +286,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Jêzaïg"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1002, }, }, @@ -287,7 +295,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Album { id: AlbumId { - year: 2008, + date: AlbumDate{ + year: 2008, + month: AlbumMonth::None, + day: 0, + }, title: String::from("Slania"), }, tracks: vec![ @@ -297,8 +309,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Samon"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 953, }, }, @@ -308,8 +320,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Primordial Breath"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1103, }, }, @@ -319,8 +331,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Inis Mona"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1117, }, }, @@ -330,8 +342,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Gray Sublime Archon"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1092, }, }, @@ -341,8 +353,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Anagantios"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 923, }, }, @@ -352,8 +364,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Bloodstained Ground"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1098, }, }, @@ -363,8 +375,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("The Somber Lay"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1068, }, }, @@ -374,8 +386,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Slanias Song"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1098, }, }, @@ -385,8 +397,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Giamonios"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 825, }, }, @@ -396,8 +408,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Tarvos"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1115, }, }, @@ -407,8 +419,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Calling the Rain"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1096, }, }, @@ -418,8 +430,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Elembivos"), }, artist: vec![String::from("Eluveitie")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1059, }, }, @@ -445,7 +457,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { ]), albums: vec![Album { id: AlbumId { - year: 2001, + date: AlbumDate{ + year: 2001, + month: AlbumMonth::None, + day: 0, + }, title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), }, tracks: vec![ @@ -455,8 +471,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Intro = Chaos"), }, artist: vec![String::from("Frontside")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1024, }, }, @@ -466,8 +482,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Modlitwa"), }, artist: vec![String::from("Frontside")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1073, }, }, @@ -477,8 +493,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Długa droga z piekła"), }, artist: vec![String::from("Frontside")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1058, }, }, @@ -488,8 +504,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Synowie ognia"), }, artist: vec![String::from("Frontside")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1066, }, }, @@ -499,8 +515,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("1902"), }, artist: vec![String::from("Frontside")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1074, }, }, @@ -510,8 +526,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Krew za krew"), }, artist: vec![String::from("Frontside")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1080, }, }, @@ -521,8 +537,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Kulminacja"), }, artist: vec![String::from("Frontside")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 992, }, }, @@ -532,8 +548,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Judasz"), }, artist: vec![String::from("Frontside")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1018, }, }, @@ -543,8 +559,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Więzy"), }, artist: vec![String::from("Frontside")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1077, }, }, @@ -554,8 +570,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Zagubione dusze"), }, artist: vec![String::from("Frontside")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1033, }, }, @@ -565,8 +581,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Linia życia"), }, artist: vec![String::from("Frontside")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 987, }, }, @@ -593,7 +609,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { ]), albums: vec![Album { id: AlbumId { - year: 2011, + date: AlbumDate{ + year: 2011, + month: AlbumMonth::None, + day: 0, + }, title: String::from("Paper Plague"), }, tracks: vec![ @@ -603,15 +623,19 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Paper Plague"), }, artist: vec![String::from("Heaven’s Basement")], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 320, }, }, ], }, Album { id: AlbumId { - year: 2011, + date: AlbumDate{ + year: 2011, + month: AlbumMonth::None, + day: 0, + }, title: String::from("Unbreakable"), }, tracks: vec![ @@ -621,8 +645,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Unbreakable"), }, artist: vec![String::from("Heaven’s Basement")], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 208, }, }, @@ -632,8 +656,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Guilt Trips and Sins"), }, artist: vec![String::from("Heaven’s Basement")], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 205, }, }, @@ -643,8 +667,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("The Long Goodbye"), }, artist: vec![String::from("Heaven’s Basement")], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 227, }, }, @@ -654,8 +678,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Close Encounters"), }, artist: vec![String::from("Heaven’s Basement")], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 213, }, }, @@ -665,8 +689,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Paranoia"), }, artist: vec![String::from("Heaven’s Basement")], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 218, }, }, @@ -676,8 +700,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Let Me Out of Here"), }, artist: vec![String::from("Heaven’s Basement")], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 207, }, }, @@ -687,8 +711,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Leeches"), }, artist: vec![String::from("Heaven’s Basement")], - quality: Quality { - format: Format::Mp3, + quality: TrackQuality { + format: TrackFormat::Mp3, bitrate: 225, }, }, @@ -714,7 +738,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { albums: vec![ Album { id: AlbumId { - year: 1984, + date: AlbumDate{ + year: 1984, + month: AlbumMonth::None, + day: 0, + }, title: String::from("Ride the Lightning"), }, tracks: vec![ @@ -724,8 +752,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Fight Fire with Fire"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 954, }, }, @@ -735,8 +763,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Ride the Lightning"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 951, }, }, @@ -746,8 +774,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("For Whom the Bell Tolls"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 889, }, }, @@ -757,8 +785,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Fade to Black"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 939, }, }, @@ -768,8 +796,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Trapped under Ice"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 955, }, }, @@ -779,8 +807,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Escape"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 941, }, }, @@ -790,8 +818,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Creeping Death"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 958, }, }, @@ -801,8 +829,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("The Call of Ktulu"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 888, }, }, @@ -810,7 +838,11 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Album { id: AlbumId { - year: 1999, + date: AlbumDate{ + year: 1999, + month: AlbumMonth::None, + day: 0, + }, title: String::from("S&M"), }, tracks: vec![ @@ -820,8 +852,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("The Ecstasy of Gold"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 875, }, }, @@ -831,8 +863,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("The Call of Ktulu"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1030, }, }, @@ -842,8 +874,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Master of Puppets"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1082, }, }, @@ -853,8 +885,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Of Wolf and Man"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1115, }, }, @@ -864,8 +896,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("The Thing That Should Not Be"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1029, }, }, @@ -875,8 +907,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Fuel"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1057, }, }, @@ -886,8 +918,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("The Memory Remains"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1080, }, }, @@ -897,8 +929,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("No Leaf Clover"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1004, }, }, @@ -908,8 +940,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Hero of the Day"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 962, }, }, @@ -919,8 +951,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Devil’s Dance"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1076, }, }, @@ -930,8 +962,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Bleeding Me"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 993, }, }, @@ -941,8 +973,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Nothing Else Matters"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 875, }, }, @@ -952,8 +984,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Until It Sleeps"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1038, }, }, @@ -963,8 +995,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("For Whom the Bell Tolls"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1072, }, }, @@ -974,8 +1006,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("−Human"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1029, }, }, @@ -985,8 +1017,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Wherever I May Roam"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1035, }, }, @@ -996,8 +1028,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Outlaw Torn"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1042, }, }, @@ -1007,8 +1039,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Sad but True"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1082, }, }, @@ -1018,8 +1050,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("One"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 1017, }, }, @@ -1029,8 +1061,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Enter Sandman"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 993, }, }, @@ -1040,8 +1072,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { title: String::from("Battery"), }, artist: vec![String::from("Metallica")], - quality: Quality { - format: Format::Flac, + quality: TrackQuality { + format: TrackFormat::Flac, bitrate: 967, }, }, -- 2.45.2 From 3b57cce671f1465bdb9228b1499e1338b63eeabf Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sat, 2 Mar 2024 09:49:50 +0100 Subject: [PATCH 02/12] Formatting --- src/core/library/beets/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/library/beets/mod.rs b/src/core/library/beets/mod.rs index 53a1ac1..d67d90d 100644 --- a/src/core/library/beets/mod.rs +++ b/src/core/library/beets/mod.rs @@ -201,7 +201,7 @@ mod testmod; mod tests { use mockall::predicate; - use crate::{core::library::testmod::LIBRARY_ITEMS, collection::album::AlbumMonth}; + use crate::{collection::album::AlbumMonth, core::library::testmod::LIBRARY_ITEMS}; use super::*; use testmod::LIBRARY_BEETS; -- 2.45.2 From b043b6b2078b95e35d52b6c06c53639214046375 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sat, 2 Mar 2024 12:25:08 +0100 Subject: [PATCH 03/12] Id should be id only --- src/core/collection/album.rs | 50 +++++- src/core/collection/track.rs | 15 +- src/core/musichoard/musichoard.rs | 35 ++--- src/core/testmod.rs | 4 +- src/tests.rs | 154 +++++++++--------- src/tui/app/selection/album.rs | 13 +- src/tui/app/selection/track.rs | 13 +- src/tui/testmod.rs | 4 +- src/tui/ui.rs | 6 +- tests/testlib.rs | 252 +++++++++++++++--------------- 10 files changed, 307 insertions(+), 239 deletions(-) diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index 492e320..fea4895 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -9,17 +9,19 @@ use crate::core::collection::{ #[derive(Clone, Debug, PartialEq, Eq)] pub struct Album { pub id: AlbumId, + pub date: AlbumDate, + pub seq: AlbumSeq, pub tracks: Vec, } /// The album identifier. #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct AlbumId { - pub date: AlbumDate, pub title: String, } // There are crates for handling dates, but we don't need much complexity beyond year-month-day. +/// The album's release date. #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct AlbumDate { pub year: u32, @@ -27,6 +29,10 @@ pub struct AlbumDate { pub day: u8, } +/// The album's sequence to determine order when two or more albums have the same release date. +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub struct AlbumSeq(pub u8); + #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub enum AlbumMonth { @@ -66,8 +72,8 @@ impl From for AlbumMonth { } impl Album { - pub fn get_sort_key(&self) -> &AlbumId { - &self.id + pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) { + (&self.date, &self.seq, &self.id) } } @@ -79,7 +85,7 @@ impl PartialOrd for Album { impl Ord for Album { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.id.cmp(&other.id) + self.get_sort_key().cmp(&other.get_sort_key()) } } @@ -116,6 +122,38 @@ mod tests { assert_eq!(>::into(255), AlbumMonth::None); } + #[test] + fn same_date_seq_cmp() { + let date = AlbumDate { + year: 2024, + month: AlbumMonth::March, + day: 2, + }; + + let album_id_1 = AlbumId { + title: String::from("album z"), + }; + let album_1 = Album { + id: album_id_1, + date: date.clone(), + seq: AlbumSeq(1), + tracks: vec![], + }; + + let album_id_2 = AlbumId { + title: String::from("album a"), + }; + let album_2 = Album { + id: album_id_2, + date: date.clone(), + seq: AlbumSeq(2), + tracks: vec![], + }; + + assert_ne!(album_1, album_2); + assert!(album_1 < album_2); + } + #[test] fn merge_album_no_overlap() { let left = FULL_COLLECTION[0].albums[0].to_owned(); @@ -129,9 +167,9 @@ mod tests { let merged = left.clone().merge(right.clone()); assert_eq!(expected, merged); - // Non-overlapping merge should be commutative. + // Non-overlapping merge should be commutative in the tracks. let merged = right.clone().merge(left.clone()); - assert_eq!(expected, merged); + assert_eq!(expected.tracks, merged.tracks); } #[test] diff --git a/src/core/collection/track.rs b/src/core/collection/track.rs index 2aa5aa1..dc4bc4f 100644 --- a/src/core/collection/track.rs +++ b/src/core/collection/track.rs @@ -4,6 +4,7 @@ use crate::core::collection::merge::Merge; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Track { pub id: TrackId, + pub number: TrackNum, pub artist: Vec, pub quality: TrackQuality, } @@ -11,10 +12,13 @@ pub struct Track { /// The track identifier. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TrackId { - pub number: u32, pub title: String, } +/// The track number. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TrackNum(pub u32); + /// The track quality. Combines format and bitrate information. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TrackQuality { @@ -23,8 +27,8 @@ pub struct TrackQuality { } impl Track { - pub fn get_sort_key(&self) -> &TrackId { - &self.id + pub fn get_sort_key(&self) -> (&TrackNum, &TrackId) { + (&self.number, &self.id) } } @@ -43,7 +47,7 @@ impl PartialOrd for Track { impl Ord for Track { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.id.cmp(&other.id) + self.get_sort_key().cmp(&other.get_sort_key()) } } @@ -61,9 +65,9 @@ mod tests { fn merge_track() { let left = Track { id: TrackId { - number: 4, title: String::from("a title"), }, + number: TrackNum(4), artist: vec![String::from("left artist")], quality: TrackQuality { format: TrackFormat::Flac, @@ -72,6 +76,7 @@ mod tests { }; let right = Track { id: left.id.clone(), + number: left.number, artist: vec![String::from("right artist")], quality: TrackQuality { format: TrackFormat::Mp3, diff --git a/src/core/musichoard/musichoard.rs b/src/core/musichoard/musichoard.rs index 2cd0ad5..779f259 100644 --- a/src/core/musichoard/musichoard.rs +++ b/src/core/musichoard/musichoard.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use crate::core::{ collection::{ - album::{Album, AlbumDate, AlbumId}, + album::{Album, AlbumDate, AlbumId, AlbumSeq}, artist::{Artist, ArtistId}, - track::{Track, TrackId, TrackQuality}, + track::{Track, TrackId, TrackNum, TrackQuality}, Collection, Merge, }, database::IDatabase, @@ -99,19 +99,20 @@ impl MusicHoard { let artist_sort = item.album_artist_sort.map(|s| ArtistId { name: s }); let album_id = AlbumId { - date: AlbumDate { - year: item.album_year, - month: item.album_month, - day: item.album_day, - }, title: item.album_title, }; + let album_date = AlbumDate { + year: item.album_year, + month: item.album_month, + day: item.album_day, + }; + let track = Track { id: TrackId { - number: item.track_number, title: item.track_title, }, + number: TrackNum(item.track_number), artist: item.track_artist, quality: TrackQuality { format: item.track_format, @@ -153,6 +154,8 @@ impl MusicHoard { Some(album) => album.tracks.push(track), None => artist.albums.push(Album { id: album_id, + date: album_date, + seq: AlbumSeq(0), tracks: vec![track], }), } @@ -891,7 +894,7 @@ mod tests { } #[test] - fn rescan_library_album_title_date_clash() { + fn rescan_library_album_id_clash() { let mut library = MockILibrary::new(); let mut expected = LIBRARY_COLLECTION.to_owned(); @@ -899,15 +902,10 @@ mod tests { let clashed_album_id = &expected[1].albums[0].id; let mut items = LIBRARY_ITEMS.to_owned(); - for item in items.iter_mut().filter(|it| { - (it.album_year == removed_album_id.date.year) - && (it.album_month == removed_album_id.date.month) - && (it.album_day == removed_album_id.date.day) - && (it.album_title == removed_album_id.title) - }) { - item.album_year = clashed_album_id.date.year; - item.album_month = clashed_album_id.date.month; - item.album_day = clashed_album_id.date.day; + for item in items + .iter_mut() + .filter(|it| it.album_title == removed_album_id.title) + { item.album_title = clashed_album_id.title.clone(); } @@ -925,6 +923,7 @@ mod tests { let mut music_hoard = MusicHoard::library(library); music_hoard.rescan_library().unwrap(); + assert_eq!(music_hoard.get_collection()[0], expected[0]); assert_eq!(music_hoard.get_collection(), &expected); } diff --git a/src/core/testmod.rs b/src/core/testmod.rs index 971587b..e5863ae 100644 --- a/src/core/testmod.rs +++ b/src/core/testmod.rs @@ -2,9 +2,9 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use crate::core::collection::{ - album::{Album, AlbumDate, AlbumId, AlbumMonth}, + album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq}, artist::{Artist, ArtistId, MusicBrainz}, - track::{Track, TrackFormat, TrackId, TrackQuality}, + track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality}, }; use crate::tests::*; diff --git a/src/tests.rs b/src/tests.rs index 058c808..6125799 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -11,19 +11,20 @@ macro_rules! library_collection { albums: vec![ Album { id: AlbumId { - date: AlbumDate { - year: 1998, - month: AlbumMonth::None, - day: 0, - }, title: "album_title a.a".to_string(), }, + date: AlbumDate { + year: 1998, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: "track a.a.1".to_string(), }, + number: TrackNum(1), artist: vec!["artist a.a.1".to_string()], quality: TrackQuality { format: TrackFormat::Flac, @@ -32,9 +33,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 2, title: "track a.a.2".to_string(), }, + number: TrackNum(2), artist: vec![ "artist a.a.2.1".to_string(), "artist a.a.2.2".to_string(), @@ -46,9 +47,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 3, title: "track a.a.3".to_string(), }, + number: TrackNum(3), artist: vec!["artist a.a.3".to_string()], quality: TrackQuality { format: TrackFormat::Flac, @@ -57,9 +58,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 4, title: "track a.a.4".to_string(), }, + number: TrackNum(4), artist: vec!["artist a.a.4".to_string()], quality: TrackQuality { format: TrackFormat::Flac, @@ -70,19 +71,20 @@ macro_rules! library_collection { }, Album { id: AlbumId { - date: AlbumDate { - year: 2015, - month: AlbumMonth::April, - day: 0, - }, title: "album_title a.b".to_string(), }, + date: AlbumDate { + year: 2015, + month: AlbumMonth::April, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: "track a.b.1".to_string(), }, + number: TrackNum(1), artist: vec!["artist a.b.1".to_string()], quality: TrackQuality { format: TrackFormat::Flac, @@ -91,9 +93,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 2, title: "track a.b.2".to_string(), }, + number: TrackNum(2), artist: vec!["artist a.b.2".to_string()], quality: TrackQuality { format: TrackFormat::Flac, @@ -114,19 +116,20 @@ macro_rules! library_collection { albums: vec![ Album { id: AlbumId { - date: AlbumDate { - year: 2003, - month: AlbumMonth::June, - day: 6, - }, title: "album_title b.a".to_string(), }, + date: AlbumDate { + year: 2003, + month: AlbumMonth::June, + day: 6, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: "track b.a.1".to_string(), }, + number: TrackNum(1), artist: vec!["artist b.a.1".to_string()], quality: TrackQuality { format: TrackFormat::Mp3, @@ -135,9 +138,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 2, title: "track b.a.2".to_string(), }, + number: TrackNum(2), artist: vec![ "artist b.a.2.1".to_string(), "artist b.a.2.2".to_string(), @@ -151,19 +154,20 @@ macro_rules! library_collection { }, Album { id: AlbumId { - date: AlbumDate { - year: 2008, - month: AlbumMonth::None, - day: 0, - }, title: "album_title b.b".to_string(), }, + date: AlbumDate { + year: 2008, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: "track b.b.1".to_string(), }, + number: TrackNum(1), artist: vec!["artist b.b.1".to_string()], quality: TrackQuality { format: TrackFormat::Flac, @@ -172,9 +176,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 2, title: "track b.b.2".to_string(), }, + number: TrackNum(2), artist: vec![ "artist b.b.2.1".to_string(), "artist b.b.2.2".to_string(), @@ -188,19 +192,20 @@ macro_rules! library_collection { }, Album { id: AlbumId { - date: AlbumDate { - year: 2009, - month: AlbumMonth::None, - day: 0, - }, title: "album_title b.c".to_string(), }, + date: AlbumDate { + year: 2009, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: "track b.c.1".to_string(), }, + number: TrackNum(1), artist: vec!["artist b.c.1".to_string()], quality: TrackQuality { format: TrackFormat::Mp3, @@ -209,9 +214,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 2, title: "track b.c.2".to_string(), }, + number: TrackNum(2), artist: vec![ "artist b.c.2.1".to_string(), "artist b.c.2.2".to_string(), @@ -225,19 +230,20 @@ macro_rules! library_collection { }, Album { id: AlbumId { - date: AlbumDate { - year: 2015, - month: AlbumMonth::None, - day: 0, - }, title: "album_title b.d".to_string(), }, + date: AlbumDate { + year: 2015, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: "track b.d.1".to_string(), }, + number: TrackNum(1), artist: vec!["artist b.d.1".to_string()], quality: TrackQuality { format: TrackFormat::Mp3, @@ -246,9 +252,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 2, title: "track b.d.2".to_string(), }, + number: TrackNum(2), artist: vec![ "artist b.d.2.1".to_string(), "artist b.d.2.2".to_string(), @@ -274,19 +280,20 @@ macro_rules! library_collection { albums: vec![ Album { id: AlbumId { - date: AlbumDate { - year: 1985, - month: AlbumMonth::None, - day: 0, - }, title: "album_title c.a".to_string(), }, + date: AlbumDate { + year: 1985, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: "track c.a.1".to_string(), }, + number: TrackNum(1), artist: vec!["artist c.a.1".to_string()], quality: TrackQuality { format: TrackFormat::Mp3, @@ -295,9 +302,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 2, title: "track c.a.2".to_string(), }, + number: TrackNum(2), artist: vec![ "artist c.a.2.1".to_string(), "artist c.a.2.2".to_string(), @@ -311,19 +318,20 @@ macro_rules! library_collection { }, Album { id: AlbumId { - date: AlbumDate { - year: 2018, - month: AlbumMonth::None, - day: 0, - }, title: "album_title c.b".to_string(), }, + date: AlbumDate { + year: 2018, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: "track c.b.1".to_string(), }, + number: TrackNum(1), artist: vec!["artist c.b.1".to_string()], quality: TrackQuality { format: TrackFormat::Flac, @@ -332,9 +340,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 2, title: "track c.b.2".to_string(), }, + number: TrackNum(2), artist: vec![ "artist c.b.2.1".to_string(), "artist c.b.2.2".to_string(), @@ -358,19 +366,20 @@ macro_rules! library_collection { albums: vec![ Album { id: AlbumId { - date: AlbumDate { - year: 1995, - month: AlbumMonth::None, - day: 0, - }, title: "album_title d.a".to_string(), }, + date: AlbumDate { + year: 1995, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: "track d.a.1".to_string(), }, + number: TrackNum(1), artist: vec!["artist d.a.1".to_string()], quality: TrackQuality { format: TrackFormat::Mp3, @@ -379,9 +388,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 2, title: "track d.a.2".to_string(), }, + number: TrackNum(2), artist: vec![ "artist d.a.2.1".to_string(), "artist d.a.2.2".to_string(), @@ -395,19 +404,20 @@ macro_rules! library_collection { }, Album { id: AlbumId { - date: AlbumDate { - year: 2028, - month: AlbumMonth::None, - day: 0, - }, title: "album_title d.b".to_string(), }, + date: AlbumDate { + year: 2028, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: "track d.b.1".to_string(), }, + number: TrackNum(1), artist: vec!["artist d.b.1".to_string()], quality: TrackQuality { format: TrackFormat::Flac, @@ -416,9 +426,9 @@ macro_rules! library_collection { }, Track { id: TrackId { - number: 2, title: "track d.b.2".to_string(), }, + number: TrackNum(2), artist: vec![ "artist d.b.2.1".to_string(), "artist d.b.2.2".to_string(), diff --git a/src/tui/app/selection/album.rs b/src/tui/app/selection/album.rs index 1329d95..c2fc06a 100644 --- a/src/tui/app/selection/album.rs +++ b/src/tui/app/selection/album.rs @@ -1,7 +1,7 @@ use std::cmp; use musichoard::collection::{ - album::{Album, AlbumId}, + album::{Album, AlbumDate, AlbumId, AlbumSeq}, track::Track, }; @@ -28,7 +28,7 @@ impl AlbumSelection { pub fn reinitialise(&mut self, albums: &[Album], album: Option) { if let Some(album) = album { - let result = albums.binary_search_by(|a| a.get_sort_key().cmp(&album.album_id)); + let result = albums.binary_search_by(|a| a.get_sort_key().cmp(&album.get_sort_key())); match result { Ok(index) => self.reinitialise_with_index(albums, index, album.track), Err(index) => self.reinitialise_with_index(albums, index, None), @@ -161,7 +161,7 @@ impl AlbumSelection { } pub struct IdSelectAlbum { - album_id: AlbumId, + key: (AlbumDate, AlbumSeq, AlbumId), track: Option, } @@ -169,12 +169,17 @@ impl IdSelectAlbum { pub fn get(albums: &[Album], selection: &AlbumSelection) -> Option { selection.state.list.selected().map(|index| { let album = &albums[index]; + let key = album.get_sort_key(); IdSelectAlbum { - album_id: album.get_sort_key().clone(), + key: (key.0.to_owned(), key.1.to_owned(), key.2.to_owned()), track: IdSelectTrack::get(&album.tracks, &selection.track), } }) } + + pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) { + (&self.key.0, &self.key.1, &self.key.2) + } } #[cfg(test)] diff --git a/src/tui/app/selection/track.rs b/src/tui/app/selection/track.rs index feedac5..a5445a1 100644 --- a/src/tui/app/selection/track.rs +++ b/src/tui/app/selection/track.rs @@ -1,6 +1,6 @@ use std::cmp; -use musichoard::collection::track::{Track, TrackId}; +use musichoard::collection::track::{Track, TrackId, TrackNum}; use crate::tui::app::selection::{Delta, SelectionState, WidgetState}; @@ -20,7 +20,7 @@ impl TrackSelection { pub fn reinitialise(&mut self, tracks: &[Track], track: Option) { if let Some(track) = track { - let result = tracks.binary_search_by(|t| t.get_sort_key().cmp(&track.track_id)); + let result = tracks.binary_search_by(|t| t.get_sort_key().cmp(&track.get_sort_key())); match result { Ok(index) | Err(index) => self.reinitialise_with_index(tracks, index), } @@ -101,18 +101,23 @@ impl TrackSelection { } pub struct IdSelectTrack { - track_id: TrackId, + key: (TrackNum, TrackId), } impl IdSelectTrack { pub fn get(tracks: &[Track], selection: &TrackSelection) -> Option { selection.state.list.selected().map(|index| { let track = &tracks[index]; + let key = track.get_sort_key(); IdSelectTrack { - track_id: track.get_sort_key().clone(), + key: (key.0.to_owned(), key.1.to_owned()), } }) } + + pub fn get_sort_key(&self) -> (&TrackNum, &TrackId) { + (&self.key.0, &self.key.1) + } } #[cfg(test)] diff --git a/src/tui/testmod.rs b/src/tui/testmod.rs index 7a3bc37..8f3e8ef 100644 --- a/src/tui/testmod.rs +++ b/src/tui/testmod.rs @@ -1,7 +1,7 @@ use musichoard::collection::{ - album::{Album, AlbumDate, AlbumId, AlbumMonth}, + album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq}, artist::{Artist, ArtistId, MusicBrainz}, - track::{Track, TrackFormat, TrackId, TrackQuality}, + track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality}, }; use once_cell::sync::Lazy; use std::collections::HashMap; diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 3a151b8..f1b425f 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -289,9 +289,7 @@ impl<'a, 'b> AlbumState<'a, 'b> { "Title: {}\n\ Year: {}", album.map(|a| a.id.title.as_str()).unwrap_or(""), - album - .map(|a| a.id.date.year.to_string()) - .unwrap_or_default(), + album.map(|a| a.date.year.to_string()).unwrap_or_default(), )); AlbumState { @@ -325,7 +323,7 @@ impl<'a, 'b> TrackState<'a, 'b> { Title: {}\n\ Artist: {}\n\ Quality: {}", - track.map(|t| t.id.number.to_string()).unwrap_or_default(), + track.map(|t| t.number.0.to_string()).unwrap_or_default(), track.map(|t| t.id.title.as_str()).unwrap_or(""), track.map(|t| t.artist.join("; ")).unwrap_or_default(), track diff --git a/tests/testlib.rs b/tests/testlib.rs index 3cf1b98..bc9fc2c 100644 --- a/tests/testlib.rs +++ b/tests/testlib.rs @@ -2,9 +2,9 @@ use once_cell::sync::Lazy; use std::collections::HashMap; use musichoard::collection::{ - album::{Album, AlbumDate, AlbumId, AlbumMonth}, + album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq}, artist::{Artist, ArtistId, MusicBrainz}, - track::{Track, TrackFormat, TrackId, TrackQuality}, + track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality}, Collection, }; @@ -33,19 +33,20 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { ]), albums: vec![Album { id: AlbumId { - date: AlbumDate{ - year: 2011, - month: AlbumMonth::None, - day: 0, - }, title: String::from("Slovo"), }, + date: AlbumDate { + year: 2011, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: String::from("Az’"), }, + number: TrackNum(1), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -54,9 +55,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 2, title: String::from("Arkaim"), }, + number: TrackNum(2), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -65,9 +66,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 3, title: String::from("Bol’no mne"), }, + number: TrackNum(3), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -76,9 +77,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 4, title: String::from("Leshiy"), }, + number: TrackNum(4), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -87,9 +88,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 5, title: String::from("Zakliatie"), }, + number: TrackNum(5), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -98,9 +99,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 6, title: String::from("Predok"), }, + number: TrackNum(6), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -109,9 +110,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 7, title: String::from("Nikogda"), }, + number: TrackNum(7), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -120,9 +121,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 8, title: String::from("Tam za tumanami"), }, + number: TrackNum(8), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -131,9 +132,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 9, title: String::from("Potomok"), }, + number: TrackNum(9), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -142,9 +143,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 10, title: String::from("Slovo"), }, + number: TrackNum(10), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -153,9 +154,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 11, title: String::from("Odna"), }, + number: TrackNum(11), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -164,9 +165,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 12, title: String::from("Vo moiom sadochke…"), }, + number: TrackNum(12), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -175,9 +176,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 13, title: String::from("Stenka na stenku"), }, + number: TrackNum(13), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -186,9 +187,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 14, title: String::from("Zimushka"), }, + number: TrackNum(14), artist: vec![String::from("Аркона")], quality: TrackQuality { format: TrackFormat::Flac, @@ -217,19 +218,20 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { albums: vec![ Album { id: AlbumId { - date: AlbumDate{ - year: 2004, - month: AlbumMonth::None, - day: 0, - }, title: String::from("Vên [re‐recorded]"), }, + date: AlbumDate { + year: 2004, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: String::from("Verja Urit an Bitus"), }, + number: TrackNum(1), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -238,9 +240,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 2, title: String::from("Uis Elveti"), }, + number: TrackNum(2), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -249,9 +251,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 3, title: String::from("Ôrô"), }, + number: TrackNum(3), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -260,9 +262,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 4, title: String::from("Lament"), }, + number: TrackNum(4), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -271,9 +273,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 5, title: String::from("Druid"), }, + number: TrackNum(5), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -282,9 +284,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 6, title: String::from("Jêzaïg"), }, + number: TrackNum(6), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -295,19 +297,20 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Album { id: AlbumId { - date: AlbumDate{ - year: 2008, - month: AlbumMonth::None, - day: 0, - }, title: String::from("Slania"), }, + date: AlbumDate { + year: 2008, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: String::from("Samon"), }, + number: TrackNum(1), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -316,9 +319,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 2, title: String::from("Primordial Breath"), }, + number: TrackNum(2), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -327,9 +330,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 3, title: String::from("Inis Mona"), }, + number: TrackNum(3), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -338,9 +341,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 4, title: String::from("Gray Sublime Archon"), }, + number: TrackNum(4), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -349,9 +352,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 5, title: String::from("Anagantios"), }, + number: TrackNum(5), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -360,9 +363,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 6, title: String::from("Bloodstained Ground"), }, + number: TrackNum(6), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -371,9 +374,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 7, title: String::from("The Somber Lay"), }, + number: TrackNum(7), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -382,9 +385,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 8, title: String::from("Slanias Song"), }, + number: TrackNum(8), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -393,9 +396,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 9, title: String::from("Giamonios"), }, + number: TrackNum(9), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -404,9 +407,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 10, title: String::from("Tarvos"), }, + number: TrackNum(10), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -415,9 +418,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 11, title: String::from("Calling the Rain"), }, + number: TrackNum(11), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -426,9 +429,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 12, title: String::from("Elembivos"), }, + number: TrackNum(12), artist: vec![String::from("Eluveitie")], quality: TrackQuality { format: TrackFormat::Flac, @@ -457,19 +460,20 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { ]), albums: vec![Album { id: AlbumId { - date: AlbumDate{ - year: 2001, - month: AlbumMonth::None, - day: 0, - }, title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), }, + date: AlbumDate { + year: 2001, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: String::from("Intro = Chaos"), }, + number: TrackNum(1), artist: vec![String::from("Frontside")], quality: TrackQuality { format: TrackFormat::Flac, @@ -478,9 +482,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 2, title: String::from("Modlitwa"), }, + number: TrackNum(2), artist: vec![String::from("Frontside")], quality: TrackQuality { format: TrackFormat::Flac, @@ -489,9 +493,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 3, title: String::from("Długa droga z piekła"), }, + number: TrackNum(3), artist: vec![String::from("Frontside")], quality: TrackQuality { format: TrackFormat::Flac, @@ -500,9 +504,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 4, title: String::from("Synowie ognia"), }, + number: TrackNum(4), artist: vec![String::from("Frontside")], quality: TrackQuality { format: TrackFormat::Flac, @@ -511,9 +515,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 5, title: String::from("1902"), }, + number: TrackNum(5), artist: vec![String::from("Frontside")], quality: TrackQuality { format: TrackFormat::Flac, @@ -522,9 +526,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 6, title: String::from("Krew za krew"), }, + number: TrackNum(6), artist: vec![String::from("Frontside")], quality: TrackQuality { format: TrackFormat::Flac, @@ -533,9 +537,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 7, title: String::from("Kulminacja"), }, + number: TrackNum(7), artist: vec![String::from("Frontside")], quality: TrackQuality { format: TrackFormat::Flac, @@ -544,9 +548,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 8, title: String::from("Judasz"), }, + number: TrackNum(8), artist: vec![String::from("Frontside")], quality: TrackQuality { format: TrackFormat::Flac, @@ -555,9 +559,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 9, title: String::from("Więzy"), }, + number: TrackNum(9), artist: vec![String::from("Frontside")], quality: TrackQuality { format: TrackFormat::Flac, @@ -566,9 +570,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 10, title: String::from("Zagubione dusze"), }, + number: TrackNum(10), artist: vec![String::from("Frontside")], quality: TrackQuality { format: TrackFormat::Flac, @@ -577,9 +581,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 11, title: String::from("Linia życia"), }, + number: TrackNum(11), artist: vec![String::from("Frontside")], quality: TrackQuality { format: TrackFormat::Flac, @@ -609,19 +613,20 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { ]), albums: vec![Album { id: AlbumId { - date: AlbumDate{ - year: 2011, - month: AlbumMonth::None, - day: 0, - }, title: String::from("Paper Plague"), }, + date: AlbumDate { + year: 2011, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 0, title: String::from("Paper Plague"), }, + number: TrackNum(0), artist: vec![String::from("Heaven’s Basement")], quality: TrackQuality { format: TrackFormat::Mp3, @@ -631,19 +636,20 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { ], }, Album { id: AlbumId { - date: AlbumDate{ - year: 2011, - month: AlbumMonth::None, - day: 0, - }, title: String::from("Unbreakable"), }, + date: AlbumDate { + year: 2011, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: String::from("Unbreakable"), }, + number: TrackNum(1), artist: vec![String::from("Heaven’s Basement")], quality: TrackQuality { format: TrackFormat::Mp3, @@ -652,9 +658,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 2, title: String::from("Guilt Trips and Sins"), }, + number: TrackNum(2), artist: vec![String::from("Heaven’s Basement")], quality: TrackQuality { format: TrackFormat::Mp3, @@ -663,9 +669,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 3, title: String::from("The Long Goodbye"), }, + number: TrackNum(3), artist: vec![String::from("Heaven’s Basement")], quality: TrackQuality { format: TrackFormat::Mp3, @@ -674,9 +680,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 4, title: String::from("Close Encounters"), }, + number: TrackNum(4), artist: vec![String::from("Heaven’s Basement")], quality: TrackQuality { format: TrackFormat::Mp3, @@ -685,9 +691,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 5, title: String::from("Paranoia"), }, + number: TrackNum(5), artist: vec![String::from("Heaven’s Basement")], quality: TrackQuality { format: TrackFormat::Mp3, @@ -696,9 +702,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 6, title: String::from("Let Me Out of Here"), }, + number: TrackNum(6), artist: vec![String::from("Heaven’s Basement")], quality: TrackQuality { format: TrackFormat::Mp3, @@ -707,9 +713,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 7, title: String::from("Leeches"), }, + number: TrackNum(7), artist: vec![String::from("Heaven’s Basement")], quality: TrackQuality { format: TrackFormat::Mp3, @@ -738,19 +744,20 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { albums: vec![ Album { id: AlbumId { - date: AlbumDate{ - year: 1984, - month: AlbumMonth::None, - day: 0, - }, title: String::from("Ride the Lightning"), }, + date: AlbumDate { + year: 1984, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: String::from("Fight Fire with Fire"), }, + number: TrackNum(1), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -759,9 +766,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 2, title: String::from("Ride the Lightning"), }, + number: TrackNum(2), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -770,9 +777,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 3, title: String::from("For Whom the Bell Tolls"), }, + number: TrackNum(3), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -781,9 +788,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 4, title: String::from("Fade to Black"), }, + number: TrackNum(4), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -792,9 +799,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 5, title: String::from("Trapped under Ice"), }, + number: TrackNum(5), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -803,9 +810,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 6, title: String::from("Escape"), }, + number: TrackNum(6), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -814,9 +821,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 7, title: String::from("Creeping Death"), }, + number: TrackNum(7), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -825,9 +832,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 8, title: String::from("The Call of Ktulu"), }, + number: TrackNum(8), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -838,19 +845,20 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Album { id: AlbumId { - date: AlbumDate{ - year: 1999, - month: AlbumMonth::None, - day: 0, - }, title: String::from("S&M"), }, + date: AlbumDate { + year: 1999, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(0), tracks: vec![ Track { id: TrackId { - number: 1, title: String::from("The Ecstasy of Gold"), }, + number: TrackNum(1), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -859,9 +867,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 2, title: String::from("The Call of Ktulu"), }, + number: TrackNum(2), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -870,9 +878,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 3, title: String::from("Master of Puppets"), }, + number: TrackNum(3), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -881,9 +889,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 4, title: String::from("Of Wolf and Man"), }, + number: TrackNum(4), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -892,9 +900,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 5, title: String::from("The Thing That Should Not Be"), }, + number: TrackNum(5), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -903,9 +911,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 6, title: String::from("Fuel"), }, + number: TrackNum(6), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -914,9 +922,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 7, title: String::from("The Memory Remains"), }, + number: TrackNum(7), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -925,9 +933,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 8, title: String::from("No Leaf Clover"), }, + number: TrackNum(8), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -936,9 +944,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 9, title: String::from("Hero of the Day"), }, + number: TrackNum(9), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -947,9 +955,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 10, title: String::from("Devil’s Dance"), }, + number: TrackNum(10), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -958,9 +966,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 11, title: String::from("Bleeding Me"), }, + number: TrackNum(11), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -969,9 +977,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 12, title: String::from("Nothing Else Matters"), }, + number: TrackNum(12), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -980,9 +988,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 13, title: String::from("Until It Sleeps"), }, + number: TrackNum(13), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -991,9 +999,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 14, title: String::from("For Whom the Bell Tolls"), }, + number: TrackNum(14), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -1002,9 +1010,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 15, title: String::from("−Human"), }, + number: TrackNum(15), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -1013,9 +1021,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 16, title: String::from("Wherever I May Roam"), }, + number: TrackNum(16), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -1024,9 +1032,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 17, title: String::from("Outlaw Torn"), }, + number: TrackNum(17), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -1035,9 +1043,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 18, title: String::from("Sad but True"), }, + number: TrackNum(18), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -1046,9 +1054,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 19, title: String::from("One"), }, + number: TrackNum(19), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -1057,9 +1065,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 20, title: String::from("Enter Sandman"), }, + number: TrackNum(20), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, @@ -1068,9 +1076,9 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { }, Track { id: TrackId { - number: 21, title: String::from("Battery"), }, + number: TrackNum(21), artist: vec![String::from("Metallica")], quality: TrackQuality { format: TrackFormat::Flac, -- 2.45.2 From bb060770b42bc672dfc05ae0fdcbe91cb1646b6d Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sat, 2 Mar 2024 12:56:49 +0100 Subject: [PATCH 04/12] Rename IdSelection to KeySelection --- src/core/collection/artist.rs | 14 +++++++------- src/tui/app/machine/reload.rs | 10 +++++----- src/tui/app/selection/album.rs | 22 +++++++++++----------- src/tui/app/selection/artist.rs | 33 +++++++++++++++++++-------------- src/tui/app/selection/mod.rs | 14 +++++++------- src/tui/app/selection/track.rs | 14 +++++++------- 6 files changed, 56 insertions(+), 51 deletions(-) diff --git a/src/core/collection/artist.rs b/src/core/collection/artist.rs index c2a09fa..8081475 100644 --- a/src/core/collection/artist.rs +++ b/src/core/collection/artist.rs @@ -41,8 +41,8 @@ impl Artist { } } - pub fn get_sort_key(&self) -> &ArtistId { - self.sort.as_ref().unwrap_or(&self.id) + pub fn get_sort_key(&self) -> (&ArtistId,) { + (self.sort.as_ref().unwrap_or(&self.id),) } pub fn set_sort_key>(&mut self, sort: SORT) { @@ -114,7 +114,7 @@ impl PartialOrd for Artist { impl Ord for Artist { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.get_sort_key().cmp(other.get_sort_key()) + self.get_sort_key().cmp(&other.get_sort_key()) } } @@ -260,7 +260,7 @@ mod tests { assert_eq!(artist.id, artist_id); assert_eq!(artist.sort, None); - assert_eq!(artist.get_sort_key(), &artist_id); + assert_eq!(artist.get_sort_key(), (&artist_id,)); assert!(artist < Artist::new(sort_id_1.clone())); assert!(artist < Artist::new(sort_id_2.clone())); @@ -268,7 +268,7 @@ mod tests { assert_eq!(artist.id, artist_id); assert_eq!(artist.sort.as_ref(), Some(&sort_id_1)); - assert_eq!(artist.get_sort_key(), &sort_id_1); + assert_eq!(artist.get_sort_key(), (&sort_id_1,)); assert!(artist > Artist::new(artist_id.clone())); assert!(artist < Artist::new(sort_id_2.clone())); @@ -276,7 +276,7 @@ mod tests { assert_eq!(artist.id, artist_id); assert_eq!(artist.sort.as_ref(), Some(&sort_id_2)); - assert_eq!(artist.get_sort_key(), &sort_id_2); + assert_eq!(artist.get_sort_key(), (&sort_id_2,)); assert!(artist > Artist::new(artist_id.clone())); assert!(artist > Artist::new(sort_id_1.clone())); @@ -284,7 +284,7 @@ mod tests { assert_eq!(artist.id, artist_id); assert_eq!(artist.sort, None); - assert_eq!(artist.get_sort_key(), &artist_id); + assert_eq!(artist.get_sort_key(), (&artist_id,)); assert!(artist < Artist::new(sort_id_1.clone())); assert!(artist < Artist::new(sort_id_2.clone())); } diff --git a/src/tui/app/machine/reload.rs b/src/tui/app/machine/reload.rs index ee6a57f..cc1638e 100644 --- a/src/tui/app/machine/reload.rs +++ b/src/tui/app/machine/reload.rs @@ -1,7 +1,7 @@ use crate::tui::{ app::{ machine::{App, AppInner, AppMachine}, - selection::IdSelection, + selection::KeySelection, AppPublic, AppState, IAppInteractReload, }, lib::IMusicHoard, @@ -36,7 +36,7 @@ impl IAppInteractReload for AppMachine { type APP = App; fn reload_library(mut self) -> Self::APP { - let previous = IdSelection::get( + let previous = KeySelection::get( self.inner.music_hoard.get_collection(), &self.inner.selection, ); @@ -45,7 +45,7 @@ impl IAppInteractReload for AppMachine { } fn reload_database(mut self) -> Self::APP { - let previous = IdSelection::get( + let previous = KeySelection::get( self.inner.music_hoard.get_collection(), &self.inner.selection, ); @@ -63,11 +63,11 @@ impl IAppInteractReload for AppMachine { } trait IAppInteractReloadPrivate { - fn refresh(self, previous: IdSelection, result: Result<(), musichoard::Error>) -> App; + fn refresh(self, previous: KeySelection, result: Result<(), musichoard::Error>) -> App; } impl IAppInteractReloadPrivate for AppMachine { - fn refresh(mut self, previous: IdSelection, result: Result<(), musichoard::Error>) -> App { + fn refresh(mut self, previous: KeySelection, result: Result<(), musichoard::Error>) -> App { match result { Ok(()) => { self.inner diff --git a/src/tui/app/selection/album.rs b/src/tui/app/selection/album.rs index c2fc06a..cce9b5a 100644 --- a/src/tui/app/selection/album.rs +++ b/src/tui/app/selection/album.rs @@ -6,7 +6,7 @@ use musichoard::collection::{ }; use crate::tui::app::selection::{ - track::{IdSelectTrack, TrackSelection}, + track::{KeySelectTrack, TrackSelection}, Delta, SelectionState, WidgetState, }; @@ -26,7 +26,7 @@ impl AlbumSelection { selection } - pub fn reinitialise(&mut self, albums: &[Album], album: Option) { + pub fn reinitialise(&mut self, albums: &[Album], album: Option) { if let Some(album) = album { let result = albums.binary_search_by(|a| a.get_sort_key().cmp(&album.get_sort_key())); match result { @@ -42,7 +42,7 @@ impl AlbumSelection { &mut self, albums: &[Album], index: usize, - active_track: Option, + active_track: Option, ) { if albums.is_empty() { self.state.list.select(None); @@ -160,19 +160,19 @@ impl AlbumSelection { } } -pub struct IdSelectAlbum { +pub struct KeySelectAlbum { key: (AlbumDate, AlbumSeq, AlbumId), - track: Option, + track: Option, } -impl IdSelectAlbum { +impl KeySelectAlbum { pub fn get(albums: &[Album], selection: &AlbumSelection) -> Option { selection.state.list.selected().map(|index| { let album = &albums[index]; let key = album.get_sort_key(); - IdSelectAlbum { + KeySelectAlbum { key: (key.0.to_owned(), key.1.to_owned(), key.2.to_owned()), - track: IdSelectTrack::get(&album.tracks, &selection.track), + track: KeySelectTrack::get(&album.tracks, &selection.track), } }) } @@ -335,20 +335,20 @@ mod tests { // Re-initialise. let expected = sel.clone(); - let active_album = IdSelectAlbum::get(albums, &sel); + let active_album = KeySelectAlbum::get(albums, &sel); sel.reinitialise(albums, active_album); assert_eq!(sel, expected); // Re-initialise out-of-bounds. let mut expected = sel.clone(); expected.decrement(albums, Delta::Line); - let active_album = IdSelectAlbum::get(albums, &sel); + let active_album = KeySelectAlbum::get(albums, &sel); sel.reinitialise(&albums[..(albums.len() - 1)], active_album); assert_eq!(sel, expected); // Re-initialise empty. let expected = AlbumSelection::initialise(&[]); - let active_album = IdSelectAlbum::get(albums, &sel); + let active_album = KeySelectAlbum::get(albums, &sel); sel.reinitialise(&[], active_album); assert_eq!(sel, expected); } diff --git a/src/tui/app/selection/artist.rs b/src/tui/app/selection/artist.rs index 8b76d5a..aa96d25 100644 --- a/src/tui/app/selection/artist.rs +++ b/src/tui/app/selection/artist.rs @@ -7,7 +7,7 @@ use musichoard::collection::{ }; use crate::tui::app::selection::{ - album::{AlbumSelection, IdSelectAlbum}, + album::{AlbumSelection, KeySelectAlbum}, Delta, SelectionState, WidgetState, }; @@ -27,9 +27,9 @@ impl ArtistSelection { selection } - pub fn reinitialise(&mut self, artists: &[Artist], active: Option) { + pub fn reinitialise(&mut self, artists: &[Artist], active: Option) { if let Some(active) = active { - let result = artists.binary_search_by(|a| a.get_sort_key().cmp(&active.artist_id)); + let result = artists.binary_search_by(|a| a.get_sort_key().cmp(&active.get_sort_key())); match result { Ok(index) => self.reinitialise_with_index(artists, index, active.album), Err(index) => self.reinitialise_with_index(artists, index, None), @@ -43,7 +43,7 @@ impl ArtistSelection { &mut self, artists: &[Artist], index: usize, - active_album: Option, + active_album: Option, ) { if artists.is_empty() { self.state.list.select(None); @@ -193,21 +193,26 @@ impl ArtistSelection { } } -pub struct IdSelectArtist { - artist_id: ArtistId, - album: Option, +pub struct KeySelectArtist { + key: (ArtistId,), + album: Option, } -impl IdSelectArtist { +impl KeySelectArtist { pub fn get(artists: &[Artist], selection: &ArtistSelection) -> Option { selection.state.list.selected().map(|index| { let artist = &artists[index]; - IdSelectArtist { - artist_id: artist.get_sort_key().clone(), - album: IdSelectAlbum::get(&artist.albums, &selection.album), + let key = artist.get_sort_key(); + KeySelectArtist { + key: (key.0.to_owned(),), + album: KeySelectAlbum::get(&artist.albums, &selection.album), } }) } + + pub fn get_sort_key(&self) -> (&ArtistId,) { + (&self.key.0,) + } } #[cfg(test)] @@ -385,20 +390,20 @@ mod tests { // Re-initialise. let expected = sel.clone(); - let active_artist = IdSelectArtist::get(artists, &sel); + let active_artist = KeySelectArtist::get(artists, &sel); sel.reinitialise(artists, active_artist); assert_eq!(sel, expected); // Re-initialise out-of-bounds. let mut expected = sel.clone(); expected.decrement(artists, Delta::Line); - let active_artist = IdSelectArtist::get(artists, &sel); + let active_artist = KeySelectArtist::get(artists, &sel); sel.reinitialise(&artists[..(artists.len() - 1)], active_artist); assert_eq!(sel, expected); // Re-initialise empty. let expected = ArtistSelection::initialise(&[]); - let active_artist = IdSelectArtist::get(artists, &sel); + let active_artist = KeySelectArtist::get(artists, &sel); sel.reinitialise(&[], active_artist); assert_eq!(sel, expected); } diff --git a/src/tui/app/selection/mod.rs b/src/tui/app/selection/mod.rs index 8cddcad..74cfb9c 100644 --- a/src/tui/app/selection/mod.rs +++ b/src/tui/app/selection/mod.rs @@ -5,7 +5,7 @@ mod track; use musichoard::collection::{album::Album, artist::Artist, track::Track, Collection}; use ratatui::widgets::ListState; -use artist::{ArtistSelection, IdSelectArtist}; +use artist::{ArtistSelection, KeySelectArtist}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Category { @@ -64,7 +64,7 @@ impl Selection { self.artist.album.track.state.list = selected.track; } - pub fn select_by_id(&mut self, artists: &[Artist], selected: IdSelection) { + pub fn select_by_id(&mut self, artists: &[Artist], selected: KeySelection) { self.artist.reinitialise(artists, selected.artist); } @@ -229,14 +229,14 @@ impl ListSelection { } } -pub struct IdSelection { - artist: Option, +pub struct KeySelection { + artist: Option, } -impl IdSelection { +impl KeySelection { pub fn get(collection: &Collection, selection: &Selection) -> Self { - IdSelection { - artist: IdSelectArtist::get(collection, &selection.artist), + KeySelection { + artist: KeySelectArtist::get(collection, &selection.artist), } } } diff --git a/src/tui/app/selection/track.rs b/src/tui/app/selection/track.rs index a5445a1..adec55b 100644 --- a/src/tui/app/selection/track.rs +++ b/src/tui/app/selection/track.rs @@ -18,7 +18,7 @@ impl TrackSelection { selection } - pub fn reinitialise(&mut self, tracks: &[Track], track: Option) { + pub fn reinitialise(&mut self, tracks: &[Track], track: Option) { if let Some(track) = track { let result = tracks.binary_search_by(|t| t.get_sort_key().cmp(&track.get_sort_key())); match result { @@ -100,16 +100,16 @@ impl TrackSelection { } } -pub struct IdSelectTrack { +pub struct KeySelectTrack { key: (TrackNum, TrackId), } -impl IdSelectTrack { +impl KeySelectTrack { pub fn get(tracks: &[Track], selection: &TrackSelection) -> Option { selection.state.list.selected().map(|index| { let track = &tracks[index]; let key = track.get_sort_key(); - IdSelectTrack { + KeySelectTrack { key: (key.0.to_owned(), key.1.to_owned()), } }) @@ -215,20 +215,20 @@ mod tests { // Re-initialise. let expected = sel.clone(); - let active_track = IdSelectTrack::get(tracks, &sel); + let active_track = KeySelectTrack::get(tracks, &sel); sel.reinitialise(tracks, active_track); assert_eq!(sel, expected); // Re-initialise out-of-bounds. let mut expected = sel.clone(); expected.decrement(tracks, Delta::Line); - let active_track = IdSelectTrack::get(tracks, &sel); + let active_track = KeySelectTrack::get(tracks, &sel); sel.reinitialise(&tracks[..(tracks.len() - 1)], active_track); assert_eq!(sel, expected); // Re-initialise empty. let expected = TrackSelection::initialise(&[]); - let active_track = IdSelectTrack::get(tracks, &sel); + let active_track = KeySelectTrack::get(tracks, &sel); sel.reinitialise(&[], active_track); assert_eq!(sel, expected); } -- 2.45.2 From e6969bfc52fd4e86e753d95f9040479b00d31697 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sat, 2 Mar 2024 16:18:50 +0100 Subject: [PATCH 05/12] New database storage --- src/core/database/json/mod.rs | 15 +++++++++-- src/core/database/json/testmod.rs | 28 +++++++++++++++---- src/core/database/serde/deserialize.rs | 37 +++++++++++++++++++++----- src/core/database/serde/mod.rs | 1 + src/core/database/serde/serialize.rs | 21 +++++++++++++-- 5 files changed, 86 insertions(+), 16 deletions(-) diff --git a/src/core/database/json/mod.rs b/src/core/database/json/mod.rs index a525dac..1e59ebd 100644 --- a/src/core/database/json/mod.rs +++ b/src/core/database/json/mod.rs @@ -72,7 +72,11 @@ mod tests { use mockall::predicate; use crate::core::{ - collection::{artist::Artist, Collection}, + collection::{ + album::{AlbumDate, AlbumMonth}, + artist::Artist, + Collection, + }, testmod::FULL_COLLECTION, }; @@ -82,7 +86,14 @@ mod tests { fn expected() -> Collection { let mut expected = FULL_COLLECTION.to_owned(); for artist in expected.iter_mut() { - artist.albums.clear(); + for album in artist.albums.iter_mut() { + album.date = AlbumDate { + year: 0, + month: AlbumMonth::None, + day: 0, + }; + album.tracks.clear(); + } } expected } diff --git a/src/core/database/json/testmod.rs b/src/core/database/json/testmod.rs index c492aed..84cc693 100644 --- a/src/core/database/json/testmod.rs +++ b/src/core/database/json/testmod.rs @@ -1,5 +1,5 @@ pub static DATABASE_JSON: &str = "{\ - \"V20240210\":\ + \"V20240302\":\ [\ {\ \"name\":\"Album_Artist ‘A’\",\ @@ -8,7 +8,11 @@ pub static DATABASE_JSON: &str = "{\ \"properties\":{\ \"MusicButler\":[\"https://www.musicbutler.io/artist-page/000000000\"],\ \"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-a/download-streaming-albums\"]\ - }\ + },\ + \"albums\":[\ + {\"title\":\"album_title a.a\",\"seq\":0},\ + {\"title\":\"album_title a.b\",\"seq\":0}\ + ]\ },\ {\ \"name\":\"Album_Artist ‘B’\",\ @@ -21,19 +25,33 @@ pub static DATABASE_JSON: &str = "{\ \"https://www.musicbutler.io/artist-page/111111112\"\ ],\ \"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums\"]\ - }\ + },\ + \"albums\":[\ + {\"title\":\"album_title b.a\",\"seq\":0},\ + {\"title\":\"album_title b.b\",\"seq\":0},\ + {\"title\":\"album_title b.c\",\"seq\":0},\ + {\"title\":\"album_title b.d\",\"seq\":0}\ + ]\ },\ {\ \"name\":\"The Album_Artist ‘C’\",\ \"sort\":\"Album_Artist ‘C’, The\",\ \"musicbrainz\":\"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111\",\ - \"properties\":{}\ + \"properties\":{},\ + \"albums\":[\ + {\"title\":\"album_title c.a\",\"seq\":0},\ + {\"title\":\"album_title c.b\",\"seq\":0}\ + ]\ },\ {\ \"name\":\"Album_Artist ‘D’\",\ \"sort\":null,\ \"musicbrainz\":null,\ - \"properties\":{}\ + \"properties\":{},\ + \"albums\":[\ + {\"title\":\"album_title d.a\",\"seq\":0},\ + {\"title\":\"album_title d.b\",\"seq\":0}\ + ]\ }\ ]\ }"; diff --git a/src/core/database/serde/deserialize.rs b/src/core/database/serde/deserialize.rs index a4f0e16..ced43e7 100644 --- a/src/core/database/serde/deserialize.rs +++ b/src/core/database/serde/deserialize.rs @@ -2,12 +2,13 @@ use std::collections::HashMap; use serde::Deserialize; -use crate::{ - collection::artist::{ArtistId, MusicBrainz}, - core::{ - collection::{artist::Artist, Collection}, - database::{serde::Database, LoadError}, +use crate::core::{ + collection::{ + album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq}, + artist::{Artist, ArtistId, MusicBrainz}, + Collection, }, + database::{serde::Database, LoadError}, }; pub type DeserializeDatabase = Database; @@ -18,6 +19,13 @@ pub struct DeserializeArtist { sort: Option, musicbrainz: Option, properties: HashMap>, + albums: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct DeserializeAlbum { + title: String, + seq: u8, } impl TryFrom for Collection { @@ -25,7 +33,7 @@ impl TryFrom for Collection { fn try_from(database: DeserializeDatabase) -> Result { match database { - Database::V20240210(collection) => collection + Database::V20240302(collection) | Database::V20240210(collection) => collection .into_iter() .map(|artist| artist.try_into()) .collect(), @@ -42,7 +50,22 @@ impl TryFrom for Artist { sort: artist.sort.map(ArtistId::new), musicbrainz: artist.musicbrainz.map(MusicBrainz::new).transpose()?, properties: artist.properties, - albums: vec![], + albums: artist.albums.into_iter().map(Into::into).collect(), }) } } + +impl From for Album { + fn from(album: DeserializeAlbum) -> Self { + Album { + id: AlbumId { title: album.title }, + date: AlbumDate { + year: 0, + month: AlbumMonth::None, + day: 0, + }, + seq: AlbumSeq(album.seq), + tracks: vec![], + } + } +} diff --git a/src/core/database/serde/mod.rs b/src/core/database/serde/mod.rs index 453b017..ac41ef7 100644 --- a/src/core/database/serde/mod.rs +++ b/src/core/database/serde/mod.rs @@ -7,5 +7,6 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub enum Database { + V20240302(Vec), V20240210(Vec), } diff --git a/src/core/database/serde/serialize.rs b/src/core/database/serde/serialize.rs index 6043ee2..56e477c 100644 --- a/src/core/database/serde/serialize.rs +++ b/src/core/database/serde/serialize.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use serde::Serialize; use crate::core::{ - collection::{artist::Artist, Collection}, + collection::{album::Album, artist::Artist, Collection}, database::serde::Database, }; @@ -15,11 +15,18 @@ pub struct SerializeArtist<'a> { sort: Option<&'a str>, musicbrainz: Option<&'a str>, properties: BTreeMap<&'a str, &'a Vec>, + albums: Vec>, +} + +#[derive(Debug, Serialize)] +pub struct SerializeAlbum<'a> { + title: &'a str, + seq: u8, } impl<'a> From<&'a Collection> for SerializeDatabase<'a> { fn from(collection: &'a Collection) -> Self { - Database::V20240210(collection.iter().map(|artist| artist.into()).collect()) + Database::V20240302(collection.iter().map(|artist| artist.into()).collect()) } } @@ -34,6 +41,16 @@ impl<'a> From<&'a Artist> for SerializeArtist<'a> { .iter() .map(|(k, v)| (k.as_ref(), v)) .collect(), + albums: artist.albums.iter().map(Into::into).collect(), + } + } +} + +impl<'a> From<&'a Album> for SerializeAlbum<'a> { + fn from(album: &'a Album) -> Self { + SerializeAlbum { + title: &album.id.title, + seq: album.seq.0, } } } -- 2.45.2 From c77b4acbd42e13bb8a3f1a5b2096fbd754c10ea2 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sat, 2 Mar 2024 18:41:31 +0100 Subject: [PATCH 06/12] Fix merging for albums --- src/core/collection/album.rs | 21 ++++++++++++- src/core/collection/artist.rs | 14 +++++++-- src/core/collection/merge.rs | 42 +++++++++++++++++++++++++- src/core/collection/mod.rs | 2 +- src/core/database/json/mod.rs | 12 ++------ src/core/database/json/testmod.rs | 12 ++++---- src/core/database/serde/deserialize.rs | 17 +++++------ src/core/database/serde/mod.rs | 8 ----- src/core/database/serde/serialize.rs | 12 ++++---- src/core/musichoard/musichoard.rs | 16 ++-------- src/tests.rs | 11 +++++-- tests/database/json.rs | 7 +++-- tests/files/database/database.json | 2 +- 13 files changed, 113 insertions(+), 63 deletions(-) diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index fea4895..03e2acb 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -1,7 +1,7 @@ use std::mem; use crate::core::collection::{ - merge::{Merge, MergeSorted}, + merge::{Merge, MergeSorted, WithId}, track::Track, }; @@ -14,6 +14,14 @@ pub struct Album { pub tracks: Vec, } +impl WithId for Album { + type Id = AlbumId; + + fn id(&self) -> &Self::Id { + &self.id + } +} + /// The album identifier. #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct AlbumId { @@ -29,6 +37,16 @@ pub struct AlbumDate { pub day: u8, } +impl Default for AlbumDate { + fn default() -> Self { + AlbumDate { + year: 0, + month: AlbumMonth::None, + day: 0, + } + } +} + /// The album's sequence to determine order when two or more albums have the same release date. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct AlbumSeq(pub u8); @@ -92,6 +110,7 @@ impl Ord for Album { impl Merge for Album { fn merge_in_place(&mut self, other: Self) { assert_eq!(self.id, other.id); + self.seq = std::cmp::max(self.seq, other.seq); let tracks = mem::take(&mut self.tracks); self.tracks = MergeSorted::new(tracks.into_iter(), other.tracks.into_iter()).collect(); } diff --git a/src/core/collection/artist.rs b/src/core/collection/artist.rs index 8081475..ab9d8b0 100644 --- a/src/core/collection/artist.rs +++ b/src/core/collection/artist.rs @@ -9,7 +9,7 @@ use uuid::Uuid; use crate::core::collection::{ album::Album, - merge::{Merge, MergeSorted}, + merge::{Merge, MergeCollections, WithId}, Error, }; @@ -23,6 +23,14 @@ pub struct Artist { pub albums: Vec, } +impl WithId for Artist { + type Id = ArtistId; + + fn id(&self) -> &Self::Id { + &self.id + } +} + /// The artist identifier. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ArtistId { @@ -121,11 +129,13 @@ impl Ord for Artist { impl Merge for Artist { fn merge_in_place(&mut self, other: Self) { assert_eq!(self.id, other.id); + self.sort = self.sort.take().or(other.sort); self.musicbrainz = self.musicbrainz.take().or(other.musicbrainz); self.properties.merge_in_place(other.properties); + let albums = mem::take(&mut self.albums); - self.albums = MergeSorted::new(albums.into_iter(), other.albums.into_iter()).collect(); + self.albums = MergeCollections::merge_iter(albums, other.albums); } } diff --git a/src/core/collection/merge.rs b/src/core/collection/merge.rs index e2a5fc5..76ed15a 100644 --- a/src/core/collection/merge.rs +++ b/src/core/collection/merge.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, collections::HashMap, hash::Hash, iter::Peekable}; +use std::{cmp::Ordering, collections::HashMap, hash::Hash, iter::Peekable, marker::PhantomData}; /// A trait for merging two objects. The merge is asymmetric with the left argument considered to be /// the primary whose properties are to be kept in case of collisions. @@ -79,3 +79,43 @@ where } } } + +pub trait WithId { + type Id; + + fn id(&self) -> &Self::Id; +} + +pub struct MergeCollections { + _id: PhantomData, + _t: PhantomData, +} + +impl MergeCollections +where + ID: Eq + Hash + Clone, + T: WithId + Merge + Ord, +{ + pub fn merge_iter>(primary: IT, secondary: IT) -> Vec { + let primary = primary + .into_iter() + .map(|item| (item.id().clone(), item)) + .collect(); + Self::merge(primary, secondary) + } + + pub fn merge>(mut primary: HashMap, secondary: IT) -> Vec { + for secondary_item in secondary { + if let Some(ref mut primary_item) = primary.get_mut(secondary_item.id()) { + primary_item.merge_in_place(secondary_item); + } else { + primary.insert(secondary_item.id().clone(), secondary_item); + } + } + + let mut collection: Vec = primary.into_values().collect(); + collection.sort_unstable(); + + collection + } +} diff --git a/src/core/collection/mod.rs b/src/core/collection/mod.rs index ea42e15..ef4386d 100644 --- a/src/core/collection/mod.rs +++ b/src/core/collection/mod.rs @@ -5,7 +5,7 @@ pub mod artist; pub mod track; mod merge; -pub use merge::Merge; +pub use merge::MergeCollections; use std::fmt::{self, Display}; diff --git a/src/core/database/json/mod.rs b/src/core/database/json/mod.rs index 1e59ebd..5eec609 100644 --- a/src/core/database/json/mod.rs +++ b/src/core/database/json/mod.rs @@ -72,11 +72,7 @@ mod tests { use mockall::predicate; use crate::core::{ - collection::{ - album::{AlbumDate, AlbumMonth}, - artist::Artist, - Collection, - }, + collection::{album::AlbumDate, artist::Artist, Collection}, testmod::FULL_COLLECTION, }; @@ -87,11 +83,7 @@ mod tests { let mut expected = FULL_COLLECTION.to_owned(); for artist in expected.iter_mut() { for album in artist.albums.iter_mut() { - album.date = AlbumDate { - year: 0, - month: AlbumMonth::None, - day: 0, - }; + album.date = AlbumDate::default(); album.tracks.clear(); } } diff --git a/src/core/database/json/testmod.rs b/src/core/database/json/testmod.rs index 84cc693..d328f8e 100644 --- a/src/core/database/json/testmod.rs +++ b/src/core/database/json/testmod.rs @@ -10,8 +10,8 @@ pub static DATABASE_JSON: &str = "{\ \"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-a/download-streaming-albums\"]\ },\ \"albums\":[\ - {\"title\":\"album_title a.a\",\"seq\":0},\ - {\"title\":\"album_title a.b\",\"seq\":0}\ + {\"title\":\"album_title a.a\",\"seq\":1},\ + {\"title\":\"album_title a.b\",\"seq\":1}\ ]\ },\ {\ @@ -27,10 +27,10 @@ pub static DATABASE_JSON: &str = "{\ \"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums\"]\ },\ \"albums\":[\ - {\"title\":\"album_title b.a\",\"seq\":0},\ - {\"title\":\"album_title b.b\",\"seq\":0},\ - {\"title\":\"album_title b.c\",\"seq\":0},\ - {\"title\":\"album_title b.d\",\"seq\":0}\ + {\"title\":\"album_title b.a\",\"seq\":1},\ + {\"title\":\"album_title b.b\",\"seq\":3},\ + {\"title\":\"album_title b.c\",\"seq\":2},\ + {\"title\":\"album_title b.d\",\"seq\":4}\ ]\ },\ {\ diff --git a/src/core/database/serde/deserialize.rs b/src/core/database/serde/deserialize.rs index ced43e7..8b403ec 100644 --- a/src/core/database/serde/deserialize.rs +++ b/src/core/database/serde/deserialize.rs @@ -4,14 +4,17 @@ use serde::Deserialize; use crate::core::{ collection::{ - album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq}, + album::{Album, AlbumDate, AlbumId, AlbumSeq}, artist::{Artist, ArtistId, MusicBrainz}, Collection, }, - database::{serde::Database, LoadError}, + database::LoadError, }; -pub type DeserializeDatabase = Database; +#[derive(Debug, Deserialize)] +pub enum DeserializeDatabase { + V20240302(Vec), +} #[derive(Debug, Deserialize)] pub struct DeserializeArtist { @@ -33,7 +36,7 @@ impl TryFrom for Collection { fn try_from(database: DeserializeDatabase) -> Result { match database { - Database::V20240302(collection) | Database::V20240210(collection) => collection + DeserializeDatabase::V20240302(collection) => collection .into_iter() .map(|artist| artist.try_into()) .collect(), @@ -59,11 +62,7 @@ impl From for Album { fn from(album: DeserializeAlbum) -> Self { Album { id: AlbumId { title: album.title }, - date: AlbumDate { - year: 0, - month: AlbumMonth::None, - day: 0, - }, + date: AlbumDate::default(), seq: AlbumSeq(album.seq), tracks: vec![], } diff --git a/src/core/database/serde/mod.rs b/src/core/database/serde/mod.rs index ac41ef7..d0a878f 100644 --- a/src/core/database/serde/mod.rs +++ b/src/core/database/serde/mod.rs @@ -2,11 +2,3 @@ pub mod deserialize; pub mod serialize; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize)] -pub enum Database { - V20240302(Vec), - V20240210(Vec), -} diff --git a/src/core/database/serde/serialize.rs b/src/core/database/serde/serialize.rs index 56e477c..5276b54 100644 --- a/src/core/database/serde/serialize.rs +++ b/src/core/database/serde/serialize.rs @@ -2,12 +2,12 @@ use std::collections::BTreeMap; use serde::Serialize; -use crate::core::{ - collection::{album::Album, artist::Artist, Collection}, - database::serde::Database, -}; +use crate::core::collection::{album::Album, artist::Artist, Collection}; -pub type SerializeDatabase<'a> = Database>; +#[derive(Debug, Serialize)] +pub enum SerializeDatabase<'a> { + V20240302(Vec>), +} #[derive(Debug, Serialize)] pub struct SerializeArtist<'a> { @@ -26,7 +26,7 @@ pub struct SerializeAlbum<'a> { impl<'a> From<&'a Collection> for SerializeDatabase<'a> { fn from(collection: &'a Collection) -> Self { - Database::V20240302(collection.iter().map(|artist| artist.into()).collect()) + SerializeDatabase::V20240302(collection.iter().map(Into::into).collect()) } } diff --git a/src/core/musichoard/musichoard.rs b/src/core/musichoard/musichoard.rs index 779f259..8995e86 100644 --- a/src/core/musichoard/musichoard.rs +++ b/src/core/musichoard/musichoard.rs @@ -5,7 +5,7 @@ use crate::core::{ album::{Album, AlbumDate, AlbumId, AlbumSeq}, artist::{Artist, ArtistId}, track::{Track, TrackId, TrackNum, TrackQuality}, - Collection, Merge, + Collection, MergeCollections, }, database::IDatabase, library::{ILibrary, Item, Query}, @@ -73,19 +73,7 @@ impl MusicHoard { } fn merge_collections(&self) -> Collection { - let mut primary = self.library_cache.clone(); - for secondary_artist in self.database_cache.iter().cloned() { - if let Some(ref mut primary_artist) = primary.get_mut(&secondary_artist.id) { - primary_artist.merge_in_place(secondary_artist); - } else { - primary.insert(secondary_artist.id.clone(), secondary_artist); - } - } - - let mut collection: Collection = primary.into_values().collect(); - Self::sort_artists(&mut collection); - - collection + MergeCollections::merge(self.library_cache.clone(), self.database_cache.clone()) } fn items_to_artists(items: Vec) -> Result, Error> { diff --git a/src/tests.rs b/src/tests.rs index 6125799..aa8e5b2 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -457,8 +457,7 @@ macro_rules! full_collection { artist_a.musicbrainz = Some( MusicBrainz::new( "https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000", - ) - .unwrap(), + ).unwrap(), ); artist_a.properties = HashMap::from([ @@ -472,6 +471,9 @@ macro_rules! full_collection { ]), ]); + artist_a.albums[0].seq = AlbumSeq(1); + artist_a.albums[1].seq = AlbumSeq(1); + let artist_b = iter.next().unwrap(); assert_eq!(artist_b.id.name, "Album_Artist ‘B’"); @@ -494,6 +496,11 @@ macro_rules! full_collection { ]), ]); + artist_b.albums[0].seq = AlbumSeq(1); + artist_b.albums[1].seq = AlbumSeq(3); + artist_b.albums[2].seq = AlbumSeq(2); + artist_b.albums[3].seq = AlbumSeq(4); + let artist_c = iter.next().unwrap(); assert_eq!(artist_c.id.name, "The Album_Artist ‘C’"); diff --git a/tests/database/json.rs b/tests/database/json.rs index 0f9d6b2..f2c6c52 100644 --- a/tests/database/json.rs +++ b/tests/database/json.rs @@ -4,7 +4,7 @@ use once_cell::sync::Lazy; use tempfile::NamedTempFile; use musichoard::{ - collection::{artist::Artist, Collection}, + collection::{album::AlbumDate, artist::Artist, Collection}, database::{ json::{backend::JsonDatabaseFileBackend, JsonDatabase}, IDatabase, @@ -19,7 +19,10 @@ pub static DATABASE_TEST_FILE: Lazy = fn expected() -> Collection { let mut expected = COLLECTION.to_owned(); for artist in expected.iter_mut() { - artist.albums.clear(); + for album in artist.albums.iter_mut() { + album.date = AlbumDate::default(); + album.tracks.clear(); + } } expected } diff --git a/tests/files/database/database.json b/tests/files/database/database.json index 465e2b9..3515a9b 100644 --- a/tests/files/database/database.json +++ b/tests/files/database/database.json @@ -1 +1 @@ -{"V20240210":[{"name":"Аркона","sort":"Arkona","musicbrainz":"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212","properties":{"Bandcamp":["https://arkonamoscow.bandcamp.com/"],"MusicButler":["https://www.musicbutler.io/artist-page/283448581"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/arkona/download-streaming-albums"]}},{"name":"Eluveitie","sort":null,"musicbrainz":"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/269358403"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/eluveitie/download-streaming-albums"]}},{"name":"Frontside","sort":null,"musicbrainz":"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/826588800"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/frontside/download-streaming-albums"]}},{"name":"Heaven’s Basement","sort":"Heaven’s Basement","musicbrainz":"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/291158685"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/heaven-s-basement/download-streaming-albums"]}},{"name":"Metallica","sort":null,"musicbrainz":"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/3996865"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/metallica/download-streaming-albums"]}}]} \ No newline at end of file +{"V20240302":[{"name":"Аркона","sort":"Arkona","musicbrainz":"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212","properties":{"Bandcamp":["https://arkonamoscow.bandcamp.com/"],"MusicButler":["https://www.musicbutler.io/artist-page/283448581"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/arkona/download-streaming-albums"]},"albums":[{"title":"Slovo","seq":0}]},{"name":"Eluveitie","sort":null,"musicbrainz":"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/269358403"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/eluveitie/download-streaming-albums"]},"albums":[{"title":"Vên [re‐recorded]","seq":0},{"title":"Slania","seq":0}]},{"name":"Frontside","sort":null,"musicbrainz":"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/826588800"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/frontside/download-streaming-albums"]},"albums":[{"title":"…nasze jest królestwo, potęga i chwała na wieki…","seq":0}]},{"name":"Heaven’s Basement","sort":"Heaven’s Basement","musicbrainz":"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/291158685"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/heaven-s-basement/download-streaming-albums"]},"albums":[{"title":"Paper Plague","seq":0},{"title":"Unbreakable","seq":0}]},{"name":"Metallica","sort":null,"musicbrainz":"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/3996865"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/metallica/download-streaming-albums"]},"albums":[{"title":"Ride the Lightning","seq":0},{"title":"S&M","seq":0}]}]} \ No newline at end of file -- 2.45.2 From 0fee810040547e02579de85e0422e3f900fe8a8e Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sat, 2 Mar 2024 18:43:58 +0100 Subject: [PATCH 07/12] Streamline code --- src/core/collection/merge.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/collection/merge.rs b/src/core/collection/merge.rs index 76ed15a..2173801 100644 --- a/src/core/collection/merge.rs +++ b/src/core/collection/merge.rs @@ -86,17 +86,19 @@ pub trait WithId { fn id(&self) -> &Self::Id; } -pub struct MergeCollections { +pub struct MergeCollections { _id: PhantomData, _t: PhantomData, + _it: PhantomData, } -impl MergeCollections +impl MergeCollections where ID: Eq + Hash + Clone, T: WithId + Merge + Ord, + IT: IntoIterator, { - pub fn merge_iter>(primary: IT, secondary: IT) -> Vec { + pub fn merge_iter(primary: IT, secondary: IT) -> Vec { let primary = primary .into_iter() .map(|item| (item.id().clone(), item)) @@ -104,7 +106,7 @@ where Self::merge(primary, secondary) } - pub fn merge>(mut primary: HashMap, secondary: IT) -> Vec { + pub fn merge(mut primary: HashMap, secondary: IT) -> Vec { for secondary_item in secondary { if let Some(ref mut primary_item) = primary.get_mut(secondary_item.id()) { primary_item.merge_in_place(secondary_item); -- 2.45.2 From adddc5ba2fe94fe7ea05e72e1b3df71bb4120f55 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sun, 3 Mar 2024 20:36:13 +0100 Subject: [PATCH 08/12] Enable changs via command --- src/bin/musichoard-edit.rs | 191 ++++++++++++++++++++---------- src/core/collection/album.rs | 51 ++++++-- src/core/musichoard/musichoard.rs | 133 ++++++++++++++++----- src/main.rs | 7 +- src/tui/mod.rs | 48 ++++++-- 5 files changed, 314 insertions(+), 116 deletions(-) diff --git a/src/bin/musichoard-edit.rs b/src/bin/musichoard-edit.rs index 00de904..94f7f0c 100644 --- a/src/bin/musichoard-edit.rs +++ b/src/bin/musichoard-edit.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use structopt::{clap::AppSettings, StructOpt}; use musichoard::{ - collection::artist::ArtistId, + collection::{album::AlbumId, artist::ArtistId}, database::json::{backend::JsonDatabaseFileBackend, JsonDatabase}, MusicHoard, MusicHoardBuilder, NoLibrary, }; @@ -22,56 +22,54 @@ struct Opt { database_file_path: PathBuf, #[structopt(subcommand)] - category: Category, + command: Command, } #[derive(StructOpt, Debug)] -enum Category { - #[structopt(about = "Edit artist information")] - Artist(ArtistCommand), +enum Command { + #[structopt(about = "Modify artist information")] + Artist(ArtistOpt), } -impl Category { - fn handle(self, music_hoard: &mut MH) { - match self { - Category::Artist(artist_command) => artist_command.handle(music_hoard), - } - } +#[derive(StructOpt, Debug)] +struct ArtistOpt { + // For some reason, not specyfing the artist name with the `long` name makes StructOpt failed + // for inexplicable reason. For example, it won't recognise `Abadde` or `Abadden` as a name and + // will insteady try to process it as a command. + #[structopt(long, help = "The name of the artist")] + name: String, + + #[structopt(subcommand)] + command: ArtistCommand, } #[derive(StructOpt, Debug)] enum ArtistCommand { #[structopt(about = "Add a new artist to the collection")] - Add(ArtistValue), + Add, #[structopt(about = "Remove an artist from the collection")] - Remove(ArtistValue), + Remove, #[structopt(about = "Edit the artist's sort name")] Sort(SortCommand), #[structopt(name = "musicbrainz", about = "Edit the MusicBrainz URL of an artist")] MusicBrainz(MusicBrainzCommand), - #[structopt(name = "property", about = "Edit a property of an artist")] + #[structopt(about = "Edit a property of an artist")] Property(PropertyCommand), + #[structopt(about = "Modify the artist's album information")] + Album(AlbumOpt), } #[derive(StructOpt, Debug)] enum SortCommand { #[structopt(about = "Set the provided name as the artist's sort name")] - Set(ArtistSortValue), + Set(SortValue), #[structopt(about = "Clear the artist's sort name")] - Clear(ArtistValue), + Clear, } #[derive(StructOpt, Debug)] -struct ArtistValue { - #[structopt(help = "The name of the artist")] - artist: String, -} - -#[derive(StructOpt, Debug)] -struct ArtistSortValue { - #[structopt(help = "The name of the artist")] - artist: String, - #[structopt(help = "The sort name of the artist")] +struct SortValue { + #[structopt(help = "The sort name")] sort: String, } @@ -80,13 +78,11 @@ enum MusicBrainzCommand { #[structopt(about = "Set the MusicBrainz URL overwriting any existing value")] Set(MusicBrainzValue), #[structopt(about = "Clear the MusicBrainz URL)")] - Clear(ArtistValue), + Clear, } #[derive(StructOpt, Debug)] struct MusicBrainzValue { - #[structopt(help = "The name of the artist")] - artist: String, #[structopt(help = "The MusicBrainz URL")] url: String, } @@ -105,8 +101,6 @@ enum PropertyCommand { #[derive(StructOpt, Debug)] struct PropertyValue { - #[structopt(help = "The name of the artist")] - artist: String, #[structopt(help = "The name of the property")] property: String, #[structopt(help = "The list of values")] @@ -115,101 +109,176 @@ struct PropertyValue { #[derive(StructOpt, Debug)] struct PropertyName { - #[structopt(help = "The name of the artist")] - artist: String, #[structopt(help = "The name of the property")] property: String, } -impl ArtistCommand { +#[derive(StructOpt, Debug)] +struct AlbumOpt { + // Using `long` for consistency with `ArtistOpt`. + #[structopt(long, help = "The title of the album")] + title: String, + + #[structopt(subcommand)] + command: AlbumCommand, +} + +#[derive(StructOpt, Debug)] +enum AlbumCommand { + #[structopt(about = "Edit the album's sequence value")] + Seq(AlbumSeqCommand), +} + +#[derive(StructOpt, Debug)] +enum AlbumSeqCommand { + #[structopt(about = "Set the sequence value overwriting any existing value")] + Set(AlbumSeqValue), + #[structopt(about = "Clear the sequence value")] + Clear, +} + +#[derive(StructOpt, Debug)] +struct AlbumSeqValue { + #[structopt(help = "The new sequence value")] + value: u8, +} + +impl Command { fn handle(self, music_hoard: &mut MH) { match self { - ArtistCommand::Add(artist_value) => { + Command::Artist(artist_opt) => artist_opt.handle(music_hoard), + } + } +} + +impl ArtistOpt { + fn handle(self, music_hoard: &mut MH) { + self.command.handle(music_hoard, &self.name) + } +} + +impl ArtistCommand { + fn handle(self, music_hoard: &mut MH, artist_name: &str) { + match self { + ArtistCommand::Add => { music_hoard - .add_artist(ArtistId::new(artist_value.artist)) + .add_artist(ArtistId::new(artist_name)) .expect("failed to add artist"); } - ArtistCommand::Remove(artist_value) => { + ArtistCommand::Remove => { music_hoard - .remove_artist(ArtistId::new(artist_value.artist)) + .remove_artist(ArtistId::new(artist_name)) .expect("failed to remove artist"); } ArtistCommand::Sort(sort_command) => { - sort_command.handle(music_hoard); + sort_command.handle(music_hoard, artist_name); } ArtistCommand::MusicBrainz(musicbrainz_command) => { - musicbrainz_command.handle(music_hoard) + musicbrainz_command.handle(music_hoard, artist_name) } ArtistCommand::Property(property_command) => { - property_command.handle(music_hoard); + property_command.handle(music_hoard, artist_name); + } + ArtistCommand::Album(album_opt) => { + album_opt.handle(music_hoard, artist_name); } } } } impl SortCommand { - fn handle(self, music_hoard: &mut MH) { + fn handle(self, music_hoard: &mut MH, artist_name: &str) { match self { SortCommand::Set(artist_sort_value) => music_hoard .set_artist_sort( - ArtistId::new(artist_sort_value.artist), + ArtistId::new(artist_name), ArtistId::new(artist_sort_value.sort), ) .expect("faild to set artist sort name"), - SortCommand::Clear(artist_value) => music_hoard - .clear_artist_sort(ArtistId::new(artist_value.artist)) + SortCommand::Clear => music_hoard + .clear_artist_sort(ArtistId::new(artist_name)) .expect("failed to clear artist sort name"), } } } impl MusicBrainzCommand { - fn handle(self, music_hoard: &mut MH) { + fn handle(self, music_hoard: &mut MH, artist_name: &str) { match self { MusicBrainzCommand::Set(musicbrainz_value) => music_hoard - .set_musicbrainz_url( - ArtistId::new(musicbrainz_value.artist), - musicbrainz_value.url, - ) + .set_artist_musicbrainz(ArtistId::new(artist_name), musicbrainz_value.url) .expect("failed to set MusicBrainz URL"), - MusicBrainzCommand::Clear(artist_value) => music_hoard - .clear_musicbrainz_url(ArtistId::new(artist_value.artist)) + MusicBrainzCommand::Clear => music_hoard + .clear_artist_musicbrainz(ArtistId::new(artist_name)) .expect("failed to clear MusicBrainz URL"), } } } impl PropertyCommand { - fn handle(self, music_hoard: &mut MH) { + fn handle(self, music_hoard: &mut MH, artist_name: &str) { match self { PropertyCommand::Add(property_value) => music_hoard - .add_to_property( - ArtistId::new(property_value.artist), + .add_to_artist_property( + ArtistId::new(artist_name), property_value.property, property_value.values, ) .expect("failed to add values to property"), PropertyCommand::Remove(property_value) => music_hoard - .remove_from_property( - ArtistId::new(property_value.artist), + .remove_from_artist_property( + ArtistId::new(artist_name), property_value.property, property_value.values, ) .expect("failed to remove values from property"), PropertyCommand::Set(property_value) => music_hoard - .set_property( - ArtistId::new(property_value.artist), + .set_artist_property( + ArtistId::new(artist_name), property_value.property, property_value.values, ) .expect("failed to set property"), PropertyCommand::Clear(property_name) => music_hoard - .clear_property(ArtistId::new(property_name.artist), property_name.property) + .clear_artist_property(ArtistId::new(artist_name), property_name.property) .expect("failed to clear property"), } } } +impl AlbumOpt { + fn handle(self, music_hoard: &mut MH, artist_name: &str) { + self.command.handle(music_hoard, artist_name, &self.title) + } +} + +impl AlbumCommand { + fn handle(self, music_hoard: &mut MH, artist_name: &str, album_name: &str) { + match self { + AlbumCommand::Seq(seq_command) => { + seq_command.handle(music_hoard, artist_name, album_name); + } + } + } +} + +impl AlbumSeqCommand { + fn handle(self, music_hoard: &mut MH, artist_name: &str, album_name: &str) { + match self { + AlbumSeqCommand::Set(seq_value) => music_hoard + .set_album_seq( + ArtistId::new(artist_name), + AlbumId::new(album_name), + seq_value.value, + ) + .expect("failed to set sequence value"), + AlbumSeqCommand::Clear => music_hoard + .clear_album_seq(ArtistId::new(artist_name), AlbumId::new(album_name)) + .expect("failed to clear sequence value"), + } + } +} + fn main() { let opt = Opt::from_args(); @@ -219,5 +288,5 @@ fn main() { .set_database(db) .build() .expect("failed to initialise MusicHoard"); - opt.category.handle(&mut music_hoard); + opt.command.handle(&mut music_hoard); } diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index 03e2acb..9a692e1 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -1,4 +1,7 @@ -use std::mem; +use std::{ + fmt::{self, Display}, + mem, +}; use crate::core::collection::{ merge::{Merge, MergeSorted, WithId}, @@ -30,25 +33,15 @@ pub struct AlbumId { // There are crates for handling dates, but we don't need much complexity beyond year-month-day. /// The album's release date. -#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] +#[derive(Clone, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct AlbumDate { pub year: u32, pub month: AlbumMonth, pub day: u8, } -impl Default for AlbumDate { - fn default() -> Self { - AlbumDate { - year: 0, - month: AlbumMonth::None, - day: 0, - } - } -} - /// The album's sequence to determine order when two or more albums have the same release date. -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] +#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct AlbumSeq(pub u8); #[repr(u8)] @@ -69,6 +62,12 @@ pub enum AlbumMonth { December = 12, } +impl Default for AlbumMonth { + fn default() -> Self { + AlbumMonth::None + } +} + impl From for AlbumMonth { fn from(value: u8) -> Self { match value { @@ -93,6 +92,14 @@ impl Album { pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) { (&self.date, &self.seq, &self.id) } + + pub fn set_seq(&mut self, seq: AlbumSeq) { + self.seq = seq; + } + + pub fn clear_seq(&mut self) { + self.seq = AlbumSeq::default(); + } } impl PartialOrd for Album { @@ -116,6 +123,24 @@ impl Merge for Album { } } +impl AsRef for AlbumId { + fn as_ref(&self) -> &AlbumId { + self + } +} + +impl AlbumId { + pub fn new>(name: S) -> AlbumId { + AlbumId { title: name.into() } + } +} + +impl Display for AlbumId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.title) + } +} + #[cfg(test)] mod tests { use crate::core::testmod::FULL_COLLECTION; diff --git a/src/core/musichoard/musichoard.rs b/src/core/musichoard/musichoard.rs index 8995e86..b82c8ea 100644 --- a/src/core/musichoard/musichoard.rs +++ b/src/core/musichoard/musichoard.rs @@ -171,6 +171,22 @@ impl MusicHoard { Error::CollectionError(format!("artist '{}' is not in the collection", artist_id)) }) } + + fn get_album_mut<'a>(artist: &'a mut Artist, album_id: &AlbumId) -> Option<&'a mut Album> { + artist.albums.iter_mut().find(|a| &a.id == album_id) + } + + fn get_album_mut_or_err<'a>( + artist: &'a mut Artist, + album_id: &AlbumId, + ) -> Result<&'a mut Album, Error> { + Self::get_album_mut(artist, album_id).ok_or_else(|| { + Error::CollectionError(format!( + "album '{}' does not belong to the artist", + album_id + )) + }) + } } impl MusicHoard { @@ -243,38 +259,62 @@ impl MusicHoard { Ok(()) } - fn update_collection(&mut self, func: F) -> Result<(), Error> + fn update_collection(&mut self, func: FN) -> Result<(), Error> where - F: FnOnce(&mut Collection), + FN: FnOnce(&mut Collection), { func(&mut self.pre_commit); self.commit() } - fn update_artist_and, F1, F2>( + fn update_artist_and, FNARTIST, FNCOLL>( &mut self, artist_id: ID, - f1: F1, - f2: F2, + fn_artist: FNARTIST, + fn_collection: FNCOLL, ) -> Result<(), Error> where - F1: FnOnce(&mut Artist), - F2: FnOnce(&mut Collection), + FNARTIST: FnOnce(&mut Artist), + FNCOLL: FnOnce(&mut Collection), { - f1(Self::get_artist_mut_or_err( + fn_artist(Self::get_artist_mut_or_err( &mut self.pre_commit, artist_id.as_ref(), )?); - self.update_collection(f2) + self.update_collection(fn_collection) } - fn update_artist, F>(&mut self, artist_id: ID, func: F) -> Result<(), Error> + fn update_artist, FN>( + &mut self, + artist_id: ID, + func: FN, + ) -> Result<(), Error> where - F: FnOnce(&mut Artist), + FN: FnOnce(&mut Artist), { self.update_artist_and(artist_id, func, |_| {}) } + fn update_album_and, ALBUM: AsRef, FNALBUM, FNARTIST, FNCOLL>( + &mut self, + artist_id: ARTIST, + album_id: ALBUM, + fn_album: FNALBUM, + fn_artist: FNARTIST, + fn_collection: FNCOLL, + ) -> Result<(), Error> + where + FNALBUM: FnOnce(&mut Album), + FNARTIST: FnOnce(&mut Artist), + FNCOLL: FnOnce(&mut Collection), + { + let artist = Self::get_artist_mut_or_err(&mut self.pre_commit, artist_id.as_ref())?; + let album = Self::get_album_mut_or_err(artist, album_id.as_ref())?; + fn_album(album); + fn_artist(artist); + self.update_collection(fn_collection) + } + pub fn add_artist>(&mut self, artist_id: ID) -> Result<(), Error> { let artist_id: ArtistId = artist_id.into(); @@ -315,7 +355,7 @@ impl MusicHoard { ) } - pub fn set_musicbrainz_url, S: AsRef>( + pub fn set_artist_musicbrainz, S: AsRef>( &mut self, artist_id: ID, url: S, @@ -324,14 +364,14 @@ impl MusicHoard { self.update_artist(artist_id, |artist| artist.set_musicbrainz_url(url)) } - pub fn clear_musicbrainz_url>( + pub fn clear_artist_musicbrainz>( &mut self, artist_id: ID, ) -> Result<(), Error> { self.update_artist(artist_id, |artist| artist.clear_musicbrainz_url()) } - pub fn add_to_property, S: AsRef + Into>( + pub fn add_to_artist_property, S: AsRef + Into>( &mut self, artist_id: ID, property: S, @@ -340,7 +380,7 @@ impl MusicHoard { self.update_artist(artist_id, |artist| artist.add_to_property(property, values)) } - pub fn remove_from_property, S: AsRef>( + pub fn remove_from_artist_property, S: AsRef>( &mut self, artist_id: ID, property: S, @@ -351,7 +391,7 @@ impl MusicHoard { }) } - pub fn set_property, S: AsRef + Into>( + pub fn set_artist_property, S: AsRef + Into>( &mut self, artist_id: ID, property: S, @@ -360,13 +400,42 @@ impl MusicHoard { self.update_artist(artist_id, |artist| artist.set_property(property, values)) } - pub fn clear_property, S: AsRef>( + pub fn clear_artist_property, S: AsRef>( &mut self, artist_id: ID, property: S, ) -> Result<(), Error> { self.update_artist(artist_id, |artist| artist.clear_property(property)) } + + pub fn set_album_seq, ALBUM: AsRef>( + &mut self, + artist_id: ARTIST, + album_id: ALBUM, + seq: u8, + ) -> Result<(), Error> { + self.update_album_and( + artist_id, + album_id, + |album| album.set_seq(AlbumSeq(seq)), + |artist| artist.albums.sort_unstable(), + |_| {}, + ) + } + + pub fn clear_album_seq, ALBUM: AsRef>( + &mut self, + artist_id: ARTIST, + album_id: ALBUM, + ) -> Result<(), Error> { + self.update_album_and( + artist_id, + album_id, + |album| album.clear_seq(), + |artist| artist.albums.sort_unstable(), + |_| {}, + ) + } } impl MusicHoard { @@ -518,7 +587,7 @@ mod tests { assert!(music_hoard.add_artist(artist_id.clone()).is_ok()); let actual_err = music_hoard - .set_musicbrainz_url(&artist_id, MUSICBUTLER) + .set_artist_musicbrainz(&artist_id, MUSICBUTLER) .unwrap_err(); let expected_err = Error::CollectionError(format!( "an error occurred when processing a URL: invalid MusicBrainz URL: {MUSICBUTLER}" @@ -544,23 +613,23 @@ mod tests { // Setting a URL on an artist not in the collection is an error. assert!(music_hoard - .set_musicbrainz_url(&artist_id_2, MUSICBRAINZ) + .set_artist_musicbrainz(&artist_id_2, MUSICBRAINZ) .is_err()); assert_eq!(music_hoard.collection[0].musicbrainz, expected); // Setting a URL on an artist. assert!(music_hoard - .set_musicbrainz_url(&artist_id, MUSICBRAINZ) + .set_artist_musicbrainz(&artist_id, MUSICBRAINZ) .is_ok()); _ = expected.insert(MusicBrainz::new(MUSICBRAINZ).unwrap()); assert_eq!(music_hoard.collection[0].musicbrainz, expected); // Clearing URLs on an artist that does not exist is an error. - assert!(music_hoard.clear_musicbrainz_url(&artist_id_2).is_err()); + assert!(music_hoard.clear_artist_musicbrainz(&artist_id_2).is_err()); assert_eq!(music_hoard.collection[0].musicbrainz, expected); // Clearing URLs. - assert!(music_hoard.clear_musicbrainz_url(&artist_id).is_ok()); + assert!(music_hoard.clear_artist_musicbrainz(&artist_id).is_ok()); _ = expected.take(); assert_eq!(music_hoard.collection[0].musicbrainz, expected); } @@ -582,13 +651,13 @@ mod tests { // Adding URLs to an artist not in the collection is an error. assert!(music_hoard - .add_to_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER]) + .add_to_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER]) .is_err()); assert!(music_hoard.collection[0].properties.is_empty()); // Adding mutliple URLs without clashes. assert!(music_hoard - .add_to_property(&artist_id, "MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]) + .add_to_artist_property(&artist_id, "MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]) .is_ok()); expected.push(MUSICBUTLER.to_owned()); expected.push(MUSICBUTLER_2.to_owned()); @@ -599,7 +668,7 @@ mod tests { // Removing URLs from an artist not in the collection is an error. assert!(music_hoard - .remove_from_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER]) + .remove_from_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER]) .is_err()); assert_eq!( music_hoard.collection[0].properties.get("MusicButler"), @@ -608,7 +677,11 @@ mod tests { // Removing multiple URLs without clashes. assert!(music_hoard - .remove_from_property(&artist_id, "MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]) + .remove_from_artist_property( + &artist_id, + "MusicButler", + vec![MUSICBUTLER, MUSICBUTLER_2] + ) .is_ok()); expected.clear(); assert!(music_hoard.collection[0].properties.is_empty()); @@ -631,13 +704,13 @@ mod tests { // Seting URL on an artist not in the collection is an error. assert!(music_hoard - .set_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER]) + .set_artist_property(&artist_id_2, "MusicButler", vec![MUSICBUTLER]) .is_err()); assert!(music_hoard.collection[0].properties.is_empty()); // Set URLs. assert!(music_hoard - .set_property(&artist_id, "MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]) + .set_artist_property(&artist_id, "MusicButler", vec![MUSICBUTLER, MUSICBUTLER_2]) .is_ok()); expected.clear(); expected.push(MUSICBUTLER.to_owned()); @@ -649,12 +722,12 @@ mod tests { // Clearing URLs on an artist that does not exist is an error. assert!(music_hoard - .clear_property(&artist_id_2, "MusicButler") + .clear_artist_property(&artist_id_2, "MusicButler") .is_err()); // Clear URLs. assert!(music_hoard - .clear_property(&artist_id, "MusicButler") + .clear_artist_property(&artist_id, "MusicButler") .is_ok()); expected.clear(); assert!(music_hoard.collection[0].properties.is_empty()); diff --git a/src/main.rs b/src/main.rs index 5c3dfa5..9449066 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,7 +75,12 @@ fn with(builder: MusicHoardBuilder) { let ui = Ui; // Run the TUI application. - Tui::run(terminal, app, ui, handler, listener).expect("failed to run tui"); + let result = Tui::run(terminal, app, ui, handler, listener); + if let Err(tui::Error::ListenerPanic(err)) = result { + std::panic::resume_unwind(err); + } else { + result.expect("failed to run tui") + }; } fn with_database(db_opt: DbOpt, builder: MusicHoardBuilder) { diff --git a/src/tui/mod.rs b/src/tui/mod.rs index f442ce6..c43e01a 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -11,12 +11,16 @@ pub use handler::EventHandler; pub use listener::EventListener; pub use ui::Ui; -use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; -use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; -use ratatui::backend::Backend; -use ratatui::Terminal; -use std::io; -use std::marker::PhantomData; +use crossterm::{ + event::{DisableMouseCapture, EnableMouseCapture}, + terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use ratatui::{backend::Backend, Terminal}; +use std::{ + marker::PhantomData, + ptr, + {any::Any, io}, +}; use crate::tui::{ app::{IAppAccess, IAppInteract}, @@ -26,11 +30,32 @@ use crate::tui::{ ui::IUi, }; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug)] pub enum Error { Io(String), Event(String), - ListenerPanic, + ListenerPanic(Box), +} + +impl Eq for Error {} + +impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + match self { + Error::Io(this) => match other { + Error::Io(other) => this == other, + _ => false, + }, + Error::Event(this) => match other { + Error::Event(other) => this == other, + _ => false, + }, + Error::ListenerPanic(this) => match other { + Error::ListenerPanic(other) => ptr::eq(this.as_ref(), other.as_ref()), + _ => false, + }, + } + } } impl From for Error { @@ -114,7 +139,8 @@ impl Tui { // Calling std::panic::resume_unwind(err) as recommended by the Rust docs // will not produce an error message. The panic error message is printed at // the location of the panic which at the time is hidden by the TUI. - Err(_) => return Err(Error::ListenerPanic), + // Therefore, propagate the error for the caller to resume unwinding. + Err(panic) => return Err(Error::ListenerPanic(panic)), } } @@ -301,14 +327,14 @@ mod tests { let result = Tui::main(terminal, app, ui, handler, listener); assert!(result.is_err()); - assert_eq!(result.unwrap_err(), Error::ListenerPanic); + assert!(matches!(result.unwrap_err(), Error::ListenerPanic(_))); } #[test] fn errors() { let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "error").into(); let event_err: Error = EventError::Recv.into(); - let listener_err = Error::ListenerPanic; + let listener_err = Error::ListenerPanic(Box::new("hello")); assert!(!format!("{:?}", io_err).is_empty()); assert!(!format!("{:?}", event_err).is_empty()); -- 2.45.2 From 54646b1c03f9889e2ed5ea1715908e51ed928964 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sun, 3 Mar 2024 20:47:16 +0100 Subject: [PATCH 09/12] Clippy --- src/core/collection/album.rs | 9 ++------- src/tui/mod.rs | 34 ++++++---------------------------- 2 files changed, 8 insertions(+), 35 deletions(-) diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index 9a692e1..d0222d4 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -45,8 +45,9 @@ pub struct AlbumDate { pub struct AlbumSeq(pub u8); #[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)] +#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] pub enum AlbumMonth { + #[default] None = 0, January = 1, February = 2, @@ -62,12 +63,6 @@ pub enum AlbumMonth { December = 12, } -impl Default for AlbumMonth { - fn default() -> Self { - AlbumMonth::None - } -} - impl From for AlbumMonth { fn from(value: u8) -> Self { match value { diff --git a/src/tui/mod.rs b/src/tui/mod.rs index c43e01a..a559391 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -18,7 +18,6 @@ use crossterm::{ use ratatui::{backend::Backend, Terminal}; use std::{ marker::PhantomData, - ptr, {any::Any, io}, }; @@ -37,27 +36,6 @@ pub enum Error { ListenerPanic(Box), } -impl Eq for Error {} - -impl PartialEq for Error { - fn eq(&self, other: &Self) -> bool { - match self { - Error::Io(this) => match other { - Error::Io(other) => this == other, - _ => false, - }, - Error::Event(this) => match other { - Error::Event(other) => this == other, - _ => false, - }, - Error::ListenerPanic(this) => match other { - Error::ListenerPanic(other) => ptr::eq(this.as_ref(), other.as_ref()), - _ => false, - }, - } - } -} - impl From for Error { fn from(err: io::Error) -> Error { Error::Io(err.to_string()) @@ -277,10 +255,9 @@ mod tests { let result = Tui::main(terminal, app, ui, handler, listener); assert!(result.is_err()); - assert_eq!( - result.unwrap_err(), - Error::Event(EventError::Recv.to_string()) - ); + + let recv_err = EventError::Recv.to_string(); + matches!(result.unwrap_err(), Error::Event(err) if err == recv_err); } #[test] @@ -304,8 +281,9 @@ mod tests { let result = Tui::main(terminal, app, ui, handler, listener); assert!(result.is_err()); - let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); - assert_eq!(result.unwrap_err(), Error::Event(error.to_string())); + let io_err = + EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")).to_string(); + matches!(result.unwrap_err(), Error::Event(err) if err == io_err); } #[test] -- 2.45.2 From 88d1dc2b01a3d6b3cbfa85239f9489f232da624a Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sun, 3 Mar 2024 23:21:44 +0100 Subject: [PATCH 10/12] Streamline some code --- src/core/collection/album.rs | 32 ++++ src/core/collection/artist.rs | 67 +++++++-- src/core/database/serde/deserialize.rs | 4 +- src/core/musichoard/musichoard.rs | 194 ++++++++++++++++--------- src/core/testmod.rs | 2 +- src/tests.rs | 24 ++- src/tui/testmod.rs | 3 +- tests/testlib.rs | 14 +- 8 files changed, 227 insertions(+), 113 deletions(-) diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index d0222d4..b750c4c 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -118,6 +118,12 @@ impl Merge for Album { } } +impl> From for AlbumId { + fn from(value: S) -> Self { + AlbumId::new(value) + } +} + impl AsRef for AlbumId { fn as_ref(&self) -> &AlbumId { self @@ -193,6 +199,32 @@ mod tests { assert!(album_1 < album_2); } + #[test] + fn set_clear_seq() { + let mut album = Album { + id: "an album".into(), + date: AlbumDate::default(), + seq: AlbumSeq::default(), + tracks: vec![], + }; + + assert_eq!(album.seq, AlbumSeq(0)); + + // Setting a seq on an album. + album.set_seq(AlbumSeq(6)); + assert_eq!(album.seq, AlbumSeq(6)); + + album.set_seq(AlbumSeq(6)); + assert_eq!(album.seq, AlbumSeq(6)); + + album.set_seq(AlbumSeq(8)); + assert_eq!(album.seq, AlbumSeq(8)); + + // Clearing seq. + album.clear_seq(); + assert_eq!(album.seq, AlbumSeq(0)); + } + #[test] fn merge_album_no_overlap() { let left = FULL_COLLECTION[0].albums[0].to_owned(); diff --git a/src/core/collection/artist.rs b/src/core/collection/artist.rs index ab9d8b0..372dc20 100644 --- a/src/core/collection/artist.rs +++ b/src/core/collection/artist.rs @@ -2,6 +2,7 @@ use std::{ collections::HashMap, fmt::{self, Debug, Display}, mem, + str::FromStr, }; use url::Url; @@ -139,6 +140,12 @@ impl Merge for Artist { } } +impl> From for ArtistId { + fn from(value: S) -> Self { + ArtistId::new(value) + } +} + impl AsRef for ArtistId { fn as_ref(&self) -> &ArtistId { self @@ -169,9 +176,13 @@ pub struct MusicBrainz(Url); impl MusicBrainz { /// Validate and wrap a MusicBrainz URL. - pub fn new>(url: S) -> Result { + pub fn new_from_str>(url: S) -> Result { let url = Url::parse(url.as_ref())?; + Self::new_from_url(url) + } + /// Validate and wrap a MusicBrainz URL. + pub fn new_from_url(url: Url) -> Result { if !url .domain() .map(|u| u.ends_with("musicbrainz.org")) @@ -199,11 +210,36 @@ impl AsRef for MusicBrainz { } } -impl TryFrom<&str> for MusicBrainz { +impl FromStr for MusicBrainz { + type Err = Error; + + fn from_str(s: &str) -> Result { + MusicBrainz::new_from_str(s) + } +} + +// A blanket TryFrom would be better, but https://stackoverflow.com/a/64407892 +macro_rules! impl_try_from_for_musicbrainz { + ($from:ty) => { + impl TryFrom<$from> for MusicBrainz { + type Error = Error; + + fn try_from(value: $from) -> Result { + MusicBrainz::new_from_str(value) + } + } + }; +} + +impl_try_from_for_musicbrainz!(&str); +impl_try_from_for_musicbrainz!(&String); +impl_try_from_for_musicbrainz!(String); + +impl TryFrom for MusicBrainz { type Error = Error; - fn try_from(value: &str) -> Result { - MusicBrainz::new(value) + fn try_from(value: Url) -> Result { + MusicBrainz::new_from_url(value) } } @@ -230,34 +266,35 @@ mod tests { #[test] fn musicbrainz() { let uuid = "d368baa8-21ca-4759-9731-0b2753071ad8"; - let url = format!("https://musicbrainz.org/artist/{uuid}"); - let mb = MusicBrainz::new(&url).unwrap(); - assert_eq!(url, mb.as_ref()); + let url_str = format!("https://musicbrainz.org/artist/{uuid}"); + let url: Url = url_str.as_str().try_into().unwrap(); + let mb: MusicBrainz = url.try_into().unwrap(); + assert_eq!(url_str, mb.as_ref()); assert_eq!(uuid, mb.mbid()); let url = "not a url at all".to_string(); let expected_error: Error = url::ParseError::RelativeUrlWithoutBase.into(); - let actual_error = MusicBrainz::new(url).unwrap_err(); + let actual_error = MusicBrainz::from_str(&url).unwrap_err(); assert_eq!(actual_error, expected_error); assert_eq!(actual_error.to_string(), expected_error.to_string()); let url = "https://musicbrainz.org/artist/i-am-not-a-uuid".to_string(); let expected_error: Error = Uuid::try_parse("i-am-not-a-uuid").unwrap_err().into(); - let actual_error = MusicBrainz::new(url).unwrap_err(); + let actual_error = MusicBrainz::from_str(&url).unwrap_err(); assert_eq!(actual_error, expected_error); assert_eq!(actual_error.to_string(), expected_error.to_string()); let url = "https://musicbrainz.org/artist".to_string(); let expected_error = Error::UrlError(format!("invalid MusicBrainz URL: {url}")); - let actual_error = MusicBrainz::new(&url).unwrap_err(); + let actual_error = MusicBrainz::from_str(&url).unwrap_err(); assert_eq!(actual_error, expected_error); assert_eq!(actual_error.to_string(), expected_error.to_string()); } #[test] fn urls() { - assert!(MusicBrainz::new(MUSICBRAINZ).is_ok()); - assert!(MusicBrainz::new(MUSICBUTLER).is_err()); + assert!(MusicBrainz::from_str(MUSICBRAINZ).is_ok()); + assert!(MusicBrainz::from_str(MUSICBUTLER).is_err()); } #[test] @@ -266,7 +303,7 @@ mod tests { let sort_id_1 = ArtistId::new("sort id 1"); let sort_id_2 = ArtistId::new("sort id 2"); - let mut artist = Artist::new(artist_id.clone()); + let mut artist = Artist::new(&artist_id.name); assert_eq!(artist.id, artist_id); assert_eq!(artist.sort, None); @@ -317,14 +354,14 @@ mod tests { // Setting a URL on an artist. artist.set_musicbrainz_url(MUSICBRAINZ.try_into().unwrap()); - _ = expected.insert(MusicBrainz::new(MUSICBRAINZ).unwrap()); + _ = expected.insert(MUSICBRAINZ.try_into().unwrap()); assert_eq!(artist.musicbrainz, expected); artist.set_musicbrainz_url(MUSICBRAINZ.try_into().unwrap()); assert_eq!(artist.musicbrainz, expected); artist.set_musicbrainz_url(MUSICBRAINZ_2.try_into().unwrap()); - _ = expected.insert(MusicBrainz::new(MUSICBRAINZ_2).unwrap()); + _ = expected.insert(MUSICBRAINZ_2.try_into().unwrap()); assert_eq!(artist.musicbrainz, expected); // Clearing URLs. diff --git a/src/core/database/serde/deserialize.rs b/src/core/database/serde/deserialize.rs index 8b403ec..cc4a64a 100644 --- a/src/core/database/serde/deserialize.rs +++ b/src/core/database/serde/deserialize.rs @@ -5,7 +5,7 @@ use serde::Deserialize; use crate::core::{ collection::{ album::{Album, AlbumDate, AlbumId, AlbumSeq}, - artist::{Artist, ArtistId, MusicBrainz}, + artist::{Artist, ArtistId}, Collection, }, database::LoadError, @@ -51,7 +51,7 @@ impl TryFrom for Artist { Ok(Artist { id: ArtistId::new(artist.name), sort: artist.sort.map(ArtistId::new), - musicbrainz: artist.musicbrainz.map(MusicBrainz::new).transpose()?, + musicbrainz: artist.musicbrainz.map(TryInto::try_into).transpose()?, properties: artist.properties, albums: artist.albums.into_iter().map(Into::into).collect(), }) diff --git a/src/core/musichoard/musichoard.rs b/src/core/musichoard/musichoard.rs index b82c8ea..cad9ec3 100644 --- a/src/core/musichoard/musichoard.rs +++ b/src/core/musichoard/musichoard.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use crate::core::{ collection::{ album::{Album, AlbumDate, AlbumId, AlbumSeq}, - artist::{Artist, ArtistId}, + artist::{Artist, ArtistId, MusicBrainz}, track::{Track, TrackId, TrackNum, TrackQuality}, Collection, MergeCollections, }, @@ -259,63 +259,61 @@ impl MusicHoard { Ok(()) } - fn update_collection(&mut self, func: FN) -> Result<(), Error> + fn update_collection(&mut self, fn_coll: FnColl) -> Result<(), Error> where - FN: FnOnce(&mut Collection), + FnColl: FnOnce(&mut Collection), { - func(&mut self.pre_commit); + fn_coll(&mut self.pre_commit); self.commit() } - fn update_artist_and, FNARTIST, FNCOLL>( + fn update_artist_and( &mut self, - artist_id: ID, - fn_artist: FNARTIST, - fn_collection: FNCOLL, + artist_id: &ArtistId, + fn_artist: FnArtist, + fn_coll: FnColl, ) -> Result<(), Error> where - FNARTIST: FnOnce(&mut Artist), - FNCOLL: FnOnce(&mut Collection), + FnArtist: FnOnce(&mut Artist), + FnColl: FnOnce(&mut Collection), { - fn_artist(Self::get_artist_mut_or_err( - &mut self.pre_commit, - artist_id.as_ref(), - )?); - self.update_collection(fn_collection) + let artist = Self::get_artist_mut_or_err(&mut self.pre_commit, artist_id)?; + fn_artist(artist); + self.update_collection(fn_coll) } - fn update_artist, FN>( + fn update_artist( &mut self, - artist_id: ID, - func: FN, + artist_id: &ArtistId, + fn_artist: FnArtist, ) -> Result<(), Error> where - FN: FnOnce(&mut Artist), + FnArtist: FnOnce(&mut Artist), { - self.update_artist_and(artist_id, func, |_| {}) + self.update_artist_and(artist_id, fn_artist, |_| {}) } - fn update_album_and, ALBUM: AsRef, FNALBUM, FNARTIST, FNCOLL>( + fn update_album_and( &mut self, - artist_id: ARTIST, - album_id: ALBUM, - fn_album: FNALBUM, - fn_artist: FNARTIST, - fn_collection: FNCOLL, + artist_id: &ArtistId, + album_id: &AlbumId, + fn_album: FnAlbum, + fn_artist: FnArtist, + fn_coll: FnColl, ) -> Result<(), Error> where - FNALBUM: FnOnce(&mut Album), - FNARTIST: FnOnce(&mut Artist), - FNCOLL: FnOnce(&mut Collection), + FnAlbum: FnOnce(&mut Album), + FnArtist: FnOnce(&mut Artist), + FnColl: FnOnce(&mut Collection), { - let artist = Self::get_artist_mut_or_err(&mut self.pre_commit, artist_id.as_ref())?; - let album = Self::get_album_mut_or_err(artist, album_id.as_ref())?; + let artist = Self::get_artist_mut_or_err(&mut self.pre_commit, artist_id)?; + let album = Self::get_album_mut_or_err(artist, album_id)?; fn_album(album); fn_artist(artist); - self.update_collection(fn_collection) + self.update_collection(fn_coll) } - pub fn add_artist>(&mut self, artist_id: ID) -> Result<(), Error> { + pub fn add_artist>(&mut self, artist_id: IntoId) -> Result<(), Error> { let artist_id: ArtistId = artist_id.into(); self.update_collection(|collection| { @@ -326,7 +324,7 @@ impl MusicHoard { }) } - pub fn remove_artist>(&mut self, artist_id: ID) -> Result<(), Error> { + pub fn remove_artist>(&mut self, artist_id: Id) -> Result<(), Error> { self.update_collection(|collection| { let index_opt = collection.iter().position(|a| &a.id == artist_id.as_ref()); if let Some(index) = index_opt { @@ -335,102 +333,109 @@ impl MusicHoard { }) } - pub fn set_artist_sort, SORT: Into>( + pub fn set_artist_sort, IntoId: Into>( &mut self, - artist_id: ID, - artist_sort: SORT, + artist_id: Id, + artist_sort: IntoId, ) -> Result<(), Error> { self.update_artist_and( - artist_id, + artist_id.as_ref(), |artist| artist.set_sort_key(artist_sort), |collection| Self::sort_artists(collection), ) } - pub fn clear_artist_sort>(&mut self, artist_id: ID) -> Result<(), Error> { + pub fn clear_artist_sort>(&mut self, artist_id: Id) -> Result<(), Error> { self.update_artist_and( - artist_id, + artist_id.as_ref(), |artist| artist.clear_sort_key(), |collection| Self::sort_artists(collection), ) } - pub fn set_artist_musicbrainz, S: AsRef>( + pub fn set_artist_musicbrainz, Mb: TryInto, E>( &mut self, - artist_id: ID, - url: S, - ) -> Result<(), Error> { - let url = url.as_ref().try_into()?; - self.update_artist(artist_id, |artist| artist.set_musicbrainz_url(url)) + artist_id: Id, + url: Mb, + ) -> Result<(), Error> + where + Error: From, + { + let mb = url.try_into()?; + self.update_artist(artist_id.as_ref(), |artist| artist.set_musicbrainz_url(mb)) } - pub fn clear_artist_musicbrainz>( + pub fn clear_artist_musicbrainz>( &mut self, - artist_id: ID, + artist_id: Id, ) -> Result<(), Error> { - self.update_artist(artist_id, |artist| artist.clear_musicbrainz_url()) + self.update_artist(artist_id.as_ref(), |artist| artist.clear_musicbrainz_url()) } - pub fn add_to_artist_property, S: AsRef + Into>( + pub fn add_to_artist_property, S: AsRef + Into>( &mut self, - artist_id: ID, + artist_id: Id, property: S, values: Vec, ) -> Result<(), Error> { - self.update_artist(artist_id, |artist| artist.add_to_property(property, values)) + self.update_artist(artist_id.as_ref(), |artist| { + artist.add_to_property(property, values) + }) } - pub fn remove_from_artist_property, S: AsRef>( + pub fn remove_from_artist_property, S: AsRef>( &mut self, - artist_id: ID, + artist_id: Id, property: S, values: Vec, ) -> Result<(), Error> { - self.update_artist(artist_id, |artist| { + self.update_artist(artist_id.as_ref(), |artist| { artist.remove_from_property(property, values) }) } - pub fn set_artist_property, S: AsRef + Into>( + pub fn set_artist_property, S: AsRef + Into>( &mut self, - artist_id: ID, + artist_id: Id, property: S, values: Vec, ) -> Result<(), Error> { - self.update_artist(artist_id, |artist| artist.set_property(property, values)) + self.update_artist(artist_id.as_ref(), |artist| { + artist.set_property(property, values) + }) } - pub fn clear_artist_property, S: AsRef>( + pub fn clear_artist_property, S: AsRef>( &mut self, - artist_id: ID, + artist_id: Id, property: S, ) -> Result<(), Error> { - self.update_artist(artist_id, |artist| artist.clear_property(property)) + self.update_artist(artist_id.as_ref(), |artist| artist.clear_property(property)) } - pub fn set_album_seq, ALBUM: AsRef>( + pub fn set_album_seq, AlbumIdRef: AsRef>( &mut self, - artist_id: ARTIST, - album_id: ALBUM, + artist_id: ArtistIdRef, + album_id: AlbumIdRef, seq: u8, ) -> Result<(), Error> { self.update_album_and( - artist_id, - album_id, + artist_id.as_ref(), + album_id.as_ref(), |album| album.set_seq(AlbumSeq(seq)), |artist| artist.albums.sort_unstable(), |_| {}, ) } - pub fn clear_album_seq, ALBUM: AsRef>( + pub fn clear_album_seq, AlbumIdRef: AsRef>( &mut self, - artist_id: ARTIST, - album_id: ALBUM, + artist_id: ArtistIdRef, + album_id: AlbumIdRef, ) -> Result<(), Error> { self.update_album_and( - artist_id, - album_id, + artist_id.as_ref(), + album_id.as_ref(), |album| album.clear_seq(), |artist| artist.albums.sort_unstable(), |_| {}, @@ -621,7 +626,7 @@ mod tests { assert!(music_hoard .set_artist_musicbrainz(&artist_id, MUSICBRAINZ) .is_ok()); - _ = expected.insert(MusicBrainz::new(MUSICBRAINZ).unwrap()); + _ = expected.insert(MUSICBRAINZ.try_into().unwrap()); assert_eq!(music_hoard.collection[0].musicbrainz, expected); // Clearing URLs on an artist that does not exist is an error. @@ -733,6 +738,51 @@ mod tests { assert!(music_hoard.collection[0].properties.is_empty()); } + #[test] + fn set_clear_album_seq() { + let mut database = MockIDatabase::new(); + + let artist_id = ArtistId::new("an artist"); + let album_id = AlbumId::new("an album"); + let album_id_2 = AlbumId::new("another album"); + + let mut database_result = vec![Artist::new(artist_id.clone())]; + database_result[0].albums.push(Album { + id: album_id.clone(), + date: AlbumDate::default(), + seq: AlbumSeq::default(), + tracks: vec![], + }); + + database + .expect_load() + .times(1) + .return_once(|| Ok(database_result)); + database.expect_save().times(2).returning(|_| Ok(())); + + let mut music_hoard = MusicHoard::database(database).unwrap(); + assert_eq!(music_hoard.collection[0].albums[0].seq, AlbumSeq(0)); + + // Seting seq on an album not belonging to the artist is an error. + assert!(music_hoard + .set_album_seq(&artist_id, &album_id_2, 6) + .is_err()); + assert_eq!(music_hoard.collection[0].albums[0].seq, AlbumSeq(0)); + + // Set seq. + assert!(music_hoard.set_album_seq(&artist_id, &album_id, 6).is_ok()); + assert_eq!(music_hoard.collection[0].albums[0].seq, AlbumSeq(6)); + + // Clearing seq on an album that does not exist is an error. + assert!(music_hoard + .clear_album_seq(&artist_id, &album_id_2) + .is_err()); + + // Clear seq. + assert!(music_hoard.clear_album_seq(&artist_id, &album_id).is_ok()); + assert_eq!(music_hoard.collection[0].albums[0].seq, AlbumSeq(0)); + } + #[test] fn merge_collection_no_overlap() { let half: usize = FULL_COLLECTION.len() / 2; diff --git a/src/core/testmod.rs b/src/core/testmod.rs index e5863ae..a0620fe 100644 --- a/src/core/testmod.rs +++ b/src/core/testmod.rs @@ -1,5 +1,5 @@ use once_cell::sync::Lazy; -use std::collections::HashMap; +use std::{collections::HashMap, str::FromStr}; use crate::core::collection::{ album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq}, diff --git a/src/tests.rs b/src/tests.rs index aa8e5b2..c54bb6e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -454,11 +454,9 @@ macro_rules! full_collection { let artist_a = iter.next().unwrap(); assert_eq!(artist_a.id.name, "Album_Artist ‘A’"); - artist_a.musicbrainz = Some( - MusicBrainz::new( - "https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000", - ).unwrap(), - ); + artist_a.musicbrainz = Some(MusicBrainz::from_str( + "https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000", + ).unwrap()); artist_a.properties = HashMap::from([ (String::from("MusicButler"), vec![ @@ -477,11 +475,9 @@ macro_rules! full_collection { let artist_b = iter.next().unwrap(); assert_eq!(artist_b.id.name, "Album_Artist ‘B’"); - artist_b.musicbrainz = Some( - MusicBrainz::new( - "https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111", - ).unwrap(), - ); + artist_b.musicbrainz = Some(MusicBrainz::from_str( + "https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111", + ).unwrap()); artist_b.properties = HashMap::from([ (String::from("MusicButler"), vec![ @@ -504,11 +500,9 @@ macro_rules! full_collection { let artist_c = iter.next().unwrap(); assert_eq!(artist_c.id.name, "The Album_Artist ‘C’"); - artist_c.musicbrainz = Some( - MusicBrainz::new( - "https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111", - ).unwrap(), - ); + artist_c.musicbrainz = Some(MusicBrainz::from_str( + "https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111", + ).unwrap()); // Nothing for artist_d diff --git a/src/tui/testmod.rs b/src/tui/testmod.rs index 8f3e8ef..1d7e506 100644 --- a/src/tui/testmod.rs +++ b/src/tui/testmod.rs @@ -1,10 +1,11 @@ +use std::{collections::HashMap, str::FromStr}; + use musichoard::collection::{ album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq}, artist::{Artist, ArtistId, MusicBrainz}, track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality}, }; use once_cell::sync::Lazy; -use std::collections::HashMap; use crate::tests::*; diff --git a/tests/testlib.rs b/tests/testlib.rs index bc9fc2c..d2bc4d3 100644 --- a/tests/testlib.rs +++ b/tests/testlib.rs @@ -1,5 +1,5 @@ use once_cell::sync::Lazy; -use std::collections::HashMap; +use std::{collections::HashMap, str::FromStr}; use musichoard::collection::{ album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq}, @@ -17,8 +17,8 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { sort: Some(ArtistId{ name: String::from("Arkona") }), - musicbrainz: Some(MusicBrainz::new( - "https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212", + musicbrainz: Some(MusicBrainz::from_str( + "https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212" ).unwrap()), properties: HashMap::from([ (String::from("MusicButler"), vec![ @@ -204,7 +204,7 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { name: String::from("Eluveitie"), }, sort: None, - musicbrainz: Some(MusicBrainz::new( + musicbrainz: Some(MusicBrainz::from_str( "https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38", ).unwrap()), properties: HashMap::from([ @@ -447,7 +447,7 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { name: String::from("Frontside"), }, sort: None, - musicbrainz: Some(MusicBrainz::new( + musicbrainz: Some(MusicBrainz::from_str( "https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490", ).unwrap()), properties: HashMap::from([ @@ -600,7 +600,7 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { sort: Some(ArtistId { name: String::from("Heaven’s Basement"), }), - musicbrainz: Some(MusicBrainz::new( + musicbrainz: Some(MusicBrainz::from_str( "https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc", ).unwrap()), properties: HashMap::from([ @@ -730,7 +730,7 @@ pub static COLLECTION: Lazy> = Lazy::new(|| -> Collection { name: String::from("Metallica"), }, sort: None, - musicbrainz: Some(MusicBrainz::new( + musicbrainz: Some(MusicBrainz::from_str( "https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab", ).unwrap()), properties: HashMap::from([ -- 2.45.2 From 930cb5c0ec2c08c785249393077a86ac174e8222 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Tue, 5 Mar 2024 21:28:31 +0100 Subject: [PATCH 11/12] Revert old listener panic behaviour --- src/main.rs | 7 +------ src/tui/mod.rs | 29 ++++++++++++----------------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9449066..5c3dfa5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,12 +75,7 @@ fn with(builder: MusicHoardBuilder) { let ui = Ui; // Run the TUI application. - let result = Tui::run(terminal, app, ui, handler, listener); - if let Err(tui::Error::ListenerPanic(err)) = result { - std::panic::resume_unwind(err); - } else { - result.expect("failed to run tui") - }; + Tui::run(terminal, app, ui, handler, listener).expect("failed to run tui"); } fn with_database(db_opt: DbOpt, builder: MusicHoardBuilder) { diff --git a/src/tui/mod.rs b/src/tui/mod.rs index a559391..0601ba1 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -16,10 +16,7 @@ use crossterm::{ terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, }; use ratatui::{backend::Backend, Terminal}; -use std::{ - marker::PhantomData, - {any::Any, io}, -}; +use std::{io, marker::PhantomData}; use crate::tui::{ app::{IAppAccess, IAppInteract}, @@ -29,11 +26,11 @@ use crate::tui::{ ui::IUi, }; -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub enum Error { Io(String), Event(String), - ListenerPanic(Box), + ListenerPanic, } impl From for Error { @@ -115,10 +112,9 @@ impl Tui { match listener_handle.join() { Ok(err) => return Err(err.into()), // Calling std::panic::resume_unwind(err) as recommended by the Rust docs - // will not produce an error message. The panic error message is printed at - // the location of the panic which at the time is hidden by the TUI. - // Therefore, propagate the error for the caller to resume unwinding. - Err(panic) => return Err(Error::ListenerPanic(panic)), + // will not produce an error message. This may be due to the panic simply + // causing the process to abort in which case there is nothing to unwind. + Err(_) => return Err(Error::ListenerPanic), } } @@ -256,8 +252,8 @@ mod tests { let result = Tui::main(terminal, app, ui, handler, listener); assert!(result.is_err()); - let recv_err = EventError::Recv.to_string(); - matches!(result.unwrap_err(), Error::Event(err) if err == recv_err); + let error = EventError::Recv; + assert_eq!(result.unwrap_err(), Error::Event(error.to_string())); } #[test] @@ -281,9 +277,8 @@ mod tests { let result = Tui::main(terminal, app, ui, handler, listener); assert!(result.is_err()); - let io_err = - EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")).to_string(); - matches!(result.unwrap_err(), Error::Event(err) if err == io_err); + let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error")); + assert_eq!(result.unwrap_err(), Error::Event(error.to_string())); } #[test] @@ -305,14 +300,14 @@ mod tests { let result = Tui::main(terminal, app, ui, handler, listener); assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), Error::ListenerPanic(_))); + assert_eq!(result.unwrap_err(), Error::ListenerPanic); } #[test] fn errors() { let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "error").into(); let event_err: Error = EventError::Recv.into(); - let listener_err = Error::ListenerPanic(Box::new("hello")); + let listener_err = Error::ListenerPanic; assert!(!format!("{:?}", io_err).is_empty()); assert!(!format!("{:?}", event_err).is_empty()); -- 2.45.2 From dd00a90a10b11d677d0467e44a3ca9a811b937fe Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Tue, 5 Mar 2024 23:20:23 +0100 Subject: [PATCH 12/12] Print new sequence information in tui --- src/core/collection/album.rs | 42 +++++++++++++++++++++++++++++++----- src/tui/ui.rs | 8 +++++-- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index b750c4c..9de9d3b 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -40,6 +40,18 @@ pub struct AlbumDate { pub day: u8, } +impl Display for AlbumDate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.month.is_none() { + write!(f, "{}", self.year) + } else if self.day == 0 { + write!(f, "{}‐{:02}", self.year, self.month as u8) + } else { + write!(f, "{}‐{:02}‐{:02}", self.year, self.month as u8, self.day) + } + } +} + /// The album's sequence to determine order when two or more albums have the same release date. #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] pub struct AlbumSeq(pub u8); @@ -83,6 +95,12 @@ impl From for AlbumMonth { } } +impl AlbumMonth { + fn is_none(&self) -> bool { + matches!(self, AlbumMonth::None) + } +} + impl Album { pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) { (&self.date, &self.seq, &self.id) @@ -148,6 +166,16 @@ mod tests { use super::*; + impl AlbumDate { + fn new>(year: u32, month: M, day: u8) -> Self { + AlbumDate { + year, + month: month.into(), + day, + } + } + } + #[test] fn album_month() { assert_eq!(>::into(0), AlbumMonth::None); @@ -167,13 +195,17 @@ mod tests { assert_eq!(>::into(255), AlbumMonth::None); } + #[test] + fn album_display() { + assert_eq!(AlbumDate::default().to_string(), "0"); + assert_eq!(AlbumDate::new(1990, 0, 0).to_string(), "1990"); + assert_eq!(AlbumDate::new(1990, 5, 0).to_string(), "1990‐05"); + assert_eq!(AlbumDate::new(1990, 5, 6).to_string(), "1990‐05‐06"); + } + #[test] fn same_date_seq_cmp() { - let date = AlbumDate { - year: 2024, - month: AlbumMonth::March, - day: 2, - }; + let date = AlbumDate::new(2024, 3, 2); let album_id_1 = AlbumId { title: String::from("album z"), diff --git a/src/tui/ui.rs b/src/tui/ui.rs index f1b425f..2d71e98 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -287,9 +287,13 @@ impl<'a, 'b> AlbumState<'a, 'b> { let album = state.list.selected().map(|i| &albums[i]); let info = Paragraph::new(format!( "Title: {}\n\ - Year: {}", + Date: {}{}", album.map(|a| a.id.title.as_str()).unwrap_or(""), - album.map(|a| a.date.year.to_string()).unwrap_or_default(), + album.map(|a| a.date.to_string()).unwrap_or_default(), + album + .filter(|a| a.seq.0 > 0) + .map(|a| format!(" ({})", a.seq.0)) + .unwrap_or_default() )); AlbumState { -- 2.45.2