From ce4935417c7031456877a3cfb246bac202d24ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 18 May 2025 16:11:48 +0200 Subject: [PATCH 01/17] Imprort YAML bundles via converting them first to JSON --- src/UniGetUI.Core.Data/CoreData.cs | 29 ++++++++++++++++++- .../Pages/SoftwarePages/PackageBundlesPage.cs | 21 +++++++++----- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/UniGetUI.Core.Data/CoreData.cs b/src/UniGetUI.Core.Data/CoreData.cs index a64063d4b1..3927a0dc7a 100644 --- a/src/UniGetUI.Core.Data/CoreData.cs +++ b/src/UniGetUI.Core.Data/CoreData.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Text.Json; +using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using UniGetUI.Core.Logging; @@ -357,10 +358,36 @@ private static string GetNewDataDirectoryOrMoveOld(string old_path, string new_p } } + + private class FlexibleBooleanConverter : JsonConverter + { + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.TokenType switch + { + JsonTokenType.True => true, + JsonTokenType.False => false, + JsonTokenType.String => bool.TryParse(reader.GetString(), out var b) ? b : + int.TryParse(reader.GetString(), out var i) ? i != 0 : + throw new JsonException("Invalid string for boolean."), + JsonTokenType.Number => reader.TryGetInt32(out var i2) ? i2 != 0 : + throw new JsonException("Invalid number for boolean."), + _ => throw new JsonException("Invalid token for boolean.") + }; + } + + public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) + { + writer.WriteBooleanValue(value); + } + } + public static JsonSerializerOptions SerializingOptions = new() { - TypeInfoResolverChain = { new DefaultJsonTypeInfoResolver() }, + NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString, + AllowTrailingCommas = true, WriteIndented = true, + Converters = { new FlexibleBooleanConverter() } }; private static int GetCodePage() diff --git a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs index 2b01f9fe23..b6edcb9a67 100644 --- a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs +++ b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs @@ -635,17 +635,22 @@ public async Task AddFromBundle(string content, BundleFormatType format) { // Deserialize data SerializableBundle_v1? DeserializedData; - if (format is BundleFormatType.JSON or BundleFormatType.UBUNDLE) + + if (format is BundleFormatType.YAML) { - DeserializedData = await Task.Run(() => JsonSerializer.Deserialize(content, CoreData.SerializingOptions)); + // Dynamic convert to JSON + format = BundleFormatType.JSON; + var yamlObject = new YamlDotNet.Serialization.Deserializer().Deserialize(content); + content = new YamlDotNet.Serialization.SerializerBuilder() + .JsonCompatible() + .Build() + .Serialize(yamlObject); + Logger.ImportantInfo("YAML bundle was converted to JSON before deserialization"); } - else if (format is BundleFormatType.YAML) + + if (format is BundleFormatType.JSON or BundleFormatType.UBUNDLE) { - IDeserializer deserializer = - new DeserializerBuilder() - .IgnoreUnmatchedProperties() - .Build(); - DeserializedData = await Task.Run(() => deserializer.Deserialize(content)); + DeserializedData = await Task.Run(() => JsonSerializer.Deserialize(content, CoreData.SerializingOptions)); } else { From c8f67efb9a0eb56de01e0ef98077066c51d81f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 18 May 2025 17:00:44 +0200 Subject: [PATCH 02/17] XML will also be converted to JSON prior to deserialization --- src/UniGetUI.Core.Data/CoreData.cs | 32 ---- .../SerializationHelpers.cs | 171 ++++++++++++++++++ .../UniGetUI.Core.Data.csproj | 4 + src/UniGetUI.Core.IconStore/IconDatabase.cs | 2 +- .../SettingsEngine_Dictionaries.cs | 4 +- .../SettingsEngine_ImportExport.cs | 4 +- .../SettingsEngine_Lists.cs | 4 +- .../CratesIOClient.cs | 2 +- .../Packages/Classes/InstallationOptions.cs | 4 +- .../Pages/SoftwarePages/PackageBundlesPage.cs | 29 +-- 10 files changed, 195 insertions(+), 61 deletions(-) create mode 100644 src/UniGetUI.Core.Data/SerializationHelpers.cs diff --git a/src/UniGetUI.Core.Data/CoreData.cs b/src/UniGetUI.Core.Data/CoreData.cs index 3927a0dc7a..5c17b6a055 100644 --- a/src/UniGetUI.Core.Data/CoreData.cs +++ b/src/UniGetUI.Core.Data/CoreData.cs @@ -358,38 +358,6 @@ private static string GetNewDataDirectoryOrMoveOld(string old_path, string new_p } } - - private class FlexibleBooleanConverter : JsonConverter - { - public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return reader.TokenType switch - { - JsonTokenType.True => true, - JsonTokenType.False => false, - JsonTokenType.String => bool.TryParse(reader.GetString(), out var b) ? b : - int.TryParse(reader.GetString(), out var i) ? i != 0 : - throw new JsonException("Invalid string for boolean."), - JsonTokenType.Number => reader.TryGetInt32(out var i2) ? i2 != 0 : - throw new JsonException("Invalid number for boolean."), - _ => throw new JsonException("Invalid token for boolean.") - }; - } - - public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) - { - writer.WriteBooleanValue(value); - } - } - - public static JsonSerializerOptions SerializingOptions = new() - { - NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString, - AllowTrailingCommas = true, - WriteIndented = true, - Converters = { new FlexibleBooleanConverter() } - }; - private static int GetCodePage() { try diff --git a/src/UniGetUI.Core.Data/SerializationHelpers.cs b/src/UniGetUI.Core.Data/SerializationHelpers.cs new file mode 100644 index 0000000000..4aa031fcd7 --- /dev/null +++ b/src/UniGetUI.Core.Data/SerializationHelpers.cs @@ -0,0 +1,171 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using System.Xml; + +namespace UniGetUI.Core.Data; + +public static class SerializationHelpers +{ + public static Task YAML_to_JSON(string YAML) + => Task.Run(() => yaml_to_json(YAML)); + + private static string yaml_to_json(string YAML) + { + var yamlObject = new YamlDotNet.Serialization.Deserializer().Deserialize(YAML); + if (yamlObject is null) return "{'message': 'deserialized YAML object was null'}"; + return new YamlDotNet.Serialization.SerializerBuilder() + .JsonCompatible() + .Build() + .Serialize(yamlObject); + } + + public static Task XML_to_JSON(string XML) + => Task.Run(() => xml_to_json(XML)); + + private static string xml_to_json(string XML) + { + var doc = new XmlDocument(); + doc.LoadXml(XML); + if (doc.DocumentElement is null) return "{'message': 'XmlDocument.DocumentElement was null'}"; + return JsonSerializer.Serialize(_convertXmlNode(doc.DocumentElement), SerializingOptions); + } + + private static object? _convertXmlNode(XmlNode node) + { + // If node has no children, return its text or attributes + if (node.ChildNodes.Count == 1 && node.FirstChild is XmlText singleText) + { + return singleText.Value; + } + + // Attributes dictionary + var dict = new Dictionary(); + if (node.Attributes?.Count > 0) + { + foreach (XmlAttribute attr in node.Attributes) + { + dict[$"@{attr.Name}"] = attr.Value; + } + } + + // Group child elements + var children = new Dictionary>(); + foreach (XmlNode child in node.ChildNodes) + { + if (child is XmlElement childElement) + { + var value = _convertXmlNode(childElement); + if (!children.ContainsKey(childElement.Name)) + children[childElement.Name] = new List(); + children[childElement.Name].Add(value); + } + } + + // Flatten repeated elements if only one group exists + if (children.Count == 1 && dict.Count == 0) + { + var firstKey = children.Keys.First(); + return children[firstKey].Count == 1 ? children[firstKey][0] : children[firstKey]; + } + + // Otherwise build normal object + foreach (var kv in children) + { + dict[kv.Key] = kv.Value.Count == 1 ? kv.Value[0] : kv.Value; + } + + return dict; + } + + public static JsonSerializerOptions SerializingOptions = new() + { + TypeInfoResolver = new DefaultJsonTypeInfoResolver(), + NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString, + AllowTrailingCommas = true, + WriteIndented = true, + Converters = { new FlexibleBooleanConverter(), new FlexibleStringConverter(), new FlexibleListStringConverter() } + }; + + private class FlexibleBooleanConverter : JsonConverter + { + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.TokenType switch + { + JsonTokenType.True => true, + JsonTokenType.False => false, + JsonTokenType.String => bool.TryParse(reader.GetString(), out var b) ? b : + int.TryParse(reader.GetString(), out var i) ? i != 0 : + throw new JsonException("Invalid string for boolean."), + JsonTokenType.Number => reader.TryGetInt32(out var i2) ? i2 != 0 : + throw new JsonException("Invalid number for boolean."), + _ => throw new JsonException("Invalid token for boolean.") + }; + } + + public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) + { + writer.WriteBooleanValue(value); + } + } + + private class FlexibleStringConverter : JsonConverter + { + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.StartObject) + { + using var doc = JsonDocument.ParseValue(ref reader); + return ""; + } + return reader.GetString() ?? ""; + } + + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + writer.WriteStringValue(value); + } + } + + private class FlexibleListStringConverter : JsonConverter> + { + public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.StartObject) + { + using var doc = JsonDocument.ParseValue(ref reader); + return []; + } + if (reader.TokenType == JsonTokenType.String) + { + return [reader.GetString() ?? ""]; + } + if (reader.TokenType == JsonTokenType.StartArray) + { + var list = new List(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + break; + if (reader.TokenType == JsonTokenType.String) + list.Add(reader.GetString() ?? string.Empty); + } + return list; + } + throw new JsonException($"Unexpected token {reader.TokenType} when reading List."); + } + + public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + foreach (var str in value) + { + writer.WriteStringValue(str); + } + writer.WriteEndArray(); + } + } +} + + diff --git a/src/UniGetUI.Core.Data/UniGetUI.Core.Data.csproj b/src/UniGetUI.Core.Data/UniGetUI.Core.Data.csproj index 5fa3d8a996..43e50dfde6 100644 --- a/src/UniGetUI.Core.Data/UniGetUI.Core.Data.csproj +++ b/src/UniGetUI.Core.Data/UniGetUI.Core.Data.csproj @@ -15,4 +15,8 @@ + + + + diff --git a/src/UniGetUI.Core.IconStore/IconDatabase.cs b/src/UniGetUI.Core.IconStore/IconDatabase.cs index e6ba407536..d9b2328df1 100644 --- a/src/UniGetUI.Core.IconStore/IconDatabase.cs +++ b/src/UniGetUI.Core.IconStore/IconDatabase.cs @@ -91,7 +91,7 @@ public async Task LoadIconAndScreenshotsDatabaseAsync() { IconScreenshotDatabase_v2 JsonData = JsonSerializer.Deserialize( await File.ReadAllTextAsync(IconsAndScreenshotsFile), - CoreData.SerializingOptions + SerializationHelpers.SerializingOptions ); if (JsonData.icons_and_screenshots is not null) { diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Dictionaries.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Dictionaries.cs index 12f703a4f1..80aa0003bc 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_Dictionaries.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_Dictionaries.cs @@ -43,7 +43,7 @@ public static partial class Settings if (result != "") { - Dictionary? item = JsonSerializer.Deserialize>(result, CoreData.SerializingOptions); + Dictionary? item = JsonSerializer.Deserialize>(result, SerializationHelpers.SerializingOptions); if (item is not null) { value = item; @@ -90,7 +90,7 @@ public static void SetDictionary(string setting, Dictionary value) try { - if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, CoreData.SerializingOptions)); + if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, SerializationHelpers.SerializingOptions)); else if (File.Exists(file)) File.Delete(file); } catch (Exception e) diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_ImportExport.cs b/src/UniGetUI.Core.Settings/SettingsEngine_ImportExport.cs index f10e8cfd90..182064f55f 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_ImportExport.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_ImportExport.cs @@ -17,7 +17,7 @@ public static void ExportToJSON(string path) settings.Add(Path.GetFileName(entry), File.ReadAllText(entry)); } - File.WriteAllText(path, JsonSerializer.Serialize(settings, CoreData.SerializingOptions)); + File.WriteAllText(path, JsonSerializer.Serialize(settings, SerializationHelpers.SerializingOptions)); } public static void ImportFromJSON(string path) @@ -31,7 +31,7 @@ public static void ImportFromJSON(string path) } ResetSettings(); - Dictionary settings = JsonSerializer.Deserialize>(File.ReadAllText(path), CoreData.SerializingOptions) ?? []; + Dictionary settings = JsonSerializer.Deserialize>(File.ReadAllText(path), SerializationHelpers.SerializingOptions) ?? []; foreach (KeyValuePair entry in settings) { if(new[] {"OperationHistory", "WinGetAlreadyUpgradedPackages.json", "TelemetryClientToken", "CurrentSessionToken"}.Contains(entry.Key)) diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Lists.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Lists.cs index 85b09bfc7c..5c9b240091 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_Lists.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_Lists.cs @@ -39,7 +39,7 @@ public static partial class Settings { if (result != "") { - List? item = JsonSerializer.Deserialize>(result, CoreData.SerializingOptions); + List? item = JsonSerializer.Deserialize>(result, SerializationHelpers.SerializingOptions); if (item is not null) { value = item; @@ -76,7 +76,7 @@ public static void SetList(string setting, List value) var file = Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{setting}.json"); try { - if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, CoreData.SerializingOptions)); + if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, SerializationHelpers.SerializingOptions)); else if (File.Exists(file)) File.Delete(file); } catch (Exception e) diff --git a/src/UniGetUI.PackageEngine.Managers.Cargo/CratesIOClient.cs b/src/UniGetUI.PackageEngine.Managers.Cargo/CratesIOClient.cs index 6975380a7a..4adcbce066 100644 --- a/src/UniGetUI.PackageEngine.Managers.Cargo/CratesIOClient.cs +++ b/src/UniGetUI.PackageEngine.Managers.Cargo/CratesIOClient.cs @@ -93,7 +93,7 @@ private static T Fetch(Uri url) var manifestStr = client.GetStringAsync(url).GetAwaiter().GetResult(); - var manifest = JsonSerializer.Deserialize(manifestStr, options: CoreData.SerializingOptions) + var manifest = JsonSerializer.Deserialize(manifestStr, options: SerializationHelpers.SerializingOptions) ?? throw new NullResponseException($"Null response for request to {url}"); return manifest; } diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs index 52d721155d..f02285ba34 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs @@ -201,7 +201,7 @@ public void SaveToDisk() string fileContents = JsonSerializer.Serialize( AsSerializable(), - CoreData.SerializingOptions + SerializationHelpers.SerializingOptions ); File.WriteAllText(optionsFile.FullName, fileContents); } @@ -227,7 +227,7 @@ public void LoadFromDisk() using FileStream inputStream = optionsFile.OpenRead(); SerializableInstallationOptions_v1? options = JsonSerializer.Deserialize( - inputStream, CoreData.SerializingOptions); + inputStream, SerializationHelpers.SerializingOptions); if (options is null) { diff --git a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs index b6edcb9a67..93e0af4ea8 100644 --- a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs +++ b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Text.Json; +using System.Xml; using System.Xml.Serialization; using ExternalLibraries.Pickers; using Microsoft.UI.Text; @@ -607,7 +608,7 @@ static int Comparison(IPackage x, IPackage y) if (formatType is BundleFormatType.JSON or BundleFormatType.UBUNDLE) ExportableData = JsonSerializer.Serialize( exportable, - CoreData.SerializingOptions); + SerializationHelpers.SerializingOptions); else if (formatType is BundleFormatType.YAML) { @@ -639,30 +640,20 @@ public async Task AddFromBundle(string content, BundleFormatType format) if (format is BundleFormatType.YAML) { // Dynamic convert to JSON - format = BundleFormatType.JSON; - var yamlObject = new YamlDotNet.Serialization.Deserializer().Deserialize(content); - content = new YamlDotNet.Serialization.SerializerBuilder() - .JsonCompatible() - .Build() - .Serialize(yamlObject); + content = await SerializationHelpers.YAML_to_JSON(content); Logger.ImportantInfo("YAML bundle was converted to JSON before deserialization"); } - if (format is BundleFormatType.JSON or BundleFormatType.UBUNDLE) + if (format is BundleFormatType.XML) { - DeserializedData = await Task.Run(() => JsonSerializer.Deserialize(content, CoreData.SerializingOptions)); - } - else - { - string tempfile = Path.GetTempFileName(); - await File.WriteAllTextAsync(tempfile, content); - StreamReader reader = new(tempfile); - XmlSerializer serializer = new(typeof(SerializableBundle_v1)); - DeserializedData = await Task.Run(() => serializer.Deserialize(reader) as SerializableBundle_v1); - reader.Close(); - File.Delete(tempfile); + // Dynamic convert to JSON + content = await SerializationHelpers.XML_to_JSON(content); + Logger.ImportantInfo("XML payload was converted to JSON dynamically before deserialization"); } + DeserializedData = await Task.Run(() => JsonSerializer.Deserialize(content, SerializationHelpers.SerializingOptions)); + + if (DeserializedData is null || DeserializedData.export_version is -1) { throw new ArgumentException("DeserializedData was null"); From 953ad480ec5d900fe919f07e5765a57a4d24335f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 18 May 2025 17:11:39 +0200 Subject: [PATCH 03/17] Only use special data converters when handling bundles. Otherwhise use default serializer options (adjusted for trim support) --- src/UniGetUI.Core.IconStore/IconDatabase.cs | 2 +- .../SettingsEngine_Dictionaries.cs | 4 +-- .../SettingsEngine_Extras.cs | 9 ++++++ .../SettingsEngine_ImportExport.cs | 4 +-- .../SettingsEngine_Lists.cs | 4 +-- src/UniGetUI.Core.Tools.Tests/MetaTests.cs | 4 +-- .../SerializationHelpers.cs | 30 ++++++++++++++----- .../CratesIOClient.cs | 2 +- .../Packages/Classes/InstallationOptions.cs | 4 +-- .../Pages/SoftwarePages/PackageBundlesPage.cs | 4 +-- 10 files changed, 46 insertions(+), 21 deletions(-) rename src/{UniGetUI.Core.Data => UniGetUI.Core.Tools}/SerializationHelpers.cs (86%) diff --git a/src/UniGetUI.Core.IconStore/IconDatabase.cs b/src/UniGetUI.Core.IconStore/IconDatabase.cs index d9b2328df1..dfc0500ca3 100644 --- a/src/UniGetUI.Core.IconStore/IconDatabase.cs +++ b/src/UniGetUI.Core.IconStore/IconDatabase.cs @@ -91,7 +91,7 @@ public async Task LoadIconAndScreenshotsDatabaseAsync() { IconScreenshotDatabase_v2 JsonData = JsonSerializer.Deserialize( await File.ReadAllTextAsync(IconsAndScreenshotsFile), - SerializationHelpers.SerializingOptions + SerializationHelpers.DefaultOptions ); if (JsonData.icons_and_screenshots is not null) { diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Dictionaries.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Dictionaries.cs index 80aa0003bc..a187870cdd 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_Dictionaries.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_Dictionaries.cs @@ -43,7 +43,7 @@ public static partial class Settings if (result != "") { - Dictionary? item = JsonSerializer.Deserialize>(result, SerializationHelpers.SerializingOptions); + Dictionary? item = JsonSerializer.Deserialize>(result, SerializationOptions); if (item is not null) { value = item; @@ -90,7 +90,7 @@ public static void SetDictionary(string setting, Dictionary value) try { - if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, SerializationHelpers.SerializingOptions)); + if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, SerializationOptions)); else if (File.Exists(file)) File.Delete(file); } catch (Exception e) diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs index dffc98804a..4233aadea7 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs @@ -1,4 +1,6 @@ using System.Net; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using Windows.Security.Credentials; using UniGetUI.Core.Logging; @@ -83,4 +85,11 @@ public static void SetProxyCredentials(string username, string password) Logger.Error(ex); } } + + private static JsonSerializerOptions SerializationOptions = new() + { + TypeInfoResolver = new DefaultJsonTypeInfoResolver(), + AllowTrailingCommas = true, + WriteIndented = true, + }; } diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_ImportExport.cs b/src/UniGetUI.Core.Settings/SettingsEngine_ImportExport.cs index 182064f55f..be8b1da2e7 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_ImportExport.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_ImportExport.cs @@ -17,7 +17,7 @@ public static void ExportToJSON(string path) settings.Add(Path.GetFileName(entry), File.ReadAllText(entry)); } - File.WriteAllText(path, JsonSerializer.Serialize(settings, SerializationHelpers.SerializingOptions)); + File.WriteAllText(path, JsonSerializer.Serialize(settings, SerializationOptions)); } public static void ImportFromJSON(string path) @@ -31,7 +31,7 @@ public static void ImportFromJSON(string path) } ResetSettings(); - Dictionary settings = JsonSerializer.Deserialize>(File.ReadAllText(path), SerializationHelpers.SerializingOptions) ?? []; + Dictionary settings = JsonSerializer.Deserialize>(File.ReadAllText(path), SerializationOptions) ?? []; foreach (KeyValuePair entry in settings) { if(new[] {"OperationHistory", "WinGetAlreadyUpgradedPackages.json", "TelemetryClientToken", "CurrentSessionToken"}.Contains(entry.Key)) diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Lists.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Lists.cs index 5c9b240091..19f0a47fc9 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_Lists.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_Lists.cs @@ -39,7 +39,7 @@ public static partial class Settings { if (result != "") { - List? item = JsonSerializer.Deserialize>(result, SerializationHelpers.SerializingOptions); + List? item = JsonSerializer.Deserialize>(result, SerializationOptions); if (item is not null) { value = item; @@ -76,7 +76,7 @@ public static void SetList(string setting, List value) var file = Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{setting}.json"); try { - if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, SerializationHelpers.SerializingOptions)); + if (value.Count != 0) File.WriteAllText(file, JsonSerializer.Serialize(value, SerializationOptions)); else if (File.Exists(file)) File.Delete(file); } catch (Exception e) diff --git a/src/UniGetUI.Core.Tools.Tests/MetaTests.cs b/src/UniGetUI.Core.Tools.Tests/MetaTests.cs index 4c3667a9e2..8539f4ebf8 100644 --- a/src/UniGetUI.Core.Tools.Tests/MetaTests.cs +++ b/src/UniGetUI.Core.Tools.Tests/MetaTests.cs @@ -17,10 +17,10 @@ public void TestJsonSerializationOptions() var lines = File.ReadAllLines(file); var jsonSerCount = lines.Count(x => x.Contains("JsonSerializer.Serialize")); var jsonDeserCount = lines.Count(x => x.Contains("JsonSerializer.Deserialize")); - var serialOptionsCount = lines.Count(x => x.Contains("CoreData.SerializingOptions")); + var serialOptionsCount = lines.Count(x => x.Contains("CoreData.DefaultOptions")); Assert.True((jsonSerCount + jsonDeserCount) <= serialOptionsCount, $"Failing on {file}. The specified file does not serialize and/or deserialize JSON with" + - $" the proper CoreData.SerializingOptions set"); + $" the proper CoreData.DefaultOptions set"); } } diff --git a/src/UniGetUI.Core.Data/SerializationHelpers.cs b/src/UniGetUI.Core.Tools/SerializationHelpers.cs similarity index 86% rename from src/UniGetUI.Core.Data/SerializationHelpers.cs rename to src/UniGetUI.Core.Tools/SerializationHelpers.cs index 4aa031fcd7..0a7ae0113a 100644 --- a/src/UniGetUI.Core.Data/SerializationHelpers.cs +++ b/src/UniGetUI.Core.Tools/SerializationHelpers.cs @@ -28,7 +28,7 @@ private static string xml_to_json(string XML) var doc = new XmlDocument(); doc.LoadXml(XML); if (doc.DocumentElement is null) return "{'message': 'XmlDocument.DocumentElement was null'}"; - return JsonSerializer.Serialize(_convertXmlNode(doc.DocumentElement), SerializingOptions); + return JsonSerializer.Serialize(_convertXmlNode(doc.DocumentElement), DefaultOptions); } private static object? _convertXmlNode(XmlNode node) @@ -78,16 +78,32 @@ private static string xml_to_json(string XML) return dict; } - public static JsonSerializerOptions SerializingOptions = new() + public static JsonSerializerOptions DefaultOptions = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver(), - NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString, AllowTrailingCommas = true, WriteIndented = true, - Converters = { new FlexibleBooleanConverter(), new FlexibleStringConverter(), new FlexibleListStringConverter() } }; - private class FlexibleBooleanConverter : JsonConverter + public static JsonSerializerOptions ImportBundleOptions = new() + { + TypeInfoResolver = new DefaultJsonTypeInfoResolver(), + NumberHandling = + JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString, + AllowTrailingCommas = true, + WriteIndented = true, + Converters = + { + new Converters.FlexibleBooleanConverter(), + new Converters.FlexibleStringConverter(), + new Converters.FlexibleListStringConverter() + } + }; +} + +internal class Converters +{ + public class FlexibleBooleanConverter : JsonConverter { public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { @@ -110,7 +126,7 @@ public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOpti } } - private class FlexibleStringConverter : JsonConverter + public class FlexibleStringConverter : JsonConverter { public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { @@ -128,7 +144,7 @@ public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOp } } - private class FlexibleListStringConverter : JsonConverter> + public class FlexibleListStringConverter : JsonConverter> { public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { diff --git a/src/UniGetUI.PackageEngine.Managers.Cargo/CratesIOClient.cs b/src/UniGetUI.PackageEngine.Managers.Cargo/CratesIOClient.cs index 4adcbce066..757a321f91 100644 --- a/src/UniGetUI.PackageEngine.Managers.Cargo/CratesIOClient.cs +++ b/src/UniGetUI.PackageEngine.Managers.Cargo/CratesIOClient.cs @@ -93,7 +93,7 @@ private static T Fetch(Uri url) var manifestStr = client.GetStringAsync(url).GetAwaiter().GetResult(); - var manifest = JsonSerializer.Deserialize(manifestStr, options: SerializationHelpers.SerializingOptions) + var manifest = JsonSerializer.Deserialize(manifestStr, options: SerializationHelpers.DefaultOptions) ?? throw new NullResponseException($"Null response for request to {url}"); return manifest; } diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs index f02285ba34..5d8f7f5491 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs @@ -201,7 +201,7 @@ public void SaveToDisk() string fileContents = JsonSerializer.Serialize( AsSerializable(), - SerializationHelpers.SerializingOptions + SerializationHelpers.DefaultOptions ); File.WriteAllText(optionsFile.FullName, fileContents); } @@ -227,7 +227,7 @@ public void LoadFromDisk() using FileStream inputStream = optionsFile.OpenRead(); SerializableInstallationOptions_v1? options = JsonSerializer.Deserialize( - inputStream, SerializationHelpers.SerializingOptions); + inputStream, SerializationHelpers.DefaultOptions); if (options is null) { diff --git a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs index 93e0af4ea8..e6b5376c00 100644 --- a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs +++ b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs @@ -608,7 +608,7 @@ static int Comparison(IPackage x, IPackage y) if (formatType is BundleFormatType.JSON or BundleFormatType.UBUNDLE) ExportableData = JsonSerializer.Serialize( exportable, - SerializationHelpers.SerializingOptions); + SerializationHelpers.DefaultOptions); else if (formatType is BundleFormatType.YAML) { @@ -651,7 +651,7 @@ public async Task AddFromBundle(string content, BundleFormatType format) Logger.ImportantInfo("XML payload was converted to JSON dynamically before deserialization"); } - DeserializedData = await Task.Run(() => JsonSerializer.Deserialize(content, SerializationHelpers.SerializingOptions)); + DeserializedData = await Task.Run(() => JsonSerializer.Deserialize(content, SerializationHelpers.ImportBundleOptions)); if (DeserializedData is null || DeserializedData.export_version is -1) From 7f14a27d4bfbe6299632f042e572e7dd73c37e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 18 May 2025 23:18:30 +0200 Subject: [PATCH 04/17] fix serialization tests not passing --- src/UniGetUI.Core.Settings.Tests/SettingsTest.cs | 6 +++--- src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs | 2 +- src/UniGetUI.Core.Tools.Tests/MetaTests.cs | 6 ++++-- src/UniGetUI.Core.Tools/SerializationHelpers.cs | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/UniGetUI.Core.Settings.Tests/SettingsTest.cs b/src/UniGetUI.Core.Settings.Tests/SettingsTest.cs index 88228c24c7..dd572781bf 100644 --- a/src/UniGetUI.Core.Settings.Tests/SettingsTest.cs +++ b/src/UniGetUI.Core.Settings.Tests/SettingsTest.cs @@ -173,7 +173,7 @@ public void TestListSettings(string SettingName, string[] ls1Array, int[] ls2Arr Assert.Equal("this is now a test case", Settings.GetListItem(SettingName, 3)); Assert.Null(Settings.GetListItem(SettingName, 4)); - Assert.Equal(Settings.GetListItem(SettingName, 0), JsonSerializer.Deserialize>(File.ReadAllText(Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{SettingName}.json")), CoreData.SerializingOptions)[0]); + Assert.Equal(Settings.GetListItem(SettingName, 0), JsonSerializer.Deserialize>(File.ReadAllText(Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{SettingName}.json")), Settings.SerializationOptions)[0]); Settings.ClearList(SettingName); Assert.Empty(Settings.GetList(SettingName) ?? ["this shouldn't be null; something's wrong"]); @@ -226,7 +226,7 @@ public void TestDictionarySettings(string SettingName, string[] keyArray, int[] Settings.SetDictionaryItem(randStr, "key", 12); Assert.Equal(12, Settings.GetDictionaryItem(randStr, "key")); Settings.SetDictionary(SettingName, test); - Assert.Equal(JsonSerializer.Serialize(test, CoreData.SerializingOptions), File.ReadAllText(Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{SettingName}.json"))); + Assert.Equal(JsonSerializer.Serialize(test, Settings.SerializationOptions), File.ReadAllText(Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{SettingName}.json"))); Assert.Equal(test[keyArray[0]]?.sub.count, Settings.GetDictionary(SettingName)?[keyArray[0]]?.sub.count); Assert.Equal(test[keyArray[1]]?.sub.count, Settings.GetDictionaryItem(SettingName, keyArray[1])?.sub.count); Settings.SetDictionaryItem(SettingName, keyArray[0], test[keyArray[1]]); @@ -257,7 +257,7 @@ public void TestDictionarySettings(string SettingName, string[] keyArray, int[] Assert.True(Settings.DictionaryContainsValue(SettingName, test[keyArray[2]])); Assert.Equal( - JsonSerializer.Serialize(Settings.GetDictionary(SettingName), CoreData.SerializingOptions), + JsonSerializer.Serialize(Settings.GetDictionary(SettingName), Settings.SerializationOptions), File.ReadAllText(Path.Join(CoreData.UniGetUIUserConfigurationDirectory, $"{SettingName}.json")) ); diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs index 4233aadea7..19d1587533 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_Extras.cs @@ -86,7 +86,7 @@ public static void SetProxyCredentials(string username, string password) } } - private static JsonSerializerOptions SerializationOptions = new() + public static JsonSerializerOptions SerializationOptions = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver(), AllowTrailingCommas = true, diff --git a/src/UniGetUI.Core.Tools.Tests/MetaTests.cs b/src/UniGetUI.Core.Tools.Tests/MetaTests.cs index 8539f4ebf8..8259da2e57 100644 --- a/src/UniGetUI.Core.Tools.Tests/MetaTests.cs +++ b/src/UniGetUI.Core.Tools.Tests/MetaTests.cs @@ -17,8 +17,10 @@ public void TestJsonSerializationOptions() var lines = File.ReadAllLines(file); var jsonSerCount = lines.Count(x => x.Contains("JsonSerializer.Serialize")); var jsonDeserCount = lines.Count(x => x.Contains("JsonSerializer.Deserialize")); - var serialOptionsCount = lines.Count(x => x.Contains("CoreData.DefaultOptions")); - Assert.True((jsonSerCount + jsonDeserCount) <= serialOptionsCount, + var serialOptionsCount1 = lines.Count(x => x.Contains("SerializationHelpers.DefaultOptions")); + var serialOptionsCount2 = lines.Count(x => x.Contains("SerializationHelpers.ImportBundleOptions")); + var serialOptionsCount3 = lines.Count(x => x.Contains("SerializationOptions")); + Assert.True((jsonSerCount + jsonDeserCount) <= serialOptionsCount1 + serialOptionsCount2 + serialOptionsCount3, $"Failing on {file}. The specified file does not serialize and/or deserialize JSON with" + $" the proper CoreData.DefaultOptions set"); } diff --git a/src/UniGetUI.Core.Tools/SerializationHelpers.cs b/src/UniGetUI.Core.Tools/SerializationHelpers.cs index 0a7ae0113a..52a4bed9b9 100644 --- a/src/UniGetUI.Core.Tools/SerializationHelpers.cs +++ b/src/UniGetUI.Core.Tools/SerializationHelpers.cs @@ -28,7 +28,7 @@ private static string xml_to_json(string XML) var doc = new XmlDocument(); doc.LoadXml(XML); if (doc.DocumentElement is null) return "{'message': 'XmlDocument.DocumentElement was null'}"; - return JsonSerializer.Serialize(_convertXmlNode(doc.DocumentElement), DefaultOptions); + return JsonSerializer.Serialize(_convertXmlNode(doc.DocumentElement), SerializationHelpers.DefaultOptions); } private static object? _convertXmlNode(XmlNode node) From 8c6d8a8f4cbb7e1d2486340161b65e594c351d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 18 May 2025 23:50:31 +0200 Subject: [PATCH 05/17] Load installation options as a dynamic JSON, instead of using deserialization --- .../Packages/Classes/InstallationOptions.cs | 18 +++++---------- .../SerializableInstallationOptions.cs | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs index 5d8f7f5491..a0ac3f023f 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Runtime.InteropServices; using System.Text.Json; +using System.Text.Json.Nodes; using UniGetUI.Core.Data; using UniGetUI.Core.Language; using UniGetUI.Core.Logging; @@ -221,20 +222,13 @@ public void LoadFromDisk() try { if (!optionsFile.Exists) - { return; - } - - using FileStream inputStream = optionsFile.OpenRead(); - SerializableInstallationOptions_v1? options = JsonSerializer.Deserialize( - inputStream, SerializationHelpers.DefaultOptions); - - if (options is null) - { - throw new InvalidOperationException("Deserialized options cannot be null!"); - } - FromSerializable(options); + var rawData = File.ReadAllText(optionsFile.FullName); + JsonNode? jsonData = JsonNode.Parse(rawData); + if (jsonData is null) return; + var serializedOptions = SerializableInstallationOptions_v1.FromJsonString(jsonData); + FromSerializable(serializedOptions); } catch (JsonException) { diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs index fac30f6c12..70f97edd65 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Nodes; + namespace UniGetUI.PackageEngine.Serializable { public class SerializableInstallationOptions_v1 @@ -29,5 +31,25 @@ public SerializableInstallationOptions_v1 Copy() SkipMinorUpdates = SkipMinorUpdates, }; } + + public static SerializableInstallationOptions_v1 FromJsonString(JsonNode data) + { + var options = new SerializableInstallationOptions_v1(); + options.SkipHashCheck = data[nameof(SkipHashCheck)]?.GetValue() ?? false; + options.InteractiveInstallation = data[nameof(InteractiveInstallation)]?.GetValue() ?? false; + options.RunAsAdministrator = data[nameof(RunAsAdministrator)]?.GetValue() ?? false; + options.Architecture = data[nameof(Architecture)]?.GetValue() ?? ""; + options.InstallationScope = data[nameof(InstallationScope)]?.GetValue() ?? ""; + + options.CustomParameters = new List(); + foreach(var element in data[nameof(CustomParameters)]?.AsArray() ?? []) + if (element is not null) options.CustomParameters.Add(element.GetValue()); + + options.PreRelease = data[nameof(PreRelease)]?.GetValue() ?? false; + options.CustomInstallLocation = data[nameof(CustomInstallLocation)]?.GetValue() ?? ""; + options.Version = data[nameof(Version)]?.GetValue() ?? ""; + options.SkipMinorUpdates = data[nameof(SkipMinorUpdates)]?.GetValue() ?? false; + return options; + } } } From 3fbb1ce6042ef943f15a07eacc09d82222e0cf97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 18 May 2025 23:55:37 +0200 Subject: [PATCH 06/17] add missing line to InstallationOptions.ToString() --- .../Packages/Classes/InstallationOptions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs index a0ac3f023f..f7f04a57ac 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs @@ -226,7 +226,9 @@ public void LoadFromDisk() var rawData = File.ReadAllText(optionsFile.FullName); JsonNode? jsonData = JsonNode.Parse(rawData); - if (jsonData is null) return; + if (jsonData is null) + return; + var serializedOptions = SerializableInstallationOptions_v1.FromJsonString(jsonData); FromSerializable(serializedOptions); } @@ -256,7 +258,8 @@ public override string ToString() $"InstallationScope={InstallationScope};" + $"InstallationScope={CustomInstallLocation};" + $"CustomParameters={customparams};" + - $"RemoveDataOnUninstall={RemoveDataOnUninstall}>"; + $"RemoveDataOnUninstall={RemoveDataOnUninstall};" + + $"PreRelease={PreRelease}>"; } } } From 579bcd0927b2af4df2a87f94531a510cdbd4e9c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Tue, 20 May 2025 14:59:41 +0200 Subject: [PATCH 07/17] Create SerializableComponent: Better, unified API for serializable objects --- .../IInstallationOptions.cs | 8 +-- .../Packages/Classes/InstallationOptions.cs | 16 +++--- .../Packages/ImportedPackage.cs | 2 +- .../SerializableComponent.cs | 52 +++++++++++++++++++ .../SerializableInstallationOptions.cs | 30 +++++------ .../SerializablePackage_v1.cs | 2 +- ...UniGetUI.PackageEngine.Serializable.csproj | 6 +++ .../DialogPages/DialogHelper_Packages.cs | 4 +- .../Pages/DialogPages/InstallOptions.xaml.cs | 8 +-- 9 files changed, 92 insertions(+), 36 deletions(-) create mode 100644 src/UniGetUI.PackageEngine.Serializable/SerializableComponent.cs diff --git a/src/UniGetUI.PAckageEngine.Interfaces/IInstallationOptions.cs b/src/UniGetUI.PAckageEngine.Interfaces/IInstallationOptions.cs index 733d9b4b34..5f16e55490 100644 --- a/src/UniGetUI.PAckageEngine.Interfaces/IInstallationOptions.cs +++ b/src/UniGetUI.PAckageEngine.Interfaces/IInstallationOptions.cs @@ -20,14 +20,14 @@ public interface IInstallationOptions public IPackage Package { get; } /// - /// Loads and applies the options from the given SerializableInstallationOptions_v1 object to the current object. + /// Loads and applies the options from the given SerializableInstallationOptions object to the current object. /// - public void FromSerializable(SerializableInstallationOptions_v1 options); + public void FromSerializable(SerializableInstallationOptions options); /// - /// Returns a SerializableInstallationOptions_v1 object containing the options of the current instance. + /// Returns a SerializableInstallationOptions object containing the options of the current instance. /// - public SerializableInstallationOptions_v1 AsSerializable(); + public SerializableInstallationOptions AsSerializable(); /// /// Saves the current options to disk, asynchronously. diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs index f7f04a57ac..e4e0532aee 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs @@ -95,9 +95,9 @@ public static async Task FromPackageAsync(IPackage package, } /// - /// Returns a new InstallationOptions object from a given SerializableInstallationOptions_v1 and a package. + /// Returns a new InstallationOptions object from a given SerializableInstallationOptions and a package. /// - public static InstallationOptions FromSerialized(SerializableInstallationOptions_v1 options, IPackage package) + public static InstallationOptions FromSerialized(SerializableInstallationOptions options, IPackage package) { InstallationOptions instance = new(package); instance.FromSerializable(options); @@ -111,9 +111,9 @@ public static InstallationOptions CreateEmpty(IPackage package) } /// - /// Loads and applies the options from the given SerializableInstallationOptions_v1 object to the current object. + /// Loads and applies the options from the given SerializableInstallationOptions object to the current object. /// - public void FromSerializable(SerializableInstallationOptions_v1 options) + public void FromSerializable(SerializableInstallationOptions options) { SkipHashCheck = options.SkipHashCheck; InteractiveInstallation = options.InteractiveInstallation; @@ -145,11 +145,11 @@ public void FromSerializable(SerializableInstallationOptions_v1 options) } /// - /// Returns a SerializableInstallationOptions_v1 object containing the options of the current instance. + /// Returns a SerializableInstallationOptions object containing the options of the current instance. /// - public SerializableInstallationOptions_v1 AsSerializable() + public SerializableInstallationOptions AsSerializable() { - SerializableInstallationOptions_v1 options = new() + SerializableInstallationOptions options = new() { SkipHashCheck = SkipHashCheck, InteractiveInstallation = InteractiveInstallation, @@ -229,7 +229,7 @@ public void LoadFromDisk() if (jsonData is null) return; - var serializedOptions = SerializableInstallationOptions_v1.FromJsonString(jsonData); + var serializedOptions = SerializableInstallationOptions.FromJsonString(jsonData); FromSerializable(serializedOptions); } catch (JsonException) diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs index 6610eb48b3..ba54d65b1c 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs @@ -11,7 +11,7 @@ public partial class ImportedPackage : Package /// Construct an invalid package with a given name, id, version, source and manager, and an optional scope. /// public SerializableUpdatesOptions_v1 updates_options; - public SerializableInstallationOptions_v1 installation_options; + public SerializableInstallationOptions installation_options; private readonly string _version; diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableComponent.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableComponent.cs new file mode 100644 index 0000000000..6b67404266 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableComponent.cs @@ -0,0 +1,52 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using UniGetUI.Core.Data; + +namespace UniGetUI.PackageEngine.Serializable; + +public abstract class SerializableComponent +{ + /// + /// Creates a deep copy of the object + /// + /// A memory-independend copy of this + public abstract SerializableComponent Copy(); + + /// + /// Loads data for this object from a JsonNode object + /// + /// The JSON from which to load the data + public abstract void LoadFromJson(JsonNode data); + + /// + /// Serializes this object into a JsonNode object + /// + /// A pretty-formatted JSON string representing the current data + public string AsJsonString() + { + return JsonSerializer.Serialize(this, SerializationHelpers.DefaultOptions); + } + + /// + /// Serializes this object into a JsonNode object + /// + /// A pretty-formatted JSON string representing the current data + public JsonNode AsJsonNode() + { + return JsonNode.Parse(AsJsonString()) ?? throw new InvalidDataException("The JSON object could not be parsed to a JsonNode"); + } + + /// + /// Creates an instance of this object with the default data + /// + public SerializableComponent() {} + + /// + /// Creates an instance of this object, and loads the data from the given JsonNode object + /// + /// The JSON from which to load the data + public SerializableComponent(JsonNode data) + { + LoadFromJson(data); + } +} diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs index 70f97edd65..186b212295 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs @@ -2,7 +2,7 @@ namespace UniGetUI.PackageEngine.Serializable { - public class SerializableInstallationOptions_v1 + public class SerializableInstallationOptions: SerializableComponent { public bool SkipHashCheck { get; set; } public bool InteractiveInstallation { get; set; } @@ -15,7 +15,7 @@ public class SerializableInstallationOptions_v1 public string Version { get; set; } = ""; public bool SkipMinorUpdates { get; set; } - public SerializableInstallationOptions_v1 Copy() + public override SerializableInstallationOptions Copy() { return new() { @@ -32,24 +32,22 @@ public SerializableInstallationOptions_v1 Copy() }; } - public static SerializableInstallationOptions_v1 FromJsonString(JsonNode data) + public override void LoadFromJson(JsonNode data) { - var options = new SerializableInstallationOptions_v1(); - options.SkipHashCheck = data[nameof(SkipHashCheck)]?.GetValue() ?? false; - options.InteractiveInstallation = data[nameof(InteractiveInstallation)]?.GetValue() ?? false; - options.RunAsAdministrator = data[nameof(RunAsAdministrator)]?.GetValue() ?? false; - options.Architecture = data[nameof(Architecture)]?.GetValue() ?? ""; - options.InstallationScope = data[nameof(InstallationScope)]?.GetValue() ?? ""; + this.SkipHashCheck = data[nameof(SkipHashCheck)]?.GetValue() ?? false; + this.InteractiveInstallation = data[nameof(InteractiveInstallation)]?.GetValue() ?? false; + this.RunAsAdministrator = data[nameof(RunAsAdministrator)]?.GetValue() ?? false; + this.Architecture = data[nameof(Architecture)]?.GetValue() ?? ""; + this.InstallationScope = data[nameof(InstallationScope)]?.GetValue() ?? ""; - options.CustomParameters = new List(); + this.CustomParameters = new List(); foreach(var element in data[nameof(CustomParameters)]?.AsArray() ?? []) - if (element is not null) options.CustomParameters.Add(element.GetValue()); + if (element is not null) this.CustomParameters.Add(element.GetValue()); - options.PreRelease = data[nameof(PreRelease)]?.GetValue() ?? false; - options.CustomInstallLocation = data[nameof(CustomInstallLocation)]?.GetValue() ?? ""; - options.Version = data[nameof(Version)]?.GetValue() ?? ""; - options.SkipMinorUpdates = data[nameof(SkipMinorUpdates)]?.GetValue() ?? false; - return options; + this.PreRelease = data[nameof(PreRelease)]?.GetValue() ?? false; + this.CustomInstallLocation = data[nameof(CustomInstallLocation)]?.GetValue() ?? ""; + this.Version = data[nameof(Version)]?.GetValue() ?? ""; + this.SkipMinorUpdates = data[nameof(SkipMinorUpdates)]?.GetValue() ?? false; } } } diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs b/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs index 5e7b93c919..7d2780f588 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs @@ -32,7 +32,7 @@ public class SerializablePackage_v1 /// /// The InstallationOptions associated to this package /// - public SerializableInstallationOptions_v1 InstallationOptions { get; set; } = new(); + public SerializableInstallationOptions InstallationOptions { get; set; } = new(); /// /// The Updates preferences associated to this package diff --git a/src/UniGetUI.PackageEngine.Serializable/UniGetUI.PackageEngine.Serializable.csproj b/src/UniGetUI.PackageEngine.Serializable/UniGetUI.PackageEngine.Serializable.csproj index 409bafff79..8561cc9883 100644 --- a/src/UniGetUI.PackageEngine.Serializable/UniGetUI.PackageEngine.Serializable.csproj +++ b/src/UniGetUI.PackageEngine.Serializable/UniGetUI.PackageEngine.Serializable.csproj @@ -5,4 +5,10 @@ + + + + + + diff --git a/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs b/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs index 0242cd1f68..2d22ef5a05 100644 --- a/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs +++ b/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs @@ -50,10 +50,10 @@ public static async Task ShowInstallOptions_ImportedPackage return dialogResult; } - private static async Task<(SerializableInstallationOptions_v1, ContentDialogResult)> ShowInstallOptions( + private static async Task<(SerializableInstallationOptions, ContentDialogResult)> ShowInstallOptions( IPackage package, OperationType operation, - SerializableInstallationOptions_v1 options) + SerializableInstallationOptions options) { InstallOptionsPage OptionsPage = new(package, operation, options); diff --git a/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml.cs b/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml.cs index 2d1ddf7558..676c951dd9 100644 --- a/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml.cs +++ b/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml.cs @@ -18,15 +18,15 @@ namespace UniGetUI.Interface.Dialogs /// public sealed partial class InstallOptionsPage : Page { - public SerializableInstallationOptions_v1 Options; + public SerializableInstallationOptions Options; public IPackage Package; public event EventHandler? Close; private readonly OperationType Operation; private readonly string packageInstallLocation; private bool _uiLoaded; - public InstallOptionsPage(IPackage package, SerializableInstallationOptions_v1 options) : this(package, OperationType.None, options) { } - public InstallOptionsPage(IPackage package, OperationType operation, SerializableInstallationOptions_v1 options) + public InstallOptionsPage(IPackage package, SerializableInstallationOptions options) : this(package, OperationType.None, options) { } + public InstallOptionsPage(IPackage package, OperationType operation, SerializableInstallationOptions options) { Package = package; InitializeComponent(); @@ -161,7 +161,7 @@ private async Task LoadVersions() VersionProgress.Visibility = Visibility.Collapsed; } - public async Task GetUpdatedOptions(bool updateIgnoredUpdates = true) + public async Task GetUpdatedOptions(bool updateIgnoredUpdates = true) { Options.RunAsAdministrator = AdminCheckBox?.IsChecked ?? false; Options.InteractiveInstallation = InteractiveCheckBox?.IsChecked ?? false; From 393ce8a5f2b372962ff36402fce5224dd3b0dd7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Tue, 20 May 2025 15:24:19 +0200 Subject: [PATCH 08/17] Add tests for SerializableInstallationOptions --- .../UniGetUI.Core.Classes.Tests.csproj | 2 - .../Packages/Classes/InstallationOptions.cs | 2 +- .../TestSerializableInstallationOptions.cs | 57 +++++++++++++++++++ ...UI.PackageEngine.Serializable.Tests.csproj | 24 ++++++++ .../SerializableComponent.cs | 10 ++-- .../SerializableInstallationOptions.cs | 10 +++- src/UniGetUI.sln | 10 ++++ 7 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs create mode 100644 src/UniGetUI.PackageEngine.Serializable.Tests/UniGetUI.PackageEngine.Serializable.Tests.csproj diff --git a/src/UniGetUI.Core.Classes.Tests/UniGetUI.Core.Classes.Tests.csproj b/src/UniGetUI.Core.Classes.Tests/UniGetUI.Core.Classes.Tests.csproj index 81246f6a1f..0d3c508e8d 100644 --- a/src/UniGetUI.Core.Classes.Tests/UniGetUI.Core.Classes.Tests.csproj +++ b/src/UniGetUI.Core.Classes.Tests/UniGetUI.Core.Classes.Tests.csproj @@ -1,7 +1,5 @@ - - false true diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs index e4e0532aee..03881b8822 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs @@ -229,7 +229,7 @@ public void LoadFromDisk() if (jsonData is null) return; - var serializedOptions = SerializableInstallationOptions.FromJsonString(jsonData); + var serializedOptions = new SerializableInstallationOptions(jsonData); FromSerializable(serializedOptions); } catch (JsonException) diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs new file mode 100644 index 0000000000..59341e4dc2 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs @@ -0,0 +1,57 @@ +using System.Text.Json.Nodes; + +namespace UniGetUI.PackageEngine.Serializable.Tests; + +public class TestSerializableInstallationOptions +{ + [Theory] + [InlineData(false, false, "", "", "", "", "", "", false, false, false, "")] + [InlineData(true, true, "testval", "testval", "testval", "testval", "testval", "testval", true, true, true, + "testval")] + [InlineData(true, false, "true", "helloWorld", "testval", "heheheheeheh", "--parse\n-int", "12", false, true, false, + "4.4.0-beta2")] + public void ToAndFromJsonNode(bool a, bool b, string c, string d, string e, string f, string g, string h, bool i, + bool j, bool k, string l) + { + var originalObject1 = new SerializableInstallationOptions() + { + SkipHashCheck = a, + Architecture = h, + CustomInstallLocation = c, + CustomParameters = [d, e, f], + InstallationScope = g, + InteractiveInstallation = b, + PreRelease = i, + RunAsAdministrator = j, + SkipMinorUpdates = k, + Version = l + }; + + var object2 = new SerializableInstallationOptions(); + string contents = originalObject1.AsJsonString(); + Assert.NotEmpty(contents); + var jsonContent = JsonNode.Parse(contents); + Assert.NotNull(jsonContent); + object2.LoadFromJson(jsonContent); + + AreEqual(originalObject1, object2); + + var object3 = new SerializableInstallationOptions(originalObject1.AsJsonNode()); + + AreEqual(originalObject1, object3); + } + + private static void AreEqual(SerializableInstallationOptions o1, SerializableInstallationOptions o2) + { + Assert.Equal(o1.SkipHashCheck, o2.SkipHashCheck); + Assert.Equal(o1.Architecture, o2.Architecture); + Assert.Equal(o1.CustomInstallLocation, o2.CustomInstallLocation); + Assert.Equal(o1.CustomParameters, o2.CustomParameters); + Assert.Equal(o1.InstallationScope, o2.InstallationScope); + Assert.Equal(o1.InteractiveInstallation, o2.InteractiveInstallation); + Assert.Equal(o1.PreRelease, o2.PreRelease); + Assert.Equal(o1.RunAsAdministrator, o2.RunAsAdministrator); + Assert.Equal(o1.SkipMinorUpdates, o2.SkipMinorUpdates); + Assert.Equal(o1.Version, o2.Version); + } +} diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/UniGetUI.PackageEngine.Serializable.Tests.csproj b/src/UniGetUI.PackageEngine.Serializable.Tests/UniGetUI.PackageEngine.Serializable.Tests.csproj new file mode 100644 index 0000000000..32dacaad25 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/UniGetUI.PackageEngine.Serializable.Tests.csproj @@ -0,0 +1,24 @@ + + + + net8.0-windows10.0.26100.0 + false + true + + + + + + + + + + + + + + + + + + diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableComponent.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableComponent.cs index 6b67404266..4e87811b86 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableComponent.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableComponent.cs @@ -4,13 +4,13 @@ namespace UniGetUI.PackageEngine.Serializable; -public abstract class SerializableComponent +public abstract class SerializableComponent where T: class { /// /// Creates a deep copy of the object /// /// A memory-independend copy of this - public abstract SerializableComponent Copy(); + public abstract T Copy(); /// /// Loads data for this object from a JsonNode object @@ -24,7 +24,7 @@ public abstract class SerializableComponent /// A pretty-formatted JSON string representing the current data public string AsJsonString() { - return JsonSerializer.Serialize(this, SerializationHelpers.DefaultOptions); + return JsonSerializer.Serialize(this as T ?? throw new InvalidCastException("Invalid type"), SerializationHelpers.DefaultOptions); } /// @@ -39,7 +39,9 @@ public JsonNode AsJsonNode() /// /// Creates an instance of this object with the default data /// - public SerializableComponent() {} + public SerializableComponent() + { + } /// /// Creates an instance of this object, and loads the data from the given JsonNode object diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs index 186b212295..6b4ad5754d 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs @@ -2,7 +2,7 @@ namespace UniGetUI.PackageEngine.Serializable { - public class SerializableInstallationOptions: SerializableComponent + public class SerializableInstallationOptions: SerializableComponent { public bool SkipHashCheck { get; set; } public bool InteractiveInstallation { get; set; } @@ -49,5 +49,13 @@ public override void LoadFromJson(JsonNode data) this.Version = data[nameof(Version)]?.GetValue() ?? ""; this.SkipMinorUpdates = data[nameof(SkipMinorUpdates)]?.GetValue() ?? false; } + + public SerializableInstallationOptions() : base() + { + } + + public SerializableInstallationOptions(JsonNode data) : base(data) + { + } } } diff --git a/src/UniGetUI.sln b/src/UniGetUI.sln index 49c002fa9f..7324d653bb 100644 --- a/src/UniGetUI.sln +++ b/src/UniGetUI.sln @@ -101,6 +101,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniGetUI.PackageEngine.Oper EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniGetUI.Interface.Telemetry", "UniGetUI.Interface.Telemetry\UniGetUI.Interface.Telemetry.csproj", "{3C8BF564-B4B5-44A7-9D8C-102C2F820EAF}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{015B44EE-32AE-4105-9016-49140743CAF9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniGetUI.PackageEngine.Serializable.Tests", "UniGetUI.PackageEngine.Serializable.Tests\UniGetUI.PackageEngine.Serializable.Tests.csproj", "{F1610A61-5444-4C11-9447-13CCA327887E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -263,6 +267,10 @@ Global {3C8BF564-B4B5-44A7-9D8C-102C2F820EAF}.Debug|x64.Build.0 = Debug|x64 {3C8BF564-B4B5-44A7-9D8C-102C2F820EAF}.Release|x64.ActiveCfg = Release|x64 {3C8BF564-B4B5-44A7-9D8C-102C2F820EAF}.Release|x64.Build.0 = Release|x64 + {F1610A61-5444-4C11-9447-13CCA327887E}.Debug|x64.ActiveCfg = Debug|x64 + {F1610A61-5444-4C11-9447-13CCA327887E}.Debug|x64.Build.0 = Debug|x64 + {F1610A61-5444-4C11-9447-13CCA327887E}.Release|x64.ActiveCfg = Release|x64 + {F1610A61-5444-4C11-9447-13CCA327887E}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -309,6 +317,8 @@ Global {E337A71E-3C30-4315-B8F1-57CBC5CF50A6} = {95168D0B-1B2C-4295-B6D4-D5BAF781B9FA} {727866B8-BBD5-43B9-933A-78199F65429C} = {7940E867-EEBA-4AFD-9904-1536F003239C} {3C8BF564-B4B5-44A7-9D8C-102C2F820EAF} = {8CF74C87-534F-4017-A4ED-F2918025E31A} + {015B44EE-32AE-4105-9016-49140743CAF9} = {7940E867-EEBA-4AFD-9904-1536F003239C} + {F1610A61-5444-4C11-9447-13CCA327887E} = {015B44EE-32AE-4105-9016-49140743CAF9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D044BB14-0B37-47E5-A579-8B30FCBA1F9F} From 926c679c1e6a5e7ffe630df2c58ec111fb5e7fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Tue, 20 May 2025 15:35:26 +0200 Subject: [PATCH 09/17] Add more tests to SerializableInstallationOptions, ensure proper JSON support --- .../TestSerializableInstallationOptions.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs index 59341e4dc2..c9955f30ba 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs @@ -41,6 +41,55 @@ public void ToAndFromJsonNode(bool a, bool b, string c, string d, string e, stri AreEqual(originalObject1, object3); } + [Theory] + [InlineData("{}", false, false, "", "", "", "", "", "", false, false, false, "")] + [InlineData(""" + { + "SkipHashCheck": true, + "InteractiveInstallation": true, + "RunAsAdministrator": false, + "Architecture": "lol", + "InstallationScope": "", + "CustomParameters": [ + "a" + ] + } + """, true, true, "", "a", "", "", "", "lol", false, false, false, "")] + + [InlineData(""" + { + "PreRelease": false, + "CustomInstallLocation": "", + "Version": "heyheyhey", + "SkipMinorUpdates": true, + "UNKNOWN_VAL1": true, + "UNKNOWN_VAL2": null, + "UNKNOWN_VAL3": 22, + "UNKNOWN_VAL4": "hehe" + } + """, false, false, "", "", "", "", + "", "", false, false, true, "heyheyhey")] + public void FromJson(string JSON, bool hash, bool inter, string installLoc, string arg1, string arg2, string arg3, string scope, string arch, bool pre, bool admin, bool skipMin, string ver) + { + Assert.NotEmpty(JSON); + var jsonContent = JsonNode.Parse(JSON); + Assert.NotNull(jsonContent); + var o2 = new SerializableInstallationOptions(jsonContent); + + var list = new List() { arg1, arg2, arg3 }.Where(x => x.Any()); + + Assert.Equal(hash, o2.SkipHashCheck); + Assert.Equal(arch, o2.Architecture); + Assert.Equal(installLoc, o2.CustomInstallLocation); + Assert.Equal(list, o2.CustomParameters); + Assert.Equal(scope, o2.InstallationScope); + Assert.Equal(inter, o2.InteractiveInstallation); + Assert.Equal(pre, o2.PreRelease); + Assert.Equal(admin, o2.RunAsAdministrator); + Assert.Equal(skipMin, o2.SkipMinorUpdates); + Assert.Equal(ver, o2.Version); + } + private static void AreEqual(SerializableInstallationOptions o1, SerializableInstallationOptions o2) { Assert.Equal(o1.SkipHashCheck, o2.SkipHashCheck); From 16698c5e482a74d57a0eeecf2aff08f683b5b759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Tue, 20 May 2025 15:44:44 +0200 Subject: [PATCH 10/17] Add tests to SerializableIncompatiblePackage --- .../IPackage.cs | 2 +- .../Packages/InvalidImportedPackage.cs | 6 +- .../Packages/Package.cs | 4 +- .../TestIncompatiblePackage.cs | 77 +++++++++++++++++++ .../TestSerializableInstallationOptions.cs | 5 +- .../SerializableBundle_v1.cs | 2 +- .../SerializableIncompatiblePackage.cs | 37 +++++++++ .../SerializableIncompatiblePackage_v1.cs | 10 --- .../SerializablePackage_v1.cs | 4 +- .../Pages/SoftwarePages/PackageBundlesPage.cs | 4 +- 10 files changed, 128 insertions(+), 23 deletions(-) create mode 100644 src/UniGetUI.PackageEngine.Serializable.Tests/TestIncompatiblePackage.cs create mode 100644 src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage.cs delete mode 100644 src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage_v1.cs diff --git a/src/UniGetUI.PAckageEngine.Interfaces/IPackage.cs b/src/UniGetUI.PAckageEngine.Interfaces/IPackage.cs index 2a1d0afd5b..715d4c9ce3 100644 --- a/src/UniGetUI.PAckageEngine.Interfaces/IPackage.cs +++ b/src/UniGetUI.PAckageEngine.Interfaces/IPackage.cs @@ -151,6 +151,6 @@ public interface IPackage : INotifyPropertyChanged, IEquatable public SerializablePackage_v1 AsSerializable(); - public SerializableIncompatiblePackage_v1 AsSerializable_Incompatible(); + public SerializableIncompatiblePackage AsSerializable_Incompatible(); } } diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/InvalidImportedPackage.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/InvalidImportedPackage.cs index f5a970e23c..353a4cc21b 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/InvalidImportedPackage.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/InvalidImportedPackage.cs @@ -55,7 +55,7 @@ public bool IsChecked public event PropertyChangedEventHandler? PropertyChanged; - public InvalidImportedPackage(SerializableIncompatiblePackage_v1 data, IManagerSource source) + public InvalidImportedPackage(SerializableIncompatiblePackage data, IManagerSource source) { Name = data.Name; Id = data.Id.Split('\\')[^1]; @@ -80,9 +80,9 @@ public SerializablePackage_v1 AsSerializable() throw new NotImplementedException(); } - public SerializableIncompatiblePackage_v1 AsSerializable_Incompatible() + public SerializableIncompatiblePackage AsSerializable_Incompatible() { - return new SerializableIncompatiblePackage_v1 + return new SerializableIncompatiblePackage { Name = Name, Id = Id, diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs index df3800e321..062d1350fb 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs @@ -319,9 +319,9 @@ public virtual SerializablePackage_v1 AsSerializable() }; } - public SerializableIncompatiblePackage_v1 AsSerializable_Incompatible() + public SerializableIncompatiblePackage AsSerializable_Incompatible() { - return new SerializableIncompatiblePackage_v1 + return new SerializableIncompatiblePackage { Id = Id, Name = Name, diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestIncompatiblePackage.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestIncompatiblePackage.cs new file mode 100644 index 0000000000..db04fcca8d --- /dev/null +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestIncompatiblePackage.cs @@ -0,0 +1,77 @@ +using System.Text.Json.Nodes; +using UniGetUI.PackageEngine.Classes.Serializable; + +namespace UniGetUI.PackageEngine.Serializable.Tests; + +public class TestIncompatiblePackage +{ + [Theory] + [InlineData("", "", "", "")] + [InlineData("UniGetUI", "MartiCliment.UniGetUI.Pre-Release", "3.2.1-beta1", "WinGet")] + [InlineData("Mr. Trololo", "\n\n\n", "\x12", "\r")] + public void ToAndFromJsonNode(string id, string name, string version, string manager) + { + var originalObject1 = new SerializableIncompatiblePackage() + { + Id = id, + Name = name, + Source = manager, + Version = version + }; + + var object2 = new SerializableIncompatiblePackage(); + string contents = originalObject1.AsJsonString(); + Assert.NotEmpty(contents); + var jsonContent = JsonNode.Parse(contents); + Assert.NotNull(jsonContent); + object2.LoadFromJson(jsonContent); + AreEqual(originalObject1, object2); + + var object3 = new SerializableIncompatiblePackage(originalObject1.AsJsonNode()); + AreEqual(originalObject1, object3); + + var object4 = originalObject1.Copy(); + AreEqual(originalObject1, object4); + } + + [Theory] + [InlineData("{}", "", "", "", "")] + [InlineData(""" + { + "Name": "name", + "Id": "true" + } + """, "true", "name", "", "")] + + [InlineData(""" + { + "Version": "false", + "Source": "lol", + "UNKNOWN_VAL1": true, + "UNKNOWN_VAL2": null, + "UNKNOWN_VAL3": 22, + "UNKNOWN_VAL4": "hehe" + } + """, "", "", "false", "lol")] + public void FromJson(string JSON, string id, string name, string version, string manager) + { + Assert.NotEmpty(JSON); + var jsonContent = JsonNode.Parse(JSON); + Assert.NotNull(jsonContent); + var o2 = new SerializableIncompatiblePackage(jsonContent); + + Assert.Equal(name, o2.Name); + Assert.Equal(id, o2.Id); + Assert.Equal(manager, o2.Source); + Assert.Equal(version, o2.Version); + + } + + private static void AreEqual(SerializableIncompatiblePackage o1, SerializableIncompatiblePackage o2) + { + Assert.Equal(o1.Name, o2.Name); + Assert.Equal(o1.Id, o2.Id); + Assert.Equal(o1.Source, o2.Source); + Assert.Equal(o1.Version, o2.Version); + } +} diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs index c9955f30ba..46d1534166 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs @@ -33,12 +33,13 @@ public void ToAndFromJsonNode(bool a, bool b, string c, string d, string e, stri var jsonContent = JsonNode.Parse(contents); Assert.NotNull(jsonContent); object2.LoadFromJson(jsonContent); - AreEqual(originalObject1, object2); var object3 = new SerializableInstallationOptions(originalObject1.AsJsonNode()); - AreEqual(originalObject1, object3); + + var object4 = originalObject1.Copy(); + AreEqual(originalObject1, object4); } [Theory] diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs index 6b2a3cc848..e987b0171b 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs @@ -10,7 +10,7 @@ public class SerializableBundle_v1 public double export_version { get; set; } = -1; public List packages { get; set; } = []; public string incompatible_packages_info { get; set; } = "Incompatible packages cannot be installed from WingetUI, but they have been listed here for logging purposes."; - public List incompatible_packages { get; set; } = []; + public List incompatible_packages { get; set; } = []; } } diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage.cs new file mode 100644 index 0000000000..0db6145131 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Nodes; +using UniGetUI.PackageEngine.Serializable; + +namespace UniGetUI.PackageEngine.Classes.Serializable +{ + public class SerializableIncompatiblePackage: SerializableComponent + { + public string Id { get; set; } = ""; + public string Name { get; set; } = ""; + public string Version { get; set; } = ""; + public string Source { get; set; } = ""; + + public override SerializableIncompatiblePackage Copy() + { + return new() + { + Id = this.Id, Name = this.Name, Version = this.Version, Source = this.Source, + }; + } + + public override void LoadFromJson(JsonNode data) + { + this.Id = data[nameof(Id)]?.GetValue() ?? ""; + this.Name = data[nameof(Name)]?.GetValue() ?? ""; + this.Version = data[nameof(Version)]?.GetValue() ?? ""; + this.Source = data[nameof(Source)]?.GetValue() ?? ""; + } + + public SerializableIncompatiblePackage(JsonNode data) : base(data) + { + } + + public SerializableIncompatiblePackage(): base() + { + } + } +} diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage_v1.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage_v1.cs deleted file mode 100644 index 1796e31784..0000000000 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage_v1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace UniGetUI.PackageEngine.Classes.Serializable -{ - public class SerializableIncompatiblePackage_v1 - { - public string Id { get; set; } = ""; - public string Name { get; set; } = ""; - public string Version { get; set; } = ""; - public string Source { get; set; } = ""; - } -} diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs b/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs index 7d2780f588..44686fc373 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs @@ -43,9 +43,9 @@ public class SerializablePackage_v1 /// Returns an equivalent copy of the current package as an Invalid Serializable Package. /// The reverse operation is not possible, since data is lost. /// - public SerializableIncompatiblePackage_v1 GetInvalidEquivalent() + public SerializableIncompatiblePackage GetInvalidEquivalent() { - return new SerializableIncompatiblePackage_v1 + return new SerializableIncompatiblePackage { Id = Id, Name = Name, diff --git a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs index e6b5376c00..ab8224f0a2 100644 --- a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs +++ b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs @@ -666,7 +666,7 @@ public async Task AddFromBundle(string content, BundleFormatType format) packages.Add(PackageFromSerializable(DeserializedPackage)); } - foreach (SerializableIncompatiblePackage_v1 DeserializedPackage in DeserializedData + foreach (SerializableIncompatiblePackage DeserializedPackage in DeserializedData .incompatible_packages) { packages.Add(InvalidPackageFromSerializable(DeserializedPackage, NullSource.Instance)); @@ -706,7 +706,7 @@ public static IPackage PackageFromSerializable(SerializablePackage_v1 raw_packag return new ImportedPackage(raw_package, manager, source); } - public static IPackage InvalidPackageFromSerializable(SerializableIncompatiblePackage_v1 raw_package, IManagerSource source) + public static IPackage InvalidPackageFromSerializable(SerializableIncompatiblePackage raw_package, IManagerSource source) { return new InvalidImportedPackage(raw_package, source); } From 52d3522a445582cbb8872a7f7006970df46454de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Tue, 20 May 2025 19:44:41 +0200 Subject: [PATCH 11/17] Migrate SerializableUpdatesOptions_v1 --- .../Packages/ImportedPackage.cs | 2 +- .../Packages/Package.cs | 2 +- .../TestSerializableUpdatesOptions.cs | 69 +++++++++++++++++++ .../SerializablePackage_v1.cs | 2 +- .../SerializableUpdatesOptions.cs | 30 ++++++++ .../SerializableUpdatesOptions_v1.cs | 8 --- 6 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs create mode 100644 src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions.cs delete mode 100644 src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions_v1.cs diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs index ba54d65b1c..a0f5bf102f 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs @@ -10,7 +10,7 @@ public partial class ImportedPackage : Package /// /// Construct an invalid package with a given name, id, version, source and manager, and an optional scope. /// - public SerializableUpdatesOptions_v1 updates_options; + public SerializableUpdatesOptions updates_options; public SerializableInstallationOptions installation_options; private readonly string _version; diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs index 062d1350fb..90b59d5a1b 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs @@ -311,7 +311,7 @@ public virtual SerializablePackage_v1 AsSerializable() Source = Source.Name, ManagerName = Manager.Name, InstallationOptions = InstallationOptions.FromPackage(this).AsSerializable(), - Updates = new SerializableUpdatesOptions_v1 + Updates = new SerializableUpdatesOptions { IgnoredVersion = GetIgnoredUpdatesVersionAsync().GetAwaiter().GetResult(), UpdatesIgnored = HasUpdatesIgnoredAsync().GetAwaiter().GetResult(), diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs new file mode 100644 index 0000000000..01bf7f52a7 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs @@ -0,0 +1,69 @@ +using System.Text.Json.Nodes; +using UniGetUI.PackageEngine.Classes.Serializable; + +namespace UniGetUI.PackageEngine.Serializable.Tests; + +public class TestSerializableUpdatesOptions +{ + [Theory] + [InlineData(false, "")] + [InlineData(true, "MartiCliment.UniGetUI.Pre-Release")] + [InlineData(false, "hey")] + [InlineData(true, "")] + public void ToAndFromJsonNode(bool ign, string ver) + { + var originalObject1 = new SerializableUpdatesOptions() + { + UpdatesIgnored = ign, + IgnoredVersion = ver, + }; + + var object2 = new SerializableUpdatesOptions(); + string contents = originalObject1.AsJsonString(); + Assert.NotEmpty(contents); + var jsonContent = JsonNode.Parse(contents); + Assert.NotNull(jsonContent); + object2.LoadFromJson(jsonContent); + AreEqual(originalObject1, object2); + + var object3 = new SerializableUpdatesOptions(originalObject1.AsJsonNode()); + AreEqual(originalObject1, object3); + + var object4 = originalObject1.Copy(); + AreEqual(originalObject1, object4); + } + + [Theory] + [InlineData("{}", false, "")] + [InlineData(""" + { + "UpdatesIgnored": true + } + """, true, "")] + + [InlineData(""" + { + "IgnoredVersion": "lol", + "UNKNOWN_VAL1": true, + "UNKNOWN_VAL2": null, + "UNKNOWN_VAL3": 22, + "UNKNOWN_VAL4": "hehe" + } + """, false, "lol")] + public void FromJson(string JSON, bool ign, string ver) + { + Assert.NotEmpty(JSON); + var jsonContent = JsonNode.Parse(JSON); + Assert.NotNull(jsonContent); + var o2 = new SerializableUpdatesOptions(jsonContent); + + Assert.Equal(ign, o2.UpdatesIgnored); + Assert.Equal(ver, o2.IgnoredVersion); + } + + private static void AreEqual(SerializableUpdatesOptions o1, SerializableUpdatesOptions o2) + { + Assert.Equal(o1.IgnoredVersion, o2.IgnoredVersion); + Assert.Equal(o1.UpdatesIgnored, o2.UpdatesIgnored); + } +} diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs b/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs index 44686fc373..31524967fe 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs @@ -37,7 +37,7 @@ public class SerializablePackage_v1 /// /// The Updates preferences associated to this package /// - public SerializableUpdatesOptions_v1 Updates { get; set; } = new(); + public SerializableUpdatesOptions Updates { get; set; } = new(); /// /// Returns an equivalent copy of the current package as an Invalid Serializable Package. diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions.cs new file mode 100644 index 0000000000..5ec3b8ced7 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions.cs @@ -0,0 +1,30 @@ +using System.Text.Json.Nodes; +using UniGetUI.PackageEngine.Serializable; + +namespace UniGetUI.PackageEngine.Classes.Serializable +{ + public class SerializableUpdatesOptions: SerializableComponent + { + public bool UpdatesIgnored { get; set; } + public string IgnoredVersion { get; set; } = ""; + + public override SerializableUpdatesOptions Copy() + { + return new() { UpdatesIgnored = this.UpdatesIgnored, IgnoredVersion = this.IgnoredVersion }; + } + + public override void LoadFromJson(JsonNode data) + { + this.UpdatesIgnored = data[nameof(UpdatesIgnored)]?.GetValue() ?? false; + this.IgnoredVersion = data[nameof(IgnoredVersion)]?.GetValue() ?? ""; + } + + public SerializableUpdatesOptions() : base() + { + } + + public SerializableUpdatesOptions(JsonNode data) : base(data) + { + } + } +} diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions_v1.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions_v1.cs deleted file mode 100644 index 2e067c83bc..0000000000 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions_v1.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace UniGetUI.PackageEngine.Classes.Serializable -{ - public class SerializableUpdatesOptions_v1 - { - public bool UpdatesIgnored { get; set; } - public string IgnoredVersion { get; set; } = ""; - } -} From 04cf96fa0c331ccc8281b7bc4f20cf58ef72d10d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Tue, 20 May 2025 20:04:50 +0200 Subject: [PATCH 12/17] Migrate SerializablePackage --- .../IPackage.cs | 2 +- .../Packages/ImportedPackage.cs | 6 +- .../Packages/InvalidImportedPackage.cs | 2 +- .../Packages/Package.cs | 4 +- .../TestSerializableInstallationOptions.cs | 2 +- .../TestSerializablePackage.cs | 96 +++++++++++++++++++ .../TestSerializableUpdatesOptions.cs | 2 +- .../SerializableBundle_v1.cs | 2 +- .../SerializablePackage.cs | 67 +++++++++++++ .../SerializablePackage_v1.cs | 57 ----------- .../Pages/SoftwarePages/PackageBundlesPage.cs | 4 +- 11 files changed, 175 insertions(+), 69 deletions(-) create mode 100644 src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs create mode 100644 src/UniGetUI.PackageEngine.Serializable/SerializablePackage.cs delete mode 100644 src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs diff --git a/src/UniGetUI.PAckageEngine.Interfaces/IPackage.cs b/src/UniGetUI.PAckageEngine.Interfaces/IPackage.cs index 715d4c9ce3..2a28c5b3ab 100644 --- a/src/UniGetUI.PAckageEngine.Interfaces/IPackage.cs +++ b/src/UniGetUI.PAckageEngine.Interfaces/IPackage.cs @@ -149,7 +149,7 @@ public interface IPackage : INotifyPropertyChanged, IEquatable /// False if the update is a major update or the update doesn't exist, true if it's a minor update public bool IsUpdateMinor(); - public SerializablePackage_v1 AsSerializable(); + public SerializablePackage AsSerializable(); public SerializableIncompatiblePackage AsSerializable_Incompatible(); } diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs index a0f5bf102f..c9bbc9c16a 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/ImportedPackage.cs @@ -27,7 +27,7 @@ public override string VersionString } } - public ImportedPackage(SerializablePackage_v1 raw_data, IPackageManager manager, IManagerSource source) + public ImportedPackage(SerializablePackage raw_data, IPackageManager manager, IManagerSource source) : base(raw_data.Name, raw_data.Id, raw_data.Version, source, manager) { _version = raw_data.Version; @@ -49,9 +49,9 @@ public async Task RegisterAndGetPackageAsync() return new Package(Name, Id, _version, Source, Manager); } - public override SerializablePackage_v1 AsSerializable() + public override SerializablePackage AsSerializable() { - return new SerializablePackage_v1 + return new SerializablePackage { Id = Id, Name = Name, diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/InvalidImportedPackage.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/InvalidImportedPackage.cs index 353a4cc21b..d2266fbb63 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/InvalidImportedPackage.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/InvalidImportedPackage.cs @@ -75,7 +75,7 @@ public Task AddToIgnoredUpdatesAsync(string version = "*") return Task.CompletedTask; } - public SerializablePackage_v1 AsSerializable() + public SerializablePackage AsSerializable() { throw new NotImplementedException(); } diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs index 90b59d5a1b..5f099f5709 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs @@ -301,9 +301,9 @@ public virtual bool IsUpdateMinor() (NormalizedVersion.Patch != NormalizedNewVersion.Patch || NormalizedVersion.Remainder != NormalizedNewVersion.Remainder); } - public virtual SerializablePackage_v1 AsSerializable() + public virtual SerializablePackage AsSerializable() { - return new SerializablePackage_v1 + return new SerializablePackage { Id = Id, Name = Name, diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs index 46d1534166..4f4dab705a 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs @@ -91,7 +91,7 @@ public void FromJson(string JSON, bool hash, bool inter, string installLoc, stri Assert.Equal(ver, o2.Version); } - private static void AreEqual(SerializableInstallationOptions o1, SerializableInstallationOptions o2) + public static void AreEqual(SerializableInstallationOptions o1, SerializableInstallationOptions o2) { Assert.Equal(o1.SkipHashCheck, o2.SkipHashCheck); Assert.Equal(o1.Architecture, o2.Architecture); diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs new file mode 100644 index 0000000000..5f791ffd5d --- /dev/null +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs @@ -0,0 +1,96 @@ +using System.Text.Json.Nodes; +using UniGetUI.PackageEngine.Classes.Serializable; + +namespace UniGetUI.PackageEngine.Serializable.Tests; + +public class TestSerializablePackage +{ + public static SerializableInstallationOptions TestOptions = new() + { + SkipHashCheck = true, + CustomParameters = ["a", "b", "c"], + Architecture = "ia64", + Version = "-1", + RunAsAdministrator = true + }; + + public static SerializableUpdatesOptions TestUpdatesOpts = new() + { + UpdatesIgnored = true, + IgnoredVersion = "Espresso Macchiato", + }; + + [Theory] + [InlineData("", "", "", "", "")] + [InlineData("UniGetUI", "MartiCliment.UniGetUI.Pre-Release", "3.2.1-beta1", "WinGet", "winget")] + [InlineData("Mr. Trololo", "\n\n\n", "\x12", "\r", "beanz")] + public void ToAndFromJsonNode(string id, string name, string version, string manager, string source) + { + var originalObject1 = new SerializablePackage() + { + Id = id, + Name = name, + Source = manager, + Version = version + }; + + var object2 = new SerializablePackage(); + string contents = originalObject1.AsJsonString(); + Assert.NotEmpty(contents); + var jsonContent = JsonNode.Parse(contents); + Assert.NotNull(jsonContent); + object2.LoadFromJson(jsonContent); + AreEqual(originalObject1, object2); + + var object3 = new SerializablePackage(originalObject1.AsJsonNode()); + AreEqual(originalObject1, object3); + + var object4 = originalObject1.Copy(); + AreEqual(originalObject1, object4); + } + + [Theory] + [InlineData("{}", "", "", "", "", "")] + [InlineData(""" + { + "Name": "name", + "Id": "true" + } + """, "true", "name", "", "", "")] + + [InlineData(""" + { + "Version": "false", + "Source": "lol", + "PackageManager": "Rodolfo Chikilicuatre", + "UNKNOWN_VAL1": true, + "UNKNOWN_VAL2": null, + "UNKNOWN_VAL3": 22, + "UNKNOWN_VAL4": "hehe" + } + """, "", "", "false", "Rodolfo Chikilicuatre", "lol")] + public void FromJson(string JSON, string id, string name, string version, string manager, string source) + { + Assert.NotEmpty(JSON); + var jsonContent = JsonNode.Parse(JSON); + Assert.NotNull(jsonContent); + var o2 = new SerializablePackage(jsonContent); + + Assert.Equal(name, o2.Name); + Assert.Equal(id, o2.Id); + Assert.Equal(manager, o2.Source); + Assert.Equal(version, o2.Version); + + } + + private static void AreEqual(SerializablePackage o1, SerializablePackage o2) + { + Assert.Equal(o1.Name, o2.Name); + Assert.Equal(o1.Id, o2.Id); + Assert.Equal(o1.Source, o2.Source); + Assert.Equal(o1.Version, o2.Version); + Assert.Equal(o1.ManagerName, o2.ManagerName); + TestSerializableInstallationOptions.AreEqual(o1.InstallationOptions, o2.InstallationOptions); + TestSerializableUpdatesOptions.AreEqual(o1.Updates, o2.Updates); + } +} diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs index 01bf7f52a7..fb87d873c4 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs @@ -61,7 +61,7 @@ public void FromJson(string JSON, bool ign, string ver) Assert.Equal(ver, o2.IgnoredVersion); } - private static void AreEqual(SerializableUpdatesOptions o1, SerializableUpdatesOptions o2) + public static void AreEqual(SerializableUpdatesOptions o1, SerializableUpdatesOptions o2) { Assert.Equal(o1.IgnoredVersion, o2.IgnoredVersion); Assert.Equal(o1.UpdatesIgnored, o2.UpdatesIgnored); diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs index e987b0171b..ac0d647941 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs @@ -8,7 +8,7 @@ public class SerializableBundle_Data public class SerializableBundle_v1 { public double export_version { get; set; } = -1; - public List packages { get; set; } = []; + public List packages { get; set; } = []; public string incompatible_packages_info { get; set; } = "Incompatible packages cannot be installed from WingetUI, but they have been listed here for logging purposes."; public List incompatible_packages { get; set; } = []; diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializablePackage.cs b/src/UniGetUI.PackageEngine.Serializable/SerializablePackage.cs new file mode 100644 index 0000000000..81bb2bd373 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Serializable/SerializablePackage.cs @@ -0,0 +1,67 @@ +using System.Text.Json.Nodes; +using UniGetUI.PackageEngine.Serializable; + +namespace UniGetUI.PackageEngine.Classes.Serializable +{ + public class SerializablePackage: SerializableComponent + { + public string Id { get; set; } = ""; + public string Name { get; set; } = ""; + public string Version { get; set; } = ""; + public string Source { get; set; } = ""; + public string ManagerName { get; set; } = ""; + + public SerializableInstallationOptions InstallationOptions { get; set; } = new(); + public SerializableUpdatesOptions Updates { get; set; } = new(); + + public override SerializablePackage Copy() + { + return new SerializablePackage() + { + Name = this.Name, + Id = this.Id, + Version = this.Version, + Source = this.Source, + ManagerName = this.ManagerName, + InstallationOptions = this.InstallationOptions.Copy(), + Updates = this.Updates.Copy(), + }; + } + + public override void LoadFromJson(JsonNode data) + { + this.Name = data[nameof(Name)]?.GetValue() ?? ""; + this.Id = data[nameof(Id)]?.GetValue() ?? ""; + this.Version = data[nameof(Version)]?.GetValue() ?? ""; + this.Source = data[nameof(Source)]?.GetValue() ?? ""; + this.ManagerName = data[nameof(ManagerName)]?.GetValue() ?? ""; + + this.InstallationOptions = new(data[nameof(InstallationOptions)] ?? new JsonObject()); + this.Updates = new(data[nameof(Updates)] ?? new JsonObject()); + } + + public SerializablePackage() : base() + { + } + + public SerializablePackage(JsonNode data) : base(data) + { + } + + + /// + /// Returns an equivalent copy of the current package as an Invalid Serializable Package. + /// The reverse operation is not possible, since data is lost. + /// + public SerializableIncompatiblePackage GetInvalidEquivalent() + { + return new SerializableIncompatiblePackage + { + Id = Id, + Name = Name, + Version = Version, + Source = Source, + }; + } + } +} diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs b/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs deleted file mode 100644 index 31524967fe..0000000000 --- a/src/UniGetUI.PackageEngine.Serializable/SerializablePackage_v1.cs +++ /dev/null @@ -1,57 +0,0 @@ -using UniGetUI.PackageEngine.Serializable; - -namespace UniGetUI.PackageEngine.Classes.Serializable -{ - public class SerializablePackage_v1 - { - /// - /// The package full, valid identifier - /// - public string Id { get; set; } = ""; - - /// - /// The package display name - /// - public string Name { get; set; } = ""; - - /// - /// The installed version of the package - /// - public string Version { get; set; } = ""; - - /// - /// The name of the source, WITHOUT the Package Manager name - /// - public string Source { get; set; } = ""; - - /// - /// The name of the package manager - /// - public string ManagerName { get; set; } = ""; - - /// - /// The InstallationOptions associated to this package - /// - public SerializableInstallationOptions InstallationOptions { get; set; } = new(); - - /// - /// The Updates preferences associated to this package - /// - public SerializableUpdatesOptions Updates { get; set; } = new(); - - /// - /// Returns an equivalent copy of the current package as an Invalid Serializable Package. - /// The reverse operation is not possible, since data is lost. - /// - public SerializableIncompatiblePackage GetInvalidEquivalent() - { - return new SerializableIncompatiblePackage - { - Id = Id, - Name = Name, - Version = Version, - Source = Source, - }; - } - } -} diff --git a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs index ab8224f0a2..a89362b349 100644 --- a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs +++ b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs @@ -661,7 +661,7 @@ public async Task AddFromBundle(string content, BundleFormatType format) List packages = []; - foreach (SerializablePackage_v1 DeserializedPackage in DeserializedData.packages) + foreach (SerializablePackage DeserializedPackage in DeserializedData.packages) { packages.Add(PackageFromSerializable(DeserializedPackage)); } @@ -677,7 +677,7 @@ public async Task AddFromBundle(string content, BundleFormatType format) return DeserializedData.export_version; } - public static IPackage PackageFromSerializable(SerializablePackage_v1 raw_package) + public static IPackage PackageFromSerializable(SerializablePackage raw_package) { IPackageManager? manager = null; IManagerSource? source; From 84b7c36844641cd9515b77efdca993715bbf8963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Tue, 20 May 2025 20:14:37 +0200 Subject: [PATCH 13/17] Better tests for SerializablePackage --- .../TestSerializablePackage.cs | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs index 5f791ffd5d..5ce9f4de1c 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs @@ -50,26 +50,33 @@ public void ToAndFromJsonNode(string id, string name, string version, string man } [Theory] - [InlineData("{}", "", "", "", "", "")] + [InlineData("{}", "", "", "", "", "", false, "")] [InlineData(""" { "Name": "name", - "Id": "true" + "Id": "true", + "Updates" : { + "IgnoredVersion": "Hey" + } } - """, "true", "name", "", "", "")] + """, "true", "name", "", "", "", false, "Hey")] [InlineData(""" - { - "Version": "false", - "Source": "lol", - "PackageManager": "Rodolfo Chikilicuatre", - "UNKNOWN_VAL1": true, - "UNKNOWN_VAL2": null, - "UNKNOWN_VAL3": 22, - "UNKNOWN_VAL4": "hehe" + { + "Version": "false", + "Source": "lol", + "ManagerName": "Rodolfo Chikilicuatre", + "UNKNOWN_VAL1": true, + "UNKNOWN_VAL2": null, + "UNKNOWN_VAL3": 22, + "UNKNOWN_VAL4": "hehe", + "InstallationOptions" : { + "SkipHashCheck": true + } } - """, "", "", "false", "Rodolfo Chikilicuatre", "lol")] - public void FromJson(string JSON, string id, string name, string version, string manager, string source) + """, "", "", "false", "Rodolfo Chikilicuatre", "lol", true, "")] + + public void FromJson(string JSON, string id, string name, string version, string manager, string source, bool skipHash, string ignoredVer) { Assert.NotEmpty(JSON); var jsonContent = JsonNode.Parse(JSON); @@ -78,9 +85,11 @@ public void FromJson(string JSON, string id, string name, string version, string Assert.Equal(name, o2.Name); Assert.Equal(id, o2.Id); - Assert.Equal(manager, o2.Source); + Assert.Equal(manager, o2.ManagerName); + Assert.Equal(source, o2.Source); Assert.Equal(version, o2.Version); - + TestSerializableInstallationOptions.AreEqual(new() { SkipHashCheck = skipHash }, o2.InstallationOptions); + TestSerializableUpdatesOptions.AreEqual(new(){IgnoredVersion = ignoredVer}, o2.Updates); } private static void AreEqual(SerializablePackage o1, SerializablePackage o2) From 732d9a75e598fa9e409f64535b31cb35722c9730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Tue, 20 May 2025 20:52:30 +0200 Subject: [PATCH 14/17] Migrate SerializableBundle --- .../TestSerializableBundle.cs | 130 ++++++++++++++++++ ...=> TestSerializableIncompatiblePackage.cs} | 4 +- .../TestSerializableInstallationOptions.cs | 2 +- .../TestSerializablePackage.cs | 2 +- .../TestSerializableUpdatesOptions.cs | 2 +- .../SerializableBundle.cs | 66 +++++++++ .../SerializableBundle_v1.cs | 16 --- .../Pages/SoftwarePages/PackageBundlesPage.cs | 8 +- 8 files changed, 205 insertions(+), 25 deletions(-) create mode 100644 src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableBundle.cs rename src/UniGetUI.PackageEngine.Serializable.Tests/{TestIncompatiblePackage.cs => TestSerializableIncompatiblePackage.cs} (93%) create mode 100644 src/UniGetUI.PackageEngine.Serializable/SerializableBundle.cs delete mode 100644 src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableBundle.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableBundle.cs new file mode 100644 index 0000000000..5aff7e5db9 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableBundle.cs @@ -0,0 +1,130 @@ +using System.Text.Json.Nodes; +using UniGetUI.PackageEngine.Classes.Serializable; + +namespace UniGetUI.PackageEngine.Serializable.Tests; + +public class TestSerializableBundle +{ + public static SerializablePackage TestPackage1 = new() + { + Name = "Package", + Id = "Identifier1", + ManagerName = "Scoop", + Source = "", + Version = "3.0", + InstallationOptions = new() + { + SkipHashCheck = true, + Architecture = "4+1€", + CustomParameters = ["--hello-world", "--another-param", "-help"] + }, + Updates = new() + { + IgnoredVersion = "12", + UpdatesIgnored = false + } + }; + + public static SerializablePackage TestPackage2 = new() + { + Name = "AnotherPackage", + Id = "Identifier2", + ManagerName = "WinGet", + Source = "msstore", + Version = "5.0", + }; + + public static SerializableIncompatiblePackage TestIncompatiblePackage = new() + { + Name = "AnotherPackage3", + Id = "Identifier4", + Source = "msstore", + Version = "5.0", + }; + + + [Fact] + public void ToAndFromJsonNode() + { + var originalObject1 = new SerializableBundle() + { + export_version = 5, + packages = [TestPackage1, TestPackage2], + incompatible_packages_info = "I'm trying to reach you regarding your car's extended warranty", + incompatible_packages = [TestIncompatiblePackage] + }; + + var object2 = new SerializableBundle(); + string contents = originalObject1.AsJsonString(); + Assert.NotEmpty(contents); + var jsonContent = JsonNode.Parse(contents); + Assert.NotNull(jsonContent); + object2.LoadFromJson(jsonContent); + AreEqual(originalObject1, object2); + + var object3 = new SerializableBundle(originalObject1.AsJsonNode()); + AreEqual(originalObject1, object3); + + var object4 = originalObject1.Copy(); + AreEqual(originalObject1, object4); + } + + [Theory] + [InlineData("{}", "", "", "")] + [InlineData(""" + { + "export_version": 2.1, + "packages": [ + { + "Id": "Hello" + }, + { + "Id": "World" + } + ], + "incompatible_packages_info": "hey", + "incompatible_packages": [ + { + "Id": "3" + } + ] + } + """, "Hello", "World", "3")] + public void FromJson(string JSON, string id1, string id2, string id3) + { + Assert.NotEmpty(JSON); + var jsonContent = JsonNode.Parse(JSON); + Assert.NotNull(jsonContent); + var o2 = new SerializableBundle(jsonContent); + + if (id1 == "") + { + Assert.Equal(0, o2.export_version); + Assert.Equal(SerializableBundle.IncompatMessage, o2.incompatible_packages_info); + Assert.Empty(o2.packages); + Assert.Empty(o2.incompatible_packages); + } + else + { + Assert.Equal(2.1, o2.export_version); + Assert.Equal("hey", o2.incompatible_packages_info); + Assert.Equal(id1, o2.packages[0].Id); + Assert.Equal(id2, o2.packages[1].Id); + Assert.Equal(id3, o2.incompatible_packages[0].Id); + } + } + + internal static void AreEqual(SerializableBundle o1, SerializableBundle o2) + { + Assert.Equal(o1.export_version, o2.export_version); + Assert.Equal(o1.incompatible_packages_info, o2.incompatible_packages_info); + + Assert.Equal(o1.packages.Count, o2.packages.Count); + for (int i = 0; i < o1.packages.Count; i++) + TestSerializablePackage.AreEqual(o1.packages[i], o2.packages[i]); + + Assert.Equal(o1.incompatible_packages.Count, o2.incompatible_packages.Count); + for (int i = 0; i < o1.incompatible_packages.Count; i++) + TestSerializableIncompatiblePackage.AreEqual(o1.incompatible_packages[i], o2.incompatible_packages[i]); + } +} diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestIncompatiblePackage.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableIncompatiblePackage.cs similarity index 93% rename from src/UniGetUI.PackageEngine.Serializable.Tests/TestIncompatiblePackage.cs rename to src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableIncompatiblePackage.cs index db04fcca8d..b1948e36b7 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestIncompatiblePackage.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableIncompatiblePackage.cs @@ -3,7 +3,7 @@ namespace UniGetUI.PackageEngine.Serializable.Tests; -public class TestIncompatiblePackage +public class TestSerializableIncompatiblePackage { [Theory] [InlineData("", "", "", "")] @@ -67,7 +67,7 @@ public void FromJson(string JSON, string id, string name, string version, string } - private static void AreEqual(SerializableIncompatiblePackage o1, SerializableIncompatiblePackage o2) + internal static void AreEqual(SerializableIncompatiblePackage o1, SerializableIncompatiblePackage o2) { Assert.Equal(o1.Name, o2.Name); Assert.Equal(o1.Id, o2.Id); diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs index 4f4dab705a..21727efaa3 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs @@ -91,7 +91,7 @@ public void FromJson(string JSON, bool hash, bool inter, string installLoc, stri Assert.Equal(ver, o2.Version); } - public static void AreEqual(SerializableInstallationOptions o1, SerializableInstallationOptions o2) + internal static void AreEqual(SerializableInstallationOptions o1, SerializableInstallationOptions o2) { Assert.Equal(o1.SkipHashCheck, o2.SkipHashCheck); Assert.Equal(o1.Architecture, o2.Architecture); diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs index 5ce9f4de1c..8d120e15f1 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializablePackage.cs @@ -92,7 +92,7 @@ public void FromJson(string JSON, string id, string name, string version, string TestSerializableUpdatesOptions.AreEqual(new(){IgnoredVersion = ignoredVer}, o2.Updates); } - private static void AreEqual(SerializablePackage o1, SerializablePackage o2) + internal static void AreEqual(SerializablePackage o1, SerializablePackage o2) { Assert.Equal(o1.Name, o2.Name); Assert.Equal(o1.Id, o2.Id); diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs index fb87d873c4..d5c3832ab3 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableUpdatesOptions.cs @@ -61,7 +61,7 @@ public void FromJson(string JSON, bool ign, string ver) Assert.Equal(ver, o2.IgnoredVersion); } - public static void AreEqual(SerializableUpdatesOptions o1, SerializableUpdatesOptions o2) + internal static void AreEqual(SerializableUpdatesOptions o1, SerializableUpdatesOptions o2) { Assert.Equal(o1.IgnoredVersion, o2.IgnoredVersion); Assert.Equal(o1.UpdatesIgnored, o2.UpdatesIgnored); diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableBundle.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableBundle.cs new file mode 100644 index 0000000000..3f09023d8e --- /dev/null +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableBundle.cs @@ -0,0 +1,66 @@ +using System.Text.Json.Nodes; +using UniGetUI.PackageEngine.Serializable; + +namespace UniGetUI.PackageEngine.Classes.Serializable +{ + public class SerializableBundle: SerializableComponent + { + public const double ExpectedVersion = 3; + public const string IncompatMessage = "Incompatible packages cannot be installed from UniGetUI, " + + "either because they came from a local source (for example Local PC)" + + "or because the package manager was unavailable. " + + "Nevertheless, they have been listed here for logging purposes."; + + + public double export_version { get; set; } = 3; + public List packages { get; set; } = []; + public string incompatible_packages_info { get; set; } = IncompatMessage; + public List incompatible_packages { get; set; } = []; + + public override SerializableBundle Copy() + { + var _packages = new List(); + var _incompatPackages = new List(); + + foreach(var package in this.packages) + _packages.Add(package.Copy()); + + foreach(var incompatPackage in this.incompatible_packages) + _incompatPackages.Add(incompatPackage.Copy()); + + return new() + { + export_version = this.export_version, + packages = _packages, + incompatible_packages_info = this.incompatible_packages_info, + incompatible_packages = _incompatPackages + }; + } + + public override void LoadFromJson(JsonNode data) + { + this.export_version = data[nameof(export_version)]?.GetValue() ?? 0; + this.incompatible_packages_info = data[nameof(incompatible_packages_info)]?.GetValue() ?? IncompatMessage; + this.packages = new List(); + this.incompatible_packages = new List(); + + foreach (JsonNode pkg in data[nameof(packages)] as JsonArray ?? []) + { + packages.Add(new SerializablePackage(pkg)); + } + + foreach (JsonNode inc_pkg in data[nameof(incompatible_packages)] as JsonArray ?? []) + { + incompatible_packages.Add(new SerializableIncompatiblePackage(inc_pkg)); + } + } + + public SerializableBundle() : base() + { + } + + public SerializableBundle(JsonNode data) : base(data) + { + } + } +} diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs deleted file mode 100644 index ac0d647941..0000000000 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableBundle_v1.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace UniGetUI.PackageEngine.Classes.Serializable -{ - public class SerializableBundle_Data - { - public const double ExpectedVersion = 2.1; - } - - public class SerializableBundle_v1 - { - public double export_version { get; set; } = -1; - public List packages { get; set; } = []; - public string incompatible_packages_info { get; set; } = "Incompatible packages cannot be installed from WingetUI, but they have been listed here for logging purposes."; - public List incompatible_packages { get; set; } = []; - - } -} diff --git a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs index a89362b349..355198fbf5 100644 --- a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs +++ b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs @@ -579,7 +579,7 @@ public async Task SaveFile() public static async Task CreateBundle(IReadOnlyList unsorted_packages, BundleFormatType formatType = BundleFormatType.UBUNDLE) { - SerializableBundle_v1 exportable = new() + SerializableBundle exportable = new() { export_version = 2.1, }; @@ -620,7 +620,7 @@ static int Comparison(IPackage x, IPackage y) { string tempfile = Path.GetTempFileName(); StreamWriter writer = new(tempfile); - XmlSerializer serializer = new(typeof(SerializableBundle_v1)); + XmlSerializer serializer = new(typeof(SerializableBundle)); serializer.Serialize(writer, exportable); writer.Close(); ExportableData = await File.ReadAllTextAsync(tempfile); @@ -635,7 +635,7 @@ static int Comparison(IPackage x, IPackage y) public async Task AddFromBundle(string content, BundleFormatType format) { // Deserialize data - SerializableBundle_v1? DeserializedData; + SerializableBundle? DeserializedData; if (format is BundleFormatType.YAML) { @@ -651,7 +651,7 @@ public async Task AddFromBundle(string content, BundleFormatType format) Logger.ImportantInfo("XML payload was converted to JSON dynamically before deserialization"); } - DeserializedData = await Task.Run(() => JsonSerializer.Deserialize(content, SerializationHelpers.ImportBundleOptions)); + DeserializedData = await Task.Run(() => JsonSerializer.Deserialize(content, SerializationHelpers.ImportBundleOptions)); if (DeserializedData is null || DeserializedData.export_version is -1) From 1b773c5b22540cff1c876d352ecd90538f667228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Tue, 20 May 2025 22:23:29 +0200 Subject: [PATCH 15/17] Fix YAML and XML serialization, remove now useless Converters --- .../SerializationHelpers.cs | 114 +++++------------- .../SerializableBundle.cs | 11 +- .../SerializableIncompatiblePackage.cs | 9 +- .../SerializableInstallationOptions.cs | 23 ++-- .../SerializablePackage.cs | 11 +- .../SerializableUpdatesOptions.cs | 5 +- .../Pages/SoftwarePages/PackageBundlesPage.cs | 39 +++--- 7 files changed, 78 insertions(+), 134 deletions(-) diff --git a/src/UniGetUI.Core.Tools/SerializationHelpers.cs b/src/UniGetUI.Core.Tools/SerializationHelpers.cs index 52a4bed9b9..be129d130b 100644 --- a/src/UniGetUI.Core.Tools/SerializationHelpers.cs +++ b/src/UniGetUI.Core.Tools/SerializationHelpers.cs @@ -1,4 +1,6 @@ +using System.Globalization; using System.Text.Json; +using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using System.Xml; @@ -84,104 +86,50 @@ private static string xml_to_json(string XML) AllowTrailingCommas = true, WriteIndented = true, }; - - public static JsonSerializerOptions ImportBundleOptions = new() - { - TypeInfoResolver = new DefaultJsonTypeInfoResolver(), - NumberHandling = - JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString, - AllowTrailingCommas = true, - WriteIndented = true, - Converters = - { - new Converters.FlexibleBooleanConverter(), - new Converters.FlexibleStringConverter(), - new Converters.FlexibleListStringConverter() - } - }; } -internal class Converters +public static class JsonNodeExtensions { - public class FlexibleBooleanConverter : JsonConverter + /// + /// Safely gets a child node by key, returning null if the key does not exist. + /// + public static T GetVal(this JsonNode node) { - public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return reader.TokenType switch - { - JsonTokenType.True => true, - JsonTokenType.False => false, - JsonTokenType.String => bool.TryParse(reader.GetString(), out var b) ? b : - int.TryParse(reader.GetString(), out var i) ? i != 0 : - throw new JsonException("Invalid string for boolean."), - JsonTokenType.Number => reader.TryGetInt32(out var i2) ? i2 != 0 : - throw new JsonException("Invalid number for boolean."), - _ => throw new JsonException("Invalid token for boolean.") - }; - } + if (typeof(T) == typeof(double) && node.GetValueKind() is JsonValueKind.String) + return (T)(object)double.Parse(node.GetValue(), CultureInfo.InvariantCulture); - public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) - { - writer.WriteBooleanValue(value); - } - } + if (typeof(T) == typeof(int) && node.GetValueKind() is JsonValueKind.String) + return (T)(object)int.Parse(node.GetValue()); - public class FlexibleStringConverter : JsonConverter - { - public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.StartObject) - { - using var doc = JsonDocument.ParseValue(ref reader); - return ""; - } - return reader.GetString() ?? ""; - } + if (typeof(T) == typeof(bool) && node.GetValueKind() is JsonValueKind.String) + return (T)(object)bool.Parse(node.GetValue()); - public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + if (typeof(T) == typeof(string) && node.GetValueKind() is JsonValueKind.Object) { - writer.WriteStringValue(value); + return (T)(object)""; } + + return node.GetValue(); } - public class FlexibleListStringConverter : JsonConverter> + /// + /// The same as JsonNode.AsArray, but can convert objects whose keys are integers to arrays + /// + /// + /// + public static JsonArray AsArray2(this JsonNode node) { - public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + if (node is JsonValue val) { - if (reader.TokenType == JsonTokenType.StartObject) - { - using var doc = JsonDocument.ParseValue(ref reader); - return []; - } - if (reader.TokenType == JsonTokenType.String) - { - return [reader.GetString() ?? ""]; - } - if (reader.TokenType == JsonTokenType.StartArray) - { - var list = new List(); - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndArray) - break; - if (reader.TokenType == JsonTokenType.String) - list.Add(reader.GetString() ?? string.Empty); - } - return list; - } - throw new JsonException($"Unexpected token {reader.TokenType} when reading List."); + JsonArray result = new(); + result.Add(val.DeepClone()); + return result; } - - public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) + else if (node is JsonObject obj && !obj.Any()) { - writer.WriteStartArray(); - foreach (var str in value) - { - writer.WriteStringValue(str); - } - writer.WriteEndArray(); + return new JsonArray(); } + + return node.AsArray(); } } - - diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableBundle.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableBundle.cs index 3f09023d8e..dffdcb1e32 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableBundle.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableBundle.cs @@ -1,5 +1,6 @@ using System.Text.Json.Nodes; using UniGetUI.PackageEngine.Serializable; +using UniGetUI.Core.Data; namespace UniGetUI.PackageEngine.Classes.Serializable { @@ -39,18 +40,20 @@ public override SerializableBundle Copy() public override void LoadFromJson(JsonNode data) { - this.export_version = data[nameof(export_version)]?.GetValue() ?? 0; - this.incompatible_packages_info = data[nameof(incompatible_packages_info)]?.GetValue() ?? IncompatMessage; + this.export_version = data[nameof(export_version)]?.GetVal() ?? 0; + this.incompatible_packages_info = data[nameof(incompatible_packages_info)]?.GetVal() ?? IncompatMessage; this.packages = new List(); this.incompatible_packages = new List(); - foreach (JsonNode pkg in data[nameof(packages)] as JsonArray ?? []) + foreach (JsonNode? pkg in data[nameof(packages)]?.AsArray2() ?? new()) { + if (pkg is null) throw new InvalidDataException("JsonNode? pkg was null, when it shouldn't"); packages.Add(new SerializablePackage(pkg)); } - foreach (JsonNode inc_pkg in data[nameof(incompatible_packages)] as JsonArray ?? []) + foreach (JsonNode? inc_pkg in data[nameof(incompatible_packages)]?.AsArray2() ?? new()) { + if (inc_pkg is null) throw new InvalidDataException("JsonNode? inc_pkg was null, when it shouldn't"); incompatible_packages.Add(new SerializableIncompatiblePackage(inc_pkg)); } } diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage.cs index 0db6145131..cb4b3a757b 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableIncompatiblePackage.cs @@ -1,4 +1,5 @@ using System.Text.Json.Nodes; +using UniGetUI.Core.Data; using UniGetUI.PackageEngine.Serializable; namespace UniGetUI.PackageEngine.Classes.Serializable @@ -20,10 +21,10 @@ public override SerializableIncompatiblePackage Copy() public override void LoadFromJson(JsonNode data) { - this.Id = data[nameof(Id)]?.GetValue() ?? ""; - this.Name = data[nameof(Name)]?.GetValue() ?? ""; - this.Version = data[nameof(Version)]?.GetValue() ?? ""; - this.Source = data[nameof(Source)]?.GetValue() ?? ""; + this.Id = data[nameof(Id)]?.GetVal() ?? ""; + this.Name = data[nameof(Name)]?.GetVal() ?? ""; + this.Version = data[nameof(Version)]?.GetVal() ?? ""; + this.Source = data[nameof(Source)]?.GetVal() ?? ""; } public SerializableIncompatiblePackage(JsonNode data) : base(data) diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs index 6b4ad5754d..fd3f7b9e1f 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs @@ -1,4 +1,5 @@ using System.Text.Json.Nodes; +using UniGetUI.Core.Data; namespace UniGetUI.PackageEngine.Serializable { @@ -34,20 +35,20 @@ public override SerializableInstallationOptions Copy() public override void LoadFromJson(JsonNode data) { - this.SkipHashCheck = data[nameof(SkipHashCheck)]?.GetValue() ?? false; - this.InteractiveInstallation = data[nameof(InteractiveInstallation)]?.GetValue() ?? false; - this.RunAsAdministrator = data[nameof(RunAsAdministrator)]?.GetValue() ?? false; - this.Architecture = data[nameof(Architecture)]?.GetValue() ?? ""; - this.InstallationScope = data[nameof(InstallationScope)]?.GetValue() ?? ""; + this.SkipHashCheck = data[nameof(SkipHashCheck)]?.GetVal() ?? false; + this.InteractiveInstallation = data[nameof(InteractiveInstallation)]?.GetVal() ?? false; + this.RunAsAdministrator = data[nameof(RunAsAdministrator)]?.GetVal() ?? false; + this.Architecture = data[nameof(Architecture)]?.GetVal() ?? ""; + this.InstallationScope = data[nameof(InstallationScope)]?.GetVal() ?? ""; this.CustomParameters = new List(); - foreach(var element in data[nameof(CustomParameters)]?.AsArray() ?? []) - if (element is not null) this.CustomParameters.Add(element.GetValue()); + foreach(var element in data[nameof(CustomParameters)]?.AsArray2() ?? []) + if (element is not null) this.CustomParameters.Add(element.GetVal()); - this.PreRelease = data[nameof(PreRelease)]?.GetValue() ?? false; - this.CustomInstallLocation = data[nameof(CustomInstallLocation)]?.GetValue() ?? ""; - this.Version = data[nameof(Version)]?.GetValue() ?? ""; - this.SkipMinorUpdates = data[nameof(SkipMinorUpdates)]?.GetValue() ?? false; + this.PreRelease = data[nameof(PreRelease)]?.GetVal() ?? false; + this.CustomInstallLocation = data[nameof(CustomInstallLocation)]?.GetVal() ?? ""; + this.Version = data[nameof(Version)]?.GetVal() ?? ""; + this.SkipMinorUpdates = data[nameof(SkipMinorUpdates)]?.GetVal() ?? false; } public SerializableInstallationOptions() : base() diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializablePackage.cs b/src/UniGetUI.PackageEngine.Serializable/SerializablePackage.cs index 81bb2bd373..12ce2117c2 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializablePackage.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializablePackage.cs @@ -1,4 +1,5 @@ using System.Text.Json.Nodes; +using UniGetUI.Core.Data; using UniGetUI.PackageEngine.Serializable; namespace UniGetUI.PackageEngine.Classes.Serializable @@ -30,11 +31,11 @@ public override SerializablePackage Copy() public override void LoadFromJson(JsonNode data) { - this.Name = data[nameof(Name)]?.GetValue() ?? ""; - this.Id = data[nameof(Id)]?.GetValue() ?? ""; - this.Version = data[nameof(Version)]?.GetValue() ?? ""; - this.Source = data[nameof(Source)]?.GetValue() ?? ""; - this.ManagerName = data[nameof(ManagerName)]?.GetValue() ?? ""; + this.Name = data[nameof(Name)]?.GetVal() ?? ""; + this.Id = data[nameof(Id)]?.GetVal() ?? ""; + this.Version = data[nameof(Version)]?.GetVal() ?? ""; + this.Source = data[nameof(Source)]?.GetVal() ?? ""; + this.ManagerName = data[nameof(ManagerName)]?.GetVal() ?? ""; this.InstallationOptions = new(data[nameof(InstallationOptions)] ?? new JsonObject()); this.Updates = new(data[nameof(Updates)] ?? new JsonObject()); diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions.cs index 5ec3b8ced7..2d5f855c72 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableUpdatesOptions.cs @@ -1,4 +1,5 @@ using System.Text.Json.Nodes; +using UniGetUI.Core.Data; using UniGetUI.PackageEngine.Serializable; namespace UniGetUI.PackageEngine.Classes.Serializable @@ -15,8 +16,8 @@ public override SerializableUpdatesOptions Copy() public override void LoadFromJson(JsonNode data) { - this.UpdatesIgnored = data[nameof(UpdatesIgnored)]?.GetValue() ?? false; - this.IgnoredVersion = data[nameof(IgnoredVersion)]?.GetValue() ?? ""; + this.UpdatesIgnored = data[nameof(UpdatesIgnored)]?.GetVal() ?? false; + this.IgnoredVersion = data[nameof(IgnoredVersion)]?.GetVal() ?? ""; } public SerializableUpdatesOptions() : base() diff --git a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs index 355198fbf5..c6d2d5fb29 100644 --- a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs +++ b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Text.Json; +using System.Text.Json.Nodes; using System.Xml; using System.Xml.Serialization; using ExternalLibraries.Pickers; @@ -489,10 +490,10 @@ public async Task OpenFromFile(string? file = null) DialogHelper.HideLoadingDialog(); - if ((int)(open_version*10) != (int)(SerializableBundle_Data.ExpectedVersion*10)) + if ((int)(open_version*10) != (int)(SerializableBundle.ExpectedVersion*10)) { // Check only up to first decimal digit, prevent floating point precision error. Logger.Warn($"The loaded bundle \"{file}\" is based on schema version {open_version}, " + - $"while this UniGetUI build expects version {SerializableBundle_Data.ExpectedVersion}." + + $"while this UniGetUI build expects version {SerializableBundle.ExpectedVersion}." + $"\nThis should not be a problem if packages show up, but be careful"); } } @@ -579,10 +580,7 @@ public async Task SaveFile() public static async Task CreateBundle(IReadOnlyList unsorted_packages, BundleFormatType formatType = BundleFormatType.UBUNDLE) { - SerializableBundle exportable = new() - { - export_version = 2.1, - }; + SerializableBundle exportable = new(); List packages = unsorted_packages.ToList(); packages.Sort(Comparison); @@ -651,33 +649,24 @@ public async Task AddFromBundle(string content, BundleFormatType format) Logger.ImportantInfo("XML payload was converted to JSON dynamically before deserialization"); } - DeserializedData = await Task.Run(() => JsonSerializer.Deserialize(content, SerializationHelpers.ImportBundleOptions)); - - - if (DeserializedData is null || DeserializedData.export_version is -1) + DeserializedData = await Task.Run(() => { - throw new ArgumentException("DeserializedData was null"); - } + return new SerializableBundle(JsonNode.Parse(content) ?? throw new Exception("Could not parse JSON object")); + }); List packages = []; - foreach (SerializablePackage DeserializedPackage in DeserializedData.packages) - { - packages.Add(PackageFromSerializable(DeserializedPackage)); - } + foreach (var pkg in DeserializedData.packages) + packages.Add(DeserializePackage(pkg)); - foreach (SerializableIncompatiblePackage DeserializedPackage in DeserializedData - .incompatible_packages) - { - packages.Add(InvalidPackageFromSerializable(DeserializedPackage, NullSource.Instance)); - } + foreach (var pkg in DeserializedData.incompatible_packages) + packages.Add(DeserializeIncompatiblePackage(pkg, NullSource.Instance)); await PEInterface.PackageBundlesLoader.AddPackagesAsync(packages); - return DeserializedData.export_version; } - public static IPackage PackageFromSerializable(SerializablePackage raw_package) + public static IPackage DeserializePackage(SerializablePackage raw_package) { IPackageManager? manager = null; IManagerSource? source; @@ -700,13 +689,13 @@ public static IPackage PackageFromSerializable(SerializablePackage raw_package) if (manager is null || source is null) { - return InvalidPackageFromSerializable(raw_package.GetInvalidEquivalent(), NullSource.Instance); + return DeserializeIncompatiblePackage(raw_package.GetInvalidEquivalent(), NullSource.Instance); } return new ImportedPackage(raw_package, manager, source); } - public static IPackage InvalidPackageFromSerializable(SerializableIncompatiblePackage raw_package, IManagerSource source) + public static IPackage DeserializeIncompatiblePackage(SerializableIncompatiblePackage raw_package, IManagerSource source) { return new InvalidImportedPackage(raw_package, source); } From 795a5cb2ad1157b1ce3daee896a9cff7fa020696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Wed, 21 May 2025 13:54:02 +0200 Subject: [PATCH 16/17] Add a method that checks if a SerializableInstallOptions differs or not from the default values --- .../SerializableInstallationOptions.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs index fd3f7b9e1f..2b02e2363a 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs @@ -51,6 +51,20 @@ public override void LoadFromJson(JsonNode data) this.SkipMinorUpdates = data[nameof(SkipMinorUpdates)]?.GetVal() ?? false; } + public bool DiffersFromDefault() + { + return SkipHashCheck is not false || + InteractiveInstallation is not false || + RunAsAdministrator is not false || + PreRelease is not false || + SkipMinorUpdates is not false || + Architecture.Any() || + InstallationScope.Any() || + CustomParameters.Where(x => x != "").Any() || + CustomInstallLocation.Any() || + Version.Any(); + } + public SerializableInstallationOptions() : base() { } From af7740159a9f6053cd515f8d2ee54c6bc62a1d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Wed, 21 May 2025 13:55:15 +0200 Subject: [PATCH 17/17] add test for this --- .../TestSerializableInstallationOptions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs index 21727efaa3..dd02ab6692 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs @@ -27,6 +27,8 @@ public void ToAndFromJsonNode(bool a, bool b, string c, string d, string e, stri Version = l }; + Assert.Equal(a, originalObject1.DiffersFromDefault()); + var object2 = new SerializableInstallationOptions(); string contents = originalObject1.AsJsonString(); Assert.NotEmpty(contents);