diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index dea9b6682a..ff7ee085f8 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -17,7 +17,6 @@ namespace Emby.Server.Implementations { { HostWebClientKey, bool.TrueString }, { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, - { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, { PlaylistsAllowDuplicatesKey, bool.TrueString } diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 0b9f805389..e7e72c686b 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -36,11 +36,6 @@ namespace Emby.Server.Implementations /// string RestartArgs { get; } - /// - /// Gets the value of the --plugin-manifest-url command line option. - /// - string PluginManifestUrl { get; } - /// /// Gets the value of the --published-server-url command line option. /// diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 80326fddf2..b1bab8cdd2 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -19,6 +19,7 @@ using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; using Microsoft.Extensions.Configuration; @@ -31,11 +32,6 @@ namespace Emby.Server.Implementations.Updates /// public class InstallationManager : IInstallationManager { - /// - /// The key for a setting that specifies a URL for the plugin repository JSON manifest. - /// - public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl"; - /// /// The logger. /// @@ -122,16 +118,14 @@ namespace Emby.Server.Implementations.Updates public IEnumerable CompletedInstallations => _completedInstallationsInternal; /// - public async Task> GetAvailablePackages(CancellationToken cancellationToken = default) + public async Task> GetPackages(string manifest, CancellationToken cancellationToken = default) { - var manifestUrl = _appConfig.GetValue(PluginManifestUrlKey); - try { using (var response = await _httpClient.SendAsync( new HttpRequestOptions { - Url = manifestUrl, + Url = manifest, CancellationToken = cancellationToken, CacheMode = CacheMode.Unconditional, CacheLength = TimeSpan.FromMinutes(3) @@ -145,23 +139,33 @@ namespace Emby.Server.Implementations.Updates } catch (SerializationException ex) { - const string LogTemplate = - "Failed to deserialize the plugin manifest retrieved from {PluginManifestUrl}. If you " + - "have specified a custom plugin repository manifest URL with --plugin-manifest-url or " + - PluginManifestUrlKey + ", please ensure that it is correct."; - _logger.LogError(ex, LogTemplate, manifestUrl); - throw; + _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest); + return Enumerable.Empty(); } } } catch (UriFormatException ex) { - const string LogTemplate = - "The URL configured for the plugin repository manifest URL is not valid: {PluginManifestUrl}. " + - "Please check the URL configured by --plugin-manifest-url or " + PluginManifestUrlKey; - _logger.LogError(ex, LogTemplate, manifestUrl); - throw; + _logger.LogError(ex, "The URL configured for the plugin repository manifest URL is not valid: {Manifest}", manifest); + return Enumerable.Empty(); } + catch (HttpException ex) + { + _logger.LogError(ex, "An error occurred while accessing the plugin manifest: {Manifest}", manifest); + return Enumerable.Empty(); + } + } + + /// + public async Task> GetAvailablePackages(CancellationToken cancellationToken = default) + { + var result = new List(); + foreach (RepositoryInfo repository in _config.Configuration.PluginRepositories) + { + result.AddRange(await GetPackages(repository.Url, cancellationToken).ConfigureAwait(true)); + } + + return result.AsReadOnly(); } /// diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index c98263223f..d633c554de 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -20,6 +20,7 @@ namespace Jellyfin.Server.Migrations typeof(Routines.CreateUserLoggingConfigFile), typeof(Routines.MigrateActivityLogDb), typeof(Routines.RemoveDuplicateExtras), + typeof(Routines.AddDefaultPluginRepository), typeof(Routines.MigrateUserDb) }; diff --git a/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs new file mode 100644 index 0000000000..a9d5ad16a1 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs @@ -0,0 +1,42 @@ +using System; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Updates; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// + /// Migration to initialize system configuration with the default plugin repository. + /// + public class AddDefaultPluginRepository : IMigrationRoutine + { + private readonly IServerConfigurationManager _serverConfigurationManager; + + private readonly RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo + { + Name = "Jellyfin Stable", + Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" + }; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public AddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// + public Guid Id => Guid.Parse("EB58EBEE-9514-4B9B-8225-12E1A40020DF"); + + /// + public string Name => "AddDefaultPluginRepository"; + + /// + public void Perform() + { + _serverConfigurationManager.Configuration.PluginRepositories.Add(_defaultRepositoryInfo); + _serverConfigurationManager.SaveConfiguration(); + } + } +} diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index cc250b06e2..a26114e778 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -79,10 +79,6 @@ namespace Jellyfin.Server [Option("restartargs", Required = false, HelpText = "Arguments for restart script.")] public string? RestartArgs { get; set; } - /// - [Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")] - public string? PluginManifestUrl { get; set; } - /// [Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] public Uri? PublishedServerUrl { get; set; } @@ -95,11 +91,6 @@ namespace Jellyfin.Server { var config = new Dictionary(); - if (PluginManifestUrl != null) - { - config.Add(InstallationManager.PluginManifestUrlKey, PluginManifestUrl); - } - if (NoWebClient) { config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString); diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index a63d06ad5f..a84556fcc4 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -13,6 +13,18 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { + [Route("/Repositories", "GET", Summary = "Gets all package repositories")] + [Authenticated] + public class GetRepositories : IReturnVoid + { + } + + [Route("/Repositories", "POST", Summary = "Sets the enabled and existing package repositories")] + [Authenticated] + public class SetRepositories : List, IReturnVoid + { + } + /// /// Class GetPackage. /// @@ -94,6 +106,7 @@ namespace MediaBrowser.Api public class PackageService : BaseApiService { private readonly IInstallationManager _installationManager; + private readonly IServerConfigurationManager _serverConfigurationManager; public PackageService( ILogger logger, @@ -103,6 +116,19 @@ namespace MediaBrowser.Api : base(logger, serverConfigurationManager, httpResultFactory) { _installationManager = installationManager; + _serverConfigurationManager = serverConfigurationManager; + } + + public object Get(GetRepositories request) + { + var result = _serverConfigurationManager.Configuration.PluginRepositories; + return ToOptimizedResult(result); + } + + public void Post(SetRepositories request) + { + _serverConfigurationManager.Configuration.PluginRepositories = request; + _serverConfigurationManager.SaveConfiguration(); } /// diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 965ffe0ec2..a5025aee96 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -40,6 +40,14 @@ namespace MediaBrowser.Common.Updates /// IEnumerable CompletedInstallations { get; } + /// + /// Parses a plugin manifest at the supplied URL. + /// + /// The URL to query. + /// The cancellation token. + /// Task{IReadOnlyList{PackageInfo}}. + Task> GetPackages(string manifest, CancellationToken cancellationToken = default); + /// /// Gets all available packages. /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 7428876204..b87c8fbab2 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -2,7 +2,9 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Updates; namespace MediaBrowser.Model.Configuration { @@ -229,6 +231,8 @@ namespace MediaBrowser.Model.Configuration public string[] CodecsUsed { get; set; } + public List PluginRepositories { get; set; } + public bool IgnoreVirtualInterfaces { get; set; } public bool EnableExternalContentInSuggestions { get; set; } diff --git a/MediaBrowser.Model/Updates/RepositoryInfo.cs b/MediaBrowser.Model/Updates/RepositoryInfo.cs new file mode 100644 index 0000000000..905327c368 --- /dev/null +++ b/MediaBrowser.Model/Updates/RepositoryInfo.cs @@ -0,0 +1,23 @@ +#nullable disable +using System; + +namespace MediaBrowser.Model.Updates +{ + /// + /// Class RepositoryInfo. + /// + public class RepositoryInfo + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the URL. + /// + /// The URL. + public string Url { get; set; } + } +}