From 2b9bab6f15139334ce4ce24382053c7d5c431290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Wed, 21 May 2025 14:02:40 +0200 Subject: [PATCH 01/12] Update SerializableInstallationOptions.cs --- .../SerializableInstallationOptions.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs index fac30f6c12..00a9ded539 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs @@ -12,6 +12,7 @@ public class SerializableInstallationOptions_v1 public string CustomInstallLocation { get; set; } = ""; public string Version { get; set; } = ""; public bool SkipMinorUpdates { get; set; } + public bool OverrideNextLevelPrefs { get; set; } public SerializableInstallationOptions_v1 Copy() { @@ -27,7 +28,56 @@ public SerializableInstallationOptions_v1 Copy() RunAsAdministrator = RunAsAdministrator, Version = Version, SkipMinorUpdates = SkipMinorUpdates, + OverrideNextLevelPrefs = OverrideNextLevelPrefs, }; } + + public override void LoadFromJson(JsonNode data) + { + 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)]?.AsArray2() ?? []) + if (element is not null) this.CustomParameters.Add(element.GetVal()); + + 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; + + // if OverrideNextLevelPrefs is not found on the JSON, set it to true or false depending + // on whether the current settings instances are different from the default values. + // This entry shall be checked the last one, to ensure all other properties are set + this.OverrideNextLevelPrefs = + data[nameof(OverrideNextLevelPrefs)]?.GetValue() ?? DiffersFromDefault(); + } + + private 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(); + // OverrideNextLevelPrefs does not need to be checked here, since + // this method is invoked before this property has been set + } + + public SerializableInstallationOptions() : base() + { + } + + public SerializableInstallationOptions(JsonNode data) : base(data) + { + } } } From 827a65d6e9956ac810e09e88430874315ed1debb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Wed, 21 May 2025 14:06:22 +0200 Subject: [PATCH 02/12] attribute needs to be public for tests to work --- .../SerializableInstallationOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs index 3a892b7920..75f314f7a0 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs @@ -59,7 +59,7 @@ public override void LoadFromJson(JsonNode data) data[nameof(OverrideNextLevelPrefs)]?.GetValue() ?? DiffersFromDefault(); } - private bool DiffersFromDefault() + public bool DiffersFromDefault() { return SkipHashCheck is not false || InteractiveInstallation is not false || From 90b5e2aab867c1fbdc0ff2ac7a5a87adb2e176aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Wed, 21 May 2025 14:08:12 +0200 Subject: [PATCH 03/12] more testing --- .../TestSerializableInstallationOptions.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs index dd02ab6692..29cd47c2b0 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs @@ -45,7 +45,7 @@ public void ToAndFromJsonNode(bool a, bool b, string c, string d, string e, stri } [Theory] - [InlineData("{}", false, false, "", "", "", "", "", "", false, false, false, "")] + [InlineData("{}", false, false, "", "", "", "", "", "", false, false, false, "", false)] [InlineData(""" { "SkipHashCheck": true, @@ -57,7 +57,7 @@ public void ToAndFromJsonNode(bool a, bool b, string c, string d, string e, stri "a" ] } - """, true, true, "", "a", "", "", "", "lol", false, false, false, "")] + """, true, true, "", "a", "", "", "", "lol", false, false, false, "", true)] [InlineData(""" { @@ -71,8 +71,8 @@ public void ToAndFromJsonNode(bool a, bool b, string c, string d, string e, stri "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) + "", "", false, false, true, "heyheyhey", true)] + 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, bool mod) { Assert.NotEmpty(JSON); var jsonContent = JsonNode.Parse(JSON); @@ -81,6 +81,8 @@ public void FromJson(string JSON, bool hash, bool inter, string installLoc, stri var list = new List() { arg1, arg2, arg3 }.Where(x => x.Any()); + Assert.Equal(mod, o2.OverrideNextLevelPrefs); + Assert.Equal(mod, o2.DiffersFromDefault()); Assert.Equal(hash, o2.SkipHashCheck); Assert.Equal(arch, o2.Architecture); Assert.Equal(installLoc, o2.CustomInstallLocation); From 1a19354f48ece559cafc3de3c60b91021ebb5e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Wed, 21 May 2025 14:30:17 +0200 Subject: [PATCH 04/12] InstallationOptions will also have the new OverrideNextLevelPrefs --- .../Packages/Classes/InstallationOptions.cs | 27 ++++++++----------- .../TestSerializableInstallationOptions.cs | 2 +- .../SerializableInstallationOptions.cs | 12 ++++----- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs index 03881b8822..9c5def561a 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs @@ -30,9 +30,9 @@ public class InstallationOptions : IInstallationOptions public bool RemoveDataOnUninstall { get; set; } public bool PreRelease { get; set; } public string CustomInstallLocation { get; set; } = ""; + public bool OverridesNextLevelOpts { get; set; } public IPackage Package { get; } - private readonly string __save_filename; private InstallationOptions(IPackage package) @@ -122,24 +122,21 @@ public void FromSerializable(SerializableInstallationOptions options) Version = options.Version; SkipMinorUpdates = options.SkipMinorUpdates; PreRelease = options.PreRelease; + OverridesNextLevelOpts = options.OverridesNextLevelOpts; - if (options.Architecture != "" && CommonTranslations.InvertedArchNames.TryGetValue(options.Architecture, out var name)) + Architecture = null; + if (options.Architecture.Any() && + CommonTranslations.InvertedArchNames.TryGetValue(options.Architecture, out var name)) { Architecture = name; } - else - { - Architecture = null; - } - if (options.InstallationScope != "" && CommonTranslations.InvertedScopeNames_NonLang.TryGetValue(options.InstallationScope, out var value)) + InstallationScope = null; + if (options.InstallationScope.Any() && + CommonTranslations.InvertedScopeNames_NonLang.TryGetValue(options.InstallationScope, out var value)) { InstallationScope = value; } - else - { - InstallationScope = null; - } CustomParameters = options.CustomParameters; } @@ -157,17 +154,15 @@ public SerializableInstallationOptions AsSerializable() CustomInstallLocation = CustomInstallLocation, PreRelease = PreRelease, Version = Version, - SkipMinorUpdates = SkipMinorUpdates + SkipMinorUpdates = SkipMinorUpdates, + OverridesNextLevelOpts = OverridesNextLevelOpts }; + if (Architecture is not null) - { options.Architecture = CommonTranslations.ArchNames[Architecture.Value]; - } if (InstallationScope is not null) - { options.InstallationScope = CommonTranslations.ScopeNames_NonLang[InstallationScope.Value]; - } options.CustomParameters = CustomParameters; return options; diff --git a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs index 29cd47c2b0..d20cd968a4 100644 --- a/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable.Tests/TestSerializableInstallationOptions.cs @@ -81,7 +81,7 @@ public void FromJson(string JSON, bool hash, bool inter, string installLoc, stri var list = new List() { arg1, arg2, arg3 }.Where(x => x.Any()); - Assert.Equal(mod, o2.OverrideNextLevelPrefs); + Assert.Equal(mod, o2.OverridesNextLevelOpts); Assert.Equal(mod, o2.DiffersFromDefault()); Assert.Equal(hash, o2.SkipHashCheck); Assert.Equal(arch, o2.Architecture); diff --git a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs index 75f314f7a0..7a78272847 100644 --- a/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable/SerializableInstallationOptions.cs @@ -15,7 +15,7 @@ public class SerializableInstallationOptions: SerializableComponent() ?? ""; this.SkipMinorUpdates = data[nameof(SkipMinorUpdates)]?.GetVal() ?? false; - // if OverrideNextLevelPrefs is not found on the JSON, set it to true or false depending + // if OverridesNextLevelOpts is not found on the JSON, set it to true or false depending // on whether the current settings instances are different from the default values. // This entry shall be checked the last one, to ensure all other properties are set - this.OverrideNextLevelPrefs = - data[nameof(OverrideNextLevelPrefs)]?.GetValue() ?? DiffersFromDefault(); + this.OverridesNextLevelOpts = + data[nameof(OverridesNextLevelOpts)]?.GetValue() ?? DiffersFromDefault(); } public bool DiffersFromDefault() @@ -71,7 +71,7 @@ SkipMinorUpdates is not false || CustomParameters.Where(x => x != "").Any() || CustomInstallLocation.Any() || Version.Any(); - // OverrideNextLevelPrefs does not need to be checked here, since + // OverridesNextLevelOpts does not need to be checked here, since // this method is invoked before this property has been set } From 77faebf428bef612bfbfd4f6e3df094410eac96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Wed, 21 May 2025 21:04:00 +0200 Subject: [PATCH 05/12] Add switch to enable/disable overriding global options --- .../DialogPages/DialogHelper_Packages.cs | 2 +- .../Pages/DialogPages/InstallOptions.xaml | 411 +++++++++++------- .../Pages/DialogPages/InstallOptions.xaml.cs | 110 ++++- src/UniGetUI/UniGetUI.csproj | 1 + 4 files changed, 340 insertions(+), 184 deletions(-) diff --git a/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs b/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs index 2d22ef5a05..5c4915acfb 100644 --- a/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs +++ b/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs @@ -68,7 +68,7 @@ public static async Task ShowInstallOptions_ImportedPackage }; OptionsDialog.PrimaryButtonText = CoreTools.Translate("Save and close"); OptionsDialog.DefaultButton = ContentDialogButton.Secondary; - OptionsDialog.Title = CoreTools.Translate("{0} installation options", package.Name); + // OptionsDialog.Title = CoreTools.Translate("{0} installation options", package.Name); OptionsDialog.Content = OptionsPage; OptionsPage.Close += (_, _) => { OptionsDialog.Hide(); }; diff --git a/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml b/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml index f899415471..6ad6c0e867 100644 --- a/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml +++ b/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml @@ -7,6 +7,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:UniGetUI.Interface.Dialogs" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:media="using:CommunityToolkit.WinUI.Media" xmlns:widgets="using:UniGetUI.Interface.Widgets" MaxWidth="700" mc:Ignorable="d"> @@ -28,181 +29,261 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -225,7 +306,7 @@ - + diff --git a/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml.cs b/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml.cs index 676c951dd9..e5194ae49f 100644 --- a/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml.cs +++ b/src/UniGetUI/Pages/DialogPages/InstallOptions.xaml.cs @@ -1,6 +1,9 @@ +using System.Data; using System.Runtime.InteropServices; +using Microsoft.Extensions.Options; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media.Imaging; using UniGetUI.Core.Language; using UniGetUI.Core.Tools; using UniGetUI.PackageEngine.Enums; @@ -33,18 +36,54 @@ public InstallOptionsPage(IPackage package, OperationType operation, Serializabl Operation = operation; Options = options; + ProfileComboBox.Items.Add(CoreTools.Translate("Install")); + ProfileComboBox.Items.Add(CoreTools.Translate("Update")); + ProfileComboBox.Items.Add(CoreTools.Translate("Uninstall")); + ProfileComboBox.SelectedIndex = operation switch { OperationType.Update => 1, OperationType.Uninstall => 2, _ => 0 }; + ProfileComboBox.SelectionChanged += (_, _) => + { + EnableDisableControls(ProfileComboBox.SelectedIndex switch + { + 1 => OperationType.Update, + 2 => OperationType.Uninstall, + _ => OperationType.Install, + }); + }; + + FollowGlobalOptionsSwitch.IsOn = !options.OverridesNextLevelOpts; + FollowGlobalOptionsSwitch.Toggled += (_, _) => + { + EnableDisableControls(ProfileComboBox.SelectedIndex switch + { + 1 => OperationType.Update, + 2 => OperationType.Uninstall, + _ => OperationType.Install, + }); + }; + + var iconSource = new BitmapImage() + { + UriSource = package.GetIconUrl(), + DecodePixelHeight = 32, + DecodePixelWidth = 32, + DecodePixelType = + DecodePixelType.Logical + }; + + PackageIcon.Source = iconSource; + async Task LoadImage() + { + iconSource.UriSource = await Task.Run(package.GetIconUrl); + } + _ = LoadImage(); + DialogTitle.Text = CoreTools.Translate("{0} installation options", package.Name); + packageInstallLocation = Package.Manager.DetailsHelper.GetInstallLocation(package) ?? CoreTools.Translate("Unset or unknown"); AdminCheckBox.IsChecked = Options.RunAsAdministrator; - AdminCheckBox.IsEnabled = Package.Manager.Capabilities.CanRunAsAdmin; - InteractiveCheckBox.IsChecked = Options.InteractiveInstallation; - InteractiveCheckBox.IsEnabled = Package.Manager.Capabilities.CanRunInteractively; - HashCheckbox.IsChecked = Options.SkipHashCheck; - HashCheckbox.IsEnabled = operation != OperationType.Uninstall && Package.Manager.Capabilities.CanSkipIntegrityChecks; - ArchitectureComboBox.IsEnabled = operation != OperationType.Uninstall && Package.Manager.Capabilities.SupportsCustomArchitectures; ArchitectureComboBox.Items.Add(CoreTools.Translate("Default")); ArchitectureComboBox.SelectedIndex = 0; @@ -60,12 +99,6 @@ public InstallOptionsPage(IPackage package, OperationType operation, Serializabl } } - VersionComboBox.IsEnabled = - (operation == OperationType.Install - || operation == OperationType.None) - && (Package.Manager.Capabilities.SupportsCustomVersions - || Package.Manager.Capabilities.SupportsPreRelease); - VersionComboBox.SelectionChanged += (_, _) => { IgnoreUpdatesCheckbox.IsChecked = @@ -99,7 +132,6 @@ public InstallOptionsPage(IPackage package, OperationType operation, Serializabl VersionProgress.Visibility = Visibility.Collapsed; } - ScopeCombo.IsEnabled = Package.Manager.Capabilities.SupportsCustomScopes; ScopeCombo.Items.Add(CoreTools.Translate("Default")); ScopeCombo.SelectedIndex = 0; if (package.Manager.Capabilities.SupportsCustomScopes) @@ -117,8 +149,6 @@ public InstallOptionsPage(IPackage package, OperationType operation, Serializabl } } - ResetDir.IsEnabled = Package.Manager.Capabilities.SupportsCustomLocations; - SelectDir.IsEnabled = Package.Manager.Capabilities.SupportsCustomLocations; if (Options.CustomInstallLocation == "") CustomInstallLocation.Text = packageInstallLocation; else CustomInstallLocation.Text = Options.CustomInstallLocation; @@ -129,10 +159,44 @@ public InstallOptionsPage(IPackage package, OperationType operation, Serializabl } _uiLoaded = true; - GenerateCommand(); + EnableDisableControls(operation); LoadIgnoredUpdates(); } + private void EnableDisableControls(OperationType operation) + { + if(FollowGlobalOptionsSwitch.IsOn) + { + OptionsPanel0.Opacity = 0.6; + OptionsPanel1.Opacity = 0.6; + OptionsPanel2.Opacity = 0.6; + OptionsPanelBase.IsEnabled = false; + PlaceholderBanner.Visibility = Visibility.Visible; + } + else + { + OptionsPanel0.Opacity = 1; + OptionsPanel1.Opacity = 1; + OptionsPanel2.Opacity = 1; + OptionsPanelBase.IsEnabled = true; + PlaceholderBanner.Visibility = Visibility.Collapsed; + + AdminCheckBox.IsEnabled = Package.Manager.Capabilities.CanRunAsAdmin; + InteractiveCheckBox.IsEnabled = Package.Manager.Capabilities.CanRunInteractively; + HashCheckbox.IsEnabled = operation != OperationType.Uninstall && Package.Manager.Capabilities.CanSkipIntegrityChecks; + ArchitectureComboBox.IsEnabled = operation != OperationType.Uninstall && Package.Manager.Capabilities.SupportsCustomArchitectures; + VersionComboBox.IsEnabled = + (operation == OperationType.Install + || operation == OperationType.None) + && (Package.Manager.Capabilities.SupportsCustomVersions + || Package.Manager.Capabilities.SupportsPreRelease); + ScopeCombo.IsEnabled = Package.Manager.Capabilities.SupportsCustomScopes; + ResetDir.IsEnabled = Package.Manager.Capabilities.SupportsCustomLocations; + SelectDir.IsEnabled = Package.Manager.Capabilities.SupportsCustomLocations; + } + GenerateCommand(); + } + private async void LoadIgnoredUpdates() { IgnoreUpdatesCheckbox.IsChecked = await Package.GetIgnoredUpdatesVersionAsync() == "*"; @@ -166,6 +230,7 @@ public async Task GetUpdatedOptions(bool update Options.RunAsAdministrator = AdminCheckBox?.IsChecked ?? false; Options.InteractiveInstallation = InteractiveCheckBox?.IsChecked ?? false; Options.SkipHashCheck = HashCheckbox?.IsChecked ?? false; + Options.OverridesNextLevelOpts = !FollowGlobalOptionsSwitch.IsOn; if (CommonTranslations.InvertedArchNames.ContainsKey(ArchitectureComboBox.SelectedValue.ToString() ?? "")) { @@ -253,8 +318,12 @@ private async void GenerateCommand() { if (!_uiLoaded) return; InstallationOptions io = InstallationOptions.FromSerialized(await GetUpdatedOptions(updateIgnoredUpdates: false), Package); - var op = Operation; - if (op is OperationType.None) op = OperationType.Install; + var op = ProfileComboBox.SelectedIndex switch + { + 1 => OperationType.Update, + 2 => OperationType.Uninstall, + _ => OperationType.Install, + }; var commandline = await Task.Run(() => Package.Manager.OperationHelper.GetParameters(Package, io, op)); CommandBox.Text = Package.Manager.Properties.ExecutableFriendlyName + " " + string.Join(" ", commandline); } @@ -263,5 +332,10 @@ private void LayoutGrid_SizeChanged(object sender, SizeChangedEventArgs e) { if(LayoutGrid.ActualSize.Y > 1 && LayoutGrid.ActualSize.Y < double.PositiveInfinity) MaxHeight = LayoutGrid.ActualSize.Y; } + + private void ProfileComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + + } } } diff --git a/src/UniGetUI/UniGetUI.csproj b/src/UniGetUI/UniGetUI.csproj index 443f29fc98..40d5d5f17f 100644 --- a/src/UniGetUI/UniGetUI.csproj +++ b/src/UniGetUI/UniGetUI.csproj @@ -73,6 +73,7 @@ + From a11b5c6214f8e25bb281d161d51b09dae3ccb118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Thu, 5 Jun 2025 12:39:18 +0200 Subject: [PATCH 06/12] prevent basic command injection (step 1/2, fix #3714) --- .../Helpers/CargoPkgOperationHelper.cs | 2 +- .../Helpers/ChocolateyPkgOperationHelper.cs | 2 +- .../Helpers/DotNetPkgOperationHelper.cs | 2 +- .../Helpers/NpmPkgOperationHelper.cs | 2 +- .../Helpers/PipPkgOperationHelper.cs | 2 +- .../Helpers/PowerShellPkgOperationHelper.cs | 2 +- .../Helpers/PowerShell7PkgOperationHelper.cs | 2 +- .../Helpers/ScoopPkgOperationHelper.cs | 2 +- .../Helpers/VcpkgPkgOperationHelper.cs | 2 +- .../Helpers/WinGetPkgOperationHelper.cs | 2 +- ...ionHelper.cs => BasePkgOperationHelper.cs} | 23 ++++++++++++++----- 11 files changed, 27 insertions(+), 16 deletions(-) rename src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/Helpers/{PackagePkgOperationHelper.cs => BasePkgOperationHelper.cs} (69%) diff --git a/src/UniGetUI.PackageEngine.Managers.Cargo/Helpers/CargoPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Cargo/Helpers/CargoPkgOperationHelper.cs index e4da839cc0..23beae6bf2 100644 --- a/src/UniGetUI.PackageEngine.Managers.Cargo/Helpers/CargoPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.Cargo/Helpers/CargoPkgOperationHelper.cs @@ -4,7 +4,7 @@ namespace UniGetUI.PackageEngine.Managers.CargoManager; -internal sealed class CargoPkgOperationHelper(Cargo cargo) : PackagePkgOperationHelper(cargo) +internal sealed class CargoPkgOperationHelper(Cargo cargo) : BasePkgOperationHelper(cargo) { protected override IReadOnlyList _getOperationParameters(IPackage package, IInstallationOptions options, OperationType operation) { diff --git a/src/UniGetUI.PackageEngine.Managers.Chocolatey/Helpers/ChocolateyPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Chocolatey/Helpers/ChocolateyPkgOperationHelper.cs index 54de0ebabd..897a421f0a 100644 --- a/src/UniGetUI.PackageEngine.Managers.Chocolatey/Helpers/ChocolateyPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.Chocolatey/Helpers/ChocolateyPkgOperationHelper.cs @@ -4,7 +4,7 @@ using UniGetUI.PackageEngine.Interfaces; namespace UniGetUI.PackageEngine.Managers.ChocolateyManager; -internal sealed class ChocolateyPkgOperationHelper : PackagePkgOperationHelper +internal sealed class ChocolateyPkgOperationHelper : BasePkgOperationHelper { public ChocolateyPkgOperationHelper(Chocolatey manager) : base(manager) { } diff --git a/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs index 4541eaed9d..b360d7a74f 100644 --- a/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs @@ -4,7 +4,7 @@ using UniGetUI.PackageEngine.Interfaces; namespace UniGetUI.PackageEngine.Managers.DotNetManager; -internal sealed class DotNetPkgOperationHelper : PackagePkgOperationHelper +internal sealed class DotNetPkgOperationHelper : BasePkgOperationHelper { public DotNetPkgOperationHelper(DotNet manager) : base(manager) { } diff --git a/src/UniGetUI.PackageEngine.Managers.Npm/Helpers/NpmPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Npm/Helpers/NpmPkgOperationHelper.cs index 49f57608a4..ec718e7786 100644 --- a/src/UniGetUI.PackageEngine.Managers.Npm/Helpers/NpmPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.Npm/Helpers/NpmPkgOperationHelper.cs @@ -3,7 +3,7 @@ using UniGetUI.PackageEngine.Interfaces; namespace UniGetUI.PackageEngine.Managers.NpmManager; -internal sealed class NpmPkgOperationHelper : PackagePkgOperationHelper +internal sealed class NpmPkgOperationHelper : BasePkgOperationHelper { public NpmPkgOperationHelper(Npm manager) : base(manager) { } diff --git a/src/UniGetUI.PackageEngine.Managers.Pip/Helpers/PipPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Pip/Helpers/PipPkgOperationHelper.cs index 8f9e8c8c2b..10be39572d 100644 --- a/src/UniGetUI.PackageEngine.Managers.Pip/Helpers/PipPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.Pip/Helpers/PipPkgOperationHelper.cs @@ -3,7 +3,7 @@ using UniGetUI.PackageEngine.Interfaces; namespace UniGetUI.PackageEngine.Managers.PipManager; -internal sealed class PipPkgOperationHelper : PackagePkgOperationHelper +internal sealed class PipPkgOperationHelper : BasePkgOperationHelper { public PipPkgOperationHelper(Pip manager) : base(manager) { } diff --git a/src/UniGetUI.PackageEngine.Managers.PowerShell/Helpers/PowerShellPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.PowerShell/Helpers/PowerShellPkgOperationHelper.cs index f23005f14e..d7bfa540e6 100644 --- a/src/UniGetUI.PackageEngine.Managers.PowerShell/Helpers/PowerShellPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.PowerShell/Helpers/PowerShellPkgOperationHelper.cs @@ -3,7 +3,7 @@ using UniGetUI.PackageEngine.Interfaces; namespace UniGetUI.PackageEngine.Managers.PowerShellManager; -internal sealed class PowerShellPkgOperationHelper : PackagePkgOperationHelper +internal sealed class PowerShellPkgOperationHelper : BasePkgOperationHelper { public PowerShellPkgOperationHelper(PowerShell manager) : base(manager) { } diff --git a/src/UniGetUI.PackageEngine.Managers.PowerShell7/Helpers/PowerShell7PkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.PowerShell7/Helpers/PowerShell7PkgOperationHelper.cs index 6bdcbd6ef4..56bc0a32ea 100644 --- a/src/UniGetUI.PackageEngine.Managers.PowerShell7/Helpers/PowerShell7PkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.PowerShell7/Helpers/PowerShell7PkgOperationHelper.cs @@ -3,7 +3,7 @@ using UniGetUI.PackageEngine.Interfaces; namespace UniGetUI.PackageEngine.Managers.PowerShell7Manager; -internal sealed class PowerShell7PkgOperationHelper : PackagePkgOperationHelper +internal sealed class PowerShell7PkgOperationHelper : BasePkgOperationHelper { public PowerShell7PkgOperationHelper(PowerShell7 manager) : base(manager) { } diff --git a/src/UniGetUI.PackageEngine.Managers.Scoop/Helpers/ScoopPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Scoop/Helpers/ScoopPkgOperationHelper.cs index 34529da319..c3c6ae2033 100644 --- a/src/UniGetUI.PackageEngine.Managers.Scoop/Helpers/ScoopPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.Scoop/Helpers/ScoopPkgOperationHelper.cs @@ -4,7 +4,7 @@ using UniGetUI.PackageEngine.Interfaces; namespace UniGetUI.PackageEngine.Managers.ScoopManager; -internal sealed class ScoopPkgOperationHelper : PackagePkgOperationHelper +internal sealed class ScoopPkgOperationHelper : BasePkgOperationHelper { public ScoopPkgOperationHelper(Scoop manager) : base(manager) { } diff --git a/src/UniGetUI.PackageEngine.Managers.Vcpkg/Helpers/VcpkgPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Vcpkg/Helpers/VcpkgPkgOperationHelper.cs index 876094dac7..a005acc842 100644 --- a/src/UniGetUI.PackageEngine.Managers.Vcpkg/Helpers/VcpkgPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.Vcpkg/Helpers/VcpkgPkgOperationHelper.cs @@ -3,7 +3,7 @@ using UniGetUI.PackageEngine.Interfaces; namespace UniGetUI.PackageEngine.Managers.VcpkgManager; -internal sealed class VcpkgPkgOperationHelper : PackagePkgOperationHelper +internal sealed class VcpkgPkgOperationHelper : BasePkgOperationHelper { public VcpkgPkgOperationHelper(Vcpkg manager) : base(manager) { } diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs index dde8e83c8e..9b9e98a936 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs @@ -9,7 +9,7 @@ using UniGetUI.PackageEngine.Interfaces; namespace UniGetUI.PackageEngine.Managers.WingetManager; -internal sealed class WinGetPkgOperationHelper : PackagePkgOperationHelper +internal sealed class WinGetPkgOperationHelper : BasePkgOperationHelper { public static string GetIdNamePiece(IPackage package) { diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/Helpers/PackagePkgOperationHelper.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/Helpers/BasePkgOperationHelper.cs similarity index 69% rename from src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/Helpers/PackagePkgOperationHelper.cs rename to src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/Helpers/BasePkgOperationHelper.cs index 168949683a..2b44f013d3 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/Helpers/PackagePkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/Helpers/BasePkgOperationHelper.cs @@ -5,11 +5,11 @@ namespace UniGetUI.PackageEngine.Classes.Manager.BaseProviders; -public abstract class PackagePkgOperationHelper : IPackageOperationHelper +public abstract class BasePkgOperationHelper : IPackageOperationHelper { protected IPackageManager Manager; - public PackagePkgOperationHelper(IPackageManager manager) + public BasePkgOperationHelper(IPackageManager manager) { Manager = manager; } @@ -30,10 +30,21 @@ public IReadOnlyList GetParameters( IInstallationOptions options, OperationType operation) { - var parameters = _getOperationParameters(package, options, operation); - Logger.Info( - $"Loaded operation parameters for package id={package.Id} on manager {Manager.Name} and operation {operation}: " + - string.Join(' ', parameters)); + var parameters = _getOperationParameters(package, options, operation).ToArray(); + + for (int i = 0; i < parameters.Length; i++) + { + parameters[i] = parameters[i] + .Replace("&", "") + .Replace("|", "") + .Replace(";", "") + .Replace("<", "") + .Replace(">", "") + .Replace("\n", ""); + } + + Logger.Info($"Loaded operation parameters for package id={package.Id} on manager {Manager.Name} and operation {operation}: " + + string.Join(' ', parameters)); return parameters; } From 341bd1210bb2ded55087afe511b51b64867df17a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Thu, 5 Jun 2025 13:39:54 +0200 Subject: [PATCH 07/12] Add behaviour to select applicable options when required --- .../IInstallationOptions.cs | 27 +-- .../UpgradablePackagesLoader.cs | 2 +- .../Packages/Classes/InstallationOptions.cs | 180 +++++++++--------- .../Packages/ImportedPackage.cs | 7 +- .../Packages/Package.cs | 2 +- src/UniGetUI/AppOperationHelper.cs | 6 +- .../DialogPages/DialogHelper_Packages.cs | 6 +- .../DialogPages/PackageDetailsPage.xaml.cs | 9 +- 8 files changed, 113 insertions(+), 126 deletions(-) diff --git a/src/UniGetUI.PAckageEngine.Interfaces/IInstallationOptions.cs b/src/UniGetUI.PAckageEngine.Interfaces/IInstallationOptions.cs index 5f16e55490..1718e6ea7e 100644 --- a/src/UniGetUI.PAckageEngine.Interfaces/IInstallationOptions.cs +++ b/src/UniGetUI.PAckageEngine.Interfaces/IInstallationOptions.cs @@ -17,44 +17,23 @@ public interface IInstallationOptions public bool RemoveDataOnUninstall { get; set; } public bool PreRelease { get; set; } public string CustomInstallLocation { get; set; } - public IPackage Package { get; } + public bool OverridesNextLevelOpts { get; set; } /// /// Loads and applies the options from the given SerializableInstallationOptions object to the current object. /// - public void FromSerializable(SerializableInstallationOptions options); + public void GetValuesFromSerializable(SerializableInstallationOptions options); /// /// Returns a SerializableInstallationOptions object containing the options of the current instance. /// - public SerializableInstallationOptions AsSerializable(); - - /// - /// Saves the current options to disk, asynchronously. - /// - public async Task SaveToDiskAsync() - { - await Task.Run(SaveToDisk); - } - - /// - /// Loads the options from disk, asynchronously. - /// - public async Task LoadFromDiskAsync() - { - await Task.Run(LoadFromDisk); - } + public SerializableInstallationOptions ToSerializable(); /// /// Saves the current options to disk. /// public void SaveToDisk(); - /// - /// Loads the options from disk. - /// - protected void LoadFromDisk(); - /// /// Returns a string representation of the current options. /// diff --git a/src/UniGetUI.PackageEngine.PackageLoader/UpgradablePackagesLoader.cs b/src/UniGetUI.PackageEngine.PackageLoader/UpgradablePackagesLoader.cs index 2fa5040f70..e2f6c53634 100644 --- a/src/UniGetUI.PackageEngine.PackageLoader/UpgradablePackagesLoader.cs +++ b/src/UniGetUI.PackageEngine.PackageLoader/UpgradablePackagesLoader.cs @@ -37,7 +37,7 @@ protected override async Task IsPackageValid(IPackage package) IgnoredPackages[package.Id] = package; return false; } - if ((await InstallationOptions.FromPackageAsync(package)).SkipMinorUpdates && package.IsUpdateMinor()) + if ((await InstallationOptions.LoadApplicableAsync(package)).SkipMinorUpdates && package.IsUpdateMinor()) { Logger.Info($"Ignoring package {package.Id} because it is a minor update ({package.VersionString} -> {package.NewVersionString}) and SkipMinorUpdates is set to true."); return false; diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs index 9c5def561a..8f71581687 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallationOptions.cs @@ -17,7 +17,7 @@ namespace UniGetUI.PackageEngine.PackageClasses /// public class InstallationOptions : IInstallationOptions { - private static readonly ConcurrentDictionary OptionsCache = []; + // private static readonly ConcurrentDictionary OptionsCache = []; public bool SkipHashCheck { get; set; } public bool InteractiveInstallation { get; set; } @@ -32,88 +32,103 @@ public class InstallationOptions : IInstallationOptions public string CustomInstallLocation { get; set; } = ""; public bool OverridesNextLevelOpts { get; set; } - public IPackage Package { get; } private readonly string __save_filename; - private InstallationOptions(IPackage package) + private InstallationOptions(string filename) { - Package = package; - __save_filename = package.Manager.Name.Replace(" ", "").Replace(".", "") + "." + package.Id; + __save_filename = Path.Join(CoreData.UniGetUIInstallationOptionsDirectory, filename); } - /// - /// Returns the InstallationOptions object associated with the given package. - /// - /// The package's InstallationOptions instance - public static InstallationOptions FromPackage(IPackage package, bool? elevated = null, bool? - interactive = null, bool? no_integrity = null, bool? remove_data = null) + private static class StoragePath { - InstallationOptions instance; - if (OptionsCache.TryGetValue(package.GetHash(), out InstallationOptions? cached_instance) && cached_instance is not null) - { - instance = cached_instance; - } - else - { - instance = new(package); - instance.LoadFromDisk(); - OptionsCache.TryAdd(package.GetHash(), instance); - } + public static string Get(IPackageManager manager) + => "GlobalValues." + manager.Name.Replace(" ", "").Replace(".", ""); - if (elevated is not null) - { - instance.RunAsAdministrator = (bool)elevated; - } + public static string Get(IPackage package) + => package.Manager.Name.Replace(" ", "").Replace(".", "") + "." + package.Id; + } - if (interactive is not null) - { - instance.InteractiveInstallation = (bool)interactive; - } + public static InstallationOptions CreateEmpty(IPackage package) + { + var pkg_path = StoragePath.Get(package); + return new(pkg_path); + } - if (no_integrity is not null) - { - instance.SkipHashCheck = (bool)no_integrity; - } + public static InstallationOptions CreateEmpty(IPackageManager manager) + { + var mgr_path = StoragePath.Get(manager); + return new(mgr_path); + } - if (remove_data is not null) - { - instance.RemoveDataOnUninstall = (bool)remove_data; - } + public static InstallationOptions FromSerialized(SerializableInstallationOptions options, IPackage package) + { + var instance = CreateEmpty(package); + instance.GetValuesFromSerializable(options); + return instance; + } + public static InstallationOptions FromSerialized(SerializableInstallationOptions options, IPackageManager manager) + { + var instance = CreateEmpty(manager); + instance.GetValuesFromSerializable(options); return instance; } - /// - /// Returns the InstallationOptions object associated with the given package. - /// - /// The package from which to load the InstallationOptions - /// The package's InstallationOptions instance - public static async Task FromPackageAsync(IPackage package, bool? elevated = null, - bool? interactive = null, bool? no_integrity = null, bool? remove_data = null) + public static InstallationOptions LoadForPackage(IPackage package) { - return await Task.Run(() => FromPackage(package, elevated, interactive, no_integrity, remove_data)); + InstallationOptions instance = CreateEmpty(package); + instance.LoadFromDisk(); + return instance; } - /// - /// Returns a new InstallationOptions object from a given SerializableInstallationOptions and a package. - /// - public static InstallationOptions FromSerialized(SerializableInstallationOptions options, IPackage package) + public static async Task LoadForPackageAsync(IPackage package) { - InstallationOptions instance = new(package); - instance.FromSerializable(options); + InstallationOptions instance = CreateEmpty(package); + await Task.Run(instance.LoadFromDisk); return instance; } - public static InstallationOptions CreateEmpty(IPackage package) + public static InstallationOptions LoadForManager(IPackageManager manager) + { + InstallationOptions instance = CreateEmpty(manager); + instance.LoadFromDisk(); + return instance; + } + + public static InstallationOptions LoadApplicable( + IPackage package, + bool? elevated = null, + bool? interactive = null, + bool? no_integrity = null, + bool? remove_data = null) { - InstallationOptions instance = new(package); + InstallationOptions instance = LoadForPackage(package); + if (!instance.OverridesNextLevelOpts) + { + instance = LoadForManager(package.Manager); + Logger.Debug($"Package {package.Id} does not override options, will use package manager's default..."); + } + + if (elevated is not null) instance.RunAsAdministrator = (bool)elevated; + if (interactive is not null) instance.InteractiveInstallation = (bool)interactive; + if (no_integrity is not null) instance.SkipHashCheck = (bool)no_integrity; + if (remove_data is not null) instance.RemoveDataOnUninstall = (bool)remove_data; + return instance; } + public static Task LoadApplicableAsync( + IPackage package, + bool? elevated = null, + bool? interactive = null, + bool? no_integrity = null, + bool? remove_data = null) + => Task.Run(() => LoadApplicable(package, elevated, interactive, no_integrity, remove_data)); + /// /// Loads and applies the options from the given SerializableInstallationOptions object to the current object. /// - public void FromSerializable(SerializableInstallationOptions options) + public void GetValuesFromSerializable(SerializableInstallationOptions options) { SkipHashCheck = options.SkipHashCheck; InteractiveInstallation = options.InteractiveInstallation; @@ -144,7 +159,7 @@ public void FromSerializable(SerializableInstallationOptions options) /// /// Returns a SerializableInstallationOptions object containing the options of the current instance. /// - public SerializableInstallationOptions AsSerializable() + public SerializableInstallationOptions ToSerializable() { SerializableInstallationOptions options = new() { @@ -168,20 +183,6 @@ public SerializableInstallationOptions AsSerializable() return options; } - private FileInfo GetPackageOptionsFile() - { - string optionsFileName = Package.Manager.Name + "." + Package.Id.Split(":")[0] + ".json"; - return new FileInfo(Path.Join(CoreData.UniGetUIInstallationOptionsDirectory, optionsFileName)); - } - - /// - /// Saves the current options to disk, asynchronously. - /// - public async Task SaveToDiskAsync() - { - await Task.Run(SaveToDisk); - } - /// /// Saves the current options to disk. /// @@ -189,52 +190,55 @@ public void SaveToDisk() { try { - FileInfo optionsFile = GetPackageOptionsFile(); - if (optionsFile.Directory?.Exists == false) + var dir = Path.GetDirectoryName(__save_filename); + ArgumentException.ThrowIfNullOrEmpty(dir); + if (!Directory.Exists(dir)) { - optionsFile.Directory.Create(); + Directory.CreateDirectory(dir); } string fileContents = JsonSerializer.Serialize( - AsSerializable(), + ToSerializable(), SerializationHelpers.DefaultOptions ); - File.WriteAllText(optionsFile.FullName, fileContents); + File.WriteAllText(__save_filename, fileContents); } catch (Exception ex) { - Logger.Error($"Could not save {Package.Id} options to disk"); + Logger.Error($"Could not save {__save_filename} options to disk"); Logger.Error(ex); } } + /// + /// Saves the current options to disk. + /// + public Task SaveToDiskAsync() => Task.Run(SaveToDisk); + /// /// Loads the options from disk. /// - public void LoadFromDisk() + private void LoadFromDisk() { - FileInfo optionsFile = GetPackageOptionsFile(); try { - if (!optionsFile.Exists) + if (!File.Exists(__save_filename)) return; - var rawData = File.ReadAllText(optionsFile.FullName); + var rawData = File.ReadAllText(__save_filename); JsonNode? jsonData = JsonNode.Parse(rawData); - if (jsonData is null) - return; - + ArgumentNullException.ThrowIfNull(jsonData); var serializedOptions = new SerializableInstallationOptions(jsonData); - FromSerializable(serializedOptions); + GetValuesFromSerializable(serializedOptions); } catch (JsonException) { - Logger.Warn("An error occurred while parsing package " + optionsFile + ". The file will be overwritten"); - File.WriteAllText(optionsFile.FullName, "{}"); + Logger.Warn("An error occurred while parsing package " + __save_filename + ". The file will be overwritten"); + File.WriteAllText(__save_filename, "{}"); } catch (Exception e) { - Logger.Error("Loading installation options for file " + optionsFile + " have failed: "); + Logger.Error("Loading installation options for file " + __save_filename + " have failed: "); Logger.Error(e); } } @@ -244,7 +248,7 @@ public void LoadFromDisk() /// public override string ToString() { - string customparams = CustomParameters is not null ? string.Join(",", CustomParameters) : "[]"; + string customparams = CustomParameters.Any() ? string.Join(",", CustomParameters) : "[]"; return $" RegisterAndGetPackageAsync() { - var options = await InstallationOptions.FromPackageAsync(this); - options.FromSerializable(installation_options); - await options.SaveToDiskAsync(); + await Task.Run(() => + { + InstallationOptions.FromSerialized(installation_options, this).SaveToDisk(); + }); if (updates_options.UpdatesIgnored) { diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs index 5f099f5709..56e54d2a7e 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs @@ -310,7 +310,7 @@ public virtual SerializablePackage AsSerializable() Version = VersionString, Source = Source.Name, ManagerName = Manager.Name, - InstallationOptions = InstallationOptions.FromPackage(this).AsSerializable(), + InstallationOptions = InstallationOptions.LoadForPackage(this).ToSerializable(), Updates = new SerializableUpdatesOptions { IgnoredVersion = GetIgnoredUpdatesVersionAsync().GetAwaiter().GetResult(), diff --git a/src/UniGetUI/AppOperationHelper.cs b/src/UniGetUI/AppOperationHelper.cs index 9b08b250fc..771072ec27 100644 --- a/src/UniGetUI/AppOperationHelper.cs +++ b/src/UniGetUI/AppOperationHelper.cs @@ -124,7 +124,7 @@ public static void Remove(AbstractOperation op) { if (package is null) return null; - var options = await InstallationOptions.FromPackageAsync(package, elevated, interactive, no_integrity); + var options = await InstallationOptions.LoadApplicableAsync(package, elevated, interactive, no_integrity); var op = new InstallPackageOperation(package, options, ignoreParallel, req); op.OperationSucceeded += (_, _) => TelemetryHandler.InstallPackage(package, TEL_OP_RESULT.SUCCESS, referral); op.OperationFailed += (_, _) => TelemetryHandler.InstallPackage(package, TEL_OP_RESULT.FAILED, referral); @@ -148,7 +148,7 @@ public static void Install(IReadOnlyList packages, TEL_InstallReferral { if (package is null) return null; - var options = await InstallationOptions.FromPackageAsync(package, elevated, interactive, no_integrity); + var options = await InstallationOptions.LoadApplicableAsync(package, elevated, interactive, no_integrity); var op = new UpdatePackageOperation(package, options, ignoreParallel, req); op.OperationSucceeded += (_, _) => TelemetryHandler.UpdatePackage(package, TEL_OP_RESULT.SUCCESS); op.OperationFailed += (_, _) => TelemetryHandler.UpdatePackage(package, TEL_OP_RESULT.FAILED); @@ -221,7 +221,7 @@ public static async void ConfirmAndUninstall(IPackage? package, bool? elevated = { if (package is null) return null; - var options = await InstallationOptions.FromPackageAsync(package, elevated, interactive, remove_data: remove_data); + var options = await InstallationOptions.LoadApplicableAsync(package, elevated, interactive, remove_data: remove_data); var op = new UninstallPackageOperation(package, options, ignoreParallel, req); op.OperationSucceeded += (_, _) => TelemetryHandler.UninstallPackage(package, TEL_OP_RESULT.SUCCESS); op.OperationFailed += (_, _) => TelemetryHandler.UninstallPackage(package, TEL_OP_RESULT.FAILED); diff --git a/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs b/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs index 5c4915acfb..a7cad147c0 100644 --- a/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs +++ b/src/UniGetUI/Pages/DialogPages/DialogHelper_Packages.cs @@ -20,13 +20,13 @@ public static partial class DialogHelper /// public static async Task ShowInstallatOptions_Continue(IPackage package, OperationType operation) { - var options = (await InstallationOptions.FromPackageAsync(package)).AsSerializable(); + var options = (await InstallationOptions.LoadForPackageAsync(package)).ToSerializable(); var (dialogOptions, dialogResult) = await ShowInstallOptions(package, operation, options); if (dialogResult != ContentDialogResult.None) { - InstallationOptions newOptions = await InstallationOptions.FromPackageAsync(package); - newOptions.FromSerializable(dialogOptions); + InstallationOptions newOptions = await InstallationOptions.LoadForPackageAsync(package); + newOptions.GetValuesFromSerializable(dialogOptions); await newOptions.SaveToDiskAsync(); } diff --git a/src/UniGetUI/Pages/DialogPages/PackageDetailsPage.xaml.cs b/src/UniGetUI/Pages/DialogPages/PackageDetailsPage.xaml.cs index 1666c56639..3c25199e3c 100644 --- a/src/UniGetUI/Pages/DialogPages/PackageDetailsPage.xaml.cs +++ b/src/UniGetUI/Pages/DialogPages/PackageDetailsPage.xaml.cs @@ -102,7 +102,7 @@ public PackageDetailsPage(IPackage package, OperationType role, TEL_InstallRefer UpgradablePackage = Package.GetUpgradablePackage(); InstalledPackage = UpgradablePackage?.GetInstalledPackage() ?? Package.GetInstalledPackage(); - var options = InstallationOptions.FromPackage(package).AsSerializable(); + var options = InstallationOptions.LoadApplicable(package).ToSerializable(); InstallOptionsPage = new InstallOptionsPage(package, OperationRole, options); InstallOptionsExpander.Content = InstallOptionsPage; @@ -578,8 +578,11 @@ public async void DoAction( { Close?.Invoke(this, EventArgs.Empty); - var newOptions = await InstallationOptions.FromPackageAsync(package); - newOptions.FromSerializable(await InstallOptionsPage.GetUpdatedOptions()); + var newOptions = await Task.Run( + () => InstallationOptions.FromSerialized( + InstallOptionsPage.GetUpdatedOptions().GetAwaiter().GetResult(), + package) + ); newOptions.SaveToDisk(); if (AsAdmin is not null) newOptions.RunAsAdministrator = (bool)AsAdmin; From 28c45846f52cca155c390fbfbb70212ef488085a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Thu, 5 Jun 2025 13:42:04 +0200 Subject: [PATCH 08/12] add missing keys --- .../Assets/Data/LanguagesReference.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/UniGetUI.Core.LanguageEngine/Assets/Data/LanguagesReference.json b/src/UniGetUI.Core.LanguageEngine/Assets/Data/LanguagesReference.json index 940c63b497..8a5c5b44fb 100644 --- a/src/UniGetUI.Core.LanguageEngine/Assets/Data/LanguagesReference.json +++ b/src/UniGetUI.Core.LanguageEngine/Assets/Data/LanguagesReference.json @@ -12,10 +12,12 @@ "et": "Estonian - Eesti", "en": "English - English", "es": "Spanish - Castellano", + "es-MX": "Spanish (Mexico)", "fa": "Persian - فارسی‎", "fi": "Finnish - Suomi", "fil": "Filipino - Filipino", "fr": "French - Français", + "gl": "Galician - Galego", "gu": "Gujarati - ગુજરાતી", "hi": "Hindi - हिंदी", "hr": "Croatian - Hrvatski", From a2b7fed3a09632012719318fd0a4dc23180f4cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Fri, 6 Jun 2025 18:48:56 +0200 Subject: [PATCH 09/12] Add per-manager installation options UI --- .../DialogPages/InstallOptions_Manager.xaml | 176 +++++++++++ .../InstallOptions_Manager.xaml.cs | 279 ++++++++++++++++++ .../ManagersPages/PackageManager.xaml | 10 + .../ManagersPages/PackageManager.xaml.cs | 3 + src/UniGetUI/UniGetUI.csproj | 4 + src/UniGetUI/UniGetUI.csproj.user | 3 + 6 files changed, 475 insertions(+) create mode 100644 src/UniGetUI/Pages/DialogPages/InstallOptions_Manager.xaml create mode 100644 src/UniGetUI/Pages/DialogPages/InstallOptions_Manager.xaml.cs diff --git a/src/UniGetUI/Pages/DialogPages/InstallOptions_Manager.xaml b/src/UniGetUI/Pages/DialogPages/InstallOptions_Manager.xaml new file mode 100644 index 0000000000..2fad9d1f16 --- /dev/null +++ b/src/UniGetUI/Pages/DialogPages/InstallOptions_Manager.xaml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + +