This establishes relation between album and song. Signed-off-by: Yonah <contrib@gensokyo.uk>
108 lines
2.9 KiB
Go
108 lines
2.9 KiB
Go
package monstersirenfetch
|
|
|
|
import (
|
|
"maps"
|
|
"os"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// 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 SongsMap `json:"songs"`
|
|
|
|
*Album
|
|
}
|
|
|
|
type (
|
|
// CompositeAlbumsMap is a map of [Album.CID] to [CompositeAlbum].
|
|
CompositeAlbumsMap map[StringInt]CompositeAlbum
|
|
|
|
// SongsMap is a map of [Song.CID] to the address of [Song].
|
|
SongsMap map[StringInt]*Song
|
|
)
|
|
|
|
func (m CompositeAlbumsMap) String() string {
|
|
if len(m) == 0 {
|
|
return "<empty>"
|
|
}
|
|
|
|
compAlbums := slices.Collect(maps.Values(m))
|
|
slices.SortFunc(compAlbums, func(a, b CompositeAlbum) int { return int(a.CID - b.CID) })
|
|
|
|
s := make([]string, len(compAlbums))
|
|
var buf strings.Builder
|
|
for i, ca := range compAlbums {
|
|
buf.WriteString(
|
|
"Album: " + ca.Name + " (" + ca.CID.String() + ")\n" +
|
|
"Cover: " + ca.CoverURL + "\n" +
|
|
"Artist(s): " + strings.Join(ca.Artists, ", ") + "\n" +
|
|
"Songs:\n")
|
|
|
|
albumSongs := slices.Collect(maps.Values(ca.Songs))
|
|
slices.SortFunc(albumSongs, func(a, b *Song) int { return int(a.CID - b.CID) })
|
|
for _, cs := range albumSongs {
|
|
buf.WriteString(
|
|
" " + strings.Join(cs.Artists, ", ") + " ─ " + cs.Name + "\n")
|
|
}
|
|
|
|
s[i] = buf.String()
|
|
buf.Reset()
|
|
}
|
|
|
|
return strings.Join(s, "\n")
|
|
}
|
|
|
|
// 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
|
|
}
|