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)