mirror of
https://github.com/navidrome/navidrome.git
synced 2026-04-03 06:41:01 +00:00
test: make all test plugins use the PDK
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
f1b85e6a19
commit
f221c01bd9
9
plugins/testdata/test-artwork/go.mod
vendored
9
plugins/testdata/test-artwork/go.mod
vendored
@ -1,5 +1,10 @@
|
||||
module test-artwork
|
||||
|
||||
go 1.23
|
||||
go 1.24
|
||||
|
||||
require github.com/extism/go-pdk v1.1.3
|
||||
require (
|
||||
github.com/extism/go-pdk v1.1.3
|
||||
github.com/navidrome/navidrome/plugins/pdk/go/host v0.0.0
|
||||
)
|
||||
|
||||
replace github.com/navidrome/navidrome/plugins/pdk/go/host => ../../pdk/go/host
|
||||
|
||||
9
plugins/testdata/test-artwork/main.go
vendored
9
plugins/testdata/test-artwork/main.go
vendored
@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
pdk "github.com/extism/go-pdk"
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
||||
)
|
||||
|
||||
// TestInput is the input for nd_test_artwork callback.
|
||||
@ -37,28 +38,28 @@ func ndTestArtwork() int32 {
|
||||
|
||||
switch strings.ToLower(input.ArtworkType) {
|
||||
case "artist":
|
||||
resp, e := ArtworkGetArtistUrl(input.ID, input.Size)
|
||||
resp, e := host.ArtworkGetArtistUrl(input.ID, input.Size)
|
||||
if e != nil {
|
||||
err = e
|
||||
} else {
|
||||
url = resp.Url
|
||||
}
|
||||
case "album":
|
||||
resp, e := ArtworkGetAlbumUrl(input.ID, input.Size)
|
||||
resp, e := host.ArtworkGetAlbumUrl(input.ID, input.Size)
|
||||
if e != nil {
|
||||
err = e
|
||||
} else {
|
||||
url = resp.Url
|
||||
}
|
||||
case "track":
|
||||
resp, e := ArtworkGetTrackUrl(input.ID, input.Size)
|
||||
resp, e := host.ArtworkGetTrackUrl(input.ID, input.Size)
|
||||
if e != nil {
|
||||
err = e
|
||||
} else {
|
||||
url = resp.Url
|
||||
}
|
||||
case "playlist":
|
||||
resp, e := ArtworkGetPlaylistUrl(input.ID, input.Size)
|
||||
resp, e := host.ArtworkGetPlaylistUrl(input.ID, input.Size)
|
||||
if e != nil {
|
||||
err = e
|
||||
} else {
|
||||
|
||||
251
plugins/testdata/test-artwork/nd_host_artwork.go
vendored
251
plugins/testdata/test-artwork/nd_host_artwork.go
vendored
@ -1,251 +0,0 @@
|
||||
// Code generated by hostgen. DO NOT EDIT.
|
||||
//
|
||||
// This file contains client wrappers for the Artwork host service.
|
||||
// It is intended for use in Navidrome plugins built with TinyGo.
|
||||
//
|
||||
//go:build wasip1
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
// artwork_getartisturl is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user artwork_getartisturl
|
||||
func artwork_getartisturl(uint64) uint64
|
||||
|
||||
// artwork_getalbumurl is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user artwork_getalbumurl
|
||||
func artwork_getalbumurl(uint64) uint64
|
||||
|
||||
// artwork_gettrackurl is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user artwork_gettrackurl
|
||||
func artwork_gettrackurl(uint64) uint64
|
||||
|
||||
// artwork_getplaylisturl is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user artwork_getplaylisturl
|
||||
func artwork_getplaylisturl(uint64) uint64
|
||||
|
||||
// ArtworkGetArtistUrlRequest is the request type for Artwork.GetArtistUrl.
|
||||
type ArtworkGetArtistUrlRequest struct {
|
||||
Id string `json:"id"`
|
||||
Size int32 `json:"size"`
|
||||
}
|
||||
|
||||
// ArtworkGetArtistUrlResponse is the response type for Artwork.GetArtistUrl.
|
||||
type ArtworkGetArtistUrlResponse struct {
|
||||
Url string `json:"url,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ArtworkGetAlbumUrlRequest is the request type for Artwork.GetAlbumUrl.
|
||||
type ArtworkGetAlbumUrlRequest struct {
|
||||
Id string `json:"id"`
|
||||
Size int32 `json:"size"`
|
||||
}
|
||||
|
||||
// ArtworkGetAlbumUrlResponse is the response type for Artwork.GetAlbumUrl.
|
||||
type ArtworkGetAlbumUrlResponse struct {
|
||||
Url string `json:"url,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ArtworkGetTrackUrlRequest is the request type for Artwork.GetTrackUrl.
|
||||
type ArtworkGetTrackUrlRequest struct {
|
||||
Id string `json:"id"`
|
||||
Size int32 `json:"size"`
|
||||
}
|
||||
|
||||
// ArtworkGetTrackUrlResponse is the response type for Artwork.GetTrackUrl.
|
||||
type ArtworkGetTrackUrlResponse struct {
|
||||
Url string `json:"url,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ArtworkGetPlaylistUrlRequest is the request type for Artwork.GetPlaylistUrl.
|
||||
type ArtworkGetPlaylistUrlRequest struct {
|
||||
Id string `json:"id"`
|
||||
Size int32 `json:"size"`
|
||||
}
|
||||
|
||||
// ArtworkGetPlaylistUrlResponse is the response type for Artwork.GetPlaylistUrl.
|
||||
type ArtworkGetPlaylistUrlResponse struct {
|
||||
Url string `json:"url,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ArtworkGetArtistUrl calls the artwork_getartisturl host function.
|
||||
// GetArtistUrl generates a public URL for an artist's artwork.
|
||||
//
|
||||
// Parameters:
|
||||
// - id: The artist's unique identifier
|
||||
// - size: Desired image size in pixels (0 for original size)
|
||||
//
|
||||
// Returns the public URL for the artwork, or an error if generation fails.
|
||||
func ArtworkGetArtistUrl(id string, size int32) (*ArtworkGetArtistUrlResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := ArtworkGetArtistUrlRequest{
|
||||
Id: id,
|
||||
Size: size,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := artwork_getartisturl(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response ArtworkGetArtistUrlResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// ArtworkGetAlbumUrl calls the artwork_getalbumurl host function.
|
||||
// GetAlbumUrl generates a public URL for an album's artwork.
|
||||
//
|
||||
// Parameters:
|
||||
// - id: The album's unique identifier
|
||||
// - size: Desired image size in pixels (0 for original size)
|
||||
//
|
||||
// Returns the public URL for the artwork, or an error if generation fails.
|
||||
func ArtworkGetAlbumUrl(id string, size int32) (*ArtworkGetAlbumUrlResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := ArtworkGetAlbumUrlRequest{
|
||||
Id: id,
|
||||
Size: size,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := artwork_getalbumurl(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response ArtworkGetAlbumUrlResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// ArtworkGetTrackUrl calls the artwork_gettrackurl host function.
|
||||
// GetTrackUrl generates a public URL for a track's artwork.
|
||||
//
|
||||
// Parameters:
|
||||
// - id: The track's (media file) unique identifier
|
||||
// - size: Desired image size in pixels (0 for original size)
|
||||
//
|
||||
// Returns the public URL for the artwork, or an error if generation fails.
|
||||
func ArtworkGetTrackUrl(id string, size int32) (*ArtworkGetTrackUrlResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := ArtworkGetTrackUrlRequest{
|
||||
Id: id,
|
||||
Size: size,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := artwork_gettrackurl(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response ArtworkGetTrackUrlResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// ArtworkGetPlaylistUrl calls the artwork_getplaylisturl host function.
|
||||
// GetPlaylistUrl generates a public URL for a playlist's artwork.
|
||||
//
|
||||
// Parameters:
|
||||
// - id: The playlist's unique identifier
|
||||
// - size: Desired image size in pixels (0 for original size)
|
||||
//
|
||||
// Returns the public URL for the artwork, or an error if generation fails.
|
||||
func ArtworkGetPlaylistUrl(id string, size int32) (*ArtworkGetPlaylistUrlResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := ArtworkGetPlaylistUrlRequest{
|
||||
Id: id,
|
||||
Size: size,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := artwork_getplaylisturl(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response ArtworkGetPlaylistUrlResponse
|
||||
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, nil
|
||||
}
|
||||
9
plugins/testdata/test-cache-plugin/go.mod
vendored
9
plugins/testdata/test-cache-plugin/go.mod
vendored
@ -1,5 +1,10 @@
|
||||
module test-cache-plugin
|
||||
|
||||
go 1.23
|
||||
go 1.25
|
||||
|
||||
require github.com/extism/go-pdk v1.1.3
|
||||
require (
|
||||
github.com/extism/go-pdk v1.1.3
|
||||
github.com/navidrome/navidrome/plugins/pdk/go/host v0.0.0
|
||||
)
|
||||
|
||||
replace github.com/navidrome/navidrome/plugins/pdk/go/host => ../../pdk/go/host
|
||||
|
||||
21
plugins/testdata/test-cache-plugin/main.go
vendored
21
plugins/testdata/test-cache-plugin/main.go
vendored
@ -4,6 +4,7 @@ package main
|
||||
|
||||
import (
|
||||
pdk "github.com/extism/go-pdk"
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
||||
)
|
||||
|
||||
// TestCacheInput is the input for nd_test_cache callback.
|
||||
@ -40,7 +41,7 @@ func ndTestCache() int32 {
|
||||
|
||||
switch input.Operation {
|
||||
case "set_string":
|
||||
_, err := CacheSetString(input.Key, input.StringVal, input.TTLSeconds)
|
||||
_, err := host.CacheSetString(input.Key, input.StringVal, input.TTLSeconds)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestCacheOutput{Error: &errStr})
|
||||
@ -50,7 +51,7 @@ func ndTestCache() int32 {
|
||||
return 0
|
||||
|
||||
case "get_string":
|
||||
resp, err := CacheGetString(input.Key)
|
||||
resp, err := host.CacheGetString(input.Key)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestCacheOutput{Error: &errStr})
|
||||
@ -60,7 +61,7 @@ func ndTestCache() int32 {
|
||||
return 0
|
||||
|
||||
case "set_int":
|
||||
_, err := CacheSetInt(input.Key, input.IntVal, input.TTLSeconds)
|
||||
_, err := host.CacheSetInt(input.Key, input.IntVal, input.TTLSeconds)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestCacheOutput{Error: &errStr})
|
||||
@ -70,7 +71,7 @@ func ndTestCache() int32 {
|
||||
return 0
|
||||
|
||||
case "get_int":
|
||||
resp, err := CacheGetInt(input.Key)
|
||||
resp, err := host.CacheGetInt(input.Key)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestCacheOutput{Error: &errStr})
|
||||
@ -80,7 +81,7 @@ func ndTestCache() int32 {
|
||||
return 0
|
||||
|
||||
case "set_float":
|
||||
_, err := CacheSetFloat(input.Key, input.FloatVal, input.TTLSeconds)
|
||||
_, err := host.CacheSetFloat(input.Key, input.FloatVal, input.TTLSeconds)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestCacheOutput{Error: &errStr})
|
||||
@ -90,7 +91,7 @@ func ndTestCache() int32 {
|
||||
return 0
|
||||
|
||||
case "get_float":
|
||||
resp, err := CacheGetFloat(input.Key)
|
||||
resp, err := host.CacheGetFloat(input.Key)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestCacheOutput{Error: &errStr})
|
||||
@ -100,7 +101,7 @@ func ndTestCache() int32 {
|
||||
return 0
|
||||
|
||||
case "set_bytes":
|
||||
_, err := CacheSetBytes(input.Key, input.BytesVal, input.TTLSeconds)
|
||||
_, err := host.CacheSetBytes(input.Key, input.BytesVal, input.TTLSeconds)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestCacheOutput{Error: &errStr})
|
||||
@ -110,7 +111,7 @@ func ndTestCache() int32 {
|
||||
return 0
|
||||
|
||||
case "get_bytes":
|
||||
resp, err := CacheGetBytes(input.Key)
|
||||
resp, err := host.CacheGetBytes(input.Key)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestCacheOutput{Error: &errStr})
|
||||
@ -120,7 +121,7 @@ func ndTestCache() int32 {
|
||||
return 0
|
||||
|
||||
case "has":
|
||||
resp, err := CacheHas(input.Key)
|
||||
resp, err := host.CacheHas(input.Key)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestCacheOutput{Error: &errStr})
|
||||
@ -130,7 +131,7 @@ func ndTestCache() int32 {
|
||||
return 0
|
||||
|
||||
case "remove":
|
||||
_, err := CacheRemove(input.Key)
|
||||
_, err := host.CacheRemove(input.Key)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestCacheOutput{Error: &errStr})
|
||||
|
||||
602
plugins/testdata/test-cache-plugin/nd_host_cache.go
vendored
602
plugins/testdata/test-cache-plugin/nd_host_cache.go
vendored
@ -1,602 +0,0 @@
|
||||
// Code generated by hostgen. DO NOT EDIT.
|
||||
//
|
||||
// This file contains client wrappers for the Cache host service.
|
||||
// It is intended for use in Navidrome plugins built with TinyGo.
|
||||
//
|
||||
//go:build wasip1
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
// cache_setstring is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user cache_setstring
|
||||
func cache_setstring(uint64) uint64
|
||||
|
||||
// cache_getstring is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user cache_getstring
|
||||
func cache_getstring(uint64) uint64
|
||||
|
||||
// cache_setint is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user cache_setint
|
||||
func cache_setint(uint64) uint64
|
||||
|
||||
// cache_getint is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user cache_getint
|
||||
func cache_getint(uint64) uint64
|
||||
|
||||
// cache_setfloat is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user cache_setfloat
|
||||
func cache_setfloat(uint64) uint64
|
||||
|
||||
// cache_getfloat is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user cache_getfloat
|
||||
func cache_getfloat(uint64) uint64
|
||||
|
||||
// cache_setbytes is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user cache_setbytes
|
||||
func cache_setbytes(uint64) uint64
|
||||
|
||||
// cache_getbytes is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user cache_getbytes
|
||||
func cache_getbytes(uint64) uint64
|
||||
|
||||
// cache_has is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user cache_has
|
||||
func cache_has(uint64) uint64
|
||||
|
||||
// cache_remove is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user cache_remove
|
||||
func cache_remove(uint64) uint64
|
||||
|
||||
// CacheSetStringRequest is the request type for Cache.SetString.
|
||||
type CacheSetStringRequest struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
TtlSeconds int64 `json:"ttlSeconds"`
|
||||
}
|
||||
|
||||
// CacheSetStringResponse is the response type for Cache.SetString.
|
||||
type CacheSetStringResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CacheGetStringRequest is the request type for Cache.GetString.
|
||||
type CacheGetStringRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// CacheGetStringResponse is the response type for Cache.GetString.
|
||||
type CacheGetStringResponse struct {
|
||||
Value string `json:"value,omitempty"`
|
||||
Exists bool `json:"exists,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CacheSetIntRequest is the request type for Cache.SetInt.
|
||||
type CacheSetIntRequest struct {
|
||||
Key string `json:"key"`
|
||||
Value int64 `json:"value"`
|
||||
TtlSeconds int64 `json:"ttlSeconds"`
|
||||
}
|
||||
|
||||
// CacheSetIntResponse is the response type for Cache.SetInt.
|
||||
type CacheSetIntResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CacheGetIntRequest is the request type for Cache.GetInt.
|
||||
type CacheGetIntRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// CacheGetIntResponse is the response type for Cache.GetInt.
|
||||
type CacheGetIntResponse struct {
|
||||
Value int64 `json:"value,omitempty"`
|
||||
Exists bool `json:"exists,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CacheSetFloatRequest is the request type for Cache.SetFloat.
|
||||
type CacheSetFloatRequest struct {
|
||||
Key string `json:"key"`
|
||||
Value float64 `json:"value"`
|
||||
TtlSeconds int64 `json:"ttlSeconds"`
|
||||
}
|
||||
|
||||
// CacheSetFloatResponse is the response type for Cache.SetFloat.
|
||||
type CacheSetFloatResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CacheGetFloatRequest is the request type for Cache.GetFloat.
|
||||
type CacheGetFloatRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// CacheGetFloatResponse is the response type for Cache.GetFloat.
|
||||
type CacheGetFloatResponse struct {
|
||||
Value float64 `json:"value,omitempty"`
|
||||
Exists bool `json:"exists,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CacheSetBytesRequest is the request type for Cache.SetBytes.
|
||||
type CacheSetBytesRequest struct {
|
||||
Key string `json:"key"`
|
||||
Value []byte `json:"value"`
|
||||
TtlSeconds int64 `json:"ttlSeconds"`
|
||||
}
|
||||
|
||||
// CacheSetBytesResponse is the response type for Cache.SetBytes.
|
||||
type CacheSetBytesResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CacheGetBytesRequest is the request type for Cache.GetBytes.
|
||||
type CacheGetBytesRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// CacheGetBytesResponse is the response type for Cache.GetBytes.
|
||||
type CacheGetBytesResponse struct {
|
||||
Value []byte `json:"value,omitempty"`
|
||||
Exists bool `json:"exists,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CacheHasRequest is the request type for Cache.Has.
|
||||
type CacheHasRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// CacheHasResponse is the response type for Cache.Has.
|
||||
type CacheHasResponse struct {
|
||||
Exists bool `json:"exists,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CacheRemoveRequest is the request type for Cache.Remove.
|
||||
type CacheRemoveRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// CacheRemoveResponse is the response type for Cache.Remove.
|
||||
type CacheRemoveResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CacheSetString calls the cache_setstring host function.
|
||||
// SetString stores a string value in the cache.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The cache key (will be namespaced with plugin ID)
|
||||
// - value: The string value to store
|
||||
// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
|
||||
//
|
||||
// Returns an error if the operation fails.
|
||||
func CacheSetString(key string, value string, ttlSeconds int64) (*CacheSetStringResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := CacheSetStringRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
TtlSeconds: ttlSeconds,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := cache_setstring(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response CacheSetStringResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// CacheGetString calls the cache_getstring host function.
|
||||
// GetString retrieves a string value from the cache.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The cache key (will be namespaced with plugin ID)
|
||||
//
|
||||
// Returns the value and whether the key exists. If the key doesn't exist
|
||||
// or the stored value is not a string, exists will be false.
|
||||
func CacheGetString(key string) (*CacheGetStringResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := CacheGetStringRequest{
|
||||
Key: key,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := cache_getstring(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response CacheGetStringResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// CacheSetInt calls the cache_setint host function.
|
||||
// SetInt stores an integer value in the cache.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The cache key (will be namespaced with plugin ID)
|
||||
// - value: The integer value to store
|
||||
// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
|
||||
//
|
||||
// Returns an error if the operation fails.
|
||||
func CacheSetInt(key string, value int64, ttlSeconds int64) (*CacheSetIntResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := CacheSetIntRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
TtlSeconds: ttlSeconds,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := cache_setint(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response CacheSetIntResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// CacheGetInt calls the cache_getint host function.
|
||||
// GetInt retrieves an integer value from the cache.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The cache key (will be namespaced with plugin ID)
|
||||
//
|
||||
// Returns the value and whether the key exists. If the key doesn't exist
|
||||
// or the stored value is not an integer, exists will be false.
|
||||
func CacheGetInt(key string) (*CacheGetIntResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := CacheGetIntRequest{
|
||||
Key: key,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := cache_getint(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response CacheGetIntResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// CacheSetFloat calls the cache_setfloat host function.
|
||||
// SetFloat stores a float value in the cache.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The cache key (will be namespaced with plugin ID)
|
||||
// - value: The float value to store
|
||||
// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
|
||||
//
|
||||
// Returns an error if the operation fails.
|
||||
func CacheSetFloat(key string, value float64, ttlSeconds int64) (*CacheSetFloatResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := CacheSetFloatRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
TtlSeconds: ttlSeconds,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := cache_setfloat(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response CacheSetFloatResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// CacheGetFloat calls the cache_getfloat host function.
|
||||
// GetFloat retrieves a float value from the cache.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The cache key (will be namespaced with plugin ID)
|
||||
//
|
||||
// Returns the value and whether the key exists. If the key doesn't exist
|
||||
// or the stored value is not a float, exists will be false.
|
||||
func CacheGetFloat(key string) (*CacheGetFloatResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := CacheGetFloatRequest{
|
||||
Key: key,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := cache_getfloat(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response CacheGetFloatResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// CacheSetBytes calls the cache_setbytes host function.
|
||||
// SetBytes stores a byte slice in the cache.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The cache key (will be namespaced with plugin ID)
|
||||
// - value: The byte slice to store
|
||||
// - ttlSeconds: Time-to-live in seconds (0 uses default of 24 hours)
|
||||
//
|
||||
// Returns an error if the operation fails.
|
||||
func CacheSetBytes(key string, value []byte, ttlSeconds int64) (*CacheSetBytesResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := CacheSetBytesRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
TtlSeconds: ttlSeconds,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := cache_setbytes(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response CacheSetBytesResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// CacheGetBytes calls the cache_getbytes host function.
|
||||
// GetBytes retrieves a byte slice from the cache.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The cache key (will be namespaced with plugin ID)
|
||||
//
|
||||
// Returns the value and whether the key exists. If the key doesn't exist
|
||||
// or the stored value is not a byte slice, exists will be false.
|
||||
func CacheGetBytes(key string) (*CacheGetBytesResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := CacheGetBytesRequest{
|
||||
Key: key,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := cache_getbytes(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response CacheGetBytesResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// CacheHas calls the cache_has host function.
|
||||
// Has checks if a key exists in the cache.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The cache key (will be namespaced with plugin ID)
|
||||
//
|
||||
// Returns true if the key exists and has not expired.
|
||||
func CacheHas(key string) (*CacheHasResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := CacheHasRequest{
|
||||
Key: key,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := cache_has(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response CacheHasResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// CacheRemove calls the cache_remove host function.
|
||||
// Remove deletes a value from the cache.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The cache key (will be namespaced with plugin ID)
|
||||
//
|
||||
// Returns an error if the operation fails. Does not return an error if the key doesn't exist.
|
||||
func CacheRemove(key string) (*CacheRemoveResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := CacheRemoveRequest{
|
||||
Key: key,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := cache_remove(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response CacheRemoveResponse
|
||||
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, nil
|
||||
}
|
||||
9
plugins/testdata/test-kvstore/go.mod
vendored
9
plugins/testdata/test-kvstore/go.mod
vendored
@ -1,5 +1,10 @@
|
||||
module test-kvstore
|
||||
|
||||
go 1.23
|
||||
go 1.24
|
||||
|
||||
require github.com/extism/go-pdk v1.1.3
|
||||
require (
|
||||
github.com/extism/go-pdk v1.1.3
|
||||
github.com/navidrome/navidrome/plugins/pdk/go/host v0.0.0
|
||||
)
|
||||
|
||||
replace github.com/navidrome/navidrome/plugins/pdk/go/host => ../../pdk/go/host
|
||||
|
||||
13
plugins/testdata/test-kvstore/main.go
vendored
13
plugins/testdata/test-kvstore/main.go
vendored
@ -4,6 +4,7 @@ package main
|
||||
|
||||
import (
|
||||
pdk "github.com/extism/go-pdk"
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
||||
)
|
||||
|
||||
// TestKVStoreInput is the input for nd_test_kvstore callback.
|
||||
@ -36,7 +37,7 @@ func ndTestKVStore() int32 {
|
||||
|
||||
switch input.Operation {
|
||||
case "set":
|
||||
_, err := KVStoreSet(input.Key, input.Value)
|
||||
_, err := host.KVStoreSet(input.Key, input.Value)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestKVStoreOutput{Error: &errStr})
|
||||
@ -46,7 +47,7 @@ func ndTestKVStore() int32 {
|
||||
return 0
|
||||
|
||||
case "get":
|
||||
resp, err := KVStoreGet(input.Key)
|
||||
resp, err := host.KVStoreGet(input.Key)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestKVStoreOutput{Error: &errStr})
|
||||
@ -56,7 +57,7 @@ func ndTestKVStore() int32 {
|
||||
return 0
|
||||
|
||||
case "delete":
|
||||
_, err := KVStoreDelete(input.Key)
|
||||
_, err := host.KVStoreDelete(input.Key)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestKVStoreOutput{Error: &errStr})
|
||||
@ -66,7 +67,7 @@ func ndTestKVStore() int32 {
|
||||
return 0
|
||||
|
||||
case "has":
|
||||
resp, err := KVStoreHas(input.Key)
|
||||
resp, err := host.KVStoreHas(input.Key)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestKVStoreOutput{Error: &errStr})
|
||||
@ -76,7 +77,7 @@ func ndTestKVStore() int32 {
|
||||
return 0
|
||||
|
||||
case "list":
|
||||
resp, err := KVStoreList(input.Prefix)
|
||||
resp, err := host.KVStoreList(input.Prefix)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestKVStoreOutput{Error: &errStr})
|
||||
@ -86,7 +87,7 @@ func ndTestKVStore() int32 {
|
||||
return 0
|
||||
|
||||
case "get_storage_used":
|
||||
resp, err := KVStoreGetStorageUsed()
|
||||
resp, err := host.KVStoreGetStorageUsed()
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestKVStoreOutput{Error: &errStr})
|
||||
|
||||
336
plugins/testdata/test-kvstore/nd_host_kvstore.go
vendored
336
plugins/testdata/test-kvstore/nd_host_kvstore.go
vendored
@ -1,336 +0,0 @@
|
||||
// Code generated by hostgen. DO NOT EDIT.
|
||||
//
|
||||
// This file contains client wrappers for the KVStore host service.
|
||||
// It is intended for use in Navidrome plugins built with TinyGo.
|
||||
//
|
||||
//go:build wasip1
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
// kvstore_set is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user kvstore_set
|
||||
func kvstore_set(uint64) uint64
|
||||
|
||||
// kvstore_get is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user kvstore_get
|
||||
func kvstore_get(uint64) uint64
|
||||
|
||||
// kvstore_delete is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user kvstore_delete
|
||||
func kvstore_delete(uint64) uint64
|
||||
|
||||
// kvstore_has is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user kvstore_has
|
||||
func kvstore_has(uint64) uint64
|
||||
|
||||
// kvstore_list is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user kvstore_list
|
||||
func kvstore_list(uint64) uint64
|
||||
|
||||
// kvstore_getstorageused is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user kvstore_getstorageused
|
||||
func kvstore_getstorageused(uint64) uint64
|
||||
|
||||
// KVStoreSetRequest is the request type for KVStore.Set.
|
||||
type KVStoreSetRequest struct {
|
||||
Key string `json:"key"`
|
||||
Value []byte `json:"value"`
|
||||
}
|
||||
|
||||
// KVStoreSetResponse is the response type for KVStore.Set.
|
||||
type KVStoreSetResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// KVStoreGetRequest is the request type for KVStore.Get.
|
||||
type KVStoreGetRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// KVStoreGetResponse is the response type for KVStore.Get.
|
||||
type KVStoreGetResponse struct {
|
||||
Value []byte `json:"value,omitempty"`
|
||||
Exists bool `json:"exists,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// KVStoreDeleteRequest is the request type for KVStore.Delete.
|
||||
type KVStoreDeleteRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// KVStoreDeleteResponse is the response type for KVStore.Delete.
|
||||
type KVStoreDeleteResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// KVStoreHasRequest is the request type for KVStore.Has.
|
||||
type KVStoreHasRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// KVStoreHasResponse is the response type for KVStore.Has.
|
||||
type KVStoreHasResponse struct {
|
||||
Exists bool `json:"exists,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// KVStoreListRequest is the request type for KVStore.List.
|
||||
type KVStoreListRequest struct {
|
||||
Prefix string `json:"prefix"`
|
||||
}
|
||||
|
||||
// KVStoreListResponse is the response type for KVStore.List.
|
||||
type KVStoreListResponse struct {
|
||||
Keys []string `json:"keys,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// KVStoreGetStorageUsedResponse is the response type for KVStore.GetStorageUsed.
|
||||
type KVStoreGetStorageUsedResponse struct {
|
||||
Bytes int64 `json:"bytes,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// KVStoreSet calls the kvstore_set host function.
|
||||
// Set stores a byte value with the given key.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The storage key (max 256 bytes, UTF-8)
|
||||
// - value: The byte slice to store
|
||||
//
|
||||
// Returns an error if the storage limit would be exceeded or the operation fails.
|
||||
func KVStoreSet(key string, value []byte) (*KVStoreSetResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := KVStoreSetRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := kvstore_set(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response KVStoreSetResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// KVStoreGet calls the kvstore_get host function.
|
||||
// Get retrieves a byte value from storage.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The storage key
|
||||
//
|
||||
// Returns the value and whether the key exists.
|
||||
func KVStoreGet(key string) (*KVStoreGetResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := KVStoreGetRequest{
|
||||
Key: key,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := kvstore_get(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response KVStoreGetResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// KVStoreDelete calls the kvstore_delete host function.
|
||||
// Delete removes a value from storage.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The storage key
|
||||
//
|
||||
// Returns an error if the operation fails. Does not return an error if the key doesn't exist.
|
||||
func KVStoreDelete(key string) (*KVStoreDeleteResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := KVStoreDeleteRequest{
|
||||
Key: key,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := kvstore_delete(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response KVStoreDeleteResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// KVStoreHas calls the kvstore_has host function.
|
||||
// Has checks if a key exists in storage.
|
||||
//
|
||||
// Parameters:
|
||||
// - key: The storage key
|
||||
//
|
||||
// Returns true if the key exists.
|
||||
func KVStoreHas(key string) (*KVStoreHasResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := KVStoreHasRequest{
|
||||
Key: key,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := kvstore_has(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response KVStoreHasResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// KVStoreList calls the kvstore_list host function.
|
||||
// List returns all keys matching the given prefix.
|
||||
//
|
||||
// Parameters:
|
||||
// - prefix: Key prefix to filter by (empty string returns all keys)
|
||||
//
|
||||
// Returns a slice of matching keys.
|
||||
func KVStoreList(prefix string) (*KVStoreListResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := KVStoreListRequest{
|
||||
Prefix: prefix,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := kvstore_list(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response KVStoreListResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// KVStoreGetStorageUsed calls the kvstore_getstorageused host function.
|
||||
// GetStorageUsed returns the total storage used by this plugin in bytes.
|
||||
func KVStoreGetStorageUsed() (*KVStoreGetStorageUsedResponse, error) {
|
||||
// No parameters - allocate empty JSON object
|
||||
reqMem := pdk.AllocateBytes([]byte("{}"))
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := kvstore_getstorageused(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response KVStoreGetStorageUsedResponse
|
||||
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, nil
|
||||
}
|
||||
9
plugins/testdata/test-library/go.mod
vendored
9
plugins/testdata/test-library/go.mod
vendored
@ -1,5 +1,10 @@
|
||||
module test-library
|
||||
|
||||
go 1.23
|
||||
go 1.24
|
||||
|
||||
require github.com/extism/go-pdk v1.1.3
|
||||
require (
|
||||
github.com/extism/go-pdk v1.1.3
|
||||
github.com/navidrome/navidrome/plugins/pdk/go/host v0.0.0
|
||||
)
|
||||
|
||||
replace github.com/navidrome/navidrome/plugins/pdk/go/host => ../../pdk/go/host
|
||||
|
||||
15
plugins/testdata/test-library/main.go
vendored
15
plugins/testdata/test-library/main.go
vendored
@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
pdk "github.com/extism/go-pdk"
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
||||
)
|
||||
|
||||
// TestLibraryInput is the input for nd_test_library callback.
|
||||
@ -21,11 +22,11 @@ type TestLibraryInput struct {
|
||||
|
||||
// TestLibraryOutput is the output from nd_test_library callback.
|
||||
type TestLibraryOutput struct {
|
||||
Library *Library `json:"library,omitempty"`
|
||||
Libraries []Library `json:"libraries,omitempty"`
|
||||
FileContent string `json:"file_content,omitempty"`
|
||||
DirEntries []string `json:"dir_entries,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
Library *host.Library `json:"library,omitempty"`
|
||||
Libraries []host.Library `json:"libraries,omitempty"`
|
||||
FileContent string `json:"file_content,omitempty"`
|
||||
DirEntries []string `json:"dir_entries,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// nd_test_library is the test callback that tests the library host functions.
|
||||
@ -41,7 +42,7 @@ func ndTestLibrary() int32 {
|
||||
|
||||
switch input.Operation {
|
||||
case "get_library":
|
||||
resp, err := LibraryGetLibrary(input.LibraryID)
|
||||
resp, err := host.LibraryGetLibrary(input.LibraryID)
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestLibraryOutput{Error: &errStr})
|
||||
@ -51,7 +52,7 @@ func ndTestLibrary() int32 {
|
||||
return 0
|
||||
|
||||
case "get_all_libraries":
|
||||
resp, err := LibraryGetAllLibraries()
|
||||
resp, err := host.LibraryGetAllLibraries()
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pdk.OutputJSON(TestLibraryOutput{Error: &errStr})
|
||||
|
||||
127
plugins/testdata/test-library/nd_host_library.go
vendored
127
plugins/testdata/test-library/nd_host_library.go
vendored
@ -1,127 +0,0 @@
|
||||
// Code generated by hostgen. DO NOT EDIT.
|
||||
//
|
||||
// This file contains client wrappers for the Library host service.
|
||||
// It is intended for use in Navidrome plugins built with TinyGo.
|
||||
//
|
||||
//go:build wasip1
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
// Library represents a music library with metadata.
|
||||
// This type mirrors the host.Library type from plugins/host/library.go.
|
||||
type Library struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path,omitempty"`
|
||||
MountPoint string `json:"mountPoint,omitempty"`
|
||||
LastScanAt int64 `json:"lastScanAt"`
|
||||
TotalSongs int32 `json:"totalSongs"`
|
||||
TotalAlbums int32 `json:"totalAlbums"`
|
||||
TotalArtists int32 `json:"totalArtists"`
|
||||
TotalSize int64 `json:"totalSize"`
|
||||
TotalDuration float64 `json:"totalDuration"`
|
||||
}
|
||||
|
||||
// library_getlibrary is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user library_getlibrary
|
||||
func library_getlibrary(uint64) uint64
|
||||
|
||||
// library_getalllibraries is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user library_getalllibraries
|
||||
func library_getalllibraries(uint64) uint64
|
||||
|
||||
// LibraryGetLibraryRequest is the request type for Library.GetLibrary.
|
||||
type LibraryGetLibraryRequest struct {
|
||||
Id int32 `json:"id"`
|
||||
}
|
||||
|
||||
// LibraryGetLibraryResponse is the response type for Library.GetLibrary.
|
||||
type LibraryGetLibraryResponse struct {
|
||||
Result *Library `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// LibraryGetAllLibrariesResponse is the response type for Library.GetAllLibraries.
|
||||
type LibraryGetAllLibrariesResponse struct {
|
||||
Result []Library `json:"result,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// LibraryGetLibrary calls the library_getlibrary host function.
|
||||
// GetLibrary retrieves metadata for a specific library by ID.
|
||||
//
|
||||
// Parameters:
|
||||
// - id: The library's unique identifier
|
||||
//
|
||||
// Returns the library metadata, or an error if the library is not found.
|
||||
func LibraryGetLibrary(id int32) (*LibraryGetLibraryResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := LibraryGetLibraryRequest{
|
||||
Id: id,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := library_getlibrary(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response LibraryGetLibraryResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// LibraryGetAllLibraries calls the library_getalllibraries host function.
|
||||
// GetAllLibraries retrieves metadata for all configured libraries.
|
||||
//
|
||||
// Returns a slice of all libraries with their metadata.
|
||||
func LibraryGetAllLibraries() (*LibraryGetAllLibrariesResponse, error) {
|
||||
// No parameters - allocate empty JSON object
|
||||
reqMem := pdk.AllocateBytes([]byte("{}"))
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := library_getalllibraries(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response LibraryGetAllLibrariesResponse
|
||||
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, nil
|
||||
}
|
||||
11
plugins/testdata/test-metadata-agent/go.mod
vendored
11
plugins/testdata/test-metadata-agent/go.mod
vendored
@ -1,5 +1,10 @@
|
||||
module test-plugin
|
||||
module test-metadata-agent
|
||||
|
||||
go 1.23
|
||||
go 1.25
|
||||
|
||||
require github.com/extism/go-pdk v1.1.3
|
||||
require (
|
||||
github.com/extism/go-pdk v1.1.3
|
||||
github.com/navidrome/navidrome v0.0.0
|
||||
)
|
||||
|
||||
replace github.com/navidrome/navidrome => ../../..
|
||||
|
||||
282
plugins/testdata/test-metadata-agent/main.go
vendored
282
plugins/testdata/test-metadata-agent/main.go
vendored
@ -1,283 +1,119 @@
|
||||
// Test plugin for Navidrome plugin system integration tests.
|
||||
// Build with: tinygo build -o ../test-plugin.wasm -target wasip1 -buildmode=c-shared ./main.go
|
||||
// Build with: tinygo build -o ../test-metadata-agent.wasm -target wasip1 -buildmode=c-shared .
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/extism/go-pdk"
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/metadata"
|
||||
)
|
||||
|
||||
func init() {
|
||||
metadata.Register(&testMetadataAgent{})
|
||||
}
|
||||
|
||||
type testMetadataAgent struct{}
|
||||
|
||||
// checkConfigError checks if the plugin is configured to return an error.
|
||||
// If "error" config is set, it returns the error message and exit code.
|
||||
// If "exitcode" is also set, it uses that value (default: 1).
|
||||
func checkConfigError() (bool, int32) {
|
||||
// If "error" config is set, it returns an error with that message.
|
||||
func checkConfigError() error {
|
||||
errMsg, hasErr := pdk.GetConfig("error")
|
||||
if !hasErr || errMsg == "" {
|
||||
return false, 0
|
||||
return nil
|
||||
}
|
||||
exitCode := int32(1)
|
||||
if code, hasCode := pdk.GetConfig("exitcode"); hasCode {
|
||||
if parsed, err := strconv.Atoi(code); err == nil {
|
||||
exitCode = int32(parsed)
|
||||
}
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
|
||||
func (t *testMetadataAgent) GetArtistMBID(input metadata.ArtistMBIDRequest) (metadata.ArtistMBIDResponse, error) {
|
||||
if err := checkConfigError(); err != nil {
|
||||
return metadata.ArtistMBIDResponse{}, err
|
||||
}
|
||||
pdk.SetErrorString(errMsg)
|
||||
return true, exitCode
|
||||
return metadata.ArtistMBIDResponse{MBID: "test-mbid-" + input.Name}, nil
|
||||
}
|
||||
|
||||
type ArtistInput struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MBID string `json:"mbid,omitempty"`
|
||||
}
|
||||
|
||||
type ArtistInputWithLimit struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MBID string `json:"mbid,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
}
|
||||
|
||||
type ArtistInputWithCount struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MBID string `json:"mbid,omitempty"`
|
||||
Count int `json:"count,omitempty"`
|
||||
}
|
||||
|
||||
type AlbumInput struct {
|
||||
Name string `json:"name"`
|
||||
Artist string `json:"artist"`
|
||||
MBID string `json:"mbid,omitempty"`
|
||||
}
|
||||
|
||||
type MBIDOutput struct {
|
||||
MBID string `json:"mbid"`
|
||||
}
|
||||
|
||||
type URLOutput struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type BiographyOutput struct {
|
||||
Biography string `json:"biography"`
|
||||
}
|
||||
|
||||
type ArtistImage struct {
|
||||
URL string `json:"url"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
type ImagesOutput struct {
|
||||
Images []ArtistImage `json:"images"`
|
||||
}
|
||||
|
||||
type SimilarArtist struct {
|
||||
Name string `json:"name"`
|
||||
MBID string `json:"mbid,omitempty"`
|
||||
}
|
||||
|
||||
type SimilarArtistsOutput struct {
|
||||
Artists []SimilarArtist `json:"artists"`
|
||||
}
|
||||
|
||||
type TopSong struct {
|
||||
Name string `json:"name"`
|
||||
MBID string `json:"mbid,omitempty"`
|
||||
}
|
||||
|
||||
type TopSongsOutput struct {
|
||||
Songs []TopSong `json:"songs"`
|
||||
}
|
||||
|
||||
type AlbumInfoOutput struct {
|
||||
Name string `json:"name"`
|
||||
MBID string `json:"mbid,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type AlbumImagesOutput struct {
|
||||
Images []ArtistImage `json:"images"`
|
||||
}
|
||||
|
||||
//go:wasmexport nd_get_artist_mbid
|
||||
func ndGetArtistMBID() int32 {
|
||||
if hasErr, code := checkConfigError(); hasErr {
|
||||
return code
|
||||
func (t *testMetadataAgent) GetArtistURL(input metadata.ArtistRequest) (metadata.ArtistURLResponse, error) {
|
||||
if err := checkConfigError(); err != nil {
|
||||
return metadata.ArtistURLResponse{}, err
|
||||
}
|
||||
var input ArtistInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
output := MBIDOutput{MBID: "test-mbid-" + input.Name}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
return metadata.ArtistURLResponse{URL: "https://test.example.com/artist/" + input.Name}, nil
|
||||
}
|
||||
|
||||
//go:wasmexport nd_get_artist_url
|
||||
func ndGetArtistURL() int32 {
|
||||
if hasErr, code := checkConfigError(); hasErr {
|
||||
return code
|
||||
func (t *testMetadataAgent) GetArtistBiography(input metadata.ArtistRequest) (metadata.ArtistBiographyResponse, error) {
|
||||
if err := checkConfigError(); err != nil {
|
||||
return metadata.ArtistBiographyResponse{}, err
|
||||
}
|
||||
var input ArtistInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
output := URLOutput{URL: "https://test.example.com/artist/" + input.Name}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
return metadata.ArtistBiographyResponse{Biography: "Biography for " + input.Name}, nil
|
||||
}
|
||||
|
||||
//go:wasmexport nd_get_artist_biography
|
||||
func ndGetArtistBiography() int32 {
|
||||
if hasErr, code := checkConfigError(); hasErr {
|
||||
return code
|
||||
func (t *testMetadataAgent) GetArtistImages(input metadata.ArtistRequest) (metadata.ArtistImagesResponse, error) {
|
||||
if err := checkConfigError(); err != nil {
|
||||
return metadata.ArtistImagesResponse{}, err
|
||||
}
|
||||
var input ArtistInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
output := BiographyOutput{Biography: "Biography for " + input.Name}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//go:wasmexport nd_get_artist_images
|
||||
func ndGetArtistImages() int32 {
|
||||
if hasErr, code := checkConfigError(); hasErr {
|
||||
return code
|
||||
}
|
||||
var input ArtistInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
output := ImagesOutput{
|
||||
Images: []ArtistImage{
|
||||
return metadata.ArtistImagesResponse{
|
||||
Images: []metadata.ImageInfo{
|
||||
{URL: "https://test.example.com/images/" + input.Name + "/large.jpg", Size: 500},
|
||||
{URL: "https://test.example.com/images/" + input.Name + "/small.jpg", Size: 100},
|
||||
},
|
||||
}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}, nil
|
||||
}
|
||||
|
||||
//go:wasmexport nd_get_similar_artists
|
||||
func ndGetSimilarArtists() int32 {
|
||||
if hasErr, code := checkConfigError(); hasErr {
|
||||
return code
|
||||
func (t *testMetadataAgent) GetSimilarArtists(input metadata.SimilarArtistsRequest) (metadata.SimilarArtistsResponse, error) {
|
||||
if err := checkConfigError(); err != nil {
|
||||
return metadata.SimilarArtistsResponse{}, err
|
||||
}
|
||||
var input ArtistInputWithLimit
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
limit := input.Limit
|
||||
limit := int(input.Limit)
|
||||
if limit == 0 {
|
||||
limit = 5
|
||||
}
|
||||
artists := make([]SimilarArtist, 0, limit)
|
||||
artists := make([]metadata.ArtistRef, 0, limit)
|
||||
for i := range limit {
|
||||
artists = append(artists, SimilarArtist{
|
||||
artists = append(artists, metadata.ArtistRef{
|
||||
Name: input.Name + " Similar " + string(rune('A'+i)),
|
||||
})
|
||||
}
|
||||
output := SimilarArtistsOutput{Artists: artists}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
return metadata.SimilarArtistsResponse{Artists: artists}, nil
|
||||
}
|
||||
|
||||
//go:wasmexport nd_get_artist_top_songs
|
||||
func ndGetArtistTopSongs() int32 {
|
||||
if hasErr, code := checkConfigError(); hasErr {
|
||||
return code
|
||||
func (t *testMetadataAgent) GetArtistTopSongs(input metadata.TopSongsRequest) (metadata.TopSongsResponse, error) {
|
||||
if err := checkConfigError(); err != nil {
|
||||
return metadata.TopSongsResponse{}, err
|
||||
}
|
||||
var input ArtistInputWithCount
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
count := input.Count
|
||||
count := int(input.Count)
|
||||
if count == 0 {
|
||||
count = 5
|
||||
}
|
||||
songs := make([]TopSong, 0, count)
|
||||
songs := make([]metadata.SongRef, 0, count)
|
||||
for i := range count {
|
||||
songs = append(songs, TopSong{
|
||||
Name: input.Name + " Song " + string(rune('1'+i)),
|
||||
songs = append(songs, metadata.SongRef{
|
||||
Name: input.Name + " Song " + strconv.Itoa(i+1),
|
||||
})
|
||||
}
|
||||
output := TopSongsOutput{Songs: songs}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
return metadata.TopSongsResponse{Songs: songs}, nil
|
||||
}
|
||||
|
||||
//go:wasmexport nd_get_album_info
|
||||
func ndGetAlbumInfo() int32 {
|
||||
if hasErr, code := checkConfigError(); hasErr {
|
||||
return code
|
||||
func (t *testMetadataAgent) GetAlbumInfo(input metadata.AlbumRequest) (metadata.AlbumInfoResponse, error) {
|
||||
if err := checkConfigError(); err != nil {
|
||||
return metadata.AlbumInfoResponse{}, err
|
||||
}
|
||||
var input AlbumInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
output := AlbumInfoOutput{
|
||||
return metadata.AlbumInfoResponse{
|
||||
Name: input.Name,
|
||||
MBID: "test-album-mbid-" + input.Name,
|
||||
Description: "Description for " + input.Name + " by " + input.Artist,
|
||||
URL: "https://test.example.com/album/" + input.Name,
|
||||
}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}, nil
|
||||
}
|
||||
|
||||
//go:wasmexport nd_get_album_images
|
||||
func ndGetAlbumImages() int32 {
|
||||
if hasErr, code := checkConfigError(); hasErr {
|
||||
return code
|
||||
func (t *testMetadataAgent) GetAlbumImages(input metadata.AlbumRequest) (metadata.AlbumImagesResponse, error) {
|
||||
if err := checkConfigError(); err != nil {
|
||||
return metadata.AlbumImagesResponse{}, err
|
||||
}
|
||||
var input AlbumInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
output := AlbumImagesOutput{
|
||||
Images: []ArtistImage{
|
||||
return metadata.AlbumImagesResponse{
|
||||
Images: []metadata.ImageInfo{
|
||||
{URL: "https://test.example.com/albums/" + input.Name + "/cover.jpg", Size: 500},
|
||||
},
|
||||
}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {}
|
||||
|
||||
13
plugins/testdata/test-scheduler/go.mod
vendored
13
plugins/testdata/test-scheduler/go.mod
vendored
@ -1,5 +1,14 @@
|
||||
module test-scheduler
|
||||
|
||||
go 1.23
|
||||
go 1.25
|
||||
|
||||
require github.com/extism/go-pdk v1.1.3
|
||||
require (
|
||||
github.com/navidrome/navidrome v0.0.0
|
||||
github.com/navidrome/navidrome/plugins/pdk/go/host v0.0.0
|
||||
)
|
||||
|
||||
require github.com/extism/go-pdk v1.1.3 // indirect
|
||||
|
||||
replace github.com/navidrome/navidrome => ../../..
|
||||
|
||||
replace github.com/navidrome/navidrome/plugins/pdk/go/host => ../../pdk/go/host
|
||||
|
||||
21
plugins/testdata/test-scheduler/main.go
vendored
21
plugins/testdata/test-scheduler/main.go
vendored
@ -2,24 +2,35 @@
|
||||
// Build with: tinygo build -o ../test-scheduler.wasm -target wasip1 -buildmode=c-shared .
|
||||
package main
|
||||
|
||||
// NdSchedulerCallback is called when a scheduled task fires.
|
||||
import (
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/scheduler"
|
||||
)
|
||||
|
||||
func init() {
|
||||
scheduler.Register(&testScheduler{})
|
||||
}
|
||||
|
||||
type testScheduler struct{}
|
||||
|
||||
// OnSchedulerCallback is called when a scheduled task fires.
|
||||
// Magic payloads trigger specific behaviors to test host functions:
|
||||
// - "schedule-followup": schedules a one-time task via host function
|
||||
// - "schedule-recurring": schedules a recurring task via host function
|
||||
// - "schedule-duplicate:<id>": attempts to schedule with the given ID (for testing duplicate detection)
|
||||
func NdSchedulerCallback(input SchedulerCallbackInput) error {
|
||||
func (t *testScheduler) OnSchedulerCallback(input scheduler.SchedulerCallbackRequest) error {
|
||||
switch {
|
||||
case input.Payload == "schedule-followup":
|
||||
if _, err := SchedulerScheduleOneTime(1, "followup-created", "followup-id"); err != nil {
|
||||
if _, err := host.SchedulerScheduleOneTime(1, "followup-created", "followup-id"); err != nil {
|
||||
return err
|
||||
}
|
||||
case input.Payload == "schedule-recurring":
|
||||
if _, err := SchedulerScheduleRecurring("@every 1s", "recurring-created", "recurring-from-plugin"); err != nil {
|
||||
if _, err := host.SchedulerScheduleRecurring("@every 1s", "recurring-created", "recurring-from-plugin"); err != nil {
|
||||
return err
|
||||
}
|
||||
case len(input.Payload) > 19 && input.Payload[:19] == "schedule-duplicate:":
|
||||
duplicateID := input.Payload[19:]
|
||||
if _, err := SchedulerScheduleOneTime(60, "duplicate-attempt", duplicateID); err != nil {
|
||||
if _, err := host.SchedulerScheduleOneTime(60, "duplicate-attempt", duplicateID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
196
plugins/testdata/test-scheduler/nd_host_scheduler.go
vendored
196
plugins/testdata/test-scheduler/nd_host_scheduler.go
vendored
@ -1,196 +0,0 @@
|
||||
// Code generated by hostgen. DO NOT EDIT.
|
||||
//
|
||||
// This file contains client wrappers for the Scheduler host service.
|
||||
// It is intended for use in Navidrome plugins built with TinyGo.
|
||||
//
|
||||
//go:build wasip1
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
// scheduler_scheduleonetime is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user scheduler_scheduleonetime
|
||||
func scheduler_scheduleonetime(uint64) uint64
|
||||
|
||||
// scheduler_schedulerecurring is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user scheduler_schedulerecurring
|
||||
func scheduler_schedulerecurring(uint64) uint64
|
||||
|
||||
// scheduler_cancelschedule is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user scheduler_cancelschedule
|
||||
func scheduler_cancelschedule(uint64) uint64
|
||||
|
||||
// SchedulerScheduleOneTimeRequest is the request type for Scheduler.ScheduleOneTime.
|
||||
type SchedulerScheduleOneTimeRequest struct {
|
||||
DelaySeconds int32 `json:"delaySeconds"`
|
||||
Payload string `json:"payload"`
|
||||
ScheduleID string `json:"scheduleId"`
|
||||
}
|
||||
|
||||
// SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime.
|
||||
type SchedulerScheduleOneTimeResponse struct {
|
||||
NewScheduleID string `json:"newScheduleId,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// SchedulerScheduleRecurringRequest is the request type for Scheduler.ScheduleRecurring.
|
||||
type SchedulerScheduleRecurringRequest struct {
|
||||
CronExpression string `json:"cronExpression"`
|
||||
Payload string `json:"payload"`
|
||||
ScheduleID string `json:"scheduleId"`
|
||||
}
|
||||
|
||||
// SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring.
|
||||
type SchedulerScheduleRecurringResponse struct {
|
||||
NewScheduleID string `json:"newScheduleId,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// SchedulerCancelScheduleRequest is the request type for Scheduler.CancelSchedule.
|
||||
type SchedulerCancelScheduleRequest struct {
|
||||
ScheduleID string `json:"scheduleId"`
|
||||
}
|
||||
|
||||
// SchedulerCancelScheduleResponse is the response type for Scheduler.CancelSchedule.
|
||||
type SchedulerCancelScheduleResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// SchedulerScheduleOneTime calls the scheduler_scheduleonetime host function.
|
||||
// ScheduleOneTime schedules a one-time event to be triggered after the specified delay.
|
||||
// Plugins that use this function must also implement the SchedulerCallback capability
|
||||
//
|
||||
// Parameters:
|
||||
// - delaySeconds: Number of seconds to wait before triggering the event
|
||||
// - payload: Data to be passed to the scheduled event handler
|
||||
// - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated
|
||||
//
|
||||
// Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails.
|
||||
func SchedulerScheduleOneTime(delaySeconds int32, payload string, scheduleID string) (*SchedulerScheduleOneTimeResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := SchedulerScheduleOneTimeRequest{
|
||||
DelaySeconds: delaySeconds,
|
||||
Payload: payload,
|
||||
ScheduleID: scheduleID,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := scheduler_scheduleonetime(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response SchedulerScheduleOneTimeResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// SchedulerScheduleRecurring calls the scheduler_schedulerecurring host function.
|
||||
// ScheduleRecurring schedules a recurring event using a cron expression.
|
||||
// Plugins that use this function must also implement the SchedulerCallback capability
|
||||
//
|
||||
// Parameters:
|
||||
// - cronExpression: Standard cron format expression (e.g., "0 0 * * *" for daily at midnight)
|
||||
// - payload: Data to be passed to each scheduled event handler invocation
|
||||
// - scheduleID: Optional unique identifier for the scheduled job. If empty, one will be generated
|
||||
//
|
||||
// Returns the schedule ID that can be used to cancel the job, or an error if scheduling fails.
|
||||
func SchedulerScheduleRecurring(cronExpression string, payload string, scheduleID string) (*SchedulerScheduleRecurringResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := SchedulerScheduleRecurringRequest{
|
||||
CronExpression: cronExpression,
|
||||
Payload: payload,
|
||||
ScheduleID: scheduleID,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := scheduler_schedulerecurring(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response SchedulerScheduleRecurringResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// SchedulerCancelSchedule calls the scheduler_cancelschedule host function.
|
||||
// CancelSchedule cancels a scheduled job identified by its schedule ID.
|
||||
//
|
||||
// This works for both one-time and recurring schedules. Once cancelled, the job will not trigger
|
||||
// any future events.
|
||||
//
|
||||
// Returns an error if the schedule ID is not found or if cancellation fails.
|
||||
func SchedulerCancelSchedule(scheduleID string) (*SchedulerCancelScheduleResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := SchedulerCancelScheduleRequest{
|
||||
ScheduleID: scheduleID,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := scheduler_cancelschedule(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response SchedulerCancelScheduleResponse
|
||||
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, nil
|
||||
}
|
||||
35
plugins/testdata/test-scheduler/pdk.gen.go
vendored
35
plugins/testdata/test-scheduler/pdk.gen.go
vendored
@ -1,35 +0,0 @@
|
||||
// THIS FILE WAS GENERATED BY `xtp-go-bindgen`. DO NOT EDIT.
|
||||
package main
|
||||
|
||||
import (
|
||||
pdk "github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
//go:wasmexport nd_scheduler_callback
|
||||
func _NdSchedulerCallback() int32 {
|
||||
var input SchedulerCallbackInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return -1
|
||||
}
|
||||
|
||||
if err := NdSchedulerCallback(input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return -1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Input provided to the scheduler callback when a scheduled task fires
|
||||
type SchedulerCallbackInput struct {
|
||||
// True if this is a recurring schedule (created via ScheduleRecurring),
|
||||
// false if it's a one-time schedule (created via ScheduleOneTime).
|
||||
IsRecurring bool `json:"isRecurring"`
|
||||
// The payload data that was provided when the task was scheduled.
|
||||
// Can be used to pass context or parameters to the callback handler.
|
||||
Payload string `json:"payload"`
|
||||
// The unique identifier for this scheduled task. This is either the ID
|
||||
// provided when scheduling, or an auto-generated UUID if none was specified.
|
||||
ScheduleId string `json:"scheduleId"`
|
||||
}
|
||||
@ -2,4 +2,9 @@ module test-subsonicapi-plugin
|
||||
|
||||
go 1.24
|
||||
|
||||
require github.com/extism/go-pdk v1.1.3
|
||||
require (
|
||||
github.com/extism/go-pdk v1.1.3
|
||||
github.com/navidrome/navidrome/plugins/pdk/go/host v0.0.0
|
||||
)
|
||||
|
||||
replace github.com/navidrome/navidrome/plugins/pdk/go/host => ../../pdk/go/host
|
||||
|
||||
@ -4,6 +4,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/extism/go-pdk"
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
||||
)
|
||||
|
||||
// call_subsonic_api is the exported function that tests the SubsonicAPI host function.
|
||||
@ -16,7 +17,7 @@ func callSubsonicAPIExport() int32 {
|
||||
uri := pdk.InputString()
|
||||
|
||||
// Call the Subsonic API via host function
|
||||
response, err := SubsonicAPICall(uri)
|
||||
response, err := host.SubsonicAPICall(uri)
|
||||
if err != nil {
|
||||
pdk.SetErrorString("failed to call SubsonicAPI: " + err.Error())
|
||||
return 1
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
// Code generated by hostgen. DO NOT EDIT.
|
||||
//
|
||||
// This file contains client wrappers for the SubsonicAPI host service.
|
||||
// It is intended for use in Navidrome plugins built with TinyGo.
|
||||
//
|
||||
//go:build wasip1
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
// subsonicapi_call is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user subsonicapi_call
|
||||
func subsonicapi_call(uint64) uint64
|
||||
|
||||
// SubsonicAPICallRequest is the request type for SubsonicAPI.Call.
|
||||
type SubsonicAPICallRequest struct {
|
||||
Uri string `json:"uri"`
|
||||
}
|
||||
|
||||
// SubsonicAPICallResponse is the response type for SubsonicAPI.Call.
|
||||
type SubsonicAPICallResponse struct {
|
||||
ResponseJSON string `json:"responseJson,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// SubsonicAPICall calls the subsonicapi_call host function.
|
||||
// Call executes a Subsonic API request and returns the JSON response.
|
||||
//
|
||||
// The uri parameter should be the Subsonic API path without the server prefix,
|
||||
// e.g., "getAlbumList2?type=random&size=10". The response is returned as raw JSON.
|
||||
func SubsonicAPICall(uri string) (*SubsonicAPICallResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := SubsonicAPICallRequest{
|
||||
Uri: uri,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := subsonicapi_call(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response SubsonicAPICallResponse
|
||||
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, nil
|
||||
}
|
||||
12
plugins/testdata/test-websocket/go.mod
vendored
12
plugins/testdata/test-websocket/go.mod
vendored
@ -1,5 +1,13 @@
|
||||
module test-websocket
|
||||
|
||||
go 1.23
|
||||
go 1.25
|
||||
|
||||
require github.com/extism/go-pdk v1.1.3
|
||||
require (
|
||||
github.com/extism/go-pdk v1.1.3
|
||||
github.com/navidrome/navidrome v0.0.0
|
||||
github.com/navidrome/navidrome/plugins/pdk/go/host v0.0.0
|
||||
)
|
||||
|
||||
replace github.com/navidrome/navidrome => ../../..
|
||||
|
||||
replace github.com/navidrome/navidrome/plugins/pdk/go/host => ../../pdk/go/host
|
||||
|
||||
101
plugins/testdata/test-websocket/main.go
vendored
101
plugins/testdata/test-websocket/main.go
vendored
@ -6,118 +6,63 @@ import (
|
||||
"errors"
|
||||
|
||||
pdk "github.com/extism/go-pdk"
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/host"
|
||||
"github.com/navidrome/navidrome/plugins/pdk/go/websocket"
|
||||
)
|
||||
|
||||
// OnTextMessageInput is the input for nd_websocket_on_text_message callback.
|
||||
type OnTextMessageInput struct {
|
||||
ConnectionID string `json:"connectionId"`
|
||||
Message string `json:"message"`
|
||||
func init() {
|
||||
websocket.Register(&testWebSocket{})
|
||||
}
|
||||
|
||||
// nd_websocket_on_text_message is called when a text message is received.
|
||||
type testWebSocket struct{}
|
||||
|
||||
// OnTextMessage is called when a text message is received.
|
||||
// Magic messages trigger specific behaviors to test host functions:
|
||||
// - "echo": sends back the same message using SendText host function
|
||||
// - "close": closes the connection using CloseConnection host function
|
||||
// - "store:MESSAGE": stores MESSAGE in plugin config for later retrieval
|
||||
// - "fail": returns an error to test error handling
|
||||
//
|
||||
//go:wasmexport nd_websocket_on_text_message
|
||||
func ndWebSocketOnTextMessage() int32 {
|
||||
var input OnTextMessageInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return -1
|
||||
}
|
||||
|
||||
func (t *testWebSocket) OnTextMessage(input websocket.OnTextMessageRequest) error {
|
||||
// Store all received messages for test verification
|
||||
storeReceivedMessage("text:" + input.Message)
|
||||
|
||||
switch input.Message {
|
||||
case "echo":
|
||||
if _, err := WebSocketSendText(input.ConnectionID, "echo:"+input.Message); err != nil {
|
||||
pdk.SetError(err)
|
||||
return -1
|
||||
if _, err := host.WebSocketSendText(input.ConnectionID, "echo:"+input.Message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "close":
|
||||
if _, err := WebSocketCloseConnection(input.ConnectionID, 1000, "closed by plugin"); err != nil {
|
||||
pdk.SetError(err)
|
||||
return -1
|
||||
if _, err := host.WebSocketCloseConnection(input.ConnectionID, 1000, "closed by plugin"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "fail":
|
||||
pdk.SetError(errors.New("intentional test failure"))
|
||||
return -1
|
||||
return errors.New("intentional test failure")
|
||||
}
|
||||
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnBinaryMessageInput is the input for nd_websocket_on_binary_message callback.
|
||||
type OnBinaryMessageInput struct {
|
||||
ConnectionID string `json:"connectionId"`
|
||||
Data string `json:"data"` // Base64 encoded
|
||||
}
|
||||
|
||||
// nd_websocket_on_binary_message is called when a binary message is received.
|
||||
//
|
||||
//go:wasmexport nd_websocket_on_binary_message
|
||||
func ndWebSocketOnBinaryMessage() int32 {
|
||||
var input OnBinaryMessageInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return -1
|
||||
}
|
||||
|
||||
// OnBinaryMessage is called when a binary message is received.
|
||||
func (t *testWebSocket) OnBinaryMessage(input websocket.OnBinaryMessageRequest) error {
|
||||
// Store received binary data for test verification
|
||||
storeReceivedMessage("binary:" + input.Data)
|
||||
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnErrorInput is the input for nd_websocket_on_error callback.
|
||||
type OnErrorInput struct {
|
||||
ConnectionID string `json:"connectionId"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// nd_websocket_on_error is called when an error occurs on a WebSocket connection.
|
||||
//
|
||||
//go:wasmexport nd_websocket_on_error
|
||||
func ndWebSocketOnError() int32 {
|
||||
var input OnErrorInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return -1
|
||||
}
|
||||
|
||||
// OnError is called when an error occurs on a WebSocket connection.
|
||||
func (t *testWebSocket) OnError(input websocket.OnErrorRequest) error {
|
||||
// Store error for test verification
|
||||
storeReceivedMessage("error:" + input.Error)
|
||||
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnCloseInput is the input for nd_websocket_on_close callback.
|
||||
type OnCloseInput struct {
|
||||
ConnectionID string `json:"connectionId"`
|
||||
Code int `json:"code"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// nd_websocket_on_close is called when a WebSocket connection is closed.
|
||||
//
|
||||
//go:wasmexport nd_websocket_on_close
|
||||
func ndWebSocketOnClose() int32 {
|
||||
var input OnCloseInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return -1
|
||||
}
|
||||
|
||||
// OnClose is called when a WebSocket connection is closed.
|
||||
func (t *testWebSocket) OnClose(input websocket.OnCloseRequest) error {
|
||||
// Store close event for test verification
|
||||
storeReceivedMessage("close:" + input.Reason)
|
||||
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// storeReceivedMessage stores messages in plugin variable storage for test verification.
|
||||
|
||||
258
plugins/testdata/test-websocket/nd_host_websocket.go
vendored
258
plugins/testdata/test-websocket/nd_host_websocket.go
vendored
@ -1,258 +0,0 @@
|
||||
// Code generated by hostgen. DO NOT EDIT.
|
||||
//
|
||||
// This file contains client wrappers for the WebSocket host service.
|
||||
// It is intended for use in Navidrome plugins built with TinyGo.
|
||||
//
|
||||
//go:build wasip1
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
// websocket_connect is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user websocket_connect
|
||||
func websocket_connect(uint64) uint64
|
||||
|
||||
// websocket_sendtext is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user websocket_sendtext
|
||||
func websocket_sendtext(uint64) uint64
|
||||
|
||||
// websocket_sendbinary is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user websocket_sendbinary
|
||||
func websocket_sendbinary(uint64) uint64
|
||||
|
||||
// websocket_closeconnection is the host function provided by Navidrome.
|
||||
//
|
||||
//go:wasmimport extism:host/user websocket_closeconnection
|
||||
func websocket_closeconnection(uint64) uint64
|
||||
|
||||
// WebSocketConnectRequest is the request type for WebSocket.Connect.
|
||||
type WebSocketConnectRequest struct {
|
||||
Url string `json:"url"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
ConnectionID string `json:"connectionId"`
|
||||
}
|
||||
|
||||
// WebSocketConnectResponse is the response type for WebSocket.Connect.
|
||||
type WebSocketConnectResponse struct {
|
||||
NewConnectionID string `json:"newConnectionId,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// WebSocketSendTextRequest is the request type for WebSocket.SendText.
|
||||
type WebSocketSendTextRequest struct {
|
||||
ConnectionID string `json:"connectionId"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// WebSocketSendTextResponse is the response type for WebSocket.SendText.
|
||||
type WebSocketSendTextResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// WebSocketSendBinaryRequest is the request type for WebSocket.SendBinary.
|
||||
type WebSocketSendBinaryRequest struct {
|
||||
ConnectionID string `json:"connectionId"`
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
// WebSocketSendBinaryResponse is the response type for WebSocket.SendBinary.
|
||||
type WebSocketSendBinaryResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// WebSocketCloseConnectionRequest is the request type for WebSocket.CloseConnection.
|
||||
type WebSocketCloseConnectionRequest struct {
|
||||
ConnectionID string `json:"connectionId"`
|
||||
Code int32 `json:"code"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// WebSocketCloseConnectionResponse is the response type for WebSocket.CloseConnection.
|
||||
type WebSocketCloseConnectionResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// WebSocketConnect calls the websocket_connect host function.
|
||||
// Connect establishes a WebSocket connection to the specified URL.
|
||||
//
|
||||
// Plugins that use this function must also implement the WebSocketCallback capability
|
||||
// to receive incoming messages and connection events.
|
||||
//
|
||||
// Parameters:
|
||||
// - url: The WebSocket URL to connect to (ws:// or wss://)
|
||||
// - headers: Optional HTTP headers to include in the handshake request
|
||||
// - connectionID: Optional unique identifier for the connection. If empty, one will be generated
|
||||
//
|
||||
// Returns the connection ID that can be used to send messages or close the connection,
|
||||
// or an error if the connection fails.
|
||||
func WebSocketConnect(url string, headers map[string]string, connectionID string) (*WebSocketConnectResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := WebSocketConnectRequest{
|
||||
Url: url,
|
||||
Headers: headers,
|
||||
ConnectionID: connectionID,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := websocket_connect(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response WebSocketConnectResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// WebSocketSendText calls the websocket_sendtext host function.
|
||||
// SendText sends a text message over an established WebSocket connection.
|
||||
//
|
||||
// Parameters:
|
||||
// - connectionID: The connection identifier returned by Connect
|
||||
// - message: The text message to send
|
||||
//
|
||||
// Returns an error if the connection is not found or if sending fails.
|
||||
func WebSocketSendText(connectionID string, message string) (*WebSocketSendTextResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := WebSocketSendTextRequest{
|
||||
ConnectionID: connectionID,
|
||||
Message: message,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := websocket_sendtext(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response WebSocketSendTextResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// WebSocketSendBinary calls the websocket_sendbinary host function.
|
||||
// SendBinary sends binary data over an established WebSocket connection.
|
||||
//
|
||||
// Parameters:
|
||||
// - connectionID: The connection identifier returned by Connect
|
||||
// - data: The binary data to send
|
||||
//
|
||||
// Returns an error if the connection is not found or if sending fails.
|
||||
func WebSocketSendBinary(connectionID string, data []byte) (*WebSocketSendBinaryResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := WebSocketSendBinaryRequest{
|
||||
ConnectionID: connectionID,
|
||||
Data: data,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := websocket_sendbinary(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response WebSocketSendBinaryResponse
|
||||
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, nil
|
||||
}
|
||||
|
||||
// WebSocketCloseConnection calls the websocket_closeconnection host function.
|
||||
// CloseConnection gracefully closes a WebSocket connection.
|
||||
//
|
||||
// Parameters:
|
||||
// - connectionID: The connection identifier returned by Connect
|
||||
// - code: WebSocket close status code (e.g., 1000 for normal closure)
|
||||
// - reason: Optional human-readable reason for closing
|
||||
//
|
||||
// Returns an error if the connection is not found or if closing fails.
|
||||
func WebSocketCloseConnection(connectionID string, code int32, reason string) (*WebSocketCloseConnectionResponse, error) {
|
||||
// Marshal request to JSON
|
||||
req := WebSocketCloseConnectionRequest{
|
||||
ConnectionID: connectionID,
|
||||
Code: code,
|
||||
Reason: reason,
|
||||
}
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqMem := pdk.AllocateBytes(reqBytes)
|
||||
defer reqMem.Free()
|
||||
|
||||
// Call the host function
|
||||
responsePtr := websocket_closeconnection(reqMem.Offset())
|
||||
|
||||
// Read the response from memory
|
||||
responseMem := pdk.FindMemory(responsePtr)
|
||||
responseBytes := responseMem.ReadBytes()
|
||||
|
||||
// Parse the response
|
||||
var response WebSocketCloseConnectionResponse
|
||||
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, nil
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user