diff --git a/plugins/host/go/nd_host_scheduler.go b/plugins/host/go/nd_host_scheduler.go new file mode 100644 index 000000000..580874b32 --- /dev/null +++ b/plugins/host/go/nd_host_scheduler.go @@ -0,0 +1,129 @@ +// Code generated by hostgen. DO NOT EDIT. +// +// This file contains client wrappers for the Scheduler host service. +// It is intended for use in Navidrome plugins built with TinyGo. + +package main + +import ( + "encoding/json" + "errors" + + "github.com/extism/go-pdk" +) + +// scheduler_scheduleonetime is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user scheduler_scheduleonetime +func scheduler_scheduleonetime(int32, uint64, uint64) uint64 + +// scheduler_schedulerecurring is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user scheduler_schedulerecurring +func scheduler_schedulerecurring(uint64, uint64, uint64) uint64 + +// scheduler_cancelschedule is the host function provided by Navidrome. +// +//go:wasmimport extism:host/user scheduler_cancelschedule +func scheduler_cancelschedule(uint64) uint64 + +// SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime. +type SchedulerScheduleOneTimeResponse struct { + NewScheduleID string `json:"newScheduleID,omitempty"` + Error string `json:"error,omitempty"` +} + +// SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring. +type SchedulerScheduleRecurringResponse struct { + NewScheduleID string `json:"newScheduleID,omitempty"` + 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. +// +// 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 []byte, scheduleID string) (*SchedulerScheduleOneTimeResponse, error) { + payloadMem := pdk.AllocateBytes(payload) + defer payloadMem.Free() + scheduleIDMem := pdk.AllocateString(scheduleID) + defer scheduleIDMem.Free() + + // Call the host function + responsePtr := scheduler_scheduleonetime(delaySeconds, payloadMem.Offset(), scheduleIDMem.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 + } + + return &response, nil +} + +// SchedulerScheduleRecurring calls the scheduler_schedulerecurring host function. +// ScheduleRecurring schedules a recurring event using a cron expression. +// +// 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 []byte, scheduleID string) (*SchedulerScheduleRecurringResponse, error) { + cronExpressionMem := pdk.AllocateString(cronExpression) + defer cronExpressionMem.Free() + payloadMem := pdk.AllocateBytes(payload) + defer payloadMem.Free() + scheduleIDMem := pdk.AllocateString(scheduleID) + defer scheduleIDMem.Free() + + // Call the host function + responsePtr := scheduler_schedulerecurring(cronExpressionMem.Offset(), payloadMem.Offset(), scheduleIDMem.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 + } + + 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(scheduelID string) error { + scheduelIDMem := pdk.AllocateString(scheduelID) + defer scheduelIDMem.Free() + + // Call the host function + responsePtr := scheduler_cancelschedule(scheduelIDMem.Offset()) + + // Read the response from memory + responseMem := pdk.FindMemory(responsePtr) + errStr := string(responseMem.ReadBytes()) + + if errStr != "" { + return errors.New(errStr) + } + + return nil +} diff --git a/plugins/host/scheduler.go b/plugins/host/scheduler.go new file mode 100644 index 000000000..e0317896c --- /dev/null +++ b/plugins/host/scheduler.go @@ -0,0 +1,42 @@ +package host + +import "context" + +// SchedulerService provides task scheduling capabilities for plugins. +// +// This service allows plugins to schedule both one-time and recurring tasks using +// cron expressions. All scheduled tasks can be cancelled using their schedule ID. +// +//nd:hostservice name=Scheduler permission=scheduler +type SchedulerService interface { + // ScheduleOneTime schedules a one-time event to be triggered after the specified delay. + // + // 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. + //nd:hostfunc + ScheduleOneTime(ctx context.Context, delaySeconds int32, payload []byte, scheduleID string) (newScheduleID string, err error) + + // ScheduleRecurring schedules a recurring event using a cron expression. + // + // 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. + //nd:hostfunc + ScheduleRecurring(ctx context.Context, cronExpression string, payload []byte, scheduleID string) (newScheduleID string, err error) + + // 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. + //nd:hostfunc + CancelSchedule(ctx context.Context, scheduelID string) error +} diff --git a/plugins/host/scheduler_gen.go b/plugins/host/scheduler_gen.go new file mode 100644 index 000000000..6f0039939 --- /dev/null +++ b/plugins/host/scheduler_gen.go @@ -0,0 +1,153 @@ +// Code generated by hostgen. DO NOT EDIT. + +package host + +import ( + "context" + "encoding/json" + + extism "github.com/extism/go-sdk" +) + +// SchedulerScheduleOneTimeResponse is the response type for Scheduler.ScheduleOneTime. +type SchedulerScheduleOneTimeResponse struct { + NewScheduleID string `json:"newScheduleID,omitempty"` + Error string `json:"error,omitempty"` +} + +// SchedulerScheduleRecurringResponse is the response type for Scheduler.ScheduleRecurring. +type SchedulerScheduleRecurringResponse struct { + NewScheduleID string `json:"newScheduleID,omitempty"` + Error string `json:"error,omitempty"` +} + +// RegisterSchedulerHostFunctions registers Scheduler service host functions. +// The returned host functions should be added to the plugin's configuration. +func RegisterSchedulerHostFunctions(service SchedulerService) []extism.HostFunction { + return []extism.HostFunction{ + newSchedulerScheduleOneTimeHostFunction(service), + newSchedulerScheduleRecurringHostFunction(service), + newSchedulerCancelScheduleHostFunction(service), + } +} + +func newSchedulerScheduleOneTimeHostFunction(service SchedulerService) extism.HostFunction { + return extism.NewHostFunctionWithStack( + "scheduler_scheduleonetime", + func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { + // Read parameters from stack + delaySeconds := extism.DecodeI32(stack[0]) + payload, err := p.ReadBytes(stack[1]) + if err != nil { + return + } + scheduleID, err := p.ReadString(stack[2]) + if err != nil { + return + } + + // Call the service method + newscheduleid, err := service.ScheduleOneTime(ctx, delaySeconds, payload, scheduleID) + if err != nil { + schedulerWriteError(p, stack, err) + return + } + // Write JSON response to plugin memory + resp := SchedulerScheduleOneTimeResponse{ + NewScheduleID: newscheduleid, + } + schedulerWriteResponse(p, stack, resp) + }, + []extism.ValueType{extism.ValueTypeI32, extism.ValueTypePTR, extism.ValueTypePTR}, + []extism.ValueType{extism.ValueTypePTR}, + ) +} + +func newSchedulerScheduleRecurringHostFunction(service SchedulerService) extism.HostFunction { + return extism.NewHostFunctionWithStack( + "scheduler_schedulerecurring", + func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { + // Read parameters from stack + cronExpression, err := p.ReadString(stack[0]) + if err != nil { + return + } + payload, err := p.ReadBytes(stack[1]) + if err != nil { + return + } + scheduleID, err := p.ReadString(stack[2]) + if err != nil { + return + } + + // Call the service method + newscheduleid, err := service.ScheduleRecurring(ctx, cronExpression, payload, scheduleID) + if err != nil { + schedulerWriteError(p, stack, err) + return + } + // Write JSON response to plugin memory + resp := SchedulerScheduleRecurringResponse{ + NewScheduleID: newscheduleid, + } + schedulerWriteResponse(p, stack, resp) + }, + []extism.ValueType{extism.ValueTypePTR, extism.ValueTypePTR, extism.ValueTypePTR}, + []extism.ValueType{extism.ValueTypePTR}, + ) +} + +func newSchedulerCancelScheduleHostFunction(service SchedulerService) extism.HostFunction { + return extism.NewHostFunctionWithStack( + "scheduler_cancelschedule", + func(ctx context.Context, p *extism.CurrentPlugin, stack []uint64) { + // Read parameters from stack + scheduelID, err := p.ReadString(stack[0]) + if err != nil { + return + } + + // Call the service method + err = service.CancelSchedule(ctx, scheduelID) + if err != nil { + // Write error string to plugin memory + if ptr, err := p.WriteString(err.Error()); err == nil { + stack[0] = ptr + } + return + } + // Write empty string to indicate success + if ptr, err := p.WriteString(""); err == nil { + stack[0] = ptr + } + }, + []extism.ValueType{extism.ValueTypePTR}, + []extism.ValueType{extism.ValueTypePTR}, + ) +} + +// schedulerWriteResponse writes a JSON response to plugin memory. +func schedulerWriteResponse(p *extism.CurrentPlugin, stack []uint64, resp any) { + respBytes, err := json.Marshal(resp) + if err != nil { + schedulerWriteError(p, stack, err) + return + } + respPtr, err := p.WriteBytes(respBytes) + if err != nil { + stack[0] = 0 + return + } + stack[0] = respPtr +} + +// schedulerWriteError writes an error response to plugin memory. +func schedulerWriteError(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 +}