using System.IO.Compression; using System.Linq; using System.Text.Encodings.Web; using System.Text.Json.Serialization; using ParadoxSaveParser.WebAPI.SaveDataFilters; namespace ParadoxSaveParser.WebAPI.BackgroundTasks; public class SaveParsingOperation { public readonly long OperationId; private readonly SaveFileMetadata _meta; private readonly ISaveDataFilter _filter; 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, ISaveDataFilter filter, ContextLogger logger, CancellationToken cancelToken) { OperationId = operationId; _meta = meta; _filter = filter; _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, _filter.SearchExpression); var result = parser.Parse(); _filter.Apply(result); _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(); } }