< Summary - Jellyfin

Information
Class: Jellyfin.Server.ServerSetupApp.SetupServer
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/ServerSetupApp/SetupServer.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 17
Coverable lines: 17
Total lines: 220
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 4
Branch coverage: 0%
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
.ctor(...)100%210%
Dispose()0%2040%
ThrowIfDisposed()100%210%
CheckHealthAsync(...)100%210%

File(s)

/srv/git/jellyfin/Jellyfin.Server/ServerSetupApp/SetupServer.cs

#LineLine coverage
 1using System;
 2using System.IO;
 3using System.Linq;
 4using System.Net;
 5using System.Threading;
 6using System.Threading.Tasks;
 7using Emby.Server.Implementations.Configuration;
 8using Emby.Server.Implementations.Serialization;
 9using Jellyfin.Networking.Manager;
 10using MediaBrowser.Common.Configuration;
 11using MediaBrowser.Common.Net;
 12using MediaBrowser.Controller;
 13using MediaBrowser.Model.System;
 14using Microsoft.AspNetCore.Builder;
 15using Microsoft.AspNetCore.Hosting;
 16using Microsoft.AspNetCore.Http;
 17using Microsoft.Extensions.Configuration;
 18using Microsoft.Extensions.DependencyInjection;
 19using Microsoft.Extensions.Diagnostics.HealthChecks;
 20using Microsoft.Extensions.Hosting;
 21using Microsoft.Extensions.Logging;
 22using Microsoft.Extensions.Primitives;
 23
 24namespace Jellyfin.Server.ServerSetupApp;
 25
 26/// <summary>
 27/// Creates a fake application pipeline that will only exist for as long as the main app is not started.
 28/// </summary>
 29public sealed class SetupServer : IDisposable
 30{
 31    private readonly Func<INetworkManager?> _networkManagerFactory;
 32    private readonly IApplicationPaths _applicationPaths;
 33    private readonly Func<IServerApplicationHost?> _serverFactory;
 34    private readonly ILoggerFactory _loggerFactory;
 35    private readonly IConfiguration _startupConfiguration;
 36    private readonly ServerConfigurationManager _configurationManager;
 37    private IHost? _startupServer;
 38    private bool _disposed;
 39
 40    /// <summary>
 41    /// Initializes a new instance of the <see cref="SetupServer"/> class.
 42    /// </summary>
 43    /// <param name="networkManagerFactory">The networkmanager.</param>
 44    /// <param name="applicationPaths">The application paths.</param>
 45    /// <param name="serverApplicationHostFactory">The servers application host.</param>
 46    /// <param name="loggerFactory">The logger factory.</param>
 47    /// <param name="startupConfiguration">The startup configuration.</param>
 48    public SetupServer(
 49        Func<INetworkManager?> networkManagerFactory,
 50        IApplicationPaths applicationPaths,
 51        Func<IServerApplicationHost?> serverApplicationHostFactory,
 52        ILoggerFactory loggerFactory,
 53        IConfiguration startupConfiguration)
 54    {
 055        _networkManagerFactory = networkManagerFactory;
 056        _applicationPaths = applicationPaths;
 057        _serverFactory = serverApplicationHostFactory;
 058        _loggerFactory = loggerFactory;
 059        _startupConfiguration = startupConfiguration;
 060        var xmlSerializer = new MyXmlSerializer();
 061        _configurationManager = new ServerConfigurationManager(_applicationPaths, loggerFactory, xmlSerializer);
 062        _configurationManager.RegisterConfiguration<NetworkConfigurationFactory>();
 063    }
 64
 65    /// <summary>
 66    /// Starts the Bind-All Setup aspcore server to provide a reflection on the current core setup.
 67    /// </summary>
 68    /// <returns>A Task.</returns>
 69    public async Task RunAsync()
 70    {
 71        ThrowIfDisposed();
 72        _startupServer = Host.CreateDefaultBuilder()
 73            .UseConsoleLifetime()
 74            .ConfigureServices(serv =>
 75            {
 76                serv.AddHealthChecks()
 77                    .AddCheck<SetupHealthcheck>("StartupCheck");
 78            })
 79            .ConfigureWebHostDefaults(webHostBuilder =>
 80                    {
 81                        webHostBuilder
 82                                .UseKestrel((builderContext, options) =>
 83                                {
 84                                    var config = _configurationManager.GetNetworkConfiguration()!;
 85                                    var knownBindInterfaces = NetworkManager.GetInterfacesCore(_loggerFactory.CreateLogg
 86                                    knownBindInterfaces = NetworkManager.FilterBindSettings(config, knownBindInterfaces.
 87                                    var bindInterfaces = NetworkManager.GetAllBindInterfaces(false, _configurationManage
 88                                    Extensions.WebHostBuilderExtensions.SetupJellyfinWebServer(
 89                                        bindInterfaces,
 90                                        config.InternalHttpPort,
 91                                        null,
 92                                        null,
 93                                        _startupConfiguration,
 94                                        _applicationPaths,
 95                                        _loggerFactory.CreateLogger<SetupServer>(),
 96                                        builderContext,
 97                                        options);
 98                                })
 99                                .Configure(app =>
 100                                {
 101                                    app.UseHealthChecks("/health");
 102
 103                                    app.Map("/startup/logger", loggerRoute =>
 104                                    {
 105                                        loggerRoute.Run(async context =>
 106                                        {
 107                                            var networkManager = _networkManagerFactory();
 108                                            if (context.Connection.RemoteIpAddress is null || networkManager is null || 
 109                                            {
 110                                                context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
 111                                                return;
 112                                            }
 113
 114                                            var logFilePath = new DirectoryInfo(_applicationPaths.LogDirectoryPath)
 115                                                .EnumerateFiles()
 116                                                .OrderBy(f => f.CreationTimeUtc)
 117                                                .FirstOrDefault()
 118                                                ?.FullName;
 119                                            if (logFilePath is not null)
 120                                            {
 121                                                await context.Response.SendFileAsync(logFilePath, CancellationToken.None
 122                                            }
 123                                        });
 124                                    });
 125
 126                                    app.Map("/System/Info/Public", systemRoute =>
 127                                    {
 128                                        systemRoute.Run(async context =>
 129                                        {
 130                                            var jfApplicationHost = _serverFactory();
 131
 132                                            var retryCounter = 0;
 133                                            while (jfApplicationHost is null && retryCounter < 5)
 134                                            {
 135                                                await Task.Delay(500).ConfigureAwait(false);
 136                                                jfApplicationHost = _serverFactory();
 137                                                retryCounter++;
 138                                            }
 139
 140                                            if (jfApplicationHost is null)
 141                                            {
 142                                                context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
 143                                                context.Response.Headers.RetryAfter = new StringValues("5");
 144                                                return;
 145                                            }
 146
 147                                            var sysInfo = new PublicSystemInfo
 148                                            {
 149                                                Version = jfApplicationHost.ApplicationVersionString,
 150                                                ProductName = jfApplicationHost.Name,
 151                                                Id = jfApplicationHost.SystemId,
 152                                                ServerName = jfApplicationHost.FriendlyName,
 153                                                LocalAddress = jfApplicationHost.GetSmartApiUrl(context.Request),
 154                                                StartupWizardCompleted = false
 155                                            };
 156
 157                                            await context.Response.WriteAsJsonAsync(sysInfo).ConfigureAwait(false);
 158                                        });
 159                                    });
 160
 161                                    app.Run((context) =>
 162                                    {
 163                                        context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
 164                                        context.Response.Headers.RetryAfter = new StringValues("5");
 165                                        context.Response.Headers.ContentType = new StringValues("text/html");
 166                                        context.Response.WriteAsync("<p>Jellyfin Server still starting. Please wait.</p>
 167                                        var networkManager = _networkManagerFactory();
 168                                        if (networkManager is not null && context.Connection.RemoteIpAddress is not null
 169                                        {
 170                                            context.Response.WriteAsync("<p>You can download the current logfiles <a hre
 171                                        }
 172
 173                                        return Task.CompletedTask;
 174                                    });
 175                                });
 176                    })
 177                    .Build();
 178        await _startupServer.StartAsync().ConfigureAwait(false);
 179    }
 180
 181    /// <summary>
 182    /// Stops the Setup server.
 183    /// </summary>
 184    /// <returns>A task. Duh.</returns>
 185    public async Task StopAsync()
 186    {
 187        ThrowIfDisposed();
 188        if (_startupServer is null)
 189        {
 190            throw new InvalidOperationException("Tried to stop a non existing startup server");
 191        }
 192
 193        await _startupServer.StopAsync().ConfigureAwait(false);
 194    }
 195
 196    /// <inheritdoc/>
 197    public void Dispose()
 198    {
 0199        if (_disposed)
 200        {
 0201            return;
 202        }
 203
 0204        _disposed = true;
 0205        _startupServer?.Dispose();
 0206    }
 207
 208    private void ThrowIfDisposed()
 209    {
 0210        ObjectDisposedException.ThrowIf(_disposed, this);
 0211    }
 212
 213    private class SetupHealthcheck : IHealthCheck
 214    {
 215        public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken 
 216        {
 0217            return Task.FromResult(HealthCheckResult.Degraded("Server is still starting up."));
 218        }
 219    }
 220}