mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
feat(plugins): define TaskQueue host service interface
Add the TaskQueueService interface with CreateQueue, Enqueue, GetTaskStatus, and CancelTask methods plus QueueConfig struct.
This commit is contained in:
parent
acd69f6a4f
commit
b76c57d2c4
57
plugins/host/taskqueue.go
Normal file
57
plugins/host/taskqueue.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package host
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// QueueConfig holds configuration for a task queue.
|
||||||
|
type QueueConfig struct {
|
||||||
|
// Concurrency is the max number of parallel workers. Default: 1.
|
||||||
|
// Capped by the plugin's manifest maxConcurrency.
|
||||||
|
Concurrency int32 `json:"concurrency"`
|
||||||
|
|
||||||
|
// MaxRetries is the number of times to retry a failed task. Default: 0.
|
||||||
|
MaxRetries int32 `json:"maxRetries"`
|
||||||
|
|
||||||
|
// BackoffMs is the initial backoff between retries in milliseconds.
|
||||||
|
// Doubles each retry (exponential: backoffMs * 2^(attempt-1)). Default: 1000.
|
||||||
|
BackoffMs int64 `json:"backoffMs"`
|
||||||
|
|
||||||
|
// DelayMs is the minimum delay between starting consecutive tasks
|
||||||
|
// in milliseconds. Useful for rate limiting. Default: 0.
|
||||||
|
DelayMs int64 `json:"delayMs"`
|
||||||
|
|
||||||
|
// RetentionMs is how long completed/failed/cancelled tasks are kept
|
||||||
|
// in milliseconds. Default: 3600000 (1h). Min: 60000 (1m). Max: 604800000 (1w).
|
||||||
|
RetentionMs int64 `json:"retentionMs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueService provides persistent task queues for plugins.
|
||||||
|
//
|
||||||
|
// This service allows plugins to create named queues with configurable concurrency,
|
||||||
|
// retry policies, and rate limiting. Tasks are persisted to SQLite and survive
|
||||||
|
// server restarts. When a task is ready to execute, the host calls the plugin's
|
||||||
|
// nd_task_execute callback function.
|
||||||
|
//
|
||||||
|
//nd:hostservice name=TaskQueue permission=taskqueue
|
||||||
|
type TaskQueueService interface {
|
||||||
|
// CreateQueue creates a named task queue with the given configuration.
|
||||||
|
// Zero-value fields in config use sensible defaults.
|
||||||
|
// If a queue with the same name already exists, returns an error.
|
||||||
|
// On startup, this also recovers any stale "running" tasks from a previous crash.
|
||||||
|
//nd:hostfunc
|
||||||
|
CreateQueue(ctx context.Context, name string, config QueueConfig) error
|
||||||
|
|
||||||
|
// Enqueue adds a task to the named queue. Returns the task ID.
|
||||||
|
// payload is opaque bytes passed back to the plugin on execution.
|
||||||
|
//nd:hostfunc
|
||||||
|
Enqueue(ctx context.Context, queueName string, payload []byte) (string, error)
|
||||||
|
|
||||||
|
// GetTaskStatus returns the status of a task: "pending", "running",
|
||||||
|
// "completed", "failed", or "cancelled".
|
||||||
|
//nd:hostfunc
|
||||||
|
GetTaskStatus(ctx context.Context, taskID string) (string, error)
|
||||||
|
|
||||||
|
// CancelTask cancels a pending task. Returns error if already
|
||||||
|
// running, completed, or failed.
|
||||||
|
//nd:hostfunc
|
||||||
|
CancelTask(ctx context.Context, taskID string) error
|
||||||
|
}
|
||||||
220
plugins/host/taskqueue_gen.go
Normal file
220
plugins/host/taskqueue_gen.go
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
// Code generated by ndpgen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package host
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
extism "github.com/extism/go-sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TaskQueueCreateQueueRequest is the request type for TaskQueue.CreateQueue.
|
||||||
|
type TaskQueueCreateQueueRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Config QueueConfig `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueCreateQueueResponse is the response type for TaskQueue.CreateQueue.
|
||||||
|
type TaskQueueCreateQueueResponse struct {
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueEnqueueRequest is the request type for TaskQueue.Enqueue.
|
||||||
|
type TaskQueueEnqueueRequest struct {
|
||||||
|
QueueName string `json:"queueName"`
|
||||||
|
Payload []byte `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueEnqueueResponse is the response type for TaskQueue.Enqueue.
|
||||||
|
type TaskQueueEnqueueResponse struct {
|
||||||
|
Result string `json:"result,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueGetTaskStatusRequest is the request type for TaskQueue.GetTaskStatus.
|
||||||
|
type TaskQueueGetTaskStatusRequest struct {
|
||||||
|
TaskID string `json:"taskId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueGetTaskStatusResponse is the response type for TaskQueue.GetTaskStatus.
|
||||||
|
type TaskQueueGetTaskStatusResponse struct {
|
||||||
|
Result string `json:"result,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueCancelTaskRequest is the request type for TaskQueue.CancelTask.
|
||||||
|
type TaskQueueCancelTaskRequest struct {
|
||||||
|
TaskID string `json:"taskId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueCancelTaskResponse is the response type for TaskQueue.CancelTask.
|
||||||
|
type TaskQueueCancelTaskResponse struct {
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterTaskQueueHostFunctions registers TaskQueue service host functions.
|
||||||
|
// The returned host functions should be added to the plugin's configuration.
|
||||||
|
func RegisterTaskQueueHostFunctions(service TaskQueueService) []extism.HostFunction {
|
||||||
|
return []extism.HostFunction{
|
||||||
|
newTaskQueueCreateQueueHostFunction(service),
|
||||||
|
newTaskQueueEnqueueHostFunction(service),
|
||||||
|
newTaskQueueGetTaskStatusHostFunction(service),
|
||||||
|
newTaskQueueCancelTaskHostFunction(service),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTaskQueueCreateQueueHostFunction(service TaskQueueService) extism.HostFunction {
|
||||||
|
return extism.NewHostFunctionWithStack(
|
||||||
|
"taskqueue_createqueue",
|
||||||
|
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
|
||||||
|
// Read JSON request from plugin memory
|
||||||
|
reqBytes, err := p.ReadBytes(stack[0])
|
||||||
|
if err != nil {
|
||||||
|
taskqueueWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req TaskQueueCreateQueueRequest
|
||||||
|
if err := json.Unmarshal(reqBytes, &req); err != nil {
|
||||||
|
taskqueueWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the service method
|
||||||
|
if svcErr := service.CreateQueue(ctx, req.Name, req.Config); svcErr != nil {
|
||||||
|
taskqueueWriteError(p, stack, svcErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write JSON response to plugin memory
|
||||||
|
resp := TaskQueueCreateQueueResponse{}
|
||||||
|
taskqueueWriteResponse(p, stack, resp)
|
||||||
|
},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTaskQueueEnqueueHostFunction(service TaskQueueService) extism.HostFunction {
|
||||||
|
return extism.NewHostFunctionWithStack(
|
||||||
|
"taskqueue_enqueue",
|
||||||
|
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
|
||||||
|
// Read JSON request from plugin memory
|
||||||
|
reqBytes, err := p.ReadBytes(stack[0])
|
||||||
|
if err != nil {
|
||||||
|
taskqueueWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req TaskQueueEnqueueRequest
|
||||||
|
if err := json.Unmarshal(reqBytes, &req); err != nil {
|
||||||
|
taskqueueWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the service method
|
||||||
|
result, svcErr := service.Enqueue(ctx, req.QueueName, req.Payload)
|
||||||
|
if svcErr != nil {
|
||||||
|
taskqueueWriteError(p, stack, svcErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write JSON response to plugin memory
|
||||||
|
resp := TaskQueueEnqueueResponse{
|
||||||
|
Result: result,
|
||||||
|
}
|
||||||
|
taskqueueWriteResponse(p, stack, resp)
|
||||||
|
},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTaskQueueGetTaskStatusHostFunction(service TaskQueueService) extism.HostFunction {
|
||||||
|
return extism.NewHostFunctionWithStack(
|
||||||
|
"taskqueue_gettaskstatus",
|
||||||
|
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
|
||||||
|
// Read JSON request from plugin memory
|
||||||
|
reqBytes, err := p.ReadBytes(stack[0])
|
||||||
|
if err != nil {
|
||||||
|
taskqueueWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req TaskQueueGetTaskStatusRequest
|
||||||
|
if err := json.Unmarshal(reqBytes, &req); err != nil {
|
||||||
|
taskqueueWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the service method
|
||||||
|
result, svcErr := service.GetTaskStatus(ctx, req.TaskID)
|
||||||
|
if svcErr != nil {
|
||||||
|
taskqueueWriteError(p, stack, svcErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write JSON response to plugin memory
|
||||||
|
resp := TaskQueueGetTaskStatusResponse{
|
||||||
|
Result: result,
|
||||||
|
}
|
||||||
|
taskqueueWriteResponse(p, stack, resp)
|
||||||
|
},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTaskQueueCancelTaskHostFunction(service TaskQueueService) extism.HostFunction {
|
||||||
|
return extism.NewHostFunctionWithStack(
|
||||||
|
"taskqueue_canceltask",
|
||||||
|
func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) {
|
||||||
|
// Read JSON request from plugin memory
|
||||||
|
reqBytes, err := p.ReadBytes(stack[0])
|
||||||
|
if err != nil {
|
||||||
|
taskqueueWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req TaskQueueCancelTaskRequest
|
||||||
|
if err := json.Unmarshal(reqBytes, &req); err != nil {
|
||||||
|
taskqueueWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the service method
|
||||||
|
if svcErr := service.CancelTask(ctx, req.TaskID); svcErr != nil {
|
||||||
|
taskqueueWriteError(p, stack, svcErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write JSON response to plugin memory
|
||||||
|
resp := TaskQueueCancelTaskResponse{}
|
||||||
|
taskqueueWriteResponse(p, stack, resp)
|
||||||
|
},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
[]extism.ValueType{extism.ValueTypePTR},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// taskqueueWriteResponse writes a JSON response to plugin memory.
|
||||||
|
func taskqueueWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) {
|
||||||
|
respBytes, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
taskqueueWriteError(p, stack, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respPtr, err := p.WriteBytes(respBytes)
|
||||||
|
if err != nil {
|
||||||
|
stack[0] = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stack[0] = respPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
// taskqueueWriteError writes an error response to plugin memory.
|
||||||
|
func taskqueueWriteError(p *extism.CurrentPlugin, stack []uint64, err error) {
|
||||||
|
errResp := struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}{Error: err.Error()}
|
||||||
|
respBytes, _ := json.Marshal(errResp)
|
||||||
|
respPtr, _ := p.WriteBytes(respBytes)
|
||||||
|
stack[0] = respPtr
|
||||||
|
}
|
||||||
219
plugins/pdk/go/host/nd_host_taskqueue.go
Normal file
219
plugins/pdk/go/host/nd_host_taskqueue.go
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
// Code generated by ndpgen. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// This file contains client wrappers for the TaskQueue host service.
|
||||||
|
// It is intended for use in Navidrome plugins built with TinyGo.
|
||||||
|
//
|
||||||
|
//go:build wasip1
|
||||||
|
|
||||||
|
package host
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueueConfig represents the QueueConfig data structure.
|
||||||
|
// QueueConfig holds configuration for a task queue.
|
||||||
|
type QueueConfig struct {
|
||||||
|
Concurrency int32 `json:"concurrency"`
|
||||||
|
MaxRetries int32 `json:"maxRetries"`
|
||||||
|
BackoffMs int64 `json:"backoffMs"`
|
||||||
|
DelayMs int64 `json:"delayMs"`
|
||||||
|
RetentionMs int64 `json:"retentionMs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// taskqueue_createqueue is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user taskqueue_createqueue
|
||||||
|
func taskqueue_createqueue(uint64) uint64
|
||||||
|
|
||||||
|
// taskqueue_enqueue is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user taskqueue_enqueue
|
||||||
|
func taskqueue_enqueue(uint64) uint64
|
||||||
|
|
||||||
|
// taskqueue_gettaskstatus is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user taskqueue_gettaskstatus
|
||||||
|
func taskqueue_gettaskstatus(uint64) uint64
|
||||||
|
|
||||||
|
// taskqueue_canceltask is the host function provided by Navidrome.
|
||||||
|
//
|
||||||
|
//go:wasmimport extism:host/user taskqueue_canceltask
|
||||||
|
func taskqueue_canceltask(uint64) uint64
|
||||||
|
|
||||||
|
type taskQueueCreateQueueRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Config QueueConfig `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type taskQueueEnqueueRequest struct {
|
||||||
|
QueueName string `json:"queueName"`
|
||||||
|
Payload []byte `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type taskQueueEnqueueResponse struct {
|
||||||
|
Result string `json:"result,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type taskQueueGetTaskStatusRequest struct {
|
||||||
|
TaskID string `json:"taskId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type taskQueueGetTaskStatusResponse struct {
|
||||||
|
Result string `json:"result,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type taskQueueCancelTaskRequest struct {
|
||||||
|
TaskID string `json:"taskId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueCreateQueue calls the taskqueue_createqueue host function.
|
||||||
|
// CreateQueue creates a named task queue with the given configuration.
|
||||||
|
// Zero-value fields in config use sensible defaults.
|
||||||
|
// If a queue with the same name already exists, returns an error.
|
||||||
|
// On startup, this also recovers any stale "running" tasks from a previous crash.
|
||||||
|
func TaskQueueCreateQueue(name string, config QueueConfig) error {
|
||||||
|
// Marshal request to JSON
|
||||||
|
req := taskQueueCreateQueueRequest{
|
||||||
|
Name: name,
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
reqBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reqMem := pdk.AllocateBytes(reqBytes)
|
||||||
|
defer reqMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := taskqueue_createqueue(reqMem.Offset())
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse error-only response
|
||||||
|
var response struct {
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.Error != "" {
|
||||||
|
return errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueEnqueue calls the taskqueue_enqueue host function.
|
||||||
|
// Enqueue adds a task to the named queue. Returns the task ID.
|
||||||
|
// payload is opaque bytes passed back to the plugin on execution.
|
||||||
|
func TaskQueueEnqueue(queueName string, payload []byte) (string, error) {
|
||||||
|
// Marshal request to JSON
|
||||||
|
req := taskQueueEnqueueRequest{
|
||||||
|
QueueName: queueName,
|
||||||
|
Payload: payload,
|
||||||
|
}
|
||||||
|
reqBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
reqMem := pdk.AllocateBytes(reqBytes)
|
||||||
|
defer reqMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := taskqueue_enqueue(reqMem.Offset())
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse the response
|
||||||
|
var response taskQueueEnqueueResponse
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Error field to Go error
|
||||||
|
if response.Error != "" {
|
||||||
|
return "", errors.New(response.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueGetTaskStatus calls the taskqueue_gettaskstatus host function.
|
||||||
|
// GetTaskStatus returns the status of a task: "pending", "running",
|
||||||
|
// "completed", "failed", or "cancelled".
|
||||||
|
func TaskQueueGetTaskStatus(taskID string) (string, error) {
|
||||||
|
// Marshal request to JSON
|
||||||
|
req := taskQueueGetTaskStatusRequest{
|
||||||
|
TaskID: taskID,
|
||||||
|
}
|
||||||
|
reqBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
reqMem := pdk.AllocateBytes(reqBytes)
|
||||||
|
defer reqMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := taskqueue_gettaskstatus(reqMem.Offset())
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse the response
|
||||||
|
var response taskQueueGetTaskStatusResponse
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Error field to Go error
|
||||||
|
if response.Error != "" {
|
||||||
|
return "", errors.New(response.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueCancelTask calls the taskqueue_canceltask host function.
|
||||||
|
// CancelTask cancels a pending task. Returns error if already
|
||||||
|
// running, completed, or failed.
|
||||||
|
func TaskQueueCancelTask(taskID string) error {
|
||||||
|
// Marshal request to JSON
|
||||||
|
req := taskQueueCancelTaskRequest{
|
||||||
|
TaskID: taskID,
|
||||||
|
}
|
||||||
|
reqBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reqMem := pdk.AllocateBytes(reqBytes)
|
||||||
|
defer reqMem.Free()
|
||||||
|
|
||||||
|
// Call the host function
|
||||||
|
responsePtr := taskqueue_canceltask(reqMem.Offset())
|
||||||
|
|
||||||
|
// Read the response from memory
|
||||||
|
responseMem := pdk.FindMemory(responsePtr)
|
||||||
|
responseBytes := responseMem.ReadBytes()
|
||||||
|
|
||||||
|
// Parse error-only response
|
||||||
|
var response struct {
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(responseBytes, &response); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.Error != "" {
|
||||||
|
return errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
84
plugins/pdk/go/host/nd_host_taskqueue_stub.go
Normal file
84
plugins/pdk/go/host/nd_host_taskqueue_stub.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Code generated by ndpgen. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// This file contains mock implementations for non-WASM builds.
|
||||||
|
// These mocks allow IDE support, compilation, and unit testing on non-WASM platforms.
|
||||||
|
// Plugin authors can use the exported mock instances to set expectations in tests.
|
||||||
|
//
|
||||||
|
//go:build !wasip1
|
||||||
|
|
||||||
|
package host
|
||||||
|
|
||||||
|
import "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
// QueueConfig represents the QueueConfig data structure.
|
||||||
|
// QueueConfig holds configuration for a task queue.
|
||||||
|
type QueueConfig struct {
|
||||||
|
Concurrency int32 `json:"concurrency"`
|
||||||
|
MaxRetries int32 `json:"maxRetries"`
|
||||||
|
BackoffMs int64 `json:"backoffMs"`
|
||||||
|
DelayMs int64 `json:"delayMs"`
|
||||||
|
RetentionMs int64 `json:"retentionMs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// mockTaskQueueService is the mock implementation for testing.
|
||||||
|
type mockTaskQueueService struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueMock is the auto-instantiated mock instance for testing.
|
||||||
|
// Use this to set expectations: host.TaskQueueMock.On("MethodName", args...).Return(values...)
|
||||||
|
var TaskQueueMock = &mockTaskQueueService{}
|
||||||
|
|
||||||
|
// CreateQueue is the mock method for TaskQueueCreateQueue.
|
||||||
|
func (m *mockTaskQueueService) CreateQueue(name string, config QueueConfig) error {
|
||||||
|
args := m.Called(name, config)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueCreateQueue delegates to the mock instance.
|
||||||
|
// CreateQueue creates a named task queue with the given configuration.
|
||||||
|
// Zero-value fields in config use sensible defaults.
|
||||||
|
// If a queue with the same name already exists, returns an error.
|
||||||
|
// On startup, this also recovers any stale "running" tasks from a previous crash.
|
||||||
|
func TaskQueueCreateQueue(name string, config QueueConfig) error {
|
||||||
|
return TaskQueueMock.CreateQueue(name, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue is the mock method for TaskQueueEnqueue.
|
||||||
|
func (m *mockTaskQueueService) Enqueue(queueName string, payload []byte) (string, error) {
|
||||||
|
args := m.Called(queueName, payload)
|
||||||
|
return args.String(0), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueEnqueue delegates to the mock instance.
|
||||||
|
// Enqueue adds a task to the named queue. Returns the task ID.
|
||||||
|
// payload is opaque bytes passed back to the plugin on execution.
|
||||||
|
func TaskQueueEnqueue(queueName string, payload []byte) (string, error) {
|
||||||
|
return TaskQueueMock.Enqueue(queueName, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTaskStatus is the mock method for TaskQueueGetTaskStatus.
|
||||||
|
func (m *mockTaskQueueService) GetTaskStatus(taskID string) (string, error) {
|
||||||
|
args := m.Called(taskID)
|
||||||
|
return args.String(0), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueGetTaskStatus delegates to the mock instance.
|
||||||
|
// GetTaskStatus returns the status of a task: "pending", "running",
|
||||||
|
// "completed", "failed", or "cancelled".
|
||||||
|
func TaskQueueGetTaskStatus(taskID string) (string, error) {
|
||||||
|
return TaskQueueMock.GetTaskStatus(taskID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelTask is the mock method for TaskQueueCancelTask.
|
||||||
|
func (m *mockTaskQueueService) CancelTask(taskID string) error {
|
||||||
|
args := m.Called(taskID)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskQueueCancelTask delegates to the mock instance.
|
||||||
|
// CancelTask cancels a pending task. Returns error if already
|
||||||
|
// running, completed, or failed.
|
||||||
|
func TaskQueueCancelTask(taskID string) error {
|
||||||
|
return TaskQueueMock.CancelTask(taskID)
|
||||||
|
}
|
||||||
153
plugins/pdk/python/host/nd_host_taskqueue.py
Normal file
153
plugins/pdk/python/host/nd_host_taskqueue.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# Code generated by ndpgen. DO NOT EDIT.
|
||||||
|
#
|
||||||
|
# This file contains client wrappers for the TaskQueue 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", "taskqueue_createqueue")
|
||||||
|
def _taskqueue_createqueue(offset: int) -> int:
|
||||||
|
"""Raw host function - do not call directly."""
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@extism.import_fn("extism:host/user", "taskqueue_enqueue")
|
||||||
|
def _taskqueue_enqueue(offset: int) -> int:
|
||||||
|
"""Raw host function - do not call directly."""
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@extism.import_fn("extism:host/user", "taskqueue_gettaskstatus")
|
||||||
|
def _taskqueue_gettaskstatus(offset: int) -> int:
|
||||||
|
"""Raw host function - do not call directly."""
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@extism.import_fn("extism:host/user", "taskqueue_canceltask")
|
||||||
|
def _taskqueue_canceltask(offset: int) -> int:
|
||||||
|
"""Raw host function - do not call directly."""
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def taskqueue_create_queue(name: str, config: Any) -> None:
|
||||||
|
"""CreateQueue creates a named task queue with the given configuration.
|
||||||
|
Zero-value fields in config use sensible defaults.
|
||||||
|
If a queue with the same name already exists, returns an error.
|
||||||
|
On startup, this also recovers any stale "running" tasks from a previous crash.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: str parameter.
|
||||||
|
config: Any parameter.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HostFunctionError: If the host function returns an error.
|
||||||
|
"""
|
||||||
|
request = {
|
||||||
|
"name": name,
|
||||||
|
"config": config,
|
||||||
|
}
|
||||||
|
request_bytes = json.dumps(request).encode("utf-8")
|
||||||
|
request_mem = extism.memory.alloc(request_bytes)
|
||||||
|
response_offset = _taskqueue_createqueue(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 taskqueue_enqueue(queue_name: str, payload: bytes) -> str:
|
||||||
|
"""Enqueue adds a task to the named queue. Returns the task ID.
|
||||||
|
payload is opaque bytes passed back to the plugin on execution.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
queue_name: str parameter.
|
||||||
|
payload: bytes parameter.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The result value.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HostFunctionError: If the host function returns an error.
|
||||||
|
"""
|
||||||
|
request = {
|
||||||
|
"queueName": queue_name,
|
||||||
|
"payload": payload,
|
||||||
|
}
|
||||||
|
request_bytes = json.dumps(request).encode("utf-8")
|
||||||
|
request_mem = extism.memory.alloc(request_bytes)
|
||||||
|
response_offset = _taskqueue_enqueue(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", "")
|
||||||
|
|
||||||
|
|
||||||
|
def taskqueue_get_task_status(task_id: str) -> str:
|
||||||
|
"""GetTaskStatus returns the status of a task: "pending", "running",
|
||||||
|
"completed", "failed", or "cancelled".
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_id: str parameter.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The result value.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HostFunctionError: If the host function returns an error.
|
||||||
|
"""
|
||||||
|
request = {
|
||||||
|
"taskId": task_id,
|
||||||
|
}
|
||||||
|
request_bytes = json.dumps(request).encode("utf-8")
|
||||||
|
request_mem = extism.memory.alloc(request_bytes)
|
||||||
|
response_offset = _taskqueue_gettaskstatus(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", "")
|
||||||
|
|
||||||
|
|
||||||
|
def taskqueue_cancel_task(task_id: str) -> None:
|
||||||
|
"""CancelTask cancels a pending task. Returns error if already
|
||||||
|
running, completed, or failed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_id: str parameter.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HostFunctionError: If the host function returns an error.
|
||||||
|
"""
|
||||||
|
request = {
|
||||||
|
"taskId": task_id,
|
||||||
|
}
|
||||||
|
request_bytes = json.dumps(request).encode("utf-8")
|
||||||
|
request_mem = extism.memory.alloc(request_bytes)
|
||||||
|
response_offset = _taskqueue_canceltask(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"])
|
||||||
|
|
||||||
184
plugins/pdk/rust/nd-pdk-host/src/nd_host_taskqueue.rs
Normal file
184
plugins/pdk/rust/nd-pdk-host/src/nd_host_taskqueue.rs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
// Code generated by ndpgen. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// This file contains client wrappers for the TaskQueue host service.
|
||||||
|
// It is intended for use in Navidrome plugins built with extism-pdk.
|
||||||
|
|
||||||
|
use extism_pdk::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// QueueConfig holds configuration for a task queue.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct QueueConfig {
|
||||||
|
pub concurrency: i32,
|
||||||
|
pub max_retries: i32,
|
||||||
|
pub backoff_ms: i64,
|
||||||
|
pub delay_ms: i64,
|
||||||
|
pub retention_ms: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct TaskQueueCreateQueueRequest {
|
||||||
|
name: String,
|
||||||
|
config: QueueConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct TaskQueueCreateQueueResponse {
|
||||||
|
#[serde(default)]
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct TaskQueueEnqueueRequest {
|
||||||
|
queue_name: String,
|
||||||
|
payload: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct TaskQueueEnqueueResponse {
|
||||||
|
#[serde(default)]
|
||||||
|
result: String,
|
||||||
|
#[serde(default)]
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct TaskQueueGetTaskStatusRequest {
|
||||||
|
task_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct TaskQueueGetTaskStatusResponse {
|
||||||
|
#[serde(default)]
|
||||||
|
result: String,
|
||||||
|
#[serde(default)]
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct TaskQueueCancelTaskRequest {
|
||||||
|
task_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct TaskQueueCancelTaskResponse {
|
||||||
|
#[serde(default)]
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[host_fn]
|
||||||
|
extern "ExtismHost" {
|
||||||
|
fn taskqueue_createqueue(input: Json<TaskQueueCreateQueueRequest>) -> Json<TaskQueueCreateQueueResponse>;
|
||||||
|
fn taskqueue_enqueue(input: Json<TaskQueueEnqueueRequest>) -> Json<TaskQueueEnqueueResponse>;
|
||||||
|
fn taskqueue_gettaskstatus(input: Json<TaskQueueGetTaskStatusRequest>) -> Json<TaskQueueGetTaskStatusResponse>;
|
||||||
|
fn taskqueue_canceltask(input: Json<TaskQueueCancelTaskRequest>) -> Json<TaskQueueCancelTaskResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CreateQueue creates a named task queue with the given configuration.
|
||||||
|
/// Zero-value fields in config use sensible defaults.
|
||||||
|
/// If a queue with the same name already exists, returns an error.
|
||||||
|
/// On startup, this also recovers any stale "running" tasks from a previous crash.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `name` - String parameter.
|
||||||
|
/// * `config` - QueueConfig parameter.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns an error if the host function call fails.
|
||||||
|
pub fn create_queue(name: &str, config: QueueConfig) -> Result<(), Error> {
|
||||||
|
let response = unsafe {
|
||||||
|
taskqueue_createqueue(Json(TaskQueueCreateQueueRequest {
|
||||||
|
name: name.to_owned(),
|
||||||
|
config: config,
|
||||||
|
}))?
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(err) = response.0.error {
|
||||||
|
return Err(Error::msg(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enqueue adds a task to the named queue. Returns the task ID.
|
||||||
|
/// payload is opaque bytes passed back to the plugin on execution.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `queue_name` - String parameter.
|
||||||
|
/// * `payload` - Vec<u8> parameter.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The result value.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns an error if the host function call fails.
|
||||||
|
pub fn enqueue(queue_name: &str, payload: Vec<u8>) -> Result<String, Error> {
|
||||||
|
let response = unsafe {
|
||||||
|
taskqueue_enqueue(Json(TaskQueueEnqueueRequest {
|
||||||
|
queue_name: queue_name.to_owned(),
|
||||||
|
payload: payload,
|
||||||
|
}))?
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(err) = response.0.error {
|
||||||
|
return Err(Error::msg(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(response.0.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GetTaskStatus returns the status of a task: "pending", "running",
|
||||||
|
/// "completed", "failed", or "cancelled".
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `task_id` - String parameter.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The result value.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns an error if the host function call fails.
|
||||||
|
pub fn get_task_status(task_id: &str) -> Result<String, Error> {
|
||||||
|
let response = unsafe {
|
||||||
|
taskqueue_gettaskstatus(Json(TaskQueueGetTaskStatusRequest {
|
||||||
|
task_id: task_id.to_owned(),
|
||||||
|
}))?
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(err) = response.0.error {
|
||||||
|
return Err(Error::msg(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(response.0.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CancelTask cancels a pending task. Returns error if already
|
||||||
|
/// running, completed, or failed.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `task_id` - String parameter.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Returns an error if the host function call fails.
|
||||||
|
pub fn cancel_task(task_id: &str) -> Result<(), Error> {
|
||||||
|
let response = unsafe {
|
||||||
|
taskqueue_canceltask(Json(TaskQueueCancelTaskRequest {
|
||||||
|
task_id: task_id.to_owned(),
|
||||||
|
}))?
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(err) = response.0.error {
|
||||||
|
return Err(Error::msg(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user