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" +}