diff --git a/plugins/testdata/test-artwork/go.mod b/plugins/testdata/test-artwork/go.mod index 6fe319d3b..9e06d549d 100644 --- a/plugins/testdata/test-artwork/go.mod +++ b/plugins/testdata/test-artwork/go.mod @@ -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 diff --git a/plugins/testdata/test-artwork/main.go b/plugins/testdata/test-artwork/main.go index 2db857789..ae52efb77 100644 --- a/plugins/testdata/test-artwork/main.go +++ b/plugins/testdata/test-artwork/main.go @@ -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 { diff --git a/plugins/testdata/test-artwork/nd_host_artwork.go b/plugins/testdata/test-artwork/nd_host_artwork.go deleted file mode 100644 index 65577b877..000000000 --- a/plugins/testdata/test-artwork/nd_host_artwork.go +++ /dev/null @@ -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 -} diff --git a/plugins/testdata/test-cache-plugin/go.mod b/plugins/testdata/test-cache-plugin/go.mod index 06aedd113..c67f66def 100644 --- a/plugins/testdata/test-cache-plugin/go.mod +++ b/plugins/testdata/test-cache-plugin/go.mod @@ -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 diff --git a/plugins/testdata/test-cache-plugin/main.go b/plugins/testdata/test-cache-plugin/main.go index d427b0314..5db5e1d64 100644 --- a/plugins/testdata/test-cache-plugin/main.go +++ b/plugins/testdata/test-cache-plugin/main.go @@ -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}) diff --git a/plugins/testdata/test-cache-plugin/nd_host_cache.go b/plugins/testdata/test-cache-plugin/nd_host_cache.go deleted file mode 100644 index 81dbfa229..000000000 --- a/plugins/testdata/test-cache-plugin/nd_host_cache.go +++ /dev/null @@ -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 -} diff --git a/plugins/testdata/test-kvstore/go.mod b/plugins/testdata/test-kvstore/go.mod index 48d1417e9..e4124444d 100644 --- a/plugins/testdata/test-kvstore/go.mod +++ b/plugins/testdata/test-kvstore/go.mod @@ -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 diff --git a/plugins/testdata/test-kvstore/main.go b/plugins/testdata/test-kvstore/main.go index 1816c2d2b..48f9a2b2f 100644 --- a/plugins/testdata/test-kvstore/main.go +++ b/plugins/testdata/test-kvstore/main.go @@ -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}) diff --git a/plugins/testdata/test-kvstore/nd_host_kvstore.go b/plugins/testdata/test-kvstore/nd_host_kvstore.go deleted file mode 100644 index a59028a6b..000000000 --- a/plugins/testdata/test-kvstore/nd_host_kvstore.go +++ /dev/null @@ -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 -} diff --git a/plugins/testdata/test-library/go.mod b/plugins/testdata/test-library/go.mod index 7a91b13ab..43f53faa1 100644 --- a/plugins/testdata/test-library/go.mod +++ b/plugins/testdata/test-library/go.mod @@ -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 diff --git a/plugins/testdata/test-library/main.go b/plugins/testdata/test-library/main.go index eb3aa9226..7291ed9ff 100644 --- a/plugins/testdata/test-library/main.go +++ b/plugins/testdata/test-library/main.go @@ -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}) diff --git a/plugins/testdata/test-library/nd_host_library.go b/plugins/testdata/test-library/nd_host_library.go deleted file mode 100644 index 389721dc3..000000000 --- a/plugins/testdata/test-library/nd_host_library.go +++ /dev/null @@ -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 -} diff --git a/plugins/testdata/test-metadata-agent/go.mod b/plugins/testdata/test-metadata-agent/go.mod index c754156e4..3960287a9 100644 --- a/plugins/testdata/test-metadata-agent/go.mod +++ b/plugins/testdata/test-metadata-agent/go.mod @@ -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 => ../../.. diff --git a/plugins/testdata/test-metadata-agent/main.go b/plugins/testdata/test-metadata-agent/main.go index f4d682e1a..2098fc05d 100644 --- a/plugins/testdata/test-metadata-agent/main.go +++ b/plugins/testdata/test-metadata-agent/main.go @@ -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() {} diff --git a/plugins/testdata/test-scheduler/go.mod b/plugins/testdata/test-scheduler/go.mod index f46d2ed0e..edae26be2 100644 --- a/plugins/testdata/test-scheduler/go.mod +++ b/plugins/testdata/test-scheduler/go.mod @@ -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 diff --git a/plugins/testdata/test-scheduler/main.go b/plugins/testdata/test-scheduler/main.go index aaa2be2b5..76bcc9077 100644 --- a/plugins/testdata/test-scheduler/main.go +++ b/plugins/testdata/test-scheduler/main.go @@ -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:": 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 } } diff --git a/plugins/testdata/test-scheduler/nd_host_scheduler.go b/plugins/testdata/test-scheduler/nd_host_scheduler.go deleted file mode 100644 index 78c2e73f1..000000000 --- a/plugins/testdata/test-scheduler/nd_host_scheduler.go +++ /dev/null @@ -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 -} diff --git a/plugins/testdata/test-scheduler/pdk.gen.go b/plugins/testdata/test-scheduler/pdk.gen.go deleted file mode 100644 index 681bea057..000000000 --- a/plugins/testdata/test-scheduler/pdk.gen.go +++ /dev/null @@ -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"` -} diff --git a/plugins/testdata/test-subsonicapi-plugin/go.mod b/plugins/testdata/test-subsonicapi-plugin/go.mod index 6b8633c71..40e4672e0 100644 --- a/plugins/testdata/test-subsonicapi-plugin/go.mod +++ b/plugins/testdata/test-subsonicapi-plugin/go.mod @@ -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 diff --git a/plugins/testdata/test-subsonicapi-plugin/main.go b/plugins/testdata/test-subsonicapi-plugin/main.go index b02f6d6da..9192fc325 100644 --- a/plugins/testdata/test-subsonicapi-plugin/main.go +++ b/plugins/testdata/test-subsonicapi-plugin/main.go @@ -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 diff --git a/plugins/testdata/test-subsonicapi-plugin/nd_host_subsonicapi.go b/plugins/testdata/test-subsonicapi-plugin/nd_host_subsonicapi.go deleted file mode 100644 index 75f70c8a7..000000000 --- a/plugins/testdata/test-subsonicapi-plugin/nd_host_subsonicapi.go +++ /dev/null @@ -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 -} diff --git a/plugins/testdata/test-websocket/go.mod b/plugins/testdata/test-websocket/go.mod index b514a079d..9f1236728 100644 --- a/plugins/testdata/test-websocket/go.mod +++ b/plugins/testdata/test-websocket/go.mod @@ -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 diff --git a/plugins/testdata/test-websocket/main.go b/plugins/testdata/test-websocket/main.go index 6b958dd5f..bd5a2b6c4 100644 --- a/plugins/testdata/test-websocket/main.go +++ b/plugins/testdata/test-websocket/main.go @@ -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. diff --git a/plugins/testdata/test-websocket/nd_host_websocket.go b/plugins/testdata/test-websocket/nd_host_websocket.go deleted file mode 100644 index d0989de8e..000000000 --- a/plugins/testdata/test-websocket/nd_host_websocket.go +++ /dev/null @@ -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 -}