diff --git a/src/c#/GeneralUpdate.Core/Configuration/ConfiginfoBuilder-Example.cs b/src/c#/GeneralUpdate.Core/Configuration/ConfiginfoBuilder-Example.cs
deleted file mode 100644
index b67d4d2b..00000000
--- a/src/c#/GeneralUpdate.Core/Configuration/ConfiginfoBuilder-Example.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using GeneralUpdate.Core.Configuration;
-
-namespace ConfiginfoBuilderExample
-{
- ///
- /// Example demonstrating the ConfiginfoBuilder usage with JSON configuration
- ///
- class Program
- {
- static void Main(string[] args)
- {
- Console.WriteLine("=== ConfiginfoBuilder Usage Examples ===\n");
-
- // Example 1: Load configuration from JSON file (recommended)
- Console.WriteLine("Example 1: Loading from update_config.json file");
- Console.WriteLine("This example requires an update_config.json file in the running directory.");
- Console.WriteLine("The configuration file has the highest priority and must contain all required settings.\n");
-
- try
- {
- // Create update_config.json for demonstration
- CreateExampleConfigFile();
-
- // Simply call Create() with no parameters - it loads from update_config.json
- var config = ConfiginfoBuilder.Create().Build();
-
- Console.WriteLine($" UpdateUrl: {config.UpdateUrl}");
- Console.WriteLine($" Token: {config.Token}");
- Console.WriteLine($" Scheme: {config.Scheme}");
- Console.WriteLine($" InstallPath: {config.InstallPath}");
- Console.WriteLine($" AppName: {config.AppName}");
- Console.WriteLine($" ClientVersion: {config.ClientVersion}");
- Console.WriteLine();
- }
- catch (FileNotFoundException ex)
- {
- Console.WriteLine($" Error: {ex.Message}");
- Console.WriteLine(" Please create update_config.json in the running directory.");
- Console.WriteLine();
- }
- finally
- {
- CleanupExampleConfigFile();
- }
-
- // Example 2: Customizing configuration after loading from file
- Console.WriteLine("Example 2: Loading from JSON and customizing with method chaining");
- try
- {
- CreateExampleConfigFile();
-
- var customConfig = ConfiginfoBuilder.Create()
- .SetAppName("CustomApp.exe")
- .SetInstallPath("/custom/path")
- .Build();
-
- Console.WriteLine($" AppName: {customConfig.AppName}");
- Console.WriteLine($" InstallPath: {customConfig.InstallPath}");
- Console.WriteLine();
- }
- catch (FileNotFoundException ex)
- {
- Console.WriteLine($" Error: {ex.Message}");
- Console.WriteLine();
- }
- finally
- {
- CleanupExampleConfigFile();
- }
-
- // Example 3: Error handling when config file is missing
- Console.WriteLine("Example 3: Error Handling - Missing Configuration File");
- try
- {
- var config = ConfiginfoBuilder.Create().Build();
- }
- catch (FileNotFoundException ex)
- {
- Console.WriteLine($" Caught expected error: {ex.Message}");
- Console.WriteLine(" This is expected when update_config.json doesn't exist.");
- }
- Console.WriteLine();
-
- Console.WriteLine("\n=== All Examples Completed! ===");
- Console.WriteLine("\nNote: ConfiginfoBuilder now requires update_config.json file.");
- Console.WriteLine("See update_config.example.json for a complete example.");
- }
-
- private static void CreateExampleConfigFile()
- {
- var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json");
- var exampleConfig = @"{
- ""UpdateUrl"": ""https://api.example.com/updates"",
- ""Token"": ""example-auth-token"",
- ""Scheme"": ""https"",
- ""AppName"": ""Update.exe"",
- ""MainAppName"": ""MyApplication.exe"",
- ""ClientVersion"": ""1.0.0"",
- ""UpgradeClientVersion"": ""1.0.0"",
- ""AppSecretKey"": ""example-secret-key"",
- ""ProductId"": ""example-product-id""
-}";
- File.WriteAllText(configPath, exampleConfig);
- }
-
- private static void CleanupExampleConfigFile()
- {
- var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json");
- if (File.Exists(configPath))
- {
- File.Delete(configPath);
- }
- }
- }
-}
diff --git a/src/c#/GeneralUpdate.Core/Configuration/Environments.cs b/src/c#/GeneralUpdate.Core/Configuration/Environments.cs
index 0cf22bfb..bd32d404 100644
--- a/src/c#/GeneralUpdate.Core/Configuration/Environments.cs
+++ b/src/c#/GeneralUpdate.Core/Configuration/Environments.cs
@@ -2,17 +2,17 @@
using System.IO;
using System.Security.Cryptography;
using System.Text;
+using GeneralUpdate.Core.Ipc;
namespace GeneralUpdate.Core.Configuration;
///
/// Secure IPC environment variable provider.
/// AES-encrypted temp files in a dedicated subdirectory, auto-deleted after read.
+/// Encryption is delegated to .
///
public static class Environments
{
- // Fixed key/IV derived from a constant — not crypto-grade, but sufficient for
- // ephemeral IPC where the file lives < 1 second and is in a per-user directory.
private static readonly byte[] _aesKey = SHA256.Create()
.ComputeHash(Encoding.UTF8.GetBytes("GeneralUpdate.IPC.EnvironmentProvider.v1"));
private static readonly byte[] _aesIV = new byte[16] { 0x47, 0x55, 0x50, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
@@ -31,33 +31,13 @@ public static void SetEnvironmentVariable(string key, string value)
{
var filePath = Path.Combine(IpcDir, $"{key}.enc");
var plainBytes = Encoding.UTF8.GetBytes(value);
- using var aes = Aes.Create();
- aes.Key = _aesKey;
- aes.IV = _aesIV;
- using var encryptor = aes.CreateEncryptor();
- var encrypted = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
- File.WriteAllBytes(filePath, encrypted);
+ IpcEncryption.EncryptToFile(plainBytes, filePath, _aesKey, _aesIV);
}
public static string GetEnvironmentVariable(string key)
{
var filePath = Path.Combine(Path.GetTempPath(), "GeneralUpdate", "ipc", $"{key}.enc");
- if (!File.Exists(filePath))
- return string.Empty;
-
- try
- {
- var encrypted = File.ReadAllBytes(filePath);
- using var aes = Aes.Create();
- aes.Key = _aesKey;
- aes.IV = _aesIV;
- using var decryptor = aes.CreateDecryptor();
- var plainBytes = decryptor.TransformFinalBlock(encrypted, 0, encrypted.Length);
- return Encoding.UTF8.GetString(plainBytes);
- }
- finally
- {
- try { File.Delete(filePath); } catch { /* best-effort cleanup */ }
- }
+ var plainBytes = IpcEncryption.DecryptFromFile(filePath, _aesKey, _aesIV);
+ return plainBytes != null ? Encoding.UTF8.GetString(plainBytes) : string.Empty;
}
}
diff --git a/src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MutiAllDownloadCompletedEventArgs.cs b/src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MultiAllDownloadCompletedEventArgs.cs
similarity index 100%
rename from src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MutiAllDownloadCompletedEventArgs.cs
rename to src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MultiAllDownloadCompletedEventArgs.cs
diff --git a/src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MutiDownloadCompletedEventArgs.cs b/src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MultiDownloadCompletedEventArgs.cs
similarity index 100%
rename from src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MutiDownloadCompletedEventArgs.cs
rename to src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MultiDownloadCompletedEventArgs.cs
diff --git a/src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MutiDownloadErrorEventArgs.cs b/src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MultiDownloadErrorEventArgs.cs
similarity index 100%
rename from src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MutiDownloadErrorEventArgs.cs
rename to src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MultiDownloadErrorEventArgs.cs
diff --git a/src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MutiDownloadStatisticsEventArgs.cs b/src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MultiDownloadStatisticsEventArgs.cs
similarity index 100%
rename from src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MutiDownloadStatisticsEventArgs.cs
rename to src/c#/GeneralUpdate.Core/Download/MultiEventArgs/MultiDownloadStatisticsEventArgs.cs
diff --git a/src/c#/GeneralUpdate.Core/FileSystem/BlackListDefaults.cs b/src/c#/GeneralUpdate.Core/FileSystem/BlackListDefaults.cs
index d5bd92a8..99a23e7c 100644
--- a/src/c#/GeneralUpdate.Core/FileSystem/BlackListDefaults.cs
+++ b/src/c#/GeneralUpdate.Core/FileSystem/BlackListDefaults.cs
@@ -2,7 +2,7 @@
namespace GeneralUpdate.Core.FileSystem;
-/// Built-in default blacklist items — previously hardcoded in BlackListManager.
+/// Built-in default blacklist items.
public static class BlackListDefaults
{
/// Default blacklisted files (system DLLs that ship with the runtime).
diff --git a/src/c#/GeneralUpdate.Core/FileSystem/BlackListManager.cs b/src/c#/GeneralUpdate.Core/FileSystem/BlackListManager.cs
deleted file mode 100644
index 887a6f41..00000000
--- a/src/c#/GeneralUpdate.Core/FileSystem/BlackListManager.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using GeneralUpdate.Core.Configuration;
-
-namespace GeneralUpdate.Core.FileSystem;
-
-/// Matches files/directories against a blacklist configuration.
-public interface IBlackListMatcher
-{
- bool IsBlacklisted(string relativeFilePath);
- bool IsBlacklistedFormat(string extension);
- bool ShouldSkipDirectory(string directoryName);
-}
-
-///
-/// Thread-safe blacklist manager. Uses Lazy<T> singleton.
-/// Matching is case-insensitive and supports prefix matching for skip directories.
-///
-[Obsolete("Use DefaultBlackListMatcher + StorageManager.BlackListMatcher instead. See #412.")]
-public class BlackListManager : IBlackListMatcher
-{
- private static readonly Lazy _lazy = new(() => new BlackListManager());
- private readonly object _lock = new();
-
- private readonly List _blackFiles =
- [
- "Microsoft.Bcl.AsyncInterfaces.dll",
- "System.Collections.Immutable.dll",
- "System.IO.Pipelines.dll",
- "System.Text.Encodings.Web.dll",
- "System.Text.Json.dll"
- ];
-
- private readonly List _blackFormats = [".patch", ".pdb", ".rar", ".tar", ".json", Format.ZIP];
- private readonly List _skipDirs = ["app-", "fail"];
-
- private BlackListManager() { }
-
- public static BlackListManager Instance => _lazy.Value;
-
- // Read-only accessors
- public IReadOnlyList BlackFiles { get { lock (_lock) return _blackFiles.ToList(); } }
- public IReadOnlyList BlackFormats { get { lock (_lock) return _blackFormats.ToList(); } }
- public IReadOnlyList SkipDirectorys { get { lock (_lock) return _skipDirs.ToList(); } }
-
- // Mutation
- public void AddBlackFiles(List? files)
- { if (files == null) return; lock (_lock) { foreach (var f in files) AddBlackFileLocked(f); } }
- public void AddBlackFile(string file)
- { if (string.IsNullOrWhiteSpace(file)) return; lock (_lock) AddBlackFileLocked(file); }
- private void AddBlackFileLocked(string file)
- { if (!_blackFiles.Contains(file)) _blackFiles.Add(file); }
-
- public void AddBlackFormats(List? formats)
- { if (formats == null) return; lock (_lock) { foreach (var f in formats) AddBlackFormatLocked(f); } }
- public void AddBlackFormat(string format)
- { if (string.IsNullOrWhiteSpace(format)) return; lock (_lock) AddBlackFormatLocked(format); }
- private void AddBlackFormatLocked(string format)
- { if (!_blackFormats.Contains(format)) _blackFormats.Add(format); }
-
- public void AddSkipDirectorys(List? dirs)
- { if (dirs == null) return; lock (_lock) { foreach (var d in dirs) AddSkipDirectoryLocked(d); } }
- public void AddSkipDirectory(string dir)
- { if (string.IsNullOrWhiteSpace(dir)) return; lock (_lock) AddSkipDirectoryLocked(dir); }
- private void AddSkipDirectoryLocked(string dir)
- { if (!_skipDirs.Contains(dir)) _skipDirs.Add(dir); }
-
- // Matching (read operations — no lock needed for immutable reads)
- public bool IsBlacklisted(string relativeFilePath)
- {
- var fileName = Path.GetFileName(relativeFilePath);
- var ext = Path.GetExtension(relativeFilePath);
- lock (_lock)
- return _blackFiles.Contains(fileName) || _blackFormats.Contains(ext);
- }
-
- public bool IsBlacklistedFormat(string extension)
- {
- lock (_lock) return _blackFormats.Contains(extension);
- }
-
- public bool ShouldSkipDirectory(string directoryName)
- {
- lock (_lock) return _skipDirs.Any(d => directoryName.Contains(d));
- }
-}
diff --git a/src/c#/GeneralUpdate.Core/FileSystem/IBlackListMatcher.cs b/src/c#/GeneralUpdate.Core/FileSystem/IBlackListMatcher.cs
new file mode 100644
index 00000000..3be745f4
--- /dev/null
+++ b/src/c#/GeneralUpdate.Core/FileSystem/IBlackListMatcher.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace GeneralUpdate.Core.FileSystem;
+
+/// Matches files/directories against a blacklist configuration.
+public interface IBlackListMatcher
+{
+ bool IsBlacklisted(string relativeFilePath);
+ bool IsBlacklistedFormat(string extension);
+ bool ShouldSkipDirectory(string directoryName);
+}
diff --git a/src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs b/src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs
index bb8b04a0..7afe6771 100644
--- a/src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs
+++ b/src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs
@@ -14,7 +14,7 @@ public sealed class StorageManager
private long _fileCount = 0;
public const string DirectoryName = "app-";
- /// Optional blacklist matcher. When set, takes precedence over BlackListManager.
+ /// Optional blacklist matcher. Must be set before any file operation.
public static IBlackListMatcher? BlackListMatcher { get; set; }
private ComparisonResult ComparisonResult { get; set; }
@@ -267,9 +267,7 @@ private IEnumerable ReadFileNode(string path, string rootPath = null)
foreach (var subPath in Directory.EnumerateFiles(path))
{
-#pragma warning disable CS0618 // Obsolete fallback
- if ((BlackListMatcher ?? BlackListManager.Instance).IsBlacklisted(subPath)) continue;
-#pragma warning restore CS0618
+ if (BlackListMatcher != null && BlackListMatcher.IsBlacklisted(subPath)) continue;
var hashAlgorithm = new Sha256HashAlgorithm();
var hash = hashAlgorithm.ComputeHash(subPath);
@@ -288,9 +286,7 @@ private IEnumerable ReadFileNode(string path, string rootPath = null)
foreach (var subPath in Directory.EnumerateDirectories(path))
{
-#pragma warning disable CS0618 // Obsolete fallback
- if ((BlackListMatcher ?? BlackListManager.Instance).ShouldSkipDirectory(subPath)) continue;
-#pragma warning restore CS0618
+ if (BlackListMatcher != null && BlackListMatcher.ShouldSkipDirectory(subPath)) continue;
resultFiles.AddRange(ReadFileNode(subPath, rootPath));
}
diff --git a/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj b/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj
index cf93fe4e..1c1424af 100644
--- a/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj
+++ b/src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj
@@ -4,7 +4,7 @@
enable
GeneralUpdate.Core
JusterZhu
- GeneralUpdate unified core — client and upgrade bootstrap, download, pipeline, strategies, utilities.
+ GeneralUpdate unified core �?client and upgrade bootstrap, download, pipeline, strategies, utilities.
Copyright 2020-2026 JusterZhu
https://github.com/GeneralLibrary/GeneralUpdate
https://github.com/GeneralLibrary/GeneralUpdate
@@ -30,6 +30,7 @@
+
diff --git a/src/c#/GeneralUpdate.Core/Ipc/IProcessInfoProvider.cs b/src/c#/GeneralUpdate.Core/Ipc/IProcessInfoProvider.cs
index bc94643a..26ddfd62 100644
--- a/src/c#/GeneralUpdate.Core/Ipc/IProcessInfoProvider.cs
+++ b/src/c#/GeneralUpdate.Core/Ipc/IProcessInfoProvider.cs
@@ -20,6 +20,7 @@ public interface IProcessInfoProvider
///
/// AES-encrypted temporary file IPC — simplest, most reliable cross-platform approach.
/// File lives in %TEMP%/GeneralUpdate/ipc/ with a random name, auto-deleted after read.
+/// Encryption is delegated to .
///
public class EncryptedFileProcessInfoProvider : IProcessInfoProvider
{
@@ -47,11 +48,7 @@ public void Send(ProcessInfo info)
{
var json = JsonSerializer.Serialize(info, ProcessInfoJsonContext.Default.ProcessInfo);
var plainBytes = Encoding.UTF8.GetBytes(json);
- using var aes = Aes.Create();
- aes.Key = Key; aes.IV = IV;
- using var enc = aes.CreateEncryptor();
- var cipher = enc.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
- File.WriteAllBytes(_filePath, cipher);
+ IpcEncryption.EncryptToFile(plainBytes, _filePath, Key, IV);
}
public Task ReceiveAsync(CancellationToken token = default)
@@ -60,17 +57,9 @@ public void Send(ProcessInfo info)
/// Synchronous receive — reads and deletes the encrypted file.
public ProcessInfo? Receive()
{
- if (!File.Exists(_filePath)) return null;
- try
- {
- var cipher = File.ReadAllBytes(_filePath);
- using var aes = Aes.Create();
- aes.Key = Key; aes.IV = IV;
- using var dec = aes.CreateDecryptor();
- var plain = dec.TransformFinalBlock(cipher, 0, cipher.Length);
- var json = Encoding.UTF8.GetString(plain);
- return JsonSerializer.Deserialize(json, ProcessInfoJsonContext.Default.ProcessInfo);
- }
- finally { try { File.Delete(_filePath); } catch { } }
+ var plain = IpcEncryption.DecryptFromFile(_filePath, Key, IV);
+ if (plain == null) return null;
+ var json = Encoding.UTF8.GetString(plain);
+ return JsonSerializer.Deserialize(json, ProcessInfoJsonContext.Default.ProcessInfo);
}
}
diff --git a/src/c#/GeneralUpdate.Core/Ipc/IpcEncryption.cs b/src/c#/GeneralUpdate.Core/Ipc/IpcEncryption.cs
new file mode 100644
index 00000000..2b3d90ff
--- /dev/null
+++ b/src/c#/GeneralUpdate.Core/Ipc/IpcEncryption.cs
@@ -0,0 +1,49 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace GeneralUpdate.Core.Ipc;
+
+///
+/// Shared AES encryption utilities for IPC.
+/// Used by both (key-value IPC) and
+/// (structured ProcessInfo IPC).
+///
+public static class IpcEncryption
+{
+ ///
+ /// AES-CBC encrypt and write to .
+ ///
+ public static void EncryptToFile(byte[] plainBytes, string filePath, byte[] key, byte[] iv)
+ {
+ using var aes = Aes.Create();
+ aes.Key = key;
+ aes.IV = iv;
+ using var encryptor = aes.CreateEncryptor();
+ var cipher = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
+ File.WriteAllBytes(filePath, cipher);
+ }
+
+ ///
+ /// Read, AES-CBC decrypt, and auto-delete the file at .
+ /// Returns null if the file does not exist.
+ ///
+ public static byte[]? DecryptFromFile(string filePath, byte[] key, byte[] iv)
+ {
+ if (!File.Exists(filePath)) return null;
+
+ try
+ {
+ var cipher = File.ReadAllBytes(filePath);
+ using var aes = Aes.Create();
+ aes.Key = key;
+ aes.IV = iv;
+ using var decryptor = aes.CreateDecryptor();
+ return decryptor.TransformFinalBlock(cipher, 0, cipher.Length);
+ }
+ finally
+ {
+ try { File.Delete(filePath); } catch { /* best-effort cleanup */ }
+ }
+ }
+}
diff --git a/src/c#/GeneralUpdate.Core/Network/HttpClientProvider.cs b/src/c#/GeneralUpdate.Core/Network/HttpClientProvider.cs
new file mode 100644
index 00000000..67071a5c
--- /dev/null
+++ b/src/c#/GeneralUpdate.Core/Network/HttpClientProvider.cs
@@ -0,0 +1,16 @@
+using System.Net.Http;
+
+namespace GeneralUpdate.Core.Network;
+
+///
+/// Provides a shared static instance.
+/// Reusing a single HttpClient prevents socket exhaustion.
+/// Do NOT dispose clients obtained from here.
+///
+public static class HttpClientProvider
+{
+ private static readonly HttpClient _shared = new();
+
+ /// Shared instance. Do NOT dispose.
+ public static HttpClient Shared => _shared;
+}
diff --git a/src/c#/GeneralUpdate.Core/Silent/SilentPollOrchestrator.cs b/src/c#/GeneralUpdate.Core/Silent/SilentPollOrchestrator.cs
index bfddc675..3ec758bd 100644
--- a/src/c#/GeneralUpdate.Core/Silent/SilentPollOrchestrator.cs
+++ b/src/c#/GeneralUpdate.Core/Silent/SilentPollOrchestrator.cs
@@ -23,7 +23,7 @@
namespace GeneralUpdate.Core.Silent;
///
-/// Silent update poll orchestrator — periodically checks for updates,
+/// Silent update poll orchestrator �?periodically checks for updates,
/// downloads them in the background, and optionally auto-installs.
/// Replaces the legacy SilentUpdateMode class.
///
@@ -126,7 +126,7 @@ private async Task PrepareUpdateIfNeededAsync(CancellationToken token)
return;
}
- // ═══ Hooks: allow cancellation before starting update ═══
+ // ══�?Hooks: allow cancellation before starting update ══�?
var updateCtx = new UpdateContext(
_configInfo.MainAppName ?? _configInfo.AppName,
_configInfo.InstallPath,
@@ -173,7 +173,7 @@ private async Task PrepareUpdateIfNeededAsync(CancellationToken token)
_configInfo.SkipDirectorys ?? BlackListDefaults.DefaultSkipDirectories);
_configInfo.ProcessInfo = JsonSerializer.Serialize(_preparedProcessInfo, ProcessInfoJsonContext.Default.ProcessInfo);
- // ═══ Reporter: update started ═══
+ // ══�?Reporter: update started ══�?
var startTime = DateTimeOffset.UtcNow;
if (_reporter != null)
{
@@ -188,7 +188,7 @@ await _reporter.ReportAsync(new UpdateReport(
// Download using new orchestrator
GeneralTracer.Info($"SilentPollOrchestrator: downloading {plan.Assets.Count} asset(s).");
- var httpClient = new System.Net.Http.HttpClient();
+ var httpClient = GeneralUpdate.Core.Network.HttpClientProvider.Shared;
var downloadSuccessCount = 0;
var downloadFailedCount = 0;
var downloadTotalBytes = 0L;
@@ -203,7 +203,7 @@ await _reporter.ReportAsync(new UpdateReport(
downloadElapsed = report.TotalDuration;
GeneralTracer.Info($"SilentPollOrchestrator: download complete. Success={downloadSuccessCount}, Failed={downloadFailedCount}");
- // ═══ Hooks + Reporter: download completed ═══
+ // ══�?Hooks + Reporter: download completed ══�?
if (_hooks != null)
{
try
@@ -256,7 +256,7 @@ await _reporter.ReportAsync(new UpdateReport(
}
return;
}
- finally { httpClient.Dispose(); }
+ finally { }
// Execute pipeline
try
@@ -268,7 +268,7 @@ await _reporter.ReportAsync(new UpdateReport(
GeneralTracer.Info("SilentPollOrchestrator: update prepared.");
Interlocked.Exchange(ref _prepared, 1);
- // ═══ Hooks + Reporter: update applied ═══
+ // ══�?Hooks + Reporter: update applied ══�?
if (_hooks != null)
{
try { await _hooks.OnAfterUpdateAsync(updateCtx).ConfigureAwait(false); }
@@ -318,7 +318,7 @@ private void OnProcessExit(object? sender, EventArgs e)
{
var updaterPath = Path.Combine(_configInfo.InstallPath, _configInfo.AppName);
- // Start the upgrade process first — it will call ReceiveAsync in its constructor.
+ // Start the upgrade process first �?it will call ReceiveAsync in its constructor.
// We then call SendAsync which creates the NamedPipe server; the upgrade's
// client connects to it. Auto-fallback (SharedMemory > EncryptedFile) handles
// timing gaps where the named pipe handshake doesn't complete in time.
diff --git a/src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs
index f484273c..31ec965b 100644
--- a/src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs
+++ b/src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs
@@ -89,7 +89,7 @@ public ClientUpdateStrategy UseUpdatePrecheck(Func fu
private async Task ExecuteWorkflowAsync()
{
- // Standard mode — silent mode is handled by GeneralUpdateBootstrap.LaunchSilentAsync().
+ // Standard mode �?silent mode is handled by GeneralUpdateBootstrap.LaunchSilentAsync().
// Runtime options (Encoding, Format, DownloadTimeOut, etc.) are already
// populated on _configInfo by Bootstrap.ApplyRuntimeOptions().
await ExecuteStandardWorkflowAsync();
@@ -185,7 +185,7 @@ private async Task ExecuteStandardWorkflowAsync()
new EncryptedFileProcessInfoProvider().Send(processInfo);
GeneralTracer.Info("ClientUpdateStrategy: ProcessInfo sent via encrypted file IPC.");
- // Backup — conditionally skipped when BackupEnabled is false
+ // Backup �?conditionally skipped when BackupEnabled is false
if (_configInfo.BackupEnabled != false)
{
Backup();
@@ -197,7 +197,7 @@ private async Task ExecuteStandardWorkflowAsync()
_osStrategy!.Create(_configInfo);
- // Download via orchestrator — wired with options from GlobalConfigInfo
+ // Download via orchestrator �?wired with options from GlobalConfigInfo
var orchOptions = Download.Models.DownloadOrchestratorOptions.From(_configInfo);
GeneralTracer.Info($"ClientUpdateStrategy: downloading {downloadPlan.Assets.Count} asset(s).");
if (_orchestrator != null)
@@ -206,13 +206,13 @@ private async Task ExecuteStandardWorkflowAsync()
}
else
{
- var httpClient = new System.Net.Http.HttpClient();
+ var httpClient = GeneralUpdate.Core.Network.HttpClientProvider.Shared;
try
{
var orchestrator = new Download.Orchestrators.DefaultDownloadOrchestrator(httpClient, orchOptions);
await orchestrator.ExecuteAsync(downloadPlan, _configInfo.TempPath).ConfigureAwait(false);
}
- finally { httpClient.Dispose(); }
+ finally { }
}
await SafeReportDownloadCompletedAsync(hooksCtx).ConfigureAwait(false);
diff --git a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs
index 51a2ff51..89496404 100644
--- a/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs
+++ b/src/c#/GeneralUpdate.Core/Strategy/OSSUpdateStrategy.cs
@@ -6,6 +6,7 @@
using System.Net.Http;
using System.Text;
using System.Text.Json;
+using System.Threading;
using System.Threading.Tasks;
using GeneralUpdate.Core.Compress;
using GeneralUpdate.Core.Configuration;
@@ -16,11 +17,11 @@
namespace GeneralUpdate.Core.Strategy;
///
-/// OSS (Object Storage Service) update strategy — client/upgrade split via AppType.
+/// OSS (Object Storage Service) update strategy �?client/upgrade split via AppType.
///
-/// - — downloads version config, checks for updates,
+///
- �?downloads version config, checks for updates,
/// starts the upgrade process, and exits.
-/// - — reads version config, downloads packages from OSS,
+///
- �?reads version config, downloads packages from OSS,
/// decompresses them, starts the main app, and exits.
///
///
@@ -51,7 +52,7 @@ public async Task ExecuteAsync()
if (_configInfo == null)
throw new InvalidOperationException("OSSUpdateStrategy not configured. Call Create() first.");
- // Dispatch by role — no env-var detection needed.
+ // Dispatch by role �?no env-var detection needed.
if (_role == AppType.OSSUpgrade)
{
await ExecuteUpgradeAsync();
@@ -217,7 +218,7 @@ private static async Task DownloadVersionConfig(string url, string path)
File.SetAttributes(path, FileAttributes.Normal);
File.Delete(path);
}
- using var httpClient = new HttpClient();
+ using var httpClient = GeneralUpdate.Core.Network.HttpClientProvider.Shared;
var bytes = await httpClient.GetByteArrayAsync(url).ConfigureAwait(false);
File.WriteAllBytes(path, bytes);
}
@@ -240,12 +241,11 @@ private async Task DownloadAssetsAsync(List assets)
}
else
{
- using var httpClient = new HttpClient
- {
- Timeout = TimeSpan.FromSeconds(_configInfo?.DownloadTimeOut > 0 ? _configInfo!.DownloadTimeOut : DefaultTimeOut)
- };
+ using var httpClient = GeneralUpdate.Core.Network.HttpClientProvider.Shared;
+ using var cts = new CancellationTokenSource(
+ TimeSpan.FromSeconds(_configInfo?.DownloadTimeOut > 0 ? _configInfo!.DownloadTimeOut : DefaultTimeOut));
var orchestrator = new DefaultDownloadOrchestrator(httpClient);
- await orchestrator.ExecuteAsync(plan, _appPath).ConfigureAwait(false);
+ await orchestrator.ExecuteAsync(plan, _appPath, token: cts.Token).ConfigureAwait(false);
}
}