mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
feat(scheduler): add scheduler callback schema and implementation for plugins
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
e4b0e31c85
commit
4631d05082
@ -200,6 +200,106 @@ func main() {}
|
||||
|
||||
Scrobbler plugins are automatically discovered and used by Navidrome's PlayTracker alongside built-in scrobblers (Last.fm, ListenBrainz).
|
||||
|
||||
### Scheduler
|
||||
|
||||
Allows plugins to schedule one-time or recurring tasks. Plugins that use the scheduler host service must export a callback function to receive scheduled events.
|
||||
|
||||
| Function | Input | Output | Description |
|
||||
|-------------------------|----------------------------------------------|-------------------|------------------------------------||
|
||||
| `nd_scheduler_callback` | `{schedule_id, payload, is_recurring}` | `{error?}` | Called when a scheduled task fires |
|
||||
|
||||
#### Scheduler Callback Input
|
||||
|
||||
```json
|
||||
{
|
||||
"schedule_id": "string",
|
||||
"payload": "string",
|
||||
"is_recurring": true
|
||||
}
|
||||
```
|
||||
|
||||
- `schedule_id`: The unique identifier for the scheduled task
|
||||
- `payload`: Data passed when the task was scheduled
|
||||
- `is_recurring`: `true` for recurring schedules, `false` for one-time
|
||||
|
||||
#### Scheduler Callback Output
|
||||
|
||||
The output is optional on success. On error, return:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "error message"
|
||||
}
|
||||
```
|
||||
|
||||
#### Using the Scheduler Host Service
|
||||
|
||||
To schedule tasks, plugins call these host functions (provided by Navidrome):
|
||||
|
||||
| Host Function | Parameters | Description |
|
||||
|------------------------------|-----------------------------------------------|------------------------------------------|
|
||||
| `scheduler_scheduleonetime` | `delay_seconds, payload, schedule_id` | Schedule a one-time callback |
|
||||
| `scheduler_schedulerecurring`| `cron_expression, payload, schedule_id` | Schedule a recurring callback |
|
||||
| `scheduler_cancelschedule` | `schedule_id` | Cancel a scheduled task |
|
||||
|
||||
#### Manifest Permissions
|
||||
|
||||
Plugins using the scheduler must declare the permission in their manifest:
|
||||
|
||||
```json
|
||||
{
|
||||
"permissions": {
|
||||
"scheduler": {
|
||||
"reason": "Schedule periodic metadata refresh"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Example Scheduler Plugin
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
type SchedulerCallbackInput struct {
|
||||
ScheduleId string `json:"schedule_id"`
|
||||
Payload string `json:"payload"`
|
||||
IsRecurring bool `json:"is_recurring"`
|
||||
}
|
||||
|
||||
type SchedulerCallbackOutput struct {
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
//go:wasmexport nd_scheduler_callback
|
||||
func ndSchedulerCallback() int32 {
|
||||
var input SchedulerCallbackInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Handle the scheduled task based on payload
|
||||
pdk.Log(pdk.LogInfo, "Task fired: " + input.ScheduleId)
|
||||
|
||||
// Return success (empty output)
|
||||
output := SchedulerCallbackOutput{}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {}
|
||||
```
|
||||
|
||||
To schedule a task from your plugin, use the generated SDK functions (see `plugins/host/go/nd_host_scheduler.go`).
|
||||
|
||||
## Developing Plugins
|
||||
|
||||
Plugins can be written in any language that compiles to WebAssembly. We recommend using the [Extism PDK](https://extism.org/docs/category/write-a-plug-in) for your language.
|
||||
|
||||
@ -6,10 +6,11 @@ new plugins using the `xtp` CLI tool.
|
||||
|
||||
## Available Schemas
|
||||
|
||||
| Schema | Description |
|
||||
|--------------------------------------------|-----------------------------------------------------------------|
|
||||
| [metadata_agent.yaml](metadata_agent.yaml) | Metadata agent for retrieving artist/album information |
|
||||
| [scrobbler.yaml](scrobbler.yaml) | Scrobbler capability for sending play data to external services |
|
||||
| Schema | Description |
|
||||
|------------------------------------------------------|-----------------------------------------------------------------|
|
||||
| [metadata_agent.yaml](metadata_agent.yaml) | Metadata agent for retrieving artist/album information |
|
||||
| [scheduler_callback.yaml](scheduler_callback.yaml) | Scheduler callback for plugins using the scheduler host service |
|
||||
| [scrobbler.yaml](scrobbler.yaml) | Scrobbler capability for sending play data to external services |
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
||||
49
plugins/schemas/scheduler_callback.yaml
Normal file
49
plugins/schemas/scheduler_callback.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
version: v1-draft
|
||||
|
||||
exports:
|
||||
nd_scheduler_callback:
|
||||
description: |
|
||||
Called when a scheduled task fires. Plugins that use the scheduler host service
|
||||
must export this function to receive callbacks when their scheduled tasks execute.
|
||||
input:
|
||||
$ref: "#/components/schemas/SchedulerCallbackInput"
|
||||
contentType: application/json
|
||||
output:
|
||||
$ref: "#/components/schemas/SchedulerCallbackOutput"
|
||||
contentType: application/json
|
||||
|
||||
components:
|
||||
schemas:
|
||||
SchedulerCallbackInput:
|
||||
description: Input provided to the scheduler callback when a scheduled task fires
|
||||
properties:
|
||||
schedule_id:
|
||||
type: string
|
||||
description: |
|
||||
The unique identifier for this scheduled task. This is either the ID
|
||||
provided when scheduling, or an auto-generated UUID if none was specified.
|
||||
payload:
|
||||
type: string
|
||||
description: |
|
||||
The payload data that was provided when the task was scheduled.
|
||||
Can be used to pass context or parameters to the callback handler.
|
||||
is_recurring:
|
||||
type: boolean
|
||||
description: |
|
||||
True if this is a recurring schedule (created via ScheduleRecurring),
|
||||
false if it's a one-time schedule (created via ScheduleOneTime).
|
||||
required:
|
||||
- schedule_id
|
||||
- payload
|
||||
- is_recurring
|
||||
|
||||
SchedulerCallbackOutput:
|
||||
description: Output from the scheduler callback
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
nullable: true
|
||||
description: |
|
||||
Error message if the callback failed to process the scheduled task.
|
||||
Empty or null indicates success. The error is logged but does not
|
||||
affect the scheduling system.
|
||||
59
plugins/testdata/fake-scheduler/main.go
vendored
59
plugins/testdata/fake-scheduler/main.go
vendored
@ -1,12 +1,15 @@
|
||||
// Fake scheduler plugin for Navidrome plugin system integration tests.
|
||||
// This plugin was created based on the scheduler_callback.yaml XTP schema.
|
||||
// Build with: tinygo build -o ../fake-scheduler.wasm -target wasip1 -buildmode=c-shared .
|
||||
//
|
||||
// Note: pdk.gen.go contains the domain types from the XTP schema where your plugin will run.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/extism/go-pdk"
|
||||
pdk "github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
// Manifest types
|
||||
@ -26,19 +29,7 @@ type SchedulerPermission struct {
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// Scheduler callback input
|
||||
type SchedulerCallbackInput struct {
|
||||
ScheduleID string `json:"schedule_id"`
|
||||
Payload string `json:"payload"`
|
||||
IsRecurring bool `json:"is_recurring"`
|
||||
}
|
||||
|
||||
// Scheduler callback output
|
||||
type SchedulerCallbackOutput struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CallRecord stores information about a callback that was received
|
||||
// CallRecord stores information about a callback that was received (for testing)
|
||||
type CallRecord struct {
|
||||
ScheduleID string `json:"schedule_id"`
|
||||
Payload string `json:"payload"`
|
||||
@ -73,51 +64,33 @@ func ndManifest() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
//go:wasmexport nd_scheduler_callback
|
||||
func ndSchedulerCallback() int32 {
|
||||
var input SchedulerCallbackInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Payload is now a plain string, no decoding needed
|
||||
payload := input.Payload
|
||||
|
||||
// NdSchedulerCallback implements the scheduler callback logic.
|
||||
// Called when a scheduled task fires.
|
||||
// This function is called by the generated wrapper in pdk.gen.go.
|
||||
func NdSchedulerCallback(input SchedulerCallbackInput) (SchedulerCallbackOutput, error) {
|
||||
// Check for configured error response
|
||||
errCfg, hasErr := pdk.GetConfig("callback_error")
|
||||
if hasErr && errCfg != "" {
|
||||
output := SchedulerCallbackOutput{Error: errCfg}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
return SchedulerCallbackOutput{Error: &errCfg}, nil
|
||||
}
|
||||
|
||||
// Track the callback
|
||||
totalCallCount++
|
||||
if record, exists := callRecords[input.ScheduleID]; exists {
|
||||
if record, exists := callRecords[input.ScheduleId]; exists {
|
||||
record.CallCount++
|
||||
} else {
|
||||
callRecords[input.ScheduleID] = &CallRecord{
|
||||
ScheduleID: input.ScheduleID,
|
||||
Payload: payload,
|
||||
callRecords[input.ScheduleId] = &CallRecord{
|
||||
ScheduleID: input.ScheduleId,
|
||||
Payload: input.Payload,
|
||||
IsRecurring: input.IsRecurring,
|
||||
CallCount: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// Log the callback for debugging
|
||||
pdk.Log(pdk.LogInfo, "Scheduler callback received: "+input.ScheduleID+" payload="+payload)
|
||||
pdk.Log(pdk.LogInfo, "Scheduler callback received: "+input.ScheduleId+" payload="+input.Payload)
|
||||
|
||||
// Success
|
||||
output := SchedulerCallbackOutput{}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
return SchedulerCallbackOutput{}, nil
|
||||
}
|
||||
|
||||
// Helper function to get call records (for testing)
|
||||
|
||||
57
plugins/testdata/fake-scheduler/pdk.gen.go
vendored
Normal file
57
plugins/testdata/fake-scheduler/pdk.gen.go
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
// THIS FILE WAS GENERATED BY `xtp-go-bindgen`. DO NOT EDIT.
|
||||
package main
|
||||
|
||||
import (
|
||||
pdk "github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
//go:wasmexport nd_scheduler_callback
|
||||
func _NdSchedulerCallback() int32 {
|
||||
var err error
|
||||
_ = err
|
||||
pdk.Log(pdk.LogDebug, "NdSchedulerCallback: getting JSON input")
|
||||
var input SchedulerCallbackInput
|
||||
err = pdk.InputJSON(&input)
|
||||
if err != nil {
|
||||
pdk.SetError(err)
|
||||
return -1
|
||||
}
|
||||
|
||||
pdk.Log(pdk.LogDebug, "NdSchedulerCallback: calling implementation function")
|
||||
output, err := NdSchedulerCallback(input)
|
||||
if err != nil {
|
||||
pdk.SetError(err)
|
||||
return -1
|
||||
}
|
||||
|
||||
pdk.Log(pdk.LogDebug, "NdSchedulerCallback: setting JSON output")
|
||||
err = pdk.OutputJSON(output)
|
||||
if err != nil {
|
||||
pdk.SetError(err)
|
||||
return -1
|
||||
}
|
||||
|
||||
pdk.Log(pdk.LogDebug, "NdSchedulerCallback: returning")
|
||||
return 0
|
||||
}
|
||||
|
||||
// Input provided to the scheduler callback when a scheduled task fires
|
||||
type SchedulerCallbackInput struct {
|
||||
// True if this is a recurring schedule (created via ScheduleRecurring),
|
||||
// false if it's a one-time schedule (created via ScheduleOneTime).
|
||||
IsRecurring bool `json:"is_recurring"`
|
||||
// The payload data that was provided when the task was scheduled.
|
||||
// Can be used to pass context or parameters to the callback handler.
|
||||
Payload string `json:"payload"`
|
||||
// The unique identifier for this scheduled task. This is either the ID
|
||||
// provided when scheduling, or an auto-generated UUID if none was specified.
|
||||
ScheduleId string `json:"schedule_id"`
|
||||
}
|
||||
|
||||
// Output from the scheduler callback
|
||||
type SchedulerCallbackOutput struct {
|
||||
// Error message if the callback failed to process the scheduled task.
|
||||
// Empty or null indicates success. The error is logged but does not
|
||||
// affect the scheduling system.
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user