mirror of
https://git.sr.ht/~phw/scotty
synced 2025-05-31 10:58:35 +02:00
Make web service clients context aware
This commit is contained in:
parent
adfe3f5771
commit
d1642b7f1f
15 changed files with 128 additions and 76 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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)
|
||||
|
|
|
@ -16,6 +16,7 @@ Scotty. If not, see <https://www.gnu.org/licenses/>.
|
|||
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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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",
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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)
|
||||
|
|
|
@ -17,6 +17,7 @@ Scotty. If not, see <https://www.gnu.org/licenses/>.
|
|||
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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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)
|
||||
|
|
|
@ -17,6 +17,7 @@ Scotty. If not, see <https://www.gnu.org/licenses/>.
|
|||
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
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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)
|
||||
|
|
|
@ -17,6 +17,7 @@ Scotty. If not, see <https://www.gnu.org/licenses/>.
|
|||
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" {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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)
|
||||
|
|
|
@ -18,6 +18,7 @@ Scotty. If not, see <https://www.gnu.org/licenses/>.
|
|||
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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue