using System.IO; using System.IO.Compression; using System.Linq; using System.Text.Encodings.Web; using System.Text.Json.Serialization; using ParadoxSaveParser.WebAPI.Database; 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 _ct; private static readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = false, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, MaxDepth = 1024, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; public SaveParsingOperation(long operationId, SaveFileMetadata meta, ISaveDataFilter filter, ContextLogger logger, CancellationToken ct) { OperationId = operationId; _meta = meta; _filter = filter; _logger = logger; _ct = ct; } 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); try { await Program.DB.SetMetadataError(_meta, errorMesage); } catch (Exception ex2) { _logger.LogError(ex2); } } finally { GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); } } private async Task ParseSaveEU4() { // wait for save file closing await Task.Delay(200, _ct); Dictionary parsedData; await using (var gamestateStream = PathHelper.CreateTempFile()) { using (var zipArchive = ZipFile.Open(_meta.GetSaveFilePath().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"); await zipEntry.Open().CopyToAsync(gamestateStream, _ct); gamestateStream.Seek(0, SeekOrigin.Begin); } await Program.DB.UpdateMetadataStatus(_meta, SaveFileProcessingStatus.Parsing); var parser = new SaveParserEU4(gamestateStream, _filter.SearchExpression); parsedData = parser.Parse(); } _filter.Apply(parsedData); await Program.DB.UpdateMetadataStatus(_meta, SaveFileProcessingStatus.SavingResults); var resultFilePath = _meta.GetParsedDataPath(); await using (var resultFile = File.OpenWrite(resultFilePath)) await JsonSerializer.SerializeAsync(resultFile, parsedData, _jsonOptions, _ct); await Program.DB.UpdateMetadataStatus(_meta, SaveFileProcessingStatus.Done); } }