diff --git a/backend/app.go b/backend/app.go index 5e94281..0813583 100644 --- a/backend/app.go +++ b/backend/app.go @@ -25,7 +25,6 @@ type App struct { Config *Config ServerManager *ServerManager ImageManager *ImageManager - LibraryManager *LibraryManager PlaybackManager *PlaybackManager Player *player.Player UpdateChecker UpdateChecker @@ -63,11 +62,10 @@ func StartupApp(appName, appVersionTag, configFile, latestReleaseURL string) (*A a.ServerManager = NewServerManager(appName) a.PlaybackManager = NewPlaybackManager(a.bgrndCtx, a.ServerManager, a.Player, &a.Config.Scrobbling) - a.LibraryManager = NewLibraryManager(a.ServerManager) a.ImageManager = NewImageManager(a.bgrndCtx, a.ServerManager, configdir.LocalCache(a.appName)) - a.LibraryManager.PreCacheCoverFn = func(coverID string) { - _, _ = a.ImageManager.GetCoverThumbnail(coverID) - } + //a.LibraryManager.PreCacheCoverFn = func(coverID string) { + //_, _ = a.ImageManager.GetCoverThumbnail(coverID) + //} return a, nil } diff --git a/backend/config.go b/backend/config.go index 91851db..b071fd2 100644 --- a/backend/config.go +++ b/backend/config.go @@ -124,7 +124,7 @@ func DefaultConfig(appVersionTag string) *Config { TracklistColumns: []string{"Artist", "Time", "Plays", "Favorite", "Rating"}, }, AlbumsPage: AlbumsPageConfig{ - SortOrder: string(AlbumSortRecentlyAdded), + SortOrder: string("Recently Added"), }, ArtistPage: ArtistPageConfig{ InitialView: "Discography", diff --git a/backend/librarymanager.go b/backend/librarymanager.go deleted file mode 100644 index 5f4919d..0000000 --- a/backend/librarymanager.go +++ /dev/null @@ -1,25 +0,0 @@ -package backend - -import ( - subsonic "github.com/dweymouth/go-subsonic/subsonic" -) - -type AlbumIterator interface { - Next() *subsonic.AlbumID3 -} - -type TrackIterator interface { - Next() *subsonic.Child -} - -type LibraryManager struct { - PreCacheCoverFn func(coverID string) - - s *ServerManager -} - -func NewLibraryManager(s *ServerManager) *LibraryManager { - return &LibraryManager{ - s: s, - } -} diff --git a/backend/mediaprovider/mediaprovider.go b/backend/mediaprovider/mediaprovider.go index 936f345..5839bdf 100644 --- a/backend/mediaprovider/mediaprovider.go +++ b/backend/mediaprovider/mediaprovider.go @@ -60,11 +60,13 @@ type MediaProvider interface { GetStreamURL(trackID string) (string, error) + GetTopTracks(artist Artist, count int) ([]*Track, error) + SetFavorite(params RatingFavoriteParameters, favorite bool) error SetRating(params RatingFavoriteParameters, rating int) error - GetPlaylists() ([]Playlist, error) + GetPlaylists() ([]*Playlist, error) CreatePlaylist(name string, trackIDs []string) error diff --git a/backend/mediaprovider/model.go b/backend/mediaprovider/model.go index bfa4e18..d4e2d36 100644 --- a/backend/mediaprovider/model.go +++ b/backend/mediaprovider/model.go @@ -21,6 +21,7 @@ type AlbumWithTracks struct { type Artist struct { ID string Name string + Favorite bool AlbumCount int } @@ -71,6 +72,7 @@ type Playlist struct { Description string Public bool Owner string + Duration int TrackCount int } diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index 5d48469..4eee99a 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -95,6 +95,7 @@ func (s *subsonicMediaProvider) GetArtist(artistID string) (*mediaprovider.Artis Artist: mediaprovider.Artist{ ID: ar.ID, Name: ar.Name, + Favorite: !ar.Starred.IsZero(), AlbumCount: ar.AlbumCount, }, Albums: sharedutil.MapSlice(ar.Album, toAlbum), @@ -168,12 +169,21 @@ func (s *subsonicMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.P return nil, err } return &mediaprovider.PlaylistWithTracks{ - Playlist: toPlaylist(pl), - Tracks: sharedutil.MapSlice(pl.Entry, toTrack), + 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), }, nil } -func (s *subsonicMediaProvider) GetPlaylists() ([]mediaprovider.Playlist, error) { +func (s *subsonicMediaProvider) GetPlaylists() ([]*mediaprovider.Playlist, error) { pl, err := s.client.GetPlaylists(map[string]string{}) if err != nil { return nil, err @@ -209,6 +219,18 @@ func (s *subsonicMediaProvider) GetStreamURL(trackID string) (string, error) { return u.String(), nil } +func (s *subsonicMediaProvider) GetTopTracks(artist mediaprovider.Artist, count int) ([]*mediaprovider.Track, error) { + params := map[string]string{} + if count > 0 { + params["count"] = strconv.Itoa(count) + } + tr, err := s.client.GetTopSongs(artist.Name, params) + if err != nil { + return nil, err + } + return sharedutil.MapSlice(tr, toTrack), nil +} + func (s *subsonicMediaProvider) ReplacePlaylistTracks(playlistID string, trackIDs []string) error { return s.client.CreatePlaylistWithTracks(trackIDs, map[string]string{"playlistId": playlistID}) } @@ -319,8 +341,9 @@ func toArtist(ar *subsonic.Artist) *mediaprovider.Artist { return nil } return &mediaprovider.Artist{ - ID: ar.ID, - Name: ar.Name, + ID: ar.ID, + Name: ar.Name, + Favorite: !ar.Starred.IsZero(), } } @@ -331,16 +354,20 @@ func toArtistFromID3(ar *subsonic.ArtistID3) *mediaprovider.Artist { return &mediaprovider.Artist{ ID: ar.ID, Name: ar.Name, + Favorite: !ar.Starred.IsZero(), AlbumCount: ar.AlbumCount, } } -func toPlaylist(pl *subsonic.Playlist) mediaprovider.Playlist { - return mediaprovider.Playlist{ +func toPlaylist(pl *subsonic.Playlist) *mediaprovider.Playlist { + return &mediaprovider.Playlist{ Name: pl.Name, + ID: pl.ID, + CoverArtID: pl.CoverArt, Description: pl.Comment, Owner: pl.Owner, Public: pl.Public, TrackCount: pl.SongCount, + Duration: pl.Duration, } } diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index fc722ff..333b612 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -34,19 +34,17 @@ type AlbumPage struct { type albumPageState struct { albumID string cfg *backend.AlbumPageConfig - lm *backend.LibraryManager + mp mediaprovider.MediaProvider pm *backend.PlaybackManager im *backend.ImageManager - sm *backend.ServerManager contr *controller.Controller } func NewAlbumPage( albumID string, cfg *backend.AlbumPageConfig, - sm *backend.ServerManager, pm *backend.PlaybackManager, - lm *backend.LibraryManager, + mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller, ) *AlbumPage { @@ -54,9 +52,8 @@ func NewAlbumPage( albumPageState: albumPageState{ albumID: albumID, cfg: cfg, - sm: sm, pm: pm, - lm: lm, + mp: mp, im: im, contr: contr, }, @@ -115,7 +112,7 @@ func (a *AlbumPage) SelectAll() { // should be called asynchronously func (a *AlbumPage) load() { - album, err := a.sm.Server.GetAlbum(a.albumID) + album, err := a.mp.GetAlbum(a.albumID) if err != nil { log.Printf("Failed to get album: %s", err.Error()) return @@ -238,7 +235,7 @@ func (a *AlbumPageHeader) Update(album *mediaprovider.AlbumWithTracks, im *backe func (a *AlbumPageHeader) toggleFavorited() { params := mediaprovider.RatingFavoriteParameters{AlbumIDs: []string{a.albumID}} - a.page.sm.Server.SetFavorite(params, a.toggleFavButton.IsFavorited) + a.page.mp.SetFavorite(params, a.toggleFavButton.IsFavorited) } func (a *AlbumPageHeader) showPopUpCover() { @@ -263,5 +260,5 @@ func formatMiscLabelStr(a *mediaprovider.AlbumWithTracks) string { } func (s *albumPageState) Restore() Page { - return NewAlbumPage(s.albumID, s.cfg, s.sm, s.pm, s.lm, s.im, s.contr) + return NewAlbumPage(s.albumID, s.cfg, s.pm, s.mp, s.im, s.contr) } diff --git a/ui/browsing/albumspage.go b/ui/browsing/albumspage.go index 1f49d44..52e9b5b 100644 --- a/ui/browsing/albumspage.go +++ b/ui/browsing/albumspage.go @@ -24,7 +24,7 @@ type AlbumsPage struct { contr *controller.Controller pm *backend.PlaybackManager im *backend.ImageManager - lm *backend.LibraryManager + mp mediaprovider.MediaProvider grid *widgets.GridView searchGrid *widgets.GridView searcher *widgets.SearchEntry @@ -55,12 +55,12 @@ func (s *selectWidget) MinSize() fyne.Size { return fyne.NewSize(170, s.Select.MinSize().Height) } -func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager) *AlbumsPage { +func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) *AlbumsPage { a := &AlbumsPage{ cfg: cfg, contr: contr, pm: pm, - lm: lm, + mp: mp, im: im, } a.ExtendBaseWidget(a) @@ -69,12 +69,12 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller, a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ SizeName: theme.SizeNameHeadingText, } - a.sortOrder = NewSelect(backend.AlbumSortOrders, a.onSortOrderChanged) - if !sharedutil.SliceContains(backend.AlbumSortOrders, cfg.SortOrder) { - cfg.SortOrder = string(backend.AlbumSortRecentlyAdded) + a.sortOrder = NewSelect(mp.AlbumSortOrders(), a.onSortOrderChanged) + if !sharedutil.SliceContains(mp.AlbumSortOrders(), cfg.SortOrder) { + cfg.SortOrder = string(mp.AlbumSortOrders()[0]) } a.sortOrder.Selected = cfg.SortOrder - iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected), a.filter) + iter := mp.IterateAlbums(a.sortOrder.Selected, "", a.filter) a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im) contr.ConnectAlbumGridActions(a.grid) a.createSearchAndFilter() @@ -112,7 +112,7 @@ func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage { cfg: saved.cfg, contr: saved.contr, pm: saved.pm, - lm: saved.lm, + mp: saved.mp, im: saved.im, searchText: saved.searchText, filter: saved.filter, @@ -123,7 +123,7 @@ func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage { a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ SizeName: theme.SizeNameHeadingText, } - a.sortOrder = NewSelect(backend.AlbumSortOrders, nil) + a.sortOrder = NewSelect(a.mp.AlbumSortOrders(), nil) a.sortOrder.Selected = saved.sortOrder a.sortOrder.OnChanged = a.onSortOrderChanged a.grid = widgets.NewGridViewFromState(saved.gridState) @@ -163,7 +163,7 @@ func (a *AlbumsPage) Reload() { if a.searchText != "" { a.doSearch(a.searchText) } else { - iter := a.lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected), a.filter) + iter := a.mp.IterateAlbums(a.sortOrder.Selected, "", a.filter) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) a.grid.Refresh() } @@ -174,7 +174,7 @@ func (a *AlbumsPage) Save() SavedPage { cfg: a.cfg, contr: a.contr, pm: a.pm, - lm: a.lm, + mp: a.mp, im: a.im, searchText: a.searchText, filter: a.filter, @@ -188,11 +188,12 @@ func (a *AlbumsPage) Save() SavedPage { } func (a *AlbumsPage) doSearch(query string) { + iter := widgets.NewGridViewAlbumIterator(a.mp.IterateAlbums("", query, a.filter)) if a.searchGrid == nil { - a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(a.lm.SearchIter(query)), a.im) + a.searchGrid = widgets.NewGridView(iter, a.im) a.contr.ConnectAlbumGridActions(a.searchGrid) } else { - a.searchGrid.Reset(widgets.NewGridViewAlbumIterator(a.lm.SearchIterWithFilter(query, a.filter))) + a.searchGrid.Reset(iter) } a.container.Objects[0] = a.searchGrid a.Refresh() @@ -200,7 +201,7 @@ func (a *AlbumsPage) doSearch(query string) { func (a *AlbumsPage) onSortOrderChanged(order string) { a.cfg.SortOrder = a.sortOrder.Selected - iter := a.lm.AlbumsIter(backend.AlbumSortOrder(order), a.filter) + iter := a.mp.IterateAlbums(order, "", a.filter) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) if a.searchText == "" { a.container.Objects[0] = a.grid @@ -215,11 +216,11 @@ func (a *AlbumsPage) CreateRenderer() fyne.WidgetRenderer { type savedAlbumsPage struct { searchText string - filter backend.AlbumFilter + filter mediaprovider.AlbumFilter cfg *backend.AlbumsPageConfig contr *controller.Controller pm *backend.PlaybackManager - lm *backend.LibraryManager + mp mediaprovider.MediaProvider im *backend.ImageManager sortOrder string gridState widgets.GridViewState diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index 860646e..86f0b41 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -20,8 +20,6 @@ import ( "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" - - "github.com/dweymouth/go-subsonic/subsonic" ) var _ fyne.Widget = (*ArtistPage)(nil) @@ -42,7 +40,7 @@ type ArtistPage struct { artistPageState - artistInfo *subsonic.ArtistID3 + artistInfo *mediaprovider.ArtistWithAlbums albumGrid *widgets.GridView tracklistCtr *fyne.Container @@ -130,7 +128,7 @@ func (a *ArtistPage) OnSongChange(track, lastScrobbledIfAny *mediaprovider.Track func (a *ArtistPage) playAllTracks() { if a.artistInfo != nil { // page loaded - for i, album := range a.artistInfo.Album { + for i, album := range a.artistInfo.Albums { a.pm.LoadAlbum(album.ID, i > 0 /*append*/, false /*shuffle*/) } a.pm.PlayFromBeginning() @@ -155,7 +153,7 @@ func (a *ArtistPage) load() { } else { a.showTopTracks() } - info, err := a.sm.Server.GetArtistInfo2(a.artistID, nil) + info, err := a.sm.Server.GetArtistInfo(a.artistID) if err != nil { log.Printf("Failed to get artist info: %s", err.Error()) } @@ -169,11 +167,11 @@ func (a *ArtistPage) showAlbumGrid() { a.activeView = 0 // if page still loading, will show discography view first return } - model := sharedutil.MapSlice(a.artistInfo.Album, func(al *subsonic.AlbumID3) widgets.GridViewItemModel { + model := sharedutil.MapSlice(a.artistInfo.Albums, func(al *mediaprovider.Album) widgets.GridViewItemModel { return widgets.GridViewItemModel{ Name: al.Name, ID: al.ID, - CoverArtID: al.CoverArt, + CoverArtID: al.CoverArtID, Secondary: strconv.Itoa(al.Year), } }) @@ -191,7 +189,7 @@ func (a *ArtistPage) showTopTracks() { a.activeView = 1 // if page still loading, will show tracks view first return } - ts, err := a.sm.Server.GetTopSongs(a.artistInfo.Name, map[string]string{"count": "20"}) + ts, err := a.sm.Server.GetTopTracks(a.artistInfo.Artist, 20) if err != nil { log.Printf("error getting top songs: %s", err.Error()) return @@ -277,18 +275,18 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader { return a } -func (a *ArtistPageHeader) Update(artist *subsonic.ArtistID3) { +func (a *ArtistPageHeader) Update(artist *mediaprovider.ArtistWithAlbums) { if artist == nil { return } - a.favoriteBtn.IsFavorited = !artist.Starred.IsZero() + a.favoriteBtn.IsFavorited = !artist.Favorite a.favoriteBtn.Refresh() a.artistID = artist.ID a.titleDisp.Segments[0].(*widget.TextSegment).Text = artist.Name a.titleDisp.Refresh() } -func (a *ArtistPageHeader) UpdateInfo(info *subsonic.ArtistInfo2) { +func (a *ArtistPageHeader) UpdateInfo(info *mediaprovider.ArtistInfo) { if info == nil { return } @@ -304,7 +302,7 @@ func (a *ArtistPageHeader) UpdateInfo(info *subsonic.ArtistInfo2) { } a.similarArtists.RemoveAll() - for i, art := range info.SimilarArtist { + for i, art := range info.SimilarArtists { if i == 0 { a.similarArtists.Add(widget.NewLabel("Similar Artists:")) } @@ -321,11 +319,11 @@ func (a *ArtistPageHeader) UpdateInfo(info *subsonic.ArtistInfo2) { } a.similarArtists.Refresh() - if info.LargeImageUrl != "" { + if info.ImageURL != "" { if a.artistImage.HaveImage() { - _ = a.artistPage.im.RefreshCachedArtistImageIfExpired(a.artistID, info.LargeImageUrl) + _ = a.artistPage.im.RefreshCachedArtistImageIfExpired(a.artistID, info.ImageURL) } else { - im, err := a.artistPage.im.FetchAndCacheArtistImage(a.artistID, info.LargeImageUrl) + im, err := a.artistPage.im.FetchAndCacheArtistImage(a.artistID, info.ImageURL) if err == nil { a.artistImage.SetImage(im, true /*tappable*/) } @@ -334,11 +332,8 @@ func (a *ArtistPageHeader) UpdateInfo(info *subsonic.ArtistInfo2) { } func (a *ArtistPageHeader) toggleFavorited() { - if a.favoriteBtn.IsFavorited { - a.artistPage.sm.Server.Star(subsonic.StarParameters{ArtistIDs: []string{a.artistID}}) - } else { - a.artistPage.sm.Server.Unstar(subsonic.StarParameters{ArtistIDs: []string{a.artistID}}) - } + params := mediaprovider.RatingFavoriteParameters{ArtistIDs: []string{a.artistID}} + a.artistPage.sm.Server.SetFavorite(params, a.favoriteBtn.IsFavorited) } func (a *ArtistPageHeader) createContainer() { diff --git a/ui/browsing/artistsgenrespage.go b/ui/browsing/artistsgenrespage.go index c694b3b..8a7078f 100644 --- a/ui/browsing/artistsgenrespage.go +++ b/ui/browsing/artistsgenrespage.go @@ -3,9 +3,9 @@ package browsing import ( "log" "strings" - "time" "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/controller" "github.com/dweymouth/supersonic/ui/layouts" @@ -16,8 +16,6 @@ import ( "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" - - "github.com/dweymouth/go-subsonic/subsonic" ) var _ fyne.Widget = (*ArtistPage)(nil) @@ -80,7 +78,7 @@ func (a *ArtistsGenresPage) load(searchOnLoad bool) { } a.model = a.buildGenresListModel(genres) } else { - artists, err := a.sm.Server.GetArtists(nil) + artists, err := a.sm.Server.GetArtists() if err != nil { log.Printf("error loading artists: %v", err.Error()) } @@ -145,29 +143,27 @@ func (s *savedArtistsGenresPage) Restore() Page { return newArtistsGenresPage(s.isGenresPage, s.contr, s.sm, s.searchText) } -func (a *ArtistsGenresPage) buildArtistListModel(artists *subsonic.ArtistsID3) []widgets.ArtistGenreListItemModel { +func (a *ArtistsGenresPage) buildArtistListModel(artists []*mediaprovider.Artist) []widgets.ArtistGenreListItemModel { model := make([]widgets.ArtistGenreListItemModel, 0) - for _, idx := range artists.Index { - for _, artist := range idx.Artist { - model = append(model, widgets.ArtistGenreListItemModel{ - ID: artist.ID, - Name: artist.Name, - AlbumCount: artist.AlbumCount, - Favorite: artist.Starred != time.Time{}, - }) - } + for _, artist := range artists { + model = append(model, widgets.ArtistGenreListItemModel{ + ID: artist.ID, + Name: artist.Name, + AlbumCount: artist.AlbumCount, + Favorite: artist.Favorite, + }) } return model } -func (a *ArtistsGenresPage) buildGenresListModel(genres []*subsonic.Genre) []widgets.ArtistGenreListItemModel { +func (a *ArtistsGenresPage) buildGenresListModel(genres []*mediaprovider.Genre) []widgets.ArtistGenreListItemModel { model := make([]widgets.ArtistGenreListItemModel, 0) for _, genre := range genres { model = append(model, widgets.ArtistGenreListItemModel{ ID: genre.Name, Name: genre.Name, AlbumCount: genre.AlbumCount, - TrackCount: genre.SongCount, + TrackCount: genre.TrackCount, Favorite: false, }) } diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index cb47f9e..9a8ed65 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -26,9 +26,8 @@ type FavoritesPage struct { pm *backend.PlaybackManager im *backend.ImageManager sm *backend.ServerManager - lm *backend.LibraryManager - filter backend.AlbumFilter + filter mediaprovider.AlbumFilter searchText string nowPlayingID string pendingViewSwitch bool @@ -44,19 +43,18 @@ type FavoritesPage struct { container *fyne.Container } -func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Controller, sm *backend.ServerManager, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager) *FavoritesPage { +func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Controller, sm *backend.ServerManager, pm *backend.PlaybackManager, im *backend.ImageManager) *FavoritesPage { a := &FavoritesPage{ - filter: backend.AlbumFilter{ExcludeUnfavorited: true}, + filter: mediaprovider.AlbumFilter{ExcludeUnfavorited: true}, cfg: cfg, contr: contr, pm: pm, - lm: lm, sm: sm, im: im, } a.ExtendBaseWidget(a) a.createHeader(0) - iter := lm.StarredIter(a.filter) + iter := sm.Server.IterateAlbums("", "", a.filter) a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im) a.contr.ConnectAlbumGridActions(a.grid) if cfg.InitialView == "Artists" { @@ -100,7 +98,6 @@ func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage { cfg: saved.cfg, contr: saved.contr, pm: saved.pm, - lm: saved.lm, sm: saved.sm, im: saved.im, searchText: saved.searchText, @@ -142,13 +139,13 @@ func (a *FavoritesPage) Reload() { if a.searchText != "" { a.doSearchAlbums(a.searchText) } else { - iter := a.lm.StarredIter(a.filter) + iter := a.sm.Server.IterateAlbums("", "", a.filter) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) } if a.tracklistCtr != nil || a.artistListCtr != nil { go func() { // re-fetch starred info from server - starred, err := a.sm.Server.GetStarred2(nil) + starred, err := a.sm.Server.GetFavorites() if err != nil { log.Printf("error getting starred items: %s", err.Error()) return @@ -156,7 +153,7 @@ func (a *FavoritesPage) Reload() { if a.tracklistCtr != nil { // refresh favorite songs view tr := a.tracklistCtr.Objects[0].(*widgets.Tracklist) - tr.Tracks = starred.Song + tr.Tracks = starred.Tracks if a.toggleBtns.ActivatedButtonIndex() == 2 { // favorite songs view is visible tr.Refresh() @@ -165,7 +162,7 @@ func (a *FavoritesPage) Reload() { if a.artistListCtr != nil { // refresh favorite artists view al := a.artistListCtr.Objects[0].(*widgets.ArtistGenreList) - al.Items = buildArtistListModel(starred.Artist) + al.Items = buildArtistListModel(starred.Artists) if a.toggleBtns.ActivatedButtonIndex() == 1 { // favorite artists view is visible al.Refresh() @@ -182,7 +179,6 @@ func (a *FavoritesPage) Save() SavedPage { pm: a.pm, sm: a.sm, im: a.im, - lm: a.lm, filter: a.filter, searchText: a.searchText, gridState: a.grid.SaveToState(), @@ -234,7 +230,7 @@ func (a *FavoritesPage) SelectAll() { } func (a *FavoritesPage) doSearchAlbums(query string) { - iter := a.lm.SearchIterWithFilter(query, a.filter) + iter := a.sm.Server.IterateAlbums("", query, a.filter) if a.searchGrid == nil { a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im) a.contr.ConnectAlbumGridActions(a.searchGrid) @@ -356,7 +352,6 @@ type savedFavoritesPage struct { pm *backend.PlaybackManager sm *backend.ServerManager im *backend.ImageManager - lm *backend.LibraryManager gridState widgets.GridViewState searchGridState widgets.GridViewState filter mediaprovider.AlbumFilter diff --git a/ui/browsing/genrepage.go b/ui/browsing/genrepage.go index f53de6e..e8c2f10 100644 --- a/ui/browsing/genrepage.go +++ b/ui/browsing/genrepage.go @@ -2,6 +2,7 @@ package browsing import ( "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/ui/controller" myTheme "github.com/dweymouth/supersonic/ui/theme" "github.com/dweymouth/supersonic/ui/util" @@ -22,12 +23,12 @@ type GenrePage struct { contr *controller.Controller im *backend.ImageManager pm *backend.PlaybackManager - lm *backend.LibraryManager + mp mediaprovider.MediaProvider grid *widgets.GridView searchGrid *widgets.GridView searcher *widgets.SearchEntry searchText string - filter backend.AlbumFilter + filter mediaprovider.AlbumFilter filterBtn *widgets.AlbumFilterButton titleDisp *widget.RichText playRandom *widget.Button @@ -37,13 +38,13 @@ type GenrePage struct { container *fyne.Container } -func NewGenrePage(genre string, contr *controller.Controller, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager) *GenrePage { +func NewGenrePage(genre string, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) *GenrePage { g := &GenrePage{ genre: genre, - filter: backend.AlbumFilter{Genres: []string{genre}}, + filter: mediaprovider.AlbumFilter{Genres: []string{genre}}, contr: contr, pm: pm, - lm: lm, + mp: mp, im: im, } g.ExtendBaseWidget(g) @@ -53,7 +54,7 @@ func NewGenrePage(genre string, contr *controller.Controller, pm *backend.Playba SizeName: theme.SizeNameHeadingText, } g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs) - iter := g.lm.GenreIter(g.genre, g.filter) + iter := g.mp.IterateAlbums("", "", g.filter) g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im) g.contr.ConnectAlbumGridActions(g.grid) g.createSearchAndFilter() @@ -90,7 +91,7 @@ func restoreGenrePage(saved *savedGenrePage) *GenrePage { genre: saved.genre, contr: saved.contr, pm: saved.pm, - lm: saved.lm, + mp: saved.mp, im: saved.im, searchText: saved.searchText, filter: saved.filter, @@ -124,7 +125,7 @@ func (g *GenrePage) Reload() { if g.searchText != "" { g.doSearch(g.searchText) } else { - iter := g.lm.GenreIter(g.genre, g.filter) + iter := g.mp.IterateAlbums("", "", g.filter) g.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) g.grid.Refresh() } @@ -137,7 +138,7 @@ func (g *GenrePage) Save() SavedPage { searchText: g.searchText, contr: g.contr, pm: g.pm, - lm: g.lm, + mp: g.mp, im: g.im, gridState: g.grid.SaveToState(), } @@ -167,7 +168,7 @@ func (g *GenrePage) OnSearched(query string) { } func (g *GenrePage) doSearch(query string) { - iter := g.lm.SearchIterWithFilter(query, g.filter) + iter := g.mp.IterateAlbums("", query, g.filter) if g.searchGrid == nil { g.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im) g.contr.ConnectAlbumGridActions(g.searchGrid) @@ -185,10 +186,10 @@ func (g *GenrePage) playRandomSongs() { type savedGenrePage struct { genre string searchText string - filter backend.AlbumFilter + filter mediaprovider.AlbumFilter contr *controller.Controller pm *backend.PlaybackManager - lm *backend.LibraryManager + mp mediaprovider.MediaProvider im *backend.ImageManager gridState widgets.GridViewState searchGridState widgets.GridViewState diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go index f7c4e08..03ea70b 100644 --- a/ui/browsing/nowplayingpage.go +++ b/ui/browsing/nowplayingpage.go @@ -2,6 +2,7 @@ package browsing import ( "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/controller" "github.com/dweymouth/supersonic/ui/layouts" @@ -10,8 +11,6 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/widget" - - "github.com/dweymouth/go-subsonic/subsonic" ) type NowPlayingPage struct { @@ -83,7 +82,7 @@ func (a *NowPlayingPage) SelectAll() { a.tracklist.SelectAll() } -func (a *NowPlayingPage) OnSongChange(song *subsonic.Child, lastScrobbledIfAny *subsonic.Child) { +func (a *NowPlayingPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Track) { if song == nil { a.nowPlayingID = "" } else { diff --git a/ui/browsing/playlistpage.go b/ui/browsing/playlistpage.go index 343925b..15d411c 100644 --- a/ui/browsing/playlistpage.go +++ b/ui/browsing/playlistpage.go @@ -5,6 +5,7 @@ import ( "log" "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/res" "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/controller" @@ -17,8 +18,6 @@ import ( "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" - - "github.com/dweymouth/go-subsonic/subsonic" ) type PlaylistPage struct { @@ -90,7 +89,7 @@ func (a *PlaylistPage) Route() controller.Route { return controller.PlaylistRoute(a.playlistID) } -func (a *PlaylistPage) OnSongChange(song *subsonic.Child, lastScrobbledIfAny *subsonic.Child) { +func (a *PlaylistPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Track) { if song == nil { a.nowPlayingID = "" } else { @@ -119,7 +118,7 @@ func (a *PlaylistPage) load() { log.Printf("Failed to get playlist: %s", err.Error()) return } - a.tracklist.Tracks = playlist.Entry + a.tracklist.Tracks = playlist.Tracks a.tracklist.SetNowPlaying(a.nowPlayingID) a.tracklist.Refresh() a.header.Update(playlist) @@ -148,10 +147,7 @@ func (a *PlaylistPage) doSetNewTrackOrder(op sharedutil.TrackReorderOp) { for i, tr := range newTracks { ids[i] = tr.ID } - err := a.sm.Server.CreatePlaylistWithTracks(ids, map[string]string{ - "playlistId": a.playlistID, - }) - if err != nil { + if err := a.sm.Server.ReplacePlaylistTracks(a.playlistID, ids); err != nil { log.Printf("error updating playlist: %s", err.Error()) } else { a.tracklist.Tracks = newTracks @@ -161,7 +157,7 @@ func (a *PlaylistPage) doSetNewTrackOrder(op sharedutil.TrackReorderOp) { } func (a *PlaylistPage) onRemoveSelectedFromPlaylist() { - a.sm.Server.UpdatePlaylistTracks(a.playlistID, nil, a.tracklist.SelectedTrackIndexes()) + a.sm.Server.EditPlaylistTracks(a.playlistID, nil, a.tracklist.SelectedTrackIndexes()) a.tracklist.UnselectAll() go a.Reload() } @@ -170,7 +166,7 @@ type PlaylistPageHeader struct { widget.BaseWidget page *PlaylistPage - playlistInfo *subsonic.Playlist + playlistInfo *mediaprovider.PlaylistWithTracks image *widgets.ImagePlaceholder editButton *widget.Button @@ -199,7 +195,7 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader { a.trackTimeLabel = widget.NewLabel("") a.editButton = widget.NewButtonWithIcon("Edit", theme.DocumentCreateIcon(), func() { if a.playlistInfo != nil { - page.contr.DoEditPlaylistWorkflow(a.playlistInfo) + page.contr.DoEditPlaylistWorkflow(&a.playlistInfo.Playlist) } }) a.editButton.Hidden = true @@ -244,18 +240,18 @@ func (a *PlaylistPageHeader) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(a.container) } -func (a *PlaylistPageHeader) Update(playlist *subsonic.Playlist) { +func (a *PlaylistPageHeader) Update(playlist *mediaprovider.PlaylistWithTracks) { a.playlistInfo = playlist - a.editButton.Hidden = playlist.Owner != a.page.sm.Server.User + a.editButton.Hidden = playlist.Owner != a.page.sm.LoggedInUser a.titleLabel.Segments[0].(*widget.TextSegment).Text = playlist.Name - a.descriptionLabel.SetText(playlist.Comment) + a.descriptionLabel.SetText(playlist.Description) a.ownerLabel.SetText(a.formatPlaylistOwnerStr(playlist)) a.trackTimeLabel.SetText(a.formatPlaylistTrackTimeStr(playlist)) a.createdAtLabel.SetText("created at TODO") var haveCover bool - if playlist.CoverArt != "" { - if im, err := a.page.im.GetCoverThumbnail(playlist.CoverArt); err == nil && im != nil { + if playlist.CoverArtID != "" { + if im, err := a.page.im.GetCoverThumbnail(playlist.CoverArtID); err == nil && im != nil { a.image.SetImage(im, false /*tappable*/) haveCover = true } @@ -268,7 +264,7 @@ func (a *PlaylistPageHeader) Update(playlist *subsonic.Playlist) { a.Refresh() } -func (a *PlaylistPageHeader) formatPlaylistOwnerStr(p *subsonic.Playlist) string { +func (a *PlaylistPageHeader) formatPlaylistOwnerStr(p *mediaprovider.PlaylistWithTracks) string { pubPriv := "Public" if !p.Public { pubPriv = "Private" @@ -276,12 +272,12 @@ func (a *PlaylistPageHeader) formatPlaylistOwnerStr(p *subsonic.Playlist) string return fmt.Sprintf("%s playlist by %s", pubPriv, p.Owner) } -func (a *PlaylistPageHeader) formatPlaylistTrackTimeStr(p *subsonic.Playlist) string { +func (a *PlaylistPageHeader) formatPlaylistTrackTimeStr(p *mediaprovider.PlaylistWithTracks) string { tracks := "tracks" - if p.SongCount == 1 { + if p.TrackCount == 1 { tracks = "track" } - return fmt.Sprintf("%d %s, %s", p.SongCount, tracks, util.SecondsToTimeString(float64(p.Duration))) + return fmt.Sprintf("%d %s, %s", p.TrackCount, tracks, util.SecondsToTimeString(float64(p.Duration))) } func (s *playlistPageState) Restore() Page { diff --git a/ui/browsing/playlistspage.go b/ui/browsing/playlistspage.go index c99d3b5..2ebbf52 100644 --- a/ui/browsing/playlistspage.go +++ b/ui/browsing/playlistspage.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/res" "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/controller" @@ -18,8 +19,6 @@ import ( "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" - - "github.com/dweymouth/go-subsonic/subsonic" ) type PlaylistsPage struct { @@ -28,8 +27,8 @@ type PlaylistsPage struct { cfg *backend.PlaylistsPageConfig contr *controller.Controller sm *backend.ServerManager - playlists []*subsonic.Playlist - searchedPlaylists []*subsonic.Playlist + playlists []*mediaprovider.Playlist + searchedPlaylists []*mediaprovider.Playlist viewToggle *widgets.ToggleButtonGroup searcher *widgets.SearchEntry @@ -76,7 +75,7 @@ func newPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageCo } func (a *PlaylistsPage) load(searchOnLoad bool) { - playlists, err := a.sm.Server.GetPlaylists(nil) + playlists, err := a.sm.Server.GetPlaylists() if err != nil { log.Printf("error loading playlists: %v", err.Error()) } @@ -93,7 +92,7 @@ func (a *PlaylistsPage) createListView() { a.listView.OnNavTo = a.showPlaylistPage } -func (a *PlaylistsPage) createGridView(playlists []*subsonic.Playlist) { +func (a *PlaylistsPage) createGridView(playlists []*mediaprovider.Playlist) { model := createPlaylistGridViewModel(playlists) a.gridView = widgets.NewFixedGridView(model, a.contr.App.ImageManager) a.gridView.OnPlay = func(id string, shuffle bool) { @@ -110,7 +109,7 @@ func (a *PlaylistsPage) createGridView(playlists []*subsonic.Playlist) { log.Printf("error loading playlist: %s", err.Error()) return } - a.contr.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(pl.Entry)) + a.contr.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(pl.Tracks)) }() } } @@ -142,17 +141,17 @@ func (a *PlaylistsPage) showGridView() { a.container.Objects[0].Refresh() } -func createPlaylistGridViewModel(playlists []*subsonic.Playlist) []widgets.GridViewItemModel { - return sharedutil.MapSlice(playlists, func(pl *subsonic.Playlist) widgets.GridViewItemModel { +func createPlaylistGridViewModel(playlists []*mediaprovider.Playlist) []widgets.GridViewItemModel { + return sharedutil.MapSlice(playlists, func(pl *mediaprovider.Playlist) widgets.GridViewItemModel { tracks := "tracks" - if pl.SongCount == 1 { + if pl.TrackCount == 1 { tracks = "track" } return widgets.GridViewItemModel{ Name: pl.Name, ID: pl.ID, - CoverArtID: pl.CoverArt, - Secondary: fmt.Sprintf("%d %s", pl.SongCount, tracks), + CoverArtID: pl.CoverArtID, + Secondary: fmt.Sprintf("%d %s", pl.TrackCount, tracks), } }) } @@ -164,15 +163,15 @@ func (a *PlaylistsPage) showPlaylistPage(id string) { func (a *PlaylistsPage) onSearched(query string) { // since the playlist list is returned in full non-paginated, we will do our own // simple search based on the name, description, and owner, rather than calling a server API - var playlists []*subsonic.Playlist + var playlists []*mediaprovider.Playlist if query == "" { a.searchedPlaylists = nil playlists = a.playlists } else { - a.searchedPlaylists = sharedutil.FilterSlice(a.playlists, func(p *subsonic.Playlist) bool { + a.searchedPlaylists = sharedutil.FilterSlice(a.playlists, func(p *mediaprovider.Playlist) bool { qLower := strings.ToLower(query) return strings.Contains(strings.ToLower(p.Name), qLower) || - strings.Contains(strings.ToLower(p.Comment), qLower) || + strings.Contains(strings.ToLower(p.Description), qLower) || strings.Contains(strings.ToLower(p.Owner), qLower) }) playlists = a.searchedPlaylists @@ -182,7 +181,7 @@ func (a *PlaylistsPage) onSearched(query string) { // update the model for both views if initialized, // refresh the active view -func (a *PlaylistsPage) refreshView(playlists []*subsonic.Playlist) { +func (a *PlaylistsPage) refreshView(playlists []*mediaprovider.Playlist) { if a.listView != nil { a.listView.Playlists = playlists } @@ -247,7 +246,7 @@ func (a *PlaylistsPage) CreateRenderer() fyne.WidgetRenderer { type PlaylistList struct { widget.BaseWidget - Playlists []*subsonic.Playlist + Playlists []*mediaprovider.Playlist OnNavTo func(string) columnsLayout *layouts.ColumnsLayout @@ -274,9 +273,9 @@ func NewPlaylistList() *PlaylistList { row := item.(*PlaylistListRow) row.ID = a.Playlists[id].ID row.nameLabel.Text = a.Playlists[id].Name - row.descrptionLabel.Text = a.Playlists[id].Comment + row.descrptionLabel.Text = a.Playlists[id].Description row.ownerLabel.Text = a.Playlists[id].Owner - row.trackCountLabel.Text = strconv.Itoa(a.Playlists[id].SongCount) + row.trackCountLabel.Text = strconv.Itoa(a.Playlists[id].TrackCount) row.Refresh() }, ) diff --git a/ui/browsing/router.go b/ui/browsing/router.go index f737cf4..10474d3 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -28,17 +28,17 @@ func NewRouter(app *backend.App, controller *controller.Controller, nav Navigati func (r Router) CreatePage(rte controller.Route) Page { switch rte.Page { case controller.Album: - return NewAlbumPage(rte.Arg, &r.App.Config.AlbumPage, r.App.ServerManager, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager, r.Controller) + return NewAlbumPage(rte.Arg, &r.App.Config.AlbumPage, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller) case controller.Albums: - return NewAlbumsPage(&r.App.Config.AlbumsPage, r.Controller, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager) + return NewAlbumsPage(&r.App.Config.AlbumsPage, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager) case controller.Artist: return NewArtistPage(rte.Arg, &r.App.Config.ArtistPage, r.App.PlaybackManager, r.App.ServerManager, r.App.ImageManager, r.Controller) case controller.Artists: return NewArtistsGenresPage(false, r.Controller, r.App.ServerManager) case controller.Favorites: - return NewFavoritesPage(&r.App.Config.FavoritesPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager) + return NewFavoritesPage(&r.App.Config.FavoritesPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager) case controller.Genre: - return NewGenrePage(rte.Arg, r.Controller, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager) + return NewGenrePage(rte.Arg, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager) case controller.Genres: return NewArtistsGenresPage(true, r.Controller, r.App.ServerManager) case controller.NowPlaying: @@ -48,7 +48,7 @@ func (r Router) CreatePage(rte controller.Route) Page { case controller.Playlists: return NewPlaylistsPage(r.Controller, &r.App.Config.PlaylistsPage, r.App.ServerManager) case controller.Tracks: - return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.App.LibraryManager) + return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.App.ServerManager.Server) } return nil } diff --git a/ui/browsing/trackspage.go b/ui/browsing/trackspage.go index dc86e46..d8a94bc 100644 --- a/ui/browsing/trackspage.go +++ b/ui/browsing/trackspage.go @@ -2,6 +2,7 @@ package browsing import ( "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/controller" "github.com/dweymouth/supersonic/ui/layouts" @@ -12,8 +13,6 @@ import ( "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/widget" - - "github.com/dweymouth/go-subsonic/subsonic" ) type TracksPage struct { @@ -37,11 +36,11 @@ type tracksPageState struct { searchText string contr *controller.Controller conf *backend.TracksPageConfig - lm *backend.LibraryManager + mp mediaprovider.MediaProvider } -func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, lm *backend.LibraryManager) *TracksPage { - t := &TracksPage{tracksPageState: tracksPageState{contr: contr, conf: conf, lm: lm}} +func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, mp mediaprovider.MediaProvider) *TracksPage { + t := &TracksPage{tracksPageState: tracksPageState{contr: contr, conf: conf, mp: mp}} t.ExtendBaseWidget(t) t.tracklist = widgets.NewTracklist(nil) @@ -79,12 +78,12 @@ func (t *TracksPage) Route() controller.Route { func (t *TracksPage) Reload() { t.tracklist.Clear() - iter := t.lm.AllTracksIterator() + iter := t.mp.IterateTracks("") // loads asynchronously t.loader = widgets.NewTracklistLoader(t.tracklist, iter) } -func (t *TracksPage) OnSongChange(track *subsonic.Child, lastScrobbledIfAny *subsonic.Child) { +func (t *TracksPage) OnSongChange(track, lastScrobbledIfAny *mediaprovider.Track) { t.nowPlayingID = sharedutil.TrackIDOrEmptyStr(track) t.tracklist.SetNowPlaying(t.nowPlayingID) if t.searchTracklist != nil { @@ -130,7 +129,7 @@ func (t *TracksPage) doSearch(query string) { } else { t.searchTracklist.Clear() } - iter := t.lm.SearchTracksIterator(query) + iter := t.mp.IterateTracks(query) t.searchLoader = widgets.NewTracklistLoader(t.searchTracklist, iter) t.container.Objects[0].(*fyne.Container).Objects[0] = t.searchTracklist t.Refresh() @@ -146,7 +145,7 @@ func (t *TracksPage) Save() SavedPage { } func (s *tracksPageState) Restore() Page { - t := NewTracksPage(s.contr, s.conf, s.lm) + t := NewTracksPage(s.contr, s.conf, s.mp) t.searchText = s.searchText if t.searchText != "" { t.searcher.Entry.Text = t.searchText diff --git a/ui/controller/controller.go b/ui/controller/controller.go index f231983..98eaef3 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -3,12 +3,10 @@ package controller import ( "image" "log" - "math" - "strconv" - "sync" "time" "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/player" "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/dialogs" @@ -20,8 +18,6 @@ import ( "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/widget" - - "github.com/dweymouth/go-subsonic/subsonic" ) type NavigationHandler func(Route) @@ -94,14 +90,14 @@ func (m *Controller) ShowPopUpImage(img image.Image) { func (m *Controller) ConnectTracklistActions(tracklist *widgets.Tracklist) { tracklist.OnAddToPlaylist = m.DoAddTracksToPlaylistWorkflow - tracklist.OnAddToQueue = func(tracks []*subsonic.Child) { + tracklist.OnAddToQueue = func(tracks []*mediaprovider.Track) { m.App.PlaybackManager.LoadTracks(tracks, true, false) } tracklist.OnPlayTrackAt = func(idx int) { m.App.PlaybackManager.LoadTracks(tracklist.Tracks, false, false) m.App.PlaybackManager.PlayTrackAt(idx) } - tracklist.OnPlaySelection = func(tracks []*subsonic.Child, shuffle bool) { + tracklist.OnPlaySelection = func(tracks []*mediaprovider.Track, shuffle bool) { m.App.PlaybackManager.LoadTracks(tracks, false, shuffle) m.App.PlaybackManager.PlayFromBeginning() } @@ -137,7 +133,7 @@ func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) { log.Printf("error loading album: %s", err.Error()) return } - m.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(album.Song)) + m.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(album.Tracks)) } } @@ -173,7 +169,10 @@ func (m *Controller) PromptForFirstServer() { // Depending on the results of that dialog, potentially create a new playlist // Add tracks to the user-specified playlist func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) { - pls, err := m.App.LibraryManager.GetUserOwnedPlaylists() + pls, err := m.App.ServerManager.Server.GetPlaylists() + 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 log.Printf("error getting user-owned playlists: %s", err.Error()) @@ -192,10 +191,9 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) { pop.Hide() m.doModalClosed() if playlistChoice < 0 { - m.App.ServerManager.Server.CreatePlaylistWithTracks( - trackIDs, map[string]string{"name": newPlaylistName}) + m.App.ServerManager.Server.CreatePlaylist(newPlaylistName, trackIDs) } else { - m.App.ServerManager.Server.UpdatePlaylistTracks( + m.App.ServerManager.Server.EditPlaylistTracks( pls[playlistChoice].ID, trackIDs, nil /*tracksToRemove*/) } } @@ -203,7 +201,7 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) { pop.Show() } -func (m *Controller) DoEditPlaylistWorkflow(playlist *subsonic.Playlist) { +func (m *Controller) DoEditPlaylistWorkflow(playlist *mediaprovider.Playlist) { dlg := dialogs.NewEditPlaylistDialog(playlist) pop := widget.NewModalPopUp(dlg, m.MainWindow.Canvas()) m.ClosePopUpOnEscape(pop) @@ -234,11 +232,7 @@ func (m *Controller) DoEditPlaylistWorkflow(playlist *subsonic.Playlist) { pop.Hide() m.doModalClosed() go func() { - err := m.App.ServerManager.Server.UpdatePlaylist(playlist.ID, map[string]string{ - "name": dlg.Name, - "comment": dlg.Description, - "public": strconv.FormatBool(dlg.IsPublic), - }) + err := m.App.ServerManager.Server.EditPlaylist(playlist.ID, dlg.Name, dlg.Description, dlg.IsPublic) if err != nil { log.Printf("error updating playlist: %s", err.Error()) } else if rte := m.CurPageFunc(); rte.Page == Playlist && rte.Arg == playlist.ID { @@ -405,49 +399,19 @@ func (c *Controller) doModalClosed() { } func (c *Controller) SetTrackFavorites(trackIDs []string, favorite bool) { - s := c.App.ServerManager.Server - if favorite { - go s.Star(subsonic.StarParameters{SongIDs: trackIDs}) - } else { - go s.Unstar(subsonic.StarParameters{SongIDs: trackIDs}) - } + c.App.ServerManager.Server.SetFavorite(mediaprovider.RatingFavoriteParameters{ + TrackIDs: trackIDs, + }, favorite) + for _, id := range trackIDs { c.App.PlaybackManager.OnTrackFavoriteStatusChanged(id, favorite) } } func (c *Controller) SetTrackRatings(trackIDs []string, rating int) { - // Subsonic doesn't allow bulk setting ratings. - // To not overwhelm the server with requests, set rating for - // only 5 tracks at a time concurrently - batchSize := 5 - batchSetRating := func(offs int, wg *sync.WaitGroup) { - for i := 0; i < batchSize && offs+i < len(trackIDs); i++ { - if wg != nil { - wg.Add(1) - } - go func(idx int) { - c.App.ServerManager.Server.SetRating(trackIDs[idx], rating) - if wg != nil { - wg.Done() - } - }(offs + i) - } - } - - if len(trackIDs) <= 5 { - // one batch only - no need to use wait group - batchSetRating(0, nil) - } else { - go func() { - numBatches := int(math.Ceil(float64(len(trackIDs)) / float64(batchSize))) - for i := 0; i < numBatches; i++ { - var wg sync.WaitGroup - batchSetRating(i*batchSize, &wg) - wg.Wait() - } - }() - } + c.App.ServerManager.Server.SetRating(mediaprovider.RatingFavoriteParameters{ + TrackIDs: trackIDs, + }, rating) // Notify PlaybackManager of rating change to update // the in-memory track models diff --git a/ui/dialogs/editplaylistdialog.go b/ui/dialogs/editplaylistdialog.go index 20e09a9..43a3a74 100644 --- a/ui/dialogs/editplaylistdialog.go +++ b/ui/dialogs/editplaylistdialog.go @@ -6,7 +6,7 @@ import ( "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/widget" - "github.com/dweymouth/go-subsonic/subsonic" + "github.com/dweymouth/supersonic/backend/mediaprovider" ) type EditPlaylistDialog struct { @@ -23,11 +23,11 @@ type EditPlaylistDialog struct { container *fyne.Container } -func NewEditPlaylistDialog(playlist *subsonic.Playlist) *EditPlaylistDialog { +func NewEditPlaylistDialog(playlist *mediaprovider.Playlist) *EditPlaylistDialog { e := &EditPlaylistDialog{ IsPublic: playlist.Public, Name: playlist.Name, - Description: playlist.Comment, + Description: playlist.Description, } e.ExtendBaseWidget(e) diff --git a/ui/widgets/gridview.go b/ui/widgets/gridview.go index 4d04f0e..5c82b8d 100644 --- a/ui/widgets/gridview.go +++ b/ui/widgets/gridview.go @@ -6,17 +6,38 @@ import ( "log" "sync" - "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/res" "github.com/dweymouth/supersonic/sharedutil" "fyne.io/fyne/v2" "fyne.io/fyne/v2/widget" - "github.com/dweymouth/go-subsonic/subsonic" ) const batchFetchSize = 6 +type BatchingIterator struct { + iter mediaprovider.AlbumIterator +} + +func NewBatchingIterator(iter mediaprovider.AlbumIterator) BatchingIterator { + return BatchingIterator{iter} +} + +func (b *BatchingIterator) NextN(n int) []*mediaprovider.Album { + results := make([]*mediaprovider.Album, 0, n) + i := 0 + for i < n { + album := b.iter.Next() + if album == nil { + break + } + results = append(results, album) + i++ + } + return results +} + type ImageFetcher interface { GetCoverThumbnailFromCache(string) (image.Image, bool) GetCoverThumbnail(string) (image.Image, error) @@ -27,24 +48,24 @@ type GridViewIterator interface { } type gridViewAlbumIterator struct { - iter *backend.BatchingIterator + iter BatchingIterator } func (g gridViewAlbumIterator) NextN(n int) []GridViewItemModel { albums := g.iter.NextN(n) - return sharedutil.MapSlice(albums, func(al *subsonic.AlbumID3) GridViewItemModel { + return sharedutil.MapSlice(albums, func(al *mediaprovider.Album) GridViewItemModel { return GridViewItemModel{ Name: al.Name, ID: al.ID, - CoverArtID: al.CoverArt, - Secondary: al.Artist, - SecondaryID: al.ArtistID, + CoverArtID: al.CoverArtID, + Secondary: al.ArtistNames[0], + SecondaryID: al.ArtistIDs[0], } }) } -func NewGridViewAlbumIterator(iter backend.AlbumIterator) GridViewIterator { - return gridViewAlbumIterator{iter: backend.NewBatchingIterator(iter)} +func NewGridViewAlbumIterator(iter mediaprovider.AlbumIterator) GridViewIterator { + return gridViewAlbumIterator{iter: NewBatchingIterator(iter)} } type GridView struct {