-
Notifications
You must be signed in to change notification settings - Fork 0
/
spotify.go
139 lines (119 loc) · 4.66 KB
/
spotify.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package main
import (
"errors"
"log"
"time"
"github.com/zmb3/spotify"
)
// SpotifyClient is a wrapper around spotify.Client that adds retry logic.
// Note that spotify.Client has an AutoRetry flag that one can set
// true, and this struct does indeed set that flag, but this only
// catches certain HTTP codes that indicate a retry may help, namely,
// 429 (Too Many Requests).
// What we want is to _also_ retry on any errors, like e.g. a 502 (Bad
// Gateway), which Spotify's API does indeed return sometimes.
// This is especially important for fangirl in particular because its
// execution times are so long (increasing the likelihood that it runs
// into a failure of Spotify's API, even if its SLA is great!).
type SpotifyClient struct {
client *spotify.Client
maxTries uint
retryDelay time.Duration
}
func NewSpotifyClient(client *spotify.Client, maxTries uint, retryDelay time.Duration) *SpotifyClient {
client.AutoRetry = true
return &SpotifyClient{
client: client,
maxTries: maxTries,
retryDelay: retryDelay,
}
}
func errIsOneOf(err error, errs ...error) bool {
for _, e := range errs {
if errors.Is(err, e) {
return true
}
}
return false
}
func wrapInRetry(fun func() error, maxTries uint, retryDelay time.Duration, allowedErrs ...error) (err error) {
for i := uint(0); i <= maxTries; i++ {
err = fun()
if err != nil && !errIsOneOf(err, allowedErrs...) {
log.Printf("errored for the %dth time: %v", i+1, err)
if i < maxTries { // Don't wait an extra amount at the end when we've hit the maxTries.
time.Sleep(retryDelay)
}
continue
}
break
}
return err
}
func wrapInRetryWithRet[T any](
fun func() (T, error),
maxTries uint,
retryDelay time.Duration,
allowedErrs ...error,
) (ret T, err error) {
// Maybe this is not that readable with the variable
// shadowing... but damn that was cool to write.
return ret, wrapInRetry(func() error {
ret, err = fun()
return err
}, maxTries, retryDelay, allowedErrs...)
}
func (sc *SpotifyClient) CurrentUsersFollowedArtistsOpt(limit int, after string) (*spotify.FullArtistCursorPage, error) {
return wrapInRetryWithRet(func() (*spotify.FullArtistCursorPage, error) {
return sc.client.CurrentUsersFollowedArtistsOpt(limit, after)
}, sc.maxTries, sc.retryDelay)
}
func (sc *SpotifyClient) GetArtistAlbumsOpt(artistID spotify.ID, options *spotify.Options, ts ...spotify.AlbumType) (*spotify.SimpleAlbumPage, error) {
return wrapInRetryWithRet(func() (*spotify.SimpleAlbumPage, error) {
return sc.client.GetArtistAlbumsOpt(artistID, options, ts...)
}, sc.maxTries, sc.retryDelay)
}
func (sc *SpotifyClient) CurrentUsersAlbums() (*spotify.SavedAlbumPage, error) {
return wrapInRetryWithRet(func() (*spotify.SavedAlbumPage, error) {
return sc.client.CurrentUsersAlbums()
}, sc.maxTries, sc.retryDelay)
}
func (sc *SpotifyClient) CreatePlaylistForUser(userID string, playlistName string, description string, public bool) (*spotify.FullPlaylist, error) {
return wrapInRetryWithRet(func() (*spotify.FullPlaylist, error) {
return sc.client.CreatePlaylistForUser(userID, playlistName, description, public)
}, sc.maxTries, sc.retryDelay)
}
func (sc *SpotifyClient) GetAlbumTracks(id spotify.ID) (*spotify.SimpleTrackPage, error) {
return wrapInRetryWithRet(func() (*spotify.SimpleTrackPage, error) {
return sc.client.GetAlbumTracks(id)
}, sc.maxTries, sc.retryDelay)
}
func (sc *SpotifyClient) AddTracksToPlaylist(playlistID spotify.ID, trackIDs ...spotify.ID) (string, error) {
return wrapInRetryWithRet(func() (string, error) {
return sc.client.AddTracksToPlaylist(playlistID, trackIDs...)
}, sc.maxTries, sc.retryDelay)
}
func (sc *SpotifyClient) CurrentUser() (*spotify.PrivateUser, error) {
return wrapInRetryWithRet(func() (*spotify.PrivateUser, error) {
return sc.client.CurrentUser()
}, sc.maxTries, sc.retryDelay)
}
// The following functions are unfortunately necessary because
// spotify.Client#NextPage() takes a spotify.pageable, but since this
// is not exported, we can't create a wrapping
// SpotifyClient#NextPage() implementation.
func (sc *SpotifyClient) NextSimpleAlbumPage(albumPage *spotify.SimpleAlbumPage) error {
return wrapInRetry(func() error {
return sc.client.NextPage(albumPage)
}, sc.maxTries, sc.retryDelay, spotify.ErrNoMorePages)
}
func (sc *SpotifyClient) NextSavedAlbumPage(albumPage *spotify.SavedAlbumPage) error {
return wrapInRetry(func() error {
return sc.client.NextPage(albumPage)
}, sc.maxTries, sc.retryDelay, spotify.ErrNoMorePages)
}
func (sc *SpotifyClient) NextSimpleTrackPage(trackPage *spotify.SimpleTrackPage) error {
return wrapInRetry(func() error {
return sc.client.NextPage(trackPage)
}, sc.maxTries, sc.retryDelay, spotify.ErrNoMorePages)
}