global using System; global using System.Collections.Generic; global using System.Text; global using System.Text.Json; global using System.Threading; global using System.Threading.Tasks; global using DTLib.Demystifier; global using DTLib.Filesystem; global using DTLib.Logging; global using ParadoxSaveParser.Lib; global using Directory = DTLib.Filesystem.Directory; global using File = DTLib.Filesystem.File; global using Path = DTLib.Filesystem.Path; using System.Collections.Concurrent; using System.IO; using DTLib.Console; using DTLib.Dtsod; using DTLib.Web; using DTLib.Web.Routes; using ParadoxSaveParser.WebAPI.BackgroundTasks; using ParadoxSaveParser.WebAPI.Routes; using ParadoxSaveParser.WebAPI.SaveDataFilters; namespace ParadoxSaveParser.WebAPI; public static class Program { private static readonly IOPath _configPath = "./config.dtsod"; private static Config _config = new(); private static bool IsDebug = true; private static readonly CancellationTokenSource _mainCancel = new(); internal static readonly ConcurrentDictionary _saveMetadataStorage = new(); public static void Main(string[] args) { Console.InputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8; Console.CursorVisible = false; #if DEBUG IsDebug = true; #endif new LaunchArgumentParser( new LaunchArgument(["-d", "--debug"], "enables debug log output to console", () => IsDebug = true) ).AllowNoArguments().ParseAndHandle(args); var loggerRoot = new CompositeLogger( new ConsoleLogger { DebugLogEnabled = IsDebug }, new FileLogger("logs", "ParadoxSaveParser.WebAPI") { DebugLogEnabled = true }); var loggerMain = new ContextLogger(nameof(Main), loggerRoot); loggerMain.LogDebug("Debug log is enabled"); Console.CancelKeyPress += (_, e) => { e.Cancel = true; loggerMain.LogInfo("Ctrl+C Pressed"); _mainCancel.Cancel(); }; try { // config if (!File.Exists(_configPath)) { loggerMain.LogWarn("config file not found."); File.WriteAllText(_configPath, _config.ToString()); loggerMain.LogWarn($"created default at {_configPath}."); } else { _config = Config.FromDtsod(new DtsodV23(File.ReadAllText(_configPath))); } PathHelper.CreateProgramDirectories(); var metaFiles = System.IO.Directory.GetFiles( PathHelper.SAVES_DIR.Str, "*.meta.json", SearchOption.TopDirectoryOnly); foreach (string metaFilePath in metaFiles) { using var metaFile = File.OpenRead(metaFilePath); var meta = JsonSerializer.Deserialize(metaFile) ?? throw new NullReferenceException(metaFilePath); if (meta.status != SaveFileProcessingStatus.Done) loggerMain.LogWarn(nameof(Main), $"metadata file '{metaFilePath}' status has invalid status {meta.status}"); if (!_saveMetadataStorage.TryAdd(meta.id, meta)) throw new Exception("Guid collision!"); } var bgJobManager = new BackgroundJobManager(loggerRoot); var saveFilters = new Dictionary { { Game.EU4, new SaveDataFilterEU4() }, }; // http server var router = new SimpleRouter(loggerRoot); router.DefaultRoute = new ServeFilesRouteHandler("public"); router.MapRoute("/getSaveStatus", HttpMethod.GET, new GetSaveStatusHandler(_mainCancel.Token)); router.MapRoute("/uploadSave/eu4", HttpMethod.POST, new UploadSaveHandler(_mainCancel.Token, bgJobManager, saveFilters)); router.MapRoute("/getSaveData", HttpMethod.GET, new GetSaveDataHandler(_mainCancel.Token)); var app = new WebApp(_config.BaseUrl, loggerRoot, router, _mainCancel.Token); app.Run().GetAwaiter().GetResult(); } catch (OperationCanceledException ex) { loggerMain.LogWarn($"catched OperationCanceledException from {ex.Source}"); } catch (Exception ex) { loggerMain.LogError(ex.ToStringDemystified()); } } }