Fix filtering of unowned albums

This commit is contained in:
Wojciech Kozlowski 2025-01-04 21:34:06 +01:00
parent b7779905bc
commit f3f3582601
2 changed files with 67 additions and 32 deletions

View File

@ -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). /// Filter for a specifying subsets of the entire collection (e.g., for display).
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct CollectionFilter { pub struct CollectionFilter {
pub include: Vec<AlbumField>, pub include: Vec<Vec<AlbumField>>,
pub except: Vec<AlbumField>, pub except: Vec<Vec<AlbumField>>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -16,11 +16,19 @@ pub enum AlbumField {
impl CollectionFilter { impl CollectionFilter {
pub fn filter_album(&self, album: &Album) -> bool { pub fn filter_album(&self, album: &Album) -> bool {
let include = Self::filter_or(&self.include, album); let include = Self::filter_and(true, &self.include, album);
let except = Self::filter_or(&self.except, album); let except = Self::filter_and(false, &self.except, album);
include && !except include && !except
} }
fn filter_and(empty: bool, group: &Vec<Vec<AlbumField>>, 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<AlbumField>, album: &Album) -> bool { fn filter_or(group: &Vec<AlbumField>, album: &Album) -> bool {
let mut filter = false; let mut filter = false;
for field in group.iter() { for field in group.iter() {
@ -52,17 +60,21 @@ mod tests {
fn test_filter() -> CollectionFilter { fn test_filter() -> CollectionFilter {
CollectionFilter { CollectionFilter {
include: vec![ include: vec![vec![
AlbumField::PrimaryType(None), AlbumField::PrimaryType(None),
AlbumField::PrimaryType(Some(AlbumPrimaryType::Ep)), AlbumField::PrimaryType(Some(AlbumPrimaryType::Ep)),
AlbumField::PrimaryType(Some(AlbumPrimaryType::Album)), AlbumField::PrimaryType(Some(AlbumPrimaryType::Album)),
], AlbumField::Ownership(AlbumOwnership::Owned(TrackFormat::Mp3)),
AlbumField::Ownership(AlbumOwnership::Owned(TrackFormat::Flac)),
]],
except: vec![ except: vec![
AlbumField::Ownership(AlbumOwnership::None), vec![AlbumField::Ownership(AlbumOwnership::None)],
AlbumField::SecondaryType(AlbumSecondaryType::Compilation), vec![
AlbumField::SecondaryType(AlbumSecondaryType::Soundtrack), AlbumField::SecondaryType(AlbumSecondaryType::Compilation),
AlbumField::SecondaryType(AlbumSecondaryType::Live), AlbumField::SecondaryType(AlbumSecondaryType::Soundtrack),
AlbumField::SecondaryType(AlbumSecondaryType::Demo), AlbumField::SecondaryType(AlbumSecondaryType::Live),
AlbumField::SecondaryType(AlbumSecondaryType::Demo),
],
], ],
} }
} }
@ -93,6 +105,9 @@ mod tests {
let filter = test_filter(); let filter = test_filter();
let mut album = test_album(); let mut album = test_album();
// Drop ownership so that filtering is truly only on type.
album.tracks.clear();
album.meta.info.primary_type = None; album.meta.info.primary_type = None;
assert!(filter.filter_album(&album)); assert!(filter.filter_album(&album));
@ -121,29 +136,42 @@ mod tests {
types.push(AlbumSecondaryType::AudioDrama); types.push(AlbumSecondaryType::AudioDrama);
assert!(filter.filter_album(&album)); assert!(filter.filter_album(&album));
// Filtered type. // Filtered type. But album is owned so it remains.
let types = &mut album.meta.info.secondary_types; let types = &mut album.meta.info.secondary_types;
types.push(AlbumSecondaryType::Live); 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. // Remove non-filtered type.
album.meta.info.secondary_types.remove(0); album.meta.info.secondary_types.remove(0);
assert!(!filter.filter_album(&album)); assert!(filter.filter_album(&album));
// Add another filtered type. // Add another filtered type.
let types = &mut album.meta.info.secondary_types; let types = &mut album.meta.info.secondary_types;
types.push(AlbumSecondaryType::Soundtrack); 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)); assert!(!filter.filter_album(&album));
} }
#[test] #[test]
fn filter_ownership() { fn filter_ownership() {
let filter = test_filter(); 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(); 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)); assert!(!filter.filter_album(&album));
// Changing ownership should make it go back to being included.
album.tracks.push(test_track()); album.tracks.push(test_track());
assert_eq!( assert_eq!(
album.get_ownership(), album.get_ownership(),

View File

@ -6,7 +6,10 @@ use ratatui::{backend::CrosstermBackend, Terminal};
use structopt::StructOpt; use structopt::StructOpt;
use musichoard::{ use musichoard::{
collection::album::{AlbumOwnership, AlbumPrimaryType, AlbumSecondaryType}, collection::{
album::{AlbumOwnership, AlbumPrimaryType, AlbumSecondaryType},
track::TrackFormat,
},
external::{ external::{
database::json::{backend::JsonDatabaseFileBackend, JsonDatabase}, database::json::{backend::JsonDatabaseFileBackend, JsonDatabase},
library::beets::{ library::beets::{
@ -73,25 +76,29 @@ struct DbOpt {
fn default_filter() -> CollectionFilter { fn default_filter() -> CollectionFilter {
CollectionFilter { CollectionFilter {
include: vec![ include: vec![vec![
AlbumField::PrimaryType(None), AlbumField::PrimaryType(None),
AlbumField::PrimaryType(Some(AlbumPrimaryType::Ep)), AlbumField::PrimaryType(Some(AlbumPrimaryType::Ep)),
AlbumField::PrimaryType(Some(AlbumPrimaryType::Album)), AlbumField::PrimaryType(Some(AlbumPrimaryType::Album)),
], AlbumField::Ownership(AlbumOwnership::Owned(TrackFormat::Mp3)),
AlbumField::Ownership(AlbumOwnership::Owned(TrackFormat::Flac)),
]],
except: vec![ except: vec![
AlbumField::SecondaryType(AlbumSecondaryType::Compilation), vec![AlbumField::Ownership(AlbumOwnership::None)],
AlbumField::SecondaryType(AlbumSecondaryType::Soundtrack), vec![
AlbumField::SecondaryType(AlbumSecondaryType::Spokenword), AlbumField::SecondaryType(AlbumSecondaryType::Compilation),
AlbumField::SecondaryType(AlbumSecondaryType::Interview), AlbumField::SecondaryType(AlbumSecondaryType::Soundtrack),
AlbumField::SecondaryType(AlbumSecondaryType::Audiobook), AlbumField::SecondaryType(AlbumSecondaryType::Spokenword),
AlbumField::SecondaryType(AlbumSecondaryType::AudioDrama), AlbumField::SecondaryType(AlbumSecondaryType::Interview),
AlbumField::SecondaryType(AlbumSecondaryType::Live), AlbumField::SecondaryType(AlbumSecondaryType::Audiobook),
AlbumField::SecondaryType(AlbumSecondaryType::Remix), AlbumField::SecondaryType(AlbumSecondaryType::AudioDrama),
AlbumField::SecondaryType(AlbumSecondaryType::DjMix), AlbumField::SecondaryType(AlbumSecondaryType::Live),
AlbumField::SecondaryType(AlbumSecondaryType::MixtapeStreet), AlbumField::SecondaryType(AlbumSecondaryType::Remix),
AlbumField::SecondaryType(AlbumSecondaryType::Demo), AlbumField::SecondaryType(AlbumSecondaryType::DjMix),
AlbumField::SecondaryType(AlbumSecondaryType::FieldRecording), AlbumField::SecondaryType(AlbumSecondaryType::MixtapeStreet),
AlbumField::Ownership(AlbumOwnership::None), AlbumField::SecondaryType(AlbumSecondaryType::Demo),
AlbumField::SecondaryType(AlbumSecondaryType::FieldRecording),
],
], ],
} }
} }
@ -100,7 +107,7 @@ fn with<Database: IDatabase + 'static, Library: ILibrary + 'static>(
builder: MusicHoardBuilder<Database, Library>, builder: MusicHoardBuilder<Database, Library>,
) { ) {
let mut music_hoard = builder.build().expect("failed to initialise MusicHoard"); 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. // Initialize the terminal user interface.
let backend = CrosstermBackend::new(io::stdout()); let backend = CrosstermBackend::new(io::stdout());