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 System.Text.Encodings.Web; using DTLib.Dtsod; using DTLib.Web; using DTLib.Web.Routes; namespace ParadoxSaveParser.WebAPI; public partial class Program { private static readonly IOPath _configPath = "./config.dtsod"; private static Config _config = new(); private static readonly ILogger _loggerRoot = new CompositeLogger( new ConsoleLogger(), new FileLogger("logs", "ParadoxSaveParser.WebAPI")); private static readonly CancellationTokenSource _mainCancel = new(); private static ConcurrentDictionary _saveMetadataStorage = new(); private static JsonSerializerOptions _saveSerializerOptions = new() { WriteIndented = false, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, MaxDepth = 1024, }; public static void Main(string[] args) { Console.InputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8; Console.CursorVisible = false; ContextLogger logger = new ContextLogger(nameof(Main), _loggerRoot); Console.CancelKeyPress += (_, e) => { e.Cancel = true; logger.LogInfo("Ctrl+C Pressed"); _mainCancel.Cancel(); }; try { // config if (!File.Exists(_configPath)) { logger.LogWarn("config file not found."); File.WriteAllText(_configPath, _config.ToString()); logger.LogWarn($"created default at {_configPath}."); } else _config = Config.FromDtsod(new DtsodV23(File.ReadAllText(_configPath))); PrepareLocalFiles(); // http server var router = new SimpleRouter(_loggerRoot); router.DefaultRoute = new ServeFilesRouteHandler("public"); router.MapRoute("/getSaveStatus", HttpMethod.GET, GetSaveStatusHandler); router.MapRoute("/uploadSave/eu4", HttpMethod.POST, UploadSaveHandler); router.MapRoute("/parseSave/eu4", HttpMethod.POST, ParseSaveEU4Handler); var app = new WebApp(_config.BaseUrl, _loggerRoot, router, _mainCancel.Token); app.Run().GetAwaiter().GetResult(); } catch (OperationCanceledException ex) { logger.LogWarn($"catched OperationCanceledException from {ex.Source}"); } catch (Exception ex) { logger.LogError(ex.ToStringDemystified()); } } public static void PrepareLocalFiles() { Directory.Create(PathHelper.DATA_DIR); Directory.Create(PathHelper.SAVES_DIR); foreach (var metaFilePath in System.IO.Directory.GetFiles( PathHelper.SAVES_DIR.Str, "*.meta.json", SearchOption.TopDirectoryOnly)) { using var metaFile = File.OpenRead(metaFilePath); var meta = JsonSerializer.Deserialize(metaFile) ?? throw new NullReferenceException(metaFilePath); if (meta.status != SaveFileProcessingStatus.Done) { _loggerRoot.LogWarn(nameof(PrepareLocalFiles), $"metadata file '{metaFilePath}' status has invalid status {meta.status}"); } if(!_saveMetadataStorage.TryAdd(meta.id, meta)) throw new Exception("Guid collision!"); } } }