using IW4MAdmin.Application.API.Master; using IW4MAdmin.Application.EventParsers; using IW4MAdmin.Application.Factories; using IW4MAdmin.Application.Meta; using IW4MAdmin.Application.Migration; using IW4MAdmin.Application.Misc; using Microsoft.Extensions.DependencyInjection; using RestEase; using SharedLibraryCore; using SharedLibraryCore.Configuration; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Dtos.Meta.Responses; using SharedLibraryCore.Exceptions; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; using SharedLibraryCore.QueryHelper; using SharedLibraryCore.Repositories; using SharedLibraryCore.Services; using Stats.Dtos; using System; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Data.Abstractions; using Data.Helpers; using Integrations.Source.Extensions; using IW4MAdmin.Application.Extensions; using IW4MAdmin.Application.Localization; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; using IW4MAdmin.Plugins.Stats.Client.Abstractions; using IW4MAdmin.Plugins.Stats.Client; using Stats.Client.Abstractions; using Stats.Client; using Stats.Helpers; namespace IW4MAdmin.Application { public class Program { public static BuildNumber Version { get; private set; } = BuildNumber.Parse(Utilities.GetVersionAsString()); public static ApplicationManager ServerManager; private static Task ApplicationTask; private static ServiceProvider serviceProvider; /// /// entrypoint of the application /// /// public static async Task Main(string[] args) { AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory); Console.OutputEncoding = Encoding.UTF8; Console.ForegroundColor = ConsoleColor.Gray; Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey); Console.WriteLine("====================================================="); Console.WriteLine(" IW4MAdmin"); Console.WriteLine(" by RaidMax "); Console.WriteLine($" Version {Utilities.GetVersionAsString()}"); Console.WriteLine("====================================================="); await LaunchAsync(args); } /// /// event callback executed when the control + c combination is detected /// gracefully stops the server manager and waits for all tasks to finish /// /// /// private static async void OnCancelKey(object sender, ConsoleCancelEventArgs e) { ServerManager?.Stop(); if (ApplicationTask != null) { await ApplicationTask; } } /// /// task that initializes application and starts the application monitoring and runtime tasks /// /// private static async Task LaunchAsync(string[] args) { restart: ITranslationLookup translationLookup = null; var logger = BuildDefaultLogger(new ApplicationConfiguration()); Utilities.DefaultLogger = logger; IServiceCollection services = null; logger.LogInformation("Begin IW4MAdmin startup. Version is {version} {@args}", Version, args); try { // do any needed housekeeping file/folder migrations ConfigurationMigration.MoveConfigFolder10518(null); ConfigurationMigration.CheckDirectories(); ConfigurationMigration.RemoveObsoletePlugins20210322(); logger.LogDebug("Configuring services..."); services = ConfigureServices(args); serviceProvider = services.BuildServiceProvider(); var versionChecker = serviceProvider.GetRequiredService(); ServerManager = (ApplicationManager) serviceProvider.GetRequiredService(); translationLookup = serviceProvider.GetRequiredService(); await versionChecker.CheckVersion(); await ServerManager.Init(); } catch (Exception e) { string failMessage = translationLookup == null ? "Failed to initialize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"]; string exitMessage = translationLookup == null ? "Press enter to exit..." : translationLookup["MANAGER_EXIT"]; logger.LogCritical(e, "Failed to initialize IW4MAdmin"); Console.WriteLine(failMessage); while (e.InnerException != null) { e = e.InnerException; } if (e is ConfigurationException configException) { if (translationLookup != null) { Console.WriteLine(translationLookup[configException.Message] .FormatExt(configException.ConfigurationFileName)); } foreach (string error in configException.Errors) { Console.WriteLine(error); } } else { Console.WriteLine(e.Message); } Console.WriteLine(exitMessage); await Console.In.ReadAsync(new char[1], 0, 1); return; } try { ApplicationTask = RunApplicationTasksAsync(logger, services); await ApplicationTask; } catch (Exception e) { logger.LogCritical(e, "Failed to launch IW4MAdmin"); string failMessage = translationLookup == null ? "Failed to launch IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"]; Console.WriteLine($"{failMessage}: {e.GetExceptionInfo()}"); } if (ServerManager.IsRestartRequested) { goto restart; } serviceProvider.Dispose(); } /// /// runs the core application tasks /// /// private static async Task RunApplicationTasksAsync(ILogger logger, IServiceCollection services) { var webfrontTask = ServerManager.GetApplicationSettings().Configuration().EnableWebFront ? WebfrontCore.Program.Init(ServerManager, serviceProvider, services, ServerManager.CancellationToken) : Task.CompletedTask; var collectionService = serviceProvider.GetRequiredService(); // we want to run this one on a manual thread instead of letting the thread pool handle it, // because we can't exit early from waiting on console input, and it prevents us from restarting var inputThread = new Thread(async () => await ReadConsoleInput(logger)); inputThread.Start(); var tasks = new[] { ServerManager.Start(), webfrontTask, serviceProvider.GetRequiredService() .RunUploadStatus(ServerManager.CancellationToken), collectionService.BeginCollectionAsync(cancellationToken: ServerManager.CancellationToken) }; logger.LogDebug("Starting webfront and input tasks"); await Task.WhenAll(tasks); logger.LogInformation("Shutdown completed successfully"); Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_SHUTDOWN_SUCCESS"]); } /// /// reads input from the console and executes entered commands on the default server /// /// private static async Task ReadConsoleInput(ILogger logger) { if (Console.IsInputRedirected) { logger.LogInformation("Disabling console input as it has been redirected"); return; } string lastCommand; var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]); try { while (!ServerManager.CancellationToken.IsCancellationRequested) { lastCommand = await Console.In.ReadLineAsync(); if (lastCommand?.Length > 0) { if (lastCommand?.Length > 0) { GameEvent E = new GameEvent() { Type = GameEvent.EventType.Command, Data = lastCommand, Origin = Origin, Owner = ServerManager.Servers[0] }; ServerManager.AddEvent(E); await E.WaitAsync(Utilities.DefaultCommandTimeout, ServerManager.CancellationToken); Console.Write('>'); } } } } catch (OperationCanceledException) { } } private static IServiceCollection HandlePluginRegistration(ApplicationConfiguration appConfig, IServiceCollection serviceCollection, IMasterApi masterApi) { var defaultLogger = BuildDefaultLogger(appConfig); var pluginServiceProvider = new ServiceCollection() .AddBaseLogger(appConfig) .AddSingleton(appConfig) .AddSingleton(masterApi) .AddSingleton() .AddSingleton() .BuildServiceProvider(); var pluginImporter = pluginServiceProvider.GetRequiredService(); // we need to register the rest client with regular collection serviceCollection.AddSingleton(masterApi); // register the native commands foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes() .Concat(typeof(Program).Assembly.GetTypes().Where(type => type.Namespace == "IW4MAdmin.Application.Commands")) .Where(_command => _command.BaseType == typeof(Command))) { defaultLogger.LogDebug("Registered native command type {name}", commandType.Name); serviceCollection.AddSingleton(typeof(IManagerCommand), commandType); } // register the plugin implementations var (plugins, commands, configurations) = pluginImporter.DiscoverAssemblyPluginImplementations(); foreach (var pluginType in plugins) { defaultLogger.LogDebug("Registered plugin type {name}", pluginType.FullName); serviceCollection.AddSingleton(typeof(IPlugin), pluginType); } // register the plugin commands foreach (var commandType in commands) { defaultLogger.LogDebug("Registered plugin command type {name}", commandType.FullName); serviceCollection.AddSingleton(typeof(IManagerCommand), commandType); } foreach (var configurationType in configurations) { defaultLogger.LogDebug("Registered plugin config type {name}", configurationType.Name); var configInstance = (IBaseConfiguration) Activator.CreateInstance(configurationType); var handlerType = typeof(BaseConfigurationHandler<>).MakeGenericType(configurationType); var handlerInstance = Activator.CreateInstance(handlerType, new[] {configInstance.Name()}); var genericInterfaceType = typeof(IConfigurationHandler<>).MakeGenericType(configurationType); serviceCollection.AddSingleton(genericInterfaceType, handlerInstance); } // register any script plugins foreach (var scriptPlugin in pluginImporter.DiscoverScriptPlugins()) { serviceCollection.AddSingleton(scriptPlugin); } // register any eventable types foreach (var assemblyType in typeof(Program).Assembly.GetTypes() .Where(_asmType => typeof(IRegisterEvent).IsAssignableFrom(_asmType)) .Union(plugins.SelectMany(_asm => _asm.Assembly.GetTypes()) .Distinct() .Where(_asmType => typeof(IRegisterEvent).IsAssignableFrom(_asmType)))) { var instance = Activator.CreateInstance(assemblyType) as IRegisterEvent; serviceCollection.AddSingleton(instance); } return serviceCollection; } /// /// Configures the dependency injection services /// private static IServiceCollection ConfigureServices(string[] args) { // setup the static resources (config/master api/translations) var serviceCollection = new ServiceCollection(); var appConfigHandler = new BaseConfigurationHandler("IW4MAdminSettings"); var defaultConfigHandler = new BaseConfigurationHandler("DefaultSettings"); var defaultConfig = defaultConfigHandler.Configuration(); var appConfig = appConfigHandler.Configuration(); var masterUri = Utilities.IsDevelopment ? new Uri("http://127.0.0.1:8080") : appConfig?.MasterUrl ?? new ApplicationConfiguration().MasterUrl; var masterRestClient = RestClient.For(masterUri); var translationLookup = Configure.Initialize(Utilities.DefaultLogger, masterRestClient, appConfig); if (appConfig == null) { appConfig = (ApplicationConfiguration) new ApplicationConfiguration().Generate(); appConfigHandler.Set(appConfig); appConfigHandler.Save(); } // register override level names foreach (var (key, value) in appConfig.OverridePermissionLevelNames) { if (!Utilities.PermissionLevelOverrides.ContainsKey(key)) { Utilities.PermissionLevelOverrides.Add(key, value); } } // build the dependency list HandlePluginRegistration(appConfig, serviceCollection, masterRestClient); serviceCollection .AddBaseLogger(appConfig) .AddSingleton(defaultConfig) .AddSingleton(_serviceProvider => serviceCollection) .AddSingleton, BaseConfigurationHandler>() .AddSingleton((IConfigurationHandler) appConfigHandler) .AddSingleton( new BaseConfigurationHandler("CommandConfiguration") as IConfigurationHandler) .AddSingleton(appConfig) .AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService>() .Configuration() ?? new CommandConfiguration()) .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton, ClientService>() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton, ReceivedPenaltyResourceQueryHelper>() .AddSingleton, AdministeredPenaltyResourceQueryHelper>() .AddSingleton, UpdatedAliasResourceQueryHelper>() .AddSingleton, ChatResourceQueryHelper>() .AddSingleton, ConnectionsResourceQueryHelper>() .AddTransient() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(typeof(ILookupCache<>), typeof(LookupCache<>)) .AddSingleton(typeof(IDataValueCache<,>), typeof(DataValueCache<,>)) .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(translationLookup) .AddDatabaseContextOptions(appConfig); if (args.Contains("serialevents")) { serviceCollection.AddSingleton(); } else { serviceCollection.AddSingleton(); } serviceCollection.AddSource(); return serviceCollection; } private static ILogger BuildDefaultLogger(ApplicationConfiguration appConfig) { var collection = new ServiceCollection() .AddBaseLogger(appConfig) .BuildServiceProvider(); return collection.GetRequiredService>(); } } }