navidrome/plugins/api/api_plugin_dev_named_registry.go
Deluan Quintão 5b73a4d5b7
feat(plugins): add TimeNow function to SchedulerService (#4337)
* feat: add TimeNow function to SchedulerService plugin

Added new TimeNow RPC method to the SchedulerService host service that returns
the current time in two formats: RFC3339Nano string and Unix milliseconds int64.
This provides plugins with a standardized way to get current time information
from the host system.

The implementation includes:
- TimeNowRequest/TimeNowResponse protobuf message definitions
- Go host service implementation using time.Now()
- Complete test coverage with format validation
- Generated WASM interface code for plugin communication

* feat: add LocalTimeZone field to TimeNow response

Added LocalTimeZone field to TimeNowResponse message in the SchedulerService
plugin host service. This field contains the server's local timezone name
(e.g., 'America/New_York', 'UTC') providing plugins with timezone context
alongside the existing RFC3339Nano and Unix milliseconds timestamps.

The implementation includes:
- New local_time_zone protobuf field definition
- Go implementation using time.Now().Location().String()
- Updated test coverage with timezone validation
- Generated protobuf serialization/deserialization code

* docs: update plugin README with TimeNow function documentation

Updated the plugins README.md to document the new TimeNow function in the
SchedulerService. The documentation includes detailed descriptions of the
three return formats (RFC3339Nano, UnixMilli, LocalTimeZone), practical
use cases, and a comprehensive Go code example showing how plugins can
access current time information for logging, calculations, and timezone-aware
operations.

* docs: remove wrong comment from InitRequest

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: add missing TimeNow method to namedSchedulerService

Added TimeNow method implementation to namedSchedulerService struct to satisfy the scheduler.SchedulerService interface contract. This method was recently added to the interface but the namedSchedulerService wrapper was not updated, causing compilation failures in plugin tests. The implementation is a simple pass-through to the underlying scheduler service since TimeNow doesn't require any special handling for named callbacks.

---------

Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-13 14:23:58 -04:00

95 lines
3.4 KiB
Go

//go:build wasip1
package api
import (
"context"
"strings"
"github.com/navidrome/navidrome/plugins/host/scheduler"
)
var callbacks = make(namedCallbacks)
// RegisterNamedSchedulerCallback registers a named scheduler callback. Named callbacks allow multiple callbacks to be registered
// within the same plugin, and for the schedules to be scoped to the named callback. If you only need a single callback, you can use
// the default (unnamed) callback registration function, RegisterSchedulerCallback.
// It returns a scheduler.SchedulerService that can be used to schedule jobs for the named callback.
//
// Notes:
//
// - You can't mix named and unnamed callbacks within the same plugin.
// - The name should be unique within the plugin, and it's recommended to use a short, descriptive name.
// - The name is case-sensitive.
func RegisterNamedSchedulerCallback(name string, cb SchedulerCallback) scheduler.SchedulerService {
callbacks[name] = cb
RegisterSchedulerCallback(&callbacks)
return &namedSchedulerService{name: name, svc: scheduler.NewSchedulerService()}
}
const zwsp = string('\u200b')
// namedCallbacks is a map of named scheduler callbacks. The key is the name of the callback, and the value is the callback itself.
type namedCallbacks map[string]SchedulerCallback
func parseKey(key string) (string, string) {
parts := strings.SplitN(key, zwsp, 2)
if len(parts) != 2 {
return "", ""
}
return parts[0], parts[1]
}
func (n *namedCallbacks) OnSchedulerCallback(ctx context.Context, req *SchedulerCallbackRequest) (*SchedulerCallbackResponse, error) {
name, scheduleId := parseKey(req.ScheduleId)
cb, exists := callbacks[name]
if !exists {
return nil, nil
}
req.ScheduleId = scheduleId
return cb.OnSchedulerCallback(ctx, req)
}
// namedSchedulerService is a wrapper around the host scheduler service that prefixes the schedule IDs with the
// callback name. It is returned by RegisterNamedSchedulerCallback, and should be used by the plugin to schedule
// jobs for the named callback.
type namedSchedulerService struct {
name string
cb SchedulerCallback
svc scheduler.SchedulerService
}
func (n *namedSchedulerService) makeKey(id string) string {
return n.name + zwsp + id
}
func (n *namedSchedulerService) mapResponse(resp *scheduler.ScheduleResponse, err error) (*scheduler.ScheduleResponse, error) {
if err != nil {
return nil, err
}
_, resp.ScheduleId = parseKey(resp.ScheduleId)
return resp, nil
}
func (n *namedSchedulerService) ScheduleOneTime(ctx context.Context, request *scheduler.ScheduleOneTimeRequest) (*scheduler.ScheduleResponse, error) {
key := n.makeKey(request.ScheduleId)
request.ScheduleId = key
return n.mapResponse(n.svc.ScheduleOneTime(ctx, request))
}
func (n *namedSchedulerService) ScheduleRecurring(ctx context.Context, request *scheduler.ScheduleRecurringRequest) (*scheduler.ScheduleResponse, error) {
key := n.makeKey(request.ScheduleId)
request.ScheduleId = key
return n.mapResponse(n.svc.ScheduleRecurring(ctx, request))
}
func (n *namedSchedulerService) CancelSchedule(ctx context.Context, request *scheduler.CancelRequest) (*scheduler.CancelResponse, error) {
key := n.makeKey(request.ScheduleId)
request.ScheduleId = key
return n.svc.CancelSchedule(ctx, request)
}
func (n *namedSchedulerService) TimeNow(ctx context.Context, request *scheduler.TimeNowRequest) (*scheduler.TimeNowResponse, error) {
return n.svc.TimeNow(ctx, request)
}