From bd62068e095ff94ebc8e142b8468efad5a023c3f Mon Sep 17 00:00:00 2001 From: Hazel Noack Date: Tue, 7 Oct 2025 13:05:52 +0200 Subject: [PATCH] implemented recursive dedupe and compile --- internal/data/song.go | 165 +++++++++++++++++++++++++++++++++++ internal/data/source.go | 35 ++++++++ internal/plugin/interface.go | 45 +++++++++- main.go | 2 +- 4 files changed, 243 insertions(+), 4 deletions(-) diff --git a/internal/data/song.go b/internal/data/song.go index ed76bf7..eef28a5 100644 --- a/internal/data/song.go +++ b/internal/data/song.go @@ -1,6 +1,56 @@ package data +import "strconv" + type MusicObject interface { + Compile() MusicObject + GetIndices() []string + Merge(other MusicObject) MusicObject +} + +func dedupeMusicObjects[T MusicObject](inputMusicObjects []T) []T { + indexMapping := map[string]int{} + deduped := []T{} + + for _, musicObject := range inputMusicObjects { + indices := musicObject.GetIndices() + + // Check if we've seen any of these indices before + found := false + var existingIndex int + + for _, index := range indices { + if idx, exists := indexMapping[index]; exists { + found = true + existingIndex = idx + break + } + } + + if found { + // Merge with existing object + existing := deduped[existingIndex] + merged := existing.Merge(musicObject).(T) + deduped[existingIndex] = merged + + // Update indices for the merged object + newIndices := merged.GetIndices() + for _, index := range newIndices { + indexMapping[index] = existingIndex + } + } else { + // Add new object + deduped = append(deduped, musicObject) + newIndex := len(deduped) - 1 + + // Register all indices for this object + for _, index := range indices { + indexMapping[index] = newIndex + } + } + } + + return deduped } type Song struct { @@ -15,6 +65,44 @@ type Song struct { Sources []Source } +func (m Song) GetIndices() []string { + res := sourceIndices(m.Sources) + if m.UnifiedName != "" { + res = append(res, "name"+m.UnifiedName) + } + if m.Id != 0 { + res = append(res, "id"+strconv.Itoa(m.Id)) + } + return res +} + +func (m Song) Merge(other MusicObject) MusicObject { + otherSong, ok := other.(Song) + if !ok { + return m + } + + if m.Id == 0 && otherSong.Id != 0 { + m.Id = otherSong.Id + } + + if m.Name == "" && otherSong.Name != "" { + m.Name = otherSong.Name + m.UnifiedName = otherSong.UnifiedName + } + + m.Album = m.Album.Merge(otherSong.Album).(Album) + m.Sources = dedupeSources(append(m.Sources, otherSong.Sources...)) + + return m +} + +func (m Song) Compile() MusicObject { + m.Sources = dedupeSources(m.Sources) + m.Artists = dedupeMusicObjects(m.Artists) + return m +} + type Album struct { Id int @@ -27,6 +115,46 @@ type Album struct { Sources []Source } +func (m Album) GetIndices() []string { + res := sourceIndices(m.Sources) + if m.UnifiedName != "" { + res = append(res, "name"+m.UnifiedName) + } + if m.Id != 0 { + res = append(res, "id"+strconv.Itoa(m.Id)) + } + return res +} + +func (m Album) Merge(other MusicObject) MusicObject { + otherAlbum, ok := other.(Album) + if !ok { + return m + } + + if m.Id == 0 && otherAlbum.Id != 0 { + m.Id = otherAlbum.Id + } + + if m.Name == "" && otherAlbum.Name != "" { + m.Name = otherAlbum.Name + m.UnifiedName = otherAlbum.UnifiedName + } + + m.Songs = dedupeMusicObjects(append(m.Songs, otherAlbum.Songs...)) + m.Artists = dedupeMusicObjects(append(m.Artists, otherAlbum.Artists...)) + m.Sources = dedupeSources(append(m.Sources, otherAlbum.Sources...)) + + return m +} + +func (m Album) Compile() MusicObject { + m.Sources = dedupeSources(m.Sources) + m.Songs = dedupeMusicObjects(m.Songs) + m.Artists = dedupeMusicObjects(m.Artists) + return m +} + type Artist struct { Id int @@ -37,3 +165,40 @@ type Artist struct { Sources []Source } + +func (m Artist) Merge(other MusicObject) MusicObject { + otherArtist, ok := other.(Artist) + if !ok { + return m + } + + if m.Id == 0 && otherArtist.Id != 0 { + m.Id = otherArtist.Id + } + if m.Name == "" && otherArtist.Name != "" { + m.Name = otherArtist.Name + m.UnifiedName = otherArtist.UnifiedName + } + + m.Albums = dedupeMusicObjects(append(m.Albums, otherArtist.Albums...)) + m.Sources = dedupeSources(append(m.Sources, otherArtist.Sources...)) + + return m +} + +func (m Artist) GetIndices() []string { + res := sourceIndices(m.Sources) + if m.UnifiedName != "" { + res = append(res, "name"+m.UnifiedName) + } + if m.Id != 0 { + res = append(res, "id"+strconv.Itoa(m.Id)) + } + return res +} + +func (m Artist) Compile() MusicObject { + m.Sources = dedupeSources(m.Sources) + m.Albums = dedupeMusicObjects(m.Albums) + return m +} diff --git a/internal/data/source.go b/internal/data/source.go index 8461039..0272350 100644 --- a/internal/data/source.go +++ b/internal/data/source.go @@ -37,3 +37,38 @@ type Source struct { SourceType *SourceType ObjectType ObjectType } + +func dedupeSources(inputSources []Source) []Source { + urlMapping := map[string]int{} + + deduped := []Source{} + + for _, source := range inputSources { + if mergeWithIndex, ok := urlMapping[source.Url]; ok { + // has to merge current source with source at index + if source.ObjectType != "" { + deduped[mergeWithIndex].ObjectType = source.ObjectType + } + if source.SourceType != nil { + deduped[mergeWithIndex].SourceType = source.SourceType + } + + } else { + // just appending + urlMapping[source.Url] = len(deduped) + deduped = append(deduped, source) + } + } + + return deduped +} + +func sourceIndices(sources []Source) []string { + res := []string{} + + for _, source := range sources { + res = append(res, "url"+source.Url) + } + + return res +} diff --git a/internal/plugin/interface.go b/internal/plugin/interface.go index 2bc261c..7385806 100644 --- a/internal/plugin/interface.go +++ b/internal/plugin/interface.go @@ -60,6 +60,10 @@ func compileSource(source data.Source) (data.Source, error) { } // find what object this source corresponds to + if source.ObjectType != "" { + return source, nil + } + sourceType := source.SourceType if sourceType.RegexSong.MatchString(source.Url) { @@ -80,7 +84,24 @@ func compileSource(source data.Source) (data.Source, error) { return source, errors.New("couldn't find corresponding object source on " + sourceType.Name + " for " + source.Url) } +func dedupeSources(sources []data.Source) []data.Source { + urls := map[string]interface{}{} + deduped := []data.Source{} + + for _, raw := range sources { + parsed, _ := compileSource(raw) + + if _, u := urls[parsed.Url]; !u { + urls[parsed.Url] = true + deduped = append(deduped, parsed) + } + } + + return deduped +} + func Fetch(source data.Source) (data.MusicObject, error) { + // the fetch function without the post processing of the music objects source, err := compileSource(source) if err != nil { return nil, err @@ -92,15 +113,33 @@ func Fetch(source data.Source) (data.MusicObject, error) { } if source.ObjectType == data.SongSource { - return plugin.FetchSong(source) + song, err := plugin.FetchSong(source) + + if err != nil { + return song, err + } + song.Sources = dedupeSources(append(song.Sources, source)) + return song, nil } if source.ObjectType == data.AlbumSource { - return plugin.FetchAlbum(source) + album, err := plugin.FetchAlbum(source) + + if err != nil { + return album, err + } + album.Sources = dedupeSources(append(album.Sources, source)) + return album, nil } if source.ObjectType == data.ArtistSource { - return plugin.FetchArtist(source) + artist, err := plugin.FetchArtist(source) + + if err != nil { + return artist, err + } + artist.Sources = dedupeSources(append(artist.Sources, source)) + return artist, nil } return nil, nil diff --git a/main.go b/main.go index 7aed47f..b0e2d6c 100644 --- a/main.go +++ b/main.go @@ -8,5 +8,5 @@ import ( func main() { fmt.Println("music kraken") - data.GetSourceType("Youtube") + fmt.Println(data.Source{}.SourceType == nil) }