< Summary - Jellyfin

Information
Class: Jellyfin.Server.Program
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Program.cs
Line coverage
66%
Covered lines: 12
Uncovered lines: 6
Coverable lines: 18
Total lines: 260
Line coverage: 66.6%
Branch coverage
50%
Covered branches: 2
Total branches: 4
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
Main(...)100%210%
CreateAppConfiguration(...)100%11100%
ConfigureAppConfiguration(...)50%4490%

File(s)

/srv/git/jellyfin/Jellyfin.Server/Program.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Diagnostics;
 4using System.Globalization;
 5using System.IO;
 6using System.Linq;
 7using System.Reflection;
 8using System.Threading;
 9using System.Threading.Tasks;
 10using CommandLine;
 11using Emby.Server.Implementations;
 12using Jellyfin.Database.Implementations;
 13using Jellyfin.Server.Extensions;
 14using Jellyfin.Server.Helpers;
 15using Jellyfin.Server.Implementations.StorageHelpers;
 16using Jellyfin.Server.ServerSetupApp;
 17using MediaBrowser.Common.Configuration;
 18using MediaBrowser.Common.Net;
 19using MediaBrowser.Controller;
 20using Microsoft.AspNetCore.Hosting;
 21using Microsoft.Data.Sqlite;
 22using Microsoft.EntityFrameworkCore;
 23using Microsoft.Extensions.Configuration;
 24using Microsoft.Extensions.DependencyInjection;
 25using Microsoft.Extensions.Hosting;
 26using Microsoft.Extensions.Logging;
 27using Microsoft.Extensions.Logging.Abstractions;
 28using Serilog;
 29using Serilog.Extensions.Logging;
 30using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
 31using ILogger = Microsoft.Extensions.Logging.ILogger;
 32
 33namespace Jellyfin.Server
 34{
 35    /// <summary>
 36    /// Class containing the entry point of the application.
 37    /// </summary>
 38    public static class Program
 39    {
 40        /// <summary>
 41        /// The name of logging configuration file containing application defaults.
 42        /// </summary>
 43        public const string LoggingConfigFileDefault = "logging.default.json";
 44
 45        /// <summary>
 46        /// The name of the logging configuration file containing the system-specific override settings.
 47        /// </summary>
 48        public const string LoggingConfigFileSystem = "logging.json";
 49
 050        private static readonly SerilogLoggerFactory _loggerFactory = new SerilogLoggerFactory();
 51        private static SetupServer? _setupServer;
 52        private static CoreAppHost? _appHost;
 053        private static IHost? _jellyfinHost = null;
 54        private static long _startTimestamp;
 055        private static ILogger _logger = NullLogger.Instance;
 56        private static bool _restartOnShutdown;
 57
 58        /// <summary>
 59        /// The entry point of the application.
 60        /// </summary>
 61        /// <param name="args">The command line arguments passed.</param>
 62        /// <returns><see cref="Task" />.</returns>
 63        public static Task Main(string[] args)
 64        {
 65            static Task ErrorParsingArguments(IEnumerable<Error> errors)
 66            {
 67                Environment.ExitCode = 1;
 68                return Task.CompletedTask;
 69            }
 70
 71            // Parse the command line arguments and either start the app or exit indicating error
 072            return Parser.Default.ParseArguments<StartupOptions>(args)
 073                .MapResult(StartApp, ErrorParsingArguments);
 74        }
 75
 76        private static async Task StartApp(StartupOptions options)
 77        {
 78            _startTimestamp = Stopwatch.GetTimestamp();
 79            ServerApplicationPaths appPaths = StartupHelpers.CreateApplicationPaths(options);
 80
 81            // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
 82            Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
 83
 84            // Enable cl-va P010 interop for tonemapping on Intel VAAPI
 85            Environment.SetEnvironmentVariable("NEOReadDebugKeys", "1");
 86            Environment.SetEnvironmentVariable("EnableExtendedVaFormats", "1");
 87
 88            await StartupHelpers.InitLoggingConfigFile(appPaths).ConfigureAwait(false);
 89
 90            // Create an instance of the application configuration to use for application startup
 91            IConfiguration startupConfig = CreateAppConfiguration(options, appPaths);
 92            _setupServer = new SetupServer(static () => _jellyfinHost?.Services?.GetService<INetworkManager>(), appPaths
 93            await _setupServer.RunAsync().ConfigureAwait(false);
 94            StartupHelpers.InitializeLoggingFramework(startupConfig, appPaths);
 95            _logger = _loggerFactory.CreateLogger("Main");
 96
 97            // Use the logging framework for uncaught exceptions instead of std error
 98            AppDomain.CurrentDomain.UnhandledException += (_, e)
 99                => _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception");
 100
 101            _logger.LogInformation(
 102                "Jellyfin version: {Version}",
 103                Assembly.GetEntryAssembly()!.GetName().Version!.ToString(3));
 104
 105            StartupHelpers.LogEnvironmentInfo(_logger, appPaths);
 106
 107            // If hosting the web client, validate the client content path
 108            if (startupConfig.HostWebClient())
 109            {
 110                var webContentPath = appPaths.WebPath;
 111                if (!Directory.Exists(webContentPath) || !Directory.EnumerateFiles(webContentPath).Any())
 112                {
 113                    _logger.LogError(
 114                        "The server is expected to host the web client, but the provided content directory is either " +
 115                        "invalid or empty: {WebContentPath}. If you do not want to host the web client with the " +
 116                        "server, you may set the '--nowebclient' command line flag, or set" +
 117                        "'{ConfigKey}=false' in your config settings",
 118                        webContentPath,
 119                        HostWebClientKey);
 120                    Environment.ExitCode = 1;
 121                    return;
 122                }
 123            }
 124
 125            StorageHelper.TestCommonPathsForStorageCapacity(appPaths, _loggerFactory.CreateLogger<Startup>());
 126
 127            StartupHelpers.PerformStaticInitialization();
 128            await Migrations.MigrationRunner.RunPreStartup(appPaths, _loggerFactory).ConfigureAwait(false);
 129
 130            do
 131            {
 132                await StartServer(appPaths, options, startupConfig).ConfigureAwait(false);
 133
 134                if (_restartOnShutdown)
 135                {
 136                    _startTimestamp = Stopwatch.GetTimestamp();
 137                    await _setupServer.StopAsync().ConfigureAwait(false);
 138                    await _setupServer.RunAsync().ConfigureAwait(false);
 139                }
 140            } while (_restartOnShutdown);
 141
 142            _setupServer.Dispose();
 143        }
 144
 145        private static async Task StartServer(IServerApplicationPaths appPaths, StartupOptions options, IConfiguration s
 146        {
 147            using CoreAppHost appHost = new CoreAppHost(
 148                            appPaths,
 149                            _loggerFactory,
 150                            options,
 151                            startupConfig);
 152            _appHost = appHost;
 153            try
 154            {
 155                _jellyfinHost = Host.CreateDefaultBuilder()
 156                    .UseConsoleLifetime()
 157                    .ConfigureServices(services => appHost.Init(services))
 158                    .ConfigureWebHostDefaults(webHostBuilder =>
 159                    {
 160                        webHostBuilder.ConfigureWebHostBuilder(appHost, startupConfig, appPaths, _logger);
 161                        if (bool.TryParse(Environment.GetEnvironmentVariable("JELLYFIN_ENABLE_IIS"), out var iisEnabled)
 162                        {
 163                            _logger.LogCritical("UNSUPPORTED HOSTING ENVIRONMENT Microsoft Internet Information Services
 164                            webHostBuilder.UseIIS();
 165                        }
 166                    })
 167                    .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(options, appPaths, startupConf
 168                    .UseSerilog()
 169                    .Build();
 170
 171                // Re-use the host service provider in the app host since ASP.NET doesn't allow a custom service collect
 172                appHost.ServiceProvider = _jellyfinHost.Services;
 173
 174                await appHost.InitializeServices(startupConfig).ConfigureAwait(false);
 175                await Migrations.MigrationRunner.Run(appHost, _loggerFactory).ConfigureAwait(false);
 176
 177                try
 178                {
 179                    await _setupServer!.StopAsync().ConfigureAwait(false);
 180                    await _jellyfinHost.StartAsync().ConfigureAwait(false);
 181
 182                    if (!OperatingSystem.IsWindows() && startupConfig.UseUnixSocket())
 183                    {
 184                        var socketPath = StartupHelpers.GetUnixSocketPath(startupConfig, appPaths);
 185
 186                        StartupHelpers.SetUnixSocketPermissions(startupConfig, socketPath, _logger);
 187                    }
 188                }
 189                catch (Exception)
 190                {
 191                    _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bin
 192                    throw;
 193                }
 194
 195                await appHost.RunStartupTasksAsync().ConfigureAwait(false);
 196
 197                _logger.LogInformation("Startup complete {Time:g}", Stopwatch.GetElapsedTime(_startTimestamp));
 198
 199                await _jellyfinHost.WaitForShutdownAsync().ConfigureAwait(false);
 200                _restartOnShutdown = appHost.ShouldRestart;
 201            }
 202            catch (Exception ex)
 203            {
 204                _restartOnShutdown = false;
 205                _logger.LogCritical(ex, "Error while starting server");
 206            }
 207            finally
 208            {
 209                // Don't throw additional exception if startup failed.
 210                if (appHost.ServiceProvider is not null)
 211                {
 212                    _logger.LogInformation("Running query planner optimizations in the database... This might take a whi
 213
 214                    var databaseProvider = appHost.ServiceProvider.GetRequiredService<IJellyfinDatabaseProvider>();
 215                    using var shutdownSource = new CancellationTokenSource();
 216                    shutdownSource.CancelAfter((int)TimeSpan.FromSeconds(60).TotalMicroseconds);
 217                    await databaseProvider.RunShutdownTask(shutdownSource.Token).ConfigureAwait(false);
 218                }
 219
 220                _appHost = null;
 221                _jellyfinHost?.Dispose();
 222            }
 223        }
 224
 225        /// <summary>
 226        /// Create the application configuration.
 227        /// </summary>
 228        /// <param name="commandLineOpts">The command line options passed to the program.</param>
 229        /// <param name="appPaths">The application paths.</param>
 230        /// <returns>The application configuration.</returns>
 231        public static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths)
 232        {
 21233            return new ConfigurationBuilder()
 21234                .ConfigureAppConfiguration(commandLineOpts, appPaths)
 21235                .Build();
 236        }
 237
 238        private static IConfigurationBuilder ConfigureAppConfiguration(
 239            this IConfigurationBuilder config,
 240            StartupOptions commandLineOpts,
 241            IApplicationPaths appPaths,
 242            IConfiguration? startupConfig = null)
 243        {
 244            // Use the swagger API page as the default redirect path if not hosting the web client
 21245            var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration;
 21246            if (startupConfig is not null && !startupConfig.HostWebClient())
 247            {
 0248                inMemoryDefaultConfig[DefaultRedirectKey] = "api-docs/swagger";
 249            }
 250
 21251            return config
 21252                .SetBasePath(appPaths.ConfigurationDirectoryPath)
 21253                .AddInMemoryCollection(inMemoryDefaultConfig)
 21254                .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true)
 21255                .AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true)
 21256                .AddEnvironmentVariables("JELLYFIN_")
 21257                .AddInMemoryCollection(commandLineOpts.ConvertToConfig());
 258        }
 259    }
 260}