monstersirenfetch/composite.go
Yonah 2e0a3a9bf8
composite: flatten album and song data
This establishes relation between album and song.

Signed-off-by: Yonah <contrib@gensokyo.uk>
2025-09-17 08:31:39 +09:00

69 lines
2.0 KiB
Go

package monstersirenfetch
import (
"os"
"strconv"
)
// AlbumConflictError is returned by [Flatten] if two or more albums share the same CID.
type AlbumConflictError struct {
Index int
Previous Album
Current Album
}
func (e *AlbumConflictError) Error() string {
return "album CID conflict on index " + strconv.Itoa(e.Index) + ": " +
"previous album " + e.Previous.Name + ", " +
"current album " + e.Current.Name
}
// SongConflictError is returned by [Flatten] if two or more songs belonging to the same [Album] share the same CID.
type SongConflictError struct {
Index int
Previous *Song
Current Song
}
func (e *SongConflictError) Error() string {
return "song CID conflict on index " + strconv.Itoa(e.Index) + ": " +
"previous song " + e.Previous.Name + ", " +
"current song " + e.Current.Name
}
// CompositeAlbum represents an [Album] with a collection of its associated identifier-only [Song].
type CompositeAlbum struct {
// Songs is a map of [Song.CID] to identifier-only [Song].
Songs map[StringInt]*Song
*Album
}
// CompositeAlbumsMap is a map of [Album.CID] to [CompositeAlbum].
type CompositeAlbumsMap map[StringInt]CompositeAlbum
// Flatten flattens [AlbumsData] and [SongsData] into a [CompositeAlbumsMap].
// All values in [CompositeAlbum.Songs] and the [CompositeAlbum.Album] field are guaranteed to be non-nil.
func Flatten(albumData AlbumsData, songsData SongsData) (CompositeAlbumsMap, error) {
m := make(CompositeAlbumsMap, len(albumData))
for i, a := range albumData {
if c, ok := m[a.CID]; ok {
return nil, &AlbumConflictError{Index: i, Previous: *c.Album, Current: a}
}
m[a.CID] = CompositeAlbum{Songs: make(map[StringInt]*Song), Album: &a}
}
for i, s := range songsData.List {
var c *Song
if a, ok := m[s.AlbumCID]; !ok {
return nil, os.ErrNotExist
} else if c, ok = a.Songs[s.CID]; ok {
return nil, &SongConflictError{Index: i, Previous: c, Current: s}
} else {
a.Songs[s.CID] = &s
}
}
return m, nil
}