diff --git a/.gitignore b/.gitignore index d5a0367bff..e399f1fc47 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ local.properties .classpath .settings/ .loadpath +*.lscache # External tool builders .externalToolBuilders/ diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 91ccb16ef9..f699c99d85 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -564,7 +564,8 @@ namespace Emby.Server.Implementations.Plugins Id = instance.Id, Status = PluginStatus.Active, Name = instance.Name, - Version = instance.Version.ToString() + Version = instance.Version.ToString(), + ImageResourceName = (instance as IHasEmbeddedImage)?.ImageResourceName }) { Instance = instance diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 79e6536fb6..0105ecf7a7 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -226,16 +226,32 @@ public class PluginsController : BaseJellyfinApiController return NotFound(); } - var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath ?? string.Empty); - if (plugin.Manifest.ImagePath is null || !System.IO.File.Exists(imagePath)) + if (!string.IsNullOrEmpty(plugin.Manifest.ImagePath)) { - return NotFound(); + var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath); + if (!System.IO.File.Exists(imagePath)) + { + return NotFound(); + } + + Response.Headers.ContentDisposition = "attachment"; + return PhysicalFile(imagePath, MimeTypes.GetMimeType(imagePath)); } - Response.Headers.ContentDisposition = "attachment"; + var resourceName = plugin.Manifest.ImageResourceName; + if (!string.IsNullOrEmpty(resourceName) && plugin.Instance is not null) + { + var stream = plugin.Instance.GetType().Assembly.GetManifestResourceStream(resourceName); + if (stream is null) + { + return NotFound(); + } - imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath); - return PhysicalFile(imagePath, MimeTypes.GetMimeType(imagePath)); + Response.Headers.ContentDisposition = "attachment"; + return File(stream, MimeTypes.GetMimeType(resourceName)); + } + + return NotFound(); } /// diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index 96af423cc3..4723be1001 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -109,7 +109,7 @@ namespace MediaBrowser.Common.Plugins { var inst = Instance?.GetPluginInfo() ?? new PluginInfo(Manifest.Name, Version, Manifest.Description, Manifest.Id, true); inst.Status = Manifest.Status; - inst.HasImage = !string.IsNullOrEmpty(Manifest.ImagePath); + inst.HasImage = !string.IsNullOrEmpty(Manifest.ImagePath) || !string.IsNullOrEmpty(Manifest.ImageResourceName); return inst; } diff --git a/MediaBrowser.Common/Plugins/PluginManifest.cs b/MediaBrowser.Common/Plugins/PluginManifest.cs index e0847ccea4..e749e85899 100644 --- a/MediaBrowser.Common/Plugins/PluginManifest.cs +++ b/MediaBrowser.Common/Plugins/PluginManifest.cs @@ -107,6 +107,15 @@ namespace MediaBrowser.Common.Plugins [JsonPropertyName("imagePath")] public string? ImagePath { get; set; } + /// + /// Gets or sets the name of an embedded resource in the plugin's assembly + /// that should be served as the plugin image. + /// Used by bundled/integrated plugins whose images are shipped inside the assembly + /// rather than on disk. Ignored when is set. + /// + [JsonIgnore] + public string? ImageResourceName { get; set; } + /// /// Gets or sets the collection of assemblies that should be loaded. /// Paths are considered relative to the plugin folder. diff --git a/MediaBrowser.Controller/Plugins/IHasEmbeddedImage.cs b/MediaBrowser.Controller/Plugins/IHasEmbeddedImage.cs new file mode 100644 index 0000000000..4196cd9f24 --- /dev/null +++ b/MediaBrowser.Controller/Plugins/IHasEmbeddedImage.cs @@ -0,0 +1,17 @@ +namespace MediaBrowser.Controller.Plugins; + +/// +/// Marker interface for integrated/bundled plugins that ship their plugin image as an embedded +/// resource inside the plugin assembly rather than as a file on disk. +/// +/// +/// This interface is intended for plugins compiled into the server. External plugins should +/// continue to declare their image via the imagePath field in meta.json. +/// +public interface IHasEmbeddedImage +{ + /// + /// Gets the name of the embedded resource in this plugin's assembly to serve as the plugin image. + /// + string ImageResourceName { get; } +} diff --git a/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalId.cs b/MediaBrowser.Providers/Books/ComicVine/ComicVineExternalId.cs similarity index 91% rename from MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalId.cs rename to MediaBrowser.Providers/Books/ComicVine/ComicVineExternalId.cs index 8cbd1f89a7..e2e785eaca 100644 --- a/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalId.cs +++ b/MediaBrowser.Providers/Books/ComicVine/ComicVineExternalId.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -namespace MediaBrowser.Providers.Plugins.ComicVine +namespace MediaBrowser.Providers.Books.ComicVine { /// public class ComicVineExternalId : IExternalId diff --git a/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalUrlProvider.cs b/MediaBrowser.Providers/Books/ComicVine/ComicVineExternalUrlProvider.cs similarity index 93% rename from MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalUrlProvider.cs rename to MediaBrowser.Providers/Books/ComicVine/ComicVineExternalUrlProvider.cs index 9122399179..a8450ec599 100644 --- a/MediaBrowser.Providers/Plugins/ComicVine/ComicVineExternalUrlProvider.cs +++ b/MediaBrowser.Providers/Books/ComicVine/ComicVineExternalUrlProvider.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -namespace MediaBrowser.Providers.Plugins.ComicVine; +namespace MediaBrowser.Providers.Books.ComicVine; /// public class ComicVineExternalUrlProvider : IExternalUrlProvider diff --git a/MediaBrowser.Providers/Plugins/ComicVine/ComicVinePersonExternalId.cs b/MediaBrowser.Providers/Books/ComicVine/ComicVinePersonExternalId.cs similarity index 92% rename from MediaBrowser.Providers/Plugins/ComicVine/ComicVinePersonExternalId.cs rename to MediaBrowser.Providers/Books/ComicVine/ComicVinePersonExternalId.cs index 26b8e11380..f625fb9649 100644 --- a/MediaBrowser.Providers/Plugins/ComicVine/ComicVinePersonExternalId.cs +++ b/MediaBrowser.Providers/Books/ComicVine/ComicVinePersonExternalId.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -namespace MediaBrowser.Providers.Plugins.ComicVine +namespace MediaBrowser.Providers.Books.ComicVine { /// public class ComicVinePersonExternalId : IExternalId diff --git a/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalId.cs b/MediaBrowser.Providers/Books/GoogleBooks/GoogleBooksExternalId.cs similarity index 91% rename from MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalId.cs rename to MediaBrowser.Providers/Books/GoogleBooks/GoogleBooksExternalId.cs index 02d3b36974..aac8cdff65 100644 --- a/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalId.cs +++ b/MediaBrowser.Providers/Books/GoogleBooks/GoogleBooksExternalId.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -namespace MediaBrowser.Providers.Plugins.GoogleBooks +namespace MediaBrowser.Providers.Books.GoogleBooks { /// public class GoogleBooksExternalId : IExternalId diff --git a/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalUrlProvider.cs b/MediaBrowser.Providers/Books/GoogleBooks/GoogleBooksExternalUrlProvider.cs similarity index 92% rename from MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalUrlProvider.cs rename to MediaBrowser.Providers/Books/GoogleBooks/GoogleBooksExternalUrlProvider.cs index 95047ee83e..0559db2e2b 100644 --- a/MediaBrowser.Providers/Plugins/GoogleBooks/GoogleBooksExternalUrlProvider.cs +++ b/MediaBrowser.Providers/Books/GoogleBooks/GoogleBooksExternalUrlProvider.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -namespace MediaBrowser.Providers.Plugins.GoogleBooks; +namespace MediaBrowser.Providers.Books.GoogleBooks; /// public class GoogleBooksExternalUrlProvider : IExternalUrlProvider diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 8b727a8cac..1032582900 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -47,22 +47,53 @@ + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs index 6c2ad0573e..bb6b67286d 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs @@ -5,12 +5,13 @@ using System; using System.Collections.Generic; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.Plugins.AudioDb { - public class Plugin : BasePlugin, IHasWebPages + public class Plugin : BasePlugin, IHasWebPages, IHasEmbeddedImage { public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) @@ -29,6 +30,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb // TODO remove when plugin removed from server. public override string ConfigurationFileName => "Jellyfin.Plugin.AudioDb.xml"; + public string ImageResourceName => GetType().Namespace + ".jellyfin-plugin-tadb.svg"; + public IEnumerable GetPages() { yield return new PluginPageInfo diff --git a/MediaBrowser.Providers/Plugins/AudioDb/jellyfin-plugin-tadb.svg b/MediaBrowser.Providers/Plugins/AudioDb/jellyfin-plugin-tadb.svg new file mode 100644 index 0000000000..94fa55cc9c --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/jellyfin-plugin-tadb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/MediaBrowser.Providers/Plugins/ListenBrainz/ListenBrainzPlugin.cs b/MediaBrowser.Providers/Plugins/ListenBrainz/ListenBrainzPlugin.cs index efac93f94e..1681f0334d 100644 --- a/MediaBrowser.Providers/Plugins/ListenBrainz/ListenBrainzPlugin.cs +++ b/MediaBrowser.Providers/Plugins/ListenBrainz/ListenBrainzPlugin.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Plugins.ListenBrainz.Configuration; @@ -11,7 +12,7 @@ namespace MediaBrowser.Providers.Plugins.ListenBrainz; /// /// ListenBrainz plugin instance. /// -public class ListenBrainzPlugin : BasePlugin, IHasWebPages +public class ListenBrainzPlugin : BasePlugin, IHasWebPages, IHasEmbeddedImage { /// /// Initializes a new instance of the class. @@ -41,6 +42,9 @@ public class ListenBrainzPlugin : BasePlugin, IHasWebPages /// public override string ConfigurationFileName => "Jellyfin.Plugin.ListenBrainz.xml"; + /// + public string ImageResourceName => GetType().Namespace + ".Configuration.ListenBrainz_logo.svg"; + /// public IEnumerable GetPages() { @@ -51,11 +55,6 @@ public class ListenBrainzPlugin : BasePlugin, IHasWebPages EmbeddedResourcePath = resourcePrefix + "config.html" }; yield return new PluginPageInfo - { - Name = Name + "Logo", - EmbeddedResourcePath = resourcePrefix + "ListenBrainz_logo.svg" - }; - yield return new PluginPageInfo { Name = Name + "Notice", EmbeddedResourcePath = resourcePrefix + "NOTICE.md" diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs index 69225d0b95..f448e6b20c 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs @@ -6,6 +6,7 @@ using System.Threading; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration; @@ -17,7 +18,7 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz; /// /// Plugin instance. /// -public class Plugin : BasePlugin, IHasWebPages, IDisposable +public class Plugin : BasePlugin, IHasWebPages, IHasEmbeddedImage, IDisposable { private readonly ILogger _logger; private readonly Lock _queryLock = new(); @@ -66,6 +67,9 @@ public class Plugin : BasePlugin, IHasWebPages, IDisposable // TODO remove when plugin removed from server. public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml"; + /// + public string ImageResourceName => GetType().Namespace + ".jellyfin-plugin-musicbrainz.svg"; + /// /// Gets the current MusicBrainz query client. /// diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/jellyfin-plugin-musicbrainz.svg b/MediaBrowser.Providers/Plugins/MusicBrainz/jellyfin-plugin-musicbrainz.svg new file mode 100644 index 0000000000..8074d59d7b --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/jellyfin-plugin-musicbrainz.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs index a0fba48f05..9066ff8523 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs @@ -5,12 +5,13 @@ using System; using System.Collections.Generic; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.Plugins.Omdb { - public class Plugin : BasePlugin, IHasWebPages + public class Plugin : BasePlugin, IHasWebPages, IHasEmbeddedImage { public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) @@ -29,6 +30,8 @@ namespace MediaBrowser.Providers.Plugins.Omdb // TODO remove when plugin removed from server. public override string ConfigurationFileName => "Jellyfin.Plugin.Omdb.xml"; + public string ImageResourceName => GetType().Namespace + ".jellyfin-plugin-omdb.png"; + public IEnumerable GetPages() { yield return new PluginPageInfo diff --git a/MediaBrowser.Providers/Plugins/Omdb/jellyfin-plugin-omdb.png b/MediaBrowser.Providers/Plugins/Omdb/jellyfin-plugin-omdb.png new file mode 100644 index 0000000000..a4bb7f7d5f Binary files /dev/null and b/MediaBrowser.Providers/Plugins/Omdb/jellyfin-plugin-omdb.png differ diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs index 28f8c0c617..167959967d 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Plugins.StudioImages.Configuration; @@ -13,7 +14,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages /// /// Artwork Plugin class. /// - public class Plugin : BasePlugin, IHasWebPages + public class Plugin : BasePlugin, IHasWebPages, IHasEmbeddedImage { /// /// Artwork repository URL. @@ -50,6 +51,9 @@ namespace MediaBrowser.Providers.Plugins.StudioImages /// public override string ConfigurationFileName => "Jellyfin.Plugin.StudioImages.xml"; + /// + public string ImageResourceName => GetType().Namespace + ".jellyfin-plugin-studioimages.svg"; + /// public IEnumerable GetPages() { diff --git a/MediaBrowser.Providers/Plugins/StudioImages/jellyfin-plugin-studioimages.svg b/MediaBrowser.Providers/Plugins/StudioImages/jellyfin-plugin-studioimages.svg new file mode 100644 index 0000000000..e9da69c571 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/StudioImages/jellyfin-plugin-studioimages.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs index 4adde8366a..04f1fd04a3 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Plugin.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; @@ -12,7 +13,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// /// Plugin class for the TMDb library. /// - public class Plugin : BasePlugin, IHasWebPages + public class Plugin : BasePlugin, IHasWebPages, IHasEmbeddedImage { /// /// Initializes a new instance of the class. @@ -44,6 +45,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// public override string ConfigurationFileName => "Jellyfin.Plugin.Tmdb.xml"; + /// + public string ImageResourceName => GetType().Namespace + ".jellyfin-plugin-tmdb.svg"; + /// /// Return the plugin configuration page. /// diff --git a/MediaBrowser.Providers/Plugins/Tmdb/jellyfin-plugin-tmdb.svg b/MediaBrowser.Providers/Plugins/Tmdb/jellyfin-plugin-tmdb.svg new file mode 100644 index 0000000000..fbebb32b60 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/jellyfin-plugin-tmdb.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/tests/Jellyfin.Providers.Tests/ExternalId/ComicVineExternalUrlProviderTests.cs b/tests/Jellyfin.Providers.Tests/ExternalId/ComicVineExternalUrlProviderTests.cs index 99604e0933..aaa500b762 100644 --- a/tests/Jellyfin.Providers.Tests/ExternalId/ComicVineExternalUrlProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/ExternalId/ComicVineExternalUrlProviderTests.cs @@ -1,7 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Entities; -using MediaBrowser.Providers.Plugins.ComicVine; +using MediaBrowser.Providers.Books.ComicVine; using Xunit; namespace Jellyfin.Providers.Tests.ExternalId diff --git a/tests/Jellyfin.Providers.Tests/ExternalId/GoogleBooksExternalUrlProviderTests.cs b/tests/Jellyfin.Providers.Tests/ExternalId/GoogleBooksExternalUrlProviderTests.cs index eec64ac53f..b9ce895dbc 100644 --- a/tests/Jellyfin.Providers.Tests/ExternalId/GoogleBooksExternalUrlProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/ExternalId/GoogleBooksExternalUrlProviderTests.cs @@ -1,7 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Entities; -using MediaBrowser.Providers.Plugins.GoogleBooks; +using MediaBrowser.Providers.Books.GoogleBooks; using Xunit; namespace Jellyfin.Providers.Tests.ExternalId