Add shortcut to reload database and/or library #116

Merged
wojtek merged 14 commits from 105---add-shortcut-to-reload-database-and/or-library into main 2024-02-03 14:32:13 +01:00
Showing only changes of commit e22fd6837e - Show all commits

View File

@ -1,4 +1,4 @@
use std::{collections::HashMap, mem}; use std::collections::HashMap;
use paste::paste; use paste::paste;
@ -20,6 +20,9 @@ pub struct MusicHoard<LIB, DB> {
collection: Collection, collection: Collection,
library: LIB, library: LIB,
database: DB, database: DB,
// There is no database cache since the database contains the entirety of the `collection`
// itself. Therefore, [`collection`] also represents the last state of the database.
library_cache: HashMap<ArtistId, Artist>,
} }
/// Phantom type for when a library implementation is not needed. /// Phantom type for when a library implementation is not needed.
@ -117,6 +120,7 @@ impl<LIB, DB> MusicHoard<LIB, DB> {
collection: vec![], collection: vec![],
library, library,
database, database,
library_cache: HashMap::new(),
} }
} }
@ -189,34 +193,17 @@ impl<LIB, DB> MusicHoard<LIB, DB> {
} }
} }
fn merge_with_primary(&mut self, primary: HashMap<ArtistId, Artist>) { fn merge_collections(&mut self) {
let collection = mem::take(&mut self.collection); let mut primary = self.library_cache.clone();
self.collection = Self::merge_collections(primary, collection); for secondary_artist in self.collection.drain(..) {
}
fn merge_with_secondary<SEC: IntoIterator<Item = Artist>>(&mut self, secondary: SEC) {
let primary_map: HashMap<ArtistId, Artist> = self
.collection
.drain(..)
.map(|a| (a.id.clone(), a))
.collect();
self.collection = Self::merge_collections(primary_map, secondary);
}
fn merge_collections<SEC: IntoIterator<Item = Artist>>(
mut primary: HashMap<ArtistId, Artist>,
secondary: SEC,
) -> Collection {
for secondary_artist in secondary.into_iter() {
if let Some(ref mut primary_artist) = primary.get_mut(&secondary_artist.id) { if let Some(ref mut primary_artist) = primary.get_mut(&secondary_artist.id) {
primary_artist.merge_in_place(secondary_artist); primary_artist.merge_in_place(secondary_artist);
} else { } else {
primary.insert(secondary_artist.id.clone(), secondary_artist); primary.insert(secondary_artist.id.clone(), secondary_artist);
} }
} }
let mut collection: Collection = primary.into_values().collect(); self.collection.extend(primary.into_values());
Self::sort_artists(&mut collection); Self::sort_artists(&mut self.collection);
collection
} }
fn items_to_artists(items: Vec<Item>) -> Result<HashMap<ArtistId, Artist>, Error> { fn items_to_artists(items: Vec<Item>) -> Result<HashMap<ArtistId, Artist>, Error> {
@ -307,10 +294,10 @@ impl<LIB: ILibrary, DB> MusicHoard<LIB, DB> {
/// Rescan the library and merge with the in-memory collection. /// Rescan the library and merge with the in-memory collection.
pub fn rescan_library(&mut self) -> Result<(), Error> { pub fn rescan_library(&mut self) -> Result<(), Error> {
let items = self.library.list(&Query::new())?; let items = self.library.list(&Query::new())?;
let mut library_collection = Self::items_to_artists(items)?; self.library_cache = Self::items_to_artists(items)?;
Self::sort_albums_and_tracks(library_collection.values_mut()); Self::sort_albums_and_tracks(self.library_cache.values_mut());
self.merge_with_primary(library_collection); self.merge_collections();
Ok(()) Ok(())
} }
} }
@ -318,10 +305,10 @@ impl<LIB: ILibrary, DB> MusicHoard<LIB, DB> {
impl<LIB, DB: IDatabase> MusicHoard<LIB, DB> { impl<LIB, DB: IDatabase> MusicHoard<LIB, DB> {
/// Load the database and merge with the in-memory collection. /// Load the database and merge with the in-memory collection.
pub fn load_from_database(&mut self) -> Result<(), Error> { pub fn load_from_database(&mut self) -> Result<(), Error> {
let mut database_collection = self.database.load()?; self.collection = self.database.load()?;
Self::sort_albums_and_tracks(database_collection.iter_mut()); Self::sort_albums_and_tracks(self.collection.iter_mut());
self.merge_with_secondary(database_collection); self.merge_collections();
Ok(()) Ok(())
} }
@ -729,25 +716,28 @@ mod tests {
let mut expected = FULL_COLLECTION.to_owned(); let mut expected = FULL_COLLECTION.to_owned();
expected.sort_unstable(); expected.sort_unstable();
let merged = MusicHoard::<NoLibrary, NoDatabase>::merge_collections( let mut mh = MusicHoard::default();
left.clone() mh.library_cache = left
.into_iter()
.map(|a| (a.id.clone(), a))
.collect(),
right.clone(),
);
assert_eq!(expected, merged);
// The merge is completely non-overlapping so it should be commutative.
let merged = MusicHoard::<NoLibrary, NoDatabase>::merge_collections(
right
.clone() .clone()
.into_iter() .into_iter()
.map(|a| (a.id.clone(), a)) .map(|a| (a.id.clone(), a))
.collect(), .collect();
left.clone(), mh.collection = right.clone();
);
assert_eq!(expected, merged); mh.merge_collections();
assert_eq!(expected, mh.collection);
// The merge is completely non-overlapping so it should be commutative.
let mut mh = MusicHoard::default();
mh.library_cache = right
.clone()
.into_iter()
.map(|a| (a.id.clone(), a))
.collect();
mh.collection = left.clone();
mh.merge_collections();
assert_eq!(expected, mh.collection);
} }
#[test] #[test]
@ -760,25 +750,28 @@ mod tests {
let mut expected = FULL_COLLECTION.to_owned(); let mut expected = FULL_COLLECTION.to_owned();
expected.sort_unstable(); expected.sort_unstable();
let merged = MusicHoard::<NoLibrary, NoDatabase>::merge_collections( let mut mh = MusicHoard::default();
left.clone() mh.library_cache = left
.into_iter()
.map(|a| (a.id.clone(), a))
.collect(),
right.clone(),
);
assert_eq!(expected, merged);
// The merge does not overwrite any data so it should be commutative.
let merged = MusicHoard::<NoLibrary, NoDatabase>::merge_collections(
right
.clone() .clone()
.into_iter() .into_iter()
.map(|a| (a.id.clone(), a)) .map(|a| (a.id.clone(), a))
.collect(), .collect();
left.clone(), mh.collection = right.clone();
);
assert_eq!(expected, merged); mh.merge_collections();
assert_eq!(expected, mh.collection);
// The merge does not overwrite any data so it should be commutative.
let mut mh = MusicHoard::default();
mh.library_cache = right
.clone()
.into_iter()
.map(|a| (a.id.clone(), a))
.collect();
mh.collection = left.clone();
mh.merge_collections();
assert_eq!(expected, mh.collection);
} }
#[test] #[test]
@ -804,27 +797,28 @@ mod tests {
expected.last_mut().as_mut().unwrap().sort = artist_sort.clone(); expected.last_mut().as_mut().unwrap().sort = artist_sort.clone();
expected.rotate_right(1); expected.rotate_right(1);
let merged = MusicHoard::<NoLibrary, NoDatabase>::merge_collections( let mut mh = MusicHoard::default();
left.clone() mh.library_cache = left
.into_iter()
.map(|a| (a.id.clone(), a))
.collect(),
right.clone(),
);
assert_eq!(expected.len(), merged.len());
assert_eq!(expected, merged);
// The merge overwrites the sort data, but no data is erased so it should be commutative.
let merged = MusicHoard::<NoLibrary, NoDatabase>::merge_collections(
right
.clone() .clone()
.into_iter() .into_iter()
.map(|a| (a.id.clone(), a)) .map(|a| (a.id.clone(), a))
.collect(), .collect();
left.clone(), mh.collection = right.clone();
);
assert_eq!(expected.len(), merged.len()); mh.merge_collections();
assert_eq!(expected, merged); assert_eq!(expected, mh.collection);
// The merge overwrites the sort data, but no data is erased so it should be commutative.
let mut mh = MusicHoard::default();
mh.library_cache = right
.clone()
.into_iter()
.map(|a| (a.id.clone(), a))
.collect();
mh.collection = left.clone();
mh.merge_collections();
assert_eq!(expected, mh.collection);
} }
#[test] #[test]