diff --git a/src/UniGetUI.Core.Data/CoreData.cs b/src/UniGetUI.Core.Data/CoreData.cs index 5c17b6a055..1eac17c0db 100644 --- a/src/UniGetUI.Core.Data/CoreData.cs +++ b/src/UniGetUI.Core.Data/CoreData.cs @@ -1,7 +1,4 @@ using System.Diagnostics; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; using UniGetUI.Core.Logging; namespace UniGetUI.Core.Data diff --git a/src/UniGetUI.Core.LanguageEngine/LanguageData.cs b/src/UniGetUI.Core.LanguageEngine/LanguageData.cs index 8c374f7e64..07e6d10cc6 100644 --- a/src/UniGetUI.Core.LanguageEngine/LanguageData.cs +++ b/src/UniGetUI.Core.LanguageEngine/LanguageData.cs @@ -1,11 +1,9 @@ using System.Collections.ObjectModel; -using System.Runtime.InteropServices; using System.Text.Json.Nodes; using UniGetUI.Core.Classes; using UniGetUI.Core.Data; using UniGetUI.Core.Logging; using UniGetUI.PackageEngine.Enums; -using Architecture = UniGetUI.PackageEngine.Enums.Architecture; namespace UniGetUI.Core.Language { diff --git a/src/UniGetUI.Core.SecureSettings/SecureSettings.cs b/src/UniGetUI.Core.SecureSettings/SecureSettings.cs index 11b2d0044e..7ffe30fbc7 100644 --- a/src/UniGetUI.Core.SecureSettings/SecureSettings.cs +++ b/src/UniGetUI.Core.SecureSettings/SecureSettings.cs @@ -1,8 +1,6 @@ using System.Diagnostics; using UniGetUI.Core.Data; using UniGetUI.Core.Tools; -using YamlDotNet.Core.Tokens; -using YamlDotNet.Serialization; namespace UniGetUI.Core.SettingsEngine.SecureSettings; diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs index e9a5054fea..33a55de508 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs @@ -74,6 +74,7 @@ public enum K DisableErrorNotifications, DisableSuccessNotifications, DisableProgressNotifications, + KillProcessesThatRefuseToDie, Test1, Test2, @@ -159,6 +160,7 @@ public static string ResolveKey(K key) K.DisableErrorNotifications => "DisableErrorNotifications", K.DisableSuccessNotifications => "DisableSuccessNotifications", K.DisableProgressNotifications => "DisableProgressNotifications", + K.KillProcessesThatRefuseToDie => "KillProcessesThatRefuseToDie", K.Test1 => "TestSetting1", K.Test2 => "TestSetting2", diff --git a/src/UniGetUI.Core.Tools/SerializationHelpers.cs b/src/UniGetUI.Core.Tools/SerializationHelpers.cs index be129d130b..4172b94dc2 100644 --- a/src/UniGetUI.Core.Tools/SerializationHelpers.cs +++ b/src/UniGetUI.Core.Tools/SerializationHelpers.cs @@ -1,7 +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; diff --git a/src/UniGetUI.PackageEngine.Enums/ManagerCapabilities.cs b/src/UniGetUI.PackageEngine.Enums/ManagerCapabilities.cs index e9f14bf2af..ad224ea79e 100644 --- a/src/UniGetUI.PackageEngine.Enums/ManagerCapabilities.cs +++ b/src/UniGetUI.PackageEngine.Enums/ManagerCapabilities.cs @@ -1,5 +1,3 @@ -using System.Runtime.InteropServices; - namespace UniGetUI.PackageEngine.ManagerClasses.Manager { public enum ProxySupport diff --git a/src/UniGetUI.PackageEngine.Enums/OverridenInstallationOptions.cs b/src/UniGetUI.PackageEngine.Enums/OverridenInstallationOptions.cs index 052363b0d0..75bdc7df31 100644 --- a/src/UniGetUI.PackageEngine.Enums/OverridenInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Enums/OverridenInstallationOptions.cs @@ -1,5 +1,3 @@ -using UniGetUI.PackageEngine.Enums; - namespace UniGetUI.PackageEngine.Structs; public struct OverridenInstallationOptions { diff --git a/src/UniGetUI.PackageEngine.Managers.Chocolatey/Chocolatey.cs b/src/UniGetUI.PackageEngine.Managers.Chocolatey/Chocolatey.cs index d76cbe26e8..0a3c486d9b 100644 --- a/src/UniGetUI.PackageEngine.Managers.Chocolatey/Chocolatey.cs +++ b/src/UniGetUI.PackageEngine.Managers.Chocolatey/Chocolatey.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Runtime.InteropServices; using UniGetUI.Core.Data; using UniGetUI.Core.Logging; using UniGetUI.Core.SettingsEngine; diff --git a/src/UniGetUI.PackageEngine.Managers.Chocolatey/Helpers/ChocolateyPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Chocolatey/Helpers/ChocolateyPkgOperationHelper.cs index b9e5d70f3b..86b962f272 100644 --- a/src/UniGetUI.PackageEngine.Managers.Chocolatey/Helpers/ChocolateyPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.Chocolatey/Helpers/ChocolateyPkgOperationHelper.cs @@ -1,4 +1,3 @@ -using System.Runtime.InteropServices; using UniGetUI.PackageEngine.Classes.Manager.BaseProviders; using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Interfaces; diff --git a/src/UniGetUI.PackageEngine.Managers.Dotnet/DotNet.cs b/src/UniGetUI.PackageEngine.Managers.Dotnet/DotNet.cs index e1476affdf..82225200c6 100644 --- a/src/UniGetUI.PackageEngine.Managers.Dotnet/DotNet.cs +++ b/src/UniGetUI.PackageEngine.Managers.Dotnet/DotNet.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Runtime.InteropServices; using System.Text.RegularExpressions; using UniGetUI.Core.Tools; using UniGetUI.Interface.Enums; diff --git a/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs index 210ce71977..944e03f0b5 100644 --- a/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs @@ -1,4 +1,3 @@ -using System.Runtime.InteropServices; using UniGetUI.PackageEngine.Classes.Manager.BaseProviders; using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Interfaces; diff --git a/src/UniGetUI.PackageEngine.Managers.Scoop/Helpers/ScoopPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Scoop/Helpers/ScoopPkgOperationHelper.cs index 58cb4214a9..0443aaaa6d 100644 --- a/src/UniGetUI.PackageEngine.Managers.Scoop/Helpers/ScoopPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.Scoop/Helpers/ScoopPkgOperationHelper.cs @@ -1,4 +1,3 @@ -using System.Runtime.InteropServices; using UniGetUI.PackageEngine.Classes.Manager.BaseProviders; using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Interfaces; diff --git a/src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs b/src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs index 049ae08f65..65c4176cb4 100644 --- a/src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs +++ b/src/UniGetUI.PackageEngine.Managers.Scoop/Scoop.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Runtime.InteropServices; using System.Text.RegularExpressions; using UniGetUI.Core.Classes; using UniGetUI.Core.Logging; diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativePackageHandler.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativePackageHandler.cs index 5dc9607547..c3f8149d3a 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativePackageHandler.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/NativePackageHandler.cs @@ -1,10 +1,8 @@ using System.Collections.Concurrent; -using System.Linq.Expressions; using Microsoft.Management.Deployment; using UniGetUI.Core.Logging; using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Interfaces; -using UniGetUI.PackageEngine.Serializable; using InstallOptions = UniGetUI.PackageEngine.Serializable.InstallOptions; namespace UniGetUI.PackageEngine.Managers.WingetManager; diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/WinGetIconsHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/WinGetIconsHelper.cs index 7fee66356a..8523ac33c0 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/WinGetIconsHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/ClientHelpers/WinGetIconsHelper.cs @@ -1,7 +1,5 @@ using System.Globalization; using System.Net; -using System.Reflection; -using System.Text; using System.Text.RegularExpressions; using Microsoft.Management.Deployment; using Microsoft.Win32; diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgDetailsHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgDetailsHelper.cs index 2ebf3b4d49..71ee33ab9d 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgDetailsHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgDetailsHelper.cs @@ -1,8 +1,6 @@ -using System.ComponentModel.Design; using System.Text.RegularExpressions; using UniGetUI.Core.IconEngine; using UniGetUI.Core.Logging; -using UniGetUI.Core.Tools; using UniGetUI.PackageEngine.Classes.Manager.BaseProviders; using UniGetUI.PackageEngine.Interfaces; using UniGetUI.PackageEngine.Managers.WinGet.ClientHelpers; diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs index e1e8b0b47d..fbe0c818da 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs @@ -1,4 +1,3 @@ -using System.Runtime.InteropServices; using Microsoft.Management.Deployment; using UniGetUI.Core.Logging; using UniGetUI.Core.SettingsEngine; @@ -7,7 +6,6 @@ using UniGetUI.PackageEngine.Classes.Packages.Classes; using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Interfaces; -using UniGetUI.PackageEngine.Serializable; using Architecture = UniGetUI.PackageEngine.Enums.Architecture; using InstallOptions = UniGetUI.PackageEngine.Serializable.InstallOptions; diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs index 353f50b248..b9964b730c 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs @@ -1,6 +1,4 @@ -using System.ComponentModel; using System.Diagnostics; -using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Security.Principal; using System.Text; diff --git a/src/UniGetUI.PackageEngine.Operations/AbstractOperation.cs b/src/UniGetUI.PackageEngine.Operations/AbstractOperation.cs index f19bc881ce..45366c932e 100644 --- a/src/UniGetUI.PackageEngine.Operations/AbstractOperation.cs +++ b/src/UniGetUI.PackageEngine.Operations/AbstractOperation.cs @@ -5,64 +5,9 @@ namespace UniGetUI.PackageOperations; -public abstract class AbstractOperation : IDisposable +public abstract partial class AbstractOperation : IDisposable { - public static class RetryMode - { - public const string NoRetry = ""; - public const string Retry = "Retry"; - public const string Retry_AsAdmin = "RetryAsAdmin"; - public const string Retry_Interactive = "RetryInteractive"; - public const string Retry_SkipIntegrity = "RetryNoHashCheck"; - } - - public class OperationMetadata - { - /// - /// Installation of X - /// - public string Title = ""; - - /// - /// X is being installed/upated/removed - /// - public string Status = ""; - - /// - /// X was installed - /// - public string SuccessTitle = ""; - - /// - /// X has been installed successfully - /// - public string SuccessMessage = ""; - - /// - /// X could not be installed. - /// - public string FailureTitle = ""; - - /// - /// X Could not be installed - /// - public string FailureMessage = ""; - - /// - /// Starting operation X with options Y - /// - public string OperationInformation = ""; - - public readonly string Identifier; - - public OperationMetadata() - { - Identifier = new Random().NextInt64(1000000, 9999999).ToString(); - } - } - public readonly OperationMetadata Metadata = new(); - public static readonly List OperationQueue = []; public event EventHandler? StatusChanged; public event EventHandler? CancelRequested; @@ -72,38 +17,12 @@ public OperationMetadata() public event EventHandler? Enqueued; public event EventHandler? OperationSucceeded; public event EventHandler? OperationFailed; - - public static int MAX_OPERATIONS; - public event EventHandler? BadgesChanged; - public class BadgeCollection - { - public readonly bool AsAdministrator; - public readonly bool Interactive; - public readonly bool SkipHashCheck; - public readonly string? Scope; - - public BadgeCollection(bool admin, bool interactive, bool skiphash, string? scope) - { - AsAdministrator = admin; - Interactive = interactive; - SkipHashCheck = skiphash; - Scope = scope; - } - } - public void ApplyCapabilities(bool admin, bool interactive, bool skiphash, string? scope) - { - BadgesChanged?.Invoke(this, new BadgeCollection(admin, interactive, skiphash, scope)); - } - - public enum LineType - { - VerboseDetails, - ProgressIndicator, - Information, - Error - } + public bool Started { get; private set; } + protected bool QUEUE_ENABLED; + protected bool FORCE_HOLD_QUEUE; + private bool IsInnerOperation; private readonly List<(string, LineType)> LogList = []; private OperationStatus _status = OperationStatus.InQueue; @@ -113,20 +32,22 @@ public OperationStatus Status set { _status = value; StatusChanged?.Invoke(this, value); } } - public bool Started { get; private set; } - protected bool QUEUE_ENABLED; - protected bool FORCE_HOLD_QUEUE; + public void ApplyCapabilities(bool admin, bool interactive, bool skiphash, string? scope) + { + BadgesChanged?.Invoke(this, new BadgeCollection(admin, interactive, skiphash, scope)); + } - private readonly AbstractOperation? requirement; + private readonly IReadOnlyList PreOperations = []; + private readonly IReadOnlyList PostOperations = []; - public AbstractOperation(bool queue_enabled, AbstractOperation? req) + public AbstractOperation( + bool queue_enabled, + IReadOnlyList? preOps = null, + IReadOnlyList? postOps = null) { QUEUE_ENABLED = queue_enabled; - if (req is not null) - { - requirement = req; - QUEUE_ENABLED = false; - } + if (preOps is not null) PreOperations = preOps; + if (postOps is not null) PostOperations = postOps; Status = OperationStatus.InQueue; Line("Please wait...", LineType.ProgressIndicator); @@ -202,20 +123,9 @@ public async Task MainThread() Enqueued?.Invoke(this, EventArgs.Empty); - if (requirement != null) - { // OPERATION REQUIREMENT HANDLER - Logger.Info($"Operation {Metadata.Title} is waiting for requirement operation {requirement.Metadata.Title}"); - Line(CoreTools.Translate("Waiting for {0} to complete...", requirement.Metadata.Title), LineType.ProgressIndicator); - { - while (requirement.Status is OperationStatus.Running or OperationStatus.InQueue) - { - await Task.Delay(100); - if (SKIP_QUEUE) break; - } - } - } - else if (QUEUE_ENABLED) - { // QUEUE HANDLER + if (QUEUE_ENABLED && !IsInnerOperation) + { + // QUEUE HANDLER SKIP_QUEUE = false; OperationQueue.Add(this); int lastPos = -2; @@ -238,61 +148,8 @@ public async Task MainThread() } // END QUEUE HANDLER - // BEGIN ACTUAL OPERATION - OperationVeredict result; - Line(CoreTools.Translate("Starting operation..."), LineType.ProgressIndicator); - if (Status is OperationStatus.InQueue) Status = OperationStatus.Running; - - do - { - OperationStarting?.Invoke(this, EventArgs.Empty); - - try - { - // Check if the operation was canceled - if (Status is OperationStatus.Canceled) - { - result = OperationVeredict.Canceled; - break; - } - - if (requirement is not null) - { - if (requirement.Status is OperationStatus.Failed) - { - Line(CoreTools.Translate("{0} has failed, that was a requirement for {1} to be run", requirement.Metadata.Title, Metadata.Title), LineType.Error); - result = OperationVeredict.Failure; - break; - } - - if (requirement.Status is OperationStatus.Canceled) - { - Line(CoreTools.Translate("The user has canceled {0}, that was a requirement for {1} to be run", requirement.Metadata.Title, Metadata.Title), LineType.Error); - result = OperationVeredict.Canceled; - break; - } - } - - Task op = PerformOperation(); - while (Status != OperationStatus.Canceled && !op.IsCompleted) await Task.Delay(100); - - if (Status is OperationStatus.Canceled) result = OperationVeredict.Canceled; - else result = op.GetAwaiter().GetResult(); - } - catch (Exception e) - { - result = OperationVeredict.Failure; - Logger.Error(e); - foreach (string l in e.ToString().Split("\n")) - { - Line(l, LineType.Error); - } - } - } while (result == OperationVeredict.AutoRetry); - - + var result = await _runOperation(); while (OperationQueue.Remove(this)); - // END OPERATION if (result == OperationVeredict.Success) { @@ -352,6 +209,91 @@ public async Task MainThread() } } + private async Task _runOperation() + { + OperationVeredict result; + + // Process preoperations + int i = 0, count = PreOperations.Count; + if(count > 0) Line("", LineType.VerboseDetails); + foreach (var preReq in PreOperations) + { + i++; + Line(CoreTools.Translate($"Running PreOperation ({i}/{count})..."), LineType.Information); + preReq.Operation.LogLineAdded += (_, line) => Line(line.Item1, line.Item2); + await preReq.Operation.MainThread(); + if (preReq.Operation.Status is not OperationStatus.Succeeded && preReq.MustSucceed) + { + Line( + CoreTools.Translate($"PreOperation {i} out of {count} failed, and was tagged as necessary. Aborting..."), + LineType.Error); + return OperationVeredict.Failure; + } + Line(CoreTools.Translate($"PreOperation {i} out of {count} finished with result {preReq.Operation.Status}"), LineType.Information); + Line("--------------------------------", LineType.Information); + Line("", LineType.VerboseDetails); + } + + // BEGIN ACTUAL OPERATION + Line(CoreTools.Translate("Starting operation..."), LineType.Information); + if (Status is OperationStatus.InQueue) Status = OperationStatus.Running; + + do + { + OperationStarting?.Invoke(this, EventArgs.Empty); + + try + { + // Check if the operation was canceled + if (Status is OperationStatus.Canceled) + { + result = OperationVeredict.Canceled; + break; + } + + Task op = PerformOperation(); + while (Status != OperationStatus.Canceled && !op.IsCompleted) await Task.Delay(100); + + if (Status is OperationStatus.Canceled) result = OperationVeredict.Canceled; + else result = op.GetAwaiter().GetResult(); + } + catch (Exception e) + { + result = OperationVeredict.Failure; + Logger.Error(e); + foreach (string l in e.ToString().Split("\n")) + { + Line(l, LineType.Error); + } + } + } while (result is OperationVeredict.AutoRetry); + + if (result is not OperationVeredict.Success) + return result; + + // Process postoperations + i = 0; count = PostOperations.Count; + foreach (var postReq in PostOperations) + { + i++; + Line("--------------------------------", LineType.Information); + Line("", LineType.VerboseDetails); + Line(CoreTools.Translate($"Running PostOperation ({i}/{count})..."), LineType.Information); + postReq.Operation.LogLineAdded += (_, line) => Line(line.Item1, line.Item2); + await postReq.Operation.MainThread(); + if (postReq.Operation.Status is not OperationStatus.Succeeded && postReq.MustSucceed) + { + Line( + CoreTools.Translate($"PostOperation {i} out of {count} failed, and was tagged as necessary. Aborting..."), + LineType.Error); + return OperationVeredict.Failure; + } + Line(CoreTools.Translate($"PostOperation {i} out of {count} finished with result {postReq.Operation.Status}"), LineType.Information); + } + + return result; + } + private bool SKIP_QUEUE; public void SkipQueue() diff --git a/src/UniGetUI.PackageEngine.Operations/AbstractOperation_Auxiliaries.cs b/src/UniGetUI.PackageEngine.Operations/AbstractOperation_Auxiliaries.cs new file mode 100644 index 0000000000..b41d60a12b --- /dev/null +++ b/src/UniGetUI.PackageEngine.Operations/AbstractOperation_Auxiliaries.cs @@ -0,0 +1,100 @@ +namespace UniGetUI.PackageOperations; + +public abstract partial class AbstractOperation +{ + public static readonly List OperationQueue = []; + public static int MAX_OPERATIONS; + + public static class RetryMode + { + public const string NoRetry = ""; + public const string Retry = "Retry"; + public const string Retry_AsAdmin = "RetryAsAdmin"; + public const string Retry_Interactive = "RetryInteractive"; + public const string Retry_SkipIntegrity = "RetryNoHashCheck"; + } + + public class OperationMetadata + { + /// + /// Installation of X + /// + public string Title = ""; + + /// + /// X is being installed/upated/removed + /// + public string Status = ""; + + /// + /// X was installed + /// + public string SuccessTitle = ""; + + /// + /// X has been installed successfully + /// + public string SuccessMessage = ""; + + /// + /// X could not be installed. + /// + public string FailureTitle = ""; + + /// + /// X Could not be installed + /// + public string FailureMessage = ""; + + /// + /// Starting operation X with options Y + /// + public string OperationInformation = ""; + + public readonly string Identifier; + + public OperationMetadata() + { + Identifier = new Random().NextInt64(1000000, 9999999).ToString(); + } + } + + public class BadgeCollection + { + public readonly bool AsAdministrator; + public readonly bool Interactive; + public readonly bool SkipHashCheck; + public readonly string? Scope; + + public BadgeCollection(bool admin, bool interactive, bool skiphash, string? scope) + { + AsAdministrator = admin; + Interactive = interactive; + SkipHashCheck = skiphash; + Scope = scope; + } + } + + public enum LineType + { + VerboseDetails, + ProgressIndicator, + Information, + Error + } + + + public struct InnerOperation + { + public readonly AbstractOperation Operation; + public readonly bool MustSucceed; + + public InnerOperation(AbstractOperation op, bool mustSucceed) + { + Operation = op; + MustSucceed = mustSucceed; + op.IsInnerOperation = true; + } + } + +} diff --git a/src/UniGetUI.PackageEngine.Operations/ProcessOperation.cs b/src/UniGetUI.PackageEngine.Operations/AbstractProcessOperation.cs similarity index 96% rename from src/UniGetUI.PackageEngine.Operations/ProcessOperation.cs rename to src/UniGetUI.PackageEngine.Operations/AbstractProcessOperation.cs index b682969f01..a31d05980e 100644 --- a/src/UniGetUI.PackageEngine.Operations/ProcessOperation.cs +++ b/src/UniGetUI.PackageEngine.Operations/AbstractProcessOperation.cs @@ -11,7 +11,10 @@ public abstract class AbstractProcessOperation : AbstractOperation protected Process process { get; private set; } private bool ProcessKilled; - protected AbstractProcessOperation(bool queue_enabled, AbstractOperation? req) : base(queue_enabled, req) + protected AbstractProcessOperation( + bool queue_enabled, + IReadOnlyList? preOps = null, + IReadOnlyList? postOps = null) : base(queue_enabled, preOps, postOps) { process = new(); CancelRequested += (_, _) => diff --git a/src/UniGetUI.PackageEngine.Operations/KillProcessOperation.cs b/src/UniGetUI.PackageEngine.Operations/KillProcessOperation.cs new file mode 100644 index 0000000000..6f9582f653 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Operations/KillProcessOperation.cs @@ -0,0 +1,65 @@ + +using System.Diagnostics; +using UniGetUI.Core.SettingsEngine; +using UniGetUI.Core.Tools; +using UniGetUI.PackageEngine.Enums; + +namespace UniGetUI.PackageOperations; + +public class KillProcessOperation: AbstractOperation +{ + private string ProcessName; + public KillProcessOperation(string procName) : base(false) + { + ProcessName = CoreTools.MakeValidFileName(procName); + Metadata.Status = $"Closing process(es) {procName}"; + Metadata.Title = $"Closing process(es) {procName}"; + Metadata.OperationInformation = " "; + Metadata.SuccessTitle = $"Done!"; + Metadata.SuccessMessage = $"Done!"; + Metadata.FailureTitle = $"Failed to close process"; + Metadata.FailureMessage = $"The process(es) {procName} could not be closed"; + } + + protected override void ApplyRetryAction(string retryMode) + { + } + + protected override async Task PerformOperation() + { + try + { + Line($"Attempting to close all processes with name {ProcessName}...", LineType.Information); + var procs = Process.GetProcessesByName(ProcessName.Replace(".exe", "")); + foreach (var proc in procs) + { + if(proc.HasExited) continue; + Line($"Attempting to close process {ProcessName} with pid={proc.Id}...", LineType.VerboseDetails); + proc.CloseMainWindow(); + await Task.WhenAny(proc.WaitForExitAsync(), Task.Delay(1000)); + if (!proc.HasExited) + { + if(Settings.Get(Settings.K.KillProcessesThatRefuseToDie)) + { + Line($"Timeout for process {ProcessName}, attempting to kill...", LineType.Information); + proc.Kill(); + } + else + { + Line($"{ProcessName} with pid={proc.Id} did not exit and will not be killed. You can change this from UniGetUI settings.", LineType.Error); + } + } + } + + return OperationVeredict.Success; + } + catch (Exception ex) + { + Line(ex.ToString(), LineType.Error); + return OperationVeredict.Failure; + } + } + + public override Task GetOperationIcon() + => Task.FromResult(new Uri("about:blank")); +} diff --git a/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs b/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs index 85baf8c142..a133ff1619 100644 --- a/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs +++ b/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs @@ -31,7 +31,7 @@ public PackageOperation( OperationType role, bool IgnoreParallelInstalls = false, AbstractOperation? req = null) - : base(!IgnoreParallelInstalls, req) + : base(!IgnoreParallelInstalls, _getPreInstallOps(options, role, req), _getPostInstallOps(options, role)) { Package = package; Options = options; @@ -128,11 +128,53 @@ public override Task GetOperationIcon() { return TaskRecycler.RunOrAttachAsync(Package.GetIconUrl); } + + private static IReadOnlyList _getPreInstallOps(InstallOptions opts, OperationType role, AbstractOperation? preReq = null) + { + List l = new(); + if(preReq is not null) l.Add(new(preReq, true)); + + foreach (var process in opts.KillBeforeOperation) + l.Add(new InnerOperation( + new KillProcessOperation(process), + mustSucceed: false)); + + if (role is OperationType.Install && opts.PreInstallCommand.Any()) + l.Add(new(new PrePostOperation(opts.PreInstallCommand), opts.AbortOnPreInstallFail)); + else if (role is OperationType.Update && opts.PreUpdateCommand.Any()) + l.Add(new(new PrePostOperation(opts.PreUpdateCommand), opts.AbortOnPreUpdateFail)); + else if (role is OperationType.Uninstall && opts.PreUninstallCommand.Any()) + l.Add(new(new PrePostOperation(opts.PreUninstallCommand), opts.AbortOnPreUninstallFail)); + + return l; + } + + private static IReadOnlyList _getPostInstallOps(InstallOptions opts, OperationType role) + { + List l = new(); + + if (role is OperationType.Install && opts.PostInstallCommand.Any()) + l.Add(new(new PrePostOperation(opts.PostInstallCommand), false)); + else if (role is OperationType.Update && opts.PostUpdateCommand.Any()) + l.Add(new(new PrePostOperation(opts.PostUpdateCommand), false)); + else if (role is OperationType.Uninstall && opts.PostUninstallCommand.Any()) + l.Add(new(new PrePostOperation(opts.PostUninstallCommand), false)); + + return l; + } } + /* + * + * + * + * PER-OPERATION PACKAGE OPERATIONS + * + * + * + */ public class InstallPackageOperation : PackageOperation { - public InstallPackageOperation( IPackage package, InstallOptions options, diff --git a/src/UniGetUI.PackageEngine.Operations/PrePostOperation.cs b/src/UniGetUI.PackageEngine.Operations/PrePostOperation.cs new file mode 100644 index 0000000000..d806f4dba0 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Operations/PrePostOperation.cs @@ -0,0 +1,63 @@ +using UniGetUI.PackageEngine.Enums; +using UniGetUI.PackageOperations; + +namespace UniGetUI.PackageEngine.Operations; + +public class PrePostOperation : AbstractOperation +{ + private string Payload; + public PrePostOperation(string payload) : base(true) + { + Payload = payload.Replace("\r", "\n").Replace("\n\n", "\n").Replace("\n", "&"); + Metadata.Status = $"Running custom operation {Payload}"; + Metadata.Title = $"Custom operation"; + Metadata.OperationInformation = " "; + Metadata.SuccessTitle = $"Done!"; + Metadata.SuccessMessage = $"Done!"; + Metadata.FailureTitle = $"Custom operation failed"; + Metadata.FailureMessage = $"The custom operation {Payload} failed to run"; + + } + + protected override void ApplyRetryAction(string retryMode) + { + } + + protected override async Task PerformOperation() + { + Line($"Running command {Payload}", LineType.Information); + var process = new System.Diagnostics.Process + { + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = $"/C {Payload}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + + process.Start(); + process.BeginErrorReadLine(); + process.BeginOutputReadLine(); + process.OutputDataReceived += (s, e) => + { + if (e.Data is not null) Line(e.Data, LineType.Information); + }; + process.ErrorDataReceived += (s, e) => + { + if (e.Data is not null) Line(e.Data, LineType.Error); + }; + await process.WaitForExitAsync(); + + int exitCode = process.ExitCode; + Line($"Exit code is {exitCode}", LineType.Information); + return (exitCode == 0 ? OperationVeredict.Success : OperationVeredict.Failure); + } + + public override Task GetOperationIcon() + => Task.FromResult(new Uri("about:blank")); + +} diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallOptionsFactory.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallOptionsFactory.cs index f3b4254d7b..871fd8dce2 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallOptionsFactory.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Classes/InstallOptionsFactory.cs @@ -223,6 +223,23 @@ private static InstallOptions EnsureSecureOptions(InstallOptions options) options.CustomParameters_Uninstall = []; } + if (!SecureSettings.Get(SecureSettings.K.AllowPrePostOpCommand)) + { + if (options.PreInstallCommand.Any()) Logger.Warn($"Pre-install command {options.PreInstallCommand} will be discarded"); + if (options.PostInstallCommand.Any()) Logger.Warn($"Post-install command {options.PostInstallCommand} will be discarded"); + if (options.PreUpdateCommand.Any()) Logger.Warn($"Pre-update command {options.PreUpdateCommand} will be discarded"); + if (options.PostUpdateCommand.Any()) Logger.Warn($"Post-update command {options.PostUpdateCommand} will be discarded"); + if (options.PreUninstallCommand.Any()) Logger.Warn($"Pre-uninstall command {options.PreUninstallCommand} will be discarded"); + if (options.PostUninstallCommand.Any()) Logger.Warn($"Post-uninstall command {options.PostUninstallCommand} will be discarded"); + + options.PreInstallCommand = ""; + options.PostInstallCommand = ""; + options.PreUpdateCommand = ""; + options.PostUpdateCommand = ""; + options.PreUninstallCommand = ""; + options.PostUninstallCommand = ""; + } + return options; } } diff --git a/src/UniGetUI.PackageEngine.Serializable/InstallOptions.cs b/src/UniGetUI.PackageEngine.Serializable/InstallOptions.cs index c5311fed78..d779487782 100644 --- a/src/UniGetUI.PackageEngine.Serializable/InstallOptions.cs +++ b/src/UniGetUI.PackageEngine.Serializable/InstallOptions.cs @@ -19,6 +19,18 @@ public class InstallOptions: SerializableComponent public bool SkipMinorUpdates { get; set; } public bool OverridesNextLevelOpts { get; set; } public bool RemoveDataOnUninstall { get; set; } + public List KillBeforeOperation { get; set; } = []; + + public string PreInstallCommand { get; set; } = ""; + public string PostInstallCommand { get; set; } = ""; + public bool AbortOnPreInstallFail { get; set; } = true; + public string PreUpdateCommand { get; set; } = ""; + public string PostUpdateCommand { get; set; } = ""; + public bool AbortOnPreUpdateFail { get; set; } = true; + public string PreUninstallCommand { get; set; } = ""; + public string PostUninstallCommand { get; set; } = ""; + public bool AbortOnPreUninstallFail { get; set; } = true; + public override InstallOptions Copy() { @@ -27,9 +39,9 @@ public override InstallOptions Copy() SkipHashCheck = SkipHashCheck, Architecture = Architecture, CustomInstallLocation = CustomInstallLocation, - CustomParameters_Install = CustomParameters_Install, - CustomParameters_Update = CustomParameters_Update, - CustomParameters_Uninstall = CustomParameters_Uninstall, + CustomParameters_Install = CustomParameters_Install.ToList(), + CustomParameters_Update = CustomParameters_Update.ToList(), + CustomParameters_Uninstall = CustomParameters_Uninstall.ToList(), InstallationScope = InstallationScope, InteractiveInstallation = InteractiveInstallation, PreRelease = PreRelease, @@ -38,6 +50,16 @@ public override InstallOptions Copy() SkipMinorUpdates = SkipMinorUpdates, OverridesNextLevelOpts = OverridesNextLevelOpts, RemoveDataOnUninstall = RemoveDataOnUninstall, + KillBeforeOperation = KillBeforeOperation.ToList(), + PreInstallCommand = PreInstallCommand, + PreUpdateCommand = PreUpdateCommand, + PreUninstallCommand = PreUninstallCommand, + PostInstallCommand = PostInstallCommand, + PostUpdateCommand = PostUpdateCommand, + PostUninstallCommand = PostUninstallCommand, + AbortOnPreInstallFail = AbortOnPreInstallFail, + AbortOnPreUpdateFail = AbortOnPreUpdateFail, + AbortOnPreUninstallFail = AbortOnPreUninstallFail }; } @@ -65,11 +87,22 @@ this.CustomParameters_Uninstall.Count is 0 && this.CustomParameters_Uninstall = ReadArrayFromJson(data, "CustomParameters"); } + this.KillBeforeOperation = ReadArrayFromJson(data, nameof(KillBeforeOperation)); 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; + this.PreInstallCommand = data[nameof(PreInstallCommand)]?.GetVal() ?? ""; + this.PreUpdateCommand = data[nameof(PreUpdateCommand)]?.GetVal() ?? ""; + this.PreUninstallCommand = data[nameof(PreUninstallCommand)]?.GetVal() ?? ""; + this.PostInstallCommand = data[nameof(PostInstallCommand)]?.GetVal() ?? ""; + this.PostUpdateCommand = data[nameof(PostUpdateCommand)]?.GetVal() ?? ""; + this.PostUninstallCommand = data[nameof(PostUninstallCommand)]?.GetVal() ?? ""; + this.AbortOnPreInstallFail = data[nameof(AbortOnPreInstallFail)]?.GetVal() ?? true; + this.AbortOnPreUpdateFail = data[nameof(AbortOnPreUpdateFail)]?.GetVal() ?? true; + this.AbortOnPreUninstallFail = data[nameof(AbortOnPreUninstallFail)]?.GetVal() ?? true; + // 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 @@ -95,12 +128,22 @@ PreRelease is not false || SkipMinorUpdates is not false || Architecture.Any() || InstallationScope.Any() || - CustomParameters_Install.Where(x => x != "").Any() || - CustomParameters_Update.Where(x => x != "").Any() || - CustomParameters_Uninstall.Where(x => x != "").Any() || + CustomParameters_Install.Where(x => x.Any()).Any() || + CustomParameters_Update.Where(x => x.Any()).Any() || + CustomParameters_Uninstall.Where(x => x.Any()).Any() || + KillBeforeOperation.Where(x => x.Any()).Any() || CustomInstallLocation.Any() || RemoveDataOnUninstall is not false || - Version.Any(); + Version.Any() || + PreInstallCommand.Any() || + PostInstallCommand.Any() || + AbortOnPreInstallFail is not true || + PreUpdateCommand.Any() || + PostUpdateCommand.Any() || + AbortOnPreUpdateFail is not true || + PreUninstallCommand.Any() || + PostUninstallCommand.Any() || + AbortOnPreUninstallFail is not true; // OverridesNextLevelOpts does not need to be checked here, since // this method is invoked before this property has been set } @@ -115,8 +158,8 @@ public InstallOptions(JsonNode data) : base(data) public override string ToString() { - string customparams = CustomParameters_Install.Any() ? string.Join(",", CustomParameters_Install) : "[]"; - customparams += CustomParameters_Update.Any() ? string.Join(",", CustomParameters_Update) : "[]"; + string customparams = CustomParameters_Install.Any() ? string.Join(",", CustomParameters_Install) : "[],"; + customparams += CustomParameters_Update.Any() ? string.Join(",", CustomParameters_Update) : "[],"; customparams += CustomParameters_Uninstall.Any() ? string.Join(",", CustomParameters_Uninstall) : "[]"; return $""; } } diff --git a/src/UniGetUI/App.xaml.cs b/src/UniGetUI/App.xaml.cs index 347912e14f..2d2056ba5e 100644 --- a/src/UniGetUI/App.xaml.cs +++ b/src/UniGetUI/App.xaml.cs @@ -95,10 +95,6 @@ public MainApp() private static async void LoadGSudo() { -#if DEBUG - Logger.Warn($"Using bundled GSudo at {CoreData.ElevatorPath} since UniGetUI Elevator is not available!"); - CoreData.ElevatorPath = (await CoreTools.WhichAsync("gsudo.exe")).Item2; -#else if (SecureSettings.Get(SecureSettings.K.ForceUserGSudo)) { var res = await CoreTools.WhichAsync("gsudo.exe"); @@ -110,6 +106,10 @@ private static async void LoadGSudo() } } +#if DEBUG + Logger.Warn($"Using bundled GSudo at {CoreData.ElevatorPath} since UniGetUI Elevator is not available!"); + CoreData.ElevatorPath = (await CoreTools.WhichAsync("gsudo.exe")).Item2; +#else CoreData.ElevatorPath = Path.Join(CoreData.UniGetUIExecutableDirectory, "Assets", "Utilities", "UniGetUI Elevator.exe"); Logger.Debug($"Using built-in UniGetUI Elevator at {CoreData.ElevatorPath}"); #endif diff --git a/src/UniGetUI/AppOperationHelper.cs b/src/UniGetUI/AppOperationHelper.cs index 4ef6f908e3..1f8aefb6f4 100644 --- a/src/UniGetUI/AppOperationHelper.cs +++ b/src/UniGetUI/AppOperationHelper.cs @@ -116,7 +116,11 @@ public static void Remove(AbstractOperation op) } /* + * + * * PACKAGE INSTALLATION + * + * */ public static async Task Install(IPackage? package, TEL_InstallReferral referral, bool? elevated = null, bool? interactive = null, bool? no_integrity = null, bool ignoreParallel = false, @@ -141,8 +145,31 @@ public static void Install(IReadOnlyList packages, TEL_InstallReferral } } + public static async Task UninstallThenReinstall(IPackage? package, TEL_InstallReferral referral) + { + if (package is null) return null; + + var options = await InstallOptionsFactory.LoadApplicableAsync(package); + + var uninstallOp = new UninstallPackageOperation(package, options); + uninstallOp.OperationSucceeded += (_, _) => TelemetryHandler.UninstallPackage(package, TEL_OP_RESULT.SUCCESS); + uninstallOp.OperationFailed += (_, _) => TelemetryHandler.UninstallPackage(package, TEL_OP_RESULT.FAILED); + + var installOp = new InstallPackageOperation(package, options, req: uninstallOp); + installOp.OperationSucceeded += (_, _) => TelemetryHandler.InstallPackage(package, TEL_OP_RESULT.SUCCESS, referral); + installOp.OperationFailed += (_, _) => TelemetryHandler.InstallPackage(package, TEL_OP_RESULT.FAILED, referral); + + Add(installOp); + Instance.MainWindow.UpdateSystemTrayStatus(); + return installOp; + } + /* + * + * * PACKAGE UPDATE + * + * */ public static async Task Update(IPackage? package, bool? elevated = null, bool? interactive = null, bool? no_integrity = null, bool ignoreParallel = false, AbstractOperation? req = null) { @@ -197,8 +224,31 @@ public static async void UpdateForId(string packageId) Logger.Warn($"[WIDGETS] No package with id={packageId} was found"); } + public static async Task UninstallThenUpdate(IPackage? package) + { + if (package is null) return null; + + var options = await InstallOptionsFactory.LoadApplicableAsync(package); + + var uninstallOp = new UninstallPackageOperation(package, options); + uninstallOp.OperationSucceeded += (_, _) => TelemetryHandler.UninstallPackage(package, TEL_OP_RESULT.SUCCESS); + uninstallOp.OperationFailed += (_, _) => TelemetryHandler.UninstallPackage(package, TEL_OP_RESULT.FAILED); + + var installOp = new UpdatePackageOperation(package, options, req: uninstallOp); + installOp.OperationSucceeded += (_, _) => TelemetryHandler.UpdatePackage(package, TEL_OP_RESULT.SUCCESS); + installOp.OperationFailed += (_, _) => TelemetryHandler.UpdatePackage(package, TEL_OP_RESULT.FAILED); + + Add(installOp); + Instance.MainWindow.UpdateSystemTrayStatus(); + return installOp; + } + /* + * + * * PACKAGE UNINSTALL + * + * */ public static async void ConfirmAndUninstall(IReadOnlyList packages, bool? elevated = null, bool? interactive = null, bool? remove_data = null) diff --git a/src/UniGetUI/Controls/MenuForPackage.cs b/src/UniGetUI/Controls/MenuForPackage.cs index c45854fa86..3bda72b84d 100644 --- a/src/UniGetUI/Controls/MenuForPackage.cs +++ b/src/UniGetUI/Controls/MenuForPackage.cs @@ -76,6 +76,7 @@ public partial class BetterTabViewItem : TabViewItem public BetterTabViewItem() { IsClosable = false; + CanDrag = false; } public void LoadText() diff --git a/src/UniGetUI/Controls/SettingsWidgets/CheckboxCard.cs b/src/UniGetUI/Controls/SettingsWidgets/CheckboxCard.cs index 81cd186a8f..f2356409d7 100644 --- a/src/UniGetUI/Controls/SettingsWidgets/CheckboxCard.cs +++ b/src/UniGetUI/Controls/SettingsWidgets/CheckboxCard.cs @@ -56,6 +56,11 @@ public Brush WarningForeground set => _warningBlock.Foreground = value; } + public double WarningOpacity + { + set => _warningBlock.Opacity = value; + } + public CheckboxCard() { _checkbox = new ToggleSwitch() @@ -74,6 +79,7 @@ public CheckboxCard() Margin = new Thickness(0, 0, 0, 0), TextWrapping = TextWrapping.Wrap, FontSize = 12, + Opacity = 0.7, Visibility = Visibility.Collapsed, }; IS_INVERTED = false; diff --git a/src/UniGetUI/CrashHandler.cs b/src/UniGetUI/CrashHandler.cs index 161ae8bfaf..9f25e0efe2 100644 --- a/src/UniGetUI/CrashHandler.cs +++ b/src/UniGetUI/CrashHandler.cs @@ -1,7 +1,5 @@ using System.Diagnostics; -using Microsoft.UI; using UniGetUI.Core.Data; -using UniGetUI.Core.Language; using UniGetUI.Core.Tools; namespace UniGetUI; diff --git a/src/UniGetUI/EntryPoint.cs b/src/UniGetUI/EntryPoint.cs index 20514ed893..37f88c4db8 100644 --- a/src/UniGetUI/EntryPoint.cs +++ b/src/UniGetUI/EntryPoint.cs @@ -3,7 +3,6 @@ using Microsoft.Windows.AppLifecycle; using UniGetUI.Core.Data; using UniGetUI.Core.Logging; -using UniGetUI.Core.Tools; namespace UniGetUI { diff --git a/src/UniGetUI/Pages/DialogPages/InstallOptions_Manager.xaml.cs b/src/UniGetUI/Pages/DialogPages/InstallOptions_Manager.xaml.cs index deeef6fd16..29c068ce43 100644 --- a/src/UniGetUI/Pages/DialogPages/InstallOptions_Manager.xaml.cs +++ b/src/UniGetUI/Pages/DialogPages/InstallOptions_Manager.xaml.cs @@ -1,7 +1,3 @@ -using System.Diagnostics; -using System.Runtime.InteropServices; -using CommunityToolkit.WinUI.Controls; -using Microsoft.Extensions.Options; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using UniGetUI.Core.Language; diff --git a/src/UniGetUI/Pages/DialogPages/InstallOptions_Package.xaml b/src/UniGetUI/Pages/DialogPages/InstallOptions_Package.xaml index ea6922d21d..d735d0d865 100644 --- a/src/UniGetUI/Pages/DialogPages/InstallOptions_Package.xaml +++ b/src/UniGetUI/Pages/DialogPages/InstallOptions_Package.xaml @@ -3,6 +3,7 @@ x:Class="UniGetUI.Interface.Dialogs.InstallOptionsPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:animations="using:CommunityToolkit.WinUI.Animations" xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:UniGetUI.Interface.Dialogs" @@ -11,6 +12,16 @@ xmlns:widgets="using:UniGetUI.Interface.Widgets" MaxWidth="700" mc:Ignorable="d"> + + + + + + + + + + @@ -130,7 +141,9 @@ x:Name="SettingsTabBar" HorizontalAlignment="Stretch" Background="{ThemeResource SystemChromeAltHighColor}" + CanReorderTabs="False" IsAddTabButtonVisible="False" + SelectionChanged="SettingsTabBar_SelectionChanged" TabWidthMode="SizeToContent"> - + Line2="Post-install" /> @@ -165,6 +180,16 @@ + + + + @@ -176,7 +201,10 @@ - + @@ -217,7 +245,17 @@ - + + + + + @@ -234,7 +272,10 @@ SelectedIndex="0" SelectionChanged="ArchitectureComboBox_SelectionChanged" /> - + @@ -250,7 +291,7 @@ VerticalAlignment="Center" SelectionChanged="ScopeCombo_SelectionChanged" /> - + @@ -291,9 +332,55 @@ + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -405,6 +659,7 @@ ProcessesToKill = new(); + private readonly ObservableCollection _runningProcesses = new(); + public ObservableCollection SuggestedProcesses = new(); + public InstallOptionsPage(IPackage package, InstallOptions options) : this(package, OperationType.None, options) { } public InstallOptionsPage(IPackage package, OperationType operation, InstallOptions options) { @@ -40,6 +44,8 @@ public InstallOptionsPage(IPackage package, OperationType operation, InstallOpti Operation = operation; Options = options; + KillProcessesThatWontDie.IsChecked = Settings.Get(Settings.K.KillProcessesThatRefuseToDie); + ProfileComboBox.Items.Add(CoreTools.Translate("Install")); ProfileComboBox.Items.Add(CoreTools.Translate("Update")); ProfileComboBox.Items.Add(CoreTools.Translate("Uninstall")); @@ -83,6 +89,9 @@ async Task LoadImage() DialogTitle.Text = CoreTools.Translate("{0} installation options", package.Name); PlaceholderText.Text = CoreTools.Translate("{0} Install options are currently locked because {0} follows the default install options.", package.Name); + KillProcessesLabel.Text = CoreTools.Translate("Select the processes that should be closed before this package is installed, updated or uninstalled."); + KillProcessesBox.PlaceholderText = CoreTools.Translate("Write here the process names here, separed by commas (,)"); + packageInstallLocation = Package.Manager.DetailsHelper.GetInstallLocation(package) ?? CoreTools.Translate("Unset or unknown"); AdminCheckBox.IsChecked = Options.RunAsAdministrator; @@ -154,20 +163,45 @@ async Task LoadImage() } } + foreach(var p in Options.KillBeforeOperation) + { + ProcessesToKill.Add(new(p)); + } if (Options.CustomInstallLocation == "") CustomInstallLocation.Text = packageInstallLocation; else CustomInstallLocation.Text = Options.CustomInstallLocation; - CustomParameters1.Text = string.Join(' ', Options.CustomParameters_Install); CustomParameters2.Text = string.Join(' ', Options.CustomParameters_Update); CustomParameters3.Text = string.Join(' ', Options.CustomParameters_Uninstall); + PreInstallCommandBox.Text = Options.PreInstallCommand; + PostInstallCommandBox.Text = Options.PostInstallCommand; + PreUpdateCommandBox.Text = Options.PreUpdateCommand; + PostUpdateCommandBox.Text = Options.PostUpdateCommand; + PreUninstallCommandBox.Text = Options.PreUninstallCommand; + PostUninstallCommandBox.Text = Options.PostUninstallCommand; + AbortInsFailedCheck.IsChecked = Options.AbortOnPreInstallFail; + AbortUpdFailedCheck.IsChecked = Options.AbortOnPreUpdateFail; + AbortUniFailedCheck.IsChecked = Options.AbortOnPreUninstallFail; + _uiLoaded = true; EnableDisableControls(operation); LoadIgnoredUpdates(); + _ = _loadProcesses(); } + private async Task _loadProcesses() + { + var processNames = await Task.Run(() => + Process.GetProcesses().Select(p => p.ProcessName).Distinct().ToList()); + + _runningProcesses.Clear(); + foreach (var name in processNames) + { + if(name.Any()) _runningProcesses.Add(new(name + ".exe")); + } + } private void EnableDisableControls(OperationType operation) { if(FollowGlobalOptionsSwitch.IsOn) @@ -188,13 +222,17 @@ private void EnableDisableControls(OperationType operation) 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; + HashCheckbox.IsEnabled = + operation is not OperationType.Uninstall + && Package.Manager.Capabilities.CanSkipIntegrityChecks; + + ArchitectureComboBox.IsEnabled = + operation is not OperationType.Uninstall + && Package.Manager.Capabilities.SupportsCustomArchitectures; + VersionComboBox.IsEnabled = - (operation == OperationType.Install - || operation == OperationType.None) - && (Package.Manager.Capabilities.SupportsCustomVersions - || Package.Manager.Capabilities.SupportsPreRelease); + operation is OperationType.Install or 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; @@ -209,6 +247,27 @@ private void EnableDisableControls(OperationType operation) CustomParametersLabel3.Opacity = IsCLIEnabled ? 1 : 0.5; GoToCLISettings.Visibility = IsCLIEnabled ? Visibility.Collapsed : Visibility.Visible; CLIDisabled.Visibility = IsCLIEnabled ? Visibility.Collapsed : Visibility.Visible; + + bool IsPrePostOpEnabled = SecureSettings.Get(SecureSettings.K.AllowPrePostOpCommand); + PreInstallCommandBox.IsEnabled = IsPrePostOpEnabled; + PostInstallCommandBox.IsEnabled = IsPrePostOpEnabled; + AbortInsFailedCheck.IsEnabled = IsPrePostOpEnabled; + PreUpdateCommandBox.IsEnabled = IsPrePostOpEnabled; + PostUpdateCommandBox.IsEnabled = IsPrePostOpEnabled; + AbortUpdFailedCheck.IsEnabled = IsPrePostOpEnabled; + PreUninstallCommandBox.IsEnabled = IsPrePostOpEnabled; + PostUninstallCommandBox.IsEnabled = IsPrePostOpEnabled; + AbortUniFailedCheck.IsEnabled = IsPrePostOpEnabled; + PeInsLabel.Opacity = IsPrePostOpEnabled ? 1 : 0.5; + PoInsLabel.Opacity = IsPrePostOpEnabled ? 1 : 0.5; + PeUpdLabel.Opacity = IsPrePostOpEnabled ? 1 : 0.5; + PoUpdLabel.Opacity = IsPrePostOpEnabled ? 1 : 0.5; + PeUniLabel.Opacity = IsPrePostOpEnabled ? 1 : 0.5; + PoUniLabel.Opacity = IsPrePostOpEnabled ? 1 : 0.5; + CustomCommandsHeaderExplainer.Opacity = IsPrePostOpEnabled ? 1 : 0.5; + GoToPrePostSettings.Visibility = IsPrePostOpEnabled ? Visibility.Collapsed : Visibility.Visible; + PrePostDisabled.Visibility = IsPrePostOpEnabled ? Visibility.Collapsed : Visibility.Visible; + GenerateCommand(); } @@ -233,14 +292,12 @@ private async Task LoadVersions() } VersionComboBox.IsEnabled = - (Operation == OperationType.Install - || Operation == OperationType.None) - && (Package.Manager.Capabilities.SupportsCustomVersions - || Package.Manager.Capabilities.SupportsPreRelease); + Operation is OperationType.Install or OperationType.None + && (Package.Manager.Capabilities.SupportsCustomVersions || Package.Manager.Capabilities.SupportsPreRelease); VersionProgress.Visibility = Visibility.Collapsed; } - public async Task GetUpdatedOptions(bool updateIgnoredUpdates = true) + public async Task GetUpdatedOptions(bool updateDetachedOptions = true) { Options.RunAsAdministrator = AdminCheckBox?.IsChecked ?? false; Options.InteractiveInstallation = InteractiveCheckBox?.IsChecked ?? false; @@ -269,6 +326,19 @@ public async Task GetUpdatedOptions(bool updateIgnoredUpdates = Options.CustomParameters_Uninstall = CustomParameters3.Text.Split(' ').ToList(); Options.PreRelease = VersionComboBox.SelectedValue.ToString() == CoreTools.Translate("PreRelease"); + Options.PreInstallCommand = PreInstallCommandBox.Text; + Options.PostInstallCommand = PostInstallCommandBox.Text; + Options.PreUpdateCommand = PreUpdateCommandBox.Text; + Options.PostUpdateCommand = PostUpdateCommandBox.Text; + Options.PreUninstallCommand = PreUninstallCommandBox.Text; + Options.PostUninstallCommand = PostUninstallCommandBox.Text; + Options.AbortOnPreInstallFail = AbortInsFailedCheck.IsChecked ?? true; + Options.AbortOnPreUpdateFail = AbortUpdFailedCheck.IsChecked ?? true; + Options.AbortOnPreUninstallFail = AbortUniFailedCheck.IsChecked ?? true; + + Options.KillBeforeOperation.Clear(); + foreach(var p in ProcessesToKill) Options.KillBeforeOperation.Add(p.Name); + if (VersionComboBox.SelectedValue.ToString() != CoreTools.Translate("PreRelease") && VersionComboBox.SelectedValue.ToString() != CoreTools.Translate("Latest")) { Options.Version = VersionComboBox.SelectedValue.ToString() ?? ""; @@ -279,8 +349,10 @@ public async Task GetUpdatedOptions(bool updateIgnoredUpdates = } Options.SkipMinorUpdates = SkipMinorUpdatesCheckbox?.IsChecked ?? false; - if (updateIgnoredUpdates) + if (updateDetachedOptions) { + Settings.Set(Settings.K.KillProcessesThatRefuseToDie, KillProcessesThatWontDie.IsChecked ?? false); + if (IgnoreUpdatesCheckbox?.IsChecked ?? false) { await Package.AddToIgnoredUpdatesAsync(version: "*"); @@ -329,7 +401,7 @@ private void CloseButton_Click(object sender, RoutedEventArgs e) private async void GenerateCommand() { if (!_uiLoaded) return; - InstallOptions options = await GetUpdatedOptions(updateIgnoredUpdates: false); + InstallOptions options = await GetUpdatedOptions(updateDetachedOptions: false); options = await InstallOptionsFactory.LoadApplicableAsync(this.Package, overridePackageOptions: options); var op = ProfileComboBox.SelectedIndex switch @@ -363,5 +435,50 @@ private void GoToSecureSettings_Click(object sender, RoutedEventArgs e) Close?.Invoke(this, EventArgs.Empty); MainApp.Instance.MainWindow.NavigationPage.OpenSettingsPage(typeof(Administrator)); } + + private void KillProcessesBox_TokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args) + { + args.Item = _runningProcesses.FirstOrDefault((item) => item.Name.Contains(args.TokenText)); + if(args.Item is null) + { + string text = args.TokenText; + if (!text.EndsWith(".exe")) text += ".exe"; + args.Item = new IOP_Proc(text); + } + } + + private async void KillProcessesBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + var text = KillProcessesBox.Text; + await Task.Delay(100); + if (text != KillProcessesBox.Text) + return; + + SuggestedProcesses.Clear(); + if (text.Trim() != "") + { + if (!text.EndsWith(".exe")) + text = text.Trim() + ".exe"; + SuggestedProcesses.Add(new(text)); + foreach (var item in _runningProcesses.Where(x => x.Name.Contains(KillProcessesBox.Text))) + { + SuggestedProcesses.Add(item); + } + } + } + + private void SettingsTabBar_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + CommandLineViewBox.Visibility = SettingsTabBar.SelectedIndex < 3 ? Visibility.Visible : Visibility.Collapsed; + } + } + + public class IOP_Proc + { + public readonly string Name; + public IOP_Proc(string name) + { + Name = name; + } } } diff --git a/src/UniGetUI/Pages/SettingsPages/GeneralPages/Administrator.xaml b/src/UniGetUI/Pages/SettingsPages/GeneralPages/Administrator.xaml index f2af768643..6128421e16 100644 --- a/src/UniGetUI/Pages/SettingsPages/GeneralPages/Administrator.xaml +++ b/src/UniGetUI/Pages/SettingsPages/GeneralPages/Administrator.xaml @@ -65,15 +65,13 @@ Text="Allow custom command-line arguments" WarningText="Custom command-line arguments can change the way in which programs are installed, upgraded or uninstalled, in a way UniGetUI cannot control. Using custom command-lines can break packages. Proceed with caution." /> - + WarningText="Pre and post install commands will be run before and after a package gets installed, upgraded or uninstalled. Be aware that they may break things unless used carefully" /> - + WarningText="Pre and post install commands can do very nasty things to your device, if designed to do so. It can be very dangerous to import the commands from a bundle, unless you trust the source of that package bundle" /> diff --git a/src/UniGetUI/Pages/SettingsPages/GeneralPages/Operations.xaml b/src/UniGetUI/Pages/SettingsPages/GeneralPages/Operations.xaml index 8d0ff4d959..493123dd08 100644 --- a/src/UniGetUI/Pages/SettingsPages/GeneralPages/Operations.xaml +++ b/src/UniGetUI/Pages/SettingsPages/GeneralPages/Operations.xaml @@ -40,6 +40,15 @@ SettingName="MaintainSuccessfulInstalls" Text="Clear successful operations from the operation list after a 5 second delay" /> + + + _ = MainApp.Operations.Install(SelectedItem, TEL_InstallReferral.ALREADY_INSTALLED); - private async void MenuUninstallThenReinstall_Invoked(object sender, RoutedEventArgs args) - { - var op = await MainApp.Operations.Uninstall(SelectedItem, ignoreParallel: true); - _ = MainApp.Operations.Install(SelectedItem, TEL_InstallReferral.ALREADY_INSTALLED, ignoreParallel: true, req: op); - } + private void MenuUninstallThenReinstall_Invoked(object sender, RoutedEventArgs args) + => _ = MainApp.Operations.UninstallThenReinstall(SelectedItem, TEL_InstallReferral.ALREADY_INSTALLED); private async void MenuIgnorePackage_Invoked(object sender, RoutedEventArgs args) { diff --git a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs index 712128aab4..d8d7411db9 100644 --- a/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs +++ b/src/UniGetUI/Pages/SoftwarePages/PackageBundlesPage.cs @@ -1,8 +1,6 @@ using System.Diagnostics; -using System.Runtime.Serialization.Formatters; using System.Text.Json; using System.Text.Json.Nodes; -using System.Xml; using System.Xml.Serialization; using ExternalLibraries.Pickers; using Microsoft.UI.Text; @@ -665,46 +663,81 @@ public async Task AddFromBundle(string content, BundleFormatType format) SecureSettings.Get(SecureSettings.K.AllowCLIArguments) && SecureSettings.Get(SecureSettings.K.AllowImportingCLIArguments); + bool AllowPrePostOps = + SecureSettings.Get(SecureSettings.K.AllowPrePostOpCommand) && + SecureSettings.Get(SecureSettings.K.AllowImportPrePostOpCommands); + foreach (var pkg in DeserializedData.packages) { - if (pkg.InstallationOptions.CustomParameters_Install.Where(x => x.Any()).Any()) + var opts = pkg.InstallationOptions; + + if (opts.CustomParameters_Install.Where(x => x.Any()).Any()) { showReport = true; - if (!packageReport.ContainsKey(pkg.Id)) - packageReport[pkg.Id] = new(); - - packageReport[pkg.Id].Add(new( - $"Custom install arguments: [{string.Join(", ", pkg.InstallationOptions.CustomParameters_Install)}]", - AllowCLIParameters)); - - if(!AllowCLIParameters) pkg.InstallationOptions.CustomParameters_Install.Clear(); + if (!packageReport.ContainsKey(pkg.Id)) packageReport[pkg.Id] = new(); + packageReport[pkg.Id].Add(new($"Custom install arguments: [{string.Join(", ", opts.CustomParameters_Install)}]", AllowCLIParameters)); + if(!AllowCLIParameters) opts.CustomParameters_Install.Clear(); } - if (pkg.InstallationOptions.CustomParameters_Update.Where(x => x.Any()).Any()) + if (opts.CustomParameters_Update.Where(x => x.Any()).Any()) { showReport = true; - if (!packageReport.ContainsKey(pkg.Id)) - packageReport[pkg.Id] = new(); - - packageReport[pkg.Id].Add(new( - $"Custom update arguments: [{string.Join(", ", pkg.InstallationOptions.CustomParameters_Update)}]", - AllowCLIParameters)); - - if(!AllowCLIParameters) pkg.InstallationOptions.CustomParameters_Update.Clear(); + if (!packageReport.ContainsKey(pkg.Id)) packageReport[pkg.Id] = new(); + packageReport[pkg.Id].Add(new($"Custom update arguments: [{string.Join(", ", opts.CustomParameters_Update)}]", AllowCLIParameters)); + if(!AllowCLIParameters) opts.CustomParameters_Update.Clear(); } - if (pkg.InstallationOptions.CustomParameters_Uninstall.Where(x => x.Any()).Any()) + if (opts.CustomParameters_Uninstall.Where(x => x.Any()).Any()) { showReport = true; - if (!packageReport.ContainsKey(pkg.Id)) - packageReport[pkg.Id] = new(); - - packageReport[pkg.Id].Add(new( - $"Custom uninstall arguments: [{string.Join(", ", pkg.InstallationOptions.CustomParameters_Uninstall)}]", - AllowCLIParameters)); + if (!packageReport.ContainsKey(pkg.Id)) packageReport[pkg.Id] = new(); + packageReport[pkg.Id].Add(new($"Custom uninstall arguments: [{string.Join(", ", opts.CustomParameters_Uninstall)}]", AllowCLIParameters)); + if(!AllowCLIParameters) opts.CustomParameters_Uninstall.Clear(); + } - if(!AllowCLIParameters) pkg.InstallationOptions.CustomParameters_Uninstall.Clear(); + if (opts.PreInstallCommand.Any()) + { + showReport = true; + if (!packageReport.ContainsKey(pkg.Id)) packageReport[pkg.Id] = new(); + packageReport[pkg.Id].Add(new($"Pre-install command: {opts.PreInstallCommand}", AllowPrePostOps)); + if (!AllowPrePostOps) opts.PreInstallCommand = ""; + } + if (opts.PostInstallCommand.Any()) + { + showReport = true; + if (!packageReport.ContainsKey(pkg.Id)) packageReport[pkg.Id] = new(); + packageReport[pkg.Id].Add(new($"Post-install command: {opts.PostInstallCommand}", AllowPrePostOps)); + if (!AllowPrePostOps) opts.PostInstallCommand = ""; + } + if (opts.PreUpdateCommand.Any()) + { + showReport = true; + if (!packageReport.ContainsKey(pkg.Id)) packageReport[pkg.Id] = new(); + packageReport[pkg.Id].Add(new($"Pre-update command: {opts.PreUpdateCommand}", AllowPrePostOps)); + if (!AllowPrePostOps) opts.PreUpdateCommand = ""; + } + if (opts.PostUpdateCommand.Any()) + { + showReport = true; + if (!packageReport.ContainsKey(pkg.Id)) packageReport[pkg.Id] = new(); + packageReport[pkg.Id].Add(new($"Post-update command: {opts.PostUpdateCommand}", AllowPrePostOps)); + if (!AllowPrePostOps) opts.PostUpdateCommand = ""; + } + if (opts.PreUninstallCommand.Any()) + { + showReport = true; + if (!packageReport.ContainsKey(pkg.Id)) packageReport[pkg.Id] = new(); + packageReport[pkg.Id].Add(new($"Pre-uninstall command: {opts.PreUninstallCommand}", AllowPrePostOps)); + if (!AllowPrePostOps) opts.PreUninstallCommand = ""; + } + if (opts.PostUninstallCommand.Any()) + { + showReport = true; + if (!packageReport.ContainsKey(pkg.Id)) packageReport[pkg.Id] = new(); + packageReport[pkg.Id].Add(new($"Post-uninstall command: {opts.PostUninstallCommand}", AllowPrePostOps)); + if (!AllowPrePostOps) opts.PostUninstallCommand = ""; } + pkg.InstallationOptions = opts; packages.Add(DeserializePackage(pkg)); } diff --git a/src/UniGetUI/Pages/SoftwarePages/SoftwareUpdatesPage.cs b/src/UniGetUI/Pages/SoftwarePages/SoftwareUpdatesPage.cs index eb3f502af3..49e6263fa4 100644 --- a/src/UniGetUI/Pages/SoftwarePages/SoftwareUpdatesPage.cs +++ b/src/UniGetUI/Pages/SoftwarePages/SoftwareUpdatesPage.cs @@ -494,11 +494,8 @@ private void MenuInteractive_Invoked(object sender, RoutedEventArgs e) private void MenuAsAdmin_Invoked(object sender, RoutedEventArgs e) => _ = MainApp.Operations.Update(SelectedItem, elevated: true); - private async void MenuUpdateAfterUninstall_Invoked(object sender, RoutedEventArgs e) - { - var op = await MainApp.Operations.Uninstall(SelectedItem); - _ = MainApp.Operations.Install(SelectedItem, TEL_InstallReferral.ALREADY_INSTALLED, req: op); - } + private void MenuUpdateAfterUninstall_Invoked(object sender, RoutedEventArgs e) + => _ = MainApp.Operations.UninstallThenUpdate(SelectedItem); private void MenuUninstall_Invoked(object sender, RoutedEventArgs e) => _ = MainApp.Operations.Uninstall(SelectedItem); diff --git a/src/UniGetUI/UniGetUI.csproj b/src/UniGetUI/UniGetUI.csproj index 9a3c1c3188..8da2b149ac 100644 --- a/src/UniGetUI/UniGetUI.csproj +++ b/src/UniGetUI/UniGetUI.csproj @@ -73,6 +73,7 @@ +