Ensure consistency between in-memory and database state #146

Merged
wojtek merged 8 commits from 120---ensure-consistency-between-in-memory-and-database-state into main 2024-03-01 09:00:53 +01:00
Showing only changes of commit bff68a84ee - Show all commits

View File

@ -17,10 +17,11 @@ use crate::core::{
#[derive(Debug)] #[derive(Debug)]
pub struct MusicHoard<LIB, DB> { pub struct MusicHoard<LIB, DB> {
collection: Collection, collection: Collection,
pre_commit: Collection,
library: LIB, library: LIB,
database: DB, database: DB,
library_cache: HashMap<ArtistId, Artist>, library_cache: HashMap<ArtistId, Artist>,
pre_commit: Collection, database_cache: Collection,
} }
/// Phantom type for when a library implementation is not needed. /// Phantom type for when a library implementation is not needed.
@ -43,10 +44,11 @@ impl MusicHoard<NoLibrary, NoDatabase> {
pub fn empty() -> Self { pub fn empty() -> Self {
MusicHoard { MusicHoard {
collection: vec![], collection: vec![],
pre_commit: vec![],
library: NoLibrary, library: NoLibrary,
database: NoDatabase, database: NoDatabase,
library_cache: HashMap::new(), library_cache: HashMap::new(),
pre_commit: vec![], database_cache: vec![],
} }
} }
} }
@ -72,14 +74,14 @@ impl<LIB, DB> MusicHoard<LIB, DB> {
fn merge_collections(&mut self) { fn merge_collections(&mut self) {
let mut primary = self.library_cache.clone(); let mut primary = self.library_cache.clone();
for secondary_artist in self.collection.drain(..) { for secondary_artist in self.database_cache.iter().cloned() {
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);
} }
} }
self.collection.extend(primary.into_values()); self.collection = primary.into_values().collect();
Self::sort_artists(&mut self.collection); Self::sort_artists(&mut self.collection);
} }
@ -178,10 +180,11 @@ impl<LIB: ILibrary> MusicHoard<LIB, NoDatabase> {
pub fn library(library: LIB) -> Self { pub fn library(library: LIB) -> Self {
MusicHoard { MusicHoard {
collection: vec![], collection: vec![],
pre_commit: vec![],
library, library,
database: NoDatabase, database: NoDatabase,
library_cache: HashMap::new(), library_cache: HashMap::new(),
pre_commit: vec![], database_cache: vec![],
} }
} }
} }
@ -203,10 +206,11 @@ impl<DB: IDatabase> MusicHoard<NoLibrary, DB> {
pub fn database(database: DB) -> Result<Self, Error> { pub fn database(database: DB) -> Result<Self, Error> {
let mut mh = MusicHoard { let mut mh = MusicHoard {
collection: vec![], collection: vec![],
pre_commit: vec![],
library: NoLibrary, library: NoLibrary,
database, database,
library_cache: HashMap::new(), library_cache: HashMap::new(),
pre_commit: vec![], database_cache: vec![],
}; };
mh.reload_database()?; mh.reload_database()?;
Ok(mh) Ok(mh)
@ -216,8 +220,8 @@ impl<DB: IDatabase> MusicHoard<NoLibrary, 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 reload_database(&mut self) -> Result<(), Error> { pub fn reload_database(&mut self) -> Result<(), Error> {
self.collection = self.database.load()?; self.database_cache = self.database.load()?;
Self::sort_albums_and_tracks(self.collection.iter_mut()); Self::sort_albums_and_tracks(self.database_cache.iter_mut());
self.merge_collections(); self.merge_collections();
self.pre_commit = self.collection.clone(); self.pre_commit = self.collection.clone();
@ -367,10 +371,11 @@ impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> {
pub fn new(library: LIB, database: DB) -> Result<Self, Error> { pub fn new(library: LIB, database: DB) -> Result<Self, Error> {
let mut mh = MusicHoard { let mut mh = MusicHoard {
collection: vec![], collection: vec![],
pre_commit: vec![],
library, library,
database, database,
library_cache: HashMap::new(), library_cache: HashMap::new(),
pre_commit: vec![], database_cache: vec![],
}; };
mh.reload_database()?; mh.reload_database()?;
Ok(mh) Ok(mh)
@ -657,12 +662,12 @@ mod tests {
expected.sort_unstable(); expected.sort_unstable();
let mut mh = MusicHoard { let mut mh = MusicHoard {
collection: right.clone(),
library_cache: left library_cache: left
.clone() .clone()
.into_iter() .into_iter()
.map(|a| (a.id.clone(), a)) .map(|a| (a.id.clone(), a))
.collect(), .collect(),
database_cache: right.clone(),
..Default::default() ..Default::default()
}; };
@ -671,12 +676,12 @@ mod tests {
// The merge is completely non-overlapping so it should be commutative. // The merge is completely non-overlapping so it should be commutative.
let mut mh = MusicHoard { let mut mh = MusicHoard {
collection: left.clone(),
library_cache: right library_cache: right
.clone() .clone()
.into_iter() .into_iter()
.map(|a| (a.id.clone(), a)) .map(|a| (a.id.clone(), a))
.collect(), .collect(),
database_cache: left.clone(),
..Default::default() ..Default::default()
}; };
@ -695,12 +700,12 @@ mod tests {
expected.sort_unstable(); expected.sort_unstable();
let mut mh = MusicHoard { let mut mh = MusicHoard {
collection: right.clone(),
library_cache: left library_cache: left
.clone() .clone()
.into_iter() .into_iter()
.map(|a| (a.id.clone(), a)) .map(|a| (a.id.clone(), a))
.collect(), .collect(),
database_cache: right.clone(),
..Default::default() ..Default::default()
}; };
@ -709,12 +714,12 @@ mod tests {
// The merge does not overwrite any data so it should be commutative. // The merge does not overwrite any data so it should be commutative.
let mut mh = MusicHoard { let mut mh = MusicHoard {
collection: left.clone(),
library_cache: right library_cache: right
.clone() .clone()
.into_iter() .into_iter()
.map(|a| (a.id.clone(), a)) .map(|a| (a.id.clone(), a))
.collect(), .collect(),
database_cache: left.clone(),
..Default::default() ..Default::default()
}; };
@ -746,12 +751,12 @@ mod tests {
expected.rotate_right(1); expected.rotate_right(1);
let mut mh = MusicHoard { let mut mh = MusicHoard {
collection: right.clone(),
library_cache: left library_cache: left
.clone() .clone()
.into_iter() .into_iter()
.map(|a| (a.id.clone(), a)) .map(|a| (a.id.clone(), a))
.collect(), .collect(),
database_cache: right.clone(),
..Default::default() ..Default::default()
}; };
@ -760,12 +765,12 @@ mod tests {
// The merge overwrites the sort data, but no data is erased so it should be commutative. // The merge overwrites the sort data, but no data is erased so it should be commutative.
let mut mh = MusicHoard { let mut mh = MusicHoard {
collection: left.clone(),
library_cache: right library_cache: right
.clone() .clone()
.into_iter() .into_iter()
.map(|a| (a.id.clone(), a)) .map(|a| (a.id.clone(), a))
.collect(), .collect(),
database_cache: left.clone(),
..Default::default() ..Default::default()
}; };
@ -792,6 +797,52 @@ mod tests {
assert_eq!(music_hoard.get_collection(), &*LIBRARY_COLLECTION); assert_eq!(music_hoard.get_collection(), &*LIBRARY_COLLECTION);
} }
#[test]
fn rescan_library_changed() {
let mut library = MockILibrary::new();
let mut seq = Sequence::new();
let library_input = Query::new();
let library_result = Ok(LIBRARY_ITEMS.to_owned());
library
.expect_list()
.with(predicate::eq(library_input))
.times(1)
.in_sequence(&mut seq)
.return_once(|_| library_result);
let library_input = Query::new();
let library_result = Ok(LIBRARY_ITEMS
.iter()
.filter(|item| item.album_title != "album_title a.a")
.cloned()
.collect());
library
.expect_list()
.with(predicate::eq(library_input))
.times(1)
.in_sequence(&mut seq)
.return_once(|_| library_result);
let mut music_hoard = MusicHoard::library(library);
music_hoard.rescan_library().unwrap();
assert!(music_hoard.get_collection()[0]
.albums
.iter()
.find(|album| album.id.title == "album_title a.a")
.is_some());
music_hoard.rescan_library().unwrap();
assert!(music_hoard.get_collection()[0]
.albums
.iter()
.find(|album| album.id.title == "album_title a.a")
.is_none());
}
#[test] #[test]
fn rescan_library_unordered() { fn rescan_library_unordered() {
let mut library = MockILibrary::new(); let mut library = MockILibrary::new();