Basic fix working

This commit is contained in:
Wojciech Kozlowski 2025-01-03 14:42:09 +01:00
parent 2b468260cc
commit 0f658f8730
4 changed files with 124 additions and 84 deletions

View File

@ -1,7 +1,7 @@
use std::mem; use std::mem;
use crate::core::collection::{ use crate::core::collection::{
merge::{Merge, MergeName, MergeSorted}, merge::{Merge, MergeSorted},
musicbrainz::{MbAlbumRef, MbRefOption}, musicbrainz::{MbAlbumRef, MbRefOption},
track::{Track, TrackFormat}, track::{Track, TrackFormat},
}; };
@ -54,12 +54,6 @@ impl AlbumLibId {
/// Unique database identifier. Use MBID for this purpose. /// Unique database identifier. Use MBID for this purpose.
pub type AlbumMbRef = MbRefOption<MbAlbumRef>; pub type AlbumMbRef = MbRefOption<MbAlbumRef>;
impl MergeName for Album {
fn name(&self) -> &str {
&self.meta.id.title
}
}
// There are crates for handling dates, but we don't need much complexity beyond year-month-day. // There are crates for handling dates, but we don't need much complexity beyond year-month-day.
/// The album's release date. /// The album's release date.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]

View File

@ -1,11 +1,12 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
fmt::{self, Debug, Display}, fmt::{self, Debug, Display},
mem,
}; };
use crate::core::collection::{ use crate::core::collection::{
album::{Album, AlbumLibId}, album::{Album, AlbumLibId},
merge::{Merge, MergeCollections, MergeName}, merge::{Merge, MergeCollections},
musicbrainz::{MbArtistRef, MbRefOption}, musicbrainz::{MbArtistRef, MbRefOption},
}; };
@ -30,12 +31,6 @@ pub struct ArtistInfo {
pub properties: HashMap<String, Vec<String>>, pub properties: HashMap<String, Vec<String>>,
} }
impl MergeName for Artist {
fn name(&self) -> &str {
&self.meta.id.name
}
}
/// The artist identifier. /// The artist identifier.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ArtistId { pub struct ArtistId {
@ -46,49 +41,6 @@ pub struct ArtistId {
/// Unique database identifier. Use MBID for this purpose. /// Unique database identifier. Use MBID for this purpose.
pub type ArtistMbRef = MbRefOption<MbArtistRef>; pub type ArtistMbRef = MbRefOption<MbArtistRef>;
impl Artist {
/// Create new [`Artist`] with the given [`ArtistId`].
pub fn new<Id: Into<ArtistId>>(id: Id) -> Self {
Artist {
meta: ArtistMeta::new(id),
albums: vec![],
}
}
fn merge_album_by_lib_id(
primary_albums: &mut [Album],
mut secondary_album: Album,
) -> Option<Album> {
let id_opt = secondary_album.meta.id.lib_id;
if let id @ AlbumLibId::Value(_) | id @ AlbumLibId::Singleton = id_opt {
let mut iter = primary_albums.iter_mut();
if let Some(ref mut primary_album) = iter.find(|a| a.meta.id.lib_id == id) {
primary_album.merge_in_place(secondary_album);
return None;
}
secondary_album.meta.id.lib_id = AlbumLibId::None;
}
Some(secondary_album)
}
fn merge_albums_by_lib_id(
primary_albums: &mut [Album],
secondary_albums: Vec<Album>,
) -> HashMap<String, Vec<Album>> {
let mut secondary_without_id = HashMap::<String, Vec<Album>>::new();
for secondary_album in secondary_albums.into_iter() {
let unmerged = Artist::merge_album_by_lib_id(primary_albums, secondary_album);
if let Some(secondary_album) = unmerged {
secondary_without_id
.entry(secondary_album.meta.id.title.clone())
.or_default()
.push(secondary_album);
}
}
secondary_without_id
}
}
impl PartialOrd for Artist { impl PartialOrd for Artist {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
@ -104,10 +56,97 @@ impl Ord for Artist {
impl Merge for Artist { impl Merge for Artist {
fn merge_in_place(&mut self, other: Self) { fn merge_in_place(&mut self, other: Self) {
self.meta.merge_in_place(other.meta); self.meta.merge_in_place(other.meta);
self.albums = MergeAlbums::merge_albums(mem::take(&mut self.albums), other.albums);
}
}
let other_without_id = Artist::merge_albums_by_lib_id(&mut self.albums, other.albums); #[derive(Debug, Default)]
MergeCollections::merge_by_name(&mut self.albums, other_without_id); struct MergeAlbums {
self.albums.sort_unstable(); primary_by_lib_id: HashMap<u32, Album>,
primary_by_singleton: HashMap<String, Album>,
primary_by_title: HashMap<String, Vec<Album>>,
secondary_by_title: HashMap<String, Vec<Album>>,
merged: Vec<Album>,
}
impl MergeAlbums {
fn merge_albums(primary_albums: Vec<Album>, secondary_albums: Vec<Album>) -> Vec<Album> {
let mut cache = MergeAlbums::new(primary_albums);
cache.merge_albums_by_lib_id(secondary_albums);
cache.merged.extend(MergeCollections::merge_by_name(
cache.primary_by_title,
cache.secondary_by_title,
));
cache.merged.sort_unstable();
cache.merged
}
fn new(primary_albums: Vec<Album>) -> Self {
let mut cache = MergeAlbums::default();
for album in primary_albums.into_iter() {
match album.meta.id.lib_id {
AlbumLibId::Value(value) => {
assert!(cache.primary_by_lib_id.insert(value, album).is_none())
}
AlbumLibId::Singleton => assert!(cache
.primary_by_singleton
.insert(album.meta.id.title.clone(), album)
.is_none()),
_ => unreachable!("primary collection should always have a lib_id"),
}
}
cache
}
fn merge_albums_by_lib_id(&mut self, secondary_albums: Vec<Album>) {
for secondary_album in secondary_albums.into_iter() {
self.merge_album_by_lib_id(secondary_album);
}
for (_, primary_album) in self.primary_by_lib_id.drain() {
self.primary_by_title
.entry(primary_album.meta.id.title.clone())
.or_default()
.push(primary_album);
}
for (title, primary_album) in self.primary_by_singleton.drain() {
self.primary_by_title
.entry(title)
.or_default()
.push(primary_album);
}
}
fn merge_album_by_lib_id(&mut self, mut secondary_album: Album) {
let title = &secondary_album.meta.id.title;
let id_opt = secondary_album.meta.id.lib_id;
let find = match id_opt {
AlbumLibId::Value(value) => self.primary_by_lib_id.remove(&value),
AlbumLibId::Singleton => self.primary_by_singleton.remove(title),
AlbumLibId::None => None,
};
if let Some(mut primary_album) = find {
primary_album.merge_in_place(secondary_album);
self.merged.push(primary_album);
return;
}
secondary_album.meta.id.lib_id = AlbumLibId::None;
self.secondary_by_title
.entry(secondary_album.meta.id.title.clone())
.or_default()
.push(secondary_album);
}
}
impl Artist {
/// Create new [`Artist`] with the given [`ArtistId`].
pub fn new<Id: Into<ArtistId>>(id: Id) -> Self {
Artist {
meta: ArtistMeta::new(id),
albums: vec![],
}
} }
} }

View File

@ -1,4 +1,4 @@
use std::{cmp::Ordering, collections::HashMap, hash::Hash, iter::Peekable, marker::PhantomData}; use std::{cmp::Ordering, collections::HashMap, hash::Hash, iter::Peekable};
/// A trait for merging two objects. The merge is asymmetric with the left argument considered to be /// A trait for merging two objects. The merge is asymmetric with the left argument considered to be
/// the primary whose properties are to be kept in case of collisions. /// the primary whose properties are to be kept in case of collisions.
@ -80,30 +80,30 @@ where
} }
} }
pub trait MergeName { pub struct MergeCollections;
fn name(&self) -> &str;
}
pub struct MergeCollections<T, IT> { impl MergeCollections {
_t: PhantomData<T>, pub fn merge_by_name<T, It>(mut primary: HashMap<String, Vec<T>>, secondary: It) -> Vec<T>
_it: PhantomData<IT>, where
} T: Merge,
It: IntoIterator<Item = (String, Vec<T>)>,
impl<T, IT> MergeCollections<T, IT> {
where let mut merged = vec![];
T: MergeName + Merge, for (title, mut secondary_items) in secondary.into_iter() {
IT: IntoIterator<Item = (String, Vec<T>)>, match primary.remove(&title) {
{ Some(mut primary_items) => {
pub fn merge_by_name(primary_items: &mut Vec<T>, secondary: IT) { // We do not support merging multiple items with same name yet. Support will be
for (name, mut secondary_items) in secondary.into_iter() { // added once encountered in the wild.
match primary_items.iter_mut().find(|item| item.name() == name) { assert_eq!(primary_items.len(), 1);
Some(ref mut primary_item) => {
// We do not support merging multiple DB items with same name yet.
assert_eq!(secondary_items.len(), 1); assert_eq!(secondary_items.len(), 1);
let mut primary_item = primary_items.pop().unwrap();
primary_item.merge_in_place(secondary_items.pop().unwrap()); primary_item.merge_in_place(secondary_items.pop().unwrap());
merged.push(primary_item);
} }
None => primary_items.extend(secondary_items), None => merged.extend(secondary_items),
} }
} }
merged.extend(primary.into_values().flatten());
merged
} }
} }

View File

@ -59,9 +59,16 @@ impl<Database, Library> IMusicHoardBasePrivate for MusicHoard<Database, Library>
} }
fn merge_collections(&self) -> Collection { fn merge_collections(&self) -> Collection {
let mut primary = self.library_cache.clone(); let mut primary = HashMap::<String, Vec<Artist>>::new();
let mut secondary = HashMap::<String, Vec<Artist>>::new(); let mut secondary = HashMap::<String, Vec<Artist>>::new();
for artist in self.library_cache.iter().cloned() {
primary
.entry(artist.meta.id.name.clone())
.or_default()
.push(artist);
}
for artist in self.database_cache.iter().cloned() { for artist in self.database_cache.iter().cloned() {
secondary secondary
.entry(artist.meta.id.name.clone()) .entry(artist.meta.id.name.clone())
@ -69,10 +76,10 @@ impl<Database, Library> IMusicHoardBasePrivate for MusicHoard<Database, Library>
.push(artist); .push(artist);
} }
MergeCollections::merge_by_name(&mut primary, secondary); let mut collection = MergeCollections::merge_by_name(primary, secondary);
primary.sort_unstable(); collection.sort_unstable();
primary collection
} }
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> {