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 {}
}