fix some bugs and restructure a bit more

This commit is contained in:
Drew Weymouth
2023-05-15 17:05:18 -07:00
parent fd106560d0
commit f926ed3fb9
14 changed files with 183 additions and 224 deletions
+3 -1
View File
@@ -44,10 +44,12 @@ type MediaProvider interface {
AlbumSortOrders() []string AlbumSortOrders() []string
IterateAlbums(sortOrder string, searchQuery string, filter AlbumFilter) AlbumIterator IterateAlbums(sortOrder string, filter AlbumFilter) AlbumIterator
IterateTracks(searchQuery string) TrackIterator IterateTracks(searchQuery string) TrackIterator
SearchAlbums(searchQuery string, filter AlbumFilter) AlbumIterator
GetRandomTracks(genre string, count int) ([]*Track, error) GetRandomTracks(genre string, count int) ([]*Track, error)
GetSimilarTracks(artistID string, count int) ([]*Track, error) GetSimilarTracks(artistID string, count int) ([]*Track, error)
+30 -34
View File
@@ -10,6 +10,30 @@ import (
"github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/sharedutil"
) )
const (
AlbumSortRecentlyAdded string = "Recently Added"
AlbumSortRecentlyPlayed string = "Recently Played"
AlbumSortFrequentlyPlayed string = "Frequently Played"
AlbumSortRandom string = "Random"
AlbumSortTitleAZ string = "Title (A-Z)"
AlbumSortArtistAZ string = "Artist (A-Z)"
AlbumSortYearAscending string = "Year (ascending)"
AlbumSortYearDescending string = "Year (descending)"
)
func (s *subsonicMediaProvider) AlbumSortOrders() []string {
return []string{
AlbumSortRecentlyAdded,
AlbumSortRecentlyPlayed,
AlbumSortFrequentlyPlayed,
AlbumSortRandom,
AlbumSortTitleAZ,
AlbumSortArtistAZ,
AlbumSortYearAscending,
AlbumSortYearDescending,
}
}
func filterMatches(f mediaprovider.AlbumFilter, album *subsonic.AlbumID3) bool { func filterMatches(f mediaprovider.AlbumFilter, album *subsonic.AlbumID3) bool {
if album == nil { if album == nil {
return false return false
@@ -34,15 +58,7 @@ func filterMatches(f mediaprovider.AlbumFilter, album *subsonic.AlbumID3) bool {
return false return false
} }
func filterIsEmpty(f mediaprovider.AlbumFilter) bool { func (s *subsonicMediaProvider) IterateAlbums(sortOrder string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator {
return !f.ExcludeFavorited && !f.ExcludeUnfavorited &&
f.MinYear == 0 && f.MaxYear == 0 && len(f.Genres) == 0
}
func (s *subsonicMediaProvider) IterateAlbums(sortOrder, searchQuery string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator {
if searchQuery != "" {
return s.newSearchIter(searchQuery, filter)
}
if sortOrder == "" && len(filter.Genres) == 1 { if sortOrder == "" && len(filter.Genres) == 1 {
return s.newBaseIter("byGenre", filter, map[string]string{"genre": filter.Genres[0]}) return s.newBaseIter("byGenre", filter, map[string]string{"genre": filter.Genres[0]})
} }
@@ -72,6 +88,10 @@ func (s *subsonicMediaProvider) IterateAlbums(sortOrder, searchQuery string, fil
} }
} }
func (s *subsonicMediaProvider) SearchAlbums(searchQuery string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator {
return s.newSearchIter(searchQuery, filter)
}
type baseIter struct { type baseIter struct {
listType string listType string
filter mediaprovider.AlbumFilter filter mediaprovider.AlbumFilter
@@ -217,7 +237,7 @@ func (s *searchIter) addNewAlbums(al []*subsonic.AlbumID3) {
if _, have := s.albumIDset[album.ID]; have { if _, have := s.albumIDset[album.ID]; have {
continue continue
} }
if filterMatches(s.filter, album) { if !filterMatches(s.filter, album) {
continue continue
} }
s.prefetched = append(s.prefetched, album) s.prefetched = append(s.prefetched, album)
@@ -326,27 +346,3 @@ func (r *randomIter) Next() *mediaprovider.Album {
return nil return nil
} }
/*
type BatchingIterator struct {
iter AlbumIterator
}
func NewBatchingIterator(iter AlbumIterator) *BatchingIterator {
return &BatchingIterator{iter}
}
func (b *BatchingIterator) NextN(n int) []*subsonic.AlbumID3 {
results := make([]*subsonic.AlbumID3, 0, n)
i := 0
for i < n {
album := b.iter.Next()
if album == nil {
break
}
results = append(results, album)
i++
}
return results
}
*/
@@ -12,17 +12,6 @@ import (
"github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/sharedutil"
) )
const (
AlbumSortRecentlyAdded string = "Recently Added"
AlbumSortRecentlyPlayed string = "Recently Played"
AlbumSortFrequentlyPlayed string = "Frequently Played"
AlbumSortRandom string = "Random"
AlbumSortTitleAZ string = "Title (A-Z)"
AlbumSortArtistAZ string = "Artist (A-Z)"
AlbumSortYearAscending string = "Year (ascending)"
AlbumSortYearDescending string = "Year (descending)"
)
type subsonicMediaProvider struct { type subsonicMediaProvider struct {
client *subsonic.Client client *subsonic.Client
} }
@@ -31,19 +20,6 @@ func SubsonicMediaProvider(subsonicClient *subsonic.Client) mediaprovider.MediaP
return &subsonicMediaProvider{client: subsonicClient} return &subsonicMediaProvider{client: subsonicClient}
} }
func (s *subsonicMediaProvider) AlbumSortOrders() []string {
return []string{
AlbumSortRecentlyAdded,
AlbumSortRecentlyPlayed,
AlbumSortFrequentlyPlayed,
AlbumSortRandom,
AlbumSortTitleAZ,
AlbumSortArtistAZ,
AlbumSortYearAscending,
AlbumSortYearDescending,
}
}
func (s *subsonicMediaProvider) CreatePlaylist(name string, trackIDs []string) error { func (s *subsonicMediaProvider) CreatePlaylist(name string, trackIDs []string) error {
return s.client.CreatePlaylistWithTracks(trackIDs, map[string]string{"name": name}) return s.client.CreatePlaylistWithTracks(trackIDs, map[string]string{"name": name})
} }
@@ -69,21 +45,11 @@ func (s *subsonicMediaProvider) GetAlbum(albumID string) (*mediaprovider.AlbumWi
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &mediaprovider.AlbumWithTracks{ album := &mediaprovider.AlbumWithTracks{
Album: mediaprovider.Album{
ID: al.ID,
Name: al.Name,
ArtistIDs: []string{al.ArtistID},
CoverArtID: al.CoverArt,
ArtistNames: []string{al.Artist},
Genres: []string{al.Genre},
Year: al.Year,
TrackCount: al.SongCount,
Favorite: !al.Starred.IsZero(),
Duration: al.Duration,
},
Tracks: sharedutil.MapSlice(al.Song, toTrack), Tracks: sharedutil.MapSlice(al.Song, toTrack),
}, nil }
fillAlbum(al, &album.Album)
return album, nil
} }
func (s *subsonicMediaProvider) GetArtist(artistID string) (*mediaprovider.ArtistWithAlbums, error) { func (s *subsonicMediaProvider) GetArtist(artistID string) (*mediaprovider.ArtistWithAlbums, error) {
@@ -168,19 +134,11 @@ func (s *subsonicMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.P
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &mediaprovider.PlaylistWithTracks{ playlist := &mediaprovider.PlaylistWithTracks{
Playlist: mediaprovider.Playlist{
ID: pl.ID,
CoverArtID: pl.CoverArt,
Name: pl.Name,
Description: pl.Comment,
TrackCount: pl.SongCount,
Public: pl.Public,
Owner: pl.Owner,
Duration: pl.Duration,
},
Tracks: sharedutil.MapSlice(pl.Entry, toTrack), Tracks: sharedutil.MapSlice(pl.Entry, toTrack),
}, nil }
fillPlaylist(pl, &playlist.Playlist)
return playlist, nil
} }
func (s *subsonicMediaProvider) GetPlaylists() ([]*mediaprovider.Playlist, error) { func (s *subsonicMediaProvider) GetPlaylists() ([]*mediaprovider.Playlist, error) {
@@ -261,34 +219,24 @@ func (s *subsonicMediaProvider) SetRating(params mediaprovider.RatingFavoritePar
var err error var err error
batchSetRating := func(offs int, wg *sync.WaitGroup) { batchSetRating := func(offs int, wg *sync.WaitGroup) {
for i := 0; i < batchSize && offs+i < len(params.TrackIDs); i++ { for i := 0; i < batchSize && offs+i < len(params.TrackIDs); i++ {
if wg != nil { wg.Add(1)
wg.Add(1)
}
go func(idx int) { go func(idx int) {
newErr := s.client.SetRating(params.TrackIDs[idx], rating) newErr := s.client.SetRating(params.TrackIDs[idx], rating)
if err == nil && newErr != nil { if err == nil && newErr != nil {
err = newErr err = newErr
} }
if wg != nil { wg.Done()
wg.Done()
}
}(offs + i) }(offs + i)
} }
} }
if len(params.TrackIDs) <= 5 { numBatches := int(math.Ceil(float64(len(params.TrackIDs)) / float64(batchSize)))
// one batch only - no need to use wait group for i := 0; i < numBatches; i++ {
batchSetRating(0, nil) var wg sync.WaitGroup
} else { batchSetRating(i*batchSize, &wg)
go func() { wg.Wait()
numBatches := int(math.Ceil(float64(len(params.TrackIDs)) / float64(batchSize)))
for i := 0; i < numBatches; i++ {
var wg sync.WaitGroup
batchSetRating(i*batchSize, &wg)
wg.Wait()
}
}()
} }
return err return err
} }
@@ -314,6 +262,7 @@ func toTrack(ch *subsonic.Child) *mediaprovider.Track {
Favorite: !ch.Starred.IsZero(), Favorite: !ch.Starred.IsZero(),
PlayCount: int(ch.PlayCount), PlayCount: int(ch.PlayCount),
FilePath: ch.Path, FilePath: ch.Path,
Size: ch.Size,
BitRate: ch.BitRate, BitRate: ch.BitRate,
} }
} }
@@ -322,18 +271,22 @@ func toAlbum(al *subsonic.AlbumID3) *mediaprovider.Album {
if al == nil { if al == nil {
return nil return nil
} }
return &mediaprovider.Album{ album := &mediaprovider.Album{}
ID: al.ID, fillAlbum(al, album)
CoverArtID: al.CoverArt, return album
Name: al.Name, }
Duration: al.Duration,
ArtistIDs: []string{al.ArtistID}, func fillAlbum(subAlbum *subsonic.AlbumID3, album *mediaprovider.Album) {
ArtistNames: []string{al.Artist}, album.ID = subAlbum.ID
Year: al.Year, album.CoverArtID = subAlbum.CoverArt
Genres: []string{al.Genre}, album.Name = subAlbum.Name
TrackCount: al.SongCount, album.Duration = subAlbum.Duration
Favorite: !al.Starred.IsZero(), album.ArtistIDs = []string{subAlbum.ArtistID}
} album.ArtistNames = []string{subAlbum.Artist}
album.Year = subAlbum.Year
album.TrackCount = subAlbum.SongCount
album.Genres = []string{subAlbum.Genre}
album.Favorite = !subAlbum.Starred.IsZero()
} }
func toArtist(ar *subsonic.Artist) *mediaprovider.Artist { func toArtist(ar *subsonic.Artist) *mediaprovider.Artist {
@@ -360,14 +313,21 @@ func toArtistFromID3(ar *subsonic.ArtistID3) *mediaprovider.Artist {
} }
func toPlaylist(pl *subsonic.Playlist) *mediaprovider.Playlist { func toPlaylist(pl *subsonic.Playlist) *mediaprovider.Playlist {
return &mediaprovider.Playlist{ if pl == nil {
Name: pl.Name, return nil
ID: pl.ID,
CoverArtID: pl.CoverArt,
Description: pl.Comment,
Owner: pl.Owner,
Public: pl.Public,
TrackCount: pl.SongCount,
Duration: pl.Duration,
} }
playlist := &mediaprovider.Playlist{}
fillPlaylist(pl, playlist)
return playlist
}
func fillPlaylist(pl *subsonic.Playlist, playlist *mediaprovider.Playlist) {
playlist.Name = pl.Name
playlist.ID = pl.ID
playlist.CoverArtID = pl.CoverArt
playlist.Description = pl.Comment
playlist.Owner = pl.Owner
playlist.Public = pl.Public
playlist.TrackCount = pl.SongCount
playlist.Duration = pl.Duration
} }
@@ -11,7 +11,7 @@ func (s *subsonicMediaProvider) IterateTracks(searchQuery string) mediaprovider.
if searchQuery == "" { if searchQuery == "" {
return &allTracksIterator{ return &allTracksIterator{
s: s, s: s,
albumIter: s.IterateAlbums(AlbumSortArtistAZ, "", mediaprovider.AlbumFilter{}), albumIter: s.IterateAlbums(AlbumSortArtistAZ, mediaprovider.AlbumFilter{}),
} }
} }
return &searchTracksIterator{ return &searchTracksIterator{
+4 -4
View File
@@ -74,7 +74,7 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller,
cfg.SortOrder = string(mp.AlbumSortOrders()[0]) cfg.SortOrder = string(mp.AlbumSortOrders()[0])
} }
a.sortOrder.Selected = cfg.SortOrder a.sortOrder.Selected = cfg.SortOrder
iter := mp.IterateAlbums(a.sortOrder.Selected, "", a.filter) iter := mp.IterateAlbums(a.sortOrder.Selected, a.filter)
a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im) a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im)
contr.ConnectAlbumGridActions(a.grid) contr.ConnectAlbumGridActions(a.grid)
a.createSearchAndFilter() a.createSearchAndFilter()
@@ -163,7 +163,7 @@ func (a *AlbumsPage) Reload() {
if a.searchText != "" { if a.searchText != "" {
a.doSearch(a.searchText) a.doSearch(a.searchText)
} else { } else {
iter := a.mp.IterateAlbums(a.sortOrder.Selected, "", a.filter) iter := a.mp.IterateAlbums(a.sortOrder.Selected, a.filter)
a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
a.grid.Refresh() a.grid.Refresh()
} }
@@ -188,7 +188,7 @@ func (a *AlbumsPage) Save() SavedPage {
} }
func (a *AlbumsPage) doSearch(query string) { func (a *AlbumsPage) doSearch(query string) {
iter := widgets.NewGridViewAlbumIterator(a.mp.IterateAlbums("", query, a.filter)) iter := widgets.NewGridViewAlbumIterator(a.mp.SearchAlbums(query, a.filter))
if a.searchGrid == nil { if a.searchGrid == nil {
a.searchGrid = widgets.NewGridView(iter, a.im) a.searchGrid = widgets.NewGridView(iter, a.im)
a.contr.ConnectAlbumGridActions(a.searchGrid) a.contr.ConnectAlbumGridActions(a.searchGrid)
@@ -201,7 +201,7 @@ func (a *AlbumsPage) doSearch(query string) {
func (a *AlbumsPage) onSortOrderChanged(order string) { func (a *AlbumsPage) onSortOrderChanged(order string) {
a.cfg.SortOrder = a.sortOrder.Selected a.cfg.SortOrder = a.sortOrder.Selected
iter := a.mp.IterateAlbums(order, "", a.filter) iter := a.mp.IterateAlbums(order, a.filter)
a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
if a.searchText == "" { if a.searchText == "" {
a.container.Objects[0] = a.grid a.container.Objects[0] = a.grid
+10 -10
View File
@@ -30,7 +30,7 @@ type artistPageState struct {
cfg *backend.ArtistPageConfig cfg *backend.ArtistPageConfig
pm *backend.PlaybackManager pm *backend.PlaybackManager
sm *backend.ServerManager mp mediaprovider.MediaProvider
im *backend.ImageManager im *backend.ImageManager
contr *controller.Controller contr *controller.Controller
} }
@@ -49,20 +49,20 @@ type ArtistPage struct {
container *fyne.Container container *fyne.Container
} }
func NewArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.PlaybackManager, sm *backend.ServerManager, im *backend.ImageManager, contr *controller.Controller) *ArtistPage { func NewArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller) *ArtistPage {
activeView := 0 activeView := 0
if cfg.InitialView == "Top Tracks" { if cfg.InitialView == "Top Tracks" {
activeView = 1 activeView = 1
} }
return newArtistPage(artistID, cfg, pm, sm, im, contr, activeView) return newArtistPage(artistID, cfg, pm, mp, im, contr, activeView)
} }
func newArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.PlaybackManager, sm *backend.ServerManager, im *backend.ImageManager, contr *controller.Controller, activeView int) *ArtistPage { func newArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller, activeView int) *ArtistPage {
a := &ArtistPage{artistPageState: artistPageState{ a := &ArtistPage{artistPageState: artistPageState{
artistID: artistID, artistID: artistID,
cfg: cfg, cfg: cfg,
pm: pm, pm: pm,
sm: sm, mp: mp,
im: im, im: im,
contr: contr, contr: contr,
activeView: activeView, activeView: activeView,
@@ -141,7 +141,7 @@ func (a *ArtistPage) playArtistRadio() {
// should be called asynchronously // should be called asynchronously
func (a *ArtistPage) load() { func (a *ArtistPage) load() {
artist, err := a.sm.Server.GetArtist(a.artistID) artist, err := a.mp.GetArtist(a.artistID)
if err != nil { if err != nil {
log.Printf("Failed to get artist: %s", err.Error()) log.Printf("Failed to get artist: %s", err.Error())
return return
@@ -153,7 +153,7 @@ func (a *ArtistPage) load() {
} else { } else {
a.showTopTracks() a.showTopTracks()
} }
info, err := a.sm.Server.GetArtistInfo(a.artistID) info, err := a.mp.GetArtistInfo(a.artistID)
if err != nil { if err != nil {
log.Printf("Failed to get artist info: %s", err.Error()) log.Printf("Failed to get artist info: %s", err.Error())
} }
@@ -189,7 +189,7 @@ func (a *ArtistPage) showTopTracks() {
a.activeView = 1 // if page still loading, will show tracks view first a.activeView = 1 // if page still loading, will show tracks view first
return return
} }
ts, err := a.sm.Server.GetTopTracks(a.artistInfo.Artist, 20) ts, err := a.mp.GetTopTracks(a.artistInfo.Artist, 20)
if err != nil { if err != nil {
log.Printf("error getting top songs: %s", err.Error()) log.Printf("error getting top songs: %s", err.Error())
return return
@@ -232,7 +232,7 @@ func (a *ArtistPage) CreateRenderer() fyne.WidgetRenderer {
} }
func (s *artistPageState) Restore() Page { func (s *artistPageState) Restore() Page {
return newArtistPage(s.artistID, s.cfg, s.pm, s.sm, s.im, s.contr, s.activeView) return newArtistPage(s.artistID, s.cfg, s.pm, s.mp, s.im, s.contr, s.activeView)
} }
type ArtistPageHeader struct { type ArtistPageHeader struct {
@@ -333,7 +333,7 @@ func (a *ArtistPageHeader) UpdateInfo(info *mediaprovider.ArtistInfo) {
func (a *ArtistPageHeader) toggleFavorited() { func (a *ArtistPageHeader) toggleFavorited() {
params := mediaprovider.RatingFavoriteParameters{ArtistIDs: []string{a.artistID}} params := mediaprovider.RatingFavoriteParameters{ArtistIDs: []string{a.artistID}}
a.artistPage.sm.Server.SetFavorite(params, a.favoriteBtn.IsFavorited) a.artistPage.mp.SetFavorite(params, a.favoriteBtn.IsFavorited)
} }
func (a *ArtistPageHeader) createContainer() { func (a *ArtistPageHeader) createContainer() {
+10 -11
View File
@@ -4,7 +4,6 @@ import (
"log" "log"
"strings" "strings"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/controller" "github.com/dweymouth/supersonic/ui/controller"
@@ -25,7 +24,7 @@ type ArtistsGenresPage struct {
isGenresPage bool isGenresPage bool
contr *controller.Controller contr *controller.Controller
sm *backend.ServerManager mp mediaprovider.MediaProvider
model []widgets.ArtistGenreListItemModel model []widgets.ArtistGenreListItemModel
list *widgets.ArtistGenreList list *widgets.ArtistGenreList
@@ -34,11 +33,11 @@ type ArtistsGenresPage struct {
searcher *widgets.SearchEntry searcher *widgets.SearchEntry
} }
func NewArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *backend.ServerManager) *ArtistsGenresPage { func NewArtistsGenresPage(isGenresPage bool, contr *controller.Controller, mp mediaprovider.MediaProvider) *ArtistsGenresPage {
return newArtistsGenresPage(isGenresPage, contr, sm, "") return newArtistsGenresPage(isGenresPage, contr, mp, "")
} }
func newArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *backend.ServerManager, searchText string) *ArtistsGenresPage { func newArtistsGenresPage(isGenresPage bool, contr *controller.Controller, mp mediaprovider.MediaProvider, searchText string) *ArtistsGenresPage {
title := "Artists" title := "Artists"
if isGenresPage { if isGenresPage {
title = "Genres" title = "Genres"
@@ -46,7 +45,7 @@ func newArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *b
a := &ArtistsGenresPage{ a := &ArtistsGenresPage{
isGenresPage: isGenresPage, isGenresPage: isGenresPage,
contr: contr, contr: contr,
sm: sm, mp: mp,
titleDisp: widget.NewRichTextWithText(title), titleDisp: widget.NewRichTextWithText(title),
} }
a.ExtendBaseWidget(a) a.ExtendBaseWidget(a)
@@ -72,13 +71,13 @@ func newArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *b
// should be called asynchronously // should be called asynchronously
func (a *ArtistsGenresPage) load(searchOnLoad bool) { func (a *ArtistsGenresPage) load(searchOnLoad bool) {
if a.isGenresPage { if a.isGenresPage {
genres, err := a.sm.Server.GetGenres() genres, err := a.mp.GetGenres()
if err != nil { if err != nil {
log.Printf("error loading genres: %v", err.Error()) log.Printf("error loading genres: %v", err.Error())
} }
a.model = a.buildGenresListModel(genres) a.model = a.buildGenresListModel(genres)
} else { } else {
artists, err := a.sm.Server.GetArtists() artists, err := a.mp.GetArtists()
if err != nil { if err != nil {
log.Printf("error loading artists: %v", err.Error()) log.Printf("error loading artists: %v", err.Error())
} }
@@ -127,7 +126,7 @@ func (a *ArtistsGenresPage) Save() SavedPage {
return &savedArtistsGenresPage{ return &savedArtistsGenresPage{
isGenresPage: a.isGenresPage, isGenresPage: a.isGenresPage,
contr: a.contr, contr: a.contr,
sm: a.sm, mp: a.mp,
searchText: a.searcher.Entry.Text, searchText: a.searcher.Entry.Text,
} }
} }
@@ -135,12 +134,12 @@ func (a *ArtistsGenresPage) Save() SavedPage {
type savedArtistsGenresPage struct { type savedArtistsGenresPage struct {
isGenresPage bool isGenresPage bool
contr *controller.Controller contr *controller.Controller
sm *backend.ServerManager mp mediaprovider.MediaProvider
searchText string searchText string
} }
func (s *savedArtistsGenresPage) Restore() Page { func (s *savedArtistsGenresPage) Restore() Page {
return newArtistsGenresPage(s.isGenresPage, s.contr, s.sm, s.searchText) return newArtistsGenresPage(s.isGenresPage, s.contr, s.mp, s.searchText)
} }
func (a *ArtistsGenresPage) buildArtistListModel(artists []*mediaprovider.Artist) []widgets.ArtistGenreListItemModel { func (a *ArtistsGenresPage) buildArtistListModel(artists []*mediaprovider.Artist) []widgets.ArtistGenreListItemModel {
+12 -12
View File
@@ -25,7 +25,7 @@ type FavoritesPage struct {
contr *controller.Controller contr *controller.Controller
pm *backend.PlaybackManager pm *backend.PlaybackManager
im *backend.ImageManager im *backend.ImageManager
sm *backend.ServerManager mp mediaprovider.MediaProvider
filter mediaprovider.AlbumFilter filter mediaprovider.AlbumFilter
searchText string searchText string
@@ -43,18 +43,18 @@ type FavoritesPage struct {
container *fyne.Container container *fyne.Container
} }
func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Controller, sm *backend.ServerManager, pm *backend.PlaybackManager, im *backend.ImageManager) *FavoritesPage { func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Controller, mp mediaprovider.MediaProvider, pm *backend.PlaybackManager, im *backend.ImageManager) *FavoritesPage {
a := &FavoritesPage{ a := &FavoritesPage{
filter: mediaprovider.AlbumFilter{ExcludeUnfavorited: true}, filter: mediaprovider.AlbumFilter{ExcludeUnfavorited: true},
cfg: cfg, cfg: cfg,
contr: contr, contr: contr,
pm: pm, pm: pm,
sm: sm, mp: mp,
im: im, im: im,
} }
a.ExtendBaseWidget(a) a.ExtendBaseWidget(a)
a.createHeader(0) a.createHeader(0)
iter := sm.Server.IterateAlbums("", "", a.filter) iter := mp.IterateAlbums("", a.filter)
a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im) a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im)
a.contr.ConnectAlbumGridActions(a.grid) a.contr.ConnectAlbumGridActions(a.grid)
if cfg.InitialView == "Artists" { if cfg.InitialView == "Artists" {
@@ -98,7 +98,7 @@ func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage {
cfg: saved.cfg, cfg: saved.cfg,
contr: saved.contr, contr: saved.contr,
pm: saved.pm, pm: saved.pm,
sm: saved.sm, mp: saved.mp,
im: saved.im, im: saved.im,
searchText: saved.searchText, searchText: saved.searchText,
filter: saved.filter, filter: saved.filter,
@@ -139,13 +139,13 @@ func (a *FavoritesPage) Reload() {
if a.searchText != "" { if a.searchText != "" {
a.doSearchAlbums(a.searchText) a.doSearchAlbums(a.searchText)
} else { } else {
iter := a.sm.Server.IterateAlbums("", "", a.filter) iter := a.mp.IterateAlbums("", a.filter)
a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
} }
if a.tracklistCtr != nil || a.artistListCtr != nil { if a.tracklistCtr != nil || a.artistListCtr != nil {
go func() { go func() {
// re-fetch starred info from server // re-fetch starred info from server
starred, err := a.sm.Server.GetFavorites() starred, err := a.mp.GetFavorites()
if err != nil { if err != nil {
log.Printf("error getting starred items: %s", err.Error()) log.Printf("error getting starred items: %s", err.Error())
return return
@@ -177,7 +177,7 @@ func (a *FavoritesPage) Save() SavedPage {
cfg: a.cfg, cfg: a.cfg,
contr: a.contr, contr: a.contr,
pm: a.pm, pm: a.pm,
sm: a.sm, mp: a.mp,
im: a.im, im: a.im,
filter: a.filter, filter: a.filter,
searchText: a.searchText, searchText: a.searchText,
@@ -230,7 +230,7 @@ func (a *FavoritesPage) SelectAll() {
} }
func (a *FavoritesPage) doSearchAlbums(query string) { func (a *FavoritesPage) doSearchAlbums(query string) {
iter := a.sm.Server.IterateAlbums("", query, a.filter) iter := a.mp.SearchAlbums(query, a.filter)
if a.searchGrid == nil { if a.searchGrid == nil {
a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im) a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im)
a.contr.ConnectAlbumGridActions(a.searchGrid) a.contr.ConnectAlbumGridActions(a.searchGrid)
@@ -266,7 +266,7 @@ func (a *FavoritesPage) onShowFavoriteArtists() {
a.createContainer(layout.NewSpacer()) a.createContainer(layout.NewSpacer())
} }
go func() { go func() {
fav, err := a.sm.Server.GetFavorites() fav, err := a.mp.GetFavorites()
if err != nil { if err != nil {
log.Printf("error getting starred items: %s", err.Error()) log.Printf("error getting starred items: %s", err.Error())
return return
@@ -315,7 +315,7 @@ func (a *FavoritesPage) onShowFavoriteSongs() {
a.createContainer(layout.NewSpacer()) a.createContainer(layout.NewSpacer())
} }
go func() { go func() {
fav, err := a.sm.Server.GetFavorites() fav, err := a.mp.GetFavorites()
if err != nil { if err != nil {
log.Printf("error getting starred items: %s", err.Error()) log.Printf("error getting starred items: %s", err.Error())
return return
@@ -350,7 +350,7 @@ type savedFavoritesPage struct {
cfg *backend.FavoritesPageConfig cfg *backend.FavoritesPageConfig
contr *controller.Controller contr *controller.Controller
pm *backend.PlaybackManager pm *backend.PlaybackManager
sm *backend.ServerManager mp mediaprovider.MediaProvider
im *backend.ImageManager im *backend.ImageManager
gridState widgets.GridViewState gridState widgets.GridViewState
searchGridState widgets.GridViewState searchGridState widgets.GridViewState
+3 -3
View File
@@ -54,7 +54,7 @@ func NewGenrePage(genre string, contr *controller.Controller, pm *backend.Playba
SizeName: theme.SizeNameHeadingText, SizeName: theme.SizeNameHeadingText,
} }
g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs) g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs)
iter := g.mp.IterateAlbums("", "", g.filter) iter := g.mp.IterateAlbums("", g.filter)
g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im) g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im)
g.contr.ConnectAlbumGridActions(g.grid) g.contr.ConnectAlbumGridActions(g.grid)
g.createSearchAndFilter() g.createSearchAndFilter()
@@ -125,7 +125,7 @@ func (g *GenrePage) Reload() {
if g.searchText != "" { if g.searchText != "" {
g.doSearch(g.searchText) g.doSearch(g.searchText)
} else { } else {
iter := g.mp.IterateAlbums("", "", g.filter) iter := g.mp.IterateAlbums("", g.filter)
g.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) g.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
g.grid.Refresh() g.grid.Refresh()
} }
@@ -168,7 +168,7 @@ func (g *GenrePage) OnSearched(query string) {
} }
func (g *GenrePage) doSearch(query string) { func (g *GenrePage) doSearch(query string) {
iter := g.mp.IterateAlbums("", query, g.filter) iter := g.mp.SearchAlbums(query, g.filter)
if g.searchGrid == nil { if g.searchGrid == nil {
g.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im) g.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im)
g.contr.ConnectAlbumGridActions(g.searchGrid) g.contr.ConnectAlbumGridActions(g.searchGrid)
+2 -4
View File
@@ -27,7 +27,6 @@ type NowPlayingPage struct {
type nowPlayingPageState struct { type nowPlayingPageState struct {
contr *controller.Controller contr *controller.Controller
conf *backend.NowPlayingPageConfig conf *backend.NowPlayingPageConfig
sm *backend.ServerManager
pm *backend.PlaybackManager pm *backend.PlaybackManager
} }
@@ -35,10 +34,9 @@ func NewNowPlayingPage(
highlightedTrackID string, highlightedTrackID string,
contr *controller.Controller, contr *controller.Controller,
conf *backend.NowPlayingPageConfig, conf *backend.NowPlayingPageConfig,
sm *backend.ServerManager,
pm *backend.PlaybackManager, pm *backend.PlaybackManager,
) *NowPlayingPage { ) *NowPlayingPage {
a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{contr: contr, conf: conf, sm: sm, pm: pm}} a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{contr: contr, conf: conf, pm: pm}}
a.ExtendBaseWidget(a) a.ExtendBaseWidget(a)
a.tracklist = widgets.NewTracklist(nil) a.tracklist = widgets.NewTracklist(nil)
a.tracklist.SetVisibleColumns(conf.TracklistColumns) a.tracklist.SetVisibleColumns(conf.TracklistColumns)
@@ -117,5 +115,5 @@ func (a *NowPlayingPage) load(highlightedTrackID string) {
} }
func (s *nowPlayingPageState) Restore() Page { func (s *nowPlayingPageState) Restore() Page {
return NewNowPlayingPage("", s.contr, s.conf, s.sm, s.pm) return NewNowPlayingPage("", s.contr, s.conf, s.pm)
} }
+9 -9
View File
@@ -26,7 +26,7 @@ type PlaylistsPage struct {
cfg *backend.PlaylistsPageConfig cfg *backend.PlaylistsPageConfig
contr *controller.Controller contr *controller.Controller
sm *backend.ServerManager mp mediaprovider.MediaProvider
playlists []*mediaprovider.Playlist playlists []*mediaprovider.Playlist
searchedPlaylists []*mediaprovider.Playlist searchedPlaylists []*mediaprovider.Playlist
@@ -38,18 +38,18 @@ type PlaylistsPage struct {
gridView *widgets.GridView gridView *widgets.GridView
} }
func NewPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageConfig, sm *backend.ServerManager) *PlaylistsPage { func NewPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageConfig, mp mediaprovider.MediaProvider) *PlaylistsPage {
activeView := 0 activeView := 0
if cfg.InitialView == "Grid" { if cfg.InitialView == "Grid" {
activeView = 1 activeView = 1
} }
return newPlaylistsPage(contr, cfg, sm, "", activeView) return newPlaylistsPage(contr, cfg, mp, "", activeView)
} }
func newPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageConfig, sm *backend.ServerManager, searchText string, activeView int) *PlaylistsPage { func newPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageConfig, mp mediaprovider.MediaProvider, searchText string, activeView int) *PlaylistsPage {
a := &PlaylistsPage{ a := &PlaylistsPage{
cfg: cfg, cfg: cfg,
sm: sm, mp: mp,
contr: contr, contr: contr,
titleDisp: widget.NewRichTextWithText("Playlists"), titleDisp: widget.NewRichTextWithText("Playlists"),
} }
@@ -75,7 +75,7 @@ func newPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageCo
} }
func (a *PlaylistsPage) load(searchOnLoad bool) { func (a *PlaylistsPage) load(searchOnLoad bool) {
playlists, err := a.sm.Server.GetPlaylists() playlists, err := a.mp.GetPlaylists()
if err != nil { if err != nil {
log.Printf("error loading playlists: %v", err.Error()) log.Printf("error loading playlists: %v", err.Error())
} }
@@ -213,7 +213,7 @@ func (a *PlaylistsPage) Save() SavedPage {
return &savedPlaylistsPage{ return &savedPlaylistsPage{
contr: a.contr, contr: a.contr,
cfg: a.cfg, cfg: a.cfg,
sm: a.sm, mp: a.mp,
searchText: a.searcher.Entry.Text, searchText: a.searcher.Entry.Text,
activeView: a.viewToggle.ActivatedButtonIndex(), activeView: a.viewToggle.ActivatedButtonIndex(),
} }
@@ -222,13 +222,13 @@ func (a *PlaylistsPage) Save() SavedPage {
type savedPlaylistsPage struct { type savedPlaylistsPage struct {
contr *controller.Controller contr *controller.Controller
cfg *backend.PlaylistsPageConfig cfg *backend.PlaylistsPageConfig
sm *backend.ServerManager mp mediaprovider.MediaProvider
searchText string searchText string
activeView int activeView int
} }
func (s *savedPlaylistsPage) Restore() Page { func (s *savedPlaylistsPage) Restore() Page {
return newPlaylistsPage(s.contr, s.cfg, s.sm, s.searchText, s.activeView) return newPlaylistsPage(s.contr, s.cfg, s.mp, s.searchText, s.activeView)
} }
func (a *PlaylistsPage) buildContainer(initialView fyne.CanvasObject) { func (a *PlaylistsPage) buildContainer(initialView fyne.CanvasObject) {
+6 -6
View File
@@ -32,21 +32,21 @@ func (r Router) CreatePage(rte controller.Route) Page {
case controller.Albums: case controller.Albums:
return NewAlbumsPage(&r.App.Config.AlbumsPage, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager) return NewAlbumsPage(&r.App.Config.AlbumsPage, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
case controller.Artist: case controller.Artist:
return NewArtistPage(rte.Arg, &r.App.Config.ArtistPage, r.App.PlaybackManager, r.App.ServerManager, r.App.ImageManager, r.Controller) return NewArtistPage(rte.Arg, &r.App.Config.ArtistPage, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller)
case controller.Artists: case controller.Artists:
return NewArtistsGenresPage(false, r.Controller, r.App.ServerManager) return NewArtistsGenresPage(false, r.Controller, r.App.ServerManager.Server)
case controller.Favorites: case controller.Favorites:
return NewFavoritesPage(&r.App.Config.FavoritesPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager) return NewFavoritesPage(&r.App.Config.FavoritesPage, r.Controller, r.App.ServerManager.Server, r.App.PlaybackManager, r.App.ImageManager)
case controller.Genre: case controller.Genre:
return NewGenrePage(rte.Arg, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager) return NewGenrePage(rte.Arg, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager)
case controller.Genres: case controller.Genres:
return NewArtistsGenresPage(true, r.Controller, r.App.ServerManager) return NewArtistsGenresPage(true, r.Controller, r.App.ServerManager.Server)
case controller.NowPlaying: case controller.NowPlaying:
return NewNowPlayingPage(rte.Arg, r.Controller, &r.App.Config.NowPlayingPage, r.App.ServerManager, r.App.PlaybackManager) return NewNowPlayingPage(rte.Arg, r.Controller, &r.App.Config.NowPlayingPage, r.App.PlaybackManager)
case controller.Playlist: case controller.Playlist:
return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager) return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager)
case controller.Playlists: case controller.Playlists:
return NewPlaylistsPage(r.Controller, &r.App.Config.PlaylistsPage, r.App.ServerManager) return NewPlaylistsPage(r.Controller, &r.App.Config.PlaylistsPage, r.App.ServerManager.Server)
case controller.Tracks: case controller.Tracks:
return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.App.ServerManager.Server) return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.App.ServerManager.Server)
} }
+41 -37
View File
@@ -128,12 +128,14 @@ func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) {
m.NavigateTo(ArtistRoute(artistID)) m.NavigateTo(ArtistRoute(artistID))
} }
grid.OnAddToPlaylist = func(albumID string) { grid.OnAddToPlaylist = func(albumID string) {
album, err := m.App.ServerManager.Server.GetAlbum(albumID) go func() {
if err != nil { album, err := m.App.ServerManager.Server.GetAlbum(albumID)
log.Printf("error loading album: %s", err.Error()) if err != nil {
return log.Printf("error loading album: %s", err.Error())
} return
m.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(album.Tracks)) }
m.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(album.Tracks))
}()
} }
} }
@@ -169,36 +171,38 @@ func (m *Controller) PromptForFirstServer() {
// Depending on the results of that dialog, potentially create a new playlist // Depending on the results of that dialog, potentially create a new playlist
// Add tracks to the user-specified playlist // Add tracks to the user-specified playlist
func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) { func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
pls, err := m.App.ServerManager.Server.GetPlaylists() go func() {
pls = sharedutil.FilterSlice(pls, func(pl *mediaprovider.Playlist) bool { pls, err := m.App.ServerManager.Server.GetPlaylists()
return pl.Owner == m.App.ServerManager.LoggedInUser pls = sharedutil.FilterSlice(pls, func(pl *mediaprovider.Playlist) bool {
}) return pl.Owner == m.App.ServerManager.LoggedInUser
if err != nil { })
// TODO: surface this error to user if err != nil {
log.Printf("error getting user-owned playlists: %s", err.Error()) // TODO: surface this error to user
return log.Printf("error getting user-owned playlists: %s", err.Error())
} return
plNames := make([]string, 0, len(pls))
for _, pl := range pls {
plNames = append(plNames, pl.Name)
}
dlg := dialogs.NewAddToPlaylistDialog("Add to Playlist", plNames)
pop := widget.NewModalPopUp(dlg, m.MainWindow.Canvas())
m.ClosePopUpOnEscape(pop)
dlg.OnCanceled = pop.Hide
dlg.OnSubmit = func(playlistChoice int, newPlaylistName string) {
pop.Hide()
m.doModalClosed()
if playlistChoice < 0 {
m.App.ServerManager.Server.CreatePlaylist(newPlaylistName, trackIDs)
} else {
m.App.ServerManager.Server.EditPlaylistTracks(
pls[playlistChoice].ID, trackIDs, nil /*tracksToRemove*/)
} }
} plNames := make([]string, 0, len(pls))
m.haveModal = true for _, pl := range pls {
pop.Show() plNames = append(plNames, pl.Name)
}
dlg := dialogs.NewAddToPlaylistDialog("Add to Playlist", plNames)
pop := widget.NewModalPopUp(dlg, m.MainWindow.Canvas())
m.ClosePopUpOnEscape(pop)
dlg.OnCanceled = pop.Hide
dlg.OnSubmit = func(playlistChoice int, newPlaylistName string) {
pop.Hide()
m.doModalClosed()
if playlistChoice < 0 {
go m.App.ServerManager.Server.CreatePlaylist(newPlaylistName, trackIDs)
} else {
go m.App.ServerManager.Server.EditPlaylistTracks(
pls[playlistChoice].ID, trackIDs, nil /*tracksToRemove*/)
}
}
m.haveModal = true
pop.Show()
}()
} }
func (m *Controller) DoEditPlaylistWorkflow(playlist *mediaprovider.Playlist) { func (m *Controller) DoEditPlaylistWorkflow(playlist *mediaprovider.Playlist) {
@@ -399,7 +403,7 @@ func (c *Controller) doModalClosed() {
} }
func (c *Controller) SetTrackFavorites(trackIDs []string, favorite bool) { func (c *Controller) SetTrackFavorites(trackIDs []string, favorite bool) {
c.App.ServerManager.Server.SetFavorite(mediaprovider.RatingFavoriteParameters{ go c.App.ServerManager.Server.SetFavorite(mediaprovider.RatingFavoriteParameters{
TrackIDs: trackIDs, TrackIDs: trackIDs,
}, favorite) }, favorite)
@@ -409,7 +413,7 @@ func (c *Controller) SetTrackFavorites(trackIDs []string, favorite bool) {
} }
func (c *Controller) SetTrackRatings(trackIDs []string, rating int) { func (c *Controller) SetTrackRatings(trackIDs []string, rating int) {
c.App.ServerManager.Server.SetRating(mediaprovider.RatingFavoriteParameters{ go c.App.ServerManager.Server.SetRating(mediaprovider.RatingFavoriteParameters{
TrackIDs: trackIDs, TrackIDs: trackIDs,
}, rating) }, rating)
+3 -3
View File
@@ -608,11 +608,11 @@ func (t *TrackRow) Update(tr *mediaprovider.Track, rowNum int) {
// Render favorite column // Render favorite column
if tr.Favorite { if tr.Favorite {
t.isFavorite = false
t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.NotFavoriteIcon
} else {
t.isFavorite = true t.isFavorite = true
t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.FavoriteIcon t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.FavoriteIcon
} else {
t.isFavorite = false
t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.NotFavoriteIcon
} }
t.rating.Rating = tr.Rating t.rating.Rating = tr.Rating