using System; using System.IO; using System.Threading.Tasks; using Autofac.Extensions.DependencyInjection; using JetBrains.Annotations; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Templates; using Serilog.Templates.Themes; using SEnvironment = System.Environment; namespace Kyoo { /// /// Program entrypoint. /// public static class Program { /// /// The path of the json configuration of the application. /// public const string JsonConfigPath = "./settings.json"; /// /// The string representation of the environment used in . /// #if DEBUG private const string Environment = "Development"; #else private const string Environment = "Production"; #endif /// /// Initialize the bootstrap logger to use it anywhere. This is done here so it is called before any method, /// even if the is not used and this binary is used as a dll. /// static Program() { LoggerConfiguration config = new(); _ConfigureLogging(null, config); Log.Logger = config.CreateBootstrapLogger().ForContext(); AppDomain.CurrentDomain.ProcessExit += (_, _) => Log.CloseAndFlush(); } /// /// Main function of the program /// /// Command line arguments public static Task Main(string[] args) { SetupDataDir(args); return StartWithHost(CreateWebHostBuilder(args).Build()); } /// /// Start the given host and log failing exceptions. /// /// The host to start. public static async Task StartWithHost(IHost host) { try { Log.Information("Running as {Name}", System.Environment.UserName); Log.Information("Data directory: {DataDirectory}", System.Environment.CurrentDirectory); await host.RunAsync(); } catch (Exception ex) { Log.Fatal(ex, "Unhandled exception"); } } /// /// Register settings.json, environment variables and command lines arguments as configuration. /// /// The configuration builder to use /// The command line arguments /// The modified configuration builder private static IConfigurationBuilder SetupConfig(IConfigurationBuilder builder, string[] args) { return builder.SetBasePath(System.Environment.CurrentDirectory) .AddJsonFile(Path.Join(AppDomain.CurrentDomain.BaseDirectory, JsonConfigPath), false, true) .AddJsonFile(JsonConfigPath, false, true) .AddEnvironmentVariables() .AddEnvironmentVariables("KYOO_") .AddCommandLine(args); } /// /// Configure the logging. /// /// The host context that contains the configuration /// The logger builder to configure. private static void _ConfigureLogging([CanBeNull] HostBuilderContext context, LoggerConfiguration builder) { if (context != null) { try { builder.ReadFrom.Configuration(context.Configuration, "logging"); } catch (Exception ex) { Log.Fatal(ex, "Could not read serilog configuration"); } } const string template = "[{@t:HH:mm:ss} {@l:u3} {Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1), 15} " + "({@i:0000000000})] {@m}{#if not EndsWith(@m, '\n')}\n{#end}{@x}"; builder .WriteTo.Console(new ExpressionTemplate(template, theme: TemplateTheme.Code)) .WriteTo.Debug() .WriteTo.File( path: "logs/log-.log", formatter: new ExpressionTemplate(template), rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true ) .Enrich.WithThreadId() .Enrich.FromLogContext(); } /// /// Create a a web host /// /// Command line parameters that can be handled by kestrel /// A new web host instance public static IHostBuilder CreateWebHostBuilder(string[] args) { IConfiguration configuration = SetupConfig(new ConfigurationBuilder(), args).Build(); return new HostBuilder() .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .UseContentRoot(AppDomain.CurrentDomain.BaseDirectory) .UseEnvironment(Environment) .ConfigureAppConfiguration(x => SetupConfig(x, args)) .UseSerilog(_ConfigureLogging) .ConfigureServices(x => x.AddRouting()) .ConfigureWebHost(x => x .UseKestrel(options => { options.AddServerHeader = false; }) .UseIIS() .UseIISIntegration() .UseUrls(configuration.GetValue("basics:url")) .UseStartup(host => PluginsStartup.FromWebHost(host, new LoggerFactory().AddSerilog())) ); } /// /// Parse the data directory from environment variables and command line arguments, create it if necessary. /// Set the current directory to said data folder and place a default configuration file if it does not already /// exists. /// /// The command line arguments public static void SetupDataDir(string[] args) { IConfiguration parsed = new ConfigurationBuilder() .AddEnvironmentVariables() .AddEnvironmentVariables("KYOO_") .AddCommandLine(args) .Build(); string path = parsed.GetValue("data_dir"); if (path == null) path = Path.Combine(SEnvironment.GetFolderPath(SEnvironment.SpecialFolder.LocalApplicationData), "Kyoo"); if (!Directory.Exists(path)) Directory.CreateDirectory(path); SEnvironment.CurrentDirectory = path; if (!File.Exists(JsonConfigPath)) File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, JsonConfigPath), JsonConfigPath); } } /// /// An useless class only used to have a logger in the main. /// internal class Application {} }