ParadoxSaveParser/ParadoxSaveParser.WebAPI/BackgroundTasks/SaveParsingOperation.cs

102 lines
3.6 KiB
C#

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<string, object> 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);
}
}