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/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 8b727a8cac..181cda3288 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -47,22 +47,65 @@ + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/ComicVine/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/ComicVine/Configuration/PluginConfiguration.cs new file mode 100644 index 0000000000..d35639d7e8 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/ComicVine/Configuration/PluginConfiguration.cs @@ -0,0 +1,10 @@ +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Providers.Plugins.ComicVine; + +/// +/// Plugin configuration for the Comic Vine provider. +/// +public class PluginConfiguration : BasePluginConfiguration +{ +} diff --git a/MediaBrowser.Providers/Plugins/ComicVine/Plugin.cs b/MediaBrowser.Providers/Plugins/ComicVine/Plugin.cs new file mode 100644 index 0000000000..101fa103b0 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/ComicVine/Plugin.cs @@ -0,0 +1,45 @@ +using System; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Providers.Plugins.ComicVine; + +/// +/// ComicVine plugin instance. +/// +public class Plugin : BasePlugin, IHasEmbeddedImage +{ + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + /// + /// Gets the current plugin instance. + /// + public static Plugin? Instance { get; private set; } + + /// + public override Guid Id => new("3ade6fd1-c76c-4560-b2df-f6bca4c2332f"); + + /// + public override string Name => "Comic Vine"; + + /// + public override string Description => "Get external links for comic books from Comic Vine."; + + /// + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.ComicVine.xml"; + + /// + public string ImageResourceName => GetType().Namespace + ".jellyfin-plugin-comicvine.svg"; +} diff --git a/MediaBrowser.Providers/Plugins/ComicVine/jellyfin-plugin-comicvine.svg b/MediaBrowser.Providers/Plugins/ComicVine/jellyfin-plugin-comicvine.svg new file mode 100644 index 0000000000..81bde53a51 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/ComicVine/jellyfin-plugin-comicvine.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/MediaBrowser.Providers/Plugins/GoogleBooks/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/GoogleBooks/Configuration/PluginConfiguration.cs new file mode 100644 index 0000000000..c32f764810 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/GoogleBooks/Configuration/PluginConfiguration.cs @@ -0,0 +1,10 @@ +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Providers.Plugins.GoogleBooks; + +/// +/// Plugin configuration for the Google Books provider. +/// +public class PluginConfiguration : BasePluginConfiguration +{ +} diff --git a/MediaBrowser.Providers/Plugins/GoogleBooks/Plugin.cs b/MediaBrowser.Providers/Plugins/GoogleBooks/Plugin.cs new file mode 100644 index 0000000000..645e27f5f9 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/GoogleBooks/Plugin.cs @@ -0,0 +1,45 @@ +using System; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Providers.Plugins.GoogleBooks; + +/// +/// Google Books plugin instance. +/// +public class Plugin : BasePlugin, IHasEmbeddedImage +{ + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + /// + /// Gets the current plugin instance. + /// + public static Plugin? Instance { get; private set; } + + /// + public override Guid Id => new("9f97232d-e7f4-432d-92d3-c709ce47e30b"); + + /// + public override string Name => "Google Books"; + + /// + public override string Description => "Get external links for books from Google Books."; + + /// + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.GoogleBooks.xml"; + + /// + public string ImageResourceName => GetType().Namespace + ".jellyfin-plugin-googlebooks.svg"; +} diff --git a/MediaBrowser.Providers/Plugins/GoogleBooks/jellyfin-plugin-googlebooks.svg b/MediaBrowser.Providers/Plugins/GoogleBooks/jellyfin-plugin-googlebooks.svg new file mode 100644 index 0000000000..f93ca4a18e --- /dev/null +++ b/MediaBrowser.Providers/Plugins/GoogleBooks/jellyfin-plugin-googlebooks.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + +