using System.IO.Compression; using System.Linq; using System.Text.Encodings.Web; using System.Text.Json.Serialization; namespace ParadoxSaveParser.WebAPI.BackgroundTasks; public class SaveParsingOperation { public readonly long OperationId; public readonly SaveFileMetadata Meta; public readonly ISearchExpression SearchQuery; private readonly ContextLogger _logger; private readonly CancellationToken _cancelToken; private static readonly JsonSerializerOptions _saveSerializerOptions = new() { WriteIndented = false, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, MaxDepth = 1024, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; public SaveParsingOperation(long operationId, SaveFileMetadata meta, ISearchExpression searchQuery, ContextLogger logger, CancellationToken cancelToken) { OperationId = operationId; Meta = meta; SearchQuery = searchQuery; _logger = logger; _cancelToken = cancelToken; } public async void StartAsync() { try { _logger.LogInfo($"Starting background parsing operation of {Meta.game} save {Meta.id}"); switch (Meta.game) { case Game.EU4: await ParseSaveEU4(); break; default: throw new ArgumentOutOfRangeException(Meta.game.ToString()); } _logger.LogInfo($"Finished parsing operation of {Meta.game} save {Meta.id}"); } catch (Exception ex) { string errorMesage = ex.ToStringDemystified(); _logger.LogError(errorMesage); Meta.errorMessage = errorMesage; } finally { GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); } } private async Task ParseSaveEU4() { // wait for save file closing await Task.Delay(200, _cancelToken); 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) throw new Exception("Invalid save format: no 'gamestate' file found"); zipEntry.ExtractToFile(extractedGamestatePath, true); } var gamestateStream = File.OpenRead(extractedGamestatePath); Meta.status = SaveFileProcessingStatus.Parsing; var parser = new SaveParserEU4(gamestateStream, SearchQuery); 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, _cancelToken); Meta.status = SaveFileProcessingStatus.Done; Meta.SaveToFile(); } }