mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
refactor(plugins): rename PlaylistGenerator to PlaylistProvider
Rename the capability from PlaylistGenerator to PlaylistProvider and the internal orchestrator struct from playlistGeneratorOrchestrator to playlistSyncer. The new names better describe what the capability does (provides playlists) rather than how it works internally. All source files, test plugin, PDK packages (Go/Rust), YAML schemas, and exported WASM function names are updated accordingly.
This commit is contained in:
parent
03a36c5bdd
commit
04aa10f988
@ -1,18 +1,18 @@
|
|||||||
package capabilities
|
package capabilities
|
||||||
|
|
||||||
// PlaylistGenerator provides dynamically-generated playlists (e.g., "Daily Mix",
|
// PlaylistProvider provides dynamically-generated playlists (e.g., "Daily Mix",
|
||||||
// personalized recommendations). Plugins implementing this capability expose two
|
// personalized recommendations). Plugins implementing this capability expose two
|
||||||
// functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for
|
// functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for
|
||||||
// fetching the heavy payload (tracks, metadata).
|
// fetching the heavy payload (tracks, metadata).
|
||||||
//
|
//
|
||||||
//nd:capability name=playlistgenerator required=true
|
//nd:capability name=playlistprovider required=true
|
||||||
type PlaylistGenerator interface {
|
type PlaylistProvider interface {
|
||||||
// GetAvailablePlaylists returns the list of playlists this plugin provides.
|
// GetAvailablePlaylists returns the list of playlists this plugin provides.
|
||||||
//nd:export name=nd_playlist_generator_get_available_playlists
|
//nd:export name=nd_playlist_provider_get_available_playlists
|
||||||
GetAvailablePlaylists(GetAvailablePlaylistsRequest) (GetAvailablePlaylistsResponse, error)
|
GetAvailablePlaylists(GetAvailablePlaylistsRequest) (GetAvailablePlaylistsResponse, error)
|
||||||
|
|
||||||
// GetPlaylist returns the full data for a single playlist (tracks, metadata).
|
// GetPlaylist returns the full data for a single playlist (tracks, metadata).
|
||||||
//nd:export name=nd_playlist_generator_get_playlist
|
//nd:export name=nd_playlist_provider_get_playlist
|
||||||
GetPlaylist(GetPlaylistRequest) (GetPlaylistResponse, error)
|
GetPlaylist(GetPlaylistRequest) (GetPlaylistResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,13 +60,13 @@ type GetPlaylistResponse struct {
|
|||||||
ValidUntil int64 `json:"validUntil"`
|
ValidUntil int64 `json:"validUntil"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlaylistGeneratorError represents an error type for playlist generator operations.
|
// PlaylistProviderError represents an error type for playlist provider operations.
|
||||||
type PlaylistGeneratorError string
|
type PlaylistProviderError string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// PlaylistGeneratorErrorNotFound indicates a playlist is currently unavailable.
|
// PlaylistProviderErrorNotFound indicates a playlist is currently unavailable.
|
||||||
PlaylistGeneratorErrorNotFound PlaylistGeneratorError = "playlist_generator(not_found)"
|
PlaylistProviderErrorNotFound PlaylistProviderError = "playlist_provider(not_found)"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error implements the error interface for PlaylistGeneratorError.
|
// Error implements the error interface for PlaylistProviderError.
|
||||||
func (e PlaylistGeneratorError) Error() string { return string(e) }
|
func (e PlaylistProviderError) Error() string { return string(e) }
|
||||||
@ -1,6 +1,6 @@
|
|||||||
version: v1-draft
|
version: v1-draft
|
||||||
exports:
|
exports:
|
||||||
nd_playlist_generator_get_available_playlists:
|
nd_playlist_provider_get_available_playlists:
|
||||||
description: GetAvailablePlaylists returns the list of playlists this plugin provides.
|
description: GetAvailablePlaylists returns the list of playlists this plugin provides.
|
||||||
input:
|
input:
|
||||||
$ref: '#/components/schemas/GetAvailablePlaylistsRequest'
|
$ref: '#/components/schemas/GetAvailablePlaylistsRequest'
|
||||||
@ -8,7 +8,7 @@ exports:
|
|||||||
output:
|
output:
|
||||||
$ref: '#/components/schemas/GetAvailablePlaylistsResponse'
|
$ref: '#/components/schemas/GetAvailablePlaylistsResponse'
|
||||||
contentType: application/json
|
contentType: application/json
|
||||||
nd_playlist_generator_get_playlist:
|
nd_playlist_provider_get_playlist:
|
||||||
description: GetPlaylist returns the full data for a single playlist (tracks, metadata).
|
description: GetPlaylist returns the full data for a single playlist (tracks, metadata).
|
||||||
input:
|
input:
|
||||||
$ref: '#/components/schemas/GetPlaylistRequest'
|
$ref: '#/components/schemas/GetPlaylistRequest'
|
||||||
@ -391,12 +391,12 @@ func (m *Manager) loadPluginWithConfig(p *model.Plugin) error {
|
|||||||
// Call plugin init function
|
// Call plugin init function
|
||||||
callPluginInit(ctx, m.plugins[p.ID])
|
callPluginInit(ctx, m.plugins[p.ID])
|
||||||
|
|
||||||
// Start PlaylistGenerator orchestrator if capability is detected
|
// Start PlaylistProvider syncer if capability is detected
|
||||||
loadedPlugin := m.plugins[p.ID]
|
loadedPlugin := m.plugins[p.ID]
|
||||||
if hasCapability(loadedPlugin.capabilities, CapabilityPlaylistGenerator) {
|
if hasCapability(loadedPlugin.capabilities, CapabilityPlaylistProvider) {
|
||||||
orch := newPlaylistGeneratorOrchestrator(m.ctx, p.ID, loadedPlugin, m.ds, m.matcher)
|
syncer := newPlaylistSyncer(m.ctx, p.ID, loadedPlugin, m.ds, m.matcher)
|
||||||
loadedPlugin.closers = append(loadedPlugin.closers, orch)
|
loadedPlugin.closers = append(loadedPlugin.closers, syncer)
|
||||||
go orch.run()
|
go syncer.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -1,26 +1,26 @@
|
|||||||
// Code generated by ndpgen. DO NOT EDIT.
|
// Code generated by ndpgen. DO NOT EDIT.
|
||||||
//
|
//
|
||||||
// This file contains export wrappers for the PlaylistGenerator capability.
|
// This file contains export wrappers for the PlaylistProvider capability.
|
||||||
// It is intended for use in Navidrome plugins built with TinyGo.
|
// It is intended for use in Navidrome plugins built with TinyGo.
|
||||||
//
|
//
|
||||||
//go:build wasip1
|
//go:build wasip1
|
||||||
|
|
||||||
package playlistgenerator
|
package playlistprovider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PlaylistGeneratorError represents an error type for playlist generator operations.
|
// PlaylistProviderError represents an error type for playlist provider operations.
|
||||||
type PlaylistGeneratorError string
|
type PlaylistProviderError string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// PlaylistGeneratorErrorNotFound indicates a playlist is currently unavailable.
|
// PlaylistProviderErrorNotFound indicates a playlist is currently unavailable.
|
||||||
PlaylistGeneratorErrorNotFound PlaylistGeneratorError = "playlist_generator(not_found)"
|
PlaylistProviderErrorNotFound PlaylistProviderError = "playlist_provider(not_found)"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error implements the error interface for PlaylistGeneratorError.
|
// Error implements the error interface for PlaylistProviderError.
|
||||||
func (e PlaylistGeneratorError) Error() string { return string(e) }
|
func (e PlaylistProviderError) Error() string { return string(e) }
|
||||||
|
|
||||||
// GetAvailablePlaylistsRequest is the request for GetAvailablePlaylists.
|
// GetAvailablePlaylistsRequest is the request for GetAvailablePlaylists.
|
||||||
type GetAvailablePlaylistsRequest struct {
|
type GetAvailablePlaylistsRequest struct {
|
||||||
@ -89,12 +89,12 @@ type SongRef struct {
|
|||||||
Duration float32 `json:"duration,omitempty"`
|
Duration float32 `json:"duration,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlaylistGenerator requires all methods to be implemented.
|
// PlaylistProvider requires all methods to be implemented.
|
||||||
// PlaylistGenerator provides dynamically-generated playlists (e.g., "Daily Mix",
|
// PlaylistProvider provides dynamically-generated playlists (e.g., "Daily Mix",
|
||||||
// personalized recommendations). Plugins implementing this capability expose two
|
// personalized recommendations). Plugins implementing this capability expose two
|
||||||
// functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for
|
// functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for
|
||||||
// fetching the heavy payload (tracks, metadata).
|
// fetching the heavy payload (tracks, metadata).
|
||||||
type PlaylistGenerator interface {
|
type PlaylistProvider interface {
|
||||||
// GetAvailablePlaylists - GetAvailablePlaylists returns the list of playlists this plugin provides.
|
// GetAvailablePlaylists - GetAvailablePlaylists returns the list of playlists this plugin provides.
|
||||||
GetAvailablePlaylists(GetAvailablePlaylistsRequest) (GetAvailablePlaylistsResponse, error)
|
GetAvailablePlaylists(GetAvailablePlaylistsRequest) (GetAvailablePlaylistsResponse, error)
|
||||||
// GetPlaylist - GetPlaylist returns the full data for a single playlist (tracks, metadata).
|
// GetPlaylist - GetPlaylist returns the full data for a single playlist (tracks, metadata).
|
||||||
@ -105,9 +105,9 @@ var (
|
|||||||
playlistImpl func(GetPlaylistRequest) (GetPlaylistResponse, error)
|
playlistImpl func(GetPlaylistRequest) (GetPlaylistResponse, error)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Register registers a playlistgenerator implementation.
|
// Register registers a playlistprovider implementation.
|
||||||
// All methods are required.
|
// All methods are required.
|
||||||
func Register(impl PlaylistGenerator) {
|
func Register(impl PlaylistProvider) {
|
||||||
availablePlaylistsImpl = impl.GetAvailablePlaylists
|
availablePlaylistsImpl = impl.GetAvailablePlaylists
|
||||||
playlistImpl = impl.GetPlaylist
|
playlistImpl = impl.GetPlaylist
|
||||||
}
|
}
|
||||||
@ -116,8 +116,8 @@ func Register(impl PlaylistGenerator) {
|
|||||||
// The host recognizes this and skips the plugin gracefully.
|
// The host recognizes this and skips the plugin gracefully.
|
||||||
const NotImplementedCode int32 = -2
|
const NotImplementedCode int32 = -2
|
||||||
|
|
||||||
//go:wasmexport nd_playlist_generator_get_available_playlists
|
//go:wasmexport nd_playlist_provider_get_available_playlists
|
||||||
func _NdPlaylistGeneratorGetAvailablePlaylists() int32 {
|
func _NdPlaylistProviderGetAvailablePlaylists() int32 {
|
||||||
if availablePlaylistsImpl == nil {
|
if availablePlaylistsImpl == nil {
|
||||||
// Return standard code - host will skip this plugin gracefully
|
// Return standard code - host will skip this plugin gracefully
|
||||||
return NotImplementedCode
|
return NotImplementedCode
|
||||||
@ -143,8 +143,8 @@ func _NdPlaylistGeneratorGetAvailablePlaylists() int32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:wasmexport nd_playlist_generator_get_playlist
|
//go:wasmexport nd_playlist_provider_get_playlist
|
||||||
func _NdPlaylistGeneratorGetPlaylist() int32 {
|
func _NdPlaylistProviderGetPlaylist() int32 {
|
||||||
if playlistImpl == nil {
|
if playlistImpl == nil {
|
||||||
// Return standard code - host will skip this plugin gracefully
|
// Return standard code - host will skip this plugin gracefully
|
||||||
return NotImplementedCode
|
return NotImplementedCode
|
||||||
@ -6,18 +6,18 @@
|
|||||||
//
|
//
|
||||||
//go:build !wasip1
|
//go:build !wasip1
|
||||||
|
|
||||||
package playlistgenerator
|
package playlistprovider
|
||||||
|
|
||||||
// PlaylistGeneratorError represents an error type for playlist generator operations.
|
// PlaylistProviderError represents an error type for playlist provider operations.
|
||||||
type PlaylistGeneratorError string
|
type PlaylistProviderError string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// PlaylistGeneratorErrorNotFound indicates a playlist is currently unavailable.
|
// PlaylistProviderErrorNotFound indicates a playlist is currently unavailable.
|
||||||
PlaylistGeneratorErrorNotFound PlaylistGeneratorError = "playlist_generator(not_found)"
|
PlaylistProviderErrorNotFound PlaylistProviderError = "playlist_provider(not_found)"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error implements the error interface for PlaylistGeneratorError.
|
// Error implements the error interface for PlaylistProviderError.
|
||||||
func (e PlaylistGeneratorError) Error() string { return string(e) }
|
func (e PlaylistProviderError) Error() string { return string(e) }
|
||||||
|
|
||||||
// GetAvailablePlaylistsRequest is the request for GetAvailablePlaylists.
|
// GetAvailablePlaylistsRequest is the request for GetAvailablePlaylists.
|
||||||
type GetAvailablePlaylistsRequest struct {
|
type GetAvailablePlaylistsRequest struct {
|
||||||
@ -86,12 +86,12 @@ type SongRef struct {
|
|||||||
Duration float32 `json:"duration,omitempty"`
|
Duration float32 `json:"duration,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlaylistGenerator requires all methods to be implemented.
|
// PlaylistProvider requires all methods to be implemented.
|
||||||
// PlaylistGenerator provides dynamically-generated playlists (e.g., "Daily Mix",
|
// PlaylistProvider provides dynamically-generated playlists (e.g., "Daily Mix",
|
||||||
// personalized recommendations). Plugins implementing this capability expose two
|
// personalized recommendations). Plugins implementing this capability expose two
|
||||||
// functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for
|
// functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for
|
||||||
// fetching the heavy payload (tracks, metadata).
|
// fetching the heavy payload (tracks, metadata).
|
||||||
type PlaylistGenerator interface {
|
type PlaylistProvider interface {
|
||||||
// GetAvailablePlaylists - GetAvailablePlaylists returns the list of playlists this plugin provides.
|
// GetAvailablePlaylists - GetAvailablePlaylists returns the list of playlists this plugin provides.
|
||||||
GetAvailablePlaylists(GetAvailablePlaylistsRequest) (GetAvailablePlaylistsResponse, error)
|
GetAvailablePlaylists(GetAvailablePlaylistsRequest) (GetAvailablePlaylistsResponse, error)
|
||||||
// GetPlaylist - GetPlaylist returns the full data for a single playlist (tracks, metadata).
|
// GetPlaylist - GetPlaylist returns the full data for a single playlist (tracks, metadata).
|
||||||
@ -103,4 +103,4 @@ const NotImplementedCode int32 = -2
|
|||||||
|
|
||||||
// Register is a no-op on non-WASM platforms.
|
// Register is a no-op on non-WASM platforms.
|
||||||
// This stub allows code to compile outside of WASM.
|
// This stub allows code to compile outside of WASM.
|
||||||
func Register(_ PlaylistGenerator) {}
|
func Register(_ PlaylistProvider) {}
|
||||||
@ -8,7 +8,7 @@
|
|||||||
pub mod lifecycle;
|
pub mod lifecycle;
|
||||||
pub mod lyrics;
|
pub mod lyrics;
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod playlistgenerator;
|
pub mod playlistprovider;
|
||||||
pub mod scheduler;
|
pub mod scheduler;
|
||||||
pub mod scrobbler;
|
pub mod scrobbler;
|
||||||
pub mod taskworker;
|
pub mod taskworker;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by ndpgen. DO NOT EDIT.
|
// Code generated by ndpgen. DO NOT EDIT.
|
||||||
//
|
//
|
||||||
// This file contains export wrappers for the PlaylistGenerator capability.
|
// This file contains export wrappers for the PlaylistProvider capability.
|
||||||
// It is intended for use in Navidrome plugins built with extism-pdk.
|
// It is intended for use in Navidrome plugins built with extism-pdk.
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -18,10 +18,10 @@ fn is_zero_u64(value: &u64) -> bool { *value == 0 }
|
|||||||
fn is_zero_f32(value: &f32) -> bool { *value == 0.0 }
|
fn is_zero_f32(value: &f32) -> bool { *value == 0.0 }
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn is_zero_f64(value: &f64) -> bool { *value == 0.0 }
|
fn is_zero_f64(value: &f64) -> bool { *value == 0.0 }
|
||||||
/// PlaylistGeneratorError represents an error type for playlist generator operations.
|
/// PlaylistProviderError represents an error type for playlist provider operations.
|
||||||
pub type PlaylistGeneratorError = &'static str;
|
pub type PlaylistProviderError = &'static str;
|
||||||
/// PlaylistGeneratorErrorNotFound indicates a playlist is currently unavailable.
|
/// PlaylistProviderErrorNotFound indicates a playlist is currently unavailable.
|
||||||
pub const PLAYLIST_GENERATOR_ERROR_NOT_FOUND: PlaylistGeneratorError = "playlist_generator(not_found)";
|
pub const PLAYLIST_PROVIDER_ERROR_NOT_FOUND: PlaylistProviderError = "playlist_provider(not_found)";
|
||||||
/// GetAvailablePlaylistsRequest is the request for GetAvailablePlaylists.
|
/// GetAvailablePlaylistsRequest is the request for GetAvailablePlaylists.
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -136,37 +136,37 @@ impl Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PlaylistGenerator requires all methods to be implemented.
|
/// PlaylistProvider requires all methods to be implemented.
|
||||||
/// PlaylistGenerator provides dynamically-generated playlists (e.g., "Daily Mix",
|
/// PlaylistProvider provides dynamically-generated playlists (e.g., "Daily Mix",
|
||||||
/// personalized recommendations). Plugins implementing this capability expose two
|
/// personalized recommendations). Plugins implementing this capability expose two
|
||||||
/// functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for
|
/// functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for
|
||||||
/// fetching the heavy payload (tracks, metadata).
|
/// fetching the heavy payload (tracks, metadata).
|
||||||
pub trait PlaylistGenerator {
|
pub trait PlaylistProvider {
|
||||||
/// GetAvailablePlaylists - GetAvailablePlaylists returns the list of playlists this plugin provides.
|
/// GetAvailablePlaylists - GetAvailablePlaylists returns the list of playlists this plugin provides.
|
||||||
fn get_available_playlists(&self, req: GetAvailablePlaylistsRequest) -> Result<GetAvailablePlaylistsResponse, Error>;
|
fn get_available_playlists(&self, req: GetAvailablePlaylistsRequest) -> Result<GetAvailablePlaylistsResponse, Error>;
|
||||||
/// GetPlaylist - GetPlaylist returns the full data for a single playlist (tracks, metadata).
|
/// GetPlaylist - GetPlaylist returns the full data for a single playlist (tracks, metadata).
|
||||||
fn get_playlist(&self, req: GetPlaylistRequest) -> Result<GetPlaylistResponse, Error>;
|
fn get_playlist(&self, req: GetPlaylistRequest) -> Result<GetPlaylistResponse, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register all exports for the PlaylistGenerator capability.
|
/// Register all exports for the PlaylistProvider capability.
|
||||||
/// This macro generates the WASM export functions for all trait methods.
|
/// This macro generates the WASM export functions for all trait methods.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! register_playlistgenerator {
|
macro_rules! register_playlistprovider {
|
||||||
($plugin_type:ty) => {
|
($plugin_type:ty) => {
|
||||||
#[extism_pdk::plugin_fn]
|
#[extism_pdk::plugin_fn]
|
||||||
pub fn nd_playlist_generator_get_available_playlists(
|
pub fn nd_playlist_provider_get_available_playlists(
|
||||||
req: extism_pdk::Json<$crate::playlistgenerator::GetAvailablePlaylistsRequest>
|
req: extism_pdk::Json<$crate::playlistprovider::GetAvailablePlaylistsRequest>
|
||||||
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::playlistgenerator::GetAvailablePlaylistsResponse>> {
|
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::playlistprovider::GetAvailablePlaylistsResponse>> {
|
||||||
let plugin = <$plugin_type>::default();
|
let plugin = <$plugin_type>::default();
|
||||||
let result = $crate::playlistgenerator::PlaylistGenerator::get_available_playlists(&plugin, req.into_inner())?;
|
let result = $crate::playlistprovider::PlaylistProvider::get_available_playlists(&plugin, req.into_inner())?;
|
||||||
Ok(extism_pdk::Json(result))
|
Ok(extism_pdk::Json(result))
|
||||||
}
|
}
|
||||||
#[extism_pdk::plugin_fn]
|
#[extism_pdk::plugin_fn]
|
||||||
pub fn nd_playlist_generator_get_playlist(
|
pub fn nd_playlist_provider_get_playlist(
|
||||||
req: extism_pdk::Json<$crate::playlistgenerator::GetPlaylistRequest>
|
req: extism_pdk::Json<$crate::playlistprovider::GetPlaylistRequest>
|
||||||
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::playlistgenerator::GetPlaylistResponse>> {
|
) -> extism_pdk::FnResult<extism_pdk::Json<$crate::playlistprovider::GetPlaylistResponse>> {
|
||||||
let plugin = <$plugin_type>::default();
|
let plugin = <$plugin_type>::default();
|
||||||
let result = $crate::playlistgenerator::PlaylistGenerator::get_playlist(&plugin, req.into_inner())?;
|
let result = $crate::playlistprovider::PlaylistProvider::get_playlist(&plugin, req.into_inner())?;
|
||||||
Ok(extism_pdk::Json(result))
|
Ok(extism_pdk::Json(result))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -15,10 +15,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CapabilityPlaylistGenerator Capability = "PlaylistGenerator"
|
CapabilityPlaylistProvider Capability = "PlaylistProvider"
|
||||||
|
|
||||||
FuncPlaylistGeneratorGetAvailablePlaylists = "nd_playlist_generator_get_available_playlists"
|
FuncPlaylistProviderGetAvailablePlaylists = "nd_playlist_provider_get_available_playlists"
|
||||||
FuncPlaylistGeneratorGetPlaylist = "nd_playlist_generator_get_playlist"
|
FuncPlaylistProviderGetPlaylist = "nd_playlist_provider_get_playlist"
|
||||||
|
|
||||||
// workChCapacity is the buffer size for the work channel.
|
// workChCapacity is the buffer size for the work channel.
|
||||||
workChCapacity = 16
|
workChCapacity = 16
|
||||||
@ -29,9 +29,9 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerCapability(
|
registerCapability(
|
||||||
CapabilityPlaylistGenerator,
|
CapabilityPlaylistProvider,
|
||||||
FuncPlaylistGeneratorGetAvailablePlaylists,
|
FuncPlaylistProviderGetAvailablePlaylists,
|
||||||
FuncPlaylistGeneratorGetPlaylist,
|
FuncPlaylistProviderGetPlaylist,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,11 +49,11 @@ type workItem struct {
|
|||||||
ownerID string // only for workSync
|
ownerID string // only for workSync
|
||||||
}
|
}
|
||||||
|
|
||||||
// playlistGeneratorOrchestrator manages playlist generation for a single plugin.
|
// playlistSyncer manages playlist synchronization for a single plugin.
|
||||||
// All mutable state (refreshTimers, discoveryTimer) is owned exclusively by the
|
// All mutable state (refreshTimers, discoveryTimer) is owned exclusively by the
|
||||||
// worker goroutine — no synchronization needed. The retryInterval and
|
// worker goroutine — no synchronization needed. The retryInterval and
|
||||||
// refreshTimerCount fields use atomics so tests can observe them race-free.
|
// refreshTimerCount fields use atomics so tests can observe them race-free.
|
||||||
type playlistGeneratorOrchestrator struct {
|
type playlistSyncer struct {
|
||||||
pluginName string
|
pluginName string
|
||||||
plugin *plugin
|
plugin *plugin
|
||||||
ds model.DataStore
|
ds model.DataStore
|
||||||
@ -68,9 +68,9 @@ type playlistGeneratorOrchestrator struct {
|
|||||||
done chan struct{} // closed when worker exits
|
done chan struct{} // closed when worker exits
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPlaylistGeneratorOrchestrator(parentCtx context.Context, pluginName string, p *plugin, ds model.DataStore, m *matcher.Matcher) *playlistGeneratorOrchestrator {
|
func newPlaylistSyncer(parentCtx context.Context, pluginName string, p *plugin, ds model.DataStore, m *matcher.Matcher) *playlistSyncer {
|
||||||
ctx, cancel := context.WithCancel(parentCtx)
|
ctx, cancel := context.WithCancel(parentCtx)
|
||||||
return &playlistGeneratorOrchestrator{
|
return &playlistSyncer{
|
||||||
pluginName: pluginName,
|
pluginName: pluginName,
|
||||||
plugin: p,
|
plugin: p,
|
||||||
ds: ds,
|
ds: ds,
|
||||||
@ -85,7 +85,7 @@ func newPlaylistGeneratorOrchestrator(parentCtx context.Context, pluginName stri
|
|||||||
|
|
||||||
// run is the single worker goroutine that processes all work items sequentially.
|
// run is the single worker goroutine that processes all work items sequentially.
|
||||||
// It performs an initial discovery before entering the main loop.
|
// It performs an initial discovery before entering the main loop.
|
||||||
func (o *playlistGeneratorOrchestrator) run() {
|
func (o *playlistSyncer) run() {
|
||||||
defer close(o.done)
|
defer close(o.done)
|
||||||
|
|
||||||
// Run initial discovery before entering the loop
|
// Run initial discovery before entering the loop
|
||||||
@ -108,10 +108,10 @@ func (o *playlistGeneratorOrchestrator) run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// discoverAndSync calls GetAvailablePlaylists, then GetPlaylist for each, matches tracks, and upserts.
|
// discoverAndSync calls GetAvailablePlaylists, then GetPlaylist for each, matches tracks, and upserts.
|
||||||
func (o *playlistGeneratorOrchestrator) discoverAndSync() {
|
func (o *playlistSyncer) discoverAndSync() {
|
||||||
ctx := o.ctx
|
ctx := o.ctx
|
||||||
resp, err := callPluginFunction[capabilities.GetAvailablePlaylistsRequest, capabilities.GetAvailablePlaylistsResponse](
|
resp, err := callPluginFunction[capabilities.GetAvailablePlaylistsRequest, capabilities.GetAvailablePlaylistsResponse](
|
||||||
ctx, o.plugin, FuncPlaylistGeneratorGetAvailablePlaylists, capabilities.GetAvailablePlaylistsRequest{},
|
ctx, o.plugin, FuncPlaylistProviderGetAvailablePlaylists, capabilities.GetAvailablePlaylistsRequest{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(ctx, "Failed to call GetAvailablePlaylists, retrying later", "plugin", o.pluginName, err)
|
log.Error(ctx, "Failed to call GetAvailablePlaylists, retrying later", "plugin", o.pluginName, err)
|
||||||
@ -144,10 +144,10 @@ func (o *playlistGeneratorOrchestrator) discoverAndSync() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// syncPlaylist calls GetPlaylist, matches tracks, and upserts the playlist in the DB.
|
// syncPlaylist calls GetPlaylist, matches tracks, and upserts the playlist in the DB.
|
||||||
func (o *playlistGeneratorOrchestrator) syncPlaylist(info capabilities.PlaylistInfo, dbID string, ownerID string) {
|
func (o *playlistSyncer) syncPlaylist(info capabilities.PlaylistInfo, dbID string, ownerID string) {
|
||||||
ctx := o.ctx
|
ctx := o.ctx
|
||||||
resp, err := callPluginFunction[capabilities.GetPlaylistRequest, capabilities.GetPlaylistResponse](
|
resp, err := callPluginFunction[capabilities.GetPlaylistRequest, capabilities.GetPlaylistResponse](
|
||||||
ctx, o.plugin, FuncPlaylistGeneratorGetPlaylist, capabilities.GetPlaylistRequest{ID: info.ID},
|
ctx, o.plugin, FuncPlaylistProviderGetPlaylist, capabilities.GetPlaylistRequest{ID: info.ID},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isPlaylistNotFoundError(err) {
|
if isPlaylistNotFoundError(err) {
|
||||||
@ -221,7 +221,7 @@ func (o *playlistGeneratorOrchestrator) syncPlaylist(info capabilities.PlaylistI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *playlistGeneratorOrchestrator) schedulePlaylistRefresh(info capabilities.PlaylistInfo, dbID string, ownerID string, delay time.Duration) {
|
func (o *playlistSyncer) schedulePlaylistRefresh(info capabilities.PlaylistInfo, dbID string, ownerID string, delay time.Duration) {
|
||||||
// Cancel existing timer if any
|
// Cancel existing timer if any
|
||||||
if timer, ok := o.refreshTimers[dbID]; ok {
|
if timer, ok := o.refreshTimers[dbID]; ok {
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
@ -235,7 +235,7 @@ func (o *playlistGeneratorOrchestrator) schedulePlaylistRefresh(info capabilitie
|
|||||||
o.refreshTimerCount.Store(int32(len(o.refreshTimers)))
|
o.refreshTimerCount.Store(int32(len(o.refreshTimers)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *playlistGeneratorOrchestrator) scheduleDiscovery(delay time.Duration) {
|
func (o *playlistSyncer) scheduleDiscovery(delay time.Duration) {
|
||||||
if o.discoveryTimer != nil {
|
if o.discoveryTimer != nil {
|
||||||
o.discoveryTimer.Stop()
|
o.discoveryTimer.Stop()
|
||||||
}
|
}
|
||||||
@ -249,11 +249,11 @@ func (o *playlistGeneratorOrchestrator) scheduleDiscovery(delay time.Duration) {
|
|||||||
|
|
||||||
// isPlaylistNotFoundError checks if the error contains a NotFound sentinel from the plugin.
|
// isPlaylistNotFoundError checks if the error contains a NotFound sentinel from the plugin.
|
||||||
func isPlaylistNotFoundError(err error) bool {
|
func isPlaylistNotFoundError(err error) bool {
|
||||||
return err != nil && strings.Contains(err.Error(), capabilities.PlaylistGeneratorErrorNotFound.Error())
|
return err != nil && strings.Contains(err.Error(), capabilities.PlaylistProviderErrorNotFound.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// stopAllTimers stops the discovery timer and all refresh timers.
|
// stopAllTimers stops the discovery timer and all refresh timers.
|
||||||
func (o *playlistGeneratorOrchestrator) stopAllTimers() {
|
func (o *playlistSyncer) stopAllTimers() {
|
||||||
if o.discoveryTimer != nil {
|
if o.discoveryTimer != nil {
|
||||||
o.discoveryTimer.Stop()
|
o.discoveryTimer.Stop()
|
||||||
}
|
}
|
||||||
@ -263,7 +263,7 @@ func (o *playlistGeneratorOrchestrator) stopAllTimers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Close cancels the context and waits for the worker goroutine to finish.
|
// Close cancels the context and waits for the worker goroutine to finish.
|
||||||
func (o *playlistGeneratorOrchestrator) Close() error {
|
func (o *playlistSyncer) Close() error {
|
||||||
o.cancel()
|
o.cancel()
|
||||||
<-o.done
|
<-o.done
|
||||||
return nil
|
return nil
|
||||||
@ -12,8 +12,8 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
// findOrchestrator finds the playlistGeneratorOrchestrator in a plugin's closers.
|
// findSyncer finds the playlistSyncer in a plugin's closers.
|
||||||
func findOrchestrator(m *Manager, pluginName string) *playlistGeneratorOrchestrator {
|
func findSyncer(m *Manager, pluginName string) *playlistSyncer {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
p, ok := m.plugins[pluginName]
|
p, ok := m.plugins[pluginName]
|
||||||
@ -21,14 +21,14 @@ func findOrchestrator(m *Manager, pluginName string) *playlistGeneratorOrchestra
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, c := range p.closers {
|
for _, c := range p.closers {
|
||||||
if orch, ok := c.(*playlistGeneratorOrchestrator); ok {
|
if syncer, ok := c.(*playlistSyncer); ok {
|
||||||
return orch
|
return syncer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = Describe("PlaylistGenerator", Ordered, func() {
|
var _ = Describe("PlaylistProvider", Ordered, func() {
|
||||||
var (
|
var (
|
||||||
pgManager *Manager
|
pgManager *Manager
|
||||||
mockPlsRepo *tests.MockPlaylistRepo
|
mockPlsRepo *tests.MockPlaylistRepo
|
||||||
@ -36,26 +36,26 @@ var _ = Describe("PlaylistGenerator", Ordered, func() {
|
|||||||
|
|
||||||
BeforeAll(func() {
|
BeforeAll(func() {
|
||||||
pgManager, _ = createTestManagerWithPlugins(nil,
|
pgManager, _ = createTestManagerWithPlugins(nil,
|
||||||
"test-playlist-generator"+PackageExtension,
|
"test-playlist-provider"+PackageExtension,
|
||||||
)
|
)
|
||||||
|
|
||||||
mockPlsRepo = pgManager.ds.(*tests.MockDataStore).MockedPlaylist.(*tests.MockPlaylistRepo)
|
mockPlsRepo = pgManager.ds.(*tests.MockDataStore).MockedPlaylist.(*tests.MockPlaylistRepo)
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("capability detection", func() {
|
Describe("capability detection", func() {
|
||||||
It("detects the PlaylistGenerator capability", func() {
|
It("detects the PlaylistProvider capability", func() {
|
||||||
names := pgManager.PluginNames(string(CapabilityPlaylistGenerator))
|
names := pgManager.PluginNames(string(CapabilityPlaylistProvider))
|
||||||
Expect(names).To(ContainElement("test-playlist-generator"))
|
Expect(names).To(ContainElement("test-playlist-provider"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("orchestrator lifecycle", func() {
|
Describe("syncer lifecycle", func() {
|
||||||
It("creates an orchestrator for the plugin", func() {
|
It("creates a syncer for the plugin", func() {
|
||||||
Expect(findOrchestrator(pgManager, "test-playlist-generator")).ToNot(BeNil())
|
Expect(findSyncer(pgManager, "test-playlist-provider")).ToNot(BeNil())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("discovers and syncs playlists from the plugin", func() {
|
It("discovers and syncs playlists from the plugin", func() {
|
||||||
// The orchestrator runs discoverAndSync in a goroutine on Start().
|
// The syncer runs discoverAndSync in a goroutine on Start().
|
||||||
// Give it a moment to complete.
|
// Give it a moment to complete.
|
||||||
Eventually(func() int {
|
Eventually(func() int {
|
||||||
return mockPlsRepo.Len()
|
return mockPlsRepo.Len()
|
||||||
@ -72,13 +72,13 @@ var _ = Describe("PlaylistGenerator", Ordered, func() {
|
|||||||
Expect(dailyMix1.Comment).To(Equal("Your personalized daily mix"))
|
Expect(dailyMix1.Comment).To(Equal("Your personalized daily mix"))
|
||||||
Expect(dailyMix1.ExternalImageURL).To(Equal("https://example.com/cover1.jpg"))
|
Expect(dailyMix1.ExternalImageURL).To(Equal("https://example.com/cover1.jpg"))
|
||||||
Expect(dailyMix1.OwnerID).To(Equal("user-1"))
|
Expect(dailyMix1.OwnerID).To(Equal("user-1"))
|
||||||
Expect(dailyMix1.PluginID).To(Equal("test-playlist-generator"))
|
Expect(dailyMix1.PluginID).To(Equal("test-playlist-provider"))
|
||||||
Expect(dailyMix1.PluginPlaylistID).To(Equal("daily-mix-1"))
|
Expect(dailyMix1.PluginPlaylistID).To(Equal("daily-mix-1"))
|
||||||
Expect(dailyMix1.Public).To(BeFalse())
|
Expect(dailyMix1.Public).To(BeFalse())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("generates deterministic playlist IDs", func() {
|
It("generates deterministic playlist IDs", func() {
|
||||||
expectedID := id.NewHash("test-playlist-generator", "daily-mix-1", "user-1")
|
expectedID := id.NewHash("test-playlist-provider", "daily-mix-1", "user-1")
|
||||||
Eventually(func() bool {
|
Eventually(func() bool {
|
||||||
_, exists := mockPlsRepo.GetData(expectedID)
|
_, exists := mockPlsRepo.GetData(expectedID)
|
||||||
return exists
|
return exists
|
||||||
@ -86,8 +86,8 @@ var _ = Describe("PlaylistGenerator", Ordered, func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("creates distinct IDs for different playlists", func() {
|
It("creates distinct IDs for different playlists", func() {
|
||||||
id1 := id.NewHash("test-playlist-generator", "daily-mix-1", "user-1")
|
id1 := id.NewHash("test-playlist-provider", "daily-mix-1", "user-1")
|
||||||
id2 := id.NewHash("test-playlist-generator", "daily-mix-2", "user-1")
|
id2 := id.NewHash("test-playlist-provider", "daily-mix-2", "user-1")
|
||||||
Expect(id1).ToNot(Equal(id2))
|
Expect(id1).ToNot(Equal(id2))
|
||||||
|
|
||||||
Eventually(func() bool {
|
Eventually(func() bool {
|
||||||
@ -101,15 +101,15 @@ var _ = Describe("PlaylistGenerator", Ordered, func() {
|
|||||||
Describe("GetAvailablePlaylists error handling", func() {
|
Describe("GetAvailablePlaylists error handling", func() {
|
||||||
It("handles plugin errors gracefully", func() {
|
It("handles plugin errors gracefully", func() {
|
||||||
errManager, _ := createTestManagerWithPlugins(map[string]map[string]string{
|
errManager, _ := createTestManagerWithPlugins(map[string]map[string]string{
|
||||||
"test-playlist-generator": {"error": "service unavailable"},
|
"test-playlist-provider": {"error": "service unavailable"},
|
||||||
}, "test-playlist-generator"+PackageExtension)
|
}, "test-playlist-provider"+PackageExtension)
|
||||||
|
|
||||||
// Should still have the orchestrator (error is logged, not fatal)
|
// Should still have the syncer (error is logged, not fatal)
|
||||||
Expect(findOrchestrator(errManager, "test-playlist-generator")).ToNot(BeNil())
|
Expect(findSyncer(errManager, "test-playlist-provider")).ToNot(BeNil())
|
||||||
|
|
||||||
// But no playlists created
|
// But no playlists created
|
||||||
errPlsRepo := errManager.ds.(*tests.MockDataStore).MockedPlaylist.(*tests.MockPlaylistRepo)
|
errPlsRepo := errManager.ds.(*tests.MockDataStore).MockedPlaylist.(*tests.MockPlaylistRepo)
|
||||||
// The orchestrator was started but GetAvailablePlaylists returned error,
|
// The syncer was started but GetAvailablePlaylists returned error,
|
||||||
// so no playlists should be created
|
// so no playlists should be created
|
||||||
Consistently(func() int {
|
Consistently(func() int {
|
||||||
return errPlsRepo.Len()
|
return errPlsRepo.Len()
|
||||||
@ -120,14 +120,14 @@ var _ = Describe("PlaylistGenerator", Ordered, func() {
|
|||||||
Describe("GetPlaylist NotFound error", func() {
|
Describe("GetPlaylist NotFound error", func() {
|
||||||
It("skips playlists when plugin returns NotFound", func() {
|
It("skips playlists when plugin returns NotFound", func() {
|
||||||
notFoundManager, _ := createTestManagerWithPlugins(map[string]map[string]string{
|
notFoundManager, _ := createTestManagerWithPlugins(map[string]map[string]string{
|
||||||
"test-playlist-generator": {
|
"test-playlist-provider": {
|
||||||
"get_playlist_error": "playlist temporarily unavailable",
|
"get_playlist_error": "playlist temporarily unavailable",
|
||||||
"get_playlist_error_type": string(capabilities.PlaylistGeneratorErrorNotFound),
|
"get_playlist_error_type": string(capabilities.PlaylistProviderErrorNotFound),
|
||||||
},
|
},
|
||||||
}, "test-playlist-generator"+PackageExtension)
|
}, "test-playlist-provider"+PackageExtension)
|
||||||
|
|
||||||
// Should still have the orchestrator
|
// Should still have the syncer
|
||||||
Expect(findOrchestrator(notFoundManager, "test-playlist-generator")).ToNot(BeNil())
|
Expect(findSyncer(notFoundManager, "test-playlist-provider")).ToNot(BeNil())
|
||||||
|
|
||||||
// No playlists should be created (all returned NotFound)
|
// No playlists should be created (all returned NotFound)
|
||||||
notFoundPlsRepo := notFoundManager.ds.(*tests.MockDataStore).MockedPlaylist.(*tests.MockPlaylistRepo)
|
notFoundPlsRepo := notFoundManager.ds.(*tests.MockDataStore).MockedPlaylist.(*tests.MockPlaylistRepo)
|
||||||
@ -136,9 +136,9 @@ var _ = Describe("PlaylistGenerator", Ordered, func() {
|
|||||||
}, "500ms").Should(Equal(0))
|
}, "500ms").Should(Equal(0))
|
||||||
|
|
||||||
// No refresh timers should be scheduled for NotFound playlists
|
// No refresh timers should be scheduled for NotFound playlists
|
||||||
orch := findOrchestrator(notFoundManager, "test-playlist-generator")
|
syncer := findSyncer(notFoundManager, "test-playlist-provider")
|
||||||
Eventually(func() int32 {
|
Eventually(func() int32 {
|
||||||
return orch.refreshTimerCount.Load()
|
return syncer.refreshTimerCount.Load()
|
||||||
}).Should(Equal(int32(0)))
|
}).Should(Equal(int32(0)))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -146,18 +146,18 @@ var _ = Describe("PlaylistGenerator", Ordered, func() {
|
|||||||
Describe("GetPlaylist transient error with RetryInterval", func() {
|
Describe("GetPlaylist transient error with RetryInterval", func() {
|
||||||
It("stores retryInterval and schedules retry on transient errors", func() {
|
It("stores retryInterval and schedules retry on transient errors", func() {
|
||||||
retryManager, _ := createTestManagerWithPlugins(map[string]map[string]string{
|
retryManager, _ := createTestManagerWithPlugins(map[string]map[string]string{
|
||||||
"test-playlist-generator": {
|
"test-playlist-provider": {
|
||||||
"get_playlist_error": "temporary failure",
|
"get_playlist_error": "temporary failure",
|
||||||
"retry_interval": "60",
|
"retry_interval": "60",
|
||||||
},
|
},
|
||||||
}, "test-playlist-generator"+PackageExtension)
|
}, "test-playlist-provider"+PackageExtension)
|
||||||
|
|
||||||
orch := findOrchestrator(retryManager, "test-playlist-generator")
|
syncer := findSyncer(retryManager, "test-playlist-provider")
|
||||||
Expect(orch).ToNot(BeNil())
|
Expect(syncer).ToNot(BeNil())
|
||||||
|
|
||||||
// retryInterval should be stored from the response
|
// retryInterval should be stored from the response
|
||||||
Eventually(func() time.Duration {
|
Eventually(func() time.Duration {
|
||||||
return time.Duration(orch.retryInterval.Load())
|
return time.Duration(syncer.retryInterval.Load())
|
||||||
}).Should(Equal(60 * time.Second))
|
}).Should(Equal(60 * time.Second))
|
||||||
|
|
||||||
// No playlists should be created (GetPlaylist failed)
|
// No playlists should be created (GetPlaylist failed)
|
||||||
@ -168,22 +168,22 @@ var _ = Describe("PlaylistGenerator", Ordered, func() {
|
|||||||
|
|
||||||
// Refresh timers should be scheduled for transient errors
|
// Refresh timers should be scheduled for transient errors
|
||||||
Eventually(func() int32 {
|
Eventually(func() int32 {
|
||||||
return orch.refreshTimerCount.Load()
|
return syncer.refreshTimerCount.Load()
|
||||||
}).Should(BeNumerically(">=", int32(1)))
|
}).Should(BeNumerically(">=", int32(1)))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("stop", func() {
|
Describe("stop", func() {
|
||||||
It("stops the orchestrator when the manager stops", func() {
|
It("stops the syncer when the manager stops", func() {
|
||||||
stopManager, _ := createTestManagerWithPlugins(nil,
|
stopManager, _ := createTestManagerWithPlugins(nil,
|
||||||
"test-playlist-generator"+PackageExtension,
|
"test-playlist-provider"+PackageExtension,
|
||||||
)
|
)
|
||||||
Expect(findOrchestrator(stopManager, "test-playlist-generator")).ToNot(BeNil())
|
Expect(findSyncer(stopManager, "test-playlist-provider")).ToNot(BeNil())
|
||||||
|
|
||||||
err := stopManager.Stop()
|
err := stopManager.Stop()
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
// After Stop(), the plugin is unloaded so findOrchestrator returns nil
|
// After Stop(), the plugin is unloaded so findSyncer returns nil
|
||||||
Expect(findOrchestrator(stopManager, "test-playlist-generator")).To(BeNil())
|
Expect(findSyncer(stopManager, "test-playlist-provider")).To(BeNil())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -136,7 +136,7 @@ func createTestManagerWithPluginsAndMetrics(pluginConfig map[string]map[string]s
|
|||||||
mockPluginRepo.SetData(enabledPlugins)
|
mockPluginRepo.SetData(enabledPlugins)
|
||||||
|
|
||||||
// Pre-seed a mock user repo with a default user so that
|
// Pre-seed a mock user repo with a default user so that
|
||||||
// PlaylistGenerator's discoverAndSync can resolve usernames.
|
// PlaylistProvider's discoverAndSync can resolve usernames.
|
||||||
mockUserRepo := tests.CreateMockUserRepo()
|
mockUserRepo := tests.CreateMockUserRepo()
|
||||||
_ = mockUserRepo.Put(&model.User{ID: "user-1", UserName: "admin"})
|
_ = mockUserRepo.Put(&model.User{ID: "user-1", UserName: "admin"})
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Test Playlist Generator",
|
|
||||||
"author": "Navidrome Test",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "A test playlist generator plugin for integration testing"
|
|
||||||
}
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
module test-playlist-generator
|
module test-playlist-provider
|
||||||
|
|
||||||
go 1.25
|
go 1.25
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// Test playlist generator plugin for Navidrome plugin system integration tests.
|
// Test playlist provider plugin for Navidrome plugin system integration tests.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -6,20 +6,20 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
"github.com/navidrome/navidrome/plugins/pdk/go/pdk"
|
||||||
pg "github.com/navidrome/navidrome/plugins/pdk/go/playlistgenerator"
|
pp "github.com/navidrome/navidrome/plugins/pdk/go/playlistprovider"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
pg.Register(&testPlaylistGenerator{})
|
pp.Register(&testPlaylistProvider{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type testPlaylistGenerator struct{}
|
type testPlaylistProvider struct{}
|
||||||
|
|
||||||
func (t *testPlaylistGenerator) GetAvailablePlaylists(_ pg.GetAvailablePlaylistsRequest) (pg.GetAvailablePlaylistsResponse, error) {
|
func (t *testPlaylistProvider) GetAvailablePlaylists(_ pp.GetAvailablePlaylistsRequest) (pp.GetAvailablePlaylistsResponse, error) {
|
||||||
// Check for configured error
|
// Check for configured error
|
||||||
errMsg, hasErr := pdk.GetConfig("error")
|
errMsg, hasErr := pdk.GetConfig("error")
|
||||||
if hasErr && errMsg != "" {
|
if hasErr && errMsg != "" {
|
||||||
return pg.GetAvailablePlaylistsResponse{}, fmt.Errorf("%s", errMsg)
|
return pp.GetAvailablePlaylistsResponse{}, fmt.Errorf("%s", errMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the owner username from config (defaults to "admin")
|
// Get the owner username from config (defaults to "admin")
|
||||||
@ -28,8 +28,8 @@ func (t *testPlaylistGenerator) GetAvailablePlaylists(_ pg.GetAvailablePlaylists
|
|||||||
ownerUsername = u
|
ownerUsername = u
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := pg.GetAvailablePlaylistsResponse{
|
resp := pp.GetAvailablePlaylistsResponse{
|
||||||
Playlists: []pg.PlaylistInfo{
|
Playlists: []pp.PlaylistInfo{
|
||||||
{ID: "daily-mix-1", OwnerUsername: ownerUsername},
|
{ID: "daily-mix-1", OwnerUsername: ownerUsername},
|
||||||
{ID: "daily-mix-2", OwnerUsername: ownerUsername},
|
{ID: "daily-mix-2", OwnerUsername: ownerUsername},
|
||||||
},
|
},
|
||||||
@ -46,40 +46,40 @@ func (t *testPlaylistGenerator) GetAvailablePlaylists(_ pg.GetAvailablePlaylists
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *testPlaylistGenerator) GetPlaylist(req pg.GetPlaylistRequest) (pg.GetPlaylistResponse, error) {
|
func (t *testPlaylistProvider) GetPlaylist(req pp.GetPlaylistRequest) (pp.GetPlaylistResponse, error) {
|
||||||
// Check for configured error
|
// Check for configured error
|
||||||
errMsg, hasErr := pdk.GetConfig("get_playlist_error")
|
errMsg, hasErr := pdk.GetConfig("get_playlist_error")
|
||||||
if hasErr && errMsg != "" {
|
if hasErr && errMsg != "" {
|
||||||
// Check if the error should be typed (e.g., NotFound)
|
// Check if the error should be typed (e.g., NotFound)
|
||||||
errType, _ := pdk.GetConfig("get_playlist_error_type")
|
errType, _ := pdk.GetConfig("get_playlist_error_type")
|
||||||
if errType == pg.PlaylistGeneratorErrorNotFound.Error() {
|
if errType == pp.PlaylistProviderErrorNotFound.Error() {
|
||||||
return pg.GetPlaylistResponse{}, fmt.Errorf("%w: %s", pg.PlaylistGeneratorErrorNotFound, errMsg)
|
return pp.GetPlaylistResponse{}, fmt.Errorf("%w: %s", pp.PlaylistProviderErrorNotFound, errMsg)
|
||||||
}
|
}
|
||||||
return pg.GetPlaylistResponse{}, fmt.Errorf("%s", errMsg)
|
return pp.GetPlaylistResponse{}, fmt.Errorf("%s", errMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch req.ID {
|
switch req.ID {
|
||||||
case "daily-mix-1":
|
case "daily-mix-1":
|
||||||
return pg.GetPlaylistResponse{
|
return pp.GetPlaylistResponse{
|
||||||
Name: "Daily Mix 1",
|
Name: "Daily Mix 1",
|
||||||
Description: "Your personalized daily mix",
|
Description: "Your personalized daily mix",
|
||||||
CoverArtURL: "https://example.com/cover1.jpg",
|
CoverArtURL: "https://example.com/cover1.jpg",
|
||||||
Tracks: []pg.SongRef{
|
Tracks: []pp.SongRef{
|
||||||
{Name: "Song A", Artist: "Artist One"},
|
{Name: "Song A", Artist: "Artist One"},
|
||||||
{Name: "Song B", Artist: "Artist Two"},
|
{Name: "Song B", Artist: "Artist Two"},
|
||||||
},
|
},
|
||||||
ValidUntil: 0, // Static, no refresh
|
ValidUntil: 0, // Static, no refresh
|
||||||
}, nil
|
}, nil
|
||||||
case "daily-mix-2":
|
case "daily-mix-2":
|
||||||
return pg.GetPlaylistResponse{
|
return pp.GetPlaylistResponse{
|
||||||
Name: "Daily Mix 2",
|
Name: "Daily Mix 2",
|
||||||
Tracks: []pg.SongRef{
|
Tracks: []pp.SongRef{
|
||||||
{Name: "Song C", Artist: "Artist Three"},
|
{Name: "Song C", Artist: "Artist Three"},
|
||||||
},
|
},
|
||||||
ValidUntil: 0,
|
ValidUntil: 0,
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
return pg.GetPlaylistResponse{}, fmt.Errorf("unknown playlist: %s", req.ID)
|
return pp.GetPlaylistResponse{}, fmt.Errorf("unknown playlist: %s", req.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
6
plugins/testdata/test-playlist-provider/manifest.json
vendored
Normal file
6
plugins/testdata/test-playlist-provider/manifest.json
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "Test Playlist Provider",
|
||||||
|
"author": "Navidrome Test",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A test playlist provider plugin for integration testing"
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user