using System.IO.Compression; using System.Linq; using System.Net; namespace ParadoxSaveParser.WebAPI; public partial class Program { private static async Task UploadSaveHandler(HttpListenerContext ctx) { string? contentType = ctx.Request.Headers.GetValues("Content-Type")?.FirstOrDefault(); if (contentType != "application/octet-stream") return await ReturnResponseError(ctx, new ErrorMessage( HttpStatusCode.BadRequest, $"Invalid request Content-Type: '{contentType}'")); string saveId = Guid.NewGuid().ToString(); var metaFilePath = PathHelper.GetMetaFilePath(saveId); if (File.Exists(metaFilePath)) return await ReturnResponseError(ctx, new ErrorMessage( HttpStatusCode.InternalServerError, $"Guid collision! file' {metaFilePath}' already exists.")); var meta = new SaveFileMetadata { id = saveId, game = Game.EU4, status = SaveFileProcessingStatus.Initialized }; if (!_saveMetadataStorage.TryAdd(saveId, meta)) return await ReturnResponseError(ctx, new ErrorMessage( HttpStatusCode.InternalServerError, $"Guid collision! Can't create metadata with id {saveId}")); meta.status = SaveFileProcessingStatus.Uploading; var saveFilePath = PathHelper.GetSaveFilePath(meta.id); await using var saveFile = File.OpenWrite(saveFilePath); await using var remoteStream = ctx.Request.InputStream; await remoteStream.CopyToAsync(saveFile, _mainCancel.Token); meta.status = SaveFileProcessingStatus.Uploaded; return await ReturnResponseString(ctx, saveId); } private static ValueOrError GetMetaFromRequestId(HttpListenerContext ctx, string requestParamName) { var idOrError = GetRequestQueryValue(ctx, requestParamName); if (idOrError.HasError) return idOrError.Error!; if (!_saveMetadataStorage.TryGetValue(idOrError.Value!, out var meta)) return new ErrorMessage(HttpStatusCode.InternalServerError, $"Save with id {idOrError.Value} not found"); return meta; } private static async Task GetSaveStatusHandler(HttpListenerContext ctx) { var metaOrError = GetMetaFromRequestId(ctx, "id"); if (metaOrError.HasError) return await ReturnResponseError(ctx, metaOrError.Error!); return await ReturnResponseJson(ctx, metaOrError.Value!); } private static async Task ParseSaveEU4Handler(HttpListenerContext ctx) { var metaOrError = GetMetaFromRequestId(ctx, "id"); if (metaOrError.HasError) return await ReturnResponseError(ctx, metaOrError.Error!); var meta = metaOrError.Value!; var searchQueryOrError = GetRequestQueryValue(ctx, "search"); if (searchQueryOrError.HasError) return await ReturnResponseError(ctx, searchQueryOrError.Error!); string searchQuery = searchQueryOrError.Value!; try { string extractedGamestatePath = PathHelper.GetSaveFilePath(meta.id) + ".gamestate"; using (var zipArchive = ZipFile.Open(PathHelper.GetSaveFilePath(meta.id).Str, ZipArchiveMode.Read)) { var zipEntry = zipArchive.Entries.FirstOrDefault(e => e.Name == "gamestate"); if (zipEntry is null) return await ReturnResponseError(ctx, new ErrorMessage( HttpStatusCode.BadRequest, "Invalid save format: no 'gamestate' file found")); zipEntry.ExtractToFile(extractedGamestatePath, true); } var gamestateStream = File.OpenRead(extractedGamestatePath); meta.status = SaveFileProcessingStatus.Parsing; var se = SearchExpressionCompiler.Compile(searchQuery); var parser = new SaveParserEU4(gamestateStream, se); var result = parser.Parse(); meta.status = SaveFileProcessingStatus.SavingResults; var resultFilePath = PathHelper.GetParsedSaveFilePath(meta.id); await using var resultFile = File.OpenWrite(resultFilePath); await JsonSerializer.SerializeAsync(resultFile, result, _saveSerializerOptions, _mainCancel.Token); meta.status = SaveFileProcessingStatus.Done; meta.SaveToFile(); } catch (Exception ex) { string errorMesage = ex.ToStringDemystified(); _loggerRoot.LogWarn(nameof(ParseSaveEU4Handler), errorMesage); return await ReturnResponseError(ctx, new ErrorMessage(HttpStatusCode.BadRequest, errorMesage)); } finally { GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); } return await ReturnResponseJson(ctx, meta); } }