< Summary - Jellyfin

Information
Class: Jellyfin.Server.Program
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Program.cs
Line coverage
70%
Covered lines: 12
Uncovered lines: 5
Coverable lines: 17
Total lines: 247
Line coverage: 70.5%
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%4.02490%

File(s)

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

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