From f3f35826019ab4450c70471d2635df00254e07f0 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sat, 4 Jan 2025 21:34:06 +0100 Subject: [PATCH] Fix filtering of unowned albums --- src/core/musichoard/filter.rs | 58 ++++++++++++++++++++++++++--------- src/main.rs | 41 +++++++++++++++---------- 2 files changed, 67 insertions(+), 32 deletions(-) diff --git a/src/core/musichoard/filter.rs b/src/core/musichoard/filter.rs index 203a95a..5452d58 100644 --- a/src/core/musichoard/filter.rs +++ b/src/core/musichoard/filter.rs @@ -3,8 +3,8 @@ use crate::core::collection::album::{Album, AlbumOwnership, AlbumPrimaryType, Al /// Filter for a specifying subsets of the entire collection (e.g., for display). #[derive(Debug, Default)] pub struct CollectionFilter { - pub include: Vec, - pub except: Vec, + pub include: Vec>, + pub except: Vec>, } #[derive(Debug)] @@ -16,11 +16,19 @@ pub enum AlbumField { impl CollectionFilter { pub fn filter_album(&self, album: &Album) -> bool { - let include = Self::filter_or(&self.include, album); - let except = Self::filter_or(&self.except, album); + let include = Self::filter_and(true, &self.include, album); + let except = Self::filter_and(false, &self.except, album); include && !except } + fn filter_and(empty: bool, group: &Vec>, album: &Album) -> bool { + let mut filter = !group.is_empty() || empty; + for field in group.iter() { + filter = filter && Self::filter_or(field, album); + } + filter + } + fn filter_or(group: &Vec, album: &Album) -> bool { let mut filter = false; for field in group.iter() { @@ -52,17 +60,21 @@ mod tests { fn test_filter() -> CollectionFilter { CollectionFilter { - include: vec![ + include: vec![vec![ AlbumField::PrimaryType(None), AlbumField::PrimaryType(Some(AlbumPrimaryType::Ep)), AlbumField::PrimaryType(Some(AlbumPrimaryType::Album)), - ], + AlbumField::Ownership(AlbumOwnership::Owned(TrackFormat::Mp3)), + AlbumField::Ownership(AlbumOwnership::Owned(TrackFormat::Flac)), + ]], except: vec![ - AlbumField::Ownership(AlbumOwnership::None), - AlbumField::SecondaryType(AlbumSecondaryType::Compilation), - AlbumField::SecondaryType(AlbumSecondaryType::Soundtrack), - AlbumField::SecondaryType(AlbumSecondaryType::Live), - AlbumField::SecondaryType(AlbumSecondaryType::Demo), + vec![AlbumField::Ownership(AlbumOwnership::None)], + vec![ + AlbumField::SecondaryType(AlbumSecondaryType::Compilation), + AlbumField::SecondaryType(AlbumSecondaryType::Soundtrack), + AlbumField::SecondaryType(AlbumSecondaryType::Live), + AlbumField::SecondaryType(AlbumSecondaryType::Demo), + ], ], } } @@ -93,6 +105,9 @@ mod tests { let filter = test_filter(); let mut album = test_album(); + // Drop ownership so that filtering is truly only on type. + album.tracks.clear(); + album.meta.info.primary_type = None; assert!(filter.filter_album(&album)); @@ -121,29 +136,42 @@ mod tests { types.push(AlbumSecondaryType::AudioDrama); assert!(filter.filter_album(&album)); - // Filtered type. + // Filtered type. But album is owned so it remains. let types = &mut album.meta.info.secondary_types; types.push(AlbumSecondaryType::Live); - assert!(!filter.filter_album(&album)); + assert_ne!(album.get_ownership(), AlbumOwnership::None); + assert!(filter.filter_album(&album)); // Remove non-filtered type. album.meta.info.secondary_types.remove(0); - assert!(!filter.filter_album(&album)); + assert!(filter.filter_album(&album)); // Add another filtered type. let types = &mut album.meta.info.secondary_types; types.push(AlbumSecondaryType::Soundtrack); + assert!(filter.filter_album(&album)); + + // Drop ownership and this should be now excluded. + album.tracks.clear(); + assert_eq!(album.get_ownership(), AlbumOwnership::None); assert!(!filter.filter_album(&album)); } #[test] fn filter_ownership() { let filter = test_filter(); - let mut album = Album::new(AlbumId::new("An Album")); + let mut album = test_album(); + // It is an album though so it should remain included. album.tracks.clear(); + assert_eq!(album.get_ownership(), AlbumOwnership::None); + assert!(filter.filter_album(&album)); + + // Change to unincluded primary type. + album.meta.info.primary_type = Some(AlbumPrimaryType::Other); assert!(!filter.filter_album(&album)); + // Changing ownership should make it go back to being included. album.tracks.push(test_track()); assert_eq!( album.get_ownership(), diff --git a/src/main.rs b/src/main.rs index 760a268..3b76b08 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,10 @@ use ratatui::{backend::CrosstermBackend, Terminal}; use structopt::StructOpt; use musichoard::{ - collection::album::{AlbumOwnership, AlbumPrimaryType, AlbumSecondaryType}, + collection::{ + album::{AlbumOwnership, AlbumPrimaryType, AlbumSecondaryType}, + track::TrackFormat, + }, external::{ database::json::{backend::JsonDatabaseFileBackend, JsonDatabase}, library::beets::{ @@ -73,25 +76,29 @@ struct DbOpt { fn default_filter() -> CollectionFilter { CollectionFilter { - include: vec![ + include: vec![vec![ AlbumField::PrimaryType(None), AlbumField::PrimaryType(Some(AlbumPrimaryType::Ep)), AlbumField::PrimaryType(Some(AlbumPrimaryType::Album)), - ], + AlbumField::Ownership(AlbumOwnership::Owned(TrackFormat::Mp3)), + AlbumField::Ownership(AlbumOwnership::Owned(TrackFormat::Flac)), + ]], except: vec![ - AlbumField::SecondaryType(AlbumSecondaryType::Compilation), - AlbumField::SecondaryType(AlbumSecondaryType::Soundtrack), - AlbumField::SecondaryType(AlbumSecondaryType::Spokenword), - AlbumField::SecondaryType(AlbumSecondaryType::Interview), - AlbumField::SecondaryType(AlbumSecondaryType::Audiobook), - AlbumField::SecondaryType(AlbumSecondaryType::AudioDrama), - AlbumField::SecondaryType(AlbumSecondaryType::Live), - AlbumField::SecondaryType(AlbumSecondaryType::Remix), - AlbumField::SecondaryType(AlbumSecondaryType::DjMix), - AlbumField::SecondaryType(AlbumSecondaryType::MixtapeStreet), - AlbumField::SecondaryType(AlbumSecondaryType::Demo), - AlbumField::SecondaryType(AlbumSecondaryType::FieldRecording), - AlbumField::Ownership(AlbumOwnership::None), + vec![AlbumField::Ownership(AlbumOwnership::None)], + vec![ + AlbumField::SecondaryType(AlbumSecondaryType::Compilation), + AlbumField::SecondaryType(AlbumSecondaryType::Soundtrack), + AlbumField::SecondaryType(AlbumSecondaryType::Spokenword), + AlbumField::SecondaryType(AlbumSecondaryType::Interview), + AlbumField::SecondaryType(AlbumSecondaryType::Audiobook), + AlbumField::SecondaryType(AlbumSecondaryType::AudioDrama), + AlbumField::SecondaryType(AlbumSecondaryType::Live), + AlbumField::SecondaryType(AlbumSecondaryType::Remix), + AlbumField::SecondaryType(AlbumSecondaryType::DjMix), + AlbumField::SecondaryType(AlbumSecondaryType::MixtapeStreet), + AlbumField::SecondaryType(AlbumSecondaryType::Demo), + AlbumField::SecondaryType(AlbumSecondaryType::FieldRecording), + ], ], } } @@ -100,7 +107,7 @@ fn with( builder: MusicHoardBuilder, ) { let mut music_hoard = builder.build().expect("failed to initialise MusicHoard"); - music_hoard.set_filter(default_filter()); + // music_hoard.set_filter(default_filter()); // Initialize the terminal user interface. let backend = CrosstermBackend::new(io::stdout());