diff --git a/plugins/cmd/ndpgen/integration_test.go b/plugins/cmd/ndpgen/integration_test.go index 275d57fd0..8c9d899bd 100644 --- a/plugins/cmd/ndpgen/integration_test.go +++ b/plugins/cmd/ndpgen/integration_test.go @@ -79,7 +79,8 @@ type TestService interface { outputStr := string(output) Expect(outputStr).To(ContainSubstring("Input directory:")) - Expect(outputStr).To(ContainSubstring("Output directory:")) + Expect(outputStr).To(ContainSubstring("Base output directory:")) + Expect(outputStr).To(ContainSubstring("Go output directory:")) Expect(outputStr).To(ContainSubstring("Found 1 host service(s)")) Expect(outputStr).To(ContainSubstring("Generated")) }) @@ -93,7 +94,7 @@ type TestService interface { Expect(filepath.Join(outputDir, "nd_host_test.go")).ToNot(BeAnExistingFile()) }) - It("infers package name from output directory", func() { + It("uses default package name 'host'", func() { customOutput, err := os.MkdirTemp("", "mypkg") Expect(err).ToNot(HaveOccurred()) defer os.RemoveAll(customOutput) @@ -102,9 +103,10 @@ type TestService interface { _, err = cmd.CombinedOutput() Expect(err).ToNot(HaveOccurred()) - content, err := os.ReadFile(filepath.Join(customOutput, "nd_host_test.go")) + // Go code goes to $output/go/host/ + content, err := os.ReadFile(filepath.Join(customOutput, "go", "host", "nd_host_test.go")) Expect(err).ToNot(HaveOccurred()) - Expect(string(content)).To(ContainSubstring("package mypkg")) + Expect(string(content)).To(ContainSubstring("package host")) }) It("returns error for invalid input directory", func() { @@ -151,8 +153,10 @@ type ServiceB interface { Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output) Expect(string(output)).To(ContainSubstring("Found 2 host service(s)")) - Expect(filepath.Join(outputDir, "nd_host_servicea.go")).To(BeAnExistingFile()) - Expect(filepath.Join(outputDir, "nd_host_serviceb.go")).To(BeAnExistingFile()) + // Go code goes to $output/go/host/ + goHostDir := filepath.Join(outputDir, "go", "host") + Expect(filepath.Join(goHostDir, "nd_host_servicea.go")).To(BeAnExistingFile()) + Expect(filepath.Join(goHostDir, "nd_host_serviceb.go")).To(BeAnExistingFile()) }) It("generates Go client code by default", func() { @@ -160,13 +164,14 @@ type ServiceB interface { output, err := cmd.CombinedOutput() Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output) - // Client code in output directory - Expect(filepath.Join(outputDir, "nd_host_test.go")).To(BeAnExistingFile()) + // Go client code goes to $output/go/host/ + goHostDir := filepath.Join(outputDir, "go", "host") + Expect(filepath.Join(goHostDir, "nd_host_test.go")).To(BeAnExistingFile()) // Stub file also generated - Expect(filepath.Join(outputDir, "nd_host_test_stub.go")).To(BeAnExistingFile()) + Expect(filepath.Join(goHostDir, "nd_host_test_stub.go")).To(BeAnExistingFile()) // doc.go and go.mod also generated - Expect(filepath.Join(outputDir, "doc.go")).To(BeAnExistingFile()) - Expect(filepath.Join(outputDir, "go.mod")).To(BeAnExistingFile()) + Expect(filepath.Join(goHostDir, "doc.go")).To(BeAnExistingFile()) + Expect(filepath.Join(goHostDir, "go.mod")).To(BeAnExistingFile()) }) }) @@ -185,13 +190,14 @@ type ServiceB interface { output, err := cmd.CombinedOutput() Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output) - // Verify Go client code - entries, err := os.ReadDir(outputDir) + // Verify Go client code (now in $output/go/host/) + goHostDir := filepath.Join(outputDir, "go", "host") + entries, err := os.ReadDir(goHostDir) Expect(err).ToNot(HaveOccurred()) var goClientFiles []string for _, e := range entries { - if e.Name() != "python" && e.Name() != "rust" && !e.IsDir() && + if !e.IsDir() && !strings.HasSuffix(e.Name(), "_stub.go") && e.Name() != "doc.go" && e.Name() != "go.mod" { goClientFiles = append(goClientFiles, e.Name()) @@ -199,7 +205,7 @@ type ServiceB interface { } Expect(goClientFiles).To(HaveLen(1), "Expected exactly one Go client file, got: %v", goClientFiles) - goClientActual, err := os.ReadFile(filepath.Join(outputDir, goClientFiles[0])) + goClientActual, err := os.ReadFile(filepath.Join(goHostDir, goClientFiles[0])) Expect(err).ToNot(HaveOccurred()) formattedGoClientActual, err := format.Source(goClientActual) @@ -212,20 +218,20 @@ type ServiceB interface { Expect(string(formattedGoClientActual)).To(Equal(string(formattedGoClientExpected)), "Go client code mismatch") - // Verify Python client code - pythonDir := filepath.Join(outputDir, "python") - pyClientEntries, err := os.ReadDir(pythonDir) + // Verify Python client code (now in $output/python/host/) + pythonHostDir := filepath.Join(outputDir, "python", "host") + pyClientEntries, err := os.ReadDir(pythonHostDir) Expect(err).ToNot(HaveOccurred()) Expect(pyClientEntries).To(HaveLen(1), "Expected exactly one Python client file") - pyClientActual, err := os.ReadFile(filepath.Join(pythonDir, pyClientEntries[0].Name())) + pyClientActual, err := os.ReadFile(filepath.Join(pythonHostDir, pyClientEntries[0].Name())) Expect(err).ToNot(HaveOccurred()) Expect(string(pyClientActual)).To(Equal(pyClientExpected), "Python client code mismatch") - // Verify Rust client code - rustDir := filepath.Join(outputDir, "rust") - rsClientEntries, err := os.ReadDir(rustDir) + // Verify Rust client code (now in $output/rust/host/) + rustHostDir := filepath.Join(outputDir, "rust", "host") + rsClientEntries, err := os.ReadDir(rustHostDir) Expect(err).ToNot(HaveOccurred()) Expect(rsClientEntries).To(HaveLen(2), "Expected Rust client file and lib.rs") @@ -239,7 +245,7 @@ type ServiceB interface { } Expect(rsClientName).ToNot(BeEmpty(), "Expected to find Rust client file") - rsClientActual, err := os.ReadFile(filepath.Join(rustDir, rsClientName)) + rsClientActual, err := os.ReadFile(filepath.Join(rustHostDir, rsClientName)) Expect(err).ToNot(HaveOccurred()) Expect(string(rsClientActual)).To(Equal(rsClientExpected), "Rust client code mismatch") @@ -286,8 +292,11 @@ type ServiceB interface { output, err := cmd.CombinedOutput() Expect(err).ToNot(HaveOccurred(), "Generation failed: %s", output) + // Go code goes to $output/go/host/ + goHostDir := filepath.Join(outputDir, "go", "host") + // Read generated client code - entries, err := os.ReadDir(outputDir) + entries, err := os.ReadDir(goHostDir) Expect(err).ToNot(HaveOccurred()) // Find the client file @@ -301,7 +310,7 @@ type ServiceB interface { } Expect(clientFileName).ToNot(BeEmpty(), "Expected to find Go client file") - content, err := os.ReadFile(filepath.Join(outputDir, clientFileName)) + content, err := os.ReadFile(filepath.Join(goHostDir, clientFileName)) Expect(err).ToNot(HaveOccurred()) // Verify key expected content @@ -334,7 +343,7 @@ go 1.24 require github.com/navidrome/navidrome/plugins/pdk/go/host v0.0.0 replace github.com/navidrome/navidrome/plugins/pdk/go/host => %s -`, outputDir) +`, goHostDir) Expect(os.WriteFile(filepath.Join(pluginDir, "go.mod"), []byte(goMod), 0600)).To(Succeed()) // Add a simple main function that imports and uses the ndpdk package @@ -351,7 +360,7 @@ var _ = ndpdk.ComprehensiveNoParams // Tidy dependencies for the generated go library goTidyLibCmd := exec.Command("go", "mod", "tidy") - goTidyLibCmd.Dir = outputDir + goTidyLibCmd.Dir = goHostDir goTidyLibOutput, err := goTidyLibCmd.CombinedOutput() Expect(err).ToNot(HaveOccurred(), "go mod tidy (library) failed: %s", goTidyLibOutput) @@ -389,11 +398,11 @@ type TestService interface { output, err := cmd.CombinedOutput() Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output) - // Verify Python client code exists - pythonDir := filepath.Join(outputDir, "python") - Expect(pythonDir).To(BeADirectory()) + // Verify Python client code exists in $output/python/host/ + pythonHostDir := filepath.Join(outputDir, "python", "host") + Expect(pythonHostDir).To(BeADirectory()) - pythonFile := filepath.Join(pythonDir, "nd_host_test.py") + pythonFile := filepath.Join(pythonHostDir, "nd_host_test.py") Expect(pythonFile).To(BeAnExistingFile()) content, err := os.ReadFile(pythonFile) @@ -423,12 +432,14 @@ type TestService interface { output, err := cmd.CombinedOutput() Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output) - // Verify both Go and Python client code exist - Expect(filepath.Join(outputDir, "nd_host_test.go")).To(BeAnExistingFile()) + // Verify Go client code exists in $output/go/host/ + goHostDir := filepath.Join(outputDir, "go", "host") + Expect(filepath.Join(goHostDir, "nd_host_test.go")).To(BeAnExistingFile()) - pythonDir := filepath.Join(outputDir, "python") - Expect(pythonDir).To(BeADirectory()) - Expect(filepath.Join(pythonDir, "nd_host_test.py")).To(BeAnExistingFile()) + // Verify Python client code exists in $output/python/host/ + pythonHostDir := filepath.Join(outputDir, "python", "host") + Expect(pythonHostDir).To(BeADirectory()) + Expect(filepath.Join(pythonHostDir, "nd_host_test.py")).To(BeAnExistingFile()) }) It("generates Python code with dataclass for multi-value returns", func() { @@ -448,7 +459,7 @@ type CacheService interface { output, err := cmd.CombinedOutput() Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output) - content, err := os.ReadFile(filepath.Join(outputDir, "python", "nd_host_cache.py")) + content, err := os.ReadFile(filepath.Join(outputDir, "python", "host", "nd_host_cache.py")) Expect(err).ToNot(HaveOccurred()) contentStr := string(content) @@ -476,7 +487,7 @@ type TestService interface { output, err := cmd.CombinedOutput() Expect(err).ToNot(HaveOccurred(), "Command failed: %s", output) - content, err := os.ReadFile(filepath.Join(outputDir, "python", "nd_host_test.py")) + content, err := os.ReadFile(filepath.Join(outputDir, "python", "host", "nd_host_test.py")) Expect(err).ToNot(HaveOccurred()) contentStr := string(content) diff --git a/plugins/cmd/ndpgen/main.go b/plugins/cmd/ndpgen/main.go index f86c04ef4..ee1e2492a 100644 --- a/plugins/cmd/ndpgen/main.go +++ b/plugins/cmd/ndpgen/main.go @@ -5,13 +5,18 @@ // // Usage: // -// ndpgen -input=./plugins/host -output=./plugins/pdk/go/host +// ndpgen -input=./plugins/host -output=./plugins/pdk +// +// This generates code into language-specific subdirectories: +// - Go: $output/go/host/ +// - Python: $output/python/host/ +// - Rust: $output/rust/host/ // // Flags: // // -input Input directory containing Go source files with annotated interfaces -// -output Output directory for generated files (default: same as input) -// -package Output package name (default: inferred from output directory) +// -output Output directory base for generated files (default: same as input) +// -package Output package name for Go (default: host) // -host-only Generate only host function wrappers (default: true, capability support TBD) // -go Generate Go client wrappers (default: true when not using -python/-rust) // -python Generate Python client wrappers (default: false) @@ -34,7 +39,10 @@ import ( // config holds the parsed command-line configuration. type config struct { inputDir string - outputDir string + outputDir string // Base output directory (e.g., plugins/pdk) + goOutputDir string // Go output: $outputDir/go/host + pythonOutputDir string // Python output: $outputDir/python/host + rustOutputDir string // Rust output: $outputDir/rust/host pkgName string hostOnly bool generateGoClient bool @@ -70,8 +78,8 @@ func main() { func parseConfig() (*config, error) { var ( inputDir = flag.String("input", ".", "Input directory containing Go source files") - outputDir = flag.String("output", "", "Output directory for generated files (default: same as input)") - pkgName = flag.String("package", "", "Output package name (default: inferred from output directory)") + outputDir = flag.String("output", "", "Base output directory for generated files (default: same as input)") + pkgName = flag.String("package", "host", "Output package name for Go (default: host)") hostOnly = flag.Bool("host-only", true, "Generate only host function wrappers (capability support TBD)") goClient = flag.Bool("go", false, "Generate Go client wrappers") pyClient = flag.Bool("python", false, "Generate Python client wrappers") @@ -94,9 +102,10 @@ func parseConfig() (*config, error) { return nil, fmt.Errorf("resolving output path: %w", err) } - if *pkgName == "" { - *pkgName = filepath.Base(absOutput) - } + // Set output directories for each language: $output/$lang/host/ + absGoOutput := filepath.Join(absOutput, "go", "host") + absPythonOutput := filepath.Join(absOutput, "python", "host") + absRustOutput := filepath.Join(absOutput, "rust", "host") // Determine what to generate // Default: generate Go clients if no language flag is specified @@ -105,6 +114,9 @@ func parseConfig() (*config, error) { return &config{ inputDir: absInput, outputDir: absOutput, + goOutputDir: absGoOutput, + pythonOutputDir: absPythonOutput, + rustOutputDir: absRustOutput, pkgName: *pkgName, hostOnly: *hostOnly, generateGoClient: *goClient || !anyLangFlag, @@ -119,7 +131,16 @@ func parseConfig() (*config, error) { func parseServices(cfg *config) ([]internal.Service, error) { if cfg.verbose { fmt.Printf("Input directory: %s\n", cfg.inputDir) - fmt.Printf("Output directory: %s\n", cfg.outputDir) + fmt.Printf("Base output directory: %s\n", cfg.outputDir) + if cfg.generateGoClient { + fmt.Printf("Go output directory: %s\n", cfg.goOutputDir) + } + if cfg.generatePyClient { + fmt.Printf("Python output directory: %s\n", cfg.pythonOutputDir) + } + if cfg.generateRsClient { + fmt.Printf("Rust output directory: %s\n", cfg.rustOutputDir) + } fmt.Printf("Package name: %s\n", cfg.pkgName) fmt.Printf("Host-only mode: %v\n", cfg.hostOnly) fmt.Printf("Generate Go client code: %v\n", cfg.generateGoClient) @@ -153,33 +174,33 @@ func parseServices(cfg *config) ([]internal.Service, error) { func generateAllCode(cfg *config, services []internal.Service) error { for _, svc := range services { if cfg.generateGoClient { - if err := generateGoClientCode(svc, cfg.outputDir, cfg.pkgName, cfg.dryRun, cfg.verbose); err != nil { + if err := generateGoClientCode(svc, cfg.goOutputDir, cfg.pkgName, cfg.dryRun, cfg.verbose); err != nil { return fmt.Errorf("generating Go client code for %s: %w", svc.Name, err) } } if cfg.generatePyClient { - if err := generatePythonClientCode(svc, cfg.outputDir, cfg.dryRun, cfg.verbose); err != nil { + if err := generatePythonClientCode(svc, cfg.pythonOutputDir, cfg.dryRun, cfg.verbose); err != nil { return fmt.Errorf("generating Python client code for %s: %w", svc.Name, err) } } if cfg.generateRsClient { - if err := generateRustClientCode(svc, cfg.outputDir, cfg.dryRun, cfg.verbose); err != nil { + if err := generateRustClientCode(svc, cfg.rustOutputDir, cfg.dryRun, cfg.verbose); err != nil { return fmt.Errorf("generating Rust client code for %s: %w", svc.Name, err) } } } if cfg.generateRsClient && len(services) > 0 { - if err := generateRustLibFile(services, cfg.outputDir, cfg.dryRun, cfg.verbose); err != nil { + if err := generateRustLibFile(services, cfg.rustOutputDir, cfg.dryRun, cfg.verbose); err != nil { return fmt.Errorf("generating Rust lib.rs: %w", err) } } if cfg.generateGoClient && len(services) > 0 { - if err := generateGoDocFile(services, cfg.outputDir, cfg.pkgName, cfg.dryRun, cfg.verbose); err != nil { + if err := generateGoDocFile(services, cfg.goOutputDir, cfg.pkgName, cfg.dryRun, cfg.verbose); err != nil { return fmt.Errorf("generating Go doc.go: %w", err) } - if err := generateGoModFile(cfg.outputDir, cfg.dryRun, cfg.verbose); err != nil { + if err := generateGoModFile(cfg.goOutputDir, cfg.dryRun, cfg.verbose); err != nil { return fmt.Errorf("generating Go go.mod: %w", err) } } @@ -265,17 +286,16 @@ func generatePythonClientCode(svc internal.Service, outputDir string, dryRun, ve return fmt.Errorf("generating code: %w", err) } - // Python code goes in python/ subdirectory - clientDir := filepath.Join(outputDir, "python") - clientFile := filepath.Join(clientDir, "nd_host_"+strings.ToLower(svc.Name)+".py") + // Python code goes directly in the output directory + clientFile := filepath.Join(outputDir, "nd_host_"+strings.ToLower(svc.Name)+".py") if dryRun { fmt.Printf("=== %s ===\n%s\n", clientFile, code) return nil } - // Create python/ subdirectory if needed - if err := os.MkdirAll(clientDir, 0755); err != nil { + // Create output directory if needed + if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("creating python client directory: %w", err) } @@ -296,17 +316,16 @@ func generateRustClientCode(svc internal.Service, outputDir string, dryRun, verb return fmt.Errorf("generating code: %w", err) } - // Rust code goes in rust/ subdirectory - clientDir := filepath.Join(outputDir, "rust") - clientFile := filepath.Join(clientDir, "nd_host_"+strings.ToLower(svc.Name)+".rs") + // Rust code goes directly in the output directory + clientFile := filepath.Join(outputDir, "nd_host_"+strings.ToLower(svc.Name)+".rs") if dryRun { fmt.Printf("=== %s ===\n%s\n", clientFile, code) return nil } - // Create rust/ subdirectory if needed - if err := os.MkdirAll(clientDir, 0755); err != nil { + // Create output directory if needed + if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("creating rust client directory: %w", err) } @@ -327,16 +346,16 @@ func generateRustLibFile(services []internal.Service, outputDir string, dryRun, return fmt.Errorf("generating lib.rs: %w", err) } - clientDir := filepath.Join(outputDir, "rust") - libFile := filepath.Join(clientDir, "lib.rs") + // lib.rs goes directly in the output directory + libFile := filepath.Join(outputDir, "lib.rs") if dryRun { fmt.Printf("=== %s ===\n%s\n", libFile, code) return nil } - // Create rust/ subdirectory if needed - if err := os.MkdirAll(clientDir, 0755); err != nil { + // Create output directory if needed + if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("creating rust client directory: %w", err) } diff --git a/plugins/pdk/go/host/doc.go b/plugins/pdk/go/host/doc.go new file mode 100644 index 000000000..3152781c2 --- /dev/null +++ b/plugins/pdk/go/host/doc.go @@ -0,0 +1,54 @@ +// Code generated by ndpgen. DO NOT EDIT. + +/* +Package host provides Navidrome Plugin Development Kit wrappers for Go/TinyGo plugins. + +This package is auto-generated by the ndpgen tool and should not be edited manually. + +# Usage + +Add this module as a dependency in your plugin's go.mod: + + require github.com/navidrome/navidrome/plugins/pdk/go/host v0.0.0 + +Then import the package in your plugin code: + + import host "github.com/navidrome/navidrome/plugins/pdk/go/host" + + func myPluginFunction() error { + // Use the cache service + _, err := host.CacheSetString("my_key", "my_value", 3600) + if err != nil { + return err + } + + // Schedule a recurring task + _, err = host.SchedulerScheduleRecurring("@every 5m", "payload", "task_id") + if err != nil { + return err + } + + return nil + } + +# Available Services + +The following host services are available: + + - Artwork: provides artwork URL generation capabilities for plugins. + - Cache: provides in-memory TTL-based caching capabilities for plugins. + - KVStore: provides persistent key-value storage for plugins. + - Library: provides access to music library metadata for plugins. + - Scheduler: provides task scheduling capabilities for plugins. + - SubsonicAPI: provides access to Navidrome's Subsonic API from plugins. + - WebSocket: provides WebSocket communication capabilities for plugins. + +# Building Plugins + +Go plugins must be compiled to WebAssembly using TinyGo: + + tinygo build -o plugin.wasm -target=wasip1 -buildmode=c-shared . + +See the examples directory for complete plugin implementations. +*/ +package host diff --git a/plugins/pdk/go/host/go.mod b/plugins/pdk/go/host/go.mod new file mode 100644 index 000000000..db230df3a --- /dev/null +++ b/plugins/pdk/go/host/go.mod @@ -0,0 +1,5 @@ +module github.com/navidrome/navidrome/plugins/pdk/go/host + +go 1.24 + +require github.com/extism/go-pdk v1.1.3 diff --git a/plugins/pdk/go/host/nd_host_artwork.go b/plugins/pdk/go/host/nd_host_artwork.go new file mode 100644 index 000000000..e2db87ff4 --- /dev/null +++ b/plugins/pdk/go/host/nd_host_artwork.go @@ -0,0 +1,251 @@ +// Code generated by ndpgen. 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 host + +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/pdk/go/host/nd_host_artwork_stub.go b/plugins/pdk/go/host/nd_host_artwork_stub.go new file mode 100644 index 000000000..522f63259 --- /dev/null +++ b/plugins/pdk/go/host/nd_host_artwork_stub.go @@ -0,0 +1,105 @@ +// Code generated by ndpgen. DO NOT EDIT. +// +// This file contains stub implementations for non-WASM builds. +// These stubs allow IDE support and compilation on non-WASM platforms. +// They panic at runtime since host functions are only available in WASM plugins. +// +//go:build !wasip1 + +package host + +// 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 is a stub that panics on non-WASM platforms. +// 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) { + panic("host: ArtworkGetArtistUrl is only available in WASM plugins") +} + +// ArtworkGetAlbumUrl is a stub that panics on non-WASM platforms. +// 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) { + panic("host: ArtworkGetAlbumUrl is only available in WASM plugins") +} + +// ArtworkGetTrackUrl is a stub that panics on non-WASM platforms. +// 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) { + panic("host: ArtworkGetTrackUrl is only available in WASM plugins") +} + +// ArtworkGetPlaylistUrl is a stub that panics on non-WASM platforms. +// 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) { + panic("host: ArtworkGetPlaylistUrl is only available in WASM plugins") +} diff --git a/plugins/pdk/go/host/nd_host_cache.go b/plugins/pdk/go/host/nd_host_cache.go new file mode 100644 index 000000000..c58cc09d7 --- /dev/null +++ b/plugins/pdk/go/host/nd_host_cache.go @@ -0,0 +1,602 @@ +// Code generated by ndpgen. 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 host + +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/pdk/go/host/nd_host_cache_stub.go b/plugins/pdk/go/host/nd_host_cache_stub.go new file mode 100644 index 000000000..c19fdefe2 --- /dev/null +++ b/plugins/pdk/go/host/nd_host_cache_stub.go @@ -0,0 +1,248 @@ +// Code generated by ndpgen. DO NOT EDIT. +// +// This file contains stub implementations for non-WASM builds. +// These stubs allow IDE support and compilation on non-WASM platforms. +// They panic at runtime since host functions are only available in WASM plugins. +// +//go:build !wasip1 + +package host + +// 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 is a stub that panics on non-WASM platforms. +// 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) { + panic("host: CacheSetString is only available in WASM plugins") +} + +// CacheGetString is a stub that panics on non-WASM platforms. +// 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) { + panic("host: CacheGetString is only available in WASM plugins") +} + +// CacheSetInt is a stub that panics on non-WASM platforms. +// 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) { + panic("host: CacheSetInt is only available in WASM plugins") +} + +// CacheGetInt is a stub that panics on non-WASM platforms. +// 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) { + panic("host: CacheGetInt is only available in WASM plugins") +} + +// CacheSetFloat is a stub that panics on non-WASM platforms. +// 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) { + panic("host: CacheSetFloat is only available in WASM plugins") +} + +// CacheGetFloat is a stub that panics on non-WASM platforms. +// 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) { + panic("host: CacheGetFloat is only available in WASM plugins") +} + +// CacheSetBytes is a stub that panics on non-WASM platforms. +// 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) { + panic("host: CacheSetBytes is only available in WASM plugins") +} + +// CacheGetBytes is a stub that panics on non-WASM platforms. +// 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) { + panic("host: CacheGetBytes is only available in WASM plugins") +} + +// CacheHas is a stub that panics on non-WASM platforms. +// 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) { + panic("host: CacheHas is only available in WASM plugins") +} + +// CacheRemove is a stub that panics on non-WASM platforms. +// 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) { + panic("host: CacheRemove is only available in WASM plugins") +} diff --git a/plugins/pdk/go/host/nd_host_kvstore.go b/plugins/pdk/go/host/nd_host_kvstore.go new file mode 100644 index 000000000..92c141db6 --- /dev/null +++ b/plugins/pdk/go/host/nd_host_kvstore.go @@ -0,0 +1,336 @@ +// Code generated by ndpgen. 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 host + +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/pdk/go/host/nd_host_kvstore_stub.go b/plugins/pdk/go/host/nd_host_kvstore_stub.go new file mode 100644 index 000000000..3b3fe494a --- /dev/null +++ b/plugins/pdk/go/host/nd_host_kvstore_stub.go @@ -0,0 +1,132 @@ +// Code generated by ndpgen. DO NOT EDIT. +// +// This file contains stub implementations for non-WASM builds. +// These stubs allow IDE support and compilation on non-WASM platforms. +// They panic at runtime since host functions are only available in WASM plugins. +// +//go:build !wasip1 + +package host + +// 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 is a stub that panics on non-WASM platforms. +// 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) { + panic("host: KVStoreSet is only available in WASM plugins") +} + +// KVStoreGet is a stub that panics on non-WASM platforms. +// 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) { + panic("host: KVStoreGet is only available in WASM plugins") +} + +// KVStoreDelete is a stub that panics on non-WASM platforms. +// 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) { + panic("host: KVStoreDelete is only available in WASM plugins") +} + +// KVStoreHas is a stub that panics on non-WASM platforms. +// 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) { + panic("host: KVStoreHas is only available in WASM plugins") +} + +// KVStoreList is a stub that panics on non-WASM platforms. +// 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) { + panic("host: KVStoreList is only available in WASM plugins") +} + +// KVStoreGetStorageUsed is a stub that panics on non-WASM platforms. +// GetStorageUsed returns the total storage used by this plugin in bytes. +func KVStoreGetStorageUsed() (*KVStoreGetStorageUsedResponse, error) { + panic("host: KVStoreGetStorageUsed is only available in WASM plugins") +} diff --git a/plugins/pdk/go/host/nd_host_library.go b/plugins/pdk/go/host/nd_host_library.go new file mode 100644 index 000000000..047b97407 --- /dev/null +++ b/plugins/pdk/go/host/nd_host_library.go @@ -0,0 +1,127 @@ +// Code generated by ndpgen. 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 host + +import ( + "encoding/json" + "errors" + + "github.com/extism/go-pdk" +) + +// Library represents the Library data structure. +// Library represents a music library with metadata. +type Library struct { + ID int32 `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + MountPoint string `json:"mountPoint"` + 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/pdk/go/host/nd_host_library_stub.go b/plugins/pdk/go/host/nd_host_library_stub.go new file mode 100644 index 000000000..6c2f004ec --- /dev/null +++ b/plugins/pdk/go/host/nd_host_library_stub.go @@ -0,0 +1,60 @@ +// Code generated by ndpgen. DO NOT EDIT. +// +// This file contains stub implementations for non-WASM builds. +// These stubs allow IDE support and compilation on non-WASM platforms. +// They panic at runtime since host functions are only available in WASM plugins. +// +//go:build !wasip1 + +package host + +// Library represents the Library data structure. +// Library represents a music library with metadata. +type Library struct { + ID int32 `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + MountPoint string `json:"mountPoint"` + LastScanAt int64 `json:"lastScanAt"` + TotalSongs int32 `json:"totalSongs"` + TotalAlbums int32 `json:"totalAlbums"` + TotalArtists int32 `json:"totalArtists"` + TotalSize int64 `json:"totalSize"` + TotalDuration float64 `json:"totalDuration"` +} + +// 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 is a stub that panics on non-WASM platforms. +// 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) { + panic("host: LibraryGetLibrary is only available in WASM plugins") +} + +// LibraryGetAllLibraries is a stub that panics on non-WASM platforms. +// GetAllLibraries retrieves metadata for all configured libraries. +// +// Returns a slice of all libraries with their metadata. +func LibraryGetAllLibraries() (*LibraryGetAllLibrariesResponse, error) { + panic("host: LibraryGetAllLibraries is only available in WASM plugins") +} diff --git a/plugins/pdk/go/host/nd_host_scheduler.go b/plugins/pdk/go/host/nd_host_scheduler.go new file mode 100644 index 000000000..144ca26fc --- /dev/null +++ b/plugins/pdk/go/host/nd_host_scheduler.go @@ -0,0 +1,196 @@ +// Code generated by ndpgen. 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 host + +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/pdk/go/host/nd_host_scheduler_stub.go b/plugins/pdk/go/host/nd_host_scheduler_stub.go new file mode 100644 index 000000000..dde181810 --- /dev/null +++ b/plugins/pdk/go/host/nd_host_scheduler_stub.go @@ -0,0 +1,84 @@ +// Code generated by ndpgen. DO NOT EDIT. +// +// This file contains stub implementations for non-WASM builds. +// These stubs allow IDE support and compilation on non-WASM platforms. +// They panic at runtime since host functions are only available in WASM plugins. +// +//go:build !wasip1 + +package host + +// 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 is a stub that panics on non-WASM platforms. +// 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) { + panic("host: SchedulerScheduleOneTime is only available in WASM plugins") +} + +// SchedulerScheduleRecurring is a stub that panics on non-WASM platforms. +// 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) { + panic("host: SchedulerScheduleRecurring is only available in WASM plugins") +} + +// SchedulerCancelSchedule is a stub that panics on non-WASM platforms. +// 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) { + panic("host: SchedulerCancelSchedule is only available in WASM plugins") +} diff --git a/plugins/pdk/go/host/nd_host_subsonicapi.go b/plugins/pdk/go/host/nd_host_subsonicapi.go new file mode 100644 index 000000000..70fbf9db9 --- /dev/null +++ b/plugins/pdk/go/host/nd_host_subsonicapi.go @@ -0,0 +1,69 @@ +// Code generated by ndpgen. 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 host + +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/pdk/go/host/nd_host_subsonicapi_stub.go b/plugins/pdk/go/host/nd_host_subsonicapi_stub.go new file mode 100644 index 000000000..47c1c8db0 --- /dev/null +++ b/plugins/pdk/go/host/nd_host_subsonicapi_stub.go @@ -0,0 +1,29 @@ +// Code generated by ndpgen. DO NOT EDIT. +// +// This file contains stub implementations for non-WASM builds. +// These stubs allow IDE support and compilation on non-WASM platforms. +// They panic at runtime since host functions are only available in WASM plugins. +// +//go:build !wasip1 + +package host + +// 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 is a stub that panics on non-WASM platforms. +// 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) { + panic("host: SubsonicAPICall is only available in WASM plugins") +} diff --git a/plugins/pdk/go/host/nd_host_websocket.go b/plugins/pdk/go/host/nd_host_websocket.go new file mode 100644 index 000000000..cb653488b --- /dev/null +++ b/plugins/pdk/go/host/nd_host_websocket.go @@ -0,0 +1,258 @@ +// Code generated by ndpgen. 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 host + +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 +} diff --git a/plugins/pdk/go/host/nd_host_websocket_stub.go b/plugins/pdk/go/host/nd_host_websocket_stub.go new file mode 100644 index 000000000..27a453fdb --- /dev/null +++ b/plugins/pdk/go/host/nd_host_websocket_stub.go @@ -0,0 +1,110 @@ +// Code generated by ndpgen. DO NOT EDIT. +// +// This file contains stub implementations for non-WASM builds. +// These stubs allow IDE support and compilation on non-WASM platforms. +// They panic at runtime since host functions are only available in WASM plugins. +// +//go:build !wasip1 + +package host + +// 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 is a stub that panics on non-WASM platforms. +// 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) { + panic("host: WebSocketConnect is only available in WASM plugins") +} + +// WebSocketSendText is a stub that panics on non-WASM platforms. +// 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) { + panic("host: WebSocketSendText is only available in WASM plugins") +} + +// WebSocketSendBinary is a stub that panics on non-WASM platforms. +// 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) { + panic("host: WebSocketSendBinary is only available in WASM plugins") +} + +// WebSocketCloseConnection is a stub that panics on non-WASM platforms. +// 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) { + panic("host: WebSocketCloseConnection is only available in WASM plugins") +} diff --git a/plugins/pdk/python/host/nd_host_artwork.py b/plugins/pdk/python/host/nd_host_artwork.py new file mode 100644 index 000000000..3919dbbe9 --- /dev/null +++ b/plugins/pdk/python/host/nd_host_artwork.py @@ -0,0 +1,183 @@ +# 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 extism-py. +# +# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly. +# The @extism.import_fn decorators are only detected when defined in the plugin's +# main __init__.py file. Copy the needed functions from this file into your plugin. + +from dataclasses import dataclass +from typing import Any + +import extism +import json + + +class HostFunctionError(Exception): + """Raised when a host function returns an error.""" + pass + + +@extism.import_fn("extism:host/user", "artwork_getartisturl") +def _artwork_getartisturl(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "artwork_getalbumurl") +def _artwork_getalbumurl(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "artwork_gettrackurl") +def _artwork_gettrackurl(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "artwork_getplaylisturl") +def _artwork_getplaylisturl(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +def artwork_get_artist_url(id: str, size: int) -> str: + """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. + + Args: + id: str parameter. + size: int parameter. + + Returns: + str: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "id": id, + "size": size, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _artwork_getartisturl(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("url", "") + + +def artwork_get_album_url(id: str, size: int) -> str: + """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. + + Args: + id: str parameter. + size: int parameter. + + Returns: + str: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "id": id, + "size": size, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _artwork_getalbumurl(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("url", "") + + +def artwork_get_track_url(id: str, size: int) -> str: + """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. + + Args: + id: str parameter. + size: int parameter. + + Returns: + str: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "id": id, + "size": size, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _artwork_gettrackurl(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("url", "") + + +def artwork_get_playlist_url(id: str, size: int) -> str: + """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. + + Args: + id: str parameter. + size: int parameter. + + Returns: + str: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "id": id, + "size": size, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _artwork_getplaylisturl(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("url", "") diff --git a/plugins/pdk/python/host/nd_host_cache.py b/plugins/pdk/python/host/nd_host_cache.py new file mode 100644 index 000000000..f06b2ad0c --- /dev/null +++ b/plugins/pdk/python/host/nd_host_cache.py @@ -0,0 +1,447 @@ +# 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 extism-py. +# +# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly. +# The @extism.import_fn decorators are only detected when defined in the plugin's +# main __init__.py file. Copy the needed functions from this file into your plugin. + +from dataclasses import dataclass +from typing import Any + +import extism +import json + + +class HostFunctionError(Exception): + """Raised when a host function returns an error.""" + pass + + +@extism.import_fn("extism:host/user", "cache_setstring") +def _cache_setstring(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "cache_getstring") +def _cache_getstring(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "cache_setint") +def _cache_setint(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "cache_getint") +def _cache_getint(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "cache_setfloat") +def _cache_setfloat(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "cache_getfloat") +def _cache_getfloat(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "cache_setbytes") +def _cache_setbytes(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "cache_getbytes") +def _cache_getbytes(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "cache_has") +def _cache_has(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "cache_remove") +def _cache_remove(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@dataclass +class CacheGetStringResult: + """Result type for cache_get_string.""" + value: str + exists: bool + + +@dataclass +class CacheGetIntResult: + """Result type for cache_get_int.""" + value: int + exists: bool + + +@dataclass +class CacheGetFloatResult: + """Result type for cache_get_float.""" + value: float + exists: bool + + +@dataclass +class CacheGetBytesResult: + """Result type for cache_get_bytes.""" + value: bytes + exists: bool + + +def cache_set_string(key: str, value: str, ttl_seconds: int) -> None: + """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. + + Args: + key: str parameter. + value: str parameter. + ttl_seconds: int parameter. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + "value": value, + "ttlSeconds": ttl_seconds, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _cache_setstring(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + + +def cache_get_string(key: str) -> CacheGetStringResult: + """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. + + Args: + key: str parameter. + + Returns: + CacheGetStringResult containing value, exists,. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _cache_getstring(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return CacheGetStringResult( + value=response.get("value", ""), + exists=response.get("exists", False), + ) + + +def cache_set_int(key: str, value: int, ttl_seconds: int) -> None: + """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. + + Args: + key: str parameter. + value: int parameter. + ttl_seconds: int parameter. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + "value": value, + "ttlSeconds": ttl_seconds, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _cache_setint(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + + +def cache_get_int(key: str) -> CacheGetIntResult: + """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. + + Args: + key: str parameter. + + Returns: + CacheGetIntResult containing value, exists,. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _cache_getint(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return CacheGetIntResult( + value=response.get("value", 0), + exists=response.get("exists", False), + ) + + +def cache_set_float(key: str, value: float, ttl_seconds: int) -> None: + """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. + + Args: + key: str parameter. + value: float parameter. + ttl_seconds: int parameter. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + "value": value, + "ttlSeconds": ttl_seconds, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _cache_setfloat(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + + +def cache_get_float(key: str) -> CacheGetFloatResult: + """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. + + Args: + key: str parameter. + + Returns: + CacheGetFloatResult containing value, exists,. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _cache_getfloat(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return CacheGetFloatResult( + value=response.get("value", 0.0), + exists=response.get("exists", False), + ) + + +def cache_set_bytes(key: str, value: bytes, ttl_seconds: int) -> None: + """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. + + Args: + key: str parameter. + value: bytes parameter. + ttl_seconds: int parameter. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + "value": value, + "ttlSeconds": ttl_seconds, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _cache_setbytes(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + + +def cache_get_bytes(key: str) -> CacheGetBytesResult: + """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. + + Args: + key: str parameter. + + Returns: + CacheGetBytesResult containing value, exists,. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _cache_getbytes(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return CacheGetBytesResult( + value=response.get("value", b""), + exists=response.get("exists", False), + ) + + +def cache_has(key: str) -> bool: + """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. + + Args: + key: str parameter. + + Returns: + bool: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _cache_has(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("exists", False) + + +def cache_remove(key: str) -> None: + """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. + + Args: + key: str parameter. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _cache_remove(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + diff --git a/plugins/pdk/python/host/nd_host_kvstore.py b/plugins/pdk/python/host/nd_host_kvstore.py new file mode 100644 index 000000000..f1a6e5724 --- /dev/null +++ b/plugins/pdk/python/host/nd_host_kvstore.py @@ -0,0 +1,241 @@ +# 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 extism-py. +# +# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly. +# The @extism.import_fn decorators are only detected when defined in the plugin's +# main __init__.py file. Copy the needed functions from this file into your plugin. + +from dataclasses import dataclass +from typing import Any + +import extism +import json + + +class HostFunctionError(Exception): + """Raised when a host function returns an error.""" + pass + + +@extism.import_fn("extism:host/user", "kvstore_set") +def _kvstore_set(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "kvstore_get") +def _kvstore_get(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "kvstore_delete") +def _kvstore_delete(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "kvstore_has") +def _kvstore_has(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "kvstore_list") +def _kvstore_list(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "kvstore_getstorageused") +def _kvstore_getstorageused(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@dataclass +class KVStoreGetResult: + """Result type for kvstore_get.""" + value: bytes + exists: bool + + +def kvstore_set(key: str, value: bytes) -> None: + """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. + + Args: + key: str parameter. + value: bytes parameter. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + "value": value, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _kvstore_set(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + + +def kvstore_get(key: str) -> KVStoreGetResult: + """Get retrieves a byte value from storage. + +Parameters: + - key: The storage key + +Returns the value and whether the key exists. + + Args: + key: str parameter. + + Returns: + KVStoreGetResult containing value, exists,. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _kvstore_get(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return KVStoreGetResult( + value=response.get("value", b""), + exists=response.get("exists", False), + ) + + +def kvstore_delete(key: str) -> None: + """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. + + Args: + key: str parameter. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _kvstore_delete(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + + +def kvstore_has(key: str) -> bool: + """Has checks if a key exists in storage. + +Parameters: + - key: The storage key + +Returns true if the key exists. + + Args: + key: str parameter. + + Returns: + bool: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "key": key, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _kvstore_has(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("exists", False) + + +def kvstore_list(prefix: str) -> Any: + """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. + + Args: + prefix: str parameter. + + Returns: + Any: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "prefix": prefix, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _kvstore_list(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("keys", None) + + +def kvstore_get_storage_used() -> int: + """GetStorageUsed returns the total storage used by this plugin in bytes. + + Returns: + int: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request_bytes = b"{}" + request_mem = extism.memory.alloc(request_bytes) + response_offset = _kvstore_getstorageused(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("bytes", 0) diff --git a/plugins/pdk/python/host/nd_host_library.py b/plugins/pdk/python/host/nd_host_library.py new file mode 100644 index 000000000..4111df2b9 --- /dev/null +++ b/plugins/pdk/python/host/nd_host_library.py @@ -0,0 +1,86 @@ +# 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 extism-py. +# +# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly. +# The @extism.import_fn decorators are only detected when defined in the plugin's +# main __init__.py file. Copy the needed functions from this file into your plugin. + +from dataclasses import dataclass +from typing import Any + +import extism +import json + + +class HostFunctionError(Exception): + """Raised when a host function returns an error.""" + pass + + +@extism.import_fn("extism:host/user", "library_getlibrary") +def _library_getlibrary(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "library_getalllibraries") +def _library_getalllibraries(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +def library_get_library(id: int) -> Any: + """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. + + Args: + id: int parameter. + + Returns: + Any: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "id": id, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _library_getlibrary(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("result", None) + + +def library_get_all_libraries() -> Any: + """GetAllLibraries retrieves metadata for all configured libraries. + +Returns a slice of all libraries with their metadata. + + Returns: + Any: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request_bytes = b"{}" + request_mem = extism.memory.alloc(request_bytes) + response_offset = _library_getalllibraries(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("result", None) diff --git a/plugins/pdk/python/host/nd_host_scheduler.py b/plugins/pdk/python/host/nd_host_scheduler.py new file mode 100644 index 000000000..3a94da94d --- /dev/null +++ b/plugins/pdk/python/host/nd_host_scheduler.py @@ -0,0 +1,143 @@ +# 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 extism-py. +# +# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly. +# The @extism.import_fn decorators are only detected when defined in the plugin's +# main __init__.py file. Copy the needed functions from this file into your plugin. + +from dataclasses import dataclass +from typing import Any + +import extism +import json + + +class HostFunctionError(Exception): + """Raised when a host function returns an error.""" + pass + + +@extism.import_fn("extism:host/user", "scheduler_scheduleonetime") +def _scheduler_scheduleonetime(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "scheduler_schedulerecurring") +def _scheduler_schedulerecurring(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "scheduler_cancelschedule") +def _scheduler_cancelschedule(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +def scheduler_schedule_one_time(delay_seconds: int, payload: str, schedule_id: str) -> str: + """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. + + Args: + delay_seconds: int parameter. + payload: str parameter. + schedule_id: str parameter. + + Returns: + str: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "delaySeconds": delay_seconds, + "payload": payload, + "scheduleId": schedule_id, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _scheduler_scheduleonetime(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("newScheduleId", "") + + +def scheduler_schedule_recurring(cron_expression: str, payload: str, schedule_id: str) -> str: + """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. + + Args: + cron_expression: str parameter. + payload: str parameter. + schedule_id: str parameter. + + Returns: + str: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "cronExpression": cron_expression, + "payload": payload, + "scheduleId": schedule_id, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _scheduler_schedulerecurring(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("newScheduleId", "") + + +def scheduler_cancel_schedule(schedule_id: str) -> None: + """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. + + Args: + schedule_id: str parameter. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "scheduleId": schedule_id, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _scheduler_cancelschedule(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + diff --git a/plugins/pdk/python/host/nd_host_subsonicapi.py b/plugins/pdk/python/host/nd_host_subsonicapi.py new file mode 100644 index 000000000..4eaf06d4c --- /dev/null +++ b/plugins/pdk/python/host/nd_host_subsonicapi.py @@ -0,0 +1,55 @@ +# 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 extism-py. +# +# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly. +# The @extism.import_fn decorators are only detected when defined in the plugin's +# main __init__.py file. Copy the needed functions from this file into your plugin. + +from dataclasses import dataclass +from typing import Any + +import extism +import json + + +class HostFunctionError(Exception): + """Raised when a host function returns an error.""" + pass + + +@extism.import_fn("extism:host/user", "subsonicapi_call") +def _subsonicapi_call(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +def subsonicapi_call(uri: str) -> str: + """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. + + Args: + uri: str parameter. + + Returns: + str: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "uri": uri, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _subsonicapi_call(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("responseJson", "") diff --git a/plugins/pdk/python/host/nd_host_websocket.py b/plugins/pdk/python/host/nd_host_websocket.py new file mode 100644 index 000000000..67938f41f --- /dev/null +++ b/plugins/pdk/python/host/nd_host_websocket.py @@ -0,0 +1,181 @@ +# 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 extism-py. +# +# IMPORTANT: Due to a limitation in extism-py, you cannot import this file directly. +# The @extism.import_fn decorators are only detected when defined in the plugin's +# main __init__.py file. Copy the needed functions from this file into your plugin. + +from dataclasses import dataclass +from typing import Any + +import extism +import json + + +class HostFunctionError(Exception): + """Raised when a host function returns an error.""" + pass + + +@extism.import_fn("extism:host/user", "websocket_connect") +def _websocket_connect(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "websocket_sendtext") +def _websocket_sendtext(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "websocket_sendbinary") +def _websocket_sendbinary(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +@extism.import_fn("extism:host/user", "websocket_closeconnection") +def _websocket_closeconnection(offset: int) -> int: + """Raw host function - do not call directly.""" + ... + + +def websocket_connect(url: str, headers: Any, connection_id: str) -> str: + """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. + + Args: + url: str parameter. + headers: Any parameter. + connection_id: str parameter. + + Returns: + str: The result value. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "url": url, + "headers": headers, + "connectionId": connection_id, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _websocket_connect(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + return response.get("newConnectionId", "") + + +def websocket_send_text(connection_id: str, message: str) -> None: + """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. + + Args: + connection_id: str parameter. + message: str parameter. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "connectionId": connection_id, + "message": message, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _websocket_sendtext(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + + +def websocket_send_binary(connection_id: str, data: bytes) -> None: + """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. + + Args: + connection_id: str parameter. + data: bytes parameter. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "connectionId": connection_id, + "data": data, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _websocket_sendbinary(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + + + +def websocket_close_connection(connection_id: str, code: int, reason: str) -> None: + """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. + + Args: + connection_id: str parameter. + code: int parameter. + reason: str parameter. + + Raises: + HostFunctionError: If the host function returns an error. + """ + request = { + "connectionId": connection_id, + "code": code, + "reason": reason, + } + request_bytes = json.dumps(request).encode("utf-8") + request_mem = extism.memory.alloc(request_bytes) + response_offset = _websocket_closeconnection(request_mem.offset) + response_mem = extism.memory.find(response_offset) + response = json.loads(extism.memory.string(response_mem)) + + if response.get("error"): + raise HostFunctionError(response["error"]) + diff --git a/plugins/host/rust/.gitignore b/plugins/pdk/rust/host/.gitignore similarity index 100% rename from plugins/host/rust/.gitignore rename to plugins/pdk/rust/host/.gitignore diff --git a/plugins/host/rust/Cargo.lock b/plugins/pdk/rust/host/Cargo.lock similarity index 100% rename from plugins/host/rust/Cargo.lock rename to plugins/pdk/rust/host/Cargo.lock diff --git a/plugins/host/rust/Cargo.toml b/plugins/pdk/rust/host/Cargo.toml similarity index 100% rename from plugins/host/rust/Cargo.toml rename to plugins/pdk/rust/host/Cargo.toml diff --git a/plugins/host/rust/README.md b/plugins/pdk/rust/host/README.md similarity index 100% rename from plugins/host/rust/README.md rename to plugins/pdk/rust/host/README.md diff --git a/plugins/pdk/rust/host/lib.rs b/plugins/pdk/rust/host/lib.rs new file mode 100644 index 000000000..b71861398 --- /dev/null +++ b/plugins/pdk/rust/host/lib.rs @@ -0,0 +1,65 @@ +// Code generated by hostgen. DO NOT EDIT. +// +//! Navidrome Host Function Wrappers for Rust Plugins +//! +//! This crate provides idiomatic Rust wrappers for all Navidrome host services. +//! It is auto-generated by the hostgen tool and should not be edited manually. +//! +//! # Usage +//! +//! Add this crate as a dependency in your plugin's Cargo.toml: +//! +//! ```toml +//! [dependencies] +//! nd-host = { path = "../../host/rust" } +//! ``` +//! +//! Then import the services you need: +//! +//! ```ignore +//! use nd_host::{cache, scheduler}; +//! +//! fn my_plugin_function() -> Result<(), extism_pdk::Error> { +//! // Use the cache service +//! cache::set_string("my_key", "my_value", 3600)?; +//! +//! // Schedule a recurring task +//! scheduler::schedule_recurring("@every 5m", "payload", "task_id")?; +//! +//! Ok(()) +//! } +//! ``` +//! +//! # Available Services +//! +//! - [`artwork`] - provides artwork URL generation capabilities for plugins. +//! - [`cache`] - provides in-memory TTL-based caching capabilities for plugins. +//! - [`kvstore`] - provides persistent key-value storage for plugins. +//! - [`library`] - provides access to music library metadata for plugins. +//! - [`scheduler`] - provides task scheduling capabilities for plugins. +//! - [`subsonicapi`] - provides access to Navidrome's Subsonic API from plugins. +//! - [`websocket`] - provides WebSocket communication capabilities for plugins. + +#[path = "nd_host_artwork.rs"] +pub mod artwork; + +#[path = "nd_host_cache.rs"] +pub mod cache; + +#[path = "nd_host_kvstore.rs"] +pub mod kvstore; + +#[path = "nd_host_library.rs"] +pub mod library; + +#[path = "nd_host_scheduler.rs"] +pub mod scheduler; + +#[path = "nd_host_subsonicapi.rs"] +pub mod subsonicapi; + +#[path = "nd_host_websocket.rs"] +pub mod websocket; + +// Re-export commonly used types from extism-pdk for convenience +pub use extism_pdk::Error; diff --git a/plugins/pdk/rust/host/nd_host_artwork.rs b/plugins/pdk/rust/host/nd_host_artwork.rs new file mode 100644 index 000000000..31ccb4370 --- /dev/null +++ b/plugins/pdk/rust/host/nd_host_artwork.rs @@ -0,0 +1,207 @@ +// 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 extism-pdk. + +use extism_pdk::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct ArtworkGetArtistUrlRequest { + id: String, + size: i32, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ArtworkGetArtistUrlResponse { + #[serde(default)] + url: String, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct ArtworkGetAlbumUrlRequest { + id: String, + size: i32, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ArtworkGetAlbumUrlResponse { + #[serde(default)] + url: String, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct ArtworkGetTrackUrlRequest { + id: String, + size: i32, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ArtworkGetTrackUrlResponse { + #[serde(default)] + url: String, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct ArtworkGetPlaylistUrlRequest { + id: String, + size: i32, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ArtworkGetPlaylistUrlResponse { + #[serde(default)] + url: String, + #[serde(default)] + error: Option, +} + +#[host_fn] +extern "ExtismHost" { + fn artwork_getartisturl(input: Json) -> Json; + fn artwork_getalbumurl(input: Json) -> Json; + fn artwork_gettrackurl(input: Json) -> Json; + fn artwork_getplaylisturl(input: Json) -> Json; +} + +/// 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. +/// +/// # Arguments +/// * `id` - String parameter. +/// * `size` - i32 parameter. +/// +/// # Returns +/// The url value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get_artist_url(id: &str, size: i32) -> Result { + let response = unsafe { + artwork_getartisturl(Json(ArtworkGetArtistUrlRequest { + id: id.to_owned(), + size: size, + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.url) +} + +/// 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. +/// +/// # Arguments +/// * `id` - String parameter. +/// * `size` - i32 parameter. +/// +/// # Returns +/// The url value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get_album_url(id: &str, size: i32) -> Result { + let response = unsafe { + artwork_getalbumurl(Json(ArtworkGetAlbumUrlRequest { + id: id.to_owned(), + size: size, + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.url) +} + +/// 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. +/// +/// # Arguments +/// * `id` - String parameter. +/// * `size` - i32 parameter. +/// +/// # Returns +/// The url value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get_track_url(id: &str, size: i32) -> Result { + let response = unsafe { + artwork_gettrackurl(Json(ArtworkGetTrackUrlRequest { + id: id.to_owned(), + size: size, + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.url) +} + +/// 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. +/// +/// # Arguments +/// * `id` - String parameter. +/// * `size` - i32 parameter. +/// +/// # Returns +/// The url value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get_playlist_url(id: &str, size: i32) -> Result { + let response = unsafe { + artwork_getplaylisturl(Json(ArtworkGetPlaylistUrlRequest { + id: id.to_owned(), + size: size, + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.url) +} diff --git a/plugins/pdk/rust/host/nd_host_cache.rs b/plugins/pdk/rust/host/nd_host_cache.rs new file mode 100644 index 000000000..f223c8de8 --- /dev/null +++ b/plugins/pdk/rust/host/nd_host_cache.rs @@ -0,0 +1,480 @@ +// 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 extism-pdk. + +use extism_pdk::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct CacheSetStringRequest { + key: String, + value: String, + ttl_seconds: i64, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CacheSetStringResponse { + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct CacheGetStringRequest { + key: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CacheGetStringResponse { + #[serde(default)] + value: String, + #[serde(default)] + exists: bool, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct CacheSetIntRequest { + key: String, + value: i64, + ttl_seconds: i64, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CacheSetIntResponse { + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct CacheGetIntRequest { + key: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CacheGetIntResponse { + #[serde(default)] + value: i64, + #[serde(default)] + exists: bool, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct CacheSetFloatRequest { + key: String, + value: f64, + ttl_seconds: i64, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CacheSetFloatResponse { + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct CacheGetFloatRequest { + key: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CacheGetFloatResponse { + #[serde(default)] + value: f64, + #[serde(default)] + exists: bool, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct CacheSetBytesRequest { + key: String, + value: Vec, + ttl_seconds: i64, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CacheSetBytesResponse { + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct CacheGetBytesRequest { + key: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CacheGetBytesResponse { + #[serde(default)] + value: Vec, + #[serde(default)] + exists: bool, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct CacheHasRequest { + key: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CacheHasResponse { + #[serde(default)] + exists: bool, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct CacheRemoveRequest { + key: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CacheRemoveResponse { + #[serde(default)] + error: Option, +} + +#[host_fn] +extern "ExtismHost" { + fn cache_setstring(input: Json) -> Json; + fn cache_getstring(input: Json) -> Json; + fn cache_setint(input: Json) -> Json; + fn cache_getint(input: Json) -> Json; + fn cache_setfloat(input: Json) -> Json; + fn cache_getfloat(input: Json) -> Json; + fn cache_setbytes(input: Json) -> Json; + fn cache_getbytes(input: Json) -> Json; + fn cache_has(input: Json) -> Json; + fn cache_remove(input: Json) -> Json; +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// * `value` - String parameter. +/// * `ttl_seconds` - i64 parameter. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn set_string(key: &str, value: &str, ttl_seconds: i64) -> Result<(), Error> { + let response = unsafe { + cache_setstring(Json(CacheSetStringRequest { + key: key.to_owned(), + value: value.to_owned(), + ttl_seconds: ttl_seconds, + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(()) +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// +/// # Returns +/// A tuple of (value, exists). +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get_string(key: &str) -> Result<(String, bool), Error> { + let response = unsafe { + cache_getstring(Json(CacheGetStringRequest { + key: key.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok((response.0.value, response.0.exists)) +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// * `value` - i64 parameter. +/// * `ttl_seconds` - i64 parameter. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn set_int(key: &str, value: i64, ttl_seconds: i64) -> Result<(), Error> { + let response = unsafe { + cache_setint(Json(CacheSetIntRequest { + key: key.to_owned(), + value: value, + ttl_seconds: ttl_seconds, + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(()) +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// +/// # Returns +/// A tuple of (value, exists). +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get_int(key: &str) -> Result<(i64, bool), Error> { + let response = unsafe { + cache_getint(Json(CacheGetIntRequest { + key: key.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok((response.0.value, response.0.exists)) +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// * `value` - f64 parameter. +/// * `ttl_seconds` - i64 parameter. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn set_float(key: &str, value: f64, ttl_seconds: i64) -> Result<(), Error> { + let response = unsafe { + cache_setfloat(Json(CacheSetFloatRequest { + key: key.to_owned(), + value: value, + ttl_seconds: ttl_seconds, + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(()) +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// +/// # Returns +/// A tuple of (value, exists). +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get_float(key: &str) -> Result<(f64, bool), Error> { + let response = unsafe { + cache_getfloat(Json(CacheGetFloatRequest { + key: key.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok((response.0.value, response.0.exists)) +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// * `value` - Vec parameter. +/// * `ttl_seconds` - i64 parameter. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn set_bytes(key: &str, value: Vec, ttl_seconds: i64) -> Result<(), Error> { + let response = unsafe { + cache_setbytes(Json(CacheSetBytesRequest { + key: key.to_owned(), + value: value, + ttl_seconds: ttl_seconds, + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(()) +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// +/// # Returns +/// A tuple of (value, exists). +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get_bytes(key: &str) -> Result<(Vec, bool), Error> { + let response = unsafe { + cache_getbytes(Json(CacheGetBytesRequest { + key: key.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok((response.0.value, response.0.exists)) +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// +/// # Returns +/// The exists value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn has(key: &str) -> Result { + let response = unsafe { + cache_has(Json(CacheHasRequest { + key: key.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.exists) +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn remove(key: &str) -> Result<(), Error> { + let response = unsafe { + cache_remove(Json(CacheRemoveRequest { + key: key.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(()) +} diff --git a/plugins/pdk/rust/host/nd_host_kvstore.rs b/plugins/pdk/rust/host/nd_host_kvstore.rs new file mode 100644 index 000000000..5c25a7e9b --- /dev/null +++ b/plugins/pdk/rust/host/nd_host_kvstore.rs @@ -0,0 +1,261 @@ +// 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 extism-pdk. + +use extism_pdk::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct KVStoreSetRequest { + key: String, + value: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct KVStoreSetResponse { + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct KVStoreGetRequest { + key: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct KVStoreGetResponse { + #[serde(default)] + value: Vec, + #[serde(default)] + exists: bool, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct KVStoreDeleteRequest { + key: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct KVStoreDeleteResponse { + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct KVStoreHasRequest { + key: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct KVStoreHasResponse { + #[serde(default)] + exists: bool, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct KVStoreListRequest { + prefix: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct KVStoreListResponse { + #[serde(default)] + keys: Vec, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct KVStoreGetStorageUsedResponse { + #[serde(default)] + bytes: i64, + #[serde(default)] + error: Option, +} + +#[host_fn] +extern "ExtismHost" { + fn kvstore_set(input: Json) -> Json; + fn kvstore_get(input: Json) -> Json; + fn kvstore_delete(input: Json) -> Json; + fn kvstore_has(input: Json) -> Json; + fn kvstore_list(input: Json) -> Json; + fn kvstore_getstorageused(input: Json) -> Json; +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// * `value` - Vec parameter. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn set(key: &str, value: Vec) -> Result<(), Error> { + let response = unsafe { + kvstore_set(Json(KVStoreSetRequest { + key: key.to_owned(), + value: value, + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(()) +} + +/// Get retrieves a byte value from storage. +/// +/// Parameters: +/// - key: The storage key +/// +/// Returns the value and whether the key exists. +/// +/// # Arguments +/// * `key` - String parameter. +/// +/// # Returns +/// A tuple of (value, exists). +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get(key: &str) -> Result<(Vec, bool), Error> { + let response = unsafe { + kvstore_get(Json(KVStoreGetRequest { + key: key.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok((response.0.value, response.0.exists)) +} + +/// 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. +/// +/// # Arguments +/// * `key` - String parameter. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn delete(key: &str) -> Result<(), Error> { + let response = unsafe { + kvstore_delete(Json(KVStoreDeleteRequest { + key: key.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(()) +} + +/// Has checks if a key exists in storage. +/// +/// Parameters: +/// - key: The storage key +/// +/// Returns true if the key exists. +/// +/// # Arguments +/// * `key` - String parameter. +/// +/// # Returns +/// The exists value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn has(key: &str) -> Result { + let response = unsafe { + kvstore_has(Json(KVStoreHasRequest { + key: key.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.exists) +} + +/// 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. +/// +/// # Arguments +/// * `prefix` - String parameter. +/// +/// # Returns +/// The keys value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn list(prefix: &str) -> Result, Error> { + let response = unsafe { + kvstore_list(Json(KVStoreListRequest { + prefix: prefix.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.keys) +} + +/// GetStorageUsed returns the total storage used by this plugin in bytes. +/// +/// # Returns +/// The bytes value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get_storage_used() -> Result { + let response = unsafe { + kvstore_getstorageused(Json(serde_json::json!({})))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.bytes) +} diff --git a/plugins/pdk/rust/host/nd_host_library.rs b/plugins/pdk/rust/host/nd_host_library.rs new file mode 100644 index 000000000..ca6bf958d --- /dev/null +++ b/plugins/pdk/rust/host/nd_host_library.rs @@ -0,0 +1,105 @@ +// 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 extism-pdk. + +use extism_pdk::*; +use serde::{Deserialize, Serialize}; + +/// Library represents a music library with metadata. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Library { + pub id: i32, + pub name: String, + #[serde(default)] + pub path: String, + #[serde(default)] + pub mount_point: String, + pub last_scan_at: i64, + pub total_songs: i32, + pub total_albums: i32, + pub total_artists: i32, + pub total_size: i64, + pub total_duration: f64, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct LibraryGetLibraryRequest { + id: i32, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct LibraryGetLibraryResponse { + #[serde(default)] + result: Option, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct LibraryGetAllLibrariesResponse { + #[serde(default)] + result: Vec, + #[serde(default)] + error: Option, +} + +#[host_fn] +extern "ExtismHost" { + fn library_getlibrary(input: Json) -> Json; + fn library_getalllibraries(input: Json) -> Json; +} + +/// 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. +/// +/// # Arguments +/// * `id` - i32 parameter. +/// +/// # Returns +/// The result value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get_library(id: i32) -> Result, Error> { + let response = unsafe { + library_getlibrary(Json(LibraryGetLibraryRequest { + id: id, + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.result) +} + +/// GetAllLibraries retrieves metadata for all configured libraries. +/// +/// Returns a slice of all libraries with their metadata. +/// +/// # Returns +/// The result value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn get_all_libraries() -> Result, Error> { + let response = unsafe { + library_getalllibraries(Json(serde_json::json!({})))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.result) +} diff --git a/plugins/pdk/rust/host/nd_host_scheduler.rs b/plugins/pdk/rust/host/nd_host_scheduler.rs new file mode 100644 index 000000000..9578463a9 --- /dev/null +++ b/plugins/pdk/rust/host/nd_host_scheduler.rs @@ -0,0 +1,159 @@ +// 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 extism-pdk. + +use extism_pdk::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct SchedulerScheduleOneTimeRequest { + delay_seconds: i32, + payload: String, + schedule_id: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SchedulerScheduleOneTimeResponse { + #[serde(default)] + new_schedule_id: String, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct SchedulerScheduleRecurringRequest { + cron_expression: String, + payload: String, + schedule_id: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SchedulerScheduleRecurringResponse { + #[serde(default)] + new_schedule_id: String, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct SchedulerCancelScheduleRequest { + schedule_id: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SchedulerCancelScheduleResponse { + #[serde(default)] + error: Option, +} + +#[host_fn] +extern "ExtismHost" { + fn scheduler_scheduleonetime(input: Json) -> Json; + fn scheduler_schedulerecurring(input: Json) -> Json; + fn scheduler_cancelschedule(input: Json) -> Json; +} + +/// 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. +/// +/// # Arguments +/// * `delay_seconds` - i32 parameter. +/// * `payload` - String parameter. +/// * `schedule_id` - String parameter. +/// +/// # Returns +/// The new_schedule_id value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn schedule_one_time(delay_seconds: i32, payload: &str, schedule_id: &str) -> Result { + let response = unsafe { + scheduler_scheduleonetime(Json(SchedulerScheduleOneTimeRequest { + delay_seconds: delay_seconds, + payload: payload.to_owned(), + schedule_id: schedule_id.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.new_schedule_id) +} + +/// 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. +/// +/// # Arguments +/// * `cron_expression` - String parameter. +/// * `payload` - String parameter. +/// * `schedule_id` - String parameter. +/// +/// # Returns +/// The new_schedule_id value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn schedule_recurring(cron_expression: &str, payload: &str, schedule_id: &str) -> Result { + let response = unsafe { + scheduler_schedulerecurring(Json(SchedulerScheduleRecurringRequest { + cron_expression: cron_expression.to_owned(), + payload: payload.to_owned(), + schedule_id: schedule_id.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.new_schedule_id) +} + +/// 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. +/// +/// # Arguments +/// * `schedule_id` - String parameter. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn cancel_schedule(schedule_id: &str) -> Result<(), Error> { + let response = unsafe { + scheduler_cancelschedule(Json(SchedulerCancelScheduleRequest { + schedule_id: schedule_id.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(()) +} diff --git a/plugins/pdk/rust/host/nd_host_subsonicapi.rs b/plugins/pdk/rust/host/nd_host_subsonicapi.rs new file mode 100644 index 000000000..d96995189 --- /dev/null +++ b/plugins/pdk/rust/host/nd_host_subsonicapi.rs @@ -0,0 +1,54 @@ +// 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 extism-pdk. + +use extism_pdk::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct SubsonicAPICallRequest { + uri: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SubsonicAPICallResponse { + #[serde(default)] + response_json: String, + #[serde(default)] + error: Option, +} + +#[host_fn] +extern "ExtismHost" { + fn subsonicapi_call(input: Json) -> Json; +} + +/// 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. +/// +/// # Arguments +/// * `uri` - String parameter. +/// +/// # Returns +/// The response_json value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn call(uri: &str) -> Result { + let response = unsafe { + subsonicapi_call(Json(SubsonicAPICallRequest { + uri: uri.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.response_json) +} diff --git a/plugins/pdk/rust/host/nd_host_websocket.rs b/plugins/pdk/rust/host/nd_host_websocket.rs new file mode 100644 index 000000000..61392072a --- /dev/null +++ b/plugins/pdk/rust/host/nd_host_websocket.rs @@ -0,0 +1,204 @@ +// 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 extism-pdk. + +use extism_pdk::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct WebSocketConnectRequest { + url: String, + headers: std::collections::HashMap, + connection_id: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct WebSocketConnectResponse { + #[serde(default)] + new_connection_id: String, + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct WebSocketSendTextRequest { + connection_id: String, + message: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct WebSocketSendTextResponse { + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct WebSocketSendBinaryRequest { + connection_id: String, + data: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct WebSocketSendBinaryResponse { + #[serde(default)] + error: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct WebSocketCloseConnectionRequest { + connection_id: String, + code: i32, + reason: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct WebSocketCloseConnectionResponse { + #[serde(default)] + error: Option, +} + +#[host_fn] +extern "ExtismHost" { + fn websocket_connect(input: Json) -> Json; + fn websocket_sendtext(input: Json) -> Json; + fn websocket_sendbinary(input: Json) -> Json; + fn websocket_closeconnection(input: Json) -> Json; +} + +/// 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. +/// +/// # Arguments +/// * `url` - String parameter. +/// * `headers` - std::collections::HashMap parameter. +/// * `connection_id` - String parameter. +/// +/// # Returns +/// The new_connection_id value. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn connect(url: &str, headers: std::collections::HashMap, connection_id: &str) -> Result { + let response = unsafe { + websocket_connect(Json(WebSocketConnectRequest { + url: url.to_owned(), + headers: headers, + connection_id: connection_id.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(response.0.new_connection_id) +} + +/// 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. +/// +/// # Arguments +/// * `connection_id` - String parameter. +/// * `message` - String parameter. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn send_text(connection_id: &str, message: &str) -> Result<(), Error> { + let response = unsafe { + websocket_sendtext(Json(WebSocketSendTextRequest { + connection_id: connection_id.to_owned(), + message: message.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(()) +} + +/// 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. +/// +/// # Arguments +/// * `connection_id` - String parameter. +/// * `data` - Vec parameter. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn send_binary(connection_id: &str, data: Vec) -> Result<(), Error> { + let response = unsafe { + websocket_sendbinary(Json(WebSocketSendBinaryRequest { + connection_id: connection_id.to_owned(), + data: data, + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(()) +} + +/// 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. +/// +/// # Arguments +/// * `connection_id` - String parameter. +/// * `code` - i32 parameter. +/// * `reason` - String parameter. +/// +/// # Errors +/// Returns an error if the host function call fails. +pub fn close_connection(connection_id: &str, code: i32, reason: &str) -> Result<(), Error> { + let response = unsafe { + websocket_closeconnection(Json(WebSocketCloseConnectionRequest { + connection_id: connection_id.to_owned(), + code: code, + reason: reason.to_owned(), + }))? + }; + + if let Some(err) = response.0.error { + return Err(Error::msg(err)); + } + + Ok(()) +}