Startup merge fails when the database has two albums with the same title as an album in the library #246

Merged
wojtek merged 11 commits from 245---startup-merge-fails-when-the-database-has-two-albums-with-the-same-title-as-an-album-in-the-library into main 2025-01-03 17:46:55 +01:00
3 changed files with 44 additions and 37 deletions
Showing only changes of commit 7b219933c1 - Show all commits

View File

@ -6,7 +6,7 @@ use std::{
use crate::core::collection::{ use crate::core::collection::{
album::{Album, AlbumLibId}, album::{Album, AlbumLibId},
merge::{Merge, MergeCollections}, merge::{Merge, MergeCollections, TitleMap},
musicbrainz::{MbArtistRef, MbRefOption}, musicbrainz::{MbArtistRef, MbRefOption},
string::{self, NormalString}, string::{self, NormalString},
}; };
@ -65,8 +65,8 @@ impl Merge for Artist {
struct MergeAlbums { struct MergeAlbums {
primary_by_lib_id: HashMap<u32, Album>, primary_by_lib_id: HashMap<u32, Album>,
primary_by_singleton: HashMap<NormalString, Album>, primary_by_singleton: HashMap<NormalString, Album>,
primary_by_title: HashMap<NormalString, Vec<Album>>, primary_by_title: TitleMap<Album>,
secondary_by_title: HashMap<NormalString, Vec<Album>>, secondary_by_title: TitleMap<Album>,
merged: Vec<Album>, merged: Vec<Album>,
} }
@ -104,16 +104,13 @@ impl MergeAlbums {
self.merge_album_by_lib_id(secondary_album); self.merge_album_by_lib_id(secondary_album);
} }
for (_, primary_album) in self.primary_by_lib_id.drain() { for (_, primary_album) in self.primary_by_lib_id.drain() {
self.primary_by_title self.primary_by_title.insert(
.entry(string::normalize_string(&primary_album.meta.id.title)) string::normalize_string(&primary_album.meta.id.title),
.or_default() primary_album,
.push(primary_album); );
} }
for (title, primary_album) in self.primary_by_singleton.drain() { for (title, primary_album) in self.primary_by_singleton.drain() {
self.primary_by_title self.primary_by_title.insert(title, primary_album);
.entry(title.clone())
.or_default()
.push(primary_album);
} }
} }
@ -134,10 +131,10 @@ impl MergeAlbums {
} }
secondary_album.meta.id.lib_id = AlbumLibId::None; secondary_album.meta.id.lib_id = AlbumLibId::None;
self.secondary_by_title self.secondary_by_title.insert(
.entry(string::normalize_string(&secondary_album.meta.id.title)) string::normalize_string(&secondary_album.meta.id.title),
.or_default() secondary_album,
.push(secondary_album); );
} }
} }

View File

@ -82,16 +82,35 @@ where
} }
} }
#[derive(Debug)]
pub struct TitleMap<T>(HashMap<NormalString, Vec<T>>);
impl<T> Default for TitleMap<T> {
fn default() -> Self {
TitleMap(HashMap::default())
}
}
impl<T> TitleMap<T> {
pub fn new() -> Self {
TitleMap(HashMap::new())
}
pub fn insert(&mut self, key: NormalString, item: T) {
self.0.entry(key).or_default().push(item);
}
fn remove(&mut self, key: &NormalString) -> Option<Vec<T>> {
self.0.remove(key)
}
}
pub struct MergeCollections; pub struct MergeCollections;
impl MergeCollections { impl MergeCollections {
pub fn merge_by_name<T, It>(mut primary: HashMap<NormalString, Vec<T>>, secondary: It) -> Vec<T> pub fn merge_by_name<T: Merge>(mut primary: TitleMap<T>, secondary: TitleMap<T>) -> Vec<T> {
where
T: Merge,
It: IntoIterator<Item = (NormalString, Vec<T>)>,
{
let mut merged = vec![]; let mut merged = vec![];
for (title, mut secondary_items) in secondary.into_iter() { for (title, mut secondary_items) in secondary.0.into_iter() {
match primary.remove(&title) { match primary.remove(&title) {
Some(mut primary_items) => { Some(mut primary_items) => {
// We do not support merging multiple items with same name yet. Support will be // We do not support merging multiple items with same name yet. Support will be
@ -105,7 +124,7 @@ impl MergeCollections {
None => merged.extend(secondary_items), None => merged.extend(secondary_items),
} }
} }
merged.extend(primary.into_values().flatten()); merged.extend(primary.0.into_values().flatten());
merged merged
} }
} }

View File

@ -1,12 +1,9 @@
use std::collections::HashMap;
use crate::core::{ use crate::core::{
collection::{ collection::{
album::{Album, AlbumId}, album::{Album, AlbumId},
artist::{Artist, ArtistId}, artist::{Artist, ArtistId},
merge::MergeCollections, merge::{MergeCollections, TitleMap},
string::{self, NormalString}, string, Collection,
Collection,
}, },
musichoard::{Error, MusicHoard}, musichoard::{Error, MusicHoard},
}; };
@ -60,21 +57,15 @@ impl<Database, Library> IMusicHoardBasePrivate for MusicHoard<Database, Library>
} }
fn merge_collections(&self) -> Collection { fn merge_collections(&self) -> Collection {
let mut primary = HashMap::<NormalString, Vec<Artist>>::new(); let mut primary = TitleMap::<Artist>::new();
let mut secondary = HashMap::<NormalString, Vec<Artist>>::new(); let mut secondary = TitleMap::<Artist>::new();
for artist in self.library_cache.iter().cloned() { for artist in self.library_cache.iter().cloned() {
primary primary.insert(string::normalize_string(&artist.meta.id.name), artist);
.entry(string::normalize_string(&artist.meta.id.name))
.or_default()
.push(artist);
} }
for artist in self.database_cache.iter().cloned() { for artist in self.database_cache.iter().cloned() {
secondary secondary.insert(string::normalize_string(&artist.meta.id.name), artist);
.entry(string::normalize_string(&artist.meta.id.name))
.or_default()
.push(artist);
} }
let mut collection = MergeCollections::merge_by_name(primary, secondary); let mut collection = MergeCollections::merge_by_name(primary, secondary);