fix(ui): cancel abandoned Spotify album loads
This commit is contained in:
+10
-2
@@ -452,9 +452,17 @@ type spotAlbumTracksMsg struct {
|
||||
// fetchSpotAlbumTracksCmd expands an album placeholder from the search results
|
||||
// into its tracks. Album entries carry no streamable path of their own, so this
|
||||
// runs before the album can reach the player.
|
||||
func fetchSpotAlbumTracksCmd(loader provider.AlbumTrackLoader, album playlist.Track, action spotAlbumAction, gen uint64) tea.Cmd {
|
||||
func fetchSpotAlbumTracksCmd(ctx context.Context, loader provider.AlbumTrackLoader, album playlist.Track, action spotAlbumAction, gen uint64) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
tracks, err := loader.AlbumTracks(album.AlbumID())
|
||||
var tracks []playlist.Track
|
||||
var err error
|
||||
if contextual, ok := loader.(interface {
|
||||
AlbumTracksContext(context.Context, string) ([]playlist.Track, error)
|
||||
}); ok {
|
||||
tracks, err = contextual.AlbumTracksContext(ctx, album.AlbumID())
|
||||
} else {
|
||||
tracks, err = loader.AlbumTracks(album.AlbumID())
|
||||
}
|
||||
return spotAlbumTracksMsg{tracks: tracks, album: album, action: action, err: err, gen: gen}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,8 @@ func (m *Model) expandSpotAlbum(album playlist.Track, action spotAlbumAction) te
|
||||
}
|
||||
m.spotSearch.albumLoading = true
|
||||
m.spotSearch.err = ""
|
||||
return fetchSpotAlbumTracksCmd(loader, album, action, nextRequest(&m.requests.spotAlbum))
|
||||
ctx := m.newSpotRequestContext(30 * time.Second)
|
||||
return fetchSpotAlbumTracksCmd(ctx, loader, album, action, nextRequest(&m.requests.spotAlbum))
|
||||
}
|
||||
|
||||
func (m *Model) spotSearchPlaylistMaybeAdjustScroll(visible int) {
|
||||
|
||||
@@ -217,6 +217,7 @@ func (m *Model) closeSpotSearch() {
|
||||
}
|
||||
|
||||
func (m *Model) invalidateSpotAlbumRequest() {
|
||||
m.cancelSpotRequest()
|
||||
nextRequest(&m.requests.spotAlbum)
|
||||
m.spotSearch.albumLoading = false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
@@ -29,18 +31,25 @@ func TestCanceledSpotAlbumResponseIsIgnored(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
canceled := make(chan struct{})
|
||||
m := Model{
|
||||
playlist: playlist.New(),
|
||||
spotSearch: spotSearchState{
|
||||
visible: true,
|
||||
screen: spotSearchResults,
|
||||
albumLoading: true,
|
||||
cancel: func() { close(canceled) },
|
||||
},
|
||||
}
|
||||
const gen = 7
|
||||
m.requests.spotAlbum = gen
|
||||
|
||||
tt.cancel(&m)
|
||||
select {
|
||||
case <-canceled:
|
||||
default:
|
||||
t.Fatal("album request context was not canceled")
|
||||
}
|
||||
if m.requests.spotAlbum == gen {
|
||||
t.Fatal("album request generation was not invalidated")
|
||||
}
|
||||
@@ -61,3 +70,24 @@ func TestCanceledSpotAlbumResponseIsIgnored(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type contextAlbumLoader struct{}
|
||||
|
||||
func (contextAlbumLoader) AlbumTracks(string) ([]playlist.Track, error) {
|
||||
return nil, errors.New("legacy album loader called")
|
||||
}
|
||||
|
||||
func (contextAlbumLoader) AlbumTracksContext(ctx context.Context, _ string) ([]playlist.Track, error) {
|
||||
<-ctx.Done()
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
func TestFetchSpotAlbumTracksCmdUsesContextLoader(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
msg := fetchSpotAlbumTracksCmd(ctx, contextAlbumLoader{}, albumResult("Album"), spotAlbumPlay, 1)().(spotAlbumTracksMsg)
|
||||
if !errors.Is(msg.err, context.Canceled) {
|
||||
t.Fatalf("album load error = %v, want context.Canceled", msg.err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,6 +737,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if msg.gen != m.requests.spotAlbum {
|
||||
return m, nil
|
||||
}
|
||||
m.cancelSpotRequest()
|
||||
m.spotSearch.albumLoading = false
|
||||
if msg.err != nil {
|
||||
m.setSpotSearchError(msg.err.Error())
|
||||
|
||||
Reference in New Issue
Block a user