Add a field that indicates album ownership #156

Merged
wojtek merged 3 commits from 47---add-a-field-that-indicates-album-ownership into main 2024-03-07 23:12:41 +01:00
3 changed files with 140 additions and 43 deletions
Showing only changes of commit 66913b8e76 - Show all commits

View File

@ -8,6 +8,8 @@ use crate::core::collection::{
track::Track, track::Track,
}; };
use super::track::TrackFormat;
/// An album is a collection of tracks that were released together. /// An album is a collection of tracks that were released together.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Album { pub struct Album {
@ -40,22 +42,16 @@ pub struct AlbumDate {
pub day: u8, pub day: u8,
} }
impl Display for AlbumDate { impl AlbumDate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { pub fn new<M: Into<AlbumMonth>>(year: u32, month: M, day: u8) -> Self {
if self.month.is_none() { AlbumDate {
write!(f, "{}", self.year) year,
} else if self.day == 0 { month: month.into(),
write!(f, "{}{:02}", self.year, self.month as u8) day,
} 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);
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum AlbumMonth { pub enum AlbumMonth {
@ -96,16 +92,41 @@ impl From<u8> for AlbumMonth {
} }
impl AlbumMonth { impl AlbumMonth {
fn is_none(&self) -> bool { pub fn is_none(&self) -> bool {
matches!(self, AlbumMonth::None) matches!(self, AlbumMonth::None)
} }
} }
/// 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);
/// The album's ownership status.
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum AlbumStatus {
#[default]
None,
Owned(TrackFormat),
}
impl AlbumStatus {
pub fn from_tracks(tracks: &[Track]) -> AlbumStatus {
match tracks.iter().map(|t| t.quality.format).min() {
Some(format) => AlbumStatus::Owned(format),
None => AlbumStatus::None,
}
}
}
impl Album { impl Album {
pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) { pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) {
(&self.date, &self.seq, &self.id) (&self.date, &self.seq, &self.id)
} }
pub fn get_status(&self) -> AlbumStatus {
AlbumStatus::from_tracks(&self.tracks)
}
pub fn set_seq(&mut self, seq: AlbumSeq) { pub fn set_seq(&mut self, seq: AlbumSeq) {
self.seq = seq; self.seq = seq;
} }
@ -166,16 +187,6 @@ mod tests {
use super::*; use super::*;
impl AlbumDate {
fn new<M: Into<AlbumMonth>>(year: u32, month: M, day: u8) -> Self {
AlbumDate {
year,
month: month.into(),
day,
}
}
}
#[test] #[test]
fn album_month() { fn album_month() {
assert_eq!(<u8 as Into<AlbumMonth>>::into(0), AlbumMonth::None); assert_eq!(<u8 as Into<AlbumMonth>>::into(0), AlbumMonth::None);
@ -195,14 +206,6 @@ mod tests {
assert_eq!(<u8 as Into<AlbumMonth>>::into(255), AlbumMonth::None); assert_eq!(<u8 as Into<AlbumMonth>>::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(), "199005");
assert_eq!(AlbumDate::new(1990, 5, 6).to_string(), "19900506");
}
#[test] #[test]
fn same_date_seq_cmp() { fn same_date_seq_cmp() {
let date = AlbumDate::new(2024, 3, 2); let date = AlbumDate::new(2024, 3, 2);

View File

@ -33,10 +33,10 @@ impl Track {
} }
/// The track file format. /// The track file format.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TrackFormat { pub enum TrackFormat {
Flac,
Mp3, Mp3,
Flac,
} }
impl PartialOrd for Track { impl PartialOrd for Track {
@ -61,6 +61,12 @@ impl Merge for Track {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn track_ord() {
assert!(TrackFormat::Mp3 < TrackFormat::Flac);
assert!(TrackFormat::Flac > TrackFormat::Mp3);
}
#[test] #[test]
fn merge_track() { fn merge_track() {
let left = Track { let left = Track {

View File

@ -1,9 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use musichoard::collection::{ use musichoard::collection::{
album::Album, album::{Album, AlbumDate, AlbumSeq, AlbumStatus},
artist::Artist, artist::Artist,
track::{Track, TrackFormat}, track::{Track, TrackFormat, TrackQuality},
Collection, Collection,
}; };
use ratatui::{ use ratatui::{
@ -287,13 +287,19 @@ impl<'a, 'b> AlbumState<'a, 'b> {
let album = state.list.selected().map(|i| &albums[i]); let album = state.list.selected().map(|i| &albums[i]);
let info = Paragraph::new(format!( let info = Paragraph::new(format!(
"Title: {}\n\ "Title: {}\n\
Date: {}{}", Date: {}{}\n\
Status: {}",
album.map(|a| a.id.title.as_str()).unwrap_or(""), album.map(|a| a.id.title.as_str()).unwrap_or(""),
album.map(|a| a.date.to_string()).unwrap_or_default(), album
.map(|a| Self::display_album_date(&a.date))
.unwrap_or_default(),
album album
.filter(|a| a.seq.0 > 0) .filter(|a| a.seq.0 > 0)
.map(|a| format!(" ({})", a.seq.0)) .map(|a| Self::display_album_seq(&a.seq))
.unwrap_or_default() .unwrap_or_default(),
album
.map(|a| Self::display_album_status(&a.get_status()))
.unwrap_or("")
)); ));
AlbumState { AlbumState {
@ -303,6 +309,30 @@ impl<'a, 'b> AlbumState<'a, 'b> {
info, info,
} }
} }
fn display_album_date(date: &AlbumDate) -> String {
if date.month.is_none() {
format!("{}", date.year)
} else if date.day == 0 {
format!("{}{:02}", date.year, date.month as u8)
} else {
format!("{}{:02}{:02}", date.year, date.month as u8, date.day)
}
}
fn display_album_seq(seq: &AlbumSeq) -> String {
format!(" ({})", seq.0)
}
fn display_album_status(status: &AlbumStatus) -> &'static str {
match status {
AlbumStatus::None => "None",
AlbumStatus::Owned(format) => match format {
TrackFormat::Mp3 => "MP3",
TrackFormat::Flac => "FLAC",
},
}
}
} }
struct TrackState<'a, 'b> { struct TrackState<'a, 'b> {
@ -331,10 +361,7 @@ impl<'a, 'b> TrackState<'a, 'b> {
track.map(|t| t.id.title.as_str()).unwrap_or(""), track.map(|t| t.id.title.as_str()).unwrap_or(""),
track.map(|t| t.artist.join("; ")).unwrap_or_default(), track.map(|t| t.artist.join("; ")).unwrap_or_default(),
track track
.map(|t| match t.quality.format { .map(|t| Self::display_track_quality(&t.quality))
TrackFormat::Flac => "FLAC".to_string(),
TrackFormat::Mp3 => format!("MP3 {}kbps", t.quality.bitrate),
})
.unwrap_or_default(), .unwrap_or_default(),
)); ));
@ -345,6 +372,13 @@ impl<'a, 'b> TrackState<'a, 'b> {
info, info,
} }
} }
fn display_track_quality(quality: &TrackQuality) -> String {
match quality.format {
TrackFormat::Flac => "FLAC".to_string(),
TrackFormat::Mp3 => format!("MP3 {}kbps", quality.bitrate),
}
}
} }
struct Minibuffer<'a> { struct Minibuffer<'a> {
@ -743,6 +777,60 @@ mod tests {
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
} }
#[test]
fn display_album_date() {
assert_eq!(AlbumState::display_album_date(&AlbumDate::default()), "0");
assert_eq!(
AlbumState::display_album_date(&AlbumDate::new(1990, 0, 0)),
"1990"
);
assert_eq!(
AlbumState::display_album_date(&AlbumDate::new(1990, 5, 0)),
"199005"
);
assert_eq!(
AlbumState::display_album_date(&AlbumDate::new(1990, 5, 6)),
"19900506"
);
}
#[test]
fn display_album_seq() {
assert_eq!(AlbumState::display_album_seq(&AlbumSeq::default()), " (0)");
assert_eq!(AlbumState::display_album_seq(&AlbumSeq(5)), " (5)");
}
#[test]
fn display_album_status() {
assert_eq!(AlbumState::display_album_status(&AlbumStatus::None), "None");
assert_eq!(
AlbumState::display_album_status(&AlbumStatus::Owned(TrackFormat::Mp3)),
"MP3"
);
assert_eq!(
AlbumState::display_album_status(&AlbumStatus::Owned(TrackFormat::Flac)),
"FLAC"
);
}
#[test]
fn display_track_quality() {
assert_eq!(
TrackState::display_track_quality(&TrackQuality {
format: TrackFormat::Flac,
bitrate: 1411
}),
"FLAC"
);
assert_eq!(
TrackState::display_track_quality(&TrackQuality {
format: TrackFormat::Mp3,
bitrate: 218
}),
"MP3 218kbps"
);
}
#[test] #[test]
fn empty() { fn empty() {
let artists: Vec<Artist> = vec![]; let artists: Vec<Artist> = vec![];