2019-01-13 19:30:41 +00:00
using System ;
using System.Diagnostics ;
2019-10-26 21:58:23 +00:00
using System.Globalization ;
2019-01-13 19:30:41 +00:00
using System.IO ;
using System.Linq ;
using System.Net ;
using System.Reflection ;
using System.Runtime.InteropServices ;
2019-10-26 20:53:53 +00:00
using System.Text ;
2019-01-28 20:58:47 +00:00
using System.Text.RegularExpressions ;
2019-01-12 22:58:58 +00:00
using System.Threading ;
2019-01-13 19:30:41 +00:00
using System.Threading.Tasks ;
2019-01-28 20:58:47 +00:00
using CommandLine ;
2019-01-13 19:30:41 +00:00
using Emby.Drawing ;
using Emby.Server.Implementations ;
using Emby.Server.Implementations.IO ;
using Emby.Server.Implementations.Networking ;
2019-01-26 19:43:13 +00:00
using Jellyfin.Drawing.Skia ;
2019-01-13 19:30:41 +00:00
using MediaBrowser.Common.Configuration ;
using MediaBrowser.Controller.Drawing ;
using MediaBrowser.Model.Globalization ;
2019-11-24 14:27:58 +00:00
using Microsoft.AspNetCore.Hosting ;
2019-01-13 19:30:41 +00:00
using Microsoft.Extensions.Configuration ;
2019-02-03 16:09:12 +00:00
using Microsoft.Extensions.DependencyInjection ;
2019-11-24 14:27:58 +00:00
using Microsoft.Extensions.DependencyInjection.Extensions ;
2019-01-13 19:30:41 +00:00
using Microsoft.Extensions.Logging ;
2019-10-26 21:58:23 +00:00
using Microsoft.Extensions.Logging.Abstractions ;
2019-01-13 19:30:41 +00:00
using Serilog ;
2020-03-02 23:35:41 +00:00
using Serilog.Events ;
2019-09-11 17:31:35 +00:00
using Serilog.Extensions.Logging ;
2019-03-11 22:07:38 +00:00
using SQLitePCL ;
2019-01-13 19:30:41 +00:00
using ILogger = Microsoft . Extensions . Logging . ILogger ;
namespace Jellyfin.Server
{
2019-08-11 13:11:53 +00:00
/// <summary>
/// Class containing the entry point of the application.
/// </summary>
2019-01-13 19:30:41 +00:00
public static class Program
{
2020-03-05 17:09:33 +00:00
/// <summary>
/// The name of logging configuration file.
/// </summary>
public static readonly string LoggingConfigFile = "logging.json" ;
2019-01-12 22:58:58 +00:00
private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource ( ) ;
private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory ( ) ;
2019-10-26 21:58:23 +00:00
private static ILogger _logger = NullLogger . Instance ;
2019-01-13 19:30:41 +00:00
private static bool _restartOnShutdown ;
2019-08-11 13:11:53 +00:00
/// <summary>
/// The entry point of the application.
/// </summary>
/// <param name="args">The command line arguments passed.</param>
/// <returns><see cref="Task" />.</returns>
2019-02-24 02:16:19 +00:00
public static Task Main ( string [ ] args )
2019-01-13 19:30:41 +00:00
{
2019-01-28 14:51:31 +00:00
// For backwards compatibility.
// Modify any input arguments now which start with single-hyphen to POSIX standard
// double-hyphen to allow parsing by CommandLineParser package.
2019-08-09 21:16:24 +00:00
const string Pattern = @"^(-[^-\s]{2})" ; // Match -xx, not -x, not --xx, not xx
const string Substitution = @"-$1" ; // Prepend with additional single-hyphen
var regex = new Regex ( Pattern ) ;
2019-01-28 14:51:31 +00:00
for ( var i = 0 ; i < args . Length ; i + + )
{
2019-08-09 21:16:24 +00:00
args [ i ] = regex . Replace ( args [ i ] , Substitution ) ;
2019-01-28 14:51:31 +00:00
}
2019-01-28 13:41:37 +00:00
// Parse the command line arguments and either start the app or exit indicating error
2019-02-24 02:16:19 +00:00
return Parser . Default . ParseArguments < StartupOptions > ( args )
. MapResult ( StartApp , _ = > Task . CompletedTask ) ;
2019-01-28 13:41:37 +00:00
}
2019-01-13 19:30:41 +00:00
2019-08-11 13:11:53 +00:00
/// <summary>
/// Shuts down the application.
/// </summary>
internal static void Shutdown ( )
2019-02-13 16:19:55 +00:00
{
if ( ! _tokenSource . IsCancellationRequested )
{
_tokenSource . Cancel ( ) ;
}
}
2019-08-11 13:11:53 +00:00
/// <summary>
/// Restarts the application.
/// </summary>
internal static void Restart ( )
2019-02-13 16:19:55 +00:00
{
_restartOnShutdown = true ;
Shutdown ( ) ;
}
2019-01-28 13:41:37 +00:00
private static async Task StartApp ( StartupOptions options )
{
2019-09-28 22:29:28 +00:00
var stopWatch = new Stopwatch ( ) ;
stopWatch . Start ( ) ;
2019-10-26 21:58:23 +00:00
2019-10-29 16:49:41 +00:00
// Log all uncaught exceptions to std error
2019-10-26 21:58:23 +00:00
static void UnhandledExceptionToConsole ( object sender , UnhandledExceptionEventArgs e ) = >
Console . Error . WriteLine ( "Unhandled Exception\n" + e . ExceptionObject . ToString ( ) ) ;
AppDomain . CurrentDomain . UnhandledException + = UnhandledExceptionToConsole ;
2019-01-18 10:10:45 +00:00
ServerApplicationPaths appPaths = CreateApplicationPaths ( options ) ;
2019-01-13 19:30:41 +00:00
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
Environment . SetEnvironmentVariable ( "JELLYFIN_LOG_DIR" , appPaths . LogDirectoryPath ) ;
2019-02-08 09:13:58 +00:00
2019-06-09 21:51:52 +00:00
IConfiguration appConfig = await CreateConfiguration ( appPaths ) . ConfigureAwait ( false ) ;
2019-02-08 09:13:58 +00:00
2019-02-17 14:33:20 +00:00
CreateLogger ( appConfig , appPaths ) ;
2019-02-08 09:13:58 +00:00
2019-01-13 19:30:41 +00:00
_logger = _loggerFactory . CreateLogger ( "Main" ) ;
2019-10-26 21:58:23 +00:00
// Log uncaught exceptions to the logging instead of std error
AppDomain . CurrentDomain . UnhandledException - = UnhandledExceptionToConsole ;
2019-01-13 19:24:27 +00:00
AppDomain . CurrentDomain . UnhandledException + = ( sender , e )
2019-01-13 19:30:41 +00:00
= > _logger . LogCritical ( ( Exception ) e . ExceptionObject , "Unhandled Exception" ) ;
2019-01-12 22:58:58 +00:00
// Intercept Ctrl+C and Ctrl+Break
Console . CancelKeyPress + = ( sender , e ) = >
{
2019-01-13 00:05:25 +00:00
if ( _tokenSource . IsCancellationRequested )
{
return ; // Already shutting down
}
2019-02-13 16:19:55 +00:00
2019-01-12 22:58:58 +00:00
e . Cancel = true ;
_logger . LogInformation ( "Ctrl+C, shutting down" ) ;
2019-01-13 00:05:25 +00:00
Environment . ExitCode = 128 + 2 ;
2019-01-12 22:58:58 +00:00
Shutdown ( ) ;
} ;
2019-01-12 22:31:45 +00:00
// Register a SIGTERM handler
AppDomain . CurrentDomain . ProcessExit + = ( sender , e ) = >
{
2019-01-12 22:58:58 +00:00
if ( _tokenSource . IsCancellationRequested )
{
return ; // Already shutting down
}
2019-02-13 16:19:55 +00:00
2019-01-12 22:31:45 +00:00
_logger . LogInformation ( "Received a SIGTERM signal, shutting down" ) ;
2019-01-13 00:05:25 +00:00
Environment . ExitCode = 128 + 15 ;
2019-01-12 22:31:45 +00:00
Shutdown ( ) ;
} ;
2019-09-27 21:58:04 +00:00
_logger . LogInformation (
"Jellyfin version: {Version}" ,
2019-10-26 21:58:23 +00:00
Assembly . GetEntryAssembly ( ) ! . GetName ( ) . Version ! . ToString ( 3 ) ) ;
2019-01-13 19:30:41 +00:00
2019-03-07 16:39:40 +00:00
ApplicationHost . LogEnvironmentInfo ( _logger , appPaths ) ;
2019-01-13 19:30:41 +00:00
2019-10-26 20:53:53 +00:00
// Make sure we have all the code pages we can get
// Ref: https://docs.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider.instance?view=netcore-3.0#remarks
Encoding . RegisterProvider ( CodePagesEncodingProvider . Instance ) ;
2019-03-26 18:20:40 +00:00
// Increase the max http request limit
// The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
ServicePointManager . DefaultConnectionLimit = Math . Max ( 96 , ServicePointManager . DefaultConnectionLimit ) ;
2019-06-14 14:32:37 +00:00
// Disable the "Expect: 100-Continue" header by default
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
ServicePointManager . Expect100Continue = false ;
2019-03-11 22:07:38 +00:00
Batteries_V2 . Init ( ) ;
2019-06-28 10:16:51 +00:00
if ( raw . sqlite3_enable_shared_cache ( 1 ) ! = raw . SQLITE_OK )
{
2019-08-09 21:16:24 +00:00
_logger . LogWarning ( "Failed to enable shared cache for SQLite" ) ;
2019-06-28 10:16:51 +00:00
}
2019-01-13 19:30:41 +00:00
2019-08-18 18:01:08 +00:00
var appHost = new CoreAppHost (
2019-01-13 19:30:41 +00:00
appPaths ,
_loggerFactory ,
options ,
2019-06-09 21:51:52 +00:00
new ManagedFileSystem ( _loggerFactory . CreateLogger < ManagedFileSystem > ( ) , appPaths ) ,
2020-01-21 19:26:30 +00:00
GetImageEncoder ( appPaths ) ,
2019-07-07 19:03:26 +00:00
new NetworkManager ( _loggerFactory . CreateLogger < NetworkManager > ( ) ) ,
2019-08-18 18:01:08 +00:00
appConfig ) ;
try
2019-01-13 19:30:41 +00:00
{
2019-11-24 14:27:58 +00:00
ServiceCollection serviceCollection = new ServiceCollection ( ) ;
await appHost . InitAsync ( serviceCollection ) . ConfigureAwait ( false ) ;
var host = CreateWebHostBuilder ( appHost , serviceCollection ) . Build ( ) ;
// A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection.
appHost . ServiceProvider = host . Services ;
appHost . FindParts ( ) ;
2020-03-05 17:09:33 +00:00
Migrations . MigrationRunner . Run ( appHost , _loggerFactory ) ;
2019-11-24 14:27:58 +00:00
try
{
await host . StartAsync ( ) . ConfigureAwait ( false ) ;
}
catch
{
_logger . LogError ( "Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again." ) ;
throw ;
}
2019-01-13 19:30:41 +00:00
2019-03-07 16:39:40 +00:00
await appHost . RunStartupTasksAsync ( ) . ConfigureAwait ( false ) ;
2019-01-25 20:33:58 +00:00
2019-09-28 22:29:28 +00:00
stopWatch . Stop ( ) ;
_logger . LogInformation ( "Startup complete {Time:g}" , stopWatch . Elapsed ) ;
2019-08-18 18:01:08 +00:00
// Block main thread until shutdown
await Task . Delay ( - 1 , _tokenSource . Token ) . ConfigureAwait ( false ) ;
}
catch ( TaskCanceledException )
{
// Don't throw on cancellation
}
catch ( Exception ex )
{
_logger . LogCritical ( ex , "Error while starting server." ) ;
}
finally
{
appHost ? . Dispose ( ) ;
2019-01-13 19:30:41 +00:00
}
if ( _restartOnShutdown )
{
StartNewInstance ( options ) ;
}
}
2019-11-24 14:27:58 +00:00
private static IWebHostBuilder CreateWebHostBuilder ( ApplicationHost appHost , IServiceCollection serviceCollection )
{
return new WebHostBuilder ( )
. UseKestrel ( options = >
{
var addresses = appHost . ServerConfigurationManager
. Configuration
. LocalNetworkAddresses
. Select ( appHost . NormalizeConfiguredLocalAddress )
. Where ( i = > i ! = null )
. ToList ( ) ;
if ( addresses . Any ( ) )
{
foreach ( var address in addresses )
{
2020-01-31 21:23:46 +00:00
_logger . LogInformation ( "Kestrel listening on {IpAddress}" , address ) ;
2019-11-24 14:27:58 +00:00
options . Listen ( address , appHost . HttpPort ) ;
if ( appHost . EnableHttps & & appHost . Certificate ! = null )
{
options . Listen (
address ,
appHost . HttpsPort ,
listenOptions = > listenOptions . UseHttps ( appHost . Certificate ) ) ;
}
}
}
else
{
_logger . LogInformation ( "Kestrel listening on all interfaces" ) ;
options . ListenAnyIP ( appHost . HttpPort ) ;
if ( appHost . EnableHttps & & appHost . Certificate ! = null )
{
options . ListenAnyIP (
appHost . HttpsPort ,
listenOptions = > listenOptions . UseHttps ( appHost . Certificate ) ) ;
}
}
} )
2020-03-02 23:35:41 +00:00
. UseSerilog ( )
2019-11-24 14:27:58 +00:00
. UseContentRoot ( appHost . ContentRoot )
. ConfigureServices ( services = >
{
// Merge the external ServiceCollection into ASP.NET DI
services . TryAdd ( serviceCollection ) ;
} )
. UseStartup < Startup > ( ) ;
}
2019-02-13 15:35:14 +00:00
/// <summary>
/// Create the data, config and log paths from the variety of inputs(command line args,
2019-06-09 21:51:52 +00:00
/// environment variables) or decide on what default to use. For Windows it's %AppPath%
2019-08-11 13:11:53 +00:00
/// for everything else the
2019-08-11 13:17:39 +00:00
/// <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html">XDG approach</a>
2019-08-11 13:11:53 +00:00
/// is followed.
2019-02-13 15:35:14 +00:00
/// </summary>
2019-08-11 13:11:53 +00:00
/// <param name="options">The <see cref="StartupOptions" /> for this instance.</param>
/// <returns><see cref="ServerApplicationPaths" />.</returns>
2019-01-18 10:10:45 +00:00
private static ServerApplicationPaths CreateApplicationPaths ( StartupOptions options )
2019-01-13 19:30:41 +00:00
{
2019-02-13 15:35:14 +00:00
// dataDir
// IF --datadir
2019-03-10 22:24:11 +00:00
// ELSE IF $JELLYFIN_DATA_DIR
2019-02-13 15:35:14 +00:00
// ELSE IF windows, use <%APPDATA%>/jellyfin
// ELSE IF $XDG_DATA_HOME then use $XDG_DATA_HOME/jellyfin
// ELSE use $HOME/.local/share/jellyfin
var dataDir = options . DataDir ;
if ( string . IsNullOrEmpty ( dataDir ) )
2019-01-13 19:30:41 +00:00
{
2019-03-10 22:24:11 +00:00
dataDir = Environment . GetEnvironmentVariable ( "JELLYFIN_DATA_DIR" ) ;
2019-02-13 15:35:14 +00:00
if ( string . IsNullOrEmpty ( dataDir ) )
2019-01-13 19:30:41 +00:00
{
2019-02-09 19:20:39 +00:00
// LocalApplicationData follows the XDG spec on unix machines
2019-06-09 21:51:52 +00:00
dataDir = Path . Combine (
Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ,
"jellyfin" ) ;
2019-01-13 19:30:41 +00:00
}
}
2019-02-13 15:35:14 +00:00
// configDir
// IF --configdir
// ELSE IF $JELLYFIN_CONFIG_DIR
// ELSE IF --datadir, use <datadir>/config (assume portable run)
// ELSE IF <datadir>/config exists, use that
// ELSE IF windows, use <datadir>/config
// ELSE IF $XDG_CONFIG_HOME use $XDG_CONFIG_HOME/jellyfin
// ELSE $HOME/.config/jellyfin
var configDir = options . ConfigDir ;
2019-01-13 19:30:41 +00:00
if ( string . IsNullOrEmpty ( configDir ) )
{
2019-02-13 15:35:14 +00:00
configDir = Environment . GetEnvironmentVariable ( "JELLYFIN_CONFIG_DIR" ) ;
if ( string . IsNullOrEmpty ( configDir ) )
2019-01-13 19:30:41 +00:00
{
2019-06-09 21:51:52 +00:00
if ( options . DataDir ! = null
| | Directory . Exists ( Path . Combine ( dataDir , "config" ) )
| | RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
2019-02-13 15:35:14 +00:00
{
// Hang config folder off already set dataDir
configDir = Path . Combine ( dataDir , "config" ) ;
}
else
{
2019-06-09 21:51:52 +00:00
// $XDG_CONFIG_HOME defines the base directory relative to which
// user specific configuration files should be stored.
2019-02-13 15:35:14 +00:00
configDir = Environment . GetEnvironmentVariable ( "XDG_CONFIG_HOME" ) ;
2019-06-09 21:51:52 +00:00
// If $XDG_CONFIG_HOME is either not set or empty,
// a default equal to $HOME /.config should be used.
2019-02-13 15:35:14 +00:00
if ( string . IsNullOrEmpty ( configDir ) )
{
2019-06-09 21:51:52 +00:00
configDir = Path . Combine (
Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ,
".config" ) ;
2019-02-13 15:35:14 +00:00
}
configDir = Path . Combine ( configDir , "jellyfin" ) ;
}
2019-01-13 19:30:41 +00:00
}
}
2019-02-13 15:35:14 +00:00
// cacheDir
// IF --cachedir
// ELSE IF $JELLYFIN_CACHE_DIR
// ELSE IF windows, use <datadir>/cache
// ELSE IF XDG_CACHE_HOME, use $XDG_CACHE_HOME/jellyfin
// ELSE HOME/.cache/jellyfin
var cacheDir = options . CacheDir ;
2019-01-28 16:52:56 +00:00
if ( string . IsNullOrEmpty ( cacheDir ) )
{
2019-02-13 15:35:14 +00:00
cacheDir = Environment . GetEnvironmentVariable ( "JELLYFIN_CACHE_DIR" ) ;
if ( string . IsNullOrEmpty ( cacheDir ) )
2019-01-28 16:52:56 +00:00
{
2019-02-13 15:35:14 +00:00
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
{
// Hang cache folder off already set dataDir
cacheDir = Path . Combine ( dataDir , "cache" ) ;
}
else
2019-01-28 16:52:56 +00:00
{
2019-06-09 21:51:52 +00:00
// $XDG_CACHE_HOME defines the base directory relative to which
// user specific non-essential data files should be stored.
2019-02-13 15:35:14 +00:00
cacheDir = Environment . GetEnvironmentVariable ( "XDG_CACHE_HOME" ) ;
2019-06-09 21:51:52 +00:00
// If $XDG_CACHE_HOME is either not set or empty,
// a default equal to $HOME/.cache should be used.
2019-02-13 15:35:14 +00:00
if ( string . IsNullOrEmpty ( cacheDir ) )
{
2019-06-09 21:51:52 +00:00
cacheDir = Path . Combine (
Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ,
".cache" ) ;
2019-02-13 15:35:14 +00:00
}
cacheDir = Path . Combine ( cacheDir , "jellyfin" ) ;
2019-01-28 16:52:56 +00:00
}
}
}
2019-03-10 20:17:48 +00:00
// webDir
// IF --webdir
// ELSE IF $JELLYFIN_WEB_DIR
// ELSE use <bindir>/jellyfin-web
var webDir = options . WebDir ;
if ( string . IsNullOrEmpty ( webDir ) )
{
webDir = Environment . GetEnvironmentVariable ( "JELLYFIN_WEB_DIR" ) ;
if ( string . IsNullOrEmpty ( webDir ) )
{
// Use default location under ResourcesPath
2019-09-24 14:22:26 +00:00
webDir = Path . Combine ( AppContext . BaseDirectory , "jellyfin-web" ) ;
2019-03-10 20:17:48 +00:00
}
}
2019-02-13 15:35:14 +00:00
// logDir
// IF --logdir
// ELSE IF $JELLYFIN_LOG_DIR
// ELSE IF --datadir, use <datadir>/log (assume portable run)
// ELSE <datadir>/log
var logDir = options . LogDir ;
2019-01-13 19:30:41 +00:00
if ( string . IsNullOrEmpty ( logDir ) )
{
2019-02-13 15:35:14 +00:00
logDir = Environment . GetEnvironmentVariable ( "JELLYFIN_LOG_DIR" ) ;
if ( string . IsNullOrEmpty ( logDir ) )
2019-01-13 19:30:41 +00:00
{
2019-02-13 15:35:14 +00:00
// Hang log folder off already set dataDir
logDir = Path . Combine ( dataDir , "log" ) ;
2019-01-13 19:30:41 +00:00
}
}
2019-02-13 15:35:14 +00:00
// Ensure the main folders exist before we continue
try
2019-01-18 10:10:45 +00:00
{
2019-03-10 22:30:10 +00:00
Directory . CreateDirectory ( dataDir ) ;
2019-01-18 10:10:45 +00:00
Directory . CreateDirectory ( logDir ) ;
2019-02-13 15:35:14 +00:00
Directory . CreateDirectory ( configDir ) ;
Directory . CreateDirectory ( cacheDir ) ;
}
catch ( IOException ex )
{
Console . Error . WriteLine ( "Error whilst attempting to create folder" ) ;
Console . Error . WriteLine ( ex . ToString ( ) ) ;
Environment . Exit ( 1 ) ;
2019-01-18 10:10:45 +00:00
}
2019-03-10 20:17:48 +00:00
return new ServerApplicationPaths ( dataDir , logDir , configDir , cacheDir , webDir ) ;
2019-01-13 19:30:41 +00:00
}
2019-02-08 09:13:58 +00:00
private static async Task < IConfiguration > CreateConfiguration ( IApplicationPaths appPaths )
2019-01-13 19:30:41 +00:00
{
2019-10-26 21:58:23 +00:00
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json" ;
2020-03-05 17:09:33 +00:00
string configPath = Path . Combine ( appPaths . ConfigurationDirectoryPath , LoggingConfigFile ) ;
2019-01-13 19:30:41 +00:00
2019-02-08 09:13:58 +00:00
if ( ! File . Exists ( configPath ) )
{
// For some reason the csproj name is used instead of the assembly name
2020-01-31 21:23:46 +00:00
await using Stream ? resource = typeof ( Program ) . Assembly . GetManifestResourceStream ( ResourcePath ) ;
if ( resource = = null )
2019-01-13 19:30:41 +00:00
{
2020-01-31 21:23:46 +00:00
throw new InvalidOperationException (
string . Format (
CultureInfo . InvariantCulture ,
"Invalid resource path: '{0}'" ,
ResourcePath ) ) ;
2019-01-13 19:30:41 +00:00
}
2020-01-31 21:23:46 +00:00
await using Stream dst = File . Open ( configPath , FileMode . CreateNew ) ;
await resource . CopyToAsync ( dst ) . ConfigureAwait ( false ) ;
2019-02-08 09:13:58 +00:00
}
2019-02-13 16:19:55 +00:00
2019-02-08 09:13:58 +00:00
return new ConfigurationBuilder ( )
. SetBasePath ( appPaths . ConfigurationDirectoryPath )
2019-10-26 20:53:53 +00:00
. AddInMemoryCollection ( ConfigurationOptions . Configuration )
2020-03-05 17:09:33 +00:00
. AddJsonFile ( LoggingConfigFile , false , true )
2019-02-08 09:13:58 +00:00
. AddEnvironmentVariables ( "JELLYFIN_" )
. Build ( ) ;
}
2019-01-13 19:30:41 +00:00
2019-02-17 14:33:20 +00:00
private static void CreateLogger ( IConfiguration configuration , IApplicationPaths appPaths )
2019-02-08 09:13:58 +00:00
{
try
{
2019-01-13 19:30:41 +00:00
// Serilog.Log is used by SerilogLoggerFactory when no logger is specified
Serilog . Log . Logger = new LoggerConfiguration ( )
. ReadFrom . Configuration ( configuration )
. Enrich . FromLogContext ( )
2020-01-13 18:44:17 +00:00
. Enrich . WithThreadId ( )
2019-01-13 19:30:41 +00:00
. CreateLogger ( ) ;
}
catch ( Exception ex )
{
Serilog . Log . Logger = new LoggerConfiguration ( )
2020-01-16 12:20:01 +00:00
. WriteTo . Console ( outputTemplate : "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}" )
2019-01-13 19:30:41 +00:00
. WriteTo . Async ( x = > x . File (
Path . Combine ( appPaths . LogDirectoryPath , "log_.log" ) ,
rollingInterval : RollingInterval . Day ,
2020-01-27 16:24:29 +00:00
outputTemplate : "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}" ) )
2019-01-13 19:30:41 +00:00
. Enrich . FromLogContext ( )
2020-01-13 18:44:17 +00:00
. Enrich . WithThreadId ( )
2019-01-13 19:30:41 +00:00
. CreateLogger ( ) ;
Serilog . Log . Logger . Fatal ( ex , "Failed to create/read logger configuration" ) ;
}
}
2020-01-21 19:26:30 +00:00
private static IImageEncoder GetImageEncoder ( IApplicationPaths appPaths )
2019-01-13 19:30:41 +00:00
{
try
{
2019-06-09 21:51:52 +00:00
// Test if the native lib is available
SkiaEncoder . TestSkia ( ) ;
return new SkiaEncoder (
_loggerFactory . CreateLogger < SkiaEncoder > ( ) ,
2020-01-21 19:26:30 +00:00
appPaths ) ;
2019-01-13 19:30:41 +00:00
}
catch ( Exception ex )
{
2019-06-09 21:51:52 +00:00
_logger . LogWarning ( ex , "Skia not available. Will fallback to NullIMageEncoder." ) ;
2019-01-13 19:30:41 +00:00
}
return new NullImageEncoder ( ) ;
}
2019-01-28 13:41:37 +00:00
private static void StartNewInstance ( StartupOptions options )
2019-01-13 19:30:41 +00:00
{
_logger . LogInformation ( "Starting new instance" ) ;
2019-10-26 21:58:23 +00:00
var module = options . RestartPath ;
2019-01-13 19:30:41 +00:00
if ( string . IsNullOrWhiteSpace ( module ) )
{
2019-06-09 21:51:52 +00:00
module = Environment . GetCommandLineArgs ( ) [ 0 ] ;
2019-01-13 19:30:41 +00:00
}
string commandLineArgsString ;
2019-01-28 13:41:37 +00:00
if ( options . RestartArgs ! = null )
2019-01-13 19:30:41 +00:00
{
2019-01-28 13:41:37 +00:00
commandLineArgsString = options . RestartArgs ? ? string . Empty ;
2019-01-13 19:30:41 +00:00
}
else
{
2019-01-23 19:08:50 +00:00
commandLineArgsString = string . Join (
2019-06-09 21:51:52 +00:00
' ' ,
2019-01-23 19:08:50 +00:00
Environment . GetCommandLineArgs ( ) . Skip ( 1 ) . Select ( NormalizeCommandLineArgument ) ) ;
2019-01-13 19:30:41 +00:00
}
_logger . LogInformation ( "Executable: {0}" , module ) ;
_logger . LogInformation ( "Arguments: {0}" , commandLineArgsString ) ;
Process . Start ( module , commandLineArgsString ) ;
}
private static string NormalizeCommandLineArgument ( string arg )
{
if ( ! arg . Contains ( " " , StringComparison . OrdinalIgnoreCase ) )
{
return arg ;
}
return "\"" + arg + "\"" ;
}
}
}