diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index 308e901..9e5c7e4 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -137,7 +137,27 @@ pub enum AlbumSecondaryType { FieldRecording, } +impl AlbumSecondaryType { + pub fn all_variants() -> [AlbumSecondaryType; 12] { + [ + AlbumSecondaryType::Compilation, + AlbumSecondaryType::Soundtrack, + AlbumSecondaryType::Spokenword, + AlbumSecondaryType::Interview, + AlbumSecondaryType::Audiobook, + AlbumSecondaryType::AudioDrama, + AlbumSecondaryType::Live, + AlbumSecondaryType::Remix, + AlbumSecondaryType::DjMix, + AlbumSecondaryType::MixtapeStreet, + AlbumSecondaryType::Demo, + AlbumSecondaryType::FieldRecording, + ] + } +} + /// The album's ownership status. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum AlbumOwnership { None, Owned(TrackFormat), @@ -453,4 +473,12 @@ mod tests { let expected = meta.clone().with_date(right.date); assert_eq!(expected, left.merge(right)); } + + #[test] + fn secondary_types_all_variants() { + let mut variants = AlbumSecondaryType::all_variants().to_vec(); + variants.sort_unstable(); + variants.dedup(); + assert_eq!(variants, AlbumSecondaryType::all_variants().to_vec()); + } } diff --git a/src/core/musichoard/base.rs b/src/core/musichoard/base.rs index f247eb0..08a04c7 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.flat_map(|a| self.filter_artist(a)).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) } @@ -117,7 +148,11 @@ impl IMusicHoardBasePrivate for MusicHoard #[cfg(test)] mod tests { - use crate::core::testmod::FULL_COLLECTION; + use crate::{ + collection::{album::AlbumPrimaryType, artist::ArtistMeta}, + core::testmod::FULL_COLLECTION, + filter::AlbumField, + }; use super::*; @@ -290,4 +325,31 @@ mod tests { mh.collection = mh.merge_collections(); assert_eq!(expected, mh.collection); } + + #[test] + fn filtered() { + let mut mh = MusicHoard { + collection: vec![Artist { + meta: ArtistMeta::new(ArtistId::new("Artist")), + albums: vec![ + Album::new(AlbumId::new("Album 1")), + Album::new(AlbumId::new("Album 2")), + ], + }], + ..Default::default() + }; + mh.collection[0].albums[0].meta.info.primary_type = Some(AlbumPrimaryType::Ep); + mh.collection[0].albums[0].meta.info.primary_type = Some(AlbumPrimaryType::Album); + + mh.set_filter(CollectionFilter { + include: vec![vec![AlbumField::PrimaryType(Some(AlbumPrimaryType::Album))]], + except: vec![], + }); + + assert_eq!(mh.get_collection().len(), 1); + assert_eq!(mh.get_filtered().len(), 1); + + assert_eq!(mh.get_collection()[0].albums.len(), 2); + assert_eq!(mh.get_filtered()[0].albums.len(), 1); + } } diff --git a/src/core/musichoard/builder.rs b/src/core/musichoard/builder.rs index 5ea72ce..1901a9f 100644 --- a/src/core/musichoard/builder.rs +++ b/src/core/musichoard/builder.rs @@ -1,6 +1,8 @@ use crate::core::{ interface::{database::IDatabase, library::ILibrary}, - musichoard::{database::IMusicHoardDatabase, Error, MusicHoard, NoDatabase, NoLibrary}, + musichoard::{ + database::IMusicHoardDatabase, CollectionFilter, Error, MusicHoard, NoDatabase, NoLibrary, + }, }; /// Builder for [`MusicHoard`]. Its purpose is to make it easier to set various combinations of @@ -62,6 +64,8 @@ impl MusicHoard { /// Create a new [`MusicHoard`] without any library or database. pub fn empty() -> Self { MusicHoard { + filter: CollectionFilter::default(), + filtered: vec![], collection: vec![], pre_commit: vec![], database: NoDatabase, @@ -83,6 +87,8 @@ impl MusicHoard { /// Create a new [`MusicHoard`] with the provided [`ILibrary`] and no database. pub fn library(library: Library) -> Self { MusicHoard { + filter: CollectionFilter::default(), + filtered: vec![], collection: vec![], pre_commit: vec![], database: NoDatabase, @@ -104,6 +110,8 @@ impl MusicHoard { /// Create a new [`MusicHoard`] with the provided [`IDatabase`] and no library. pub fn database(database: Database) -> Result { let mut mh = MusicHoard { + filter: CollectionFilter::default(), + filtered: vec![], collection: vec![], pre_commit: vec![], database, @@ -127,6 +135,8 @@ impl MusicHoard { /// Create a new [`MusicHoard`] with the provided [`ILibrary`] and [`IDatabase`]. pub fn new(database: Database, library: Library) -> Result { let mut mh = MusicHoard { + filter: CollectionFilter::default(), + filtered: vec![], collection: vec![], pre_commit: vec![], database, diff --git a/src/core/musichoard/database.rs b/src/core/musichoard/database.rs index 0d387aa..990871c 100644 --- a/src/core/musichoard/database.rs +++ b/src/core/musichoard/database.rs @@ -120,6 +120,8 @@ impl IMusicHoardDatabase for MusicHoard IMusicHoardDatabasePrivate for MusicHoard { fn commit(&mut self) -> Result<(), Error> { self.collection = self.pre_commit.clone(); + self.filtered = self.filter_collection(); Ok(()) } } @@ -376,6 +379,7 @@ impl IMusicHoardDatabasePrivate for MusicHoard>, + pub except: 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(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: &[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().any(|st| st == filter) + } + AlbumField::Ownership(filter) => *filter == album.get_ownership(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::collection::{ + album::AlbumId, + track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality}, + }; + + use super::*; + + pub fn test_filter() -> CollectionFilter { + CollectionFilter { + 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![ + vec![AlbumField::Ownership(AlbumOwnership::None)], + vec![ + AlbumField::SecondaryType(AlbumSecondaryType::Compilation), + AlbumField::SecondaryType(AlbumSecondaryType::Soundtrack), + AlbumField::SecondaryType(AlbumSecondaryType::Live), + AlbumField::SecondaryType(AlbumSecondaryType::Demo), + ], + ], + } + } + + fn test_track() -> Track { + Track { + id: TrackId { + title: String::from("Track"), + }, + number: TrackNum(1), + artist: vec![String::from("Artist")], + quality: TrackQuality { + format: TrackFormat::Mp3, + bitrate: 320, + }, + } + } + + fn test_album() -> Album { + let mut album = Album::new(AlbumId::new("An Album")); + album.meta.info.primary_type = Some(AlbumPrimaryType::Album); + album.tracks.push(test_track()); + album + } + + #[test] + fn filter_primary_type() { + let filter = test_filter(); + let mut album = test_album(); + + // Drop ownership so that filtering is truly only on type. + album.tracks.clear(); + assert_eq!(album.get_ownership(), AlbumOwnership::None); + + album.meta.info.primary_type = None; + assert!(filter.filter_album(&album)); + + album.meta.info.primary_type = Some(AlbumPrimaryType::Ep); + assert!(filter.filter_album(&album)); + + album.meta.info.primary_type = Some(AlbumPrimaryType::Album); + assert!(filter.filter_album(&album)); + + album.meta.info.primary_type = Some(AlbumPrimaryType::Broadcast); + assert!(!filter.filter_album(&album)); + + album.meta.info.primary_type = Some(AlbumPrimaryType::Other); + assert!(!filter.filter_album(&album)); + } + + #[test] + fn filter_secondary_type() { + let filter = test_filter(); + let mut album = test_album(); + + assert!(filter.filter_album(&album)); + + // Non-filtered type. + let types = &mut album.meta.info.secondary_types; + types.push(AlbumSecondaryType::AudioDrama); + assert!(filter.filter_album(&album)); + + // Filtered type. But album is owned so it remains. + let types = &mut album.meta.info.secondary_types; + types.push(AlbumSecondaryType::Live); + assert_ne!(album.get_ownership(), AlbumOwnership::None); + 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)); + + // Remove one of the two filtered types. + album.meta.info.secondary_types.pop(); + assert!(!filter.filter_album(&album)); + + // Remove the second filtered type. Should now be included by primary type. + album.meta.info.secondary_types.pop(); + assert!(filter.filter_album(&album)); + } + + #[test] + fn filter_ownership() { + let filter = test_filter(); + 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(), + AlbumOwnership::Owned(TrackFormat::Mp3) + ); + assert!(filter.filter_album(&album)); + + album.tracks[0].quality.format = TrackFormat::Flac; + assert_eq!( + album.get_ownership(), + AlbumOwnership::Owned(TrackFormat::Flac) + ); + assert!(filter.filter_album(&album)); + } +} diff --git a/src/core/musichoard/mod.rs b/src/core/musichoard/mod.rs index f6ba502..3b3230d 100644 --- a/src/core/musichoard/mod.rs +++ b/src/core/musichoard/mod.rs @@ -5,18 +5,21 @@ 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::Collection; - -use crate::core::interface::{ - database::{LoadError as DatabaseLoadError, SaveError as DatabaseSaveError}, - library::Error as LibraryError, +use crate::core::{ + collection::Collection, + interface::{ + database::{LoadError as DatabaseLoadError, SaveError as DatabaseSaveError}, + library::Error as LibraryError, + }, }; /// The Music Hoard. It is responsible for pulling information from both the library and the @@ -24,6 +27,8 @@ use crate::core::interface::{ // TODO: Split into inner and external/interfaces to facilitate building. #[derive(Debug)] pub struct MusicHoard { + filter: CollectionFilter, + filtered: Collection, collection: Collection, pre_commit: Collection, database: Database, 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..c3cb2be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,10 @@ use ratatui::{backend::CrosstermBackend, Terminal}; use structopt::StructOpt; use musichoard::{ + collection::{ + album::{AlbumOwnership, AlbumPrimaryType, AlbumSecondaryType}, + track::TrackFormat, + }, external::{ database::json::{backend::JsonDatabaseFileBackend, JsonDatabase}, library::beets::{ @@ -14,11 +18,12 @@ use musichoard::{ }, musicbrainz::{api::MusicBrainzClient, http::MusicBrainzHttp}, }, + filter::{AlbumField, CollectionFilter}, interface::{ database::{IDatabase, NullDatabase}, library::{ILibrary, NullLibrary}, }, - MusicHoardBuilder, NoDatabase, NoLibrary, + IMusicHoardBase, MusicHoardBuilder, NoDatabase, NoLibrary, }; use tui::{ @@ -69,10 +74,31 @@ 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)), + AlbumField::Ownership(AlbumOwnership::Owned(TrackFormat::Mp3)), + AlbumField::Ownership(AlbumOwnership::Owned(TrackFormat::Flac)), + ]], + except: vec![ + vec![AlbumField::Ownership(AlbumOwnership::None)], + AlbumSecondaryType::all_variants() + .iter() + .cloned() + .map(AlbumField::SecondaryType) + .collect(), + ], + } +} + 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()); diff --git a/src/tui/app/machine/browse_state.rs b/src/tui/app/machine/browse_state.rs index 00e9cca..1785581 100644 --- a/src/tui/app/machine/browse_state.rs +++ b/src/tui/app/machine/browse_state.rs @@ -45,14 +45,14 @@ impl IAppInteractBrowse for AppMachine { fn increment_selection(mut self, delta: Delta) -> Self::APP { self.inner .selection - .increment_selection(self.inner.music_hoard.get_collection(), delta); + .increment_selection(self.inner.music_hoard.get_filtered(), delta); self.into() } fn decrement_selection(mut self, delta: Delta) -> Self::APP { self.inner .selection - .decrement_selection(self.inner.music_hoard.get_collection(), delta); + .decrement_selection(self.inner.music_hoard.get_filtered(), delta); self.into() } @@ -68,7 +68,7 @@ impl IAppInteractBrowse for AppMachine { let orig = ListSelection::get(&self.inner.selection); self.inner .selection - .reset(self.inner.music_hoard.get_collection()); + .reset(self.inner.music_hoard.get_filtered()); AppMachine::search_state(self.inner, orig).into() } diff --git a/src/tui/app/machine/fetch_state.rs b/src/tui/app/machine/fetch_state.rs index 9ce871c..55eb380 100644 --- a/src/tui/app/machine/fetch_state.rs +++ b/src/tui/app/machine/fetch_state.rs @@ -102,7 +102,7 @@ impl AppMachine { } fn fetch_job(inner: &AppInner, rx: MbApiReceiver) -> Result { - let coll = inner.music_hoard.get_collection(); + let coll = inner.music_hoard.get_filtered(); let artist = match inner.selection.state_artist(coll) { Some(artist_state) => &coll[artist_state.index], @@ -184,18 +184,22 @@ impl AppMachine { inner: &mut AppInner, fetch_albums: Vec, ) -> Result<(), musichoard::Error> { - let coll = inner.music_hoard.get_collection(); + let coll = inner.music_hoard.get_filtered(); let artist_state = inner.selection.state_artist(coll).unwrap(); let artist = &coll[artist_state.index]; let selection = KeySelection::get(coll, &inner.selection); - let artist_id = &artist.meta.id.clone(); + // Find the artist in the full collection to correctly identify already existing albums. + let artist_id = artist.meta.id.clone(); + let coll = inner.music_hoard.get_collection(); + let artist = coll.iter().find(|a| a.meta.id == artist_id).unwrap(); + for new in Self::new_albums(fetch_albums, &artist.albums).into_iter() { - inner.music_hoard.add_album(artist_id, new)?; + inner.music_hoard.add_album(&artist_id, new)?; } - let coll = inner.music_hoard.get_collection(); + let coll = inner.music_hoard.get_filtered(); inner.selection.select_by_id(coll, selection); Ok(()) @@ -361,7 +365,7 @@ impl IAppEventFetch for AppMachine { #[cfg(test)] mod tests { - use mockall::predicate; + use mockall::{predicate, Sequence}; use musichoard::collection::{ album::AlbumMeta, artist::{ArtistId, ArtistMeta}, @@ -735,11 +739,18 @@ mod tests { tx.send(fetch_result).unwrap(); drop(tx); - let mut music_hoard = music_hoard(collection); + let mut music_hoard = music_hoard(collection.clone()); + let mut seq = Sequence::new(); + music_hoard + .expect_get_collection() + .times(1) + .in_sequence(&mut seq) + .return_const(collection); music_hoard .expect_add_album() .with(predicate::eq(artist_id), predicate::eq(new_album)) .times(1) + .in_sequence(&mut seq) .return_once(|_, _| Ok(())); let app = AppMachine::app_fetch_next(inner(music_hoard), fetch); @@ -762,11 +773,18 @@ mod tests { tx.send(fetch_result).unwrap(); drop(tx); - let mut music_hoard = music_hoard(collection); + let mut music_hoard = music_hoard(collection.clone()); + let mut seq = Sequence::new(); + music_hoard + .expect_get_collection() + .times(1) + .in_sequence(&mut seq) + .return_const(collection); music_hoard .expect_add_album() .with(predicate::eq(artist_id), predicate::eq(new_album)) .times(1) + .in_sequence(&mut seq) .return_once(|_, _| Err(musichoard::Error::CollectionError(String::from("get rekt")))); let app = AppMachine::app_fetch_next(inner(music_hoard), fetch); diff --git a/src/tui/app/machine/match_state.rs b/src/tui/app/machine/match_state.rs index 17eec10..4c8e83c 100644 --- a/src/tui/app/machine/match_state.rs +++ b/src/tui/app/machine/match_state.rs @@ -447,7 +447,8 @@ mod tests { let (_tx, rx) = mpsc::channel(); let app_matches = MatchState::new(matches_info.clone(), FetchState::search(rx)); - let mut music_hoard = music_hoard(vec![]); + let collection = vec![]; + let mut music_hoard = music_hoard(collection.clone()); let artist_id = ArtistId::new("Artist"); match matches_info { EntityMatches::Album(_) => { @@ -456,6 +457,11 @@ mod tests { let info = AlbumInfo::default(); let mut seq = Sequence::new(); + music_hoard + .expect_get_collection() + .times(1) + .in_sequence(&mut seq) + .return_const(collection); music_hoard .expect_merge_album_info() .with(eq(artist_id.clone()), eq(album_id.clone()), eq(info)) @@ -595,7 +601,8 @@ mod tests { let (_tx, rx) = mpsc::channel(); let app_matches = MatchState::new(matches_info.clone(), FetchState::search(rx)); - let mut music_hoard = music_hoard(vec![]); + let collection = vec![]; + let mut music_hoard = music_hoard(collection.clone()); match matches_info { EntityMatches::Artist(_) => panic!(), EntityMatches::Album(matches) => { @@ -606,6 +613,11 @@ mod tests { let artist = matches.artist.clone(); let mut seq = Sequence::new(); + music_hoard + .expect_get_collection() + .times(1) + .in_sequence(&mut seq) + .return_const(collection); music_hoard .expect_merge_album_info() .with(eq(artist.clone()), eq(album_id.clone()), eq(meta.info)) @@ -647,13 +659,19 @@ mod tests { let (_tx, rx) = mpsc::channel(); let app_matches = MatchState::new(matches_info.clone(), FetchState::search(rx)); - let mut music_hoard = music_hoard(vec![artist]); + let collection = vec![artist]; + let mut music_hoard = music_hoard(collection.clone()); match matches_info { EntityMatches::Artist(_) => panic!(), EntityMatches::Album(matches) => { let artist = matches.artist.clone(); let mut seq = Sequence::new(); + music_hoard + .expect_get_collection() + .times(1) + .in_sequence(&mut seq) + .return_const(collection); music_hoard .expect_remove_album() .times(1) diff --git a/src/tui/app/machine/mod.rs b/src/tui/app/machine/mod.rs index 2d0322f..6f630a5 100644 --- a/src/tui/app/machine/mod.rs +++ b/src/tui/app/machine/mod.rs @@ -173,7 +173,7 @@ impl AppInner { music_hoard: MH, musicbrainz: MB, ) -> Self { - let selection = Selection::new(music_hoard.get_collection()); + let selection = Selection::new(music_hoard.get_filtered()); AppInner { running: true, music_hoard: Box::new(music_hoard), @@ -186,7 +186,7 @@ impl AppInner { impl<'a> From<&'a mut AppInner> for AppPublicInner<'a> { fn from(inner: &'a mut AppInner) -> Self { AppPublicInner { - collection: inner.music_hoard.get_collection(), + collection: inner.music_hoard.get_filtered(), selection: &mut inner.selection, } } @@ -330,7 +330,7 @@ mod tests { pub fn music_hoard(collection: Collection) -> MockIMusicHoard { let mut music_hoard = MockIMusicHoard::new(); - music_hoard.expect_get_collection().return_const(collection); + music_hoard.expect_get_filtered().return_const(collection); music_hoard } @@ -594,7 +594,7 @@ mod tests { .expect_rescan_library() .times(1) .return_once(|| Err(musichoard::Error::LibraryError(String::from("get rekt")))); - music_hoard.expect_get_collection().return_const(vec![]); + music_hoard.expect_get_filtered().return_const(vec![]); let app = App::new(music_hoard, mb_job_sender()); assert!(app.is_running()); diff --git a/src/tui/app/machine/reload_state.rs b/src/tui/app/machine/reload_state.rs index 25def5b..7be5033 100644 --- a/src/tui/app/machine/reload_state.rs +++ b/src/tui/app/machine/reload_state.rs @@ -28,19 +28,15 @@ impl IAppInteractReload for AppMachine { type APP = App; fn reload_library(mut self) -> Self::APP { - let previous = KeySelection::get( - self.inner.music_hoard.get_collection(), - &self.inner.selection, - ); + let previous = + KeySelection::get(self.inner.music_hoard.get_filtered(), &self.inner.selection); let result = self.inner.music_hoard.rescan_library(); self.refresh(previous, result) } fn reload_database(mut self) -> Self::APP { - let previous = KeySelection::get( - self.inner.music_hoard.get_collection(), - &self.inner.selection, - ); + let previous = + KeySelection::get(self.inner.music_hoard.get_filtered(), &self.inner.selection); let result = self.inner.music_hoard.reload_database(); self.refresh(previous, result) } @@ -60,7 +56,7 @@ impl IAppInteractReloadPrivate for AppMachine { Ok(()) => { self.inner .selection - .select_by_id(self.inner.music_hoard.get_collection(), previous); + .select_by_id(self.inner.music_hoard.get_filtered(), previous); AppMachine::browse_state(self.inner).into() } Err(err) => AppMachine::error_state(self.inner, err.to_string()).into(), diff --git a/src/tui/app/machine/search_state.rs b/src/tui/app/machine/search_state.rs index 736bcec..1e4dc0d 100644 --- a/src/tui/app/machine/search_state.rs +++ b/src/tui/app/machine/search_state.rs @@ -66,7 +66,7 @@ impl IAppInteractSearch for AppMachine { } fn step_back(mut self) -> Self::APP { - let collection = self.inner.music_hoard.get_collection(); + let collection = self.inner.music_hoard.get_filtered(); if let Some(memo) = self.state.memo.pop() { if memo.char { self.state.string.pop(); @@ -104,7 +104,7 @@ trait IAppInteractSearchPrivate { impl IAppInteractSearchPrivate for AppMachine { fn incremental_search(&mut self, next: bool) { - let collection = self.inner.music_hoard.get_collection(); + let collection = self.inner.music_hoard.get_filtered(); let search = &self.state.string; let sel = &self.inner.selection; @@ -121,7 +121,7 @@ impl IAppInteractSearchPrivate for AppMachine { }; if result.is_some() { - let collection = self.inner.music_hoard.get_collection(); + let collection = self.inner.music_hoard.get_filtered(); self.inner.selection.select(collection, result); } } diff --git a/src/tui/lib/mod.rs b/src/tui/lib/mod.rs index c08eb3a..ef67eec 100644 --- a/src/tui/lib/mod.rs +++ b/src/tui/lib/mod.rs @@ -18,6 +18,8 @@ use mockall::automock; pub trait IMusicHoard { fn rescan_library(&mut self) -> Result<(), musichoard::Error>; fn reload_database(&mut self) -> Result<(), musichoard::Error>; + + fn get_filtered(&self) -> &Collection; fn get_collection(&self) -> &Collection; fn add_album( @@ -65,6 +67,10 @@ impl IMusicHoard for MusicHoard::reload_database(self) } + fn get_filtered(&self) -> &Collection { + ::get_filtered(self) + } + fn get_collection(&self) -> &Collection { ::get_collection(self) } diff --git a/src/tui/mod.rs b/src/tui/mod.rs index 2a2fd80..630fcb5 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -200,7 +200,7 @@ mod tests { music_hoard.expect_reload_database().returning(|| Ok(())); music_hoard.expect_rescan_library().returning(|| Ok(())); - music_hoard.expect_get_collection().return_const(collection); + music_hoard.expect_get_filtered().return_const(collection); music_hoard }