From eac27d3b17dd31fca2c964c65e65b9d4054178c1 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sat, 4 Jan 2025 17:26:09 +0100 Subject: [PATCH] Code draft --- src/core/collection/album.rs | 2 +- src/core/musichoard/base.rs | 33 ++++++++++++++++++++++- src/core/musichoard/filter.rs | 50 +++++++++++++++++++++++++++++++++++ src/core/musichoard/mod.rs | 34 +++--------------------- src/lib.rs | 4 +-- src/main.rs | 38 +++++++++++++++++++++----- 6 files changed, 120 insertions(+), 41 deletions(-) create mode 100644 src/core/musichoard/filter.rs diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index 95dbefb..e5d0033 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -138,7 +138,7 @@ pub enum AlbumSecondaryType { } /// The album's ownership status. -#[derive(Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum AlbumOwnership { None, Owned(TrackFormat), diff --git a/src/core/musichoard/base.rs b/src/core/musichoard/base.rs index f247eb0..db8f910 100644 --- a/src/core/musichoard/base.rs +++ b/src/core/musichoard/base.rs @@ -5,14 +5,25 @@ use crate::core::{ merge::{MergeCollections, NormalMap}, string, Collection, }, - musichoard::{Error, MusicHoard}, + musichoard::{filter::CollectionFilter, Error, MusicHoard}, }; pub trait IMusicHoardBase { + fn set_filter(&mut self, filter: CollectionFilter); + fn get_filtered(&self) -> &Collection; fn get_collection(&self) -> &Collection; } impl IMusicHoardBase for MusicHoard { + fn set_filter(&mut self, filter: CollectionFilter) { + self.filter = filter; + self.filtered = self.filter_collection(); + } + + fn get_filtered(&self) -> &Collection { + &self.filtered + } + fn get_collection(&self) -> &Collection { &self.collection } @@ -23,6 +34,8 @@ pub trait IMusicHoardBasePrivate { fn sort_albums_and_tracks<'a, C: Iterator>(collection: C); fn merge_collections(&self) -> Collection; + fn filter_collection(&self) -> Collection; + fn filter_artist(&self, artist: &Artist) -> Option; fn get_artist<'a>(collection: &'a Collection, artist_id: &ArtistId) -> Option<&'a Artist>; fn get_artist_mut<'a>( @@ -74,6 +87,24 @@ impl IMusicHoardBasePrivate for MusicHoard collection } + fn filter_collection(&self) -> Collection { + let iter = self.collection.iter(); + iter.map(|a| self.filter_artist(a)).flatten().collect() + } + + fn filter_artist(&self, artist: &Artist) -> Option { + let iter = artist.albums.iter(); + let filtered = iter.filter(|a| self.filter.filter_album(a)); + let albums: Vec = filtered.cloned().collect(); + + if albums.is_empty() { + return None; + } + + let meta = artist.meta.clone(); + Some(Artist { meta, albums }) + } + fn get_artist<'a>(collection: &'a Collection, artist_id: &ArtistId) -> Option<&'a Artist> { collection.iter().find(|a| &a.meta.id == artist_id) } diff --git a/src/core/musichoard/filter.rs b/src/core/musichoard/filter.rs new file mode 100644 index 0000000..d58fcb6 --- /dev/null +++ b/src/core/musichoard/filter.rs @@ -0,0 +1,50 @@ +use crate::core::collection::album::{Album, AlbumOwnership, AlbumPrimaryType, AlbumSecondaryType}; + +/// Filter for a specifying subsets of the entire collection (e.g., for display). +#[derive(Debug, Default)] +pub struct CollectionFilter { + pub include: Vec>, + pub exclude: Vec>, +} + +#[derive(Debug)] +pub enum AlbumField { + PrimaryType(Option), + SecondaryType(AlbumSecondaryType), + Ownership(AlbumOwnership), +} + +impl CollectionFilter { + pub fn filter_album(&self, album: &Album) -> bool { + let include = Self::filter_and(&self.include, album); + let exclude = Self::filter_and(&self.exclude, album); + include && !exclude + } + + fn filter_and(group: &Vec>, album: &Album) -> bool { + let mut filter = !group.is_empty(); + for group in group.iter() { + filter = filter && Self::filter_or(group, album); + } + filter + } + + fn filter_or(group: &Vec, album: &Album) -> bool { + let mut filter = false; + for field in group.iter() { + filter = filter || Self::filter_field(field, album); + } + filter + } + + fn filter_field(field: &AlbumField, album: &Album) -> bool { + match field { + AlbumField::PrimaryType(filter) => *filter == album.meta.info.primary_type, + AlbumField::SecondaryType(filter) => { + let types = &album.meta.info.secondary_types; + types.iter().find(|st| *st == filter).is_some() + } + AlbumField::Ownership(filter) => *filter == album.get_ownership(), + } + } +} diff --git a/src/core/musichoard/mod.rs b/src/core/musichoard/mod.rs index 8fb7026..3b3230d 100644 --- a/src/core/musichoard/mod.rs +++ b/src/core/musichoard/mod.rs @@ -5,19 +5,17 @@ mod database; mod library; pub mod builder; +pub mod filter; pub use base::IMusicHoardBase; pub use database::IMusicHoardDatabase; +pub use filter::CollectionFilter; pub use library::IMusicHoardLibrary; use std::fmt::{self, Display}; use crate::core::{ - collection::{ - album::{AlbumOwnership, AlbumPrimaryType, AlbumSecondaryType}, - track::TrackFormat, - Collection, - }, + collection::Collection, interface::{ database::{LoadError as DatabaseLoadError, SaveError as DatabaseSaveError}, library::Error as LibraryError, @@ -39,32 +37,6 @@ pub struct MusicHoard { library_cache: Collection, } -/// Filter for a specifying subsets of the entire collection (e.g., for display). -// Filter is inclusive, not exclusive. To include something in the filtered collection, the filter -// must select it. It also means that the options below are additive - the more options are set, the -// more items will be included. -#[derive(Debug)] -pub struct CollectionFilter { - primary_types: Vec, - secondary_types: Vec, - include_untyped: bool, - ownership: Vec, -} - -impl Default for CollectionFilter { - fn default() -> Self { - CollectionFilter { - primary_types: vec![AlbumPrimaryType::Ep, AlbumPrimaryType::Album], - secondary_types: vec![], - include_untyped: true, - ownership: vec![ - AlbumOwnership::Owned(TrackFormat::Mp3), - AlbumOwnership::Owned(TrackFormat::Flac), - ], - } - } -} - /// Phantom type for when a library implementation is not needed. #[derive(Debug)] pub struct NoLibrary; diff --git a/src/lib.rs b/src/lib.rs index 6e8d317..dd8dea7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,8 +10,8 @@ pub use core::collection; pub use core::interface; pub use core::musichoard::{ - builder::MusicHoardBuilder, Error, IMusicHoardBase, IMusicHoardDatabase, IMusicHoardLibrary, - MusicHoard, NoDatabase, NoLibrary, + builder::MusicHoardBuilder, filter, Error, IMusicHoardBase, IMusicHoardDatabase, + IMusicHoardLibrary, MusicHoard, NoDatabase, NoLibrary, }; #[cfg(test)] diff --git a/src/main.rs b/src/main.rs index cfbf750..bd6c6ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,19 +6,17 @@ use ratatui::{backend::CrosstermBackend, Terminal}; use structopt::StructOpt; use musichoard::{ - external::{ + collection::album::{AlbumOwnership, AlbumPrimaryType, AlbumSecondaryType}, external::{ database::json::{backend::JsonDatabaseFileBackend, JsonDatabase}, library::beets::{ executor::{ssh::BeetsLibrarySshExecutor, BeetsLibraryProcessExecutor}, BeetsLibrary, }, musicbrainz::{api::MusicBrainzClient, http::MusicBrainzHttp}, - }, - interface::{ + }, filter::{AlbumField, CollectionFilter}, interface::{ database::{IDatabase, NullDatabase}, library::{ILibrary, NullLibrary}, - }, - MusicHoardBuilder, NoDatabase, NoLibrary, + }, IMusicHoardBase, MusicHoardBuilder, NoDatabase, NoLibrary }; use tui::{ @@ -69,10 +67,38 @@ struct DbOpt { no_database: bool, } +fn default_filter() -> CollectionFilter { + CollectionFilter { + include: vec![vec![ + AlbumField::PrimaryType(None), + AlbumField::PrimaryType(Some(AlbumPrimaryType::Ep)), + AlbumField::PrimaryType(Some(AlbumPrimaryType::Album)), + ]], + exclude: vec![ + 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), + ], + ], + } +} + fn with( builder: MusicHoardBuilder, ) { - let 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()); // Initialize the terminal user interface. let backend = CrosstermBackend::new(io::stdout());