/// <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>();
        }
Пример #2
0
        /// <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);
        }