mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
add tests, add count retrieval
This commit is contained in:
parent
d1f963b0f8
commit
6b72ce3c0d
@ -19,6 +19,11 @@ type ScrobbleOptions struct {
|
||||
MaxItems int `json:"maxItems"`
|
||||
}
|
||||
|
||||
type ScrobbleCountOptions struct {
|
||||
FromTimestamp *int64 `json:"fromTimestamp,omitempty"`
|
||||
ToTimestamp *int64 `json:"toTimestamp,omitempty"`
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverService allows a plugin to retrieve scrobbles for one or more authorized users.
|
||||
// It will only provide the media_file ID and submission time, which can be combined with the MatcherService
|
||||
// to fetch deduped tracks
|
||||
@ -35,4 +40,7 @@ type ScrobbleRetrieverService interface {
|
||||
|
||||
//nd:hostfunc
|
||||
GetScrobbles(ctx context.Context, username string, options ScrobbleOptions) (*ScrobbleList, error)
|
||||
|
||||
//nd:hostfunc
|
||||
GetScrobbleCount(ctx context.Context, username string, options ScrobbleCountOptions) (int64, error)
|
||||
}
|
||||
|
||||
@ -9,17 +9,6 @@ import (
|
||||
extism "github.com/extism/go-sdk"
|
||||
)
|
||||
|
||||
// ScrobbleRetrieverGetLastTimestampRequest is the request type for ScrobbleRetriever.GetLastTimestamp.
|
||||
type ScrobbleRetrieverGetLastTimestampRequest struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetLastTimestampResponse is the response type for ScrobbleRetriever.GetLastTimestamp.
|
||||
type ScrobbleRetrieverGetLastTimestampResponse struct {
|
||||
Result *int64 `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetFirstTimestampRequest is the request type for ScrobbleRetriever.GetFirstTimestamp.
|
||||
type ScrobbleRetrieverGetFirstTimestampRequest struct {
|
||||
Username string `json:"username"`
|
||||
@ -31,6 +20,17 @@ type ScrobbleRetrieverGetFirstTimestampResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetLastTimestampRequest is the request type for ScrobbleRetriever.GetLastTimestamp.
|
||||
type ScrobbleRetrieverGetLastTimestampRequest struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetLastTimestampResponse is the response type for ScrobbleRetriever.GetLastTimestamp.
|
||||
type ScrobbleRetrieverGetLastTimestampResponse struct {
|
||||
Result *int64 `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetScrobblesRequest is the request type for ScrobbleRetriever.GetScrobbles.
|
||||
type ScrobbleRetrieverGetScrobblesRequest struct {
|
||||
Username string `json:"username"`
|
||||
@ -43,50 +43,29 @@ type ScrobbleRetrieverGetScrobblesResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetScrobbleCountRequest is the request type for ScrobbleRetriever.GetScrobbleCount.
|
||||
type ScrobbleRetrieverGetScrobbleCountRequest struct {
|
||||
Username string `json:"username"`
|
||||
Options ScrobbleCountOptions `json:"options"`
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetScrobbleCountResponse is the response type for ScrobbleRetriever.GetScrobbleCount.
|
||||
type ScrobbleRetrieverGetScrobbleCountResponse struct {
|
||||
Result int64 `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// RegisterScrobbleRetrieverHostFunctions registers ScrobbleRetriever service host functions.
|
||||
// The returned host functions should be added to the plugin's configuration.
|
||||
func RegisterScrobbleRetrieverHostFunctions(service ScrobbleRetrieverService) []extism.HostFunction {
|
||||
return []extism.HostFunction{
|
||||
newScrobbleRetrieverGetLastTimestampHostFunction(service),
|
||||
newScrobbleRetrieverGetFirstTimestampHostFunction(service),
|
||||
newScrobbleRetrieverGetLastTimestampHostFunction(service),
|
||||
newScrobbleRetrieverGetScrobblesHostFunction(service),
|
||||
newScrobbleRetrieverGetScrobbleCountHostFunction(service),
|
||||
}
|
||||
}
|
||||
|
||||
func newScrobbleRetrieverGetLastTimestampHostFunction(service ScrobbleRetrieverService) extism.HostFunction {
|
||||
return extism.NewHostFunctionWithStack(
|
||||
"scrobbleretriever_getlasttimestamp",
|
||||
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
|
||||
// Read JSON request from plugin memory
|
||||
reqBytes, err := p.ReadBytes(stack[0])
|
||||
if err != nil {
|
||||
scrobbleretrieverWriteError(p, stack, err)
|
||||
return
|
||||
}
|
||||
var req ScrobbleRetrieverGetLastTimestampRequest
|
||||
if err := json.Unmarshal(reqBytes, &req); err != nil {
|
||||
scrobbleretrieverWriteError(p, stack, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Call the service method
|
||||
result, svcErr := service.GetLastTimestamp(ctx, req.Username)
|
||||
if svcErr != nil {
|
||||
scrobbleretrieverWriteError(p, stack, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Write JSON response to plugin memory
|
||||
resp := ScrobbleRetrieverGetLastTimestampResponse{
|
||||
Result: result,
|
||||
}
|
||||
scrobbleretrieverWriteResponse(p, stack, resp)
|
||||
},
|
||||
[]extism.ValueType{extism.ValueTypePTR},
|
||||
[]extism.ValueType{extism.ValueTypePTR},
|
||||
)
|
||||
}
|
||||
|
||||
func newScrobbleRetrieverGetFirstTimestampHostFunction(service ScrobbleRetrieverService) extism.HostFunction {
|
||||
return extism.NewHostFunctionWithStack(
|
||||
"scrobbleretriever_getfirsttimestamp",
|
||||
@ -121,6 +100,40 @@ func newScrobbleRetrieverGetFirstTimestampHostFunction(service ScrobbleRetriever
|
||||
)
|
||||
}
|
||||
|
||||
func newScrobbleRetrieverGetLastTimestampHostFunction(service ScrobbleRetrieverService) extism.HostFunction {
|
||||
return extism.NewHostFunctionWithStack(
|
||||
"scrobbleretriever_getlasttimestamp",
|
||||
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
|
||||
// Read JSON request from plugin memory
|
||||
reqBytes, err := p.ReadBytes(stack[0])
|
||||
if err != nil {
|
||||
scrobbleretrieverWriteError(p, stack, err)
|
||||
return
|
||||
}
|
||||
var req ScrobbleRetrieverGetLastTimestampRequest
|
||||
if err := json.Unmarshal(reqBytes, &req); err != nil {
|
||||
scrobbleretrieverWriteError(p, stack, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Call the service method
|
||||
result, svcErr := service.GetLastTimestamp(ctx, req.Username)
|
||||
if svcErr != nil {
|
||||
scrobbleretrieverWriteError(p, stack, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Write JSON response to plugin memory
|
||||
resp := ScrobbleRetrieverGetLastTimestampResponse{
|
||||
Result: result,
|
||||
}
|
||||
scrobbleretrieverWriteResponse(p, stack, resp)
|
||||
},
|
||||
[]extism.ValueType{extism.ValueTypePTR},
|
||||
[]extism.ValueType{extism.ValueTypePTR},
|
||||
)
|
||||
}
|
||||
|
||||
func newScrobbleRetrieverGetScrobblesHostFunction(service ScrobbleRetrieverService) extism.HostFunction {
|
||||
return extism.NewHostFunctionWithStack(
|
||||
"scrobbleretriever_getscrobbles",
|
||||
@ -155,6 +168,40 @@ func newScrobbleRetrieverGetScrobblesHostFunction(service ScrobbleRetrieverServi
|
||||
)
|
||||
}
|
||||
|
||||
func newScrobbleRetrieverGetScrobbleCountHostFunction(service ScrobbleRetrieverService) extism.HostFunction {
|
||||
return extism.NewHostFunctionWithStack(
|
||||
"scrobbleretriever_getscrobblecount",
|
||||
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
|
||||
// Read JSON request from plugin memory
|
||||
reqBytes, err := p.ReadBytes(stack[0])
|
||||
if err != nil {
|
||||
scrobbleretrieverWriteError(p, stack, err)
|
||||
return
|
||||
}
|
||||
var req ScrobbleRetrieverGetScrobbleCountRequest
|
||||
if err := json.Unmarshal(reqBytes, &req); err != nil {
|
||||
scrobbleretrieverWriteError(p, stack, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Call the service method
|
||||
result, svcErr := service.GetScrobbleCount(ctx, req.Username, req.Options)
|
||||
if svcErr != nil {
|
||||
scrobbleretrieverWriteError(p, stack, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Write JSON response to plugin memory
|
||||
resp := ScrobbleRetrieverGetScrobbleCountResponse{
|
||||
Result: result,
|
||||
}
|
||||
scrobbleretrieverWriteResponse(p, stack, resp)
|
||||
},
|
||||
[]extism.ValueType{extism.ValueTypePTR},
|
||||
[]extism.ValueType{extism.ValueTypePTR},
|
||||
)
|
||||
}
|
||||
|
||||
// scrobbleretrieverWriteResponse writes a JSON response to plugin memory.
|
||||
func scrobbleretrieverWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
|
||||
respBytes, err := json.Marshal(resp)
|
||||
|
||||
@ -64,20 +64,20 @@ func (s *scrobbleRetrieverServiceImpl) GetScrobbles(ctx context.Context, usernam
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.MaxItems == 0 {
|
||||
if options.MaxItems < 1 || options.MaxItems > 5000 {
|
||||
options.MaxItems = 5000
|
||||
}
|
||||
|
||||
// Fetch one more item than requested. The last item is the next timestamp to fetch
|
||||
options.MaxItems += 1
|
||||
|
||||
var filters squirrel.Sqlizer
|
||||
var filters squirrel.And
|
||||
if options.FromTimestamp != nil {
|
||||
filters = squirrel.GtOrEq{"submission_time": *options.FromTimestamp}
|
||||
filters = append(filters, squirrel.GtOrEq{"submission_time": *options.FromTimestamp})
|
||||
}
|
||||
|
||||
if options.ToTimestamp != nil {
|
||||
filters = squirrel.And{filters, squirrel.LtOrEq{"submission_time": *options.ToTimestamp}}
|
||||
filters = append(filters, squirrel.LtOrEq{"submission_time": *options.ToTimestamp})
|
||||
}
|
||||
|
||||
var order string
|
||||
@ -99,13 +99,18 @@ func (s *scrobbleRetrieverServiceImpl) GetScrobbles(ctx context.Context, usernam
|
||||
}
|
||||
|
||||
var nextTimestamp *int64
|
||||
var targetLen int
|
||||
|
||||
if len(scrobbles) == options.MaxItems {
|
||||
nextTimestamp = &scrobbles[options.MaxItems-1].SubmissionTime
|
||||
targetLen = options.MaxItems - 1
|
||||
} else {
|
||||
targetLen = len(scrobbles)
|
||||
}
|
||||
|
||||
scrobbleRefs := make([]host.ScrobbleRef, options.MaxItems-1)
|
||||
for idx := range scrobbleRefs {
|
||||
scrobbleRefs := make([]host.ScrobbleRef, targetLen)
|
||||
|
||||
for idx := range targetLen {
|
||||
scrobbleRefs[idx].ID = scrobbles[idx].ID
|
||||
scrobbleRefs[idx].MediaFileID = scrobbles[idx].MediaFileID
|
||||
scrobbleRefs[idx].SubmissionTime = scrobbles[idx].SubmissionTime
|
||||
@ -119,4 +124,30 @@ func (s *scrobbleRetrieverServiceImpl) GetScrobbles(ctx context.Context, usernam
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (s *scrobbleRetrieverServiceImpl) GetScrobbleCount(ctx context.Context, username string, options host.ScrobbleCountOptions) (int64, error) {
|
||||
ctx, err := s.getUserContext(ctx, username)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var filters squirrel.And
|
||||
if options.FromTimestamp != nil {
|
||||
filters = append(filters, squirrel.GtOrEq{"submission_time": *options.FromTimestamp})
|
||||
}
|
||||
|
||||
if options.ToTimestamp != nil {
|
||||
filters = append(filters, squirrel.LtOrEq{"submission_time": *options.ToTimestamp})
|
||||
}
|
||||
|
||||
count, err := s.ds.Scrobble(ctx).CountAll(model.QueryOptions{
|
||||
Filters: filters,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
var _ host.ScrobbleRetrieverService = (*scrobbleRetrieverServiceImpl)(nil)
|
||||
|
||||
290
plugins/host_scrobbleretriever_test.go
Normal file
290
plugins/host_scrobbleretriever_test.go
Normal file
@ -0,0 +1,290 @@
|
||||
//go:build !windows
|
||||
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/db"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
"github.com/navidrome/navidrome/persistence"
|
||||
"github.com/navidrome/navidrome/plugins/host"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Scrobbble Retriever Host Function", Ordered, func() {
|
||||
var (
|
||||
manager *Manager
|
||||
tmpDir string
|
||||
dataStore *tests.MockDataStore
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
ctx := GinkgoT().Context()
|
||||
|
||||
var err error
|
||||
tmpDir, err = os.MkdirTemp("", "scrobble-retriever-test-*")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
conf.Server.DbPath = filepath.Join(tmpDir, "test-scanner.db?_journal_mode=WAL")
|
||||
|
||||
db.Init(ctx)
|
||||
DeferCleanup(func() {
|
||||
Expect(tests.ClearDB()).To(Succeed())
|
||||
})
|
||||
dataStore = &tests.MockDataStore{RealDS: persistence.New(db.Db())}
|
||||
|
||||
// Copy test plugin to temp dir
|
||||
srcPath := filepath.Join(testdataDir, "test-scrobble-retriever"+PackageExtension)
|
||||
destPath := filepath.Join(tmpDir, "test-scrobble-retriever"+PackageExtension)
|
||||
data, err := os.ReadFile(srcPath)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = os.WriteFile(destPath, data, 0600)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Setup config
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
conf.Server.Plugins.Enabled = true
|
||||
conf.Server.Plugins.Folder = conf.NewDir(tmpDir)
|
||||
conf.Server.Plugins.AutoReload = false
|
||||
|
||||
userRepo := dataStore.User(ctx)
|
||||
// Add test users
|
||||
_ = userRepo.Put(&model.User{
|
||||
ID: "user1",
|
||||
UserName: "testuser",
|
||||
IsAdmin: false,
|
||||
})
|
||||
_ = userRepo.Put(&model.User{
|
||||
ID: "admin1",
|
||||
UserName: "adminuser",
|
||||
IsAdmin: true,
|
||||
})
|
||||
|
||||
err = dataStore.MediaFile(ctx).Put(&model.MediaFile{ID: "1", LibraryID: 1})
|
||||
Expect(err).To(BeNil())
|
||||
err = dataStore.MediaFile(ctx).Put(&model.MediaFile{ID: "2", LibraryID: 1})
|
||||
Expect(err).To(BeNil())
|
||||
err = dataStore.MediaFile(ctx).Put(&model.MediaFile{ID: "3", LibraryID: 1})
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
scrobbleCtx := request.WithUser(GinkgoT().Context(), model.User{ID: "admin1", UserName: "adminuser"})
|
||||
|
||||
scrobbleRepo := dataStore.Scrobble(scrobbleCtx)
|
||||
err = scrobbleRepo.RecordScrobble("1", time.Unix(0, 0))
|
||||
Expect(err).To(BeNil())
|
||||
err = scrobbleRepo.RecordScrobble("2", time.Unix(1, 0))
|
||||
Expect(err).To(BeNil())
|
||||
err = scrobbleRepo.RecordScrobble("3", time.Unix(2, 0))
|
||||
Expect(err).To(BeNil())
|
||||
err = scrobbleRepo.RecordScrobble("1", time.Unix(2, 0))
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// Create and configure manager
|
||||
manager = &Manager{
|
||||
plugins: make(map[string]*plugin),
|
||||
ds: dataStore,
|
||||
}
|
||||
router := &fakeSubsonicRouter{}
|
||||
manager.SetSubsonicRouter(router)
|
||||
|
||||
// Pre-enable the plugin in the mock repo so it loads on startup
|
||||
// Compute SHA256 of the plugin file to match what syncPlugins will compute
|
||||
pluginPath := filepath.Join(tmpDir, "test-scrobble-retriever"+PackageExtension)
|
||||
wasmData, err := os.ReadFile(pluginPath)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
hash := sha256.Sum256(wasmData)
|
||||
hashHex := hex.EncodeToString(hash[:])
|
||||
|
||||
dataStore.MockedPlugin = tests.CreateMockPluginRepo()
|
||||
|
||||
mockPluginRepo := dataStore.Plugin(GinkgoT().Context()).(*tests.MockPluginRepo)
|
||||
mockPluginRepo.Permitted = true
|
||||
enabledPlugin := model.Plugin{
|
||||
ID: "test-scrobble-retriever",
|
||||
Path: pluginPath,
|
||||
SHA256: hashHex,
|
||||
Enabled: true,
|
||||
Users: `["user1","admin1"]`,
|
||||
}
|
||||
mockPluginRepo.SetData(model.Plugins{enabledPlugin})
|
||||
|
||||
// Start the manager
|
||||
err = manager.Start(GinkgoT().Context())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
DeferCleanup(func() {
|
||||
_ = manager.Stop()
|
||||
_ = os.RemoveAll(tmpDir)
|
||||
})
|
||||
})
|
||||
|
||||
Describe("no items", func() {
|
||||
var plugin *plugin
|
||||
|
||||
BeforeEach(func() {
|
||||
manager.mu.RLock()
|
||||
plugin = manager.plugins["test-scrobble-retriever"]
|
||||
manager.mu.RUnlock()
|
||||
Expect(plugin).ToNot(BeNil())
|
||||
})
|
||||
|
||||
It("calls get first timestamp", func() {
|
||||
instance, err := plugin.instance(GinkgoT().Context())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer instance.Close(GinkgoT().Context())
|
||||
|
||||
exit, output, err := instance.Call("call_get_first_timestamp", []byte("testuser"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(exit).To(Equal(uint32(0)))
|
||||
|
||||
Expect(output).To(Equal([]byte("{\"timestamp\":null}")))
|
||||
})
|
||||
|
||||
It("calls get last timestamp", func() {
|
||||
instance, err := plugin.instance(GinkgoT().Context())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer instance.Close(GinkgoT().Context())
|
||||
|
||||
exit, output, err := instance.Call("call_get_last_timestamp", []byte("testuser"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(exit).To(Equal(uint32(0)))
|
||||
|
||||
Expect(output).To(Equal([]byte("{\"timestamp\":null}")))
|
||||
})
|
||||
|
||||
It("calls scrobbles", func() {
|
||||
instance, err := plugin.instance(GinkgoT().Context())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer instance.Close(GinkgoT().Context())
|
||||
|
||||
exit, output, err := instance.Call("call_get_scrobbles", []byte(`{"username":"testuser"}`))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(exit).To(Equal(uint32(0)))
|
||||
|
||||
Expect(output).To(Equal([]byte(`{"scrobbles":[],"nextTimestamp":null}`)))
|
||||
})
|
||||
|
||||
It("calls get scrobble count", func() {
|
||||
instance, err := plugin.instance(GinkgoT().Context())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer instance.Close(GinkgoT().Context())
|
||||
|
||||
exit, output, err := instance.Call("call_get_scrobbles_count", []byte(`{"username":"testuser"}`))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(exit).To(Equal(uint32(0)))
|
||||
Expect(output).To(Equal([]byte("0")))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("with items", func() {
|
||||
var plugin *plugin
|
||||
|
||||
p := func(val int64) *int64 {
|
||||
return &val
|
||||
}
|
||||
|
||||
scrobbles := []host.ScrobbleRef{
|
||||
{ID: 1, MediaFileID: "1", SubmissionTime: 0},
|
||||
{ID: 2, MediaFileID: "2", SubmissionTime: 1},
|
||||
{ID: 3, MediaFileID: "3", SubmissionTime: 2},
|
||||
{ID: 4, MediaFileID: "1", SubmissionTime: 2},
|
||||
}
|
||||
|
||||
scrobblesReversed := make([]host.ScrobbleRef, 4)
|
||||
|
||||
BeforeAll(func() {
|
||||
for idx := range scrobbles {
|
||||
scrobblesReversed[3-idx] = scrobbles[idx]
|
||||
}
|
||||
})
|
||||
|
||||
BeforeEach(func() {
|
||||
manager.mu.RLock()
|
||||
plugin = manager.plugins["test-scrobble-retriever"]
|
||||
manager.mu.RUnlock()
|
||||
Expect(plugin).ToNot(BeNil())
|
||||
})
|
||||
|
||||
It("calls get first timestamp", func() {
|
||||
instance, err := plugin.instance(GinkgoT().Context())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer instance.Close(GinkgoT().Context())
|
||||
|
||||
exit, output, err := instance.Call("call_get_first_timestamp", []byte("adminuser"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(exit).To(Equal(uint32(0)))
|
||||
|
||||
Expect(output).To(Equal([]byte("{\"timestamp\":0}")))
|
||||
})
|
||||
|
||||
It("calls get last timestamp", func() {
|
||||
instance, err := plugin.instance(GinkgoT().Context())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer instance.Close(GinkgoT().Context())
|
||||
|
||||
exit, output, err := instance.Call("call_get_last_timestamp", []byte("adminuser"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(exit).To(Equal(uint32(0)))
|
||||
|
||||
Expect(output).To(Equal([]byte("{\"timestamp\":2}")))
|
||||
})
|
||||
|
||||
DescribeTable("getScrobbles", func(params string, scrobbles []host.ScrobbleRef, timestamp *int64) {
|
||||
instance, err := plugin.instance(GinkgoT().Context())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer instance.Close(GinkgoT().Context())
|
||||
|
||||
exit, output, err := instance.Call("call_get_scrobbles", []byte(params))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(exit).To(Equal(uint32(0)))
|
||||
|
||||
var scrobbleList host.ScrobbleList
|
||||
Expect(json.Unmarshal(output, &scrobbleList)).To(Succeed())
|
||||
Expect(scrobbleList).To(Equal(host.ScrobbleList{
|
||||
Scrobbles: scrobbles,
|
||||
NextTimestamp: timestamp,
|
||||
}))
|
||||
},
|
||||
Entry("calls scrobbles in ascending order", `{"username":"adminuser"}`, scrobbles, nil),
|
||||
Entry("calls scrobbles in ascending order, beyond range", `{"username":"adminuser","fromTimestamp":-1, "toTimestamp": 1000}`, scrobbles, nil),
|
||||
Entry("calls subset of scrobbles in ascending order, next timestamp", `{"username":"adminuser","maxItems":2}`, scrobbles[:2], p(2)),
|
||||
Entry("calls subset of scrobbles in ascending order, with offset next timestamp", `{"username":"adminuser","maxItems":2,"fromTimestamp":1}`, scrobbles[1:3], p(2)),
|
||||
Entry("calls subset of scrobbles in ascending order, from and to timestamp", `{"username":"adminuser","toTimestamp":2,"fromTimestamp":1}`, scrobbles[1:], nil),
|
||||
Entry("calls in reverse order, full", `{"username":"adminuser","toTimestamp":2}`, scrobblesReversed, nil),
|
||||
Entry("calls in reverse order, with count", `{"username":"adminuser","toTimestamp":2, "maxItems": 3}`, scrobblesReversed[:3], p(0)),
|
||||
Entry("calls in reverse order, with count of 1", `{"username":"adminuser","toTimestamp":2, "maxItems": 1}`, scrobblesReversed[:1], p(2)),
|
||||
)
|
||||
|
||||
DescribeTable("GetScrobblesCount", func(params string, count int) {
|
||||
instance, err := plugin.instance(GinkgoT().Context())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer instance.Close(GinkgoT().Context())
|
||||
|
||||
exit, output, err := instance.Call("call_get_scrobbles_count", []byte(params))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(exit).To(Equal(uint32(0)))
|
||||
|
||||
value, err := strconv.ParseInt(string(output), 10, 64)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(value).To(Equal(int64(count)))
|
||||
},
|
||||
Entry("gets all scrobbles", `{"username":"adminuser"}`, 4),
|
||||
Entry("gets two scrobbles ascending", `{"username":"adminuser", "fromTimestamp": 2}`, 2),
|
||||
Entry("gets one scrobble descending", `{"username":"adminuser", "toTimestamp": 0}`, 1),
|
||||
Entry("filters upper and bottom", `{"username":"adminuser", "fromTimestamp": 1, "toTimestamp": 1}`, 1),
|
||||
Entry("accepts filter out of range", `{"username":"adminuser", "fromTimestamp": -1, "toTimestamp": 1000}`, 4),
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -14,6 +14,12 @@ import (
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
||||
)
|
||||
|
||||
// ScrobbleCountOptions represents the ScrobbleCountOptions data structure.
|
||||
type ScrobbleCountOptions struct {
|
||||
FromTimestamp *int64 `json:"fromTimestamp"`
|
||||
ToTimestamp *int64 `json:"toTimestamp"`
|
||||
}
|
||||
|
||||
// ScrobbleList represents the ScrobbleList data structure.
|
||||
type ScrobbleList struct {
|
||||
Scrobbles []ScrobbleRef `json:"scrobbles"`
|
||||
@ -34,29 +40,25 @@ type ScrobbleRef struct {
|
||||
SubmissionTime int64 `json:"submissionTime"`
|
||||
}
|
||||
|
||||
// scrobbleretriever_getlasttimestamp is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user scrobbleretriever_getlasttimestamp
|
||||
func scrobbleretriever_getlasttimestamp(uint64) uint64
|
||||
|
||||
// scrobbleretriever_getfirsttimestamp is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user scrobbleretriever_getfirsttimestamp
|
||||
func scrobbleretriever_getfirsttimestamp(uint64) uint64
|
||||
|
||||
// scrobbleretriever_getlasttimestamp is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user scrobbleretriever_getlasttimestamp
|
||||
func scrobbleretriever_getlasttimestamp(uint64) uint64
|
||||
|
||||
// scrobbleretriever_getscrobbles is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user scrobbleretriever_getscrobbles
|
||||
func scrobbleretriever_getscrobbles(uint64) uint64
|
||||
|
||||
type scrobbleRetrieverGetLastTimestampRequest struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type scrobbleRetrieverGetLastTimestampResponse struct {
|
||||
Result *int64 `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
// scrobbleretriever_getscrobblecount is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user scrobbleretriever_getscrobblecount
|
||||
func scrobbleretriever_getscrobblecount(uint64) uint64
|
||||
|
||||
type scrobbleRetrieverGetFirstTimestampRequest struct {
|
||||
Username string `json:"username"`
|
||||
@ -67,6 +69,15 @@ type scrobbleRetrieverGetFirstTimestampResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type scrobbleRetrieverGetLastTimestampRequest struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type scrobbleRetrieverGetLastTimestampResponse struct {
|
||||
Result *int64 `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type scrobbleRetrieverGetScrobblesRequest struct {
|
||||
Username string `json:"username"`
|
||||
Options ScrobbleOptions `json:"options"`
|
||||
@ -77,39 +88,14 @@ type scrobbleRetrieverGetScrobblesResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetLastTimestamp calls the scrobbleretriever_getlasttimestamp host function.
|
||||
// GetLastTimestamp returns the unix timestamp of the most recent scrobble for the user
|
||||
func ScrobbleRetrieverGetLastTimestamp(username string) (*int64, error) {
|
||||
// Marshal request to JSON
|
||||
req := scrobbleRetrieverGetLastTimestampRequest{
|
||||
Username: username,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
type scrobbleRetrieverGetScrobbleCountRequest struct {
|
||||
Username string `json:"username"`
|
||||
Options ScrobbleCountOptions `json:"options"`
|
||||
}
|
||||
|
||||
// Call the host function
|
||||
responsePtr := scrobbleretriever_getlasttimestamp(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response scrobbleRetrieverGetLastTimestampResponse
|
||||
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert Error field to Go error
|
||||
if response.Error != "" {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response.Result, nil
|
||||
type scrobbleRetrieverGetScrobbleCountResponse struct {
|
||||
Result int64 `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetFirstTimestamp calls the scrobbleretriever_getfirsttimestamp host function.
|
||||
@ -147,6 +133,41 @@ func ScrobbleRetrieverGetFirstTimestamp(username string) (*int64, error) {
|
||||
return response.Result, nil
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetLastTimestamp calls the scrobbleretriever_getlasttimestamp host function.
|
||||
// GetLastTimestamp returns the unix timestamp of the most recent scrobble for the user
|
||||
func ScrobbleRetrieverGetLastTimestamp(username string) (*int64, error) {
|
||||
// Marshal request to JSON
|
||||
req := scrobbleRetrieverGetLastTimestampRequest{
|
||||
Username: username,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := scrobbleretriever_getlasttimestamp(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response scrobbleRetrieverGetLastTimestampResponse
|
||||
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert Error field to Go error
|
||||
if response.Error != "" {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response.Result, nil
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetScrobbles calls the scrobbleretriever_getscrobbles host function.
|
||||
func ScrobbleRetrieverGetScrobbles(username string, options ScrobbleOptions) (*ScrobbleList, error) {
|
||||
// Marshal request to JSON
|
||||
@ -181,3 +202,38 @@ func ScrobbleRetrieverGetScrobbles(username string, options ScrobbleOptions) (*S
|
||||
|
||||
return response.Result, nil
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetScrobbleCount calls the scrobbleretriever_getscrobblecount host function.
|
||||
func ScrobbleRetrieverGetScrobbleCount(username string, options ScrobbleCountOptions) (int64, error) {
|
||||
// Marshal request to JSON
|
||||
req := scrobbleRetrieverGetScrobbleCountRequest{
|
||||
Username: username,
|
||||
Options: options,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := scrobbleretriever_getscrobblecount(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response scrobbleRetrieverGetScrobbleCountResponse
|
||||
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Convert Error field to Go error
|
||||
if response.Error != "" {
|
||||
return 0, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response.Result, nil
|
||||
}
|
||||
|
||||
@ -12,6 +12,12 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// ScrobbleCountOptions represents the ScrobbleCountOptions data structure.
|
||||
type ScrobbleCountOptions struct {
|
||||
FromTimestamp *int64 `json:"fromTimestamp"`
|
||||
ToTimestamp *int64 `json:"toTimestamp"`
|
||||
}
|
||||
|
||||
// ScrobbleList represents the ScrobbleList data structure.
|
||||
type ScrobbleList struct {
|
||||
Scrobbles []ScrobbleRef `json:"scrobbles"`
|
||||
@ -41,18 +47,6 @@ type mockScrobbleRetrieverService struct {
|
||||
// Use this to set expectations: host.ScrobbleRetrieverMock.On("MethodName", args...).Return(values...)
|
||||
var ScrobbleRetrieverMock = &mockScrobbleRetrieverService{}
|
||||
|
||||
// GetLastTimestamp is the mock method for ScrobbleRetrieverGetLastTimestamp.
|
||||
func (m *mockScrobbleRetrieverService) GetLastTimestamp(username string) (*int64, error) {
|
||||
args := m.Called(username)
|
||||
return args.Get(0).(*int64), args.Error(1)
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetLastTimestamp delegates to the mock instance.
|
||||
// GetLastTimestamp returns the unix timestamp of the most recent scrobble for the user
|
||||
func ScrobbleRetrieverGetLastTimestamp(username string) (*int64, error) {
|
||||
return ScrobbleRetrieverMock.GetLastTimestamp(username)
|
||||
}
|
||||
|
||||
// GetFirstTimestamp is the mock method for ScrobbleRetrieverGetFirstTimestamp.
|
||||
func (m *mockScrobbleRetrieverService) GetFirstTimestamp(username string) (*int64, error) {
|
||||
args := m.Called(username)
|
||||
@ -65,6 +59,18 @@ func ScrobbleRetrieverGetFirstTimestamp(username string) (*int64, error) {
|
||||
return ScrobbleRetrieverMock.GetFirstTimestamp(username)
|
||||
}
|
||||
|
||||
// GetLastTimestamp is the mock method for ScrobbleRetrieverGetLastTimestamp.
|
||||
func (m *mockScrobbleRetrieverService) GetLastTimestamp(username string) (*int64, error) {
|
||||
args := m.Called(username)
|
||||
return args.Get(0).(*int64), args.Error(1)
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetLastTimestamp delegates to the mock instance.
|
||||
// GetLastTimestamp returns the unix timestamp of the most recent scrobble for the user
|
||||
func ScrobbleRetrieverGetLastTimestamp(username string) (*int64, error) {
|
||||
return ScrobbleRetrieverMock.GetLastTimestamp(username)
|
||||
}
|
||||
|
||||
// GetScrobbles is the mock method for ScrobbleRetrieverGetScrobbles.
|
||||
func (m *mockScrobbleRetrieverService) GetScrobbles(username string, options ScrobbleOptions) (*ScrobbleList, error) {
|
||||
args := m.Called(username, options)
|
||||
@ -75,3 +81,14 @@ func (m *mockScrobbleRetrieverService) GetScrobbles(username string, options Scr
|
||||
func ScrobbleRetrieverGetScrobbles(username string, options ScrobbleOptions) (*ScrobbleList, error) {
|
||||
return ScrobbleRetrieverMock.GetScrobbles(username, options)
|
||||
}
|
||||
|
||||
// GetScrobbleCount is the mock method for ScrobbleRetrieverGetScrobbleCount.
|
||||
func (m *mockScrobbleRetrieverService) GetScrobbleCount(username string, options ScrobbleCountOptions) (int64, error) {
|
||||
args := m.Called(username, options)
|
||||
return args.Get(0).(int64), args.Error(1)
|
||||
}
|
||||
|
||||
// ScrobbleRetrieverGetScrobbleCount delegates to the mock instance.
|
||||
func ScrobbleRetrieverGetScrobbleCount(username string, options ScrobbleCountOptions) (int64, error) {
|
||||
return ScrobbleRetrieverMock.GetScrobbleCount(username, options)
|
||||
}
|
||||
|
||||
@ -6,6 +6,15 @@
|
||||
use extism_pdk::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScrobbleCountOptions {
|
||||
#[serde(default)]
|
||||
pub from_timestamp: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub to_timestamp: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScrobbleList {
|
||||
@ -34,13 +43,13 @@ pub struct ScrobbleRef {
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ScrobbleRetrieverGetLastTimestampRequest {
|
||||
struct ScrobbleRetrieverGetFirstTimestampRequest {
|
||||
username: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ScrobbleRetrieverGetLastTimestampResponse {
|
||||
struct ScrobbleRetrieverGetFirstTimestampResponse {
|
||||
#[serde(default)]
|
||||
result: Option<i64>,
|
||||
#[serde(default)]
|
||||
@ -49,13 +58,13 @@ struct ScrobbleRetrieverGetLastTimestampResponse {
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ScrobbleRetrieverGetFirstTimestampRequest {
|
||||
struct ScrobbleRetrieverGetLastTimestampRequest {
|
||||
username: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ScrobbleRetrieverGetFirstTimestampResponse {
|
||||
struct ScrobbleRetrieverGetLastTimestampResponse {
|
||||
#[serde(default)]
|
||||
result: Option<i64>,
|
||||
#[serde(default)]
|
||||
@ -78,35 +87,28 @@ struct ScrobbleRetrieverGetScrobblesResponse {
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[host_fn]
|
||||
extern "ExtismHost" {
|
||||
fn scrobbleretriever_getlasttimestamp(input: Json<ScrobbleRetrieverGetLastTimestampRequest>) -> Json<ScrobbleRetrieverGetLastTimestampResponse>;
|
||||
fn scrobbleretriever_getfirsttimestamp(input: Json<ScrobbleRetrieverGetFirstTimestampRequest>) -> Json<ScrobbleRetrieverGetFirstTimestampResponse>;
|
||||
fn scrobbleretriever_getscrobbles(input: Json<ScrobbleRetrieverGetScrobblesRequest>) -> Json<ScrobbleRetrieverGetScrobblesResponse>;
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ScrobbleRetrieverGetScrobbleCountRequest {
|
||||
username: String,
|
||||
options: ScrobbleCountOptions,
|
||||
}
|
||||
|
||||
/// GetLastTimestamp returns the unix timestamp of the most recent scrobble for the user
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `username` - String parameter.
|
||||
///
|
||||
/// # Returns
|
||||
/// The result value.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the host function call fails.
|
||||
pub fn get_last_timestamp(username: &str) -> Result<Option<i64>, Error> {
|
||||
let response = unsafe {
|
||||
scrobbleretriever_getlasttimestamp(Json(ScrobbleRetrieverGetLastTimestampRequest {
|
||||
username: username.to_owned(),
|
||||
}))?
|
||||
};
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ScrobbleRetrieverGetScrobbleCountResponse {
|
||||
#[serde(default)]
|
||||
result: i64,
|
||||
#[serde(default)]
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
if let Some(err) = response.0.error {
|
||||
return Err(Error::msg(err));
|
||||
}
|
||||
|
||||
Ok(response.0.result)
|
||||
#[host_fn]
|
||||
extern "ExtismHost" {
|
||||
fn scrobbleretriever_getfirsttimestamp(input: Json<ScrobbleRetrieverGetFirstTimestampRequest>) -> Json<ScrobbleRetrieverGetFirstTimestampResponse>;
|
||||
fn scrobbleretriever_getlasttimestamp(input: Json<ScrobbleRetrieverGetLastTimestampRequest>) -> Json<ScrobbleRetrieverGetLastTimestampResponse>;
|
||||
fn scrobbleretriever_getscrobbles(input: Json<ScrobbleRetrieverGetScrobblesRequest>) -> Json<ScrobbleRetrieverGetScrobblesResponse>;
|
||||
fn scrobbleretriever_getscrobblecount(input: Json<ScrobbleRetrieverGetScrobbleCountRequest>) -> Json<ScrobbleRetrieverGetScrobbleCountResponse>;
|
||||
}
|
||||
|
||||
/// GetFirstTimestamp returns the unix timestamp of the oldest scrobble for the user
|
||||
@ -133,6 +135,30 @@ pub fn get_first_timestamp(username: &str) -> Result<Option<i64>, Error> {
|
||||
Ok(response.0.result)
|
||||
}
|
||||
|
||||
/// GetLastTimestamp returns the unix timestamp of the most recent scrobble for the user
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `username` - String parameter.
|
||||
///
|
||||
/// # Returns
|
||||
/// The result value.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the host function call fails.
|
||||
pub fn get_last_timestamp(username: &str) -> Result<Option<i64>, Error> {
|
||||
let response = unsafe {
|
||||
scrobbleretriever_getlasttimestamp(Json(ScrobbleRetrieverGetLastTimestampRequest {
|
||||
username: username.to_owned(),
|
||||
}))?
|
||||
};
|
||||
|
||||
if let Some(err) = response.0.error {
|
||||
return Err(Error::msg(err));
|
||||
}
|
||||
|
||||
Ok(response.0.result)
|
||||
}
|
||||
|
||||
/// Calls the scrobbleretriever_getscrobbles host function.
|
||||
///
|
||||
/// # Arguments
|
||||
@ -158,3 +184,29 @@ pub fn get_scrobbles(username: &str, options: ScrobbleOptions) -> Result<Option<
|
||||
|
||||
Ok(response.0.result)
|
||||
}
|
||||
|
||||
/// Calls the scrobbleretriever_getscrobblecount host function.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `username` - String parameter.
|
||||
/// * `options` - ScrobbleCountOptions parameter.
|
||||
///
|
||||
/// # Returns
|
||||
/// The result value.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the host function call fails.
|
||||
pub fn get_scrobble_count(username: &str, options: ScrobbleCountOptions) -> Result<i64, Error> {
|
||||
let response = unsafe {
|
||||
scrobbleretriever_getscrobblecount(Json(ScrobbleRetrieverGetScrobbleCountRequest {
|
||||
username: username.to_owned(),
|
||||
options: options,
|
||||
}))?
|
||||
};
|
||||
|
||||
if let Some(err) = response.0.error {
|
||||
return Err(Error::msg(err));
|
||||
}
|
||||
|
||||
Ok(response.0.result)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module test-sonic-similarity
|
||||
module test-scrobble-retriever
|
||||
|
||||
go 1.25
|
||||
|
||||
|
||||
64
plugins/testdata/test-scrobble-retriever/main.go
vendored
64
plugins/testdata/test-scrobble-retriever/main.go
vendored
@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
||||
)
|
||||
@ -40,3 +42,65 @@ func callGetLastTimestamp() int32 {
|
||||
pdk.OutputJSON(TestScrobbleTimestampOutput{Timestamp: time})
|
||||
return 0
|
||||
}
|
||||
|
||||
type TestScrobbleOptions struct {
|
||||
Username string `json:"username"`
|
||||
FromTimestamp *int64 `json:"fromTimestamp,omitempty"`
|
||||
ToTimestamp *int64 `json:"toTimestamp,omitempty"`
|
||||
MaxItems int `json:"maxItems"`
|
||||
}
|
||||
|
||||
//go:wasmexport call_get_scrobbles
|
||||
func callGetScrobbles() int32 {
|
||||
var options TestScrobbleOptions
|
||||
err := pdk.InputJSON(&options)
|
||||
|
||||
if err != nil {
|
||||
pdk.SetErrorString("failed to deserialize input " + err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
scrobbles, err := host.ScrobbleRetrieverGetScrobbles(options.Username, host.ScrobbleOptions{
|
||||
FromTimestamp: options.FromTimestamp,
|
||||
ToTimestamp: options.ToTimestamp,
|
||||
MaxItems: options.MaxItems,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
pdk.SetErrorString("failed to call scrobble retriever api " + err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
pdk.OutputJSON(scrobbles)
|
||||
return 0
|
||||
}
|
||||
|
||||
type TestScrobbleCountOptions struct {
|
||||
Username string `json:"username"`
|
||||
FromTimestamp *int64 `json:"fromTimestamp,omitempty"`
|
||||
ToTimestamp *int64 `json:"toTimestamp,omitempty"`
|
||||
}
|
||||
|
||||
//go:wasmexport call_get_scrobbles_count
|
||||
func callGetScrobblesCount() int32 {
|
||||
var options TestScrobbleOptions
|
||||
err := pdk.InputJSON(&options)
|
||||
|
||||
if err != nil {
|
||||
pdk.SetErrorString("failed to deserialize input " + err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
count, err := host.ScrobbleRetrieverGetScrobbleCount(options.Username, host.ScrobbleCountOptions{
|
||||
FromTimestamp: options.FromTimestamp,
|
||||
ToTimestamp: options.ToTimestamp,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
pdk.SetErrorString("failed to call scrobble retriever api " + err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
pdk.OutputString(strconv.FormatInt(count, 10))
|
||||
return 0
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user