From 52ffdd26fda2e8f3e2a0bfe5520c8a37ff9d0c99 Mon Sep 17 00:00:00 2001 From: yangweijie <917647288@qq.com> Date: Sat, 6 Jun 2026 07:00:38 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0xget=20=E5=8A=A0=E9=80=9F?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Generated Files/Secrets.Generated.cs | 11 ++ .../Infrastructure/AvaloniaAutoUpdater.cs | 13 +- .../Pages/SettingsPages/InternetViewModel.cs | 8 + .../Views/Pages/SettingsPages/Internet.axaml | 20 ++ .../SettingsEngine_Names.cs | 4 + src/UniGetUI.Core.Tools/Tools.cs | 50 +++++ .../OverridenInstallationOptions.cs | 15 +- .../Helpers/WinGetPkgOperationHelper.cs | 65 +++++++ .../DownloadOperation.cs | 16 +- .../PackageOperations.cs | 84 ++++++++- .../XGetInstallerDownloadOperation.cs | 172 ++++++++++++++++++ src/UniGetUI/AutoUpdater.cs | 11 +- .../SettingsPages/GeneralPages/Internet.xaml | 28 +++ 13 files changed, 479 insertions(+), 18 deletions(-) create mode 100644 Generated Files/Secrets.Generated.cs create mode 100644 src/UniGetUI.PackageEngine.Operations/XGetInstallerDownloadOperation.cs diff --git a/Generated Files/Secrets.Generated.cs b/Generated Files/Secrets.Generated.cs new file mode 100644 index 0000000000..373f8acdcc --- /dev/null +++ b/Generated Files/Secrets.Generated.cs @@ -0,0 +1,11 @@ +// Auto-generated file - do not modify +namespace UniGetUI.Services +{ + internal static partial class Secrets + { + public static partial string GetGitHubClientId() => "CLIENT_ID_UNSET"; + public static partial string GetGitHubClientSecret() => "CLIENT_SECRET_UNSET"; + public static partial string GetOpenSearchUsername() => "OPENSEARCH_USERNAME_UNSET"; + public static partial string GetOpenSearchPassword() => "OPENSEARCH_PASSWORD_UNSET"; + } +} diff --git a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaAutoUpdater.cs b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaAutoUpdater.cs index a0aa17a3a9..0c897c1c63 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaAutoUpdater.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaAutoUpdater.cs @@ -1120,12 +1120,19 @@ private static async Task DownloadInstallerAsync( throw new InvalidOperationException($"Download URL is not allowed: {url}"); } - LogUpdateDebug($"Downloading installer from {url}"); + // Apply GitHub URL acceleration if configured (security check uses original URL) + string acceleratedUrl = CoreTools.AccelerateDownloadUrl(new Uri(url))?.ToString() ?? url; + + LogUpdateDebug( + acceleratedUrl != url + ? $"Downloading installer from {acceleratedUrl} (accelerated from {url})" + : $"Downloading installer from {url}" + ); using HttpClient client = new(CreateHttpClientHandler(overrides)); client.Timeout = TimeSpan.FromSeconds(600); client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString); - HttpResponseMessage response = await client.GetAsync(url); + HttpResponseMessage response = await client.GetAsync(acceleratedUrl); response.EnsureSuccessStatusCode(); using FileStream fs = new(destination, FileMode.OpenOrCreate); @@ -1137,7 +1144,7 @@ private static async Task DownloadInstallerAsync( // ------------------------------------------------------------------ HTTP client private static HttpClientHandler CreateHttpClientHandler(UpdaterOverrides overrides) { - var handler = new HttpClientHandler(); + HttpClientHandler handler = CoreTools.GenericHttpClientParameters; if (overrides.DisableTlsValidation) { LogUpdateWarn("Registry override: TLS certificate validation is disabled for updater requests."); diff --git a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/InternetViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/InternetViewModel.cs index 0f8249f659..d78598e7a3 100644 --- a/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/InternetViewModel.cs +++ b/src/UniGetUI.Avalonia/ViewModels/Pages/SettingsPages/InternetViewModel.cs @@ -23,11 +23,13 @@ public partial class InternetViewModel : ViewModelBase [ObservableProperty] private bool _isProxyEnabled; [ObservableProperty] private bool _isProxyAuthEnabled; + [ObservableProperty] private bool _isGitHubAccelerationEnabled; public InternetViewModel() { _isProxyEnabled = CoreSettings.Get(CoreSettings.K.EnableProxy); _isProxyAuthEnabled = CoreSettings.Get(CoreSettings.K.EnableProxyAuth); + _isGitHubAccelerationEnabled = CoreSettings.Get(CoreSettings.K.EnableGitHubAcceleration); } public SettingsCard BuildCredentialsCard() @@ -196,6 +198,12 @@ private void RefreshProxyAuthEnabled() ApplyProxyToProcess(); } + [RelayCommand] + private void RefreshGitHubAccelerationEnabled() + { + IsGitHubAccelerationEnabled = CoreSettings.Get(CoreSettings.K.EnableGitHubAcceleration); + } + [RelayCommand] private static void ApplyProxy() => ApplyProxyToProcess(); diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Internet.axaml b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Internet.axaml index 5b465128d9..ed9206b5ec 100644 --- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Internet.axaml +++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Internet.axaml @@ -56,6 +56,26 @@ Text="{t:Translate Wait for the device to be connected to the internet before attempting to do tasks that require internet connectivity.}" StateChangedCommand="{Binding ShowRestartRequiredCommand}" CornerRadius="8"/> + + + + + + + + diff --git a/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs b/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs index 8aa5e26db5..232e992db5 100644 --- a/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs +++ b/src/UniGetUI.Core.Settings/SettingsEngine_Names.cs @@ -94,6 +94,8 @@ public enum K DisableInstallerHostChangeWarning, BunPreferLatestVersions, TrayIconStyle, + EnableGitHubAcceleration, + GitHubAcceleratorUrl, Test1, Test2, @@ -199,6 +201,8 @@ public static string ResolveKey(K key) K.DisableInstallerHostChangeWarning => "DisableInstallerHostChangeWarning", K.BunPreferLatestVersions => "BunPreferLatestVersions", K.TrayIconStyle => "TrayIconStyle", + K.EnableGitHubAcceleration => "EnableGitHubAcceleration", + K.GitHubAcceleratorUrl => "GitHubAcceleratorUrl", K.Test1 => "TestSetting1", K.Test2 => "TestSetting2", diff --git a/src/UniGetUI.Core.Tools/Tools.cs b/src/UniGetUI.Core.Tools/Tools.cs index 74ec86a1dd..da56a4f3f0 100644 --- a/src/UniGetUI.Core.Tools/Tools.cs +++ b/src/UniGetUI.Core.Tools/Tools.cs @@ -52,6 +52,52 @@ static CoreTools() LoadStaticTranslation(); } + /// + /// Accelerates a GitHub download URL by rewriting it through a configured accelerator + /// (e.g., a self-hosted xget-src Cloudflare Worker or ghproxy-style service). + /// Only transforms URLs from known GitHub domains when GitHub acceleration is enabled. + /// + /// The original download URL + /// The accelerated URL if applicable, or the original URL otherwise + public static Uri? AccelerateDownloadUrl(Uri? url) + { + if (url is null) + return null; + + if (!Settings.Get(Settings.K.EnableGitHubAcceleration)) + return url; + + string acceleratorBase = Settings.GetValue(Settings.K.GitHubAcceleratorUrl); + if (string.IsNullOrWhiteSpace(acceleratorBase)) + return url; + + string original = url.ToString(); + + // Map GitHub domains to xget-src platform keys. + // For github.com, the key is "gh". For subdomains like raw.githubusercontent.com, + // the key is the full hostname (as used by xget-src and similar proxies). + var domainMappings = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["https://github.com/"] = "gh", + ["https://raw.githubusercontent.com/"] = "raw.githubusercontent.com", + ["https://objects.githubusercontent.com/"] = "objects.githubusercontent.com", + ["https://release-assets.githubusercontent.com/"] = "release-assets.githubusercontent.com", + }; + + foreach (var kvp in domainMappings) + { + if (original.StartsWith(kvp.Key, StringComparison.OrdinalIgnoreCase)) + { + string path = original[kvp.Key.Length..]; + var accelerated = new Uri($"{acceleratorBase.TrimEnd('/')}/{kvp.Value}/{path.TrimStart('/')}"); + Logger.Debug($"GitHub acceleration: {original} -> {accelerated}"); + return accelerated; + } + } + + return url; + } + /// /// Translate a string to the current language /// @@ -328,6 +374,8 @@ public static long GetFileSizeAsLong(Uri? url) if (url is null) return 0; + url = AccelerateDownloadUrl(url) ?? url; + try { using HttpClient client = new(CoreTools.GenericHttpClientParameters); @@ -347,6 +395,8 @@ public static long GetFileSizeAsLong(Uri? url) public static string GetFileName(Uri url) { + url = AccelerateDownloadUrl(url) ?? url; + try { var handler = CoreTools.GenericHttpClientParameters; diff --git a/src/UniGetUI.PackageEngine.Enums/OverridenInstallationOptions.cs b/src/UniGetUI.PackageEngine.Enums/OverridenInstallationOptions.cs index 85e07d2a61..713f68c15b 100644 --- a/src/UniGetUI.PackageEngine.Enums/OverridenInstallationOptions.cs +++ b/src/UniGetUI.PackageEngine.Enums/OverridenInstallationOptions.cs @@ -8,6 +8,19 @@ public struct OverridenInstallationOptions public bool? WinGet_SpecifyVersion = null; public bool Pip_BreakSystemPackages = false; + /// + /// Path to a pre-downloaded installer (used by WinGet GitHub acceleration). + /// When set, PackageOperation.PrepareProcessStartInfo runs this local installer + /// instead of calling winget.exe. + /// + public string? AcceleratedInstallerPath = null; + + /// + /// Installer type (msi, exe, inno, nullsoft, wix, msix, portable, burn). + /// Determines silent-install arguments for the accelerated local installer. + /// + public string? AcceleratedInstallerType = null; + public OverridenInstallationOptions(string? scope = null, bool? runAsAdministrator = null) { Scope = scope; @@ -16,6 +29,6 @@ public OverridenInstallationOptions(string? scope = null, bool? runAsAdministrat public override string ToString() { - return $""; + return $""; } } diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs index 3853b10735..3323d8ffdb 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs @@ -29,6 +29,54 @@ public static string GetIdNamePiece(IPackage package) public WinGetPkgOperationHelper(WinGet manager) : base(manager) { } + /// + /// Checks whether GitHub acceleration is applicable for this package. + /// Returns true if the installer URL is from a GitHub domain and + /// acceleration is enabled (the actual download is done by + /// XGetInstallerDownloadOperation as a pre-operation). + /// + private static bool IsAccelerationApplicable(IPackage package, OperationType operation) + { + if (operation is not OperationType.Update) + return false; + + if (!Settings.Get(Settings.K.EnableGitHubAcceleration)) + return false; + + string? acceleratorBase = Settings.GetValue(Settings.K.GitHubAcceleratorUrl); + if (string.IsNullOrWhiteSpace(acceleratorBase)) + return false; + + try + { + package.Details.Load().GetAwaiter().GetResult(); + } + catch + { + Logger.Warn($"GitHub acceleration: failed to load details for {package.Id}"); + return false; + } + + Uri? installerUrl = package.Details.InstallerUrl; + if (installerUrl is null) + return false; + + string urlStr = installerUrl.ToString(); + bool isGitHub = urlStr.StartsWith("https://github.com/", StringComparison.OrdinalIgnoreCase) + || urlStr.StartsWith("https://raw.githubusercontent.com/", StringComparison.OrdinalIgnoreCase) + || urlStr.StartsWith("https://objects.githubusercontent.com/", StringComparison.OrdinalIgnoreCase) + || urlStr.StartsWith("https://release-assets.githubusercontent.com/", StringComparison.OrdinalIgnoreCase); + + if (!isGitHub) + return false; + + Uri? accelerated = CoreTools.AccelerateDownloadUrl(installerUrl); + if (accelerated is null || accelerated == installerUrl) + return false; + + return true; + } + protected override IReadOnlyList _getOperationParameters( IPackage package, InstallOptions options, @@ -41,6 +89,12 @@ OperationType operation bool usePinget = ((WinGet)Manager).SelectedCliToolKind == WinGetCliToolKind.BundledPinget; + // GitHub acceleration: signal that local installer should be used + if (IsAccelerationApplicable(package, operation)) + { + return ["--accelerated-local-install"]; + } + List parameters = [ operation switch @@ -242,6 +296,17 @@ protected override OperationVeredict _getOperationResult( int returnCode ) { + // Local accelerated installer: simple 0=success, non-zero=failure + if (package.OverridenOptions.AcceleratedInstallerPath is not null) + { + if (returnCode == 0) + { + MarkUpgradeAsDone(package); + return OperationVeredict.Success; + } + return OperationVeredict.Failure; + } + // See https://github.com/microsoft/winget-cli/blob/master/doc/windows/package-manager/winget/returnCodes.md for reference uint uintCode = (uint)returnCode; diff --git a/src/UniGetUI.PackageEngine.Operations/DownloadOperation.cs b/src/UniGetUI.PackageEngine.Operations/DownloadOperation.cs index e139f391c4..bca2e8d406 100644 --- a/src/UniGetUI.PackageEngine.Operations/DownloadOperation.cs +++ b/src/UniGetUI.PackageEngine.Operations/DownloadOperation.cs @@ -97,10 +97,22 @@ protected override async Task PerformOperation() downloadLocation = Path.Join(downloadLocation, fileName); } - Line($"Download URL found at {downloadUrl} ", LineType.Information); + // Apply GitHub URL acceleration if configured + Uri acceleratedUrl = CoreTools.AccelerateDownloadUrl(downloadUrl) + ?? downloadUrl + ?? throw new InvalidOperationException("Download URL was null after acceleration fallback"); + if (acceleratedUrl != downloadUrl!) + { + Line($"Download URL accelerated to {acceleratedUrl} ", LineType.Information); + } + else + { + Line($"Download URL found at {downloadUrl} ", LineType.Information); + } + using var httpClient = new HttpClient(CoreTools.GenericHttpClientParameters); using var response = await httpClient.GetAsync( - downloadUrl, + acceleratedUrl, HttpCompletionOption.ResponseHeadersRead ); diff --git a/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs b/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs index 56741694d2..53898db1d1 100644 --- a/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs +++ b/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs @@ -38,7 +38,7 @@ public PackageOperation( ) : base( !IgnoreParallelInstalls, - _getPreInstallOps(options, role, req), + _getPreInstallOps(options, role, package, req), _getPostInstallOps(options, role, package) ) { @@ -99,16 +99,67 @@ protected override void ApplyRetryAction(string retryMode) + Package.OverridenOptions.ToString(); } + private static (string exe, string args) GetInstallerExecInfo(string? installerType, string localPath) + { + string quoted = $"\"{localPath}\""; + return installerType?.ToLowerInvariant() switch + { + "msi" or "wix" => ("msiexec", $"/i {quoted} /quiet /norestart"), + "msix" => ("powershell", $"-NoProfile -Command \"Add-AppxPackage -Path {quoted}\""), + "inno" => (localPath, "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /NORUN"), + "nullsoft" or "nsis" => (localPath, "/S"), + "burn" => (localPath, "/quiet /norestart"), + _ => (localPath, "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"), + }; + } + protected sealed override void PrepareProcessStartInfo() { bool IsAdmin = CoreTools.IsAdministrator(); Package.SetTag(PackageTag.OnQueue); + + // GitHub acceleration: run pre-downloaded local installer directly + if (Package.OverridenOptions.AcceleratedInstallerPath is not null) + { + var (exe, args) = GetInstallerExecInfo( + Package.OverridenOptions.AcceleratedInstallerType, + Package.OverridenOptions.AcceleratedInstallerPath); + + if (RequiresAdminRights() && IsAdmin is false) + { + IsAdmin = true; + if ( + OperatingSystem.IsLinux() + || Settings.Get(Settings.K.DoCacheAdminRights) + || Settings.Get(Settings.K.DoCacheAdminRightsForBatches) + ) + { + RequestCachingOfUACPrompt(); + } + + process.StartInfo.FileName = CoreData.ElevatorPath; + process.StartInfo.Arguments = + $"{CoreData.ElevatorArgs} \"{exe}\" {args}".TrimStart(); + } + else + { + process.StartInfo.FileName = exe; + process.StartInfo.Arguments = args; + } + + ApplyCapabilities( + IsAdmin, + Options.InteractiveInstallation, + (Options.SkipHashCheck && Role is not OperationType.Uninstall), + Package.OverridenOptions.Scope ?? Options.InstallationScope + ); + return; + } + string operation_args = string.Join( " ", Package.Manager.OperationHelper.GetParameters(Package, Options, Role) ); - string FileName, - Arguments; if (RequiresAdminRights() && IsAdmin is false) { @@ -122,14 +173,14 @@ protected sealed override void PrepareProcessStartInfo() RequestCachingOfUACPrompt(); } - FileName = CoreData.ElevatorPath; - Arguments = + process.StartInfo.FileName = CoreData.ElevatorPath; + process.StartInfo.Arguments = $"{CoreData.ElevatorArgs} \"{Package.Manager.Status.ExecutablePath}\" {Package.Manager.Status.ExecutableCallArgs} {operation_args}".TrimStart(); } else { - FileName = Package.Manager.Status.ExecutablePath; - Arguments = $"{Package.Manager.Status.ExecutableCallArgs} {operation_args}"; + process.StartInfo.FileName = Package.Manager.Status.ExecutablePath; + process.StartInfo.Arguments = $"{Package.Manager.Status.ExecutableCallArgs} {operation_args}"; } if (IsAdmin && IsWinGetManager(Package.Manager)) @@ -137,9 +188,6 @@ protected sealed override void PrepareProcessStartInfo() RedirectWinGetTempFolder(); } - process.StartInfo.FileName = FileName; - process.StartInfo.Arguments = Arguments; - ApplyCapabilities( IsAdmin, Options.InteractiveInstallation, @@ -238,6 +286,7 @@ public override Task GetOperationIcon() private static IReadOnlyList _getPreInstallOps( InstallOptions opts, OperationType role, + IPackage package, AbstractOperation? preReq = null ) { @@ -245,6 +294,21 @@ private static IReadOnlyList _getPreInstallOps( if (preReq is not null) l.Add(new(preReq, true)); + // GitHub acceleration: pre-download installer before running winget + if ( + role is OperationType.Update + && Settings.Get(Settings.K.EnableGitHubAcceleration) + && !string.IsNullOrWhiteSpace(Settings.GetValue(Settings.K.GitHubAcceleratorUrl)) + ) + { + l.Add( + new InnerOperation( + new XGetInstallerDownloadOperation(package), + mustSucceed: true + ) + ); + } + foreach (var process in opts.KillBeforeOperation) l.Add(new InnerOperation(new KillProcessOperation(process), mustSucceed: false)); diff --git a/src/UniGetUI.PackageEngine.Operations/XGetInstallerDownloadOperation.cs b/src/UniGetUI.PackageEngine.Operations/XGetInstallerDownloadOperation.cs new file mode 100644 index 0000000000..ba6addb9a9 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Operations/XGetInstallerDownloadOperation.cs @@ -0,0 +1,172 @@ +using UniGetUI.Core.Logging; +using UniGetUI.Core.SettingsEngine; +using UniGetUI.Core.Tools; +using UniGetUI.PackageEngine.Enums; +using UniGetUI.PackageEngine.Interfaces; +using UniGetUI.PackageOperations; + +namespace UniGetUI.PackageEngine.Operations; + +/// +/// Downloads a WinGet package installer via the configured GitHub accelerator +/// and stores the local path on OverridenOptions.AcceleratedInstallerPath. +/// Runs as a pre-operation so the download does not block the UI thread. +/// +public class XGetInstallerDownloadOperation : AbstractOperation +{ + private readonly IPackage _package; + + public XGetInstallerDownloadOperation(IPackage package) + : base(queue_enabled: false) + { + _package = package; + + Metadata.OperationInformation = + "Downloading accelerated installer for Package=" + _package.Id; + Metadata.Title = CoreTools.Translate( + "Downloading {package} installer via accelerator", + new Dictionary { { "package", _package.Name } } + ); + Metadata.Status = CoreTools.Translate( + "{0} installer is being downloaded via accelerator", + _package.Name + ); + Metadata.SuccessTitle = CoreTools.Translate("Download succeeded"); + Metadata.SuccessMessage = CoreTools.Translate( + "{package} installer was downloaded successfully", + new Dictionary { { "package", _package.Name } } + ); + Metadata.FailureTitle = CoreTools.Translate("Download failed"); + Metadata.FailureMessage = CoreTools.Translate( + "{package} installer could not be downloaded via accelerator", + new Dictionary { { "package", _package.Name } } + ); + } + + public override Task GetOperationIcon() + { + return Task.Run(_package.GetIconUrl); + } + + protected override void ApplyRetryAction(string retryMode) + { + } + + protected override async Task PerformOperation() + { + try + { + Line( + $"Fetching installer details for {_package.Name}...", + LineType.Information + ); + + await _package.Details.Load(); + + Uri? installerUrl = _package.Details.InstallerUrl; + if (installerUrl is null) + { + Line( + "No installer URL found for this package.", + LineType.Error + ); + return OperationVeredict.Failure; + } + + Uri? accelerated = CoreTools.AccelerateDownloadUrl(installerUrl); + if (accelerated is null || accelerated == installerUrl) + { + Line( + "URL is not a GitHub domain or acceleration not applicable; skipping.", + LineType.Information + ); + return OperationVeredict.Success; + } + + string? installerType = _package.Details.InstallerType; + if (string.IsNullOrWhiteSpace(installerType)) + installerType = "exe"; + + string safeId = string.Join("_", _package.Id.Split(Path.GetInvalidFileNameChars())); + string tempDir = Path.Join(Path.GetTempPath(), "UniGetUI", "xget", safeId); + Directory.CreateDirectory(tempDir); + + string? fileName = await _package.GetInstallerFileName(); + if (string.IsNullOrWhiteSpace(fileName)) + { + string ext = installerType switch + { + "msi" => ".msi", + "msix" => ".msix", + "wix" => ".msi", + _ => ".exe", + }; + fileName = CoreTools.MakeValidFileName(_package.Name) + ext; + } + + string localPath = Path.Join(tempDir, fileName); + + if (File.Exists(localPath)) + { + Line($"Using cached installer at {localPath}", LineType.Information); + _package.OverridenOptions.AcceleratedInstallerPath = localPath; + _package.OverridenOptions.AcceleratedInstallerType = installerType; + return OperationVeredict.Success; + } + + Line($"Downloading from {accelerated}", LineType.Information); + + using var httpClient = new HttpClient(CoreTools.GenericHttpClientParameters); + httpClient.Timeout = TimeSpan.FromMinutes(10); + + using var response = await httpClient.GetAsync( + accelerated, HttpCompletionOption.ResponseHeadersRead); + + response.EnsureSuccessStatusCode(); + + var totalBytes = response.Content.Headers.ContentLength ?? -1L; + var canReportProgress = totalBytes > 0; + + using var contentStream = await response.Content.ReadAsStreamAsync(); + using var fileStream = new FileStream( + localPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true); + + var buffer = new byte[4 * 1024 * 1024]; + long totalRead = 0; + int bytesRead; + int oldProgress = -1; + + while ((bytesRead = await contentStream.ReadAsync(buffer)) > 0) + { + await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead)); + totalRead += bytesRead; + + if (canReportProgress) + { + var progress = (int)((totalRead * 100L) / totalBytes); + if (progress != oldProgress) + { + oldProgress = progress; + Line( + CoreTools.TextProgressGenerator( + 30, progress, + $"{CoreTools.FormatAsSize(totalRead)}/{CoreTools.FormatAsSize(totalBytes)}" + ), + LineType.ProgressIndicator + ); + } + } + } + + Line($"Saved to {localPath}", LineType.Information); + _package.OverridenOptions.AcceleratedInstallerPath = localPath; + _package.OverridenOptions.AcceleratedInstallerType = installerType; + return OperationVeredict.Success; + } + catch (Exception ex) + { + Line($"{ex.GetType()}: {ex.Message}", LineType.Error); + return OperationVeredict.Failure; + } + } +} diff --git a/src/UniGetUI/AutoUpdater.cs b/src/UniGetUI/AutoUpdater.cs index af066775d4..fd695256a7 100644 --- a/src/UniGetUI/AutoUpdater.cs +++ b/src/UniGetUI/AutoUpdater.cs @@ -639,12 +639,19 @@ UpdaterOverrides updaterOverrides throw new InvalidOperationException($"Download URL is not allowed: {downloadUrl}"); } - LogUpdateDebug($"Downloading installer from {downloadUrl} to {installerLocation}"); + // Apply GitHub URL acceleration if configured (security check uses original URL) + string acceleratedUrl = CoreTools.AccelerateDownloadUrl(new Uri(downloadUrl))?.ToString() ?? downloadUrl; + + LogUpdateDebug( + acceleratedUrl != downloadUrl + ? $"Downloading installer from {acceleratedUrl} (accelerated from {downloadUrl}) to {installerLocation}" + : $"Downloading installer from {downloadUrl} to {installerLocation}" + ); using (HttpClient client = new(CreateHttpClientHandler(updaterOverrides))) { client.Timeout = TimeSpan.FromSeconds(600); client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString); - HttpResponseMessage result = await client.GetAsync(downloadUrl); + HttpResponseMessage result = await client.GetAsync(acceleratedUrl); result.EnsureSuccessStatusCode(); using FileStream fs = new(installerLocation, FileMode.OpenOrCreate); await result.Content.CopyToAsync(fs); diff --git a/src/UniGetUI/Pages/SettingsPages/GeneralPages/Internet.xaml b/src/UniGetUI/Pages/SettingsPages/GeneralPages/Internet.xaml index 056e0c070b..b70d171b12 100644 --- a/src/UniGetUI/Pages/SettingsPages/GeneralPages/Internet.xaml +++ b/src/UniGetUI/Pages/SettingsPages/GeneralPages/Internet.xaml @@ -174,6 +174,34 @@ StateChanged="ShowRestartBanner" Text="Wait for the device to be connected to the internet before attempting to do tasks that require internet connectivity." /> + + + + + + + + + + + + + + + + From 4299550bb7919863e146f736d9a13c8c002b55eb Mon Sep 17 00:00:00 2001 From: yangweijie <917647288@qq.com> Date: Sat, 6 Jun 2026 22:35:35 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=AE=89=E8=A3=85=E6=97=B6=E4=B9=9F?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=8A=A0=E9=80=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Helpers/WinGetPkgOperationHelper.cs | 5 +++-- .../PackageOperations.cs | 2 +- .../XGetInstallerDownloadOperation.cs | 12 ++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs index 3323d8ffdb..51add7fc57 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/Helpers/WinGetPkgOperationHelper.cs @@ -37,7 +37,7 @@ public WinGetPkgOperationHelper(WinGet manager) /// private static bool IsAccelerationApplicable(IPackage package, OperationType operation) { - if (operation is not OperationType.Update) + if (operation is not (OperationType.Update or OperationType.Install)) return false; if (!Settings.Get(Settings.K.EnableGitHubAcceleration)) @@ -301,7 +301,8 @@ int returnCode { if (returnCode == 0) { - MarkUpgradeAsDone(package); + if (operation is OperationType.Update) + MarkUpgradeAsDone(package); return OperationVeredict.Success; } return OperationVeredict.Failure; diff --git a/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs b/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs index 53898db1d1..aa323f7704 100644 --- a/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs +++ b/src/UniGetUI.PackageEngine.Operations/PackageOperations.cs @@ -296,7 +296,7 @@ private static IReadOnlyList _getPreInstallOps( // GitHub acceleration: pre-download installer before running winget if ( - role is OperationType.Update + (role is OperationType.Update or OperationType.Install) && Settings.Get(Settings.K.EnableGitHubAcceleration) && !string.IsNullOrWhiteSpace(Settings.GetValue(Settings.K.GitHubAcceleratorUrl)) ) diff --git a/src/UniGetUI.PackageEngine.Operations/XGetInstallerDownloadOperation.cs b/src/UniGetUI.PackageEngine.Operations/XGetInstallerDownloadOperation.cs index ba6addb9a9..43a13cdefa 100644 --- a/src/UniGetUI.PackageEngine.Operations/XGetInstallerDownloadOperation.cs +++ b/src/UniGetUI.PackageEngine.Operations/XGetInstallerDownloadOperation.cs @@ -61,7 +61,7 @@ protected override async Task PerformOperation() LineType.Information ); - await _package.Details.Load(); + await _package.Details.Load().ConfigureAwait(false); Uri? installerUrl = _package.Details.InstallerUrl; if (installerUrl is null) @@ -91,7 +91,7 @@ protected override async Task PerformOperation() string tempDir = Path.Join(Path.GetTempPath(), "UniGetUI", "xget", safeId); Directory.CreateDirectory(tempDir); - string? fileName = await _package.GetInstallerFileName(); + string? fileName = await _package.GetInstallerFileName().ConfigureAwait(false); if (string.IsNullOrWhiteSpace(fileName)) { string ext = installerType switch @@ -120,14 +120,14 @@ protected override async Task PerformOperation() httpClient.Timeout = TimeSpan.FromMinutes(10); using var response = await httpClient.GetAsync( - accelerated, HttpCompletionOption.ResponseHeadersRead); + accelerated, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); response.EnsureSuccessStatusCode(); var totalBytes = response.Content.Headers.ContentLength ?? -1L; var canReportProgress = totalBytes > 0; - using var contentStream = await response.Content.ReadAsStreamAsync(); + using var contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); using var fileStream = new FileStream( localPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true); @@ -136,9 +136,9 @@ protected override async Task PerformOperation() int bytesRead; int oldProgress = -1; - while ((bytesRead = await contentStream.ReadAsync(buffer)) > 0) + while ((bytesRead = await contentStream.ReadAsync(buffer).ConfigureAwait(false)) > 0) { - await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead)); + await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead)).ConfigureAwait(false); totalRead += bytesRead; if (canReportProgress)