Add a filtering tool to only show only certain release group types #252
@ -138,7 +138,7 @@ pub enum AlbumSecondaryType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The album's ownership status.
|
/// The album's ownership status.
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum AlbumOwnership {
|
pub enum AlbumOwnership {
|
||||||
None,
|
None,
|
||||||
Owned(TrackFormat),
|
Owned(TrackFormat),
|
||||||
|
@ -5,14 +5,25 @@ use crate::core::{
|
|||||||
merge::{MergeCollections, NormalMap},
|
merge::{MergeCollections, NormalMap},
|
||||||
string, Collection,
|
string, Collection,
|
||||||
},
|
},
|
||||||
musichoard::{Error, MusicHoard},
|
musichoard::{filter::CollectionFilter, Error, MusicHoard},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait IMusicHoardBase {
|
pub trait IMusicHoardBase {
|
||||||
|
fn set_filter(&mut self, filter: CollectionFilter);
|
||||||
|
fn get_filtered(&self) -> &Collection;
|
||||||
fn get_collection(&self) -> &Collection;
|
fn get_collection(&self) -> &Collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Database, Library> IMusicHoardBase for MusicHoard<Database, Library> {
|
impl<Database, Library> IMusicHoardBase for MusicHoard<Database, Library> {
|
||||||
|
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 {
|
fn get_collection(&self) -> &Collection {
|
||||||
&self.collection
|
&self.collection
|
||||||
}
|
}
|
||||||
@ -23,6 +34,8 @@ pub trait IMusicHoardBasePrivate {
|
|||||||
fn sort_albums_and_tracks<'a, C: Iterator<Item = &'a mut Artist>>(collection: C);
|
fn sort_albums_and_tracks<'a, C: Iterator<Item = &'a mut Artist>>(collection: C);
|
||||||
|
|
||||||
fn merge_collections(&self) -> Collection;
|
fn merge_collections(&self) -> Collection;
|
||||||
|
fn filter_collection(&self) -> Collection;
|
||||||
|
fn filter_artist(&self, artist: &Artist) -> Option<Artist>;
|
||||||
|
|
||||||
fn get_artist<'a>(collection: &'a Collection, artist_id: &ArtistId) -> Option<&'a Artist>;
|
fn get_artist<'a>(collection: &'a Collection, artist_id: &ArtistId) -> Option<&'a Artist>;
|
||||||
fn get_artist_mut<'a>(
|
fn get_artist_mut<'a>(
|
||||||
@ -74,6 +87,24 @@ impl<Database, Library> IMusicHoardBasePrivate for MusicHoard<Database, Library>
|
|||||||
collection
|
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<Artist> {
|
||||||
|
let iter = artist.albums.iter();
|
||||||
|
let filtered = iter.filter(|a| self.filter.filter_album(a));
|
||||||
|
let albums: Vec<Album> = 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> {
|
fn get_artist<'a>(collection: &'a Collection, artist_id: &ArtistId) -> Option<&'a Artist> {
|
||||||
collection.iter().find(|a| &a.meta.id == artist_id)
|
collection.iter().find(|a| &a.meta.id == artist_id)
|
||||||
}
|
}
|
||||||
|
50
src/core/musichoard/filter.rs
Normal file
50
src/core/musichoard/filter.rs
Normal file
@ -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<Vec<AlbumField>>,
|
||||||
|
pub exclude: Vec<Vec<AlbumField>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AlbumField {
|
||||||
|
PrimaryType(Option<AlbumPrimaryType>),
|
||||||
|
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<Vec<AlbumField>>, 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<AlbumField>, 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,19 +5,17 @@ mod database;
|
|||||||
mod library;
|
mod library;
|
||||||
|
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
|
pub mod filter;
|
||||||
|
|
||||||
pub use base::IMusicHoardBase;
|
pub use base::IMusicHoardBase;
|
||||||
pub use database::IMusicHoardDatabase;
|
pub use database::IMusicHoardDatabase;
|
||||||
|
pub use filter::CollectionFilter;
|
||||||
pub use library::IMusicHoardLibrary;
|
pub use library::IMusicHoardLibrary;
|
||||||
|
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
collection::{
|
collection::Collection,
|
||||||
album::{AlbumOwnership, AlbumPrimaryType, AlbumSecondaryType},
|
|
||||||
track::TrackFormat,
|
|
||||||
Collection,
|
|
||||||
},
|
|
||||||
interface::{
|
interface::{
|
||||||
database::{LoadError as DatabaseLoadError, SaveError as DatabaseSaveError},
|
database::{LoadError as DatabaseLoadError, SaveError as DatabaseSaveError},
|
||||||
library::Error as LibraryError,
|
library::Error as LibraryError,
|
||||||
@ -39,32 +37,6 @@ pub struct MusicHoard<Database, Library> {
|
|||||||
library_cache: Collection,
|
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<AlbumPrimaryType>,
|
|
||||||
secondary_types: Vec<AlbumSecondaryType>,
|
|
||||||
include_untyped: bool,
|
|
||||||
ownership: Vec<AlbumOwnership>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
/// Phantom type for when a library implementation is not needed.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NoLibrary;
|
pub struct NoLibrary;
|
||||||
|
@ -10,8 +10,8 @@ pub use core::collection;
|
|||||||
pub use core::interface;
|
pub use core::interface;
|
||||||
|
|
||||||
pub use core::musichoard::{
|
pub use core::musichoard::{
|
||||||
builder::MusicHoardBuilder, Error, IMusicHoardBase, IMusicHoardDatabase, IMusicHoardLibrary,
|
builder::MusicHoardBuilder, filter, Error, IMusicHoardBase, IMusicHoardDatabase,
|
||||||
MusicHoard, NoDatabase, NoLibrary,
|
IMusicHoardLibrary, MusicHoard, NoDatabase, NoLibrary,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
38
src/main.rs
38
src/main.rs
@ -6,19 +6,17 @@ use ratatui::{backend::CrosstermBackend, Terminal};
|
|||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use musichoard::{
|
use musichoard::{
|
||||||
external::{
|
collection::album::{AlbumOwnership, AlbumPrimaryType, AlbumSecondaryType}, external::{
|
||||||
database::json::{backend::JsonDatabaseFileBackend, JsonDatabase},
|
database::json::{backend::JsonDatabaseFileBackend, JsonDatabase},
|
||||||
library::beets::{
|
library::beets::{
|
||||||
executor::{ssh::BeetsLibrarySshExecutor, BeetsLibraryProcessExecutor},
|
executor::{ssh::BeetsLibrarySshExecutor, BeetsLibraryProcessExecutor},
|
||||||
BeetsLibrary,
|
BeetsLibrary,
|
||||||
},
|
},
|
||||||
musicbrainz::{api::MusicBrainzClient, http::MusicBrainzHttp},
|
musicbrainz::{api::MusicBrainzClient, http::MusicBrainzHttp},
|
||||||
},
|
}, filter::{AlbumField, CollectionFilter}, interface::{
|
||||||
interface::{
|
|
||||||
database::{IDatabase, NullDatabase},
|
database::{IDatabase, NullDatabase},
|
||||||
library::{ILibrary, NullLibrary},
|
library::{ILibrary, NullLibrary},
|
||||||
},
|
}, IMusicHoardBase, MusicHoardBuilder, NoDatabase, NoLibrary
|
||||||
MusicHoardBuilder, NoDatabase, NoLibrary,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use tui::{
|
use tui::{
|
||||||
@ -69,10 +67,38 @@ struct DbOpt {
|
|||||||
no_database: bool,
|
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<Database: IDatabase + 'static, Library: ILibrary + 'static>(
|
fn with<Database: IDatabase + 'static, Library: ILibrary + 'static>(
|
||||||
builder: MusicHoardBuilder<Database, Library>,
|
builder: MusicHoardBuilder<Database, Library>,
|
||||||
) {
|
) {
|
||||||
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.
|
// Initialize the terminal user interface.
|
||||||
let backend = CrosstermBackend::new(io::stdout());
|
let backend = CrosstermBackend::new(io::stdout());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user