From e9c7c8f5c16df017b569548bae7c2038ab0d0bb2 Mon Sep 17 00:00:00 2001 From: Timerix Date: Sat, 5 Apr 2025 05:59:22 +0500 Subject: [PATCH] code reformat and cleanup --- .../ParadoxSaveParser.Lib.Tests.csproj | 4 +- .../SearchExpressionTests.cs | 14 +- ParadoxSaveParser.Lib/BufferedEnumerator.cs | 14 +- ParadoxSaveParser.Lib/SaveParserEU4.cs | 547 +++++++++--------- ParadoxSaveParser.Lib/SearchExpression.cs | 60 +- ParadoxSaveParser.WebAPI/Config.cs | 12 +- .../ParadoxSaveParser.WebAPI.csproj | 4 +- ParadoxSaveParser.WebAPI/PathHelper.cs | 3 + .../Program.HttpHelpers.cs | 24 +- ParadoxSaveParser.WebAPI/SaveFileMetadata.cs | 19 +- 10 files changed, 357 insertions(+), 344 deletions(-) diff --git a/ParadoxSaveParser.Lib.Tests/ParadoxSaveParser.Lib.Tests.csproj b/ParadoxSaveParser.Lib.Tests/ParadoxSaveParser.Lib.Tests.csproj index 79e7bca..b167bad 100644 --- a/ParadoxSaveParser.Lib.Tests/ParadoxSaveParser.Lib.Tests.csproj +++ b/ParadoxSaveParser.Lib.Tests/ParadoxSaveParser.Lib.Tests.csproj @@ -11,7 +11,7 @@ - + @@ -23,7 +23,7 @@ - + diff --git a/ParadoxSaveParser.Lib.Tests/SearchExpressionTests.cs b/ParadoxSaveParser.Lib.Tests/SearchExpressionTests.cs index a2c0180..12453b3 100644 --- a/ParadoxSaveParser.Lib.Tests/SearchExpressionTests.cs +++ b/ParadoxSaveParser.Lib.Tests/SearchExpressionTests.cs @@ -8,24 +8,24 @@ namespace ParadoxSaveParser.Lib.Tests; [TestOf(typeof(ISearchExpression))] public class SearchExpressionTests { - byte[] _smallSaveData; - [SetUp] public void Setup() { _smallSaveData = "EU4txt a={ b={ c=0 d=1 e=2 } f=3 }".ToBytes(); } - - private static JsonSerializerOptions _smallSaveSerializerOptions = new() + private byte[] _smallSaveData; + + + private static readonly JsonSerializerOptions _smallSaveSerializerOptions = new() { WriteIndented = false, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - MaxDepth = 1024, + MaxDepth = 1024 }; - internal static string JsonToPdx(string json) => - json.Substring(1, json.Length - 2) + internal static string JsonToPdx(string json) + => json.Substring(1, json.Length - 2) .Replace(",", " ").Replace("{", "{ ").Replace("}", " }") .Replace("\"", "").Replace("[", "").Replace("]", "").Replace(":", "="); diff --git a/ParadoxSaveParser.Lib/BufferedEnumerator.cs b/ParadoxSaveParser.Lib/BufferedEnumerator.cs index 7d244aa..2bca57b 100644 --- a/ParadoxSaveParser.Lib/BufferedEnumerator.cs +++ b/ParadoxSaveParser.Lib/BufferedEnumerator.cs @@ -39,21 +39,21 @@ namespace ParadoxSaveParser.Lib; /// public class BufferedEnumerator : IEnumerator> { - private IEnumerator _enumerator; - private int _bufferSize; - LinkedList _llist = new(); + private readonly int _bufferSize; private LinkedListNode? _currentNode; private int _currentNodeIndex = -1; - + private readonly IEnumerator _enumerator; + private readonly LinkedList _llist = new(); + public BufferedEnumerator(IEnumerator enumerator, int bufferSize) { _enumerator = enumerator; _bufferSize = bufferSize; } - + public bool MoveNext() { - if(_currentNodeIndex >= _bufferSize / 2) + if (_currentNodeIndex >= _bufferSize / 2) _llist.RemoveFirst(); while (_llist.Count < _bufferSize && _enumerator.MoveNext()) @@ -62,7 +62,7 @@ public class BufferedEnumerator : IEnumerator> } if (_llist.Count == 0) return false; - + _currentNodeIndex++; _currentNode = _currentNode is null ? _llist.First : _currentNode.Next; return _currentNode is not null; diff --git a/ParadoxSaveParser.Lib/SaveParserEU4.cs b/ParadoxSaveParser.Lib/SaveParserEU4.cs index e1e0c13..ed82fc3 100644 --- a/ParadoxSaveParser.Lib/SaveParserEU4.cs +++ b/ParadoxSaveParser.Lib/SaveParserEU4.cs @@ -6,17 +6,21 @@ global using System.Text; namespace ParadoxSaveParser.Lib; /// -/// Sequential parser that doesn't cache anything. +/// Sequential parser that doesn't cache anything. /// public class SaveParserEU4 { protected Stream _saveFile; - private BufferedEnumerator _tokens; private ISearchExpression? _searchExprCurrent; - - /// Uncompressed stream of gamestate file which can be extracted from save archive - /// Parsing whole save takes 10 seconds on mid pc and takes 1GB of RAM, - /// so you should specify what exactly you want to get from save file + private readonly BufferedEnumerator _tokens; + + /// + /// Uncompressed stream of gamestate file which can be extracted from save archive + /// + /// + /// Parsing whole save takes 10 seconds on mid pc and takes 1GB of RAM, + /// so you should specify what exactly you want to get from save file + /// public SaveParserEU4(Stream savefile, ISearchExpression? query) { _tokens = new BufferedEnumerator(LexTextSave(), 5); @@ -24,13 +28,279 @@ public class SaveParserEU4 _searchExprCurrent = query; } + protected IEnumerator LexTextSave() + { + string expectedHeader = "EU4txt"; + byte[] headBytes = new byte[expectedHeader.Length]; + _saveFile.ReadExactly(headBytes); + string headStr = Encoding.UTF8.GetString(headBytes); + if (headStr != expectedHeader) + throw new Exception($"Invalid gamestate header. Expected '{expectedHeader}', got '{headStr}'."); + + StringBuilder str = new(); + int line = 2; + int column = 0; + bool isQuoteOpen = false; + bool isStrInQuotes = false; + Token strToken = new() + { + type = TokenType.Invalid, + column = -1, + line = -1 + }; + + bool TryCompleteStringToken() + { + if (isQuoteOpen) + return false; + + // strings in quotes may be empty + if (!isStrInQuotes && (str.Length <= 0 || str[0] == '#')) + return false; + + strToken = new Token + { + type = TokenType.StringOrNumber, + column = (short)(column - str.Length), + line = line, + value = str.ToString() + }; + str.Clear(); + isStrInQuotes = false; + return true; + } + + while (_saveFile.CanRead) + { + int c = _saveFile.ReadByte(); + column++; + switch (c) + { + case -1: + if (TryCompleteStringToken()) + yield return strToken; + yield break; + case '\"': + isQuoteOpen = !isQuoteOpen; + isStrInQuotes = true; + break; + case ' ': + case '\t': + case '\r': + if (TryCompleteStringToken()) + yield return strToken; + break; + case '\n': + if (TryCompleteStringToken()) + yield return strToken; + line++; + column = 0; + break; + case '=': + if (TryCompleteStringToken()) + yield return strToken; + yield return new Token + { + type = TokenType.Equals, + line = line, column = (short)column + }; + break; + case '{': + if (TryCompleteStringToken()) + yield return strToken; + yield return new Token + { + type = TokenType.BracketOpen, + line = line, column = (short)column + }; + break; + case '}': + if (TryCompleteStringToken()) + yield return strToken; + yield return new Token + { + type = TokenType.BracketClose, + line = line, column = (short)column + }; + break; + default: + // Skip control characters, which are invisible and causing frontend bugs. + // I dont know why there are so many of them in strings. + if (c >= 0x20) + str.Append((char)c); + break; + } + } + } + + + // doesn't move next + private object? ParseValue() + { + var tok = _tokens.Current.Value; + switch (tok.type) + { + case TokenType.StringOrNumber: + if (string.IsNullOrEmpty(tok.value)) + return string.Empty; + if (tok.value[0] != '-' && !char.IsDigit(tok.value[0])) + return tok.value; + if (tok.value.Contains('.') && double.TryParse(tok.value, out double d)) + return d; + if (long.TryParse(tok.value, out long l)) + return l; + return tok.value; + case TokenType.BracketOpen: + object obj = ParseListOrDict(); + return obj; + case TokenType.BracketClose: + return null; + default: + throw new UnexpectedTokenException(tok); + } + } + + + // skips next value + /// true if skipped value, false if current token is closing bracket + private bool SkipValue() + { + var tok = _tokens.Current.Value; + if (tok.type == TokenType.BracketOpen) + { + SkipObject(); + return true; + } + + return tok.type != TokenType.BracketClose; + } + + // skips all tokens inside curly braces block + private void SkipObject(int bracketBalance = 1) + { + while (bracketBalance != 0 && _tokens.MoveNext()) + { + var tok = _tokens.Current.Value; + if (tok.type == TokenType.BracketOpen) + bracketBalance++; + else if (tok.type == TokenType.BracketClose) + bracketBalance--; + } + } + + // doesn't move next + private object ParseListOrDict() + { + var first = _tokens.Current.Next; + var second = _tokens.Current.Next?.Next; + if (first?.Value.type == TokenType.StringOrNumber && second?.Value.type == TokenType.Equals) + return ParseDict(); + + return ParseList(); + } + + // moves next + private List ParseList() + { + List list = new(); + while (true) + { + if (!_tokens.MoveNext()) + throw new Exception("Unexpected end of file"); + object? value = ParseValue(); + if (value is null) + break; + list.Add(value); + } + + return list; + } + + // moves next + private Dictionary> ParseDict() + { + Dictionary> dict = new(); + + // root is a dict without closing bracket, so this method must check _tokenIndex < _tokens.Count + for (int localIndex = 0; _tokens.MoveNext(); localIndex++) + { + var tok = _tokens.Current.Value; + // end of dictionary + if (tok.type == TokenType.BracketClose) + break; + + // Saves may contain some blocks without key. + // Such blocks are skipped because idk where to put them. + // Example: `technology_group=tech_cannorian{ } + // { } { } { }` + if (tok.type == TokenType.BracketOpen) + { + SkipObject(); + continue; + } + + if (tok.type != TokenType.StringOrNumber) + throw new UnexpectedTokenException(tok); + + string key = tok.value!; + + // next token should be `=` or `{` + if (!_tokens.MoveNext()) + throw new UnexpectedTokenException(tok); + tok = _tokens.Current.Value; + if (tok.type == TokenType.Equals) + { + // skip `=` + if (!_tokens.MoveNext()) + throw new UnexpectedTokenException(tok); + } + // Saves may contain object definition without `=`. + // Example: `map_area_data {` instead of `map_area_data = {` + else if (tok.type != TokenType.BracketOpen) + { + throw new UnexpectedTokenException(tok); + } + + ISearchExpression? searchExprNext = null; + if (_searchExprCurrent != null + && !_searchExprCurrent.DoesMatch(new SearchArgs(key, localIndex), out searchExprNext)) + { + SkipValue(); + continue; + } + + var searExpressionPrevious = _searchExprCurrent; + _searchExprCurrent = searchExprNext; + object? value = ParseValue(); + if (value is null) + throw new UnexpectedTokenException(_tokens.Current.Value); + _searchExprCurrent = searExpressionPrevious; + + if (!dict.TryGetValue(key, out var list)) + { + list = new List(); + dict.Add(key, list); + } + + list.Add(value); + } + + return dict; + } + + public Dictionary> Parse() + { + var root = ParseDict(); + return root; + } + protected enum TokenType : byte { Invalid, StringOrNumber, Equals, BracketOpen, - BracketClose, + BracketClose } protected struct Token @@ -63,7 +333,7 @@ public class SaveParserEU4 default: throw new ArgumentOutOfRangeException(type.ToString()); } - + return $"{line}:{column} '{s}'"; } } @@ -72,268 +342,7 @@ public class SaveParserEU4 { public UnexpectedTokenException(Token token) : base($"Unexpected token: {token}") - {} - } - - protected IEnumerator LexTextSave() - { - string expectedHeader = "EU4txt"; - byte[] headBytes = new byte[expectedHeader.Length]; - _saveFile.ReadExactly(headBytes); - string headStr = Encoding.UTF8.GetString(headBytes); - if (headStr != expectedHeader) - throw new Exception($"Invalid gamestate header. Expected '{expectedHeader}', got '{headStr}'."); - - StringBuilder str = new(); - int line = 2; - int column = 0; - bool isQuoteOpen = false; - bool isStrInQuotes = false; - Token strToken = new() { - type = TokenType.Invalid, - column = -1, - line = -1 - }; - - bool TryCompleteStringToken() - { - if (isQuoteOpen) - return false; - - // strings in quotes may be empty - if (!isStrInQuotes && (str.Length <= 0 || str[0] == '#')) - return false; - - strToken = new Token - { - type = TokenType.StringOrNumber, - column = (short)(column - str.Length), - line = line, - value = str.ToString() - }; - str.Clear(); - isStrInQuotes = false; - return true; } - - while (_saveFile.CanRead) - { - int c = _saveFile.ReadByte(); - column++; - switch (c) - { - case -1: - if(TryCompleteStringToken()) - yield return strToken; - yield break; - case '\"': - isQuoteOpen = !isQuoteOpen; - isStrInQuotes = true; - break; - case ' ': - case '\t': - case '\r': - if(TryCompleteStringToken()) - yield return strToken; - break; - case '\n': - if(TryCompleteStringToken()) - yield return strToken; - line++; - column = 0; - break; - case '=': - if(TryCompleteStringToken()) - yield return strToken; - yield return new Token - { - type = TokenType.Equals, - line = line, column = (short)column - }; - break; - case '{': - if(TryCompleteStringToken()) - yield return strToken; - yield return new Token - { - type = TokenType.BracketOpen, - line = line, column = (short)column - }; - break; - case '}': - if(TryCompleteStringToken()) - yield return strToken; - yield return new Token - { - type = TokenType.BracketClose, - line = line, column = (short)column - }; - break; - default: - // Skip control characters, which are invisible and causing frontend bugs. - // I dont know why there are so many of them in strings. - if(c >= 0x20) - str.Append((char)c); - break; - } - } - } - - - // doesn't move next - private object? ParseValue() - { - Token tok = _tokens.Current.Value; - switch (tok.type) - { - case TokenType.StringOrNumber: - if(string.IsNullOrEmpty(tok.value)) - return string.Empty; - if (tok.value[0] != '-' && !char.IsDigit(tok.value[0])) - return tok.value; - if(tok.value.Contains('.') && Double.TryParse(tok.value, out double d)) - return d; - if (Int64.TryParse(tok.value, out long l)) - return l; - return tok.value; - case TokenType.BracketOpen: - var obj = ParseListOrDict(); - return obj; - case TokenType.BracketClose: - return null; - default: - throw new UnexpectedTokenException(tok); - } - } - - - // skips next value - /// true if skipped value, false if current token is closing bracket - private bool SkipValue() - { - Token tok = _tokens.Current.Value; - if (tok.type == TokenType.BracketOpen) - { - SkipObject(); - return true; - } - - return tok.type != TokenType.BracketClose; - } - - // skips all tokens inside curly braces block - private void SkipObject(int bracketBalance = 1) - { - while (bracketBalance != 0 && _tokens.MoveNext()) - { - Token tok = _tokens.Current.Value; - if (tok.type == TokenType.BracketOpen) - bracketBalance++; - else if (tok.type == TokenType.BracketClose) - bracketBalance--; - } - } - - // doesn't move next - private object ParseListOrDict() - { - var first = _tokens.Current.Next; - var second = _tokens.Current.Next?.Next; - if (first?.Value.type == TokenType.StringOrNumber && second?.Value.type == TokenType.Equals) - return ParseDict(); - - return ParseList(); - } - - // moves next - private List ParseList() - { - List list = new(); - while(true) - { - if(!_tokens.MoveNext()) - throw new Exception("Unexpected end of file"); - object? value = ParseValue(); - if (value is null) - break; - list.Add(value); - } - return list; - } - - // moves next - private Dictionary> ParseDict() - { - Dictionary> dict = new(); - - // root is a dict without closing bracket, so this method must check _tokenIndex < _tokens.Count - for (int localIndex = 0; _tokens.MoveNext(); localIndex++) - { - Token tok = _tokens.Current.Value; - // end of dictionary - if (tok.type == TokenType.BracketClose) - break; - - // Saves may contain some blocks without key. - // Such blocks are skipped because idk where to put them. - // Example: `technology_group=tech_cannorian{ } - // { } { } { }` - if (tok.type == TokenType.BracketOpen) - { - SkipObject(); - continue; - } - - if(tok.type != TokenType.StringOrNumber) - throw new UnexpectedTokenException(tok); - - string key = tok.value!; - - // next token should be `=` or `{` - if(!_tokens.MoveNext()) - throw new UnexpectedTokenException(tok); - tok = _tokens.Current.Value; - if (tok.type == TokenType.Equals) - { - // skip `=` - if (!_tokens.MoveNext()) - throw new UnexpectedTokenException(tok); - } - // Saves may contain object definition without `=`. - // Example: `map_area_data {` instead of `map_area_data = {` - else if (tok.type != TokenType.BracketOpen) - throw new UnexpectedTokenException(tok); - - ISearchExpression? searchExprNext = null; - if (_searchExprCurrent != null - && !_searchExprCurrent.DoesMatch(new SearchArgs(key, localIndex), out searchExprNext)) - { - SkipValue(); - continue; - } - - var searExpressionPrevious = _searchExprCurrent; - _searchExprCurrent = searchExprNext; - object? value = ParseValue(); - if (value is null) - throw new UnexpectedTokenException(_tokens.Current.Value); - _searchExprCurrent = searExpressionPrevious; - - if(!dict.TryGetValue(key, out List? list)) - { - list = new List(); - dict.Add(key, list); - } - list.Add(value); - } - - return dict; - } - - public Dictionary> Parse() - { - var root = ParseDict(); - return root; } } \ No newline at end of file diff --git a/ParadoxSaveParser.Lib/SearchExpression.cs b/ParadoxSaveParser.Lib/SearchExpression.cs index f4d838d..87ae6d1 100644 --- a/ParadoxSaveParser.Lib/SearchExpression.cs +++ b/ParadoxSaveParser.Lib/SearchExpression.cs @@ -9,15 +9,15 @@ public interface ISearchExpression public static class SearchExpressionCompiler { - private static bool CharEqualsAndNotEscaped(char c, ReadOnlySpan chars, int i) => - chars[i] == c && (i < 1 || chars[i - 1] != '\\') && (i < 2 || chars[i - 2] != '\\'); - + private static bool CharEqualsAndNotEscaped(char c, ReadOnlySpan chars, int i) + => chars[i] == c && (i < 1 || chars[i - 1] != '\\') && (i < 2 || chars[i - 2] != '\\'); + public static ISearchExpression Compile(ReadOnlySpan query) { - if(query.IsEmpty) + if (query.IsEmpty) throw new ArgumentNullException(nameof(query)); - - if(query[0] is '(') + + if (query[0] is '(') { var subExprs = new List(); int supExprBegin = 1; @@ -25,9 +25,13 @@ public static class SearchExpressionCompiler for (int i = supExprBegin; i < query.Length && bracketBalance != 0; i++) { if (CharEqualsAndNotEscaped('(', query, i)) + { bracketBalance++; + } else if (CharEqualsAndNotEscaped(')', query, i)) + { bracketBalance--; + } else if (bracketBalance == 1 && CharEqualsAndNotEscaped('|', query, i)) { var subPart = query.Slice(supExprBegin, i - supExprBegin); @@ -37,14 +41,14 @@ public static class SearchExpressionCompiler } } - if(query[^1] != ')') + if (query[^1] != ')') throw new NotImplementedException("Expressions after ')' are not supported"); - + if (bracketBalance > 0) throw new Exception("Too many opening brackets"); if (bracketBalance < 0) - throw new Exception("Too many closing brackets"); - + throw new Exception("Too many closing brackets"); + var subPartLast = query.Slice(supExprBegin, query.Length - supExprBegin - 1); var subExprLast = Compile(subPartLast); subExprs.Add(subExprLast); @@ -54,35 +58,31 @@ public static class SearchExpressionCompiler int partBeforePointLength = 0; while (partBeforePointLength < query.Length) { - if(CharEqualsAndNotEscaped('.', query, partBeforePointLength)) + if (CharEqualsAndNotEscaped('.', query, partBeforePointLength)) break; partBeforePointLength++; } + var part = query.Slice(0, partBeforePointLength); ReadOnlySpan remaining = default; if (partBeforePointLength < query.Length) remaining = query.Slice(partBeforePointLength + 1); - if (part is "*") - { - return new AnyMatchExpression(remaining.IsEmpty ? null : Compile(remaining)); - } + if (part is "*") return new AnyMatchExpression(remaining.IsEmpty ? null : Compile(remaining)); for (int j = 0; j < part.Length; j++) - { - if(CharEqualsAndNotEscaped('*', part, j)) + if (CharEqualsAndNotEscaped('*', part, j)) throw new NotImplementedException("pattern matching other than '*' is not implemented yet"); - } - + if (part[0] is '[') { part = part.Slice(1, part.Length - 2); return new IndexMatchExpression(int.Parse(part), remaining.IsEmpty ? null : Compile(remaining)); } - + return new ExactMatchExpression(part.ToString(), remaining.IsEmpty ? null : Compile(remaining)); } - + private record AnyMatchExpression(ISearchExpression? next) : ISearchExpression { public bool DoesMatch(SearchArgs args, out ISearchExpression? nextSearchExpression) @@ -91,19 +91,15 @@ public static class SearchExpressionCompiler return true; } } - + private record MultipleMatchExpression(List subExprs) : ISearchExpression { public bool DoesMatch(SearchArgs args, out ISearchExpression? nextSearchExpression) { foreach (var e in subExprs) - { - if(e.DoesMatch(args, out nextSearchExpression)) - { + if (e.DoesMatch(args, out nextSearchExpression)) return true; - } - } - + nextSearchExpression = null; return false; } @@ -118,12 +114,12 @@ public static class SearchExpressionCompiler nextSearchExpression = next; return true; } - + nextSearchExpression = null; return false; } } - + private record ExactMatchExpression(string key, ISearchExpression? next) : ISearchExpression { public bool DoesMatch(SearchArgs args, out ISearchExpression? nextSearchExpression) @@ -133,9 +129,9 @@ public static class SearchExpressionCompiler nextSearchExpression = next; return true; } - + nextSearchExpression = null; return false; } } -} +} \ No newline at end of file diff --git a/ParadoxSaveParser.WebAPI/Config.cs b/ParadoxSaveParser.WebAPI/Config.cs index 5946402..492a759 100644 --- a/ParadoxSaveParser.WebAPI/Config.cs +++ b/ParadoxSaveParser.WebAPI/Config.cs @@ -5,10 +5,10 @@ namespace ParadoxSaveParser.WebAPI; public class Config { public const int ActualVersion = 1; - - public int Version = ActualVersion; public string BaseUrl = "http://127.0.0.1:5226/"; + public int Version = ActualVersion; + public static Config FromDtsod(DtsodV23 d) { var cfg = new Config @@ -18,16 +18,16 @@ public class Config }; if (cfg.Version < ActualVersion) throw new Exception($"config is obsolete (config v{cfg.Version} < program v{ActualVersion})"); - if(cfg.Version > ActualVersion) + if (cfg.Version > ActualVersion) throw new Exception($"program is obsolete (config v{cfg.Version} > program v{ActualVersion})"); return cfg; } - public DtsodV23 ToDtsod() => - new() + public DtsodV23 ToDtsod() + => new() { { "version", Version }, - { "baseUrl", BaseUrl }, + { "baseUrl", BaseUrl } }; public override string ToString() => ToDtsod().ToString(); diff --git a/ParadoxSaveParser.WebAPI/ParadoxSaveParser.WebAPI.csproj b/ParadoxSaveParser.WebAPI/ParadoxSaveParser.WebAPI.csproj index a278f18..4de0743 100644 --- a/ParadoxSaveParser.WebAPI/ParadoxSaveParser.WebAPI.csproj +++ b/ParadoxSaveParser.WebAPI/ParadoxSaveParser.WebAPI.csproj @@ -9,10 +9,10 @@ - + - + diff --git a/ParadoxSaveParser.WebAPI/PathHelper.cs b/ParadoxSaveParser.WebAPI/PathHelper.cs index 8f5a93d..b6a7e0e 100644 --- a/ParadoxSaveParser.WebAPI/PathHelper.cs +++ b/ParadoxSaveParser.WebAPI/PathHelper.cs @@ -4,7 +4,10 @@ public static class PathHelper { public static readonly IOPath DATA_DIR = "data"; public static readonly IOPath SAVES_DIR = Path.Concat(DATA_DIR, "saves"); + public static IOPath GetMetaFilePath(string save_id) => Path.Concat(SAVES_DIR, save_id + ".meta.json"); + public static IOPath GetSaveFilePath(string save_id) => Path.Concat(SAVES_DIR, save_id + ".eu4"); + public static IOPath GetParsedSaveFilePath(string save_id) => Path.Concat(SAVES_DIR, save_id + ".parsed.json"); } \ No newline at end of file diff --git a/ParadoxSaveParser.WebAPI/Program.HttpHelpers.cs b/ParadoxSaveParser.WebAPI/Program.HttpHelpers.cs index ed0132c..a46c3bc 100644 --- a/ParadoxSaveParser.WebAPI/Program.HttpHelpers.cs +++ b/ParadoxSaveParser.WebAPI/Program.HttpHelpers.cs @@ -14,7 +14,7 @@ public partial class Program Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, MaxDepth = 1024 }; - + public static async Task ReturnResponseString(HttpListenerContext ctx, string value, HttpStatusCode statusCode = HttpStatusCode.OK) { @@ -53,6 +53,17 @@ public partial class Program return error.StatusCode; } + + internal static ValueOrError GetRequestQueryValue(HttpListenerContext ctx, string paramName) + { + string[]? values = ctx.Request.QueryString.GetValues(paramName); + string? value = values?.FirstOrDefault(); + if (string.IsNullOrEmpty(value)) + return new ErrorMessage(HttpStatusCode.BadRequest, + $"No request parameter '{paramName}' provided"); + return value; + } + public record ErrorMessage { public ErrorMessage(HttpStatusCode statusCode, string message) @@ -83,15 +94,4 @@ public partial class Program public static implicit operator ValueOrError(ErrorMessage e) => new(default, e); } - - - internal static ValueOrError GetRequestQueryValue(HttpListenerContext ctx, string paramName) - { - string[]? values = ctx.Request.QueryString.GetValues(paramName); - string? value = values?.FirstOrDefault(); - if (string.IsNullOrEmpty(value)) - return new ErrorMessage(HttpStatusCode.BadRequest, - $"No request parameter '{paramName}' provided"); - return value; - } } \ No newline at end of file diff --git a/ParadoxSaveParser.WebAPI/SaveFileMetadata.cs b/ParadoxSaveParser.WebAPI/SaveFileMetadata.cs index c8e1dac..22061db 100644 --- a/ParadoxSaveParser.WebAPI/SaveFileMetadata.cs +++ b/ParadoxSaveParser.WebAPI/SaveFileMetadata.cs @@ -4,26 +4,31 @@ namespace ParadoxSaveParser.WebAPI; public enum SaveFileProcessingStatus { - Initialized, Uploading, Uploaded, Parsing, SavingResults, Done + Initialized, + Uploading, + Uploaded, + Parsing, + SavingResults, + Done } public enum Game { - Unknown, EU4 + Unknown, + EU4 } public class SaveFileMetadata { + private static readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true }; public required string id { get; init; } - + [JsonConverter(typeof(JsonStringEnumConverter))] public required Game game { get; init; } - + [JsonConverter(typeof(JsonStringEnumConverter))] public required SaveFileProcessingStatus status { get; set; } - - - private static readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true }; + public void SaveToFile() { using var metaFile = File.OpenWrite(PathHelper.GetMetaFilePath(id));