From 04aa10f988783db16280271a5dd9dce5941bc340 Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 5 Mar 2026 16:51:07 -0500 Subject: [PATCH] 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. --- ...list_generator.go => playlist_provider.go} | 22 ++--- ..._generator.yaml => playlist_provider.yaml} | 4 +- plugins/manager_loader.go | 10 +-- .../playlistprovider.go} | 34 ++++---- .../playlistprovider_stub.go} | 22 ++--- .../pdk/rust/nd-pdk-capabilities/src/lib.rs | 2 +- ...aylistgenerator.rs => playlistprovider.rs} | 36 ++++----- ...list_generator.go => playlist_provider.go} | 40 +++++----- ...ator_test.go => playlist_provider_test.go} | 80 +++++++++---------- plugins/plugins_suite_test.go | 2 +- .../test-playlist-generator/manifest.json | 6 -- .../go.mod | 2 +- .../go.sum | 0 .../main.go | 34 ++++---- .../test-playlist-provider/manifest.json | 6 ++ 15 files changed, 150 insertions(+), 150 deletions(-) rename plugins/capabilities/{playlist_generator.go => playlist_provider.go} (76%) rename plugins/capabilities/{playlist_generator.yaml => playlist_provider.yaml} (98%) rename plugins/pdk/go/{playlistgenerator/playlistgenerator.go => playlistprovider/playlistprovider.go} (82%) rename plugins/pdk/go/{playlistgenerator/playlistgenerator_stub.go => playlistprovider/playlistprovider_stub.go} (84%) rename plugins/pdk/rust/nd-pdk-capabilities/src/{playlistgenerator.rs => playlistprovider.rs} (82%) rename plugins/{playlist_generator.go => playlist_provider.go} (83%) rename plugins/{playlist_generator_test.go => playlist_provider_test.go} (62%) delete mode 100644 plugins/testdata/test-playlist-generator/manifest.json rename plugins/testdata/{test-playlist-generator => test-playlist-provider}/go.mod (93%) rename plugins/testdata/{test-playlist-generator => test-playlist-provider}/go.sum (100%) rename plugins/testdata/{test-playlist-generator => test-playlist-provider}/main.go (59%) create mode 100644 plugins/testdata/test-playlist-provider/manifest.json diff --git a/plugins/capabilities/playlist_generator.go b/plugins/capabilities/playlist_provider.go similarity index 76% rename from plugins/capabilities/playlist_generator.go rename to plugins/capabilities/playlist_provider.go index 4d034a7c0..72ebaca7d 100644 --- a/plugins/capabilities/playlist_generator.go +++ b/plugins/capabilities/playlist_provider.go @@ -1,18 +1,18 @@ 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 // functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for // fetching the heavy payload (tracks, metadata). // -//nd:capability name=playlistgenerator required=true -type PlaylistGenerator interface { +//nd:capability name=playlistprovider required=true +type PlaylistProvider interface { // 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) // 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) } @@ -60,13 +60,13 @@ type GetPlaylistResponse struct { ValidUntil int64 `json:"validUntil"` } -// PlaylistGeneratorError represents an error type for playlist generator operations. -type PlaylistGeneratorError string +// PlaylistProviderError represents an error type for playlist provider operations. +type PlaylistProviderError string const ( - // PlaylistGeneratorErrorNotFound indicates a playlist is currently unavailable. - PlaylistGeneratorErrorNotFound PlaylistGeneratorError = "playlist_generator(not_found)" + // PlaylistProviderErrorNotFound indicates a playlist is currently unavailable. + PlaylistProviderErrorNotFound PlaylistProviderError = "playlist_provider(not_found)" ) -// Error implements the error interface for PlaylistGeneratorError. -func (e PlaylistGeneratorError) Error() string { return string(e) } +// Error implements the error interface for PlaylistProviderError. +func (e PlaylistProviderError) Error() string { return string(e) } diff --git a/plugins/capabilities/playlist_generator.yaml b/plugins/capabilities/playlist_provider.yaml similarity index 98% rename from plugins/capabilities/playlist_generator.yaml rename to plugins/capabilities/playlist_provider.yaml index 519387660..66604a6bc 100644 --- a/plugins/capabilities/playlist_generator.yaml +++ b/plugins/capabilities/playlist_provider.yaml @@ -1,6 +1,6 @@ version: v1-draft exports: - nd_playlist_generator_get_available_playlists: + nd_playlist_provider_get_available_playlists: description: GetAvailablePlaylists returns the list of playlists this plugin provides. input: $ref: '#/components/schemas/GetAvailablePlaylistsRequest' @@ -8,7 +8,7 @@ exports: output: $ref: '#/components/schemas/GetAvailablePlaylistsResponse' 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). input: $ref: '#/components/schemas/GetPlaylistRequest' diff --git a/plugins/manager_loader.go b/plugins/manager_loader.go index 406c2f183..895b944fb 100644 --- a/plugins/manager_loader.go +++ b/plugins/manager_loader.go @@ -391,12 +391,12 @@ func (m *Manager) loadPluginWithConfig(p *model.Plugin) error { // Call plugin init function 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] - if hasCapability(loadedPlugin.capabilities, CapabilityPlaylistGenerator) { - orch := newPlaylistGeneratorOrchestrator(m.ctx, p.ID, loadedPlugin, m.ds, m.matcher) - loadedPlugin.closers = append(loadedPlugin.closers, orch) - go orch.run() + if hasCapability(loadedPlugin.capabilities, CapabilityPlaylistProvider) { + syncer := newPlaylistSyncer(m.ctx, p.ID, loadedPlugin, m.ds, m.matcher) + loadedPlugin.closers = append(loadedPlugin.closers, syncer) + go syncer.run() } return nil diff --git a/plugins/pdk/go/playlistgenerator/playlistgenerator.go b/plugins/pdk/go/playlistprovider/playlistprovider.go similarity index 82% rename from plugins/pdk/go/playlistgenerator/playlistgenerator.go rename to plugins/pdk/go/playlistprovider/playlistprovider.go index 15453c13f..42a99af00 100644 --- a/plugins/pdk/go/playlistgenerator/playlistgenerator.go +++ b/plugins/pdk/go/playlistprovider/playlistprovider.go @@ -1,26 +1,26 @@ // 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. // //go:build wasip1 -package playlistgenerator +package playlistprovider import ( "github.com/navidrome/navidrome/plugins/pdk/go/pdk" ) -// PlaylistGeneratorError represents an error type for playlist generator operations. -type PlaylistGeneratorError string +// PlaylistProviderError represents an error type for playlist provider operations. +type PlaylistProviderError string const ( - // PlaylistGeneratorErrorNotFound indicates a playlist is currently unavailable. - PlaylistGeneratorErrorNotFound PlaylistGeneratorError = "playlist_generator(not_found)" + // PlaylistProviderErrorNotFound indicates a playlist is currently unavailable. + PlaylistProviderErrorNotFound PlaylistProviderError = "playlist_provider(not_found)" ) -// Error implements the error interface for PlaylistGeneratorError. -func (e PlaylistGeneratorError) Error() string { return string(e) } +// Error implements the error interface for PlaylistProviderError. +func (e PlaylistProviderError) Error() string { return string(e) } // GetAvailablePlaylistsRequest is the request for GetAvailablePlaylists. type GetAvailablePlaylistsRequest struct { @@ -89,12 +89,12 @@ type SongRef struct { Duration float32 `json:"duration,omitempty"` } -// PlaylistGenerator requires all methods to be implemented. -// PlaylistGenerator provides dynamically-generated playlists (e.g., "Daily Mix", +// PlaylistProvider requires all methods to be implemented. +// PlaylistProvider provides dynamically-generated playlists (e.g., "Daily Mix", // personalized recommendations). Plugins implementing this capability expose two // functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for // fetching the heavy payload (tracks, metadata). -type PlaylistGenerator interface { +type PlaylistProvider interface { // GetAvailablePlaylists - GetAvailablePlaylists returns the list of playlists this plugin provides. GetAvailablePlaylists(GetAvailablePlaylistsRequest) (GetAvailablePlaylistsResponse, error) // GetPlaylist - GetPlaylist returns the full data for a single playlist (tracks, metadata). @@ -105,9 +105,9 @@ var ( playlistImpl func(GetPlaylistRequest) (GetPlaylistResponse, error) ) -// Register registers a playlistgenerator implementation. +// Register registers a playlistprovider implementation. // All methods are required. -func Register(impl PlaylistGenerator) { +func Register(impl PlaylistProvider) { availablePlaylistsImpl = impl.GetAvailablePlaylists playlistImpl = impl.GetPlaylist } @@ -116,8 +116,8 @@ func Register(impl PlaylistGenerator) { // The host recognizes this and skips the plugin gracefully. const NotImplementedCode int32 = -2 -//go:wasmexport nd_playlist_generator_get_available_playlists -func _NdPlaylistGeneratorGetAvailablePlaylists() int32 { +//go:wasmexport nd_playlist_provider_get_available_playlists +func _NdPlaylistProviderGetAvailablePlaylists() int32 { if availablePlaylistsImpl == nil { // Return standard code - host will skip this plugin gracefully return NotImplementedCode @@ -143,8 +143,8 @@ func _NdPlaylistGeneratorGetAvailablePlaylists() int32 { return 0 } -//go:wasmexport nd_playlist_generator_get_playlist -func _NdPlaylistGeneratorGetPlaylist() int32 { +//go:wasmexport nd_playlist_provider_get_playlist +func _NdPlaylistProviderGetPlaylist() int32 { if playlistImpl == nil { // Return standard code - host will skip this plugin gracefully return NotImplementedCode diff --git a/plugins/pdk/go/playlistgenerator/playlistgenerator_stub.go b/plugins/pdk/go/playlistprovider/playlistprovider_stub.go similarity index 84% rename from plugins/pdk/go/playlistgenerator/playlistgenerator_stub.go rename to plugins/pdk/go/playlistprovider/playlistprovider_stub.go index 348492a44..9fdff8e48 100644 --- a/plugins/pdk/go/playlistgenerator/playlistgenerator_stub.go +++ b/plugins/pdk/go/playlistprovider/playlistprovider_stub.go @@ -6,18 +6,18 @@ // //go:build !wasip1 -package playlistgenerator +package playlistprovider -// PlaylistGeneratorError represents an error type for playlist generator operations. -type PlaylistGeneratorError string +// PlaylistProviderError represents an error type for playlist provider operations. +type PlaylistProviderError string const ( - // PlaylistGeneratorErrorNotFound indicates a playlist is currently unavailable. - PlaylistGeneratorErrorNotFound PlaylistGeneratorError = "playlist_generator(not_found)" + // PlaylistProviderErrorNotFound indicates a playlist is currently unavailable. + PlaylistProviderErrorNotFound PlaylistProviderError = "playlist_provider(not_found)" ) -// Error implements the error interface for PlaylistGeneratorError. -func (e PlaylistGeneratorError) Error() string { return string(e) } +// Error implements the error interface for PlaylistProviderError. +func (e PlaylistProviderError) Error() string { return string(e) } // GetAvailablePlaylistsRequest is the request for GetAvailablePlaylists. type GetAvailablePlaylistsRequest struct { @@ -86,12 +86,12 @@ type SongRef struct { Duration float32 `json:"duration,omitempty"` } -// PlaylistGenerator requires all methods to be implemented. -// PlaylistGenerator provides dynamically-generated playlists (e.g., "Daily Mix", +// PlaylistProvider requires all methods to be implemented. +// PlaylistProvider provides dynamically-generated playlists (e.g., "Daily Mix", // personalized recommendations). Plugins implementing this capability expose two // functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for // fetching the heavy payload (tracks, metadata). -type PlaylistGenerator interface { +type PlaylistProvider interface { // GetAvailablePlaylists - GetAvailablePlaylists returns the list of playlists this plugin provides. GetAvailablePlaylists(GetAvailablePlaylistsRequest) (GetAvailablePlaylistsResponse, error) // 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. // This stub allows code to compile outside of WASM. -func Register(_ PlaylistGenerator) {} +func Register(_ PlaylistProvider) {} diff --git a/plugins/pdk/rust/nd-pdk-capabilities/src/lib.rs b/plugins/pdk/rust/nd-pdk-capabilities/src/lib.rs index f0f21b0e8..43b3f428c 100644 --- a/plugins/pdk/rust/nd-pdk-capabilities/src/lib.rs +++ b/plugins/pdk/rust/nd-pdk-capabilities/src/lib.rs @@ -8,7 +8,7 @@ pub mod lifecycle; pub mod lyrics; pub mod metadata; -pub mod playlistgenerator; +pub mod playlistprovider; pub mod scheduler; pub mod scrobbler; pub mod taskworker; diff --git a/plugins/pdk/rust/nd-pdk-capabilities/src/playlistgenerator.rs b/plugins/pdk/rust/nd-pdk-capabilities/src/playlistprovider.rs similarity index 82% rename from plugins/pdk/rust/nd-pdk-capabilities/src/playlistgenerator.rs rename to plugins/pdk/rust/nd-pdk-capabilities/src/playlistprovider.rs index 9e9d3e555..de5da4516 100644 --- a/plugins/pdk/rust/nd-pdk-capabilities/src/playlistgenerator.rs +++ b/plugins/pdk/rust/nd-pdk-capabilities/src/playlistprovider.rs @@ -1,6 +1,6 @@ // 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. 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 } #[allow(dead_code)] fn is_zero_f64(value: &f64) -> bool { *value == 0.0 } -/// PlaylistGeneratorError represents an error type for playlist generator operations. -pub type PlaylistGeneratorError = &'static str; -/// PlaylistGeneratorErrorNotFound indicates a playlist is currently unavailable. -pub const PLAYLIST_GENERATOR_ERROR_NOT_FOUND: PlaylistGeneratorError = "playlist_generator(not_found)"; +/// PlaylistProviderError represents an error type for playlist provider operations. +pub type PlaylistProviderError = &'static str; +/// PlaylistProviderErrorNotFound indicates a playlist is currently unavailable. +pub const PLAYLIST_PROVIDER_ERROR_NOT_FOUND: PlaylistProviderError = "playlist_provider(not_found)"; /// GetAvailablePlaylistsRequest is the request for GetAvailablePlaylists. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -136,37 +136,37 @@ impl Error { } } -/// PlaylistGenerator requires all methods to be implemented. -/// PlaylistGenerator provides dynamically-generated playlists (e.g., "Daily Mix", +/// PlaylistProvider requires all methods to be implemented. +/// PlaylistProvider provides dynamically-generated playlists (e.g., "Daily Mix", /// personalized recommendations). Plugins implementing this capability expose two /// functions: GetAvailablePlaylists for lightweight discovery and GetPlaylist for /// fetching the heavy payload (tracks, metadata). -pub trait PlaylistGenerator { +pub trait PlaylistProvider { /// GetAvailablePlaylists - GetAvailablePlaylists returns the list of playlists this plugin provides. fn get_available_playlists(&self, req: GetAvailablePlaylistsRequest) -> Result; /// GetPlaylist - GetPlaylist returns the full data for a single playlist (tracks, metadata). fn get_playlist(&self, req: GetPlaylistRequest) -> Result; } -/// 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. #[macro_export] -macro_rules! register_playlistgenerator { +macro_rules! register_playlistprovider { ($plugin_type:ty) => { #[extism_pdk::plugin_fn] - pub fn nd_playlist_generator_get_available_playlists( - req: extism_pdk::Json<$crate::playlistgenerator::GetAvailablePlaylistsRequest> - ) -> extism_pdk::FnResult> { + pub fn nd_playlist_provider_get_available_playlists( + req: extism_pdk::Json<$crate::playlistprovider::GetAvailablePlaylistsRequest> + ) -> extism_pdk::FnResult> { 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)) } #[extism_pdk::plugin_fn] - pub fn nd_playlist_generator_get_playlist( - req: extism_pdk::Json<$crate::playlistgenerator::GetPlaylistRequest> - ) -> extism_pdk::FnResult> { + pub fn nd_playlist_provider_get_playlist( + req: extism_pdk::Json<$crate::playlistprovider::GetPlaylistRequest> + ) -> extism_pdk::FnResult> { 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)) } }; diff --git a/plugins/playlist_generator.go b/plugins/playlist_provider.go similarity index 83% rename from plugins/playlist_generator.go rename to plugins/playlist_provider.go index a2a684228..aa28990ea 100644 --- a/plugins/playlist_generator.go +++ b/plugins/playlist_provider.go @@ -15,10 +15,10 @@ import ( ) const ( - CapabilityPlaylistGenerator Capability = "PlaylistGenerator" + CapabilityPlaylistProvider Capability = "PlaylistProvider" - FuncPlaylistGeneratorGetAvailablePlaylists = "nd_playlist_generator_get_available_playlists" - FuncPlaylistGeneratorGetPlaylist = "nd_playlist_generator_get_playlist" + FuncPlaylistProviderGetAvailablePlaylists = "nd_playlist_provider_get_available_playlists" + FuncPlaylistProviderGetPlaylist = "nd_playlist_provider_get_playlist" // workChCapacity is the buffer size for the work channel. workChCapacity = 16 @@ -29,9 +29,9 @@ const ( func init() { registerCapability( - CapabilityPlaylistGenerator, - FuncPlaylistGeneratorGetAvailablePlaylists, - FuncPlaylistGeneratorGetPlaylist, + CapabilityPlaylistProvider, + FuncPlaylistProviderGetAvailablePlaylists, + FuncPlaylistProviderGetPlaylist, ) } @@ -49,11 +49,11 @@ type workItem struct { 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 // worker goroutine — no synchronization needed. The retryInterval and // refreshTimerCount fields use atomics so tests can observe them race-free. -type playlistGeneratorOrchestrator struct { +type playlistSyncer struct { pluginName string plugin *plugin ds model.DataStore @@ -68,9 +68,9 @@ type playlistGeneratorOrchestrator struct { 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) - return &playlistGeneratorOrchestrator{ + return &playlistSyncer{ pluginName: pluginName, plugin: p, 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. // It performs an initial discovery before entering the main loop. -func (o *playlistGeneratorOrchestrator) run() { +func (o *playlistSyncer) run() { defer close(o.done) // 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. -func (o *playlistGeneratorOrchestrator) discoverAndSync() { +func (o *playlistSyncer) discoverAndSync() { ctx := o.ctx resp, err := callPluginFunction[capabilities.GetAvailablePlaylistsRequest, capabilities.GetAvailablePlaylistsResponse]( - ctx, o.plugin, FuncPlaylistGeneratorGetAvailablePlaylists, capabilities.GetAvailablePlaylistsRequest{}, + ctx, o.plugin, FuncPlaylistProviderGetAvailablePlaylists, capabilities.GetAvailablePlaylistsRequest{}, ) if err != nil { 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. -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 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 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 if timer, ok := o.refreshTimers[dbID]; ok { timer.Stop() @@ -235,7 +235,7 @@ func (o *playlistGeneratorOrchestrator) schedulePlaylistRefresh(info capabilitie 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 { 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. 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. -func (o *playlistGeneratorOrchestrator) stopAllTimers() { +func (o *playlistSyncer) stopAllTimers() { if o.discoveryTimer != nil { o.discoveryTimer.Stop() } @@ -263,7 +263,7 @@ func (o *playlistGeneratorOrchestrator) stopAllTimers() { } // 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.done return nil diff --git a/plugins/playlist_generator_test.go b/plugins/playlist_provider_test.go similarity index 62% rename from plugins/playlist_generator_test.go rename to plugins/playlist_provider_test.go index 2086e0436..7d18e04e6 100644 --- a/plugins/playlist_generator_test.go +++ b/plugins/playlist_provider_test.go @@ -12,8 +12,8 @@ import ( . "github.com/onsi/gomega" ) -// findOrchestrator finds the playlistGeneratorOrchestrator in a plugin's closers. -func findOrchestrator(m *Manager, pluginName string) *playlistGeneratorOrchestrator { +// findSyncer finds the playlistSyncer in a plugin's closers. +func findSyncer(m *Manager, pluginName string) *playlistSyncer { m.mu.RLock() defer m.mu.RUnlock() p, ok := m.plugins[pluginName] @@ -21,14 +21,14 @@ func findOrchestrator(m *Manager, pluginName string) *playlistGeneratorOrchestra return nil } for _, c := range p.closers { - if orch, ok := c.(*playlistGeneratorOrchestrator); ok { - return orch + if syncer, ok := c.(*playlistSyncer); ok { + return syncer } } return nil } -var _ = Describe("PlaylistGenerator", Ordered, func() { +var _ = Describe("PlaylistProvider", Ordered, func() { var ( pgManager *Manager mockPlsRepo *tests.MockPlaylistRepo @@ -36,26 +36,26 @@ var _ = Describe("PlaylistGenerator", Ordered, func() { BeforeAll(func() { pgManager, _ = createTestManagerWithPlugins(nil, - "test-playlist-generator"+PackageExtension, + "test-playlist-provider"+PackageExtension, ) mockPlsRepo = pgManager.ds.(*tests.MockDataStore).MockedPlaylist.(*tests.MockPlaylistRepo) }) Describe("capability detection", func() { - It("detects the PlaylistGenerator capability", func() { - names := pgManager.PluginNames(string(CapabilityPlaylistGenerator)) - Expect(names).To(ContainElement("test-playlist-generator")) + It("detects the PlaylistProvider capability", func() { + names := pgManager.PluginNames(string(CapabilityPlaylistProvider)) + Expect(names).To(ContainElement("test-playlist-provider")) }) }) - Describe("orchestrator lifecycle", func() { - It("creates an orchestrator for the plugin", func() { - Expect(findOrchestrator(pgManager, "test-playlist-generator")).ToNot(BeNil()) + Describe("syncer lifecycle", func() { + It("creates a syncer for the plugin", func() { + Expect(findSyncer(pgManager, "test-playlist-provider")).ToNot(BeNil()) }) 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. Eventually(func() int { return mockPlsRepo.Len() @@ -72,13 +72,13 @@ var _ = Describe("PlaylistGenerator", Ordered, func() { Expect(dailyMix1.Comment).To(Equal("Your personalized daily mix")) Expect(dailyMix1.ExternalImageURL).To(Equal("https://example.com/cover1.jpg")) 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.Public).To(BeFalse()) }) 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 { _, exists := mockPlsRepo.GetData(expectedID) return exists @@ -86,8 +86,8 @@ var _ = Describe("PlaylistGenerator", Ordered, func() { }) It("creates distinct IDs for different playlists", func() { - id1 := id.NewHash("test-playlist-generator", "daily-mix-1", "user-1") - id2 := id.NewHash("test-playlist-generator", "daily-mix-2", "user-1") + id1 := id.NewHash("test-playlist-provider", "daily-mix-1", "user-1") + id2 := id.NewHash("test-playlist-provider", "daily-mix-2", "user-1") Expect(id1).ToNot(Equal(id2)) Eventually(func() bool { @@ -101,15 +101,15 @@ var _ = Describe("PlaylistGenerator", Ordered, func() { Describe("GetAvailablePlaylists error handling", func() { It("handles plugin errors gracefully", func() { errManager, _ := createTestManagerWithPlugins(map[string]map[string]string{ - "test-playlist-generator": {"error": "service unavailable"}, - }, "test-playlist-generator"+PackageExtension) + "test-playlist-provider": {"error": "service unavailable"}, + }, "test-playlist-provider"+PackageExtension) - // Should still have the orchestrator (error is logged, not fatal) - Expect(findOrchestrator(errManager, "test-playlist-generator")).ToNot(BeNil()) + // Should still have the syncer (error is logged, not fatal) + Expect(findSyncer(errManager, "test-playlist-provider")).ToNot(BeNil()) // But no playlists created 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 Consistently(func() int { return errPlsRepo.Len() @@ -120,14 +120,14 @@ var _ = Describe("PlaylistGenerator", Ordered, func() { Describe("GetPlaylist NotFound error", func() { It("skips playlists when plugin returns NotFound", func() { notFoundManager, _ := createTestManagerWithPlugins(map[string]map[string]string{ - "test-playlist-generator": { + "test-playlist-provider": { "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 - Expect(findOrchestrator(notFoundManager, "test-playlist-generator")).ToNot(BeNil()) + // Should still have the syncer + Expect(findSyncer(notFoundManager, "test-playlist-provider")).ToNot(BeNil()) // No playlists should be created (all returned NotFound) notFoundPlsRepo := notFoundManager.ds.(*tests.MockDataStore).MockedPlaylist.(*tests.MockPlaylistRepo) @@ -136,9 +136,9 @@ var _ = Describe("PlaylistGenerator", Ordered, func() { }, "500ms").Should(Equal(0)) // No refresh timers should be scheduled for NotFound playlists - orch := findOrchestrator(notFoundManager, "test-playlist-generator") + syncer := findSyncer(notFoundManager, "test-playlist-provider") Eventually(func() int32 { - return orch.refreshTimerCount.Load() + return syncer.refreshTimerCount.Load() }).Should(Equal(int32(0))) }) }) @@ -146,18 +146,18 @@ var _ = Describe("PlaylistGenerator", Ordered, func() { Describe("GetPlaylist transient error with RetryInterval", func() { It("stores retryInterval and schedules retry on transient errors", func() { retryManager, _ := createTestManagerWithPlugins(map[string]map[string]string{ - "test-playlist-generator": { + "test-playlist-provider": { "get_playlist_error": "temporary failure", "retry_interval": "60", }, - }, "test-playlist-generator"+PackageExtension) + }, "test-playlist-provider"+PackageExtension) - orch := findOrchestrator(retryManager, "test-playlist-generator") - Expect(orch).ToNot(BeNil()) + syncer := findSyncer(retryManager, "test-playlist-provider") + Expect(syncer).ToNot(BeNil()) // retryInterval should be stored from the response Eventually(func() time.Duration { - return time.Duration(orch.retryInterval.Load()) + return time.Duration(syncer.retryInterval.Load()) }).Should(Equal(60 * time.Second)) // No playlists should be created (GetPlaylist failed) @@ -168,22 +168,22 @@ var _ = Describe("PlaylistGenerator", Ordered, func() { // Refresh timers should be scheduled for transient errors Eventually(func() int32 { - return orch.refreshTimerCount.Load() + return syncer.refreshTimerCount.Load() }).Should(BeNumerically(">=", int32(1))) }) }) Describe("stop", func() { - It("stops the orchestrator when the manager stops", func() { + It("stops the syncer when the manager stops", func() { 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() Expect(err).ToNot(HaveOccurred()) - // After Stop(), the plugin is unloaded so findOrchestrator returns nil - Expect(findOrchestrator(stopManager, "test-playlist-generator")).To(BeNil()) + // After Stop(), the plugin is unloaded so findSyncer returns nil + Expect(findSyncer(stopManager, "test-playlist-provider")).To(BeNil()) }) }) }) diff --git a/plugins/plugins_suite_test.go b/plugins/plugins_suite_test.go index 42bf64e5b..d284577d5 100644 --- a/plugins/plugins_suite_test.go +++ b/plugins/plugins_suite_test.go @@ -136,7 +136,7 @@ func createTestManagerWithPluginsAndMetrics(pluginConfig map[string]map[string]s mockPluginRepo.SetData(enabledPlugins) // 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.Put(&model.User{ID: "user-1", UserName: "admin"}) diff --git a/plugins/testdata/test-playlist-generator/manifest.json b/plugins/testdata/test-playlist-generator/manifest.json deleted file mode 100644 index 6874d089f..000000000 --- a/plugins/testdata/test-playlist-generator/manifest.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Test Playlist Generator", - "author": "Navidrome Test", - "version": "1.0.0", - "description": "A test playlist generator plugin for integration testing" -} diff --git a/plugins/testdata/test-playlist-generator/go.mod b/plugins/testdata/test-playlist-provider/go.mod similarity index 93% rename from plugins/testdata/test-playlist-generator/go.mod rename to plugins/testdata/test-playlist-provider/go.mod index 7539672b5..b09064953 100644 --- a/plugins/testdata/test-playlist-generator/go.mod +++ b/plugins/testdata/test-playlist-provider/go.mod @@ -1,4 +1,4 @@ -module test-playlist-generator +module test-playlist-provider go 1.25 diff --git a/plugins/testdata/test-playlist-generator/go.sum b/plugins/testdata/test-playlist-provider/go.sum similarity index 100% rename from plugins/testdata/test-playlist-generator/go.sum rename to plugins/testdata/test-playlist-provider/go.sum diff --git a/plugins/testdata/test-playlist-generator/main.go b/plugins/testdata/test-playlist-provider/main.go similarity index 59% rename from plugins/testdata/test-playlist-generator/main.go rename to plugins/testdata/test-playlist-provider/main.go index f98168b5c..41def98aa 100644 --- a/plugins/testdata/test-playlist-generator/main.go +++ b/plugins/testdata/test-playlist-provider/main.go @@ -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 import ( @@ -6,20 +6,20 @@ import ( "strconv" "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() { - 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 errMsg, hasErr := pdk.GetConfig("error") 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") @@ -28,8 +28,8 @@ func (t *testPlaylistGenerator) GetAvailablePlaylists(_ pg.GetAvailablePlaylists ownerUsername = u } - resp := pg.GetAvailablePlaylistsResponse{ - Playlists: []pg.PlaylistInfo{ + resp := pp.GetAvailablePlaylistsResponse{ + Playlists: []pp.PlaylistInfo{ {ID: "daily-mix-1", OwnerUsername: ownerUsername}, {ID: "daily-mix-2", OwnerUsername: ownerUsername}, }, @@ -46,40 +46,40 @@ func (t *testPlaylistGenerator) GetAvailablePlaylists(_ pg.GetAvailablePlaylists 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 errMsg, hasErr := pdk.GetConfig("get_playlist_error") if hasErr && errMsg != "" { // Check if the error should be typed (e.g., NotFound) errType, _ := pdk.GetConfig("get_playlist_error_type") - if errType == pg.PlaylistGeneratorErrorNotFound.Error() { - return pg.GetPlaylistResponse{}, fmt.Errorf("%w: %s", pg.PlaylistGeneratorErrorNotFound, errMsg) + if errType == pp.PlaylistProviderErrorNotFound.Error() { + 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 { case "daily-mix-1": - return pg.GetPlaylistResponse{ + return pp.GetPlaylistResponse{ Name: "Daily Mix 1", Description: "Your personalized daily mix", CoverArtURL: "https://example.com/cover1.jpg", - Tracks: []pg.SongRef{ + Tracks: []pp.SongRef{ {Name: "Song A", Artist: "Artist One"}, {Name: "Song B", Artist: "Artist Two"}, }, ValidUntil: 0, // Static, no refresh }, nil case "daily-mix-2": - return pg.GetPlaylistResponse{ + return pp.GetPlaylistResponse{ Name: "Daily Mix 2", - Tracks: []pg.SongRef{ + Tracks: []pp.SongRef{ {Name: "Song C", Artist: "Artist Three"}, }, ValidUntil: 0, }, nil default: - return pg.GetPlaylistResponse{}, fmt.Errorf("unknown playlist: %s", req.ID) + return pp.GetPlaylistResponse{}, fmt.Errorf("unknown playlist: %s", req.ID) } } diff --git a/plugins/testdata/test-playlist-provider/manifest.json b/plugins/testdata/test-playlist-provider/manifest.json new file mode 100644 index 000000000..f429c71d6 --- /dev/null +++ b/plugins/testdata/test-playlist-provider/manifest.json @@ -0,0 +1,6 @@ +{ + "name": "Test Playlist Provider", + "author": "Navidrome Test", + "version": "1.0.0", + "description": "A test playlist provider plugin for integration testing" +}