From 26af5ea45a8ea02b7a3f20b0ebc31ef19f850dea Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 25 Feb 2020 16:51:36 +0100 Subject: [PATCH 01/29] Do not set a static content root if the jellyfin-web directory does not exist or is empty --- .../AppBase/BaseApplicationPaths.cs | 2 +- .../ApplicationHost.cs | 2 +- Jellyfin.Server/Program.cs | 20 ++++++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index c3cdcc2222..be2d198efc 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.AppBase /// /// Gets the path to the web UI resources folder. /// - /// The web UI resources path. + /// The web UI resources path, or null if the server is not hosting any web content. public string WebPath { get; } /// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8ea188724a..a5b88f64f2 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -240,7 +240,7 @@ namespace Emby.Server.Implementations public int HttpsPort { get; private set; } /// - /// Gets the content root for the webhost. + /// Gets the content root for the webhost. If the webhost is not serving static web content, this will be null. /// public string ContentRoot { get; private set; } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 1dd598236b..2ce7379fba 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -222,7 +222,7 @@ namespace Jellyfin.Server private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection) { - return new WebHostBuilder() + var webhostBuilder = new WebHostBuilder() .UseKestrel(options => { var addresses = appHost.ServerConfigurationManager @@ -260,13 +260,20 @@ namespace Jellyfin.Server } } }) - .UseContentRoot(appHost.ContentRoot) .ConfigureServices(services => { // Merge the external ServiceCollection into ASP.NET DI services.TryAdd(serviceCollection); }) .UseStartup(); + + // Set the root directory for static content, if one exists + if (!string.IsNullOrEmpty(appHost.ContentRoot)) + { + webhostBuilder.UseContentRoot(appHost.ContentRoot); + } + + return webhostBuilder; } /// @@ -383,7 +390,7 @@ namespace Jellyfin.Server // webDir // IF --webdir // ELSE IF $JELLYFIN_WEB_DIR - // ELSE use /jellyfin-web + // ELSE /jellyfin-web var webDir = options.WebDir; if (string.IsNullOrEmpty(webDir)) @@ -397,6 +404,13 @@ namespace Jellyfin.Server } } + // Reset webDir if the directory does not exist, or is empty + if (!Directory.Exists(webDir) || !Directory.GetFiles(webDir).Any()) + { + _logger.LogInformation("Server will not host static content because the web content directory does not exist or is empty: {ContentRoot}", webDir); + webDir = null; + } + // logDir // IF --logdir // ELSE IF $JELLYFIN_LOG_DIR From 1b5999a1bc11ee386131e4bbce561896c0e1bced Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 25 Feb 2020 17:01:57 +0100 Subject: [PATCH 02/29] Open the Swagger API page on server start if not hosting the static web content --- Emby.Server.Implementations/ApplicationHost.cs | 3 +++ Emby.Server.Implementations/Browser/BrowserLauncher.cs | 10 ++++++++++ .../EntryPoints/StartupWizard.cs | 6 +++++- MediaBrowser.Controller/IServerApplicationHost.cs | 5 +++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a5b88f64f2..789b8724c3 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -244,6 +244,9 @@ namespace Emby.Server.Implementations /// public string ContentRoot { get; private set; } + /// + public bool IsHostingContent => ContentRoot != null; + /// /// Gets the server configuration manager. /// diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index f5da0d0183..b17c2b2700 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -29,6 +29,16 @@ namespace Emby.Server.Implementations.Browser OpenDashboardPage("index.html", appHost); } + /// + /// Opens the swagger API page. + /// + /// The app host. + public static void OpenSwaggerPage(IServerApplicationHost appHost) + { + var url = appHost.GetLocalApiUrl("localhost") + "/swagger/index.html"; + OpenUrl(appHost, url); + } + /// /// Opens the URL. /// diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 5f2d629fea..6b7e2805bd 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -36,7 +36,11 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - if (!_config.Configuration.IsStartupWizardCompleted) + if (!_appHost.IsHostingContent) + { + BrowserLauncher.OpenSwaggerPage(_appHost); + } + else if (!_config.Configuration.IsStartupWizardCompleted) { BrowserLauncher.OpenWebApp(_appHost); } diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 25f0905eb8..134ed110ba 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -16,6 +16,11 @@ namespace MediaBrowser.Controller { event EventHandler HasUpdateAvailableChanged; + /// + /// Gets a value indicating whether the server is hosting the static web content from jellyfin-web. + /// + bool IsHostingContent { get; } + /// /// Gets the system info. /// From f3be93a4de86177d87a3be919b7aaa6e394b819d Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 25 Feb 2020 17:02:51 +0100 Subject: [PATCH 03/29] Use the swagger API page as the default redirect path if not hosting the jellyfin-web content --- Emby.Server.Implementations/ConfigurationOptions.cs | 8 +++++++- Jellyfin.Server/Program.cs | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 2ea7ff6e91..b733bd2d57 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -3,9 +3,15 @@ using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations { + /// + /// Static class containing the default configuration options for the web server. + /// public static class ConfigurationOptions { - public static Dictionary Configuration => new Dictionary + /// + /// Gets the default configuration options. + /// + public static Dictionary DefaultConfiguration => new Dictionary { { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }, diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 2ce7379fba..8c133576ca 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -468,9 +468,16 @@ namespace Jellyfin.Server await resource.CopyToAsync(dst).ConfigureAwait(false); } + // Use the swagger API page as the default redirect path if not hosting the jellyfin-web content + var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; + if (string.IsNullOrEmpty(appPaths.WebPath)) + { + inMemoryDefaultConfig["HttpListenerHost:DefaultRedirectPath"] = "swagger/index.html"; + } + return new ConfigurationBuilder() .SetBasePath(appPaths.ConfigurationDirectoryPath) - .AddInMemoryCollection(ConfigurationOptions.Configuration) + .AddInMemoryCollection(inMemoryDefaultConfig) .AddJsonFile("logging.json", false, true) .AddEnvironmentVariables("JELLYFIN_") .Build(); From 192ec57b608a2cd1f8b337fb416b84abccd7a2a0 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 25 Feb 2020 17:22:21 +0100 Subject: [PATCH 04/29] Remove log message executed before logger is initialized --- Emby.Server.Implementations/ConfigurationOptions.cs | 2 +- Jellyfin.Server/Program.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index b733bd2d57..456484813d 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations public static class ConfigurationOptions { /// - /// Gets the default configuration options. + /// Gets a new copy of the default configuration options. /// public static Dictionary DefaultConfiguration => new Dictionary { diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 8c133576ca..c4e3cd7268 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -392,7 +392,6 @@ namespace Jellyfin.Server // ELSE IF $JELLYFIN_WEB_DIR // ELSE /jellyfin-web var webDir = options.WebDir; - if (string.IsNullOrEmpty(webDir)) { webDir = Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR"); @@ -407,7 +406,6 @@ namespace Jellyfin.Server // Reset webDir if the directory does not exist, or is empty if (!Directory.Exists(webDir) || !Directory.GetFiles(webDir).Any()) { - _logger.LogInformation("Server will not host static content because the web content directory does not exist or is empty: {ContentRoot}", webDir); webDir = null; } From d95ccbacac7df20ab445e73dd9405ec1391c7a7a Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Wed, 26 Feb 2020 16:11:09 +0100 Subject: [PATCH 05/29] Use IsHostingContent instead of explicitly checking ContentRoot --- Jellyfin.Server/Program.cs | 2 +- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index c4e3cd7268..4048a0d031 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -268,7 +268,7 @@ namespace Jellyfin.Server .UseStartup(); // Set the root directory for static content, if one exists - if (!string.IsNullOrEmpty(appHost.ContentRoot)) + if (appHost.IsHostingContent) { webhostBuilder.UseContentRoot(appHost.ContentRoot); } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 5280d455c4..43a84d3815 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -149,9 +149,9 @@ namespace MediaBrowser.Model.Configuration public bool EnableDashboardResponseCaching { get; set; } /// - /// Allows the dashboard to be served from a custom path. + /// Gets or sets a custom path to serve the dashboard from. /// - /// The dashboard source path. + /// The dashboard source path, or null if the default path should be used. public string DashboardSourcePath { get; set; } /// From 3043b7323b1a12e888ba8c4eb71f904724f43e3a Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 28 Feb 2020 17:57:38 +0100 Subject: [PATCH 06/29] Use constants for settings keys --- Emby.Server.Implementations/ConfigurationOptions.cs | 6 ++++-- .../HttpServer/HttpListenerHost.cs | 8 +++++++- Jellyfin.Server/Program.cs | 3 ++- MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs | 7 ++++++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 456484813d..cc0d314edc 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using Emby.Server.Implementations.HttpServer; +using MediaBrowser.Providers.Music; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; namespace Emby.Server.Implementations @@ -13,8 +15,8 @@ namespace Emby.Server.Implementations /// public static Dictionary DefaultConfiguration => new Dictionary { - { "HttpListenerHost:DefaultRedirectPath", "web/index.html" }, - { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }, + { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, + { MusicBrainzAlbumProvider.BaseUrlKey, "https://www.musicbrainz.org" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" } }; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 85602a67f0..7f16c68343 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -30,6 +30,12 @@ namespace Emby.Server.Implementations.HttpServer { public class HttpListenerHost : IHttpServer, IDisposable { + /// + /// The settings key for a setting that specifies the default redirect path + /// to use for requests where the URL base prefix is invalid or missing. + /// + public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; + private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; @@ -58,7 +64,7 @@ namespace Emby.Server.Implementations.HttpServer _appHost = applicationHost; _logger = logger; _config = config; - _defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; + _defaultRedirectPath = configuration[DefaultRedirectKey]; _baseUrlPrefix = _config.Configuration.BaseUrl; _networkManager = networkManager; _jsonSerializer = jsonSerializer; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 4048a0d031..7e03b0c9ca 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using CommandLine; using Emby.Drawing; using Emby.Server.Implementations; +using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Networking; using Jellyfin.Drawing.Skia; @@ -470,7 +471,7 @@ namespace Jellyfin.Server var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; if (string.IsNullOrEmpty(appPaths.WebPath)) { - inMemoryDefaultConfig["HttpListenerHost:DefaultRedirectPath"] = "swagger/index.html"; + inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "swagger/index.html"; } return new ConfigurationBuilder() diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index 8e71b625ee..ed7688e5a9 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -22,6 +22,11 @@ namespace MediaBrowser.Providers.Music { public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder { + /// + /// The settings key for a setting that specifies the base URL to use for sending requests to MusicBrainz. + /// + public const string BaseUrlKey = "MusicBrainz:BaseUrl"; + /// /// The Jellyfin user-agent is unrestricted but source IP must not exceed /// one request per second, therefore we rate limit to avoid throttling. @@ -57,7 +62,7 @@ namespace MediaBrowser.Providers.Music _appHost = appHost; _logger = logger; - _musicBrainzBaseUrl = configuration["MusicBrainz:BaseUrl"]; + _musicBrainzBaseUrl = configuration[BaseUrlKey]; // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit _stopWatchMusicBrainz.Start(); From 3f4b9e9a81278c6f5c817036a7ae52b6f70557e4 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 28 Feb 2020 20:40:45 +0100 Subject: [PATCH 07/29] Add new 'nowebcontent' configuration flag --- .../ConfigurationOptions.cs | 1 + .../HttpServer/HttpListenerHost.cs | 2 +- .../Extensions/ConfigurationExtensions.cs | 31 +++++++++++++++++++ .../Music/MusicBrainzAlbumProvider.cs | 2 +- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index cc0d314edc..08a493cb79 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -15,6 +15,7 @@ namespace Emby.Server.Implementations /// public static Dictionary DefaultConfiguration => new Dictionary { + { NoWebContentKey, bool.FalseString }, { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, { MusicBrainzAlbumProvider.BaseUrlKey, "https://www.musicbrainz.org" }, { FfmpegProbeSizeKey, "1G" }, diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 7f16c68343..546a59517d 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.HttpServer public class HttpListenerHost : IHttpServer, IDisposable { /// - /// The settings key for a setting that specifies the default redirect path + /// The key for a setting that specifies the default redirect path /// to use for requests where the URL base prefix is invalid or missing. /// public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 76c9b4b26c..9dbc1a243f 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Extensions.Configuration; namespace MediaBrowser.Controller.Extensions @@ -7,6 +8,11 @@ namespace MediaBrowser.Controller.Extensions /// public static class ConfigurationExtensions { + /// + /// The key for a setting that indicates whether the application should host static web content. + /// + public const string NoWebContentKey = "nowebcontent"; + /// /// The key for the FFmpeg probe size option. /// @@ -17,6 +23,16 @@ namespace MediaBrowser.Controller.Extensions /// public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration"; + /// + /// Retrieves a config value indicating whether the application should not host + /// static web content from the . + /// + /// The configuration to retrieve the value from. + /// The parsed config value. + /// The config value is not a valid bool string. See . + public static bool IsNoWebContent(this IConfiguration configuration) + => configuration.ParseBoolean(NoWebContentKey); + /// /// Retrieves the FFmpeg probe size from the . /// @@ -32,5 +48,20 @@ namespace MediaBrowser.Controller.Extensions /// The FFmpeg analyse duration option. public static string GetFFmpegAnalyzeDuration(this IConfiguration configuration) => configuration[FfmpegAnalyzeDurationKey]; + + /// + /// Convert the specified configuration string value its equivalent. + /// + /// The configuration to retrieve and parse the setting from. + /// The key to use to retrieve the string value from the configuration. + /// The parsed boolean value. + /// The config value is not a valid bool string. See . + public static bool ParseBoolean(this IConfiguration configuration, string key) + { + string configValue = configuration[key]; + return bool.TryParse(configValue, out bool result) ? + result : + throw new FormatException($"Invalid value for configuration option '{key}' (expected a boolean): {configValue}"); + } } } diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index ed7688e5a9..d217c6ad20 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder { /// - /// The settings key for a setting that specifies the base URL to use for sending requests to MusicBrainz. + /// The key for a setting that specifies the base URL to use for sending requests to MusicBrainz. /// public const string BaseUrlKey = "MusicBrainz:BaseUrl"; From 29bad073ebeb10813c6468b6159ce3bd06398134 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 28 Feb 2020 20:49:04 +0100 Subject: [PATCH 08/29] Use config setting to decide if web content should be hosted Also fail server startup if web content is expected but missing --- .../ApplicationHost.cs | 5 +--- .../EntryPoints/StartupWizard.cs | 4 ++- Jellyfin.Server/Program.cs | 25 +++++++++++-------- .../IServerApplicationHost.cs | 5 ---- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 789b8724c3..8ea188724a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -240,13 +240,10 @@ namespace Emby.Server.Implementations public int HttpsPort { get; private set; } /// - /// Gets the content root for the webhost. If the webhost is not serving static web content, this will be null. + /// Gets the content root for the webhost. /// public string ContentRoot { get; private set; } - /// - public bool IsHostingContent => ContentRoot != null; - /// /// Gets the server configuration manager. /// diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 6b7e2805bd..7c2b3cd5f2 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -2,7 +2,9 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Browser; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Plugins; +using Microsoft.Extensions.Configuration; namespace Emby.Server.Implementations.EntryPoints { @@ -36,7 +38,7 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - if (!_appHost.IsHostingContent) + if (_appHost.Resolve().IsNoWebContent()) { BrowserLauncher.OpenSwaggerPage(_appHost); } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 7e03b0c9ca..843eb9ea94 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -19,7 +19,7 @@ using Emby.Server.Implementations.Networking; using Jellyfin.Drawing.Skia; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Model.Globalization; +using MediaBrowser.Controller.Extensions; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -177,7 +177,7 @@ namespace Jellyfin.Server ServiceCollection serviceCollection = new ServiceCollection(); await appHost.InitAsync(serviceCollection).ConfigureAwait(false); - var host = CreateWebHostBuilder(appHost, serviceCollection).Build(); + var host = CreateWebHostBuilder(appHost, serviceCollection, appConfig).Build(); // A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection. appHost.ServiceProvider = host.Services; @@ -221,7 +221,7 @@ namespace Jellyfin.Server } } - private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection) + private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection, IConfiguration appConfig) { var webhostBuilder = new WebHostBuilder() .UseKestrel(options => @@ -268,9 +268,18 @@ namespace Jellyfin.Server }) .UseStartup(); - // Set the root directory for static content, if one exists - if (appHost.IsHostingContent) + if (!appConfig.IsNoWebContent()) { + // Fail startup if the web content does not exist + if (!Directory.Exists(appHost.ContentRoot) || !Directory.GetFiles(appHost.ContentRoot).Any()) + { + throw new InvalidOperationException( + "The server is expected to host web content, but the provided content directory is either " + + $"invalid or empty: {appHost.ContentRoot}. If you do not want to host web content with the " + + $"server, you may set the '{MediaBrowser.Controller.Extensions.ConfigurationExtensions.NoWebContentKey}' flag."); + } + + // Configure the web host to host the static web content webhostBuilder.UseContentRoot(appHost.ContentRoot); } @@ -404,12 +413,6 @@ namespace Jellyfin.Server } } - // Reset webDir if the directory does not exist, or is empty - if (!Directory.Exists(webDir) || !Directory.GetFiles(webDir).Any()) - { - webDir = null; - } - // logDir // IF --logdir // ELSE IF $JELLYFIN_LOG_DIR diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 134ed110ba..25f0905eb8 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -16,11 +16,6 @@ namespace MediaBrowser.Controller { event EventHandler HasUpdateAvailableChanged; - /// - /// Gets a value indicating whether the server is hosting the static web content from jellyfin-web. - /// - bool IsHostingContent { get; } - /// /// Gets the system info. /// From d437950ac30ee294ab275362abe711ae3c14ac32 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Wed, 11 Mar 2020 22:55:10 +0100 Subject: [PATCH 09/29] Parse config value correctly --- .../Extensions/ConfigurationExtensions.cs | 17 +---------------- .../MediaBrowser.Controller.csproj | 1 + 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 9dbc1a243f..1a9ac09ee9 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Controller.Extensions /// The parsed config value. /// The config value is not a valid bool string. See . public static bool IsNoWebContent(this IConfiguration configuration) - => configuration.ParseBoolean(NoWebContentKey); + => configuration.GetValue(NoWebContentKey); /// /// Retrieves the FFmpeg probe size from the . @@ -48,20 +48,5 @@ namespace MediaBrowser.Controller.Extensions /// The FFmpeg analyse duration option. public static string GetFFmpegAnalyzeDuration(this IConfiguration configuration) => configuration[FfmpegAnalyzeDurationKey]; - - /// - /// Convert the specified configuration string value its equivalent. - /// - /// The configuration to retrieve and parse the setting from. - /// The key to use to retrieve the string value from the configuration. - /// The parsed boolean value. - /// The config value is not a valid bool string. See . - public static bool ParseBoolean(this IConfiguration configuration, string key) - { - string configValue = configuration[key]; - return bool.TryParse(configValue, out bool result) ? - result : - throw new FormatException($"Invalid value for configuration option '{key}' (expected a boolean): {configValue}"); - } } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 88e9055e84..bcca9e4a18 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -9,6 +9,7 @@ + From 547f248a620c2de61d72703590f9c5c4720ba6e2 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 12:59:34 +0100 Subject: [PATCH 10/29] Update XML documentation for WebPath --- .../AppBase/BaseApplicationPaths.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index be2d198efc..8bcd087452 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -1,11 +1,13 @@ using System; using System.IO; using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Extensions; +using Microsoft.Extensions.Configuration; namespace Emby.Server.Implementations.AppBase { /// - /// Provides a base class to hold common application paths used by both the Ui and Server. + /// Provides a base class to hold common application paths used by both the UI and Server. /// This can be subclassed to add application-specific paths. /// public abstract class BaseApplicationPaths : IApplicationPaths @@ -40,7 +42,10 @@ namespace Emby.Server.Implementations.AppBase /// /// Gets the path to the web UI resources folder. /// - /// The web UI resources path, or null if the server is not hosting any web content. + /// The web UI resources path. + /// + /// This value is not relevant if is true. + /// public string WebPath { get; } /// From 4102e3afd0a9df29faea598769db2212a00d64ce Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 13:00:14 +0100 Subject: [PATCH 11/29] Rename IsNoWebContent to NoWebContent --- Emby.Server.Implementations/EntryPoints/StartupWizard.cs | 2 +- Jellyfin.Server/Program.cs | 3 ++- MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 7c2b3cd5f2..bc6b8c956a 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -38,7 +38,7 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - if (_appHost.Resolve().IsNoWebContent()) + if (_appHost.Resolve().NoWebContent()) { BrowserLauncher.OpenSwaggerPage(_appHost); } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 9450fee705..6412db7518 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -287,7 +287,8 @@ namespace Jellyfin.Server }) .UseStartup(); - if (!startupConfig.IsNoWebContent()) + // Set up static content hosting unless it has been disabled via config + if (!startupConfig.NoWebContent()) { // Fail startup if the web content does not exist if (!Directory.Exists(appHost.ContentRoot) || !Directory.GetFiles(appHost.ContentRoot).Any()) diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 1a9ac09ee9..e802eeed2a 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Controller.Extensions /// The configuration to retrieve the value from. /// The parsed config value. /// The config value is not a valid bool string. See . - public static bool IsNoWebContent(this IConfiguration configuration) + public static bool NoWebContent(this IConfiguration configuration) => configuration.GetValue(NoWebContentKey); /// From a9c1ff91193d8f645225ee9f7001370fe304ae8d Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 13:00:34 +0100 Subject: [PATCH 12/29] Remove unnecessary method in BrowserLauncher --- .../Browser/BrowserLauncher.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index b17c2b2700..752650ae18 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -8,25 +8,14 @@ namespace Emby.Server.Implementations.Browser /// public static class BrowserLauncher { - /// - /// Opens the dashboard page. - /// - /// The page. - /// The app host. - private static void OpenDashboardPage(string page, IServerApplicationHost appHost) - { - var url = appHost.GetLocalApiUrl("localhost") + "/web/" + page; - - OpenUrl(appHost, url); - } - /// /// Opens the web client. /// /// The app host. public static void OpenWebApp(IServerApplicationHost appHost) { - OpenDashboardPage("index.html", appHost); + var url = appHost.GetLocalApiUrl("localhost") + "/web/index.html"; + OpenUrl(appHost, url); } /// From 420e3619fb062727a914a4f73bf67edb9e9aaf6c Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 13:01:05 +0100 Subject: [PATCH 13/29] Use startup configuration to set the default redirect path --- Jellyfin.Server/Program.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 6412db7518..168ee081ee 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -278,7 +278,7 @@ namespace Jellyfin.Server } } }) - .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(appPaths)) + .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(appPaths, startupConfig)) .UseSerilog() .ConfigureServices(services => { @@ -499,11 +499,11 @@ namespace Jellyfin.Server .Build(); } - private static IConfigurationBuilder ConfigureAppConfiguration(this IConfigurationBuilder config, IApplicationPaths appPaths) + private static IConfigurationBuilder ConfigureAppConfiguration(this IConfigurationBuilder config, IApplicationPaths appPaths, IConfiguration startupConfig = null) { // Use the swagger API page as the default redirect path if not hosting the jellyfin-web content var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; - if (string.IsNullOrEmpty(appPaths.WebPath)) + if (startupConfig != null && startupConfig.NoWebContent()) { inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "swagger/index.html"; } From a67e32f8ecd46dfe2193ab3c81160bc830cd9349 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 13:14:57 +0100 Subject: [PATCH 14/29] Clean up and document BrowserLauncher correctly --- .../Browser/BrowserLauncher.cs | 28 +++++++++---------- .../IServerApplicationHost.cs | 5 ++++ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index 752650ae18..96096e142a 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -1,21 +1,21 @@ using System; using MediaBrowser.Controller; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Browser { /// - /// Class BrowserLauncher. + /// Assists in opening application URLs in an external browser. /// public static class BrowserLauncher { /// - /// Opens the web client. + /// Opens the home page of the web client. /// /// The app host. public static void OpenWebApp(IServerApplicationHost appHost) { - var url = appHost.GetLocalApiUrl("localhost") + "/web/index.html"; - OpenUrl(appHost, url); + TryOpenUrl(appHost, "/web/index.html"); } /// @@ -24,27 +24,25 @@ namespace Emby.Server.Implementations.Browser /// The app host. public static void OpenSwaggerPage(IServerApplicationHost appHost) { - var url = appHost.GetLocalApiUrl("localhost") + "/swagger/index.html"; - OpenUrl(appHost, url); + TryOpenUrl(appHost, "/swagger/index.html"); } /// - /// Opens the URL. + /// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored. /// - /// The application host instance. + /// The application host. /// The URL. - private static void OpenUrl(IServerApplicationHost appHost, string url) + private static void TryOpenUrl(IServerApplicationHost appHost, string url) { try { - appHost.LaunchUrl(url); + string baseUrl = appHost.GetLocalApiUrl("localhost"); + appHost.LaunchUrl(baseUrl + url); } - catch (NotSupportedException) - { - - } - catch (Exception) + catch (Exception ex) { + var logger = appHost.Resolve(); + logger?.LogError(ex, "Failed to open browser window with URL {URL}", url); } } } diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 25f0905eb8..608ffc61c2 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -82,6 +82,11 @@ namespace MediaBrowser.Controller /// The local API URL. string GetLocalApiUrl(IPAddress address); + /// + /// Open a URL in an external browser window. + /// + /// The URL to open. + /// is false. void LaunchUrl(string url); void EnableLoopback(string appName); From 6a6293afc774001743985d615d651ca4a877bfd7 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 13:15:47 +0100 Subject: [PATCH 15/29] Make startup config nullable when configuring app configuration --- Jellyfin.Server/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 168ee081ee..c839867f12 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -499,7 +499,7 @@ namespace Jellyfin.Server .Build(); } - private static IConfigurationBuilder ConfigureAppConfiguration(this IConfigurationBuilder config, IApplicationPaths appPaths, IConfiguration startupConfig = null) + private static IConfigurationBuilder ConfigureAppConfiguration(this IConfigurationBuilder config, IApplicationPaths appPaths, IConfiguration? startupConfig = null) { // Use the swagger API page as the default redirect path if not hosting the jellyfin-web content var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; From 6fbdf0d6a20d8bf230cbf2ba002612b49fa45b76 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 15:23:50 +0100 Subject: [PATCH 16/29] Construct ApplicationHost with DI framework instead of manually --- .../ApplicationHost.cs | 32 ++++++++----------- .../HttpServer/HttpListenerHost.cs | 5 ++- Jellyfin.Server/Program.cs | 3 +- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 3d79cae1e5..7262268a97 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -613,7 +613,7 @@ namespace Emby.Server.Implementations DiscoverTypes(); - await RegisterResources(serviceCollection, startupConfig).ConfigureAwait(false); + await RegisterServices(serviceCollection, startupConfig).ConfigureAwait(false); ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; if (string.IsNullOrEmpty(ContentRoot)) @@ -650,9 +650,9 @@ namespace Emby.Server.Implementations } /// - /// Registers resources that classes will depend on + /// Registers services/resources with the service collection that will be available via DI. /// - protected async Task RegisterResources(IServiceCollection serviceCollection, IConfiguration startupConfig) + protected async Task RegisterServices(IServiceCollection serviceCollection, IConfiguration startupConfig) { serviceCollection.AddMemoryCache(); @@ -770,20 +770,8 @@ namespace Emby.Server.Implementations CertificateInfo = GetCertificateInfo(true); Certificate = GetCertificate(CertificateInfo); - HttpServer = new HttpListenerHost( - this, - LoggerFactory.CreateLogger(), - ServerConfigurationManager, - startupConfig, - NetworkManager, - JsonSerializer, - XmlSerializer, - CreateHttpListener()) - { - GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading") - }; - - serviceCollection.AddSingleton(HttpServer); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); serviceCollection.AddSingleton(ImageProcessor); @@ -891,6 +879,14 @@ namespace Emby.Server.Implementations ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; } + /// + /// Create services registered with the service container that need to be initialized at application startup. + /// + public void InitializeServices() + { + HttpServer = Resolve(); + } + public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) { // Distinct these to prevent users from reporting problems that aren't actually problems @@ -1196,8 +1192,6 @@ namespace Emby.Server.Implementations }); } - protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(LoggerFactory.CreateLogger()); - private CertificateInfo GetCertificateInfo(bool generateCertificate) { // Custom cert diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 546a59517d..7633eec4ea 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -18,6 +18,7 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Events; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; @@ -59,7 +60,8 @@ namespace Emby.Server.Implementations.HttpServer INetworkManager networkManager, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, - IHttpListener socketListener) + IHttpListener socketListener, + ILocalizationManager localizationManager) { _appHost = applicationHost; _logger = logger; @@ -76,6 +78,7 @@ namespace Emby.Server.Implementations.HttpServer Instance = this; ResponseFilters = Array.Empty>(); + GlobalResponse = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); } public event EventHandler> WebSocketConnected; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index c839867f12..cb886b8ce0 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -191,8 +191,9 @@ namespace Jellyfin.Server var webHost = CreateWebHostBuilder(appHost, serviceCollection, startupConfig, appPaths).Build(); - // A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection. + // Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection. appHost.ServiceProvider = webHost.Services; + appHost.InitializeServices(); appHost.FindParts(); Migrations.MigrationRunner.Run(appHost, _loggerFactory); From 7e3caec5836c0afeec58215ac33d625fa7f6fefa Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 15:29:39 +0100 Subject: [PATCH 17/29] Make PackageCreator.ModifyHtml() static and clean up XML documentation This eliminates the need to create a dummy instance to call this method --- .../Api/DashboardService.cs | 2 +- MediaBrowser.WebDashboard/Api/PackageCreator.cs | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 9e0aae7820..83cc6c6820 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -203,7 +203,7 @@ namespace MediaBrowser.WebDashboard.Api return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream)); } - return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator(DashboardUIPath).ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersionString, null)); + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => PackageCreator.ModifyHtml(false, stream, null, _appHost.ApplicationVersionString, null)); } throw new ResourceNotFoundException(); diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 133bf61e8c..e8eb0389f9 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -28,7 +28,8 @@ namespace MediaBrowser.WebDashboard.Api if (resourceStream != null && IsCoreHtml(virtualPath)) { - resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); + bool isMainIndexPage = string.Equals(virtualPath, "index.html", StringComparison.OrdinalIgnoreCase); + resourceStream = await ModifyHtml(isMainIndexPage, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); } return resourceStream; @@ -45,18 +46,19 @@ namespace MediaBrowser.WebDashboard.Api } /// - /// Modifies the HTML by adding common meta tags, css and js. + /// Modifies the source HTML stream by adding common meta tags, css and js. /// - /// Task{Stream}. - public async Task ModifyHtml( - string path, + /// + /// A task that represents the async operation to read and modify the input stream. + /// The task result contains a stream containing the modified HTML content. + /// + public static async Task ModifyHtml( + bool isMainIndexPage, Stream sourceStream, string mode, string appVersion, string localizationCulture) { - var isMainIndexPage = string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase); - string html; using (var reader = new StreamReader(sourceStream, Encoding.UTF8)) { From 0996ce28987027afe5943ca9787d4a5695c2ee0a Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 15:31:43 +0100 Subject: [PATCH 18/29] Add --nowebcontent command line flag Added without any functionality --- Jellyfin.Server/StartupOptions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 1fb1c5af8e..42c5fa86e3 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -15,6 +15,12 @@ namespace Jellyfin.Server [Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")] public string? DataDir { get; set; } + /// + /// Gets or sets a value indicating whether the server should not host static web content. + /// + [Option("nowebcontent", Required = false, HelpText = "Indicates that the web server should not host any static web content.")] + public bool NoWebContent { get; set; } + /// /// Gets or sets the path to the web directory. /// From 2a0153737131a404a8ccd386ad09ff4a1a697dfc Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 15:34:09 +0100 Subject: [PATCH 19/29] Merge command line options into framework configuration --- Jellyfin.Server/Program.cs | 23 +++++++++++++++-------- Jellyfin.Server/StartupOptions.cs | 13 +++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index cb886b8ce0..bfb2fc6b57 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -113,9 +113,10 @@ namespace Jellyfin.Server // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath); - // Create an instance of the application configuration to use for application startup await InitLoggingConfigFile(appPaths).ConfigureAwait(false); - IConfiguration startupConfig = CreateAppConfiguration(appPaths); + + // Create an instance of the application configuration to use for application startup + IConfiguration startupConfig = CreateAppConfiguration(options, appPaths); // Initialize logging framework InitializeLoggingFramework(startupConfig, appPaths); @@ -189,7 +190,7 @@ namespace Jellyfin.Server ServiceCollection serviceCollection = new ServiceCollection(); await appHost.InitAsync(serviceCollection, startupConfig).ConfigureAwait(false); - var webHost = CreateWebHostBuilder(appHost, serviceCollection, startupConfig, appPaths).Build(); + var webHost = CreateWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build(); // Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection. appHost.ServiceProvider = webHost.Services; @@ -238,6 +239,7 @@ namespace Jellyfin.Server private static IWebHostBuilder CreateWebHostBuilder( ApplicationHost appHost, IServiceCollection serviceCollection, + StartupOptions commandLineOpts, IConfiguration startupConfig, IApplicationPaths appPaths) { @@ -279,7 +281,7 @@ namespace Jellyfin.Server } } }) - .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(appPaths, startupConfig)) + .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(commandLineOpts, appPaths, startupConfig)) .UseSerilog() .ConfigureServices(services => { @@ -493,14 +495,18 @@ namespace Jellyfin.Server await resource.CopyToAsync(dst).ConfigureAwait(false); } - private static IConfiguration CreateAppConfiguration(IApplicationPaths appPaths) + private static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths) { return new ConfigurationBuilder() - .ConfigureAppConfiguration(appPaths) + .ConfigureAppConfiguration(commandLineOpts, appPaths) .Build(); } - private static IConfigurationBuilder ConfigureAppConfiguration(this IConfigurationBuilder config, IApplicationPaths appPaths, IConfiguration? startupConfig = null) + private static IConfigurationBuilder ConfigureAppConfiguration( + this IConfigurationBuilder config, + StartupOptions commandLineOpts, + IApplicationPaths appPaths, + IConfiguration? startupConfig = null) { // Use the swagger API page as the default redirect path if not hosting the jellyfin-web content var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; @@ -514,7 +520,8 @@ namespace Jellyfin.Server .AddInMemoryCollection(inMemoryDefaultConfig) .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true) .AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true) - .AddEnvironmentVariables("JELLYFIN_"); + .AddEnvironmentVariables("JELLYFIN_") + .AddInMemoryCollection(commandLineOpts.ConvertToConfig()); } /// diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 42c5fa86e3..0f15488f40 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -1,5 +1,8 @@ +using System.Collections.Generic; +using System.Globalization; using CommandLine; using Emby.Server.Implementations; +using MediaBrowser.Controller.Extensions; namespace Jellyfin.Server { @@ -72,5 +75,15 @@ namespace Jellyfin.Server /// [Option("restartargs", Required = false, HelpText = "Arguments for restart script.")] public string? RestartArgs { get; set; } + + /// + /// Gets the command line options as a dictionary that can be used in the .NET configuration system. + /// + /// The configuration dictionary. + public Dictionary ConvertToConfig() + => new Dictionary + { + { ConfigurationExtensions.NoWebContentKey, NoWebContent.ToString(CultureInfo.InvariantCulture) } + }; } } From 602112fba433fa8919133d7ff292b0cd5a912aa2 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 16:24:04 +0100 Subject: [PATCH 20/29] Return 404 for static web resources when the 'nowebcontent' flag is set --- .../Api/DashboardService.cs | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 83cc6c6820..11901c25fb 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -7,12 +7,14 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Plugins; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Services; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace MediaBrowser.WebDashboard.Api @@ -113,6 +115,7 @@ namespace MediaBrowser.WebDashboard.Api private readonly IFileSystem _fileSystem; private IResourceFileManager _resourceFileManager; + private readonly IConfiguration _appConfig; /// /// Initializes a new instance of the class. @@ -123,7 +126,8 @@ namespace MediaBrowser.WebDashboard.Api IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, ILogger logger, - IHttpResultFactory resultFactory) + IHttpResultFactory resultFactory, + IConfiguration appConfig) { _appHost = appHost; _serverConfigurationManager = serverConfigurationManager; @@ -131,16 +135,22 @@ namespace MediaBrowser.WebDashboard.Api _logger = logger; _resultFactory = resultFactory; _resourceFileManager = resourceFileManager; + _appConfig = appConfig; } /// - /// Gets the path for the web interface. + /// Gets the path of the directory containing the static web interface content, or null if the server is not + /// hosting the static web content. /// - /// The path for the web interface. public string DashboardUIPath { get { + if (_appConfig.NoWebContent()) + { + return null; + } + if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.DashboardSourcePath)) { return _serverConfigurationManager.Configuration.DashboardSourcePath; @@ -301,6 +311,11 @@ namespace MediaBrowser.WebDashboard.Api /// System.Object. public async Task Get(GetDashboardResource request) { + if (_appConfig.NoWebContent() || DashboardUIPath == null) + { + throw new ResourceNotFoundException(); + } + var path = request.ResourceName; var contentType = MimeTypes.GetMimeType(path); @@ -372,6 +387,11 @@ namespace MediaBrowser.WebDashboard.Api public async Task Get(GetDashboardPackage request) { + if (_appConfig.NoWebContent() || DashboardUIPath == null) + { + throw new ResourceNotFoundException(); + } + var mode = request.Mode; var inputPath = string.IsNullOrWhiteSpace(mode) ? From 05ab61200a181ff39034a5c3ba30a4ee2672971d Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 17:42:57 +0100 Subject: [PATCH 21/29] Do not call UseContentRoot() on the web host builder This call has nothing to do with static web content and should not have ever been called with the web content path: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/?view=aspnetcore-3.1&tabs=windows#content-root Since this call is removed, the ContentRoot property is also removed from ApplicationHost as it is no longer needed. Finally, move validation of the static content path to the DashboardService where it is actually used. --- .../AppBase/BaseApplicationPaths.cs | 10 +--------- .../ApplicationHost.cs | 11 ---------- Jellyfin.Server/Program.cs | 20 +------------------ .../Configuration/IApplicationPaths.cs | 9 +++++++-- .../Api/DashboardService.cs | 11 ++++++++++ 5 files changed, 20 insertions(+), 41 deletions(-) diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 8bcd087452..bc47817438 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -1,8 +1,6 @@ using System; using System.IO; using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Extensions; -using Microsoft.Extensions.Configuration; namespace Emby.Server.Implementations.AppBase { @@ -39,13 +37,7 @@ namespace Emby.Server.Implementations.AppBase /// The program data path. public string ProgramDataPath { get; } - /// - /// Gets the path to the web UI resources folder. - /// - /// The web UI resources path. - /// - /// This value is not relevant if is true. - /// + /// public string WebPath { get; } /// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7262268a97..52c5176271 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -236,11 +236,6 @@ namespace Emby.Server.Implementations /// public int HttpsPort { get; private set; } - /// - /// Gets the content root for the webhost. - /// - public string ContentRoot { get; private set; } - /// /// Gets the server configuration manager. /// @@ -614,12 +609,6 @@ namespace Emby.Server.Implementations DiscoverTypes(); await RegisterServices(serviceCollection, startupConfig).ConfigureAwait(false); - - ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; - if (string.IsNullOrEmpty(ContentRoot)) - { - ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; - } } public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index bfb2fc6b57..67251eb249 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -243,7 +243,7 @@ namespace Jellyfin.Server IConfiguration startupConfig, IApplicationPaths appPaths) { - var webhostBuilder = new WebHostBuilder() + return new WebHostBuilder() .UseKestrel(options => { var addresses = appHost.ServerConfigurationManager @@ -289,24 +289,6 @@ namespace Jellyfin.Server services.TryAdd(serviceCollection); }) .UseStartup(); - - // Set up static content hosting unless it has been disabled via config - if (!startupConfig.NoWebContent()) - { - // Fail startup if the web content does not exist - if (!Directory.Exists(appHost.ContentRoot) || !Directory.GetFiles(appHost.ContentRoot).Any()) - { - throw new InvalidOperationException( - "The server is expected to host web content, but the provided content directory is either " + - $"invalid or empty: {appHost.ContentRoot}. If you do not want to host web content with the " + - $"server, you may set the '{MediaBrowser.Controller.Extensions.ConfigurationExtensions.NoWebContentKey}' flag."); - } - - // Configure the web host to host the static web content - webhostBuilder.UseContentRoot(appHost.ContentRoot); - } - - return webhostBuilder; } /// diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index 5bdea7d8b3..870b90796c 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -1,3 +1,5 @@ +using MediaBrowser.Model.Configuration; + namespace MediaBrowser.Common.Configuration { /// @@ -12,9 +14,12 @@ namespace MediaBrowser.Common.Configuration string ProgramDataPath { get; } /// - /// Gets the path to the web UI resources folder + /// Gets the path to the web UI resources folder. /// - /// The web UI resources path. + /// + /// This value is not relevant if the server is configured to not host any static web content. Additionally, + /// the value for takes precedence over this one. + /// string WebPath { get; } /// diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 11901c25fb..0114f5d602 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -136,6 +136,17 @@ namespace MediaBrowser.WebDashboard.Api _resultFactory = resultFactory; _resourceFileManager = resourceFileManager; _appConfig = appConfig; + + // Validate web content path + string webContentPath = DashboardUIPath; + bool webContentPathValid = appConfig.NoWebContent() || (Directory.Exists(webContentPath) && Directory.GetFiles(webContentPath).Any()); + if (!webContentPathValid) + { + throw new InvalidOperationException( + "The server is expected to host web content, but the provided content directory is either " + + $"invalid or empty: {webContentPath}. If you do not want to host web content with the server, " + + $"you may set the '{Controller.Extensions.ConfigurationExtensions.NoWebContentKey}' flag."); + } } /// From 5d482590463232d1462b2f4738920bbd05c25165 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 17:43:52 +0100 Subject: [PATCH 22/29] Use the same key constant for command line config and all other config --- Jellyfin.Server/StartupOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 0f15488f40..ac5df3925e 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -21,7 +21,7 @@ namespace Jellyfin.Server /// /// Gets or sets a value indicating whether the server should not host static web content. /// - [Option("nowebcontent", Required = false, HelpText = "Indicates that the web server should not host any static web content.")] + [Option(ConfigurationExtensions.NoWebContentKey, Required = false, HelpText = "Indicates that the web server should not host any static web content.")] public bool NoWebContent { get; set; } /// From 923313bb1cda2f265c6fa62f8b2b3f053cafd5a4 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 15 Mar 2020 17:49:13 +0100 Subject: [PATCH 23/29] Add debugging profile for running the server without web content --- Jellyfin.Server/Properties/launchSettings.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Jellyfin.Server/Properties/launchSettings.json diff --git a/Jellyfin.Server/Properties/launchSettings.json b/Jellyfin.Server/Properties/launchSettings.json new file mode 100644 index 0000000000..d68a611c1b --- /dev/null +++ b/Jellyfin.Server/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "Jellyfin.Server": { + "commandName": "Project" + }, + "Jellyfin.Server (nowebcontent)": { + "commandName": "Project", + "commandLineArgs": "--nowebcontent" + } + } +} \ No newline at end of file From 7dd2f3a8f2dc6ea4db5ef9e1db4684b39cbcb6d3 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 17 Mar 2020 14:19:43 +0100 Subject: [PATCH 24/29] Do not add command line flag to the config hierarchy unless it is explicitly set --- Jellyfin.Server/StartupOptions.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index ac5df3925e..0abc0fd911 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -81,9 +81,15 @@ namespace Jellyfin.Server /// /// The configuration dictionary. public Dictionary ConvertToConfig() - => new Dictionary + { + var config = new Dictionary(); + + if (NoWebContent) { - { ConfigurationExtensions.NoWebContentKey, NoWebContent.ToString(CultureInfo.InvariantCulture) } - }; + config.Add(ConfigurationExtensions.NoWebContentKey, bool.TrueString); + } + + return config; + } } } From 1a63c3f3644f8144ac7761724476891f4dc1d2cd Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 20 Mar 2020 13:13:20 +0100 Subject: [PATCH 25/29] Inject IConfiguration instead of resolving manually --- Emby.Server.Implementations/EntryPoints/StartupWizard.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index bc6b8c956a..9a41396ce0 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -13,10 +13,8 @@ namespace Emby.Server.Implementations.EntryPoints /// public sealed class StartupWizard : IServerEntryPoint { - /// - /// The app host. - /// private readonly IServerApplicationHost _appHost; + private readonly IConfiguration _appConfig; private readonly IServerConfigurationManager _config; /// @@ -24,9 +22,10 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The application host. /// The configuration manager. - public StartupWizard(IServerApplicationHost appHost, IServerConfigurationManager config) + public StartupWizard(IServerApplicationHost appHost, IConfiguration appConfig, IServerConfigurationManager config) { _appHost = appHost; + _appConfig = appConfig; _config = config; } @@ -38,7 +37,7 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - if (_appHost.Resolve().NoWebContent()) + if (_appConfig.NoWebContent()) { BrowserLauncher.OpenSwaggerPage(_appHost); } From aa546dd36abb688cb3a5d10e589521ebf79ef610 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sat, 21 Mar 2020 18:25:09 +0100 Subject: [PATCH 26/29] Rename command line option to --nowebclient and config setting to HostWebClient --- .../ConfigurationOptions.cs | 2 +- .../EntryPoints/StartupWizard.cs | 2 +- Jellyfin.Server/Program.cs | 4 +-- .../Properties/launchSettings.json | 6 ++--- Jellyfin.Server/StartupOptions.cs | 10 +++---- .../Extensions/ConfigurationExtensions.cs | 8 +++--- .../Api/DashboardService.cs | 27 ++++++++++--------- 7 files changed, 31 insertions(+), 28 deletions(-) diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 814d4b8b57..4574a64fdb 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations /// public static Dictionary DefaultConfiguration => new Dictionary { - { NoWebContentKey, bool.FalseString }, + { HostWebClientKey, bool.TrueString }, { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 9a41396ce0..8e97719311 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - if (_appConfig.NoWebContent()) + if (!_appConfig.HostWebClient()) { BrowserLauncher.OpenSwaggerPage(_appHost); } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 67251eb249..d9ca141363 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -490,9 +490,9 @@ namespace Jellyfin.Server IApplicationPaths appPaths, IConfiguration? startupConfig = null) { - // Use the swagger API page as the default redirect path if not hosting the jellyfin-web content + // Use the swagger API page as the default redirect path if not hosting the web client var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; - if (startupConfig != null && startupConfig.NoWebContent()) + if (startupConfig != null && !startupConfig.HostWebClient()) { inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "swagger/index.html"; } diff --git a/Jellyfin.Server/Properties/launchSettings.json b/Jellyfin.Server/Properties/launchSettings.json index d68a611c1b..53d9fe1656 100644 --- a/Jellyfin.Server/Properties/launchSettings.json +++ b/Jellyfin.Server/Properties/launchSettings.json @@ -3,9 +3,9 @@ "Jellyfin.Server": { "commandName": "Project" }, - "Jellyfin.Server (nowebcontent)": { + "Jellyfin.Server (nowebclient)": { "commandName": "Project", - "commandLineArgs": "--nowebcontent" + "commandLineArgs": "--nowebclient" } } -} \ No newline at end of file +} diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 0abc0fd911..c93577d3ea 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -19,10 +19,10 @@ namespace Jellyfin.Server public string? DataDir { get; set; } /// - /// Gets or sets a value indicating whether the server should not host static web content. + /// Gets or sets a value indicating whether the server should not host the web client. /// - [Option(ConfigurationExtensions.NoWebContentKey, Required = false, HelpText = "Indicates that the web server should not host any static web content.")] - public bool NoWebContent { get; set; } + [Option("nowebclient", Required = false, HelpText = "Indicates that the web server should not host the web client.")] + public bool NoWebClient { get; set; } /// /// Gets or sets the path to the web directory. @@ -84,9 +84,9 @@ namespace Jellyfin.Server { var config = new Dictionary(); - if (NoWebContent) + if (NoWebClient) { - config.Add(ConfigurationExtensions.NoWebContentKey, bool.TrueString); + config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString); } return config; diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 900cc6cb57..c951499847 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -9,9 +9,9 @@ namespace MediaBrowser.Controller.Extensions public static class ConfigurationExtensions { /// - /// The key for a setting that indicates whether the application should host static web content. + /// The key for a setting that indicates whether the application should host web client content. /// - public const string NoWebContentKey = "nowebcontent"; + public const string HostWebClientKey = "hostwebclient"; /// /// The key for the FFmpeg probe size option. @@ -34,8 +34,8 @@ namespace MediaBrowser.Controller.Extensions /// The configuration to retrieve the value from. /// The parsed config value. /// The config value is not a valid bool string. See . - public static bool NoWebContent(this IConfiguration configuration) - => configuration.GetValue(NoWebContentKey); + public static bool HostWebClient(this IConfiguration configuration) + => configuration.GetValue(HostWebClientKey); /// /// Gets the FFmpeg probe size from the . diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 3e47ce6829..a71d685fbb 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -136,15 +136,18 @@ namespace MediaBrowser.WebDashboard.Api _fileSystem = fileSystem; _resultFactory = resultFactory; - // Validate web content path - string webContentPath = DashboardUIPath; - bool webContentPathValid = appConfig.NoWebContent() || (Directory.Exists(webContentPath) && Directory.GetFiles(webContentPath).Any()); - if (!webContentPathValid) + // If hosting the web client, validate the client content path + if (appConfig.HostWebClient()) { - throw new InvalidOperationException( - "The server is expected to host web content, but the provided content directory is either " + - $"invalid or empty: {webContentPath}. If you do not want to host web content with the server, " + - $"you may set the '{Controller.Extensions.ConfigurationExtensions.NoWebContentKey}' flag."); + string webContentPath = DashboardUIPath; + if (!Directory.Exists(webContentPath) || !Directory.GetFiles(webContentPath).Any()) + { + throw new InvalidOperationException( + "The server is expected to host the web client, but the provided content directory is either " + + $"invalid or empty: {webContentPath}. If you do not want to host the web client with the " + + "server, you may set the '--nowebclient' command line flag, or set" + + $"'{Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings."); + } } } @@ -156,13 +159,13 @@ namespace MediaBrowser.WebDashboard.Api /// /// Gets the path of the directory containing the static web interface content, or null if the server is not - /// hosting the static web content. + /// hosting the web client. /// public string DashboardUIPath { get { - if (_appConfig.NoWebContent()) + if (!_appConfig.HostWebClient()) { return null; } @@ -329,7 +332,7 @@ namespace MediaBrowser.WebDashboard.Api /// System.Object. public async Task Get(GetDashboardResource request) { - if (_appConfig.NoWebContent() || DashboardUIPath == null) + if (!_appConfig.HostWebClient() || DashboardUIPath == null) { throw new ResourceNotFoundException(); } @@ -405,7 +408,7 @@ namespace MediaBrowser.WebDashboard.Api public async Task Get(GetDashboardPackage request) { - if (_appConfig.NoWebContent() || DashboardUIPath == null) + if (!_appConfig.HostWebClient() || DashboardUIPath == null) { throw new ResourceNotFoundException(); } From 4bccaafb57abbc75858896374d42819f1c73fb20 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Tue, 24 Mar 2020 16:27:54 +0100 Subject: [PATCH 27/29] Use 'Length' instead of 'Any()' for an array --- MediaBrowser.WebDashboard/Api/DashboardService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index a71d685fbb..1ca035f7bd 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -140,7 +140,7 @@ namespace MediaBrowser.WebDashboard.Api if (appConfig.HostWebClient()) { string webContentPath = DashboardUIPath; - if (!Directory.Exists(webContentPath) || !Directory.GetFiles(webContentPath).Any()) + if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0) { throw new InvalidOperationException( "The server is expected to host the web client, but the provided content directory is either " + From ca85bef7c57108977d0f9813940f1ca942902d30 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Wed, 25 Mar 2020 18:52:14 +0100 Subject: [PATCH 28/29] Move check for web client directory to application startup in Program.cs --- Jellyfin.Server/Program.cs | 16 ++++++++++ .../Api/DashboardService.cs | 29 +++++++++++-------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index d9ca141363..2939d33782 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -20,6 +20,7 @@ using Jellyfin.Drawing.Skia; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Extensions; +using MediaBrowser.WebDashboard.Api; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -185,8 +186,23 @@ namespace Jellyfin.Server new ManagedFileSystem(_loggerFactory.CreateLogger(), appPaths), GetImageEncoder(appPaths), new NetworkManager(_loggerFactory.CreateLogger())); + try { + // If hosting the web client, validate the client content path + if (startupConfig.HostWebClient()) + { + string webContentPath = DashboardService.GetDashboardUIPath(startupConfig, appHost.ServerConfigurationManager); + if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0) + { + throw new InvalidOperationException( + "The server is expected to host the web client, but the provided content directory is either " + + $"invalid or empty: {webContentPath}. If you do not want to host the web client with the " + + "server, you may set the '--nowebclient' command line flag, or set" + + $"'{MediaBrowser.Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings."); + } + } + ServiceCollection serviceCollection = new ServiceCollection(); await appHost.InitAsync(serviceCollection, startupConfig).ConfigureAwait(false); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 1ca035f7bd..938ab513b5 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -161,22 +161,27 @@ namespace MediaBrowser.WebDashboard.Api /// Gets the path of the directory containing the static web interface content, or null if the server is not /// hosting the web client. /// - public string DashboardUIPath + public string DashboardUIPath => GetDashboardUIPath(_appConfig, _serverConfigurationManager); + + /// + /// Gets the path of the directory containing the static web interface content. + /// + /// The app configuration. + /// The server configuration manager. + /// The directory path, or null if the server is not hosting the web client. + public static string GetDashboardUIPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager) { - get + if (!appConfig.HostWebClient()) { - if (!_appConfig.HostWebClient()) - { - return null; - } - - if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.DashboardSourcePath)) - { - return _serverConfigurationManager.Configuration.DashboardSourcePath; - } + return null; + } - return _serverConfigurationManager.ApplicationPaths.WebPath; + if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath)) + { + return serverConfigManager.Configuration.DashboardSourcePath; } + + return serverConfigManager.ApplicationPaths.WebPath; } [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] From 861bad1edaec10fa03d2f7cf12bb8b7b6ae1802c Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Wed, 1 Apr 2020 13:26:47 +0200 Subject: [PATCH 29/29] Apply suggestions from code review --- .../Extensions/ConfigurationExtensions.cs | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index c951499847..c0043c0efa 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Extensions public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates"; /// - /// Gets a value indicating whether the application should not host static web content from the . + /// Gets a value indicating whether the application should host static web content from the . /// /// The configuration to retrieve the value from. /// The parsed config value. diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 938ab513b5..133a35527d 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -135,20 +135,6 @@ namespace MediaBrowser.WebDashboard.Api _serverConfigurationManager = serverConfigurationManager; _fileSystem = fileSystem; _resultFactory = resultFactory; - - // If hosting the web client, validate the client content path - if (appConfig.HostWebClient()) - { - string webContentPath = DashboardUIPath; - if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0) - { - throw new InvalidOperationException( - "The server is expected to host the web client, but the provided content directory is either " + - $"invalid or empty: {webContentPath}. If you do not want to host the web client with the " + - "server, you may set the '--nowebclient' command line flag, or set" + - $"'{Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings."); - } - } } ///