diff --git a/internal/backends/deezer/client.go b/internal/backends/deezer/client.go index 05264ae..3ab2b6c 100644 --- a/internal/backends/deezer/client.go +++ b/internal/backends/deezer/client.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,7 @@ THE SOFTWARE. package deezer import ( + "context" "errors" "strconv" @@ -52,14 +53,14 @@ func NewClient(token oauth2.TokenSource) Client { } } -func (c Client) UserHistory(offset int, limit int) (result HistoryResult, err error) { +func (c Client) UserHistory(ctx context.Context, offset int, limit int) (result HistoryResult, err error) { const path = "/user/me/history" - return listRequest[HistoryResult](c, path, offset, limit) + return listRequest[HistoryResult](ctx, c, path, offset, limit) } -func (c Client) UserTracks(offset int, limit int) (TracksResult, error) { +func (c Client) UserTracks(ctx context.Context, offset int, limit int) (TracksResult, error) { const path = "/user/me/tracks" - return listRequest[TracksResult](c, path, offset, limit) + return listRequest[TracksResult](ctx, c, path, offset, limit) } func (c Client) setToken(req *resty.Request) error { @@ -72,8 +73,9 @@ func (c Client) setToken(req *resty.Request) error { return nil } -func listRequest[T Result](c Client, path string, offset int, limit int) (result T, err error) { +func listRequest[T Result](ctx context.Context, c Client, path string, offset int, limit int) (result T, err error) { request := c.HTTPClient.R(). + SetContext(ctx). SetQueryParams(map[string]string{ "index": strconv.Itoa(offset), "limit": strconv.Itoa(limit), diff --git a/internal/backends/deezer/client_test.go b/internal/backends/deezer/client_test.go index c90b01a..8b61804 100644 --- a/internal/backends/deezer/client_test.go +++ b/internal/backends/deezer/client_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,7 @@ THE SOFTWARE. package deezer_test import ( + "context" "net/http" "testing" @@ -48,7 +49,8 @@ func TestGetUserHistory(t *testing.T) { "https://api.deezer.com/user/me/history", "testdata/user-history.json") - result, err := client.UserHistory(0, 2) + ctx := context.Background() + result, err := client.UserHistory(ctx, 0, 2) require.NoError(t, err) assert := assert.New(t) @@ -69,7 +71,8 @@ func TestGetUserTracks(t *testing.T) { "https://api.deezer.com/user/me/tracks", "testdata/user-tracks.json") - result, err := client.UserTracks(0, 2) + ctx := context.Background() + result, err := client.UserTracks(ctx, 0, 2) require.NoError(t, err) assert := assert.New(t) diff --git a/internal/backends/deezer/deezer.go b/internal/backends/deezer/deezer.go index 2209769..f3e3d37 100644 --- a/internal/backends/deezer/deezer.go +++ b/internal/backends/deezer/deezer.go @@ -16,6 +16,7 @@ Scotty. If not, see . package deezer import ( + "context" "fmt" "math" "net/url" @@ -78,6 +79,8 @@ func (b *DeezerApiBackend) OAuth2Setup(token oauth2.TokenSource) error { } func (b *DeezerApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { + ctx := context.TODO() + // Choose a high offset, we attempt to search the loves backwards starting // at the oldest one. offset := math.MaxInt32 @@ -96,7 +99,7 @@ func (b *DeezerApiBackend) ExportListens(oldestTimestamp time.Time, results chan out: for { - result, err := b.client.UserHistory(offset, perPage) + result, err := b.client.UserHistory(ctx, offset, perPage) if err != nil { p.Export.Abort() progress <- p @@ -154,6 +157,8 @@ out: } func (b *DeezerApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { + ctx := context.TODO() + // Choose a high offset, we attempt to search the loves backwards starting // at the oldest one. offset := math.MaxInt32 @@ -168,7 +173,7 @@ func (b *DeezerApiBackend) ExportLoves(oldestTimestamp time.Time, results chan m out: for { - result, err := b.client.UserTracks(offset, perPage) + result, err := b.client.UserTracks(ctx, offset, perPage) if err != nil { p.Export.Abort() progress <- p diff --git a/internal/backends/funkwhale/client.go b/internal/backends/funkwhale/client.go index c231c94..3471612 100644 --- a/internal/backends/funkwhale/client.go +++ b/internal/backends/funkwhale/client.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,6 +22,7 @@ THE SOFTWARE. package funkwhale import ( + "context" "errors" "strconv" @@ -54,15 +55,10 @@ func NewClient(serverURL string, token string) Client { } } -func (c Client) GetHistoryListenings(user string, page int, perPage int) (result ListeningsResult, err error) { +func (c Client) GetHistoryListenings(ctx context.Context, user string, page int, perPage int) (result ListeningsResult, err error) { const path = "/api/v1/history/listenings" - response, err := c.HTTPClient.R(). - SetQueryParams(map[string]string{ - "username": user, - "page": strconv.Itoa(page), - "page_size": strconv.Itoa(perPage), - "ordering": "-creation_date", - }). + response, err := c.buildListRequest(ctx, page, perPage). + SetQueryParam("username", user). SetResult(&result). Get(path) @@ -73,14 +69,9 @@ func (c Client) GetHistoryListenings(user string, page int, perPage int) (result return } -func (c Client) GetFavoriteTracks(page int, perPage int) (result FavoriteTracksResult, err error) { +func (c Client) GetFavoriteTracks(ctx context.Context, page int, perPage int) (result FavoriteTracksResult, err error) { const path = "/api/v1/favorites/tracks" - response, err := c.HTTPClient.R(). - SetQueryParams(map[string]string{ - "page": strconv.Itoa(page), - "page_size": strconv.Itoa(perPage), - "ordering": "-creation_date", - }). + response, err := c.buildListRequest(ctx, page, perPage). SetResult(&result). Get(path) @@ -90,3 +81,13 @@ func (c Client) GetFavoriteTracks(page int, perPage int) (result FavoriteTracksR } return } + +func (c Client) buildListRequest(ctx context.Context, page int, perPage int) *resty.Request { + return c.HTTPClient.R(). + SetContext(ctx). + SetQueryParams(map[string]string{ + "page": strconv.Itoa(page), + "page_size": strconv.Itoa(perPage), + "ordering": "-creation_date", + }) +} diff --git a/internal/backends/funkwhale/client_test.go b/internal/backends/funkwhale/client_test.go index e850a4d..d6b04e0 100644 --- a/internal/backends/funkwhale/client_test.go +++ b/internal/backends/funkwhale/client_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,6 +22,7 @@ THE SOFTWARE. package funkwhale_test import ( + "context" "net/http" "testing" @@ -49,7 +50,8 @@ func TestGetHistoryListenings(t *testing.T) { "https://funkwhale.example.com/api/v1/history/listenings", "testdata/listenings.json") - result, err := client.GetHistoryListenings("outsidecontext", 0, 2) + ctx := context.Background() + result, err := client.GetHistoryListenings(ctx, "outsidecontext", 0, 2) require.NoError(t, err) assert := assert.New(t) @@ -73,7 +75,8 @@ func TestGetFavoriteTracks(t *testing.T) { "https://funkwhale.example.com/api/v1/favorites/tracks", "testdata/favorite-tracks.json") - result, err := client.GetFavoriteTracks(0, 2) + ctx := context.Background() + result, err := client.GetFavoriteTracks(ctx, 0, 2) require.NoError(t, err) assert := assert.New(t) diff --git a/internal/backends/funkwhale/funkwhale.go b/internal/backends/funkwhale/funkwhale.go index cd2f28e..434716f 100644 --- a/internal/backends/funkwhale/funkwhale.go +++ b/internal/backends/funkwhale/funkwhale.go @@ -17,6 +17,7 @@ Scotty. If not, see . package funkwhale import ( + "context" "sort" "time" @@ -61,6 +62,7 @@ func (b *FunkwhaleApiBackend) InitConfig(config *config.ServiceConfig) error { } func (b *FunkwhaleApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { + ctx := context.TODO() page := 1 perPage := MaxItemsPerGet @@ -74,7 +76,7 @@ func (b *FunkwhaleApiBackend) ExportListens(oldestTimestamp time.Time, results c out: for { - result, err := b.client.GetHistoryListenings(b.username, page, perPage) + result, err := b.client.GetHistoryListenings(ctx, b.username, page, perPage) if err != nil { p.Export.Abort() progress <- p @@ -118,6 +120,7 @@ out: } func (b *FunkwhaleApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { + ctx := context.TODO() page := 1 perPage := MaxItemsPerGet @@ -131,7 +134,7 @@ func (b *FunkwhaleApiBackend) ExportLoves(oldestTimestamp time.Time, results cha out: for { - result, err := b.client.GetFavoriteTracks(page, perPage) + result, err := b.client.GetFavoriteTracks(ctx, page, perPage) if err != nil { p.Export.Abort() progress <- p diff --git a/internal/backends/listenbrainz/client.go b/internal/backends/listenbrainz/client.go index fff476c..d1a1fa6 100644 --- a/internal/backends/listenbrainz/client.go +++ b/internal/backends/listenbrainz/client.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,6 +22,7 @@ THE SOFTWARE. package listenbrainz import ( + "context" "errors" "strconv" "time" @@ -60,10 +61,11 @@ func NewClient(token string) Client { } } -func (c Client) GetListens(user string, maxTime time.Time, minTime time.Time) (result GetListensResult, err error) { +func (c Client) GetListens(ctx context.Context, user string, maxTime time.Time, minTime time.Time) (result GetListensResult, err error) { const path = "/user/{username}/listens" errorResult := ErrorResult{} response, err := c.HTTPClient.R(). + SetContext(ctx). SetPathParam("username", user). SetQueryParams(map[string]string{ "max_ts": strconv.FormatInt(maxTime.Unix(), 10), @@ -81,10 +83,11 @@ func (c Client) GetListens(user string, maxTime time.Time, minTime time.Time) (r return } -func (c Client) SubmitListens(listens ListenSubmission) (result StatusResult, err error) { +func (c Client) SubmitListens(ctx context.Context, listens ListenSubmission) (result StatusResult, err error) { const path = "/submit-listens" errorResult := ErrorResult{} response, err := c.HTTPClient.R(). + SetContext(ctx). SetBody(listens). SetResult(&result). SetError(&errorResult). @@ -97,10 +100,11 @@ func (c Client) SubmitListens(listens ListenSubmission) (result StatusResult, er return } -func (c Client) GetFeedback(user string, status int, offset int) (result GetFeedbackResult, err error) { +func (c Client) GetFeedback(ctx context.Context, user string, status int, offset int) (result GetFeedbackResult, err error) { const path = "/feedback/user/{username}/get-feedback" errorResult := ErrorResult{} response, err := c.HTTPClient.R(). + SetContext(ctx). SetPathParam("username", user). SetQueryParams(map[string]string{ "status": strconv.Itoa(status), @@ -119,10 +123,11 @@ func (c Client) GetFeedback(user string, status int, offset int) (result GetFeed return } -func (c Client) SendFeedback(feedback Feedback) (result StatusResult, err error) { +func (c Client) SendFeedback(ctx context.Context, feedback Feedback) (result StatusResult, err error) { const path = "/feedback/recording-feedback" errorResult := ErrorResult{} response, err := c.HTTPClient.R(). + SetContext(ctx). SetBody(feedback). SetResult(&result). SetError(&errorResult). @@ -135,10 +140,11 @@ func (c Client) SendFeedback(feedback Feedback) (result StatusResult, err error) return } -func (c Client) Lookup(recordingName string, artistName string) (result LookupResult, err error) { +func (c Client) Lookup(ctx context.Context, recordingName string, artistName string) (result LookupResult, err error) { const path = "/metadata/lookup" errorResult := ErrorResult{} response, err := c.HTTPClient.R(). + SetContext(ctx). SetQueryParams(map[string]string{ "recording_name": recordingName, "artist_name": artistName, diff --git a/internal/backends/listenbrainz/client_test.go b/internal/backends/listenbrainz/client_test.go index 2e841ae..45bb0de 100644 --- a/internal/backends/listenbrainz/client_test.go +++ b/internal/backends/listenbrainz/client_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,6 +22,7 @@ THE SOFTWARE. package listenbrainz_test import ( + "context" "net/http" "testing" "time" @@ -49,7 +50,9 @@ func TestGetListens(t *testing.T) { "https://api.listenbrainz.org/1/user/outsidecontext/listens", "testdata/listens.json") - result, err := client.GetListens("outsidecontext", time.Now(), time.Now().Add(-2*time.Hour)) + ctx := context.Background() + result, err := client.GetListens(ctx, "outsidecontext", + time.Now(), time.Now().Add(-2*time.Hour)) require.NoError(t, err) assert := assert.New(t) @@ -92,8 +95,8 @@ func TestSubmitListens(t *testing.T) { }, }, } - result, err := client.SubmitListens(listens) - require.NoError(t, err) + ctx := context.Background() + result, err := client.SubmitListens(ctx, listens) assert.Equal(t, "ok", result.Status) } @@ -107,7 +110,8 @@ func TestGetFeedback(t *testing.T) { "https://api.listenbrainz.org/1/feedback/user/outsidecontext/get-feedback", "testdata/feedback.json") - result, err := client.GetFeedback("outsidecontext", 1, 3) + ctx := context.Background() + result, err := client.GetFeedback(ctx, "outsidecontext", 1, 0) require.NoError(t, err) assert := assert.New(t) @@ -135,7 +139,8 @@ func TestSendFeedback(t *testing.T) { RecordingMBID: "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", Score: 1, } - result, err := client.SendFeedback(feedback) + ctx := context.Background() + result, err := client.SendFeedback(ctx, feedback) require.NoError(t, err) assert.Equal(t, "ok", result.Status) @@ -149,7 +154,8 @@ func TestLookup(t *testing.T) { "https://api.listenbrainz.org/1/metadata/lookup", "testdata/lookup.json") - result, err := client.Lookup("Paradise Lost", "Say Just Words") + ctx := context.Background() + result, err := client.Lookup(ctx, "Paradise Lost", "Say Just Words") require.NoError(t, err) assert := assert.New(t) diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index 61597d1..d622aff 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -17,6 +17,7 @@ Scotty. If not, see . package listenbrainz import ( + "context" "fmt" "sort" "time" @@ -73,6 +74,7 @@ func (b *ListenBrainzApiBackend) StartImport() error { return nil } func (b *ListenBrainzApiBackend) FinishImport() error { return nil } func (b *ListenBrainzApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { + ctx := context.TODO() startTime := time.Now() minTime := oldestTimestamp if minTime.Unix() < 1 { @@ -87,7 +89,7 @@ func (b *ListenBrainzApiBackend) ExportListens(oldestTimestamp time.Time, result } for { - result, err := b.client.GetListens(b.username, time.Now(), minTime) + result, err := b.client.GetListens(ctx, b.username, time.Now(), minTime) if err != nil { p.Export.Abort() progress <- p @@ -135,6 +137,7 @@ func (b *ListenBrainzApiBackend) ExportListens(oldestTimestamp time.Time, result } func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { + ctx := context.TODO() total := len(export.Items) p := models.TransferProgress{}.FromImportResult(importResult, false) for i := 0; i < total; i += MaxListensPerRequest { @@ -151,7 +154,7 @@ func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, impo for _, l := range listens { if b.checkDuplicates { - isDupe, err := b.checkDuplicateListen(l) + isDupe, err := b.checkDuplicateListen(ctx, l) p.Import.Elapsed += 1 progress <- p if err != nil { @@ -182,7 +185,7 @@ func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, impo } if len(submission.Payload) > 0 { - _, err := b.client.SubmitListens(submission) + _, err := b.client.SubmitListens(ctx, submission) if err != nil { return importResult, err } @@ -199,12 +202,13 @@ func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, impo } func (b *ListenBrainzApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { + ctx := context.TODO() exportChan := make(chan models.LovesResult) p := models.TransferProgress{ Export: &models.Progress{}, } - go b.exportLoves(oldestTimestamp, exportChan) + go b.exportLoves(ctx, oldestTimestamp, exportChan) for existingLoves := range exportChan { if existingLoves.Error != nil { p.Export.Abort() @@ -224,14 +228,14 @@ func (b *ListenBrainzApiBackend) ExportLoves(oldestTimestamp time.Time, results progress <- p } -func (b *ListenBrainzApiBackend) exportLoves(oldestTimestamp time.Time, results chan models.LovesResult) { +func (b *ListenBrainzApiBackend) exportLoves(ctx context.Context, oldestTimestamp time.Time, results chan models.LovesResult) { offset := 0 defer close(results) loves := make(models.LovesList, 0, 2*MaxItemsPerGet) out: for { - result, err := b.client.GetFeedback(b.username, 1, offset) + result, err := b.client.GetFeedback(ctx, b.username, 1, offset) if err != nil { results <- models.LovesResult{Error: err} return @@ -272,9 +276,10 @@ out: } func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { + ctx := context.TODO() if len(b.existingMBIDs) == 0 { existingLovesChan := make(chan models.LovesResult) - go b.exportLoves(time.Unix(0, 0), existingLovesChan) + go b.exportLoves(ctx, time.Unix(0, 0), existingLovesChan) // TODO: Store MBIDs directly b.existingMBIDs = make(map[mbtypes.MBID]bool, MaxItemsPerGet) @@ -303,7 +308,7 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe } if recordingMBID == "" { - lookup, err := b.client.Lookup(love.TrackName, love.ArtistName()) + lookup, err := b.client.Lookup(ctx, love.TrackName, love.ArtistName()) if err == nil { recordingMBID = lookup.RecordingMBID } @@ -315,7 +320,7 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe if b.existingMBIDs[recordingMBID] { ok = true } else { - resp, err := b.client.SendFeedback(Feedback{ + resp, err := b.client.SendFeedback(ctx, Feedback{ RecordingMBID: recordingMBID, Score: 1, }) @@ -351,7 +356,7 @@ var defaultDuration = time.Duration(3 * time.Minute) const trackSimilarityThreshold = 0.9 -func (b *ListenBrainzApiBackend) checkDuplicateListen(listen models.Listen) (bool, error) { +func (b *ListenBrainzApiBackend) checkDuplicateListen(ctx context.Context, listen models.Listen) (bool, error) { // Find listens duration := listen.Duration if duration == 0 { @@ -359,7 +364,7 @@ func (b *ListenBrainzApiBackend) checkDuplicateListen(listen models.Listen) (boo } minTime := listen.ListenedAt.Add(-duration) maxTime := listen.ListenedAt.Add(duration) - candidates, err := b.client.GetListens(b.username, maxTime, minTime) + candidates, err := b.client.GetListens(ctx, b.username, maxTime, minTime) if err != nil { return false, err } diff --git a/internal/backends/maloja/client.go b/internal/backends/maloja/client.go index 249819a..b80cb56 100644 --- a/internal/backends/maloja/client.go +++ b/internal/backends/maloja/client.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,6 +22,7 @@ THE SOFTWARE. package maloja import ( + "context" "errors" "strconv" @@ -48,9 +49,10 @@ func NewClient(serverURL string, token string) Client { } } -func (c Client) GetScrobbles(page int, perPage int) (result GetScrobblesResult, err error) { +func (c Client) GetScrobbles(ctx context.Context, page int, perPage int) (result GetScrobblesResult, err error) { const path = "/apis/mlj_1/scrobbles" response, err := c.HTTPClient.R(). + SetContext(ctx). SetQueryParams(map[string]string{ "page": strconv.Itoa(page), "perpage": strconv.Itoa(perPage), @@ -65,10 +67,11 @@ func (c Client) GetScrobbles(page int, perPage int) (result GetScrobblesResult, return } -func (c Client) NewScrobble(scrobble NewScrobble) (result NewScrobbleResult, err error) { +func (c Client) NewScrobble(ctx context.Context, scrobble NewScrobble) (result NewScrobbleResult, err error) { const path = "/apis/mlj_1/newscrobble" scrobble.Key = c.token response, err := c.HTTPClient.R(). + SetContext(ctx). SetBody(scrobble). SetResult(&result). Post(path) diff --git a/internal/backends/maloja/client_test.go b/internal/backends/maloja/client_test.go index 54316a8..415f911 100644 --- a/internal/backends/maloja/client_test.go +++ b/internal/backends/maloja/client_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,6 +22,7 @@ THE SOFTWARE. package maloja_test import ( + "context" "net/http" "testing" @@ -48,7 +49,8 @@ func TestGetScrobbles(t *testing.T) { "https://maloja.example.com/apis/mlj_1/scrobbles", "testdata/scrobbles.json") - result, err := client.GetScrobbles(0, 2) + ctx := context.Background() + result, err := client.GetScrobbles(ctx, 0, 2) require.NoError(t, err) assert := assert.New(t) @@ -69,12 +71,13 @@ func TestNewScrobble(t *testing.T) { url := server + "/apis/mlj_1/newscrobble" httpmock.RegisterResponder("POST", url, responder) + ctx := context.Background() scrobble := maloja.NewScrobble{ Title: "Oweynagat", Artist: "Dool", Time: 1699574369, } - result, err := client.NewScrobble(scrobble) + result, err := client.NewScrobble(ctx, scrobble) require.NoError(t, err) assert.Equal(t, "success", result.Status) diff --git a/internal/backends/maloja/maloja.go b/internal/backends/maloja/maloja.go index 8968942..4a4965e 100644 --- a/internal/backends/maloja/maloja.go +++ b/internal/backends/maloja/maloja.go @@ -17,6 +17,7 @@ Scotty. If not, see . package maloja import ( + "context" "errors" "sort" "strings" @@ -64,6 +65,7 @@ func (b *MalojaApiBackend) StartImport() error { return nil } func (b *MalojaApiBackend) FinishImport() error { return nil } func (b *MalojaApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { + ctx := context.TODO() page := 0 perPage := MaxItemsPerGet @@ -77,7 +79,7 @@ func (b *MalojaApiBackend) ExportListens(oldestTimestamp time.Time, results chan out: for { - result, err := b.client.GetScrobbles(page, perPage) + result, err := b.client.GetScrobbles(ctx, page, perPage) if err != nil { p.Export.Abort() progress <- p @@ -112,6 +114,8 @@ out: } func (b *MalojaApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { + ctx := context.TODO() + p := models.TransferProgress{}.FromImportResult(importResult, false) for _, listen := range export.Items { scrobble := NewScrobble{ @@ -124,7 +128,7 @@ func (b *MalojaApiBackend) ImportListens(export models.ListensResult, importResu Nofix: b.nofix, } - resp, err := b.client.NewScrobble(scrobble) + resp, err := b.client.NewScrobble(ctx, scrobble) if err != nil { return importResult, err } else if resp.Status != "success" { diff --git a/internal/backends/spotify/client.go b/internal/backends/spotify/client.go index ff2b0a3..94d50ac 100644 --- a/internal/backends/spotify/client.go +++ b/internal/backends/spotify/client.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -59,17 +59,18 @@ func NewClient(token oauth2.TokenSource) Client { } } -func (c Client) RecentlyPlayedAfter(after time.Time, limit int) (RecentlyPlayedResult, error) { - return c.recentlyPlayed(&after, nil, limit) +func (c Client) RecentlyPlayedAfter(ctx context.Context, after time.Time, limit int) (RecentlyPlayedResult, error) { + return c.recentlyPlayed(ctx, &after, nil, limit) } -func (c Client) RecentlyPlayedBefore(before time.Time, limit int) (RecentlyPlayedResult, error) { - return c.recentlyPlayed(nil, &before, limit) +func (c Client) RecentlyPlayedBefore(ctx context.Context, before time.Time, limit int) (RecentlyPlayedResult, error) { + return c.recentlyPlayed(ctx, nil, &before, limit) } -func (c Client) recentlyPlayed(after *time.Time, before *time.Time, limit int) (result RecentlyPlayedResult, err error) { +func (c Client) recentlyPlayed(ctx context.Context, after *time.Time, before *time.Time, limit int) (result RecentlyPlayedResult, err error) { const path = "/me/player/recently-played" request := c.HTTPClient.R(). + SetContext(ctx). SetQueryParam("limit", strconv.Itoa(limit)). SetResult(&result) if after != nil { @@ -85,9 +86,10 @@ func (c Client) recentlyPlayed(after *time.Time, before *time.Time, limit int) ( return } -func (c Client) UserTracks(offset int, limit int) (result TracksResult, err error) { +func (c Client) UserTracks(ctx context.Context, offset int, limit int) (result TracksResult, err error) { const path = "/me/tracks" response, err := c.HTTPClient.R(). + SetContext(ctx). SetQueryParams(map[string]string{ "offset": strconv.Itoa(offset), "limit": strconv.Itoa(limit), diff --git a/internal/backends/spotify/client_test.go b/internal/backends/spotify/client_test.go index 78ff063..8135e1d 100644 --- a/internal/backends/spotify/client_test.go +++ b/internal/backends/spotify/client_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Philipp Wolfer +Copyright © 2023-2025 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,6 +22,7 @@ THE SOFTWARE. package spotify_test import ( + "context" "net/http" "testing" "time" @@ -47,7 +48,8 @@ func TestRecentlyPlayedAfter(t *testing.T) { "https://api.spotify.com/v1/me/player/recently-played", "testdata/recently-played.json") - result, err := client.RecentlyPlayedAfter(time.Now(), 3) + ctx := context.Background() + result, err := client.RecentlyPlayedAfter(ctx, time.Now(), 3) require.NoError(t, err) assert := assert.New(t) @@ -67,7 +69,8 @@ func TestGetUserTracks(t *testing.T) { "https://api.spotify.com/v1/me/tracks", "testdata/user-tracks.json") - result, err := client.UserTracks(0, 2) + ctx := context.Background() + result, err := client.UserTracks(ctx, 0, 2) require.NoError(t, err) assert := assert.New(t) diff --git a/internal/backends/spotify/spotify.go b/internal/backends/spotify/spotify.go index 5d45087..73434b3 100644 --- a/internal/backends/spotify/spotify.go +++ b/internal/backends/spotify/spotify.go @@ -18,6 +18,7 @@ Scotty. If not, see . package spotify import ( + "context" "math" "net/url" "sort" @@ -96,6 +97,7 @@ func (b *SpotifyApiBackend) OAuth2Setup(token oauth2.TokenSource) error { } func (b *SpotifyApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { + ctx := context.TODO() startTime := time.Now() minTime := oldestTimestamp @@ -107,7 +109,7 @@ func (b *SpotifyApiBackend) ExportListens(oldestTimestamp time.Time, results cha } for { - result, err := b.client.RecentlyPlayedAfter(minTime, MaxItemsPerGet) + result, err := b.client.RecentlyPlayedAfter(ctx, minTime, MaxItemsPerGet) if err != nil { p.Export.Abort() progress <- p @@ -163,6 +165,7 @@ func (b *SpotifyApiBackend) ExportListens(oldestTimestamp time.Time, results cha } func (b *SpotifyApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { + ctx := context.TODO() // Choose a high offset, we attempt to search the loves backwards starting // at the oldest one. offset := math.MaxInt32 @@ -178,7 +181,7 @@ func (b *SpotifyApiBackend) ExportLoves(oldestTimestamp time.Time, results chan out: for { - result, err := b.client.UserTracks(offset, perPage) + result, err := b.client.UserTracks(ctx, offset, perPage) if err != nil { p.Export.Abort() progress <- p