/// <summary> /// Configure dependency injected services /// </summary> /// <param name="services">The <see cref="IServiceCollection"/> to configure</param> public void ConfigureServices(IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } // needful services.AddSingleton <IApplication>(this); // configure configuration services.UseStandardConfig <UpdatesConfiguration>(configuration); services.UseStandardConfig <DatabaseConfiguration>(configuration); services.UseStandardConfig <GeneralConfiguration>(configuration); services.UseStandardConfig <FileLoggingConfiguration>(configuration); services.UseStandardConfig <ControlPanelConfiguration>(configuration); // enable options which give us config reloading services.AddOptions(); // this is needful for the setup wizard services.AddLogging(); // other stuff needed for for setup wizard and configuration services.AddSingleton <IConsole, IO.Console>(); services.AddSingleton <IDatabaseConnectionFactory, DatabaseConnectionFactory>(); services.AddSingleton <ISetupWizard, SetupWizard>(); services.AddSingleton <IPlatformIdentifier, PlatformIdentifier>(); services.AddSingleton <IAsyncDelayer, AsyncDelayer>(); GeneralConfiguration generalConfiguration; DatabaseConfiguration databaseConfiguration; FileLoggingConfiguration fileLoggingConfiguration; ControlPanelConfiguration controlPanelConfiguration; IPlatformIdentifier platformIdentifier; // temporarily build the service provider in it's current state // do it here so we can run the setup wizard if necessary // also allows us to get some options and other services we need for continued configuration using (var provider = services.BuildServiceProvider()) { // run the wizard if necessary var setupWizard = provider.GetRequiredService <ISetupWizard>(); var applicationLifetime = provider.GetRequiredService <Microsoft.AspNetCore.Hosting.IApplicationLifetime>(); var setupWizardRan = setupWizard.CheckRunWizard(applicationLifetime.ApplicationStopping).GetAwaiter().GetResult(); // load the configuration options we need var generalOptions = provider.GetRequiredService <IOptions <GeneralConfiguration> >(); generalConfiguration = generalOptions.Value; // unless this is set, in which case, we leave if (setupWizardRan && generalConfiguration.SetupWizardMode == SetupWizardMode.Only) { throw new OperationCanceledException("Exiting due to SetupWizardMode configuration!"); // we don't inject a logger in the constuctor to log this because it's not yet configured } var dbOptions = provider.GetRequiredService <IOptions <DatabaseConfiguration> >(); databaseConfiguration = dbOptions.Value; var loggingOptions = provider.GetRequiredService <IOptions <FileLoggingConfiguration> >(); fileLoggingConfiguration = loggingOptions.Value; var controlPanelOptions = provider.GetRequiredService <IOptions <ControlPanelConfiguration> >(); controlPanelConfiguration = controlPanelOptions.Value; platformIdentifier = provider.GetRequiredService <IPlatformIdentifier>(); } // setup file logging via serilog if (!fileLoggingConfiguration.Disable) { services.AddLogging(builder => { // common app data is C:/ProgramData on windows, else /usr/shar var logPath = !String.IsNullOrEmpty(fileLoggingConfiguration.Directory) ? fileLoggingConfiguration.Directory : ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), VersionPrefix, "Logs"); logPath = ioManager.ConcatPath(logPath, "tgs-{Date}.log"); LogEventLevel?ConvertLogLevel(LogLevel logLevel) { switch (logLevel) { case LogLevel.Critical: return(LogEventLevel.Fatal); case LogLevel.Debug: return(LogEventLevel.Debug); case LogLevel.Error: return(LogEventLevel.Error); case LogLevel.Information: return(LogEventLevel.Information); case LogLevel.Trace: return(LogEventLevel.Verbose); case LogLevel.Warning: return(LogEventLevel.Warning); case LogLevel.None: return(null); default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid log level {0}", logLevel)); } } var logEventLevel = ConvertLogLevel(fileLoggingConfiguration.LogLevel); var microsoftEventLevel = ConvertLogLevel(fileLoggingConfiguration.MicrosoftLogLevel); var formatter = new MessageTemplateTextFormatter("{Timestamp:o} {RequestId,13} [{Level:u3}] {SourceContext:l}: {Message} ({EventId:x8}){NewLine}{Exception}", null); var configuration = new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.Async(w => w.RollingFile(formatter, logPath, shared: true, flushToDiskInterval: TimeSpan.FromSeconds(2))); if (logEventLevel.HasValue) { configuration.MinimumLevel.Is(logEventLevel.Value); } if (microsoftEventLevel.HasValue) { configuration.MinimumLevel.Override("Microsoft", microsoftEventLevel.Value); } builder.AddSerilog(configuration.CreateLogger(), true); }); } // configure bearer token validation services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(jwtBearerOptions => { // this line isn't actually run until the first request is made // at that point tokenFactory will be populated jwtBearerOptions.TokenValidationParameters = tokenFactory.ValidationParameters; jwtBearerOptions.Events = new JwtBearerEvents { // Application is our composition root so this monstrosity of a line is okay OnTokenValidated = ctx => ctx.HttpContext.RequestServices.GetRequiredService <IClaimsInjector>().InjectClaimsIntoContext(ctx, ctx.HttpContext.RequestAborted) }; }); // f*****g prevents converting 'sub' to M$ bs // can't be done in the above lambda, that's too late JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // add mvc, configure the json serializer settings services .AddMvc(options => { var dataAnnotationValidator = options.ModelValidatorProviders.Single(validator => validator.GetType().Name == "DataAnnotationsModelValidatorProvider"); options.ModelValidatorProviders.Remove(dataAnnotationValidator); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_1) .AddJsonOptions(options => { options.AllowInputFormatterExceptionMessages = true; options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; options.SerializerSettings.CheckAdditionalContent = true; options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error; options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; options.SerializerSettings.Converters = new[] { new VersionConverter() }; }); if (hostingEnvironment.IsDevelopment()) { string GetDocumentationFilePath(string assemblyLocation) => ioManager.ConcatPath(ioManager.GetDirectoryName(assemblyLocation), String.Concat(ioManager.GetFileNameWithoutExtension(assemblyLocation), ".xml")); var assemblyDocumentationPath = GetDocumentationFilePath(assemblyInformationProvider.Path); var apiDocumentationPath = GetDocumentationFilePath(typeof(ApiHeaders).Assembly.Location); services.AddSwaggerGen(genOptions => SwaggerConfiguration.Configure(genOptions, assemblyDocumentationPath, apiDocumentationPath)); } // enable browser detection services.AddDetectionCore().AddBrowser(); // CORS conditionally enabled later services.AddCors(); void AddTypedContext <TContext>() where TContext : DatabaseContext <TContext> { services.AddDbContext <TContext>(builder => { if (hostingEnvironment.IsDevelopment()) { builder.EnableSensitiveDataLogging(); } }); services.AddScoped <IDatabaseContext>(x => x.GetRequiredService <TContext>()); } // add the correct database context type var dbType = databaseConfiguration.DatabaseType; switch (dbType) { case DatabaseType.MySql: case DatabaseType.MariaDB: AddTypedContext <MySqlDatabaseContext>(); break; case DatabaseType.SqlServer: AddTypedContext <SqlServerDatabaseContext>(); break; default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType)); } // configure other database services services.AddSingleton <IDatabaseContextFactory, DatabaseContextFactory>(); services.AddSingleton <IDatabaseSeeder, DatabaseSeeder>(); // configure security services services.AddScoped <IAuthenticationContextFactory, AuthenticationContextFactory>(); services.AddScoped <IClaimsInjector, ClaimsInjector>(); services.AddSingleton <IIdentityCache, IdentityCache>(); services.AddSingleton <ICryptographySuite, CryptographySuite>(); services.AddSingleton <ITokenFactory, TokenFactory>(); services.AddSingleton <IPasswordHasher <Models.User>, PasswordHasher <Models.User> >(); // configure platform specific services if (platformIdentifier.IsWindows) { if (generalConfiguration.UseBasicWatchdogOnWindows) { services.AddSingleton <IWatchdogFactory, WatchdogFactory>(); } else { services.AddSingleton <IWatchdogFactory, WindowsWatchdogFactory>(); } services.AddSingleton <ISystemIdentityFactory, WindowsSystemIdentityFactory>(); services.AddSingleton <ISymlinkFactory, WindowsSymlinkFactory>(); services.AddSingleton <IByondInstaller, WindowsByondInstaller>(); services.AddSingleton <IPostWriteHandler, WindowsPostWriteHandler>(); services.AddSingleton <WindowsNetworkPromptReaper>(); services.AddSingleton <INetworkPromptReaper>(x => x.GetRequiredService <WindowsNetworkPromptReaper>()); services.AddSingleton <IHostedService>(x => x.GetRequiredService <WindowsNetworkPromptReaper>()); } else { services.AddSingleton <IWatchdogFactory, WatchdogFactory>(); services.AddSingleton <ISystemIdentityFactory, PosixSystemIdentityFactory>(); services.AddSingleton <ISymlinkFactory, PosixSymlinkFactory>(); services.AddSingleton <IByondInstaller, PosixByondInstaller>(); services.AddSingleton <IPostWriteHandler, PosixPostWriteHandler>(); services.AddSingleton <INetworkPromptReaper, PosixNetworkPromptReaper>(); } // configure misc services services.AddSingleton <ISynchronousIOManager, SynchronousIOManager>(); services.AddSingleton <IGitHubClientFactory, GitHubClientFactory>(); services.AddSingleton <IProcessExecutor, ProcessExecutor>(); services.AddSingleton <IByondTopicSender>(new ByondTopicSender { ReceiveTimeout = generalConfiguration.ByondTopicTimeout, SendTimeout = generalConfiguration.ByondTopicTimeout }); // configure component services services.AddSingleton <ICredentialsProvider, CredentialsProvider>(); services.AddSingleton <IProviderFactory, ProviderFactory>(); services.AddSingleton <IChatFactory, ChatFactory>(); services.AddSingleton <IInstanceFactory, InstanceFactory>(); // configure root services services.AddSingleton <InstanceManager>(); services.AddSingleton <IInstanceManager>(x => x.GetRequiredService <InstanceManager>()); services.AddSingleton <IHostedService>(x => x.GetRequiredService <InstanceManager>()); services.AddSingleton <IJobManager, JobManager>(); }
/// <inheritdoc /> public async Task <RenderResult> RenderMap(string mapPath, MapRegion region, string outputDirectory, string postfix, CancellationToken cancellationToken) { if (mapPath == null) { throw new ArgumentNullException(nameof(mapPath)); } if (outputDirectory == null) { throw new ArgumentNullException(nameof(outputDirectory)); } if (postfix == null) { throw new ArgumentNullException(nameof(postfix)); } var mapName = ioManager.GetFileNameWithoutExtension(mapPath); var outFile = ioManager.ConcatPath(outputDirectory, String.Format(CultureInfo.InvariantCulture, "{0}.{1}png", mapName, postfix != null ? String.Concat(postfix, '.') : null)); var args = String.Format(CultureInfo.InvariantCulture, GenerateRenderCommandLine(region), mapPath); await ioManager.CreateDirectory(ioManager.ConcatPath("data", "minimaps"), cancellationToken).ConfigureAwait(false); var output = new StringBuilder(); var errorOutput = new StringBuilder(); Task <int> processTask; using (var P = CreateDMMToolsProcess(output, errorOutput)) { P.StartInfo.Arguments = args; processTask = StartAndWaitForProcessExit(P, cancellationToken); await processTask.ConfigureAwait(false); } var toolOutput = String.Format(CultureInfo.InvariantCulture, "Exit Code: {0}{1}StdOut:{1}{2}{1}StdErr:{1}{3}", processTask.Result, Environment.NewLine, output, errorOutput); bool expectNext = false; string result = null; foreach (var I in output.ToString().Split(' ')) { var text = I.Trim(); if (text == "saving") { expectNext = true; } else if (expectNext && text.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)) { result = text; break; } else { expectNext = false; } } var rresult = new RenderResult { MapRegion = region, CommandLine = args, ToolOutput = toolOutput, InputPath = mapPath }; if (result != null) { await ioManager.MoveFile(result, outFile, cancellationToken).ConfigureAwait(false); rresult.OutputPath = ioManager.ResolvePath(outFile); } return(rresult); }