From 8984d4ab3938efde2a2b51b1da1f9c6ec73d7929 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sun, 14 May 2023 17:55:44 -0700 Subject: [PATCH] add trackiterator to subsonic adapter --- .../subsonic/subsonicmediaprovider.go | 12 +- .../mediaprovider/subsonic/trackiterator.go | 141 ++++++++++++++++++ 2 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 backend/mediaprovider/subsonic/trackiterator.go diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index 0023591..90009be 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -80,7 +80,7 @@ func (s *subsonicMediaProvider) GetAlbum(albumID string) (*mediaprovider.AlbumWi Favorite: !al.Starred.IsZero(), Duration: al.Duration, }, - Tracks: sharedutil.MapSlice(al.Song, s.toTrack), + Tracks: sharedutil.MapSlice(al.Song, toTrack), }, nil } @@ -138,7 +138,7 @@ func (s *subsonicMediaProvider) GetFavorites() (mediaprovider.Favorites, error) return mediaprovider.Favorites{ Albums: sharedutil.MapSlice(fav.Album, toAlbum), Artists: sharedutil.MapSlice(fav.Artist, toArtistFromID3), - Tracks: sharedutil.MapSlice(fav.Song, s.toTrack), + Tracks: sharedutil.MapSlice(fav.Song, toTrack), }, nil } @@ -163,7 +163,7 @@ func (s *subsonicMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.P } return &mediaprovider.PlaylistWithTracks{ Playlist: toPlaylist(pl), - Tracks: sharedutil.MapSlice(pl.Entry, s.toTrack), + Tracks: sharedutil.MapSlice(pl.Entry, toTrack), }, nil } @@ -184,7 +184,7 @@ func (s *subsonicMediaProvider) GetRandomTracks(genreName string, count int) ([] if err != nil { return nil, err } - return sharedutil.MapSlice(tr, s.toTrack), nil + return sharedutil.MapSlice(tr, toTrack), nil } func (s *subsonicMediaProvider) GetSimilarTracks(artistID string, count int) ([]mediaprovider.Track, error) { @@ -192,10 +192,10 @@ func (s *subsonicMediaProvider) GetSimilarTracks(artistID string, count int) ([] if err != nil { return nil, err } - return sharedutil.MapSlice(tr, s.toTrack), nil + return sharedutil.MapSlice(tr, toTrack), nil } -func (s *subsonicMediaProvider) toTrack(ch *subsonic.Child) mediaprovider.Track { +func toTrack(ch *subsonic.Child) mediaprovider.Track { if ch == nil { log.Println("subsonicMediaProvider: toTrack called on nil track") return mediaprovider.Track{} diff --git a/backend/mediaprovider/subsonic/trackiterator.go b/backend/mediaprovider/subsonic/trackiterator.go new file mode 100644 index 0000000..4930749 --- /dev/null +++ b/backend/mediaprovider/subsonic/trackiterator.go @@ -0,0 +1,141 @@ +package subsonic + +import ( + "log" + + "github.com/dweymouth/go-subsonic/subsonic" + "github.com/dweymouth/supersonic/backend/mediaprovider" +) + +func (s *subsonicMediaProvider) IterateTracks(searchQuery string) mediaprovider.TrackIterator { + if searchQuery == "" { + return &allTracksIterator{ + s: s, + albumIter: s.IterateAlbums(AlbumSortArtistAZ, "", mediaprovider.AlbumFilter{}), + } + } + return &searchTracksIterator{ + searchIterBase: searchIterBase{ + s: s.client, + query: searchQuery, + }, + trackIDset: make(map[string]bool), + } +} + +type allTracksIterator struct { + s *subsonicMediaProvider + albumIter mediaprovider.AlbumIterator + curAlbum *mediaprovider.AlbumWithTracks + curTrackIdx int + done bool +} + +func (a *allTracksIterator) Next() *mediaprovider.Track { + if a.done { + return nil + } + + // fetch next album + if a.curAlbum == nil || a.curTrackIdx >= len(a.curAlbum.Tracks) { + al := a.albumIter.Next() + if al == nil { + a.done = true + return nil + } + alWithTracks, err := a.s.GetAlbum(al.ID) + if err != nil { + log.Printf("error fetching album: %s", err.Error()) + } + if len(alWithTracks.Tracks) == 0 { + // in the unlikely case of an album with zero tracks, + // just call recursively to move to next album + return a.Next() + } + a.curAlbum = alWithTracks + a.curTrackIdx = 0 + } + + tr := a.curAlbum.Tracks[a.curTrackIdx] + a.curTrackIdx += 1 + return &tr +} + +type searchTracksIterator struct { + searchIterBase + + prefetched []*subsonic.Child + prefetchedPos int + trackIDset map[string]bool + done bool +} + +func (s *searchTracksIterator) Next() *mediaprovider.Track { + if s.done { + return nil + } + + // prefetch more search results from server + if len(s.prefetched) == 0 { + results := s.searchIterBase.fetchResults() + + if results != nil { + // add results from songs search + s.addNewTracks(results.Song) + s.songOffset += len(results.Song) + + // add results from artists search + for _, artist := range results.Artist { + artist, err := s.s.GetArtist(artist.ID) + if err != nil { + log.Printf("error fetching artist: %s", err.Error()) + } else { + s.addNewTracksFromAlbums(artist.Album) + } + } + s.artistOffset += len(results.Artist) + + // add results from albums search + s.addNewTracksFromAlbums(results.Album) + s.albumOffset += len(results.Album) + } + } + + // return from prefetched results + if len(s.prefetched) > 0 { + tr := s.prefetched[s.prefetchedPos] + s.prefetchedPos++ + if s.prefetchedPos == len(s.prefetched) { + s.prefetched = s.prefetched[:0] + s.prefetchedPos = 0 + } + track := toTrack(tr) + return &track + } + + // no more results + s.done = true + s.prefetched = nil + s.trackIDset = nil + return nil +} + +func (s *searchTracksIterator) addNewTracks(tracks []*subsonic.Child) { + for _, tr := range tracks { + if _, have := s.trackIDset[tr.ID]; have { + continue + } + s.prefetched = append(s.prefetched, tr) + s.trackIDset[tr.ID] = true + } +} + +func (s *searchTracksIterator) addNewTracksFromAlbums(albums []*subsonic.AlbumID3) { + for _, al := range albums { + if album, err := s.s.GetAlbum(al.ID); err != nil { + log.Printf("error fetching album: %s", err.Error()) + } else { + s.addNewTracks(album.Song) + } + } +}