Make web service clients context aware

This commit is contained in:
Philipp Wolfer 2025-05-22 09:22:05 +02:00
parent adfe3f5771
commit d1642b7f1f
No known key found for this signature in database
GPG key ID: 8FDF744D4919943B
15 changed files with 128 additions and 76 deletions

View file

@ -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,

View file

@ -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)

View file

@ -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
}