Skip to content

Commit

Permalink
device command
Browse files Browse the repository at this point in the history
  • Loading branch information
brianstrauch committed Jul 15, 2021
1 parent 811763d commit 6bfc0a5
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 80 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.16

require (
github.com/blang/semver v3.5.1+incompatible
github.com/brianstrauch/spotify v0.4.1
github.com/brianstrauch/spotify v0.5.0
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/browser v0.0.0-20210606212950-a7b7a6107d32
github.com/rhysd/go-github-selfupdate v1.2.3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/brianstrauch/spotify v0.4.1 h1:v5J5kH9/BWGJXFCnnUVKPzK6f9nCxdj9UDUvMDBXdww=
github.com/brianstrauch/spotify v0.4.1/go.mod h1:sImRT74obai+LNgFVxr+qNVq2yt5KEfvNxz6ZDfYWaA=
github.com/brianstrauch/spotify v0.5.0 h1:IUEY5JPEPJJJdyVdpcH8ZzviGxI3Ra3huWPN+517MbU=
github.com/brianstrauch/spotify v0.5.0/go.mod h1:sImRT74obai+LNgFVxr+qNVq2yt5KEfvNxz6ZDfYWaA=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
Expand Down
16 changes: 16 additions & 0 deletions internal/device/device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package device

import (
"github.com/spf13/cobra"
)

func NewCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "device",
Short: "Manage devices.",
}

cmd.AddCommand(NewListCommand())

return cmd
}
58 changes: 58 additions & 0 deletions internal/device/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package device

import (
"errors"
"fmt"
"spotify/internal"
"strings"

"github.com/brianstrauch/spotify"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
)

func NewListCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List your devices.",
RunE: func(cmd *cobra.Command, args []string) error {
api, err := internal.Authenticate()
if err != nil {
return err
}

list, err := List(api)
if err != nil {
return err
}

fmt.Print(list)
return nil
},
}
}

func List(api *spotify.API) (string, error) {
devices, err := api.GetDevices()
if err != nil {
return "", err
}

if len(devices) == 0 {
return "", errors.New(internal.ErrNoDevices)
}

output := new(strings.Builder)

table := tablewriter.NewWriter(output)
table.SetBorder(false)

table.SetHeader([]string{"ID", "Device"})

for _, device := range devices {
table.Append([]string{device.ID, device.Name})
}
table.Render()

return output.String(), nil
}
3 changes: 1 addition & 2 deletions internal/errors.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package internal

const (
ErrAlreadyPaused = "Already paused"
ErrAlreadyPlaying = "Already playing"
ErrAlreadyUpToDate = "Already up to date"
ErrLoginFailed = "Login failed"
ErrNoActiveDevice = "Player command failed: No active device found"
ErrNoDevices = "No devices"
ErrNoPlaylists = "No playlists"
ErrNoPrevious = "No track before this one"
ErrNotLoggedIn = `You are not logged in: Run "spotify login"`
Expand Down
68 changes: 35 additions & 33 deletions internal/mock_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,35 @@ import (
)

type APIInterface interface {
GetPlayback() (*spotify.Playback, error)
Pause() error
Play(uris ...string) error
Queue(uri string) error
RemoveSavedTracks(ids ...string) error
Repeat(state string) error
SaveTracks(ids ...string) error
Search(q string, limit int) (*spotify.Paging, error)
Shuffle(state bool) error
RemoveSavedTracks(ids ...string) error

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

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

type MockAPI struct {
mock.Mock
}

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

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

func (m *MockAPI) GetPlayback() (*spotify.Playback, error) {
args := m.Called()

Expand All @@ -36,23 +48,23 @@ func (m *MockAPI) GetPlayback() (*spotify.Playback, error) {
return playback.(*spotify.Playback), err
}

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

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

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

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

Expand All @@ -61,8 +73,13 @@ func (m *MockAPI) Repeat(state string) error {
return args.Error(0)
}

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

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

Expand All @@ -78,18 +95,3 @@ func (m *MockAPI) Search(q string, limit int) (*spotify.Paging, error) {

return page.(*spotify.Paging), err
}

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

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

func (m *MockAPI) SkipToPreviousTrack() error {
args := m.Called()
return args.Error(0)
}
21 changes: 15 additions & 6 deletions internal/p/p.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

func NewCommand() *cobra.Command {
return &cobra.Command{
cmd := &cobra.Command{
Use: "p [song]",
// Keep hidden, since this command is an alias.
Hidden: true,
Expand All @@ -23,7 +23,12 @@ func NewCommand() *cobra.Command {

query := strings.Join(args, " ")

status, err := p(api, query)
deviceID, err := cmd.Flags().GetString("device-id")
if err != nil {
return err
}

status, err := p(api, query, deviceID)
if err != nil {
return err
}
Expand All @@ -32,11 +37,15 @@ func NewCommand() *cobra.Command {
return nil
},
}

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

return cmd
}

func p(api internal.APIInterface, query string) (string, error) {
func p(api internal.APIInterface, query, deviceID string) (string, error) {
if len(query) > 0 {
return play.Play(api, query)
return play.Play(api, query, deviceID)
}

playback, err := api.GetPlayback()
Expand All @@ -49,8 +58,8 @@ func p(api internal.APIInterface, query string) (string, error) {
}

if playback.IsPlaying {
return pause.Pause(api)
return pause.Pause(api, deviceID)
} else {
return play.Play(api, "")
return play.Play(api, "", deviceID)
}
}
10 changes: 5 additions & 5 deletions internal/p/p_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ func TestP_Play(t *testing.T) {

api.On("GetPlayback").Return(playback1, nil).Twice()
api.On("GetPlayback").Return(playback2, nil).Once()
api.On("Play", []string(nil)).Return(nil)
api.On("Play", "", []string(nil)).Return(nil)

status, err := p(api, "")
status, err := p(api, "", "")
require.NoError(t, err)
require.Equal(t, " Song\r🎵\n Artist\r🎤\n 0:00 [ ] 0:01\r▶️\n", status)
}
Expand Down Expand Up @@ -65,9 +65,9 @@ func TestP_Pause(t *testing.T) {

api.On("GetPlayback").Return(playback1, nil).Twice()
api.On("GetPlayback").Return(playback2, nil).Once()
api.On("Pause").Return(nil)
api.On("Pause", "").Return(nil)

status, err := p(api, "")
status, err := p(api, "", "")
require.NoError(t, err)
require.Equal(t, " Song\r🎵\n Artist\r🎤\n 0:00 [ ] 0:01\r\n", status)
}
Expand All @@ -76,7 +76,7 @@ func TestP_ErrNoActiveDevice(t *testing.T) {
api := new(internal.MockAPI)
api.On("GetPlayback").Return(nil, nil)

_, err := p(api, "")
_, err := p(api, "", "")
require.Error(t, err)
require.Equal(t, internal.ErrNoActiveDevice, err.Error())
}
21 changes: 14 additions & 7 deletions internal/pause/pause.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func NewCommand() *cobra.Command {
return &cobra.Command{
cmd := &cobra.Command{
Use: "pause",
Short: "Pause music.",
RunE: func(cmd *cobra.Command, _ []string) error {
Expand All @@ -19,7 +19,12 @@ func NewCommand() *cobra.Command {
return err
}

status, err := Pause(api)
deviceID, err := cmd.Flags().GetString("device-id")
if err != nil {
return err
}

status, err := Pause(api, deviceID)
if err != nil {
return err
}
Expand All @@ -28,9 +33,13 @@ func NewCommand() *cobra.Command {
return nil
},
}

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

return cmd
}

func Pause(api internal.APIInterface) (string, error) {
func Pause(api internal.APIInterface, deviceID string) (string, error) {
playback, err := api.GetPlayback()
if err != nil {
return "", err
Expand All @@ -40,10 +49,8 @@ func Pause(api internal.APIInterface) (string, error) {
return "", errors.New(internal.ErrNoActiveDevice)
}

if err := api.Pause(); err != nil {
if err.Error() == internal.ErrRestrictionViolated {
return "", errors.New(internal.ErrAlreadyPaused)
}
if err := api.Pause(deviceID); err != nil {
return "", err
}

playback, err = internal.WaitForUpdatedPlayback(api, func(playback *spotify.Playback) bool {
Expand Down
12 changes: 6 additions & 6 deletions internal/pause/pause_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,28 @@ func TestPause(t *testing.T) {

api.On("GetPlayback").Return(playback1, nil).Once()
api.On("GetPlayback").Return(playback2, nil).Once()
api.On("Pause").Return(nil)
api.On("Pause", "").Return(nil)

status, err := Pause(api)
status, err := Pause(api, "")
require.NoError(t, err)
require.Equal(t, " Song\r🎵\n Artist\r🎤\n 0:00 [ ] 0:01\r\n", status)
}

func TestPause_ErrAlreadyPaused(t *testing.T) {
api := new(internal.MockAPI)
api.On("GetPlayback").Return(new(spotify.Playback), nil)
api.On("Pause").Return(errors.New(internal.ErrRestrictionViolated))
api.On("Pause", "").Return(errors.New(internal.ErrRestrictionViolated))

_, err := Pause(api)
_, err := Pause(api, "")
require.Error(t, err)
require.Equal(t, internal.ErrAlreadyPaused, err.Error())
require.Equal(t, internal.ErrRestrictionViolated, err.Error())
}

func TestPause_ErrNoActiveDevice(t *testing.T) {
api := new(internal.MockAPI)
api.On("GetPlayback").Return(nil, nil)

_, err := Pause(api)
_, err := Pause(api, "")
require.Error(t, err)
require.Equal(t, internal.ErrNoActiveDevice, err.Error())
}
Loading

0 comments on commit 6bfc0a5

Please sign in to comment.