Skip to content

Commit

Permalink
fix playlist bug
Browse files Browse the repository at this point in the history
  • Loading branch information
brianstrauch committed Aug 2, 2021
1 parent a48566e commit 2733406
Show file tree
Hide file tree
Showing 20 changed files with 187 additions and 222 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ require (
github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.7.0
)

replace github.com/brianstrauch/spotify => /Users/brianstrauch/Documents/go/src/github.com/brianstrauch/spotify
5 changes: 3 additions & 2 deletions internal/back/back.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,16 @@ func back(api internal.APIInterface) (string, error) {
}

id := playback.Item.ID
progressMs := playback.ProgressMs

if err := api.SkipToPreviousTrack(); err != nil {
if err.Error() == internal.ErrRestrictionViolated {
if err.Error() == "Player command failed: Restriction violated" {
return "", errors.New(internal.ErrNoPrevious)
}
}

playback, err = internal.WaitForUpdatedPlayback(api, func(playback *spotify.Playback) bool {
return playback.Item.ID != id
return playback.Item.ID != id || playback.ProgressMs < progressMs
})
if err != nil {
return "", err
Expand Down
8 changes: 5 additions & 3 deletions internal/back/back_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ func TestBack(t *testing.T) {
ProgressMs: 0,
Item: spotify.Item{
Track: spotify.Track{
Meta: spotify.Meta{ID: "1"},
Meta: spotify.Meta{
ID: "1",
Type: "track",
},
Name: "Track",
Artists: []spotify.Artist{{Name: "Artist"}},
Duration: &spotify.Duration{Duration: time.Second},
},
Type: "track",
},
}

Expand All @@ -43,7 +45,7 @@ func TestBack(t *testing.T) {
func TestBack_ErrNoPrevious(t *testing.T) {
api := new(internal.MockAPI)
api.On("GetPlayback").Return(new(spotify.Playback), nil)
api.On("SkipToPreviousTrack").Return(errors.New(internal.ErrRestrictionViolated))
api.On("SkipToPreviousTrack").Return(errors.New("Player command failed: Restriction violated"))

_, err := back(api)
require.Error(t, err)
Expand Down
46 changes: 41 additions & 5 deletions internal/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package internal

import (
"errors"
"strings"
"time"

"github.com/brianstrauch/spotify"
Expand Down Expand Up @@ -46,14 +47,14 @@ func SaveToken(token *spotify.Token) error {
return viper.WriteConfig()
}

func WaitForUpdatedPlayback(api APIInterface, isUpdated func(playback *spotify.Playback) bool) (*spotify.Playback, error) {
func WaitForUpdatedPlayback(api APIInterface, isUpdated func(*spotify.Playback) bool) (*spotify.Playback, error) {
timeout := time.After(time.Second)
tick := time.NewTicker(100 * time.Millisecond)

for {
select {
case <-timeout:
return nil, errors.New("request timed out")
return nil, errors.New(ErrRequestTimedOut)
case <-tick.C:
playback, err := api.GetPlayback()
if err != nil {
Expand All @@ -67,10 +68,45 @@ func WaitForUpdatedPlayback(api APIInterface, isUpdated func(playback *spotify.P
}
}

func Search(api APIInterface, query, searchType string) (*spotify.Track, error) {
page, err := api.Search(query, searchType, 1)
func SearchTrack(api APIInterface, query string) (*spotify.Track, error) {
paging, err := api.Search(query, "track", 1)
if err != nil {
return nil, err
}
return page.Tracks.Items[0], nil

tracks := paging.Tracks.Items
if len(tracks) == 0 {
return nil, errors.New(ErrTrackNotFound)
}

return paging.Tracks.Items[0], nil
}

func SearchAlbum(api APIInterface, query string) (*spotify.Album, error) {
paging, err := api.Search(query, "album", 1)
if err != nil {
return nil, err
}

albums := paging.Albums.Items
if len(albums) == 0 {
return nil, errors.New(ErrAlbumNotFound)
}

return albums[0], nil
}

func SearchPlaylist(api APIInterface, query string) (*spotify.Playlist, error) {
playlists, err := api.GetPlaylists()
if err != nil {
return nil, err
}

for _, playlist := range playlists {
if strings.EqualFold(playlist.Name, query) {
return playlist, nil
}
}

return nil, errors.New(ErrPlaylistNotFound)
}
23 changes: 13 additions & 10 deletions internal/errors.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package internal

const (
ErrAlreadyUpToDate = "Already up to date"
ErrLoginFailed = "Login failed"
ErrNoActiveDevice = "Player command failed: No active device found"
ErrNoAlbums = "No albums found"
ErrNoDevices = "No devices found"
ErrNoPlaylists = "No playlists found"
ErrNoPrevious = "No track before this one"
ErrNotLoggedIn = `You are not logged in: Run "spotify login"`
ErrRestrictionViolated = "Player command failed: Restriction violated"
ErrSavePodcast = "Saving podcasts is not allowed"
ErrAlbumNotFound = "album not found"
ErrAlreadyUpToDate = "already up to date"
ErrInvalidPlayArgs = "you may only pass args, --album, or --playlist"
ErrLoginFailed = "login failed"
ErrNoActiveDevice = "no active spotify session found"
ErrNoDevices = "no devices found"
ErrNoPlaylists = "no playlists found"
ErrNoPrevious = "no track before this one"
ErrNotLoggedIn = `you are not logged in, run "spotify login"`
ErrPlaylistNotFound = "playlist not found"
ErrRequestTimedOut = "request timed out"
ErrSavePodcast = "saving podcasts is not allowed"
ErrTrackNotFound = "track not found"
)
1 change: 1 addition & 0 deletions internal/login/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func login() (*spotify.Token, error) {
}

scopes := []string{
spotify.ScopePlaylistReadPrivate,
spotify.ScopeUserLibraryModify,
spotify.ScopeUserModifyPlaybackState,
spotify.ScopeUserReadPlaybackState,
Expand Down
56 changes: 31 additions & 25 deletions internal/mock_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ type APIInterface interface {

GetPlayback() (*spotify.Playback, error)
GetDevices() ([]*spotify.Device, error)
Play(deviceID, contextURI string, uris ...string) error
Pause(deviceID string) error
Play(contextURI string, uris ...string) error
Pause() error
SkipToNextTrack() error
SkipToPreviousTrack() error
Repeat(state string) error
Shuffle(state bool) error
Queue(uri string) error

GetPlaylists() ([]*spotify.Playlist, error)

Search(q, searchType string, limit int) (*spotify.Paging, error)
}

Expand All @@ -27,13 +29,11 @@ type MockAPI struct {
}

func (m *MockAPI) SaveTracks(ids ...string) error {
args := m.Called(ids)
return args.Error(0)
return m.Called(ids).Error(0)
}

func (m *MockAPI) RemoveSavedTracks(ids ...string) error {
args := m.Called(ids)
return args.Error(0)
return m.Called(ids).Error(0)
}

func (m *MockAPI) GetPlayback() (*spotify.Playback, error) {
Expand All @@ -58,50 +58,56 @@ func (m *MockAPI) GetDevices() ([]*spotify.Device, error) {
return devices.([]*spotify.Device), err
}

func (m *MockAPI) Play(deviceID, contextURI string, uris ...string) error {
args := m.Called(deviceID, contextURI, uris)
return args.Error(0)
func (m *MockAPI) Play(contextURI string, uris ...string) error {
return m.Called(contextURI, uris).Error(0)
}

func (m *MockAPI) Pause(deviceID string) error {
args := m.Called(deviceID)
return args.Error(0)
func (m *MockAPI) Pause() error {
return m.Called().Error(0)
}

func (m *MockAPI) SkipToNextTrack() error {
args := m.Called()
return args.Error(0)
return m.Called().Error(0)
}

func (m *MockAPI) SkipToPreviousTrack() error {
args := m.Called()
return args.Error(0)
return m.Called().Error(0)
}

func (m *MockAPI) Repeat(state string) error {
args := m.Called(state)
return args.Error(0)
return m.Called(state).Error(0)
}

func (m *MockAPI) Shuffle(state bool) error {
args := m.Called(state)
return args.Error(0)
return m.Called(state).Error(0)
}

func (m *MockAPI) Queue(uri string) error {
args := m.Called(uri)
return args.Error(0)
return m.Called(uri).Error(0)
}

func (m *MockAPI) GetPlaylists() ([]*spotify.Playlist, error) {
args := m.Called()

playlists := args.Get(0)
err := args.Error(1)

if playlists == nil {
return nil, err
}

return playlists.([]*spotify.Playlist), err
}

func (m *MockAPI) Search(q, searchType string, limit int) (*spotify.Paging, error) {
args := m.Called(q, searchType, limit)

page := args.Get(0)
paging := args.Get(0)
err := args.Error(1)

if page == nil {
if paging == nil {
return nil, err
}

return page.(*spotify.Paging), err
return paging.(*spotify.Paging), err
}
3 changes: 1 addition & 2 deletions internal/next/next.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,14 @@ func next(api internal.APIInterface) (string, error) {
return "", errors.New(internal.ErrNoActiveDevice)
}

progressMs := playback.ProgressMs
id := playback.Item.ID
progressMs := playback.ProgressMs

if err := api.SkipToNextTrack(); err != nil {
return "", err
}

playback, err = internal.WaitForUpdatedPlayback(api, func(playback *spotify.Playback) bool {
// TODO: Handle edge case where the next song is the same and progressMs = 0
return playback.Item.ID != id || playback.ProgressMs < progressMs
})
if err != nil {
Expand Down
6 changes: 4 additions & 2 deletions internal/next/next_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ func TestNext(t *testing.T) {
ProgressMs: 0,
Item: spotify.Item{
Track: spotify.Track{
Meta: spotify.Meta{ID: "0"},
Meta: spotify.Meta{
ID: "0",
Type: "track",
},
Name: "Track",
Artists: []spotify.Artist{{Name: "Artist"}},
Duration: &spotify.Duration{Duration: time.Second},
},
Type: "track",
},
}

Expand Down
32 changes: 12 additions & 20 deletions internal/p/p.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,24 @@ func NewCommand() *cobra.Command {
if err != nil {
return err
}
query := strings.Join(args, " ")
queryType := "track"

deviceID, err := cmd.Flags().GetString("device-id")
track := strings.Join(args, " ")

playlist, err := cmd.Flags().GetString("playlist")
if err != nil {
return err
}

contextQuery, err := cmd.Flags().GetString("playlist")
album, err := cmd.Flags().GetString("album")
if err != nil {
return err
}

if contextQuery == "" {
contextQuery, err = cmd.Flags().GetString("album")
if err != nil {
return err
}
queryType = "album"
} else {
queryType = "playlist"
if track != "" && playlist != "" || track != "" && album != "" || playlist != "" && album != "" {
return errors.New(internal.ErrInvalidPlayArgs)
}

status, err := p(api, query, contextQuery, queryType, deviceID)
status, err := p(api, track, playlist, album)
if err != nil {
return err
}
Expand All @@ -52,30 +46,28 @@ func NewCommand() *cobra.Command {
},
}

cmd.Flags().String("device-id", "", "device ID from 'spotify device list'")
cmd.Flags().String("playlist", "", "playlist name from 'spotify playlist list'")
cmd.Flags().String("album", "", "album name")

return cmd
}

func p(api internal.APIInterface, query, contextQuery, queryType, deviceID string) (string, error) {
if len(query) > 0 || len(contextQuery) > 0 {
return play.Play(api, query, contextQuery, queryType, deviceID)
func p(api internal.APIInterface, track, playlist, album string) (string, error) {
if track != "" || playlist != "" || album != "" {
return play.Play(api, track, playlist, album)
}

playback, err := api.GetPlayback()
if err != nil {
return "", err
}

if playback == nil {
return "", errors.New(internal.ErrNoActiveDevice)
}

if playback.IsPlaying {
return pause.Pause(api, deviceID)
return pause.Pause(api)
} else {
return play.Play(api, query, contextQuery, queryType, deviceID)
return play.Play(api, "", "", "")
}
}
Loading

0 comments on commit 2733406

Please sign in to comment.