< Summary - Jellyfin

Information
Class: Jellyfin.Server.Program
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Program.cs
Line coverage
86%
Covered lines: 19
Uncovered lines: 3
Coverable lines: 22
Total lines: 320
Line coverage: 86.3%
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%11100%
Main(...)100%210%
CreateAppConfiguration(...)100%11100%
ConfigureAppConfiguration(...)50%4490%
PrepareDatabaseProvider(...)100%11100%

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 Emby.Server.Implementations.Configuration;
 13using Emby.Server.Implementations.Serialization;
 14using Jellyfin.Database.Implementations;
 15using Jellyfin.Server.Extensions;
 16using Jellyfin.Server.Helpers;
 17using Jellyfin.Server.Implementations.DatabaseConfiguration;
 18using Jellyfin.Server.Implementations.Extensions;
 19using Jellyfin.Server.Implementations.StorageHelpers;
 20using Jellyfin.Server.Migrations;
 21using Jellyfin.Server.ServerSetupApp;
 22using MediaBrowser.Common.Configuration;
 23using MediaBrowser.Common.Net;
 24using MediaBrowser.Controller;
 25using Microsoft.AspNetCore.Hosting;
 26using Microsoft.EntityFrameworkCore;
 27using Microsoft.Extensions.Configuration;
 28using Microsoft.Extensions.DependencyInjection;
 29using Microsoft.Extensions.Hosting;
 30using Microsoft.Extensions.Logging;
 31using Microsoft.Extensions.Logging.Abstractions;
 32using Serilog;
 33using Serilog.Extensions.Logging;
 34using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
 35using ILogger = Microsoft.Extensions.Logging.ILogger;
 36
 37namespace Jellyfin.Server
 38{
 39    /// <summary>
 40    /// Class containing the entry point of the application.
 41    /// </summary>
 42    public static class Program
 43    {
 44        /// <summary>
 45        /// The name of logging configuration file containing application defaults.
 46        /// </summary>
 47        public const string LoggingConfigFileDefault = "logging.default.json";
 48
 49        /// <summary>
 50        /// The name of the logging configuration file containing the system-specific override settings.
 51        /// </summary>
 52        public const string LoggingConfigFileSystem = "logging.json";
 53
 154        private static readonly SerilogLoggerFactory _loggerFactory = new SerilogLoggerFactory();
 55        private static SetupServer? _setupServer;
 56        private static CoreAppHost? _appHost;
 157        private static IHost? _jellyfinHost = null;
 58        private static long _startTimestamp;
 159        private static ILogger _logger = NullLogger.Instance;
 60        private static bool _restartOnShutdown;
 61
 62        /// <summary>
 63        /// The entry point of the application.
 64        /// </summary>
 65        /// <param name="args">The command line arguments passed.</param>
 66        /// <returns><see cref="Task" />.</returns>
 67        public static Task Main(string[] args)
 68        {
 69            static Task ErrorParsingArguments(IEnumerable<Error> errors)
 70            {
 71                Environment.ExitCode = 1;
 72                return Task.CompletedTask;
 73            }
 74
 75            // Parse the command line arguments and either start the app or exit indicating error
 076            return Parser.Default.ParseArguments<StartupOptions>(args)
 077                .MapResult(StartApp, ErrorParsingArguments);
 78        }
 79
 80        private static async Task StartApp(StartupOptions options)
 81        {
 82            _startTimestamp = Stopwatch.GetTimestamp();
 83            ServerApplicationPaths appPaths = StartupHelpers.CreateApplicationPaths(options);
 84            appPaths.MakeSanityCheckOrThrow();
 85
 86            // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
 87            Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
 88
 89            // Enable cl-va P010 interop for tonemapping on Intel VAAPI
 90            Environment.SetEnvironmentVariable("NEOReadDebugKeys", "1");
 91            Environment.SetEnvironmentVariable("EnableExtendedVaFormats", "1");
 92
 93            await StartupHelpers.InitLoggingConfigFile(appPaths).ConfigureAwait(false);
 94
 95            // Create an instance of the application configuration to use for application startup
 96            IConfiguration startupConfig = CreateAppConfiguration(options, appPaths);
 97            _setupServer = new SetupServer(static () => _jellyfinHost?.Services?.GetService<INetworkManager>(), appPaths
 98            await _setupServer.RunAsync().ConfigureAwait(false);
 99            StartupHelpers.InitializeLoggingFramework(startupConfig, appPaths);
 100            _logger = _loggerFactory.CreateLogger("Main");
 101
 102            // Use the logging framework for uncaught exceptions instead of std error
 103            AppDomain.CurrentDomain.UnhandledException += (_, e)
 104                => _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception");
 105
 106            _logger.LogInformation(
 107                "Jellyfin version: {Version}",
 108                Assembly.GetEntryAssembly()!.GetName().Version!.ToString(3));
 109
 110            StartupHelpers.LogEnvironmentInfo(_logger, appPaths);
 111
 112            // If hosting the web client, validate the client content path
 113            if (startupConfig.HostWebClient())
 114            {
 115                var webContentPath = appPaths.WebPath;
 116                if (!Directory.Exists(webContentPath) || !Directory.EnumerateFiles(webContentPath).Any())
 117                {
 118                    _logger.LogError(
 119                        "The server is expected to host the web client, but the provided content directory is either " +
 120                        "invalid or empty: {WebContentPath}. If you do not want to host the web client with the " +
 121                        "server, you may set the '--nowebclient' command line flag, or set" +
 122                        "'{ConfigKey}=false' in your config settings",
 123                        webContentPath,
 124                        HostWebClientKey);
 125                    Environment.ExitCode = 1;
 126                    return;
 127                }
 128            }
 129
 130            StorageHelper.TestCommonPathsForStorageCapacity(appPaths, _loggerFactory.CreateLogger<Startup>());
 131
 132            StartupHelpers.PerformStaticInitialization();
 133
 134            await ApplyStartupMigrationAsync(appPaths, startupConfig).ConfigureAwait(false);
 135
 136            do
 137            {
 138                await StartServer(appPaths, options, startupConfig).ConfigureAwait(false);
 139
 140                if (_restartOnShutdown)
 141                {
 142                    _startTimestamp = Stopwatch.GetTimestamp();
 143                    await _setupServer.StopAsync().ConfigureAwait(false);
 144                    await _setupServer.RunAsync().ConfigureAwait(false);
 145                }
 146            } while (_restartOnShutdown);
 147
 148            _setupServer.Dispose();
 149        }
 150
 151        private static async Task StartServer(IServerApplicationPaths appPaths, StartupOptions options, IConfiguration s
 152        {
 153            using CoreAppHost appHost = new CoreAppHost(
 154                            appPaths,
 155                            _loggerFactory,
 156                            options,
 157                            startupConfig);
 158            _appHost = appHost;
 159            try
 160            {
 161                _jellyfinHost = Host.CreateDefaultBuilder()
 162                    .UseConsoleLifetime()
 163                    .ConfigureServices(services => appHost.Init(services))
 164                    .ConfigureWebHostDefaults(webHostBuilder =>
 165                    {
 166                        webHostBuilder.ConfigureWebHostBuilder(appHost, startupConfig, appPaths, _logger);
 167                        if (bool.TryParse(Environment.GetEnvironmentVariable("JELLYFIN_ENABLE_IIS"), out var iisEnabled)
 168                        {
 169                            _logger.LogCritical("UNSUPPORTED HOSTING ENVIRONMENT Microsoft Internet Information Services
 170                            webHostBuilder.UseIIS();
 171                        }
 172                    })
 173                    .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(options, appPaths, startupConf
 174                    .UseSerilog()
 175                    .Build();
 176
 177                // Re-use the host service provider in the app host since ASP.NET doesn't allow a custom service collect
 178                appHost.ServiceProvider = _jellyfinHost.Services;
 179
 180                PrepareDatabaseProvider(appHost.ServiceProvider);
 181
 182                await ApplyCoreMigrationsAsync(appHost.ServiceProvider, Migrations.Stages.JellyfinMigrationStageTypes.Co
 183
 184                await appHost.InitializeServices(startupConfig).ConfigureAwait(false);
 185
 186                await ApplyCoreMigrationsAsync(appHost.ServiceProvider, Migrations.Stages.JellyfinMigrationStageTypes.Ap
 187
 188                try
 189                {
 190                    await _setupServer!.StopAsync().ConfigureAwait(false);
 191                    await _jellyfinHost.StartAsync().ConfigureAwait(false);
 192
 193                    if (!OperatingSystem.IsWindows() && startupConfig.UseUnixSocket())
 194                    {
 195                        var socketPath = StartupHelpers.GetUnixSocketPath(startupConfig, appPaths);
 196
 197                        StartupHelpers.SetUnixSocketPermissions(startupConfig, socketPath, _logger);
 198                    }
 199                }
 200                catch (Exception)
 201                {
 202                    _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bin
 203                    throw;
 204                }
 205
 206                await appHost.RunStartupTasksAsync().ConfigureAwait(false);
 207
 208                _logger.LogInformation("Startup complete {Time:g}", Stopwatch.GetElapsedTime(_startTimestamp));
 209
 210                await _jellyfinHost.WaitForShutdownAsync().ConfigureAwait(false);
 211                _restartOnShutdown = appHost.ShouldRestart;
 212            }
 213            catch (Exception ex)
 214            {
 215                _restartOnShutdown = false;
 216                _logger.LogCritical(ex, "Error while starting server");
 217            }
 218            finally
 219            {
 220                // Don't throw additional exception if startup failed.
 221                if (appHost.ServiceProvider is not null)
 222                {
 223                    _logger.LogInformation("Running query planner optimizations in the database... This might take a whi
 224
 225                    var databaseProvider = appHost.ServiceProvider.GetRequiredService<IJellyfinDatabaseProvider>();
 226                    using var shutdownSource = new CancellationTokenSource();
 227                    shutdownSource.CancelAfter((int)TimeSpan.FromSeconds(60).TotalMicroseconds);
 228                    await databaseProvider.RunShutdownTask(shutdownSource.Token).ConfigureAwait(false);
 229                }
 230
 231                _appHost = null;
 232                _jellyfinHost?.Dispose();
 233            }
 234        }
 235
 236        /// <summary>
 237        /// [Internal]Runs the startup Migrations.
 238        /// </summary>
 239        /// <remarks>
 240        /// Not intended to be used other then by jellyfin and its tests.
 241        /// </remarks>
 242        /// <param name="appPaths">Application Paths.</param>
 243        /// <param name="startupConfig">Startup Config.</param>
 244        /// <returns>A task.</returns>
 245        public static async Task ApplyStartupMigrationAsync(ServerApplicationPaths appPaths, IConfiguration startupConfi
 246        {
 247            var startupConfigurationManager = new ServerConfigurationManager(appPaths, _loggerFactory, new MyXmlSerializ
 248            startupConfigurationManager.AddParts([new DatabaseConfigurationFactory()]);
 249            var migrationStartupServiceProvider = new ServiceCollection()
 250                .AddLogging(d => d.AddSerilog())
 251                .AddJellyfinDbContext(startupConfigurationManager, startupConfig)
 252                .AddSingleton<IApplicationPaths>(appPaths)
 253                .AddSingleton<ServerApplicationPaths>(appPaths);
 254            var startupService = migrationStartupServiceProvider.BuildServiceProvider();
 255
 256            PrepareDatabaseProvider(startupService);
 257
 258            var jellyfinMigrationService = ActivatorUtilities.CreateInstance<JellyfinMigrationService>(startupService);
 259            await jellyfinMigrationService.CheckFirstTimeRunOrMigration(appPaths).ConfigureAwait(false);
 260            await jellyfinMigrationService.MigrateStepAsync(Migrations.Stages.JellyfinMigrationStageTypes.PreInitialisat
 261        }
 262
 263        /// <summary>
 264        /// [Internal]Runs the Jellyfin migrator service with the Core stage.
 265        /// </summary>
 266        /// <remarks>
 267        /// Not intended to be used other then by jellyfin and its tests.
 268        /// </remarks>
 269        /// <param name="serviceProvider">The service provider.</param>
 270        /// <param name="jellyfinMigrationStage">The stage to run.</param>
 271        /// <returns>A task.</returns>
 272        public static async Task ApplyCoreMigrationsAsync(IServiceProvider serviceProvider, Migrations.Stages.JellyfinMi
 273        {
 274            var jellyfinMigrationService = ActivatorUtilities.CreateInstance<JellyfinMigrationService>(serviceProvider);
 275            await jellyfinMigrationService.MigrateStepAsync(jellyfinMigrationStage, serviceProvider).ConfigureAwait(fals
 276        }
 277
 278        /// <summary>
 279        /// Create the application configuration.
 280        /// </summary>
 281        /// <param name="commandLineOpts">The command line options passed to the program.</param>
 282        /// <param name="appPaths">The application paths.</param>
 283        /// <returns>The application configuration.</returns>
 284        public static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths)
 285        {
 21286            return new ConfigurationBuilder()
 21287                .ConfigureAppConfiguration(commandLineOpts, appPaths)
 21288                .Build();
 289        }
 290
 291        private static IConfigurationBuilder ConfigureAppConfiguration(
 292            this IConfigurationBuilder config,
 293            StartupOptions commandLineOpts,
 294            IApplicationPaths appPaths,
 295            IConfiguration? startupConfig = null)
 296        {
 297            // Use the swagger API page as the default redirect path if not hosting the web client
 21298            var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration;
 21299            if (startupConfig is not null && !startupConfig.HostWebClient())
 300            {
 0301                inMemoryDefaultConfig[DefaultRedirectKey] = "api-docs/swagger";
 302            }
 303
 21304            return config
 21305                .SetBasePath(appPaths.ConfigurationDirectoryPath)
 21306                .AddInMemoryCollection(inMemoryDefaultConfig)
 21307                .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true)
 21308                .AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true)
 21309                .AddEnvironmentVariables("JELLYFIN_")
 21310                .AddInMemoryCollection(commandLineOpts.ConvertToConfig());
 311        }
 312
 313        private static void PrepareDatabaseProvider(IServiceProvider services)
 314        {
 21315            var factory = services.GetRequiredService<IDbContextFactory<JellyfinDbContext>>();
 21316            var provider = services.GetRequiredService<IJellyfinDatabaseProvider>();
 21317            provider.DbContextFactory = factory;
 21318        }
 319    }
 320}