diff --git a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaPackageOperationHelper.cs b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaPackageOperationHelper.cs index 29d7a8331c..ff2167a7ff 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaPackageOperationHelper.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaPackageOperationHelper.cs @@ -16,6 +16,10 @@ using UniGetUI.PackageEngine.Operations; using UniGetUI.PackageEngine.PackageClasses; using UniGetUI.PackageEngine.PackageLoader; +using UniGetUI.PackageOperations; +#if WINDOWS +using UniGetUI.PackageEngine.Managers.WingetManager; +#endif namespace UniGetUI.Avalonia.Infrastructure; @@ -81,6 +85,14 @@ public static async Task AskLocationAndDownloadAsync(IPackage? package, TEL_Inst if (package is null) return; if (MainWindow.Instance is not { } win) return; +#if WINDOWS + if (package.Manager is WinGet && Settings.Get(Settings.K.WinGetDownloadFullManifest)) + { + await AskFolderAndDownloadWinGetManifestAsync(package, referral, win); + return; + } +#endif + await package.Details.Load(); if (package.Details.InstallerUrl is null) @@ -141,9 +153,18 @@ public static async Task DownloadSelectedAsync(IEnumerable packages, T var outputPath = folder?.TryGetLocalPath(); if (outputPath is null) return; + bool fullManifest = Settings.Get(Settings.K.WinGetDownloadFullManifest); foreach (var pkg in eligible) { - var op = new DownloadOperation(pkg, outputPath); + AbstractOperation op; +#if WINDOWS + if (fullManifest && pkg.Manager is WinGet) + op = new WinGetManifestDownloadOperation(pkg, outputPath); + else + op = new DownloadOperation(pkg, outputPath); +#else + op = new DownloadOperation(pkg, outputPath); +#endif op.OperationSucceeded += (_, _) => TelemetryHandler.DownloadPackage(pkg, TEL_OP_RESULT.SUCCESS, referral); op.OperationFailed += (_, _) => TelemetryHandler.DownloadPackage(pkg, TEL_OP_RESULT.FAILED, referral); AvaloniaOperationRegistry.Add(op); @@ -151,6 +172,26 @@ public static async Task DownloadSelectedAsync(IEnumerable packages, T } } +#if WINDOWS + private static async Task AskFolderAndDownloadWinGetManifestAsync( + IPackage package, + TEL_InstallReferral referral, + MainWindow win) + { + var folders = await win.StorageProvider.OpenFolderPickerAsync( + new FolderPickerOpenOptions { AllowMultiple = false }); + var folder = folders.FirstOrDefault(); + var outputPath = folder?.TryGetLocalPath(); + if (outputPath is null) return; + + var op = new WinGetManifestDownloadOperation(package, outputPath); + op.OperationSucceeded += (_, _) => TelemetryHandler.DownloadPackage(package, TEL_OP_RESULT.SUCCESS, referral); + op.OperationFailed += (_, _) => TelemetryHandler.DownloadPackage(package, TEL_OP_RESULT.FAILED, referral); + AvaloniaOperationRegistry.Add(op); + _ = op.MainThread(); + } +#endif + /// /// Runs the WinGet self-repair sequence elevated and shows a result notification. /// Only meaningful on Windows; no-ops on other platforms. diff --git a/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj b/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj index 1f6da35cc0..5b5ca0a8bc 100644 --- a/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj +++ b/src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj @@ -94,7 +94,7 @@ - + diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/PackageManagerPage.axaml.cs b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/PackageManagerPage.axaml.cs index 04747ae25d..1228cf75ff 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/PackageManagerPage.axaml.cs +++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/PackageManagerPage.axaml.cs @@ -420,6 +420,14 @@ private void BuildExtraControls(CheckboxCard_Dict disableNotifsCard) { Text = CoreTools.Translate("Force install location parameter when updating packages with custom locations"), SettingName = CoreSettings.K.WinGetForceLocationOnUpdate, + CornerRadius = new CornerRadius(0), + BorderThickness = new Thickness(1, 0, 1, 1), + }); + + ExtraControls.Children.Add(new CheckboxCard + { + Text = CoreTools.Translate("Download full package manifest alongside the installer"), + SettingName = CoreSettings.K.WinGetDownloadFullManifest, CornerRadius = new CornerRadius(0, 0, 8, 8), BorderThickness = new Thickness(1, 0, 1, 1), }); diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs index 601c1865f6..5a6e9e52e8 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs @@ -89,6 +89,7 @@ public enum K PerManagerMinimumUpdateAgeCustom, WinGetCliToolPreference, WinGetComApiPolicy, + WinGetDownloadFullManifest, DisableClassicMode, DisableInstallerHostChangeWarning, BunPreferLatestVersions, @@ -192,6 +193,7 @@ public static string ResolveKey(K key) K.PerManagerMinimumUpdateAgeCustom => "PerManagerMinimumUpdateAgeCustom", K.WinGetCliToolPreference => "WinGetCliToolPreference", K.WinGetComApiPolicy => "WinGetComApiPolicy", + K.WinGetDownloadFullManifest => "WinGetDownloadFullManifest", K.DisableClassicMode => "DisableClassicMode", K.DisableInstallerHostChangeWarning => "DisableInstallerHostChangeWarning", K.BunPreferLatestVersions => "BunPreferLatestVersions", diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/InternalsVisibleTo.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/InternalsVisibleTo.cs index eeb63dad19..f9f7a709af 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/InternalsVisibleTo.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/InternalsVisibleTo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("UniGetUI.PackageEngine.Tests")] +[assembly: InternalsVisibleTo("UniGetUI.PackageEngine.Operations")] diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/UniGetUI.PackageEngine.Managers.WinGet.csproj b/src/UniGetUI.PackageEngine.Managers.WinGet/UniGetUI.PackageEngine.Managers.WinGet.csproj index 07c211a81b..d394ea4d22 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/UniGetUI.PackageEngine.Managers.WinGet.csproj +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/UniGetUI.PackageEngine.Managers.WinGet.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/UniGetUI.PackageEngine.Operations/WinGetManifestDownloadOperation.cs b/src/UniGetUI.PackageEngine.Operations/WinGetManifestDownloadOperation.cs new file mode 100644 index 0000000000..db52ad744e --- /dev/null +++ b/src/UniGetUI.PackageEngine.Operations/WinGetManifestDownloadOperation.cs @@ -0,0 +1,115 @@ +#if WINDOWS +using UniGetUI.Core.Tools; +using UniGetUI.PackageEngine.Enums; +using UniGetUI.PackageEngine.Interfaces; +using UniGetUI.PackageEngine.Managers.WingetManager; +using UniGetUI.PackageOperations; + +namespace UniGetUI.PackageEngine.Operations; + +public class WinGetManifestDownloadOperation : AbstractProcessOperation +{ + private readonly IPackage _package; + private readonly string _downloadDirectory; + public IPackage Package => _package; + public string DownloadLocation => _downloadDirectory; + + public WinGetManifestDownloadOperation(IPackage package, string downloadDirectory) + : base(true, null) + { + _package = package; + _downloadDirectory = downloadDirectory; + + Metadata.OperationInformation = + "Downloading installer and manifest for WinGet Package=" + + _package.Id + + " into " + + _downloadDirectory; + Metadata.Title = CoreTools.Translate( + "{package} installer and manifest download", + new Dictionary { { "package", _package.Name } } + ); + Metadata.Status = CoreTools.Translate( + "{0} installer and manifest are being downloaded", + _package.Name + ); + Metadata.SuccessTitle = CoreTools.Translate("Download succeeded"); + Metadata.SuccessMessage = CoreTools.Translate( + "{package} installer and manifest were downloaded successfully", + new Dictionary { { "package", _package.Name } } + ); + Metadata.FailureTitle = CoreTools.Translate( + "Download failed", + new Dictionary { { "package", _package.Name } } + ); + Metadata.FailureMessage = CoreTools.Translate( + "{package} installer and manifest could not be downloaded", + new Dictionary { { "package", _package.Name } } + ); + } + + public override Task GetOperationIcon() + { + return Task.Run(_package.GetIconUrl); + } + + protected override void ApplyRetryAction(string retryMode) + { + // Do nothing + } + + protected override void PrepareProcessStartInfo() + { + var winget = (WinGet)_package.Manager; + bool usePinget = winget.SelectedCliToolKind == WinGetCliToolKind.BundledPinget; + + List args = ["download"]; + + args.AddRange(WinGetPkgOperationHelper.GetIdNamePiece(_package).Split(" ")); + + if (!_package.Source.IsVirtualManager) + { + args.AddRange(["--source", _package.Source.Name]); + } + + args.AddRange(["--download-directory", $"\"{_downloadDirectory}\""]); + + if (!usePinget) + { + args.AddRange( + [ + "--accept-package-agreements", + "--accept-source-agreements", + "--disable-interactivity", + ] + ); + + string proxyArg = WinGet.GetProxyArgument(); + if (proxyArg.Length > 0) + { + args.AddRange(proxyArg.Split(' ')); + } + } + + process.StartInfo.FileName = winget.Status.ExecutablePath; + process.StartInfo.Arguments = + winget.Status.ExecutableCallArgs + " " + string.Join(" ", args); + } + + protected override Task GetProcessVeredict( + int ReturnCode, + List Output + ) + { + // winget download return codes follow the standard winget code space. + uint uintCode = (uint)ReturnCode; + + if (uintCode is 0x8A150077 or 0x8A15010C or 0x8A150005) + return Task.FromResult(OperationVeredict.Canceled); + + return Task.FromResult( + ReturnCode == 0 ? OperationVeredict.Success : OperationVeredict.Failure + ); + } +} +#endif diff --git a/src/UniGetUI/AppOperationHelper.cs b/src/UniGetUI/AppOperationHelper.cs index 01348477a8..f4cc6d7806 100644 --- a/src/UniGetUI/AppOperationHelper.cs +++ b/src/UniGetUI/AppOperationHelper.cs @@ -3,6 +3,7 @@ using Microsoft.UI.Xaml.Controls; using UniGetUI.Controls.OperationWidgets; using UniGetUI.Core.Logging; +using UniGetUI.Core.SettingsEngine; using UniGetUI.Core.Tools; using UniGetUI.Interface; using UniGetUI.Interface.Enums; @@ -11,6 +12,7 @@ using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Interfaces; using UniGetUI.PackageEngine.Managers.PowerShellManager; +using UniGetUI.PackageEngine.Managers.WingetManager; using UniGetUI.PackageEngine.Operations; using UniGetUI.PackageEngine.PackageClasses; using UniGetUI.PackageEngine.PackageLoader; @@ -65,6 +67,15 @@ TEL_InstallReferral referral { if (package is null) return null; + + if ( + package.Manager is WinGet + && Settings.Get(Settings.K.WinGetDownloadFullManifest) + ) + { + return await AskFolderAndDownloadWinGetManifest(package, referral); + } + int loadingId = DialogHelper.ShowLoadingDialog(CoreTools.Translate("Please wait...")); try { @@ -152,6 +163,38 @@ TEL_InstallReferral referral } } + private static async Task AskFolderAndDownloadWinGetManifest( + IPackage package, + TEL_InstallReferral referral + ) + { + try + { + var hWnd = Instance.MainWindow.GetWindowHandle(); + var picker = new ExternalLibraries.Pickers.FolderPicker(hWnd); + var outputPath = await Task.Run(picker.Show); + if (string.IsNullOrEmpty(outputPath)) + return null; + + var op = new WinGetManifestDownloadOperation(package, outputPath); + op.OperationSucceeded += (_, _) => + TelemetryHandler.DownloadPackage(package, TEL_OP_RESULT.SUCCESS, referral); + op.OperationFailed += (_, _) => + TelemetryHandler.DownloadPackage(package, TEL_OP_RESULT.FAILED, referral); + Add(op); + Instance.MainWindow.UpdateSystemTrayStatus(); + return op; + } + catch (Exception ex) + { + Logger.Error( + $"An error occurred while downloading the WinGet manifest for package {package.Id}" + ); + Logger.Error(ex); + return null; + } + } + public static async Task Download( IEnumerable packages, TEL_InstallReferral referral @@ -168,6 +211,7 @@ TEL_InstallReferral referral if (outputPath == "") return; + bool fullManifest = Settings.Get(Settings.K.WinGetDownloadFullManifest); foreach (var package in packages) { if ( @@ -179,7 +223,10 @@ TEL_InstallReferral referral continue; } - var op = new DownloadOperation(package, outputPath); + AbstractOperation op = + fullManifest && package.Manager is WinGet + ? new WinGetManifestDownloadOperation(package, outputPath) + : new DownloadOperation(package, outputPath); op.OperationSucceeded += (_, _) => TelemetryHandler.DownloadPackage(package, TEL_OP_RESULT.SUCCESS, referral); op.OperationFailed += (_, _) => diff --git a/src/UniGetUI/Pages/SettingsPages/ManagersPages/PackageManager.xaml.cs b/src/UniGetUI/Pages/SettingsPages/ManagersPages/PackageManager.xaml.cs index b951159157..f40d790f28 100644 --- a/src/UniGetUI/Pages/SettingsPages/ManagersPages/PackageManager.xaml.cs +++ b/src/UniGetUI/Pages/SettingsPages/ManagersPages/PackageManager.xaml.cs @@ -223,6 +223,17 @@ protected override void OnNavigatedTo(NavigationEventArgs e) }; ExtraControls.Children.Add(WinGet_ForceLocationWhenUpdating); + CheckboxCard WinGet_DownloadFullManifest = new() + { + Text = CoreTools.Translate( + "Download full package manifest alongside the installer" + ), + SettingName = Settings.K.WinGetDownloadFullManifest, + CornerRadius = new CornerRadius(0), + BorderThickness = new Thickness(1, 0, 1, 1), + }; + ExtraControls.Children.Add(WinGet_DownloadFullManifest); + CheckboxCard WinGet_EnableTroubleshooter = new() { Text = CoreTools.Translate("Enable the automatic WinGet troubleshooter"), diff --git a/src/UniGetUI/UniGetUI.csproj b/src/UniGetUI/UniGetUI.csproj index 618d2bee53..1e4b25d8ce 100644 --- a/src/UniGetUI/UniGetUI.csproj +++ b/src/UniGetUI/UniGetUI.csproj @@ -221,7 +221,7 @@ - +