Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 10 additions & 16 deletions src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
Expand Down Expand Up @@ -96,8 +96,7 @@ private async Task<GeneralUpdateBootstrap> LaunchWithStrategy(IStrategy roleStra
// Resolve DownloadSource from extension registry (Hub, custom, etc.)
var resolvedSource = ResolveExtension<Download.Abstractions.IDownloadSource>();

// Inject SignalR Hub download source if configured (not available in AOT)
#if !AOT
// Inject SignalR Hub download source if configured
if (resolvedSource == null)
{
var hubConfig = GetOption(UpdateOptions.Hub);
Expand All @@ -110,7 +109,6 @@ private async Task<GeneralUpdateBootstrap> LaunchWithStrategy(IStrategy roleStra
GeneralTracer.Info("GeneralUpdateBootstrap: HubDownloadSource started from HubConfig.");
}
}
#endif
clientStrat.DownloadSource = resolvedSource;
if (_updatePrecheck != null)
clientStrat.UseUpdatePrecheck(_updatePrecheck);
Expand All @@ -128,6 +126,14 @@ private async Task<GeneralUpdateBootstrap> LaunchWithStrategy(IStrategy roleStra
}

roleStrategy.Create(_configInfo);

// Check custom skip condition before executing update
if (_customSkipOption?.Invoke() == true)
{
GeneralTracer.Info("GeneralUpdateBootstrap: update skipped by custom skip option.");
return this;
}

await roleStrategy.ExecuteAsync();
}
catch (Exception ex)
Expand Down Expand Up @@ -316,11 +322,9 @@ private void ApplyRuntimeOptions()
/// Silent update mode — starts a background poll loop and returns immediately.
/// The orchestrator checks for updates periodically and prepares them.
/// When the host process exits, the prepared update is applied.
/// Not available in AOT builds (SignalR dependency).
/// </summary>
private async Task LaunchSilentAsync()
{
#if !AOT
GeneralTracer.Info("GeneralUpdateBootstrap: starting silent update mode.");

var pollMinutes = GetOption(UpdateOptions.SilentPollIntervalMinutes);
Expand All @@ -341,10 +345,6 @@ private async Task LaunchSilentAsync()

await orchestrator.StartAsync().ConfigureAwait(false);
GeneralTracer.Info("GeneralUpdateBootstrap: silent update mode started, returning to caller.");
#else
GeneralTracer.Warn("GeneralUpdateBootstrap: silent update not available in AOT builds.");
await Task.CompletedTask;
#endif
}

private void InitBlackList()
Expand Down Expand Up @@ -396,12 +396,6 @@ private static bool IsOssUpgrade(string clientVersion, string serverVersion)
// Strategy & Events
// ════════════════════════════════════════════════════════════════

protected override GeneralUpdateBootstrap StrategyFactory()
=> throw new NotImplementedException("Role strategies handle this.");

protected override Task ExecuteStrategyAsync() => throw new NotImplementedException();
protected override void ExecuteStrategy() => throw new NotImplementedException();

private GeneralUpdateBootstrap AddListener<TArgs>(Action<object, TArgs> action) where TArgs : EventArgs
{
if (action is null) throw new ArgumentNullException(nameof(action));
Expand Down
12 changes: 5 additions & 7 deletions src/c#/GeneralUpdate.Core/Compress/CompressProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@ namespace GeneralUpdate.Core.Compress;

public class CompressProvider
{
private static ICompressionStrategy _compressionStrategy;

private CompressProvider() { }

public static void Compress(string compressType,string sourcePath, string destinationPath, bool includeRootDirectory, Encoding encoding)
public static void Compress(string compressType, string sourcePath, string destinationPath, bool includeRootDirectory, Encoding encoding)
{
_compressionStrategy = GetCompressionStrategy(compressType);
_compressionStrategy.Compress(sourcePath, destinationPath, includeRootDirectory, encoding);
var strategy = GetCompressionStrategy(compressType);
strategy.Compress(sourcePath, destinationPath, includeRootDirectory, encoding);
}

public static void Decompress(string compressType, string archivePath, string destinationPath, Encoding encoding)
{
_compressionStrategy = GetCompressionStrategy(compressType);
_compressionStrategy.Decompress(archivePath, destinationPath, encoding);
var strategy = GetCompressionStrategy(compressType);
strategy.Decompress(archivePath, destinationPath, encoding);
}

private static ICompressionStrategy GetCompressionStrategy(string compressType) => compressType switch
Expand Down
3 changes: 0 additions & 3 deletions src/c#/GeneralUpdate.Core/Configuration/AbstractBootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ protected internal AbstractBootstrap()
}

public abstract Task<TBootstrap> LaunchAsync();
protected abstract void ExecuteStrategy();
protected abstract Task ExecuteStrategyAsync();
protected abstract TBootstrap StrategyFactory();

public TBootstrap Option<T>(UpdateOption<T> option, T value)
{
Expand Down
9 changes: 0 additions & 9 deletions src/c#/GeneralUpdate.Core/Configuration/ConfiginfoBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public class ConfiginfoBuilder
private string _updateLogUrl;
private string _reportUrl;
private string _bowl;
private string _script;
private string _driverDirectory;
private List<string> _blackFiles;
private List<string> _blackFormats;
Expand Down Expand Up @@ -313,14 +312,6 @@ public ConfiginfoBuilder SetBowl(string bowl)
/// <summary>
/// Sets the shell script content.
/// </summary>
/// <param name="script">Shell script content used to grant file permissions on Linux/Unix systems.</param>
/// <returns>The current ConfiginfoBuilder instance for method chaining.</returns>
public ConfiginfoBuilder SetScript(string script)
{
_script = script;
return this;
}

/// <summary>
/// Sets the driver directory.
/// </summary>
Expand Down
2 changes: 0 additions & 2 deletions src/c#/GeneralUpdate.Core/Configuration/GlobalConfigInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ public class GlobalConfigInfo : BaseConfigInfo
/// Directory path containing driver files for update.
/// Used when DriveEnabled is true to locate driver files for installation.
/// </summary>
public string DriverDirectory { get; set; }

/// <summary>
/// Indicates whether differential patch update is enabled.
/// Computed from UpdateOption.Patch or defaults to true.
Expand Down
8 changes: 6 additions & 2 deletions src/c#/GeneralUpdate.Core/Configuration/ObjectTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ namespace GeneralUpdate.Core.Configuration;

public sealed class ObjectTranslator
{
public static string GetPacketHash(object version) =>
!GeneralTracer.IsTracingEnabled() ? string.Empty : $"[PacketHash]:{(version as VersionInfo).Hash} ";
public static string GetPacketHash(object version)
{
if (!GeneralTracer.IsTracingEnabled()) return string.Empty;
if (version is VersionInfo vi) return $"[PacketHash]:{vi.Hash} ";
return string.Empty;
}
}
1 change: 0 additions & 1 deletion src/c#/GeneralUpdate.Core/Configuration/ProcessInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public ProcessInfo() { }
/// <param name="bowl">The process name to terminate before updating</param>
/// <param name="scheme">The URL scheme for update requests</param>
/// <param name="token">The authentication token</param>
/// <param name="script">The Linux permission script</param>
/// <param name="driverDirectory">The directory path containing driver files</param>
/// <param name="blackFileFormats">List of file format extensions to skip</param>
/// <param name="blackFiles">List of specific files to skip</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace GeneralUpdate.Core.Download.Executors;

/// <summary>
/// HTTP-based download executor with optional Range/resume support.
/// Uses the shared HttpClient from VersionService for consistent SSL/auth handling.
/// </summary>
public class HttpDownloadExecutor : IDownloadExecutor
{
Expand All @@ -33,7 +32,6 @@ public async Task<DownloadResult> ExecuteAsync(
{
var sw = Stopwatch.StartNew();
int retries = 0;
long totalBytes = -1;
long existingBytes = 0;

// Check for existing partial file (resume support; skip when disabled)
Expand All @@ -45,8 +43,6 @@ public async Task<DownloadResult> ExecuteAsync(
try
{
using var request = new HttpRequestMessage(HttpMethod.Get, url);

// Request resume from existing position (skip when resume is disabled)
if (_enableResume && existingBytes > 0)
request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(existingBytes, null);

Expand All @@ -57,56 +53,67 @@ public async Task<DownloadResult> ExecuteAsync(
request, HttpCompletionOption.ResponseHeadersRead, cts.Token)
.ConfigureAwait(false);

// If server doesn't support Range, discard partial file
if (_enableResume && existingBytes > 0 && response.StatusCode != System.Net.HttpStatusCode.PartialContent)
{
existingBytes = 0;
File.Delete(destPath);
}

response.EnsureSuccessStatusCode();
totalBytes = response.Content.Headers.ContentLength ?? -1;
var totalBytes = response.Content.Headers.ContentLength ?? -1;

// Append or create file
var mode = existingBytes > 0 ? FileMode.Append : FileMode.Create;
using var fs = new FileStream(destPath, mode, FileAccess.Write, FileShare.None);
using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);

var buffer = new byte[8192];
long downloaded = existingBytes;
int read;
long lastReport = 0;

while ((read = await stream.ReadAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false)) > 0)
{
await fs.WriteAsync(buffer, 0, read, token).ConfigureAwait(false);
downloaded += read;

// Report progress every ~250ms
var now = sw.ElapsedMilliseconds;
if (now - lastReport >= 250 || downloaded == totalBytes + existingBytes)
{
lastReport = now;
var pct = totalBytes > 0 ? (double)downloaded / (totalBytes + existingBytes) * 100 : -1;
progress?.Report(new DownloadProgress(
Path.GetFileName(destPath), downloaded,
totalBytes > 0 ? totalBytes + existingBytes : null,
pct, DownloadStatus.Downloading));
}
}
var (downloaded, elapsed) = await StreamDownloadAsync(stream, fs, totalBytes, existingBytes,
destPath, progress, sw, token).ConfigureAwait(false);

sw.Stop();
progress?.Report(new DownloadProgress(
Path.GetFileName(destPath), downloaded,
totalBytes > 0 ? totalBytes + existingBytes : null,
100, DownloadStatus.Completed));

return new DownloadResult(url, destPath, downloaded, sw.Elapsed, retries, true, null);
return new DownloadResult(url, destPath, downloaded, elapsed, retries, true, null);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
sw.Stop();
return new DownloadResult(url, null, existingBytes, sw.Elapsed, retries, false, ex.Message);
}
}

/// <summary>
/// Shared download loop: reads from source stream, writes to dest, reports progress.
/// Used by both HTTP and OSS executors to avoid duplicated buffer/read/write/progress logic.
/// </summary>
internal static async Task<(long Downloaded, TimeSpan Elapsed)> StreamDownloadAsync(
Stream source, Stream dest, long totalBytes, long existingBytes,
string destPath, IProgress<DownloadProgress>? progress, Stopwatch sw, CancellationToken token)
{
var buffer = new byte[8192];
long downloaded = existingBytes;
long lastReport = 0;
int read;

while ((read = await source.ReadAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false)) > 0)
{
await dest.WriteAsync(buffer, 0, read, token).ConfigureAwait(false);
downloaded += read;

var now = sw.ElapsedMilliseconds;
if (now - lastReport >= 250 || downloaded == totalBytes + existingBytes)
{
lastReport = now;
var pct = totalBytes > 0 ? (double)downloaded / (totalBytes + existingBytes) * 100 : -1;
progress?.Report(new DownloadProgress(
Path.GetFileName(destPath), downloaded,
totalBytes > 0 ? totalBytes + existingBytes : null,
pct, DownloadStatus.Downloading));
}
}

sw.Stop();
return (downloaded, sw.Elapsed);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public async Task<DownloadResult> ExecuteAsync(
CancellationToken token = default)
{
var sw = System.Diagnostics.Stopwatch.StartNew();
long lastReport = 0;
try
{
using var response = await _client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token)
Expand All @@ -32,25 +31,13 @@ public async Task<DownloadResult> ExecuteAsync(
Directory.CreateDirectory(Path.GetDirectoryName(destPath)!);
using var fs = new FileStream(destPath, FileMode.Create);
using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var buffer = new byte[8192];
long downloaded = 0;
int read;
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false)) > 0)
{
await fs.WriteAsync(buffer, 0, read, token).ConfigureAwait(false);
downloaded += read;
var now = sw.ElapsedMilliseconds;
if (now - lastReport >= 250 || downloaded == total)
{
lastReport = now;
var pct = total > 0 ? (double)downloaded / total * 100 : -1;
progress?.Report(new DownloadProgress(
Path.GetFileName(destPath), downloaded, total > 0 ? total : null, pct, DownloadStatus.Downloading));
}
}
sw.Stop();
progress?.Report(new DownloadProgress(Path.GetFileName(destPath), downloaded, total > 0 ? total : null, 100, DownloadStatus.Completed));
return new DownloadResult(url, destPath, downloaded, sw.Elapsed, 0, true, null);

var (downloaded, elapsed) = await HttpDownloadExecutor.StreamDownloadAsync(
stream, fs, total, 0, destPath, progress, sw, token).ConfigureAwait(false);

progress?.Report(new DownloadProgress(
Path.GetFileName(destPath), downloaded, total > 0 ? total : null, 100, DownloadStatus.Completed));
return new DownloadResult(url, destPath, downloaded, elapsed, 0, true, null);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

namespace GeneralUpdate.Core.Download
{
public class MultiDownloadCompletedEventArgs(object version, bool isComplated) : EventArgs
public class MultiDownloadCompletedEventArgs(object version, bool isCompleted) : EventArgs
{
public object Version { get; private set; } = version;

public bool IsComplated { get; private set; } = isComplated;
public bool IsCompleted { get; private set; } = isCompleted;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using GeneralUpdate.Core.Download.Policy;
using GeneralUpdate.Core.Download.Models;
using GeneralUpdate.Core.Download.Pipeline;
using GeneralUpdate.Core.Download.Progress;

namespace GeneralUpdate.Core.Download.Orchestrators;

Expand Down Expand Up @@ -119,6 +120,14 @@ public async Task<DownloadReport> ExecuteAsync(
await Task.WhenAll(tasks).ConfigureAwait(false);
sw.Stop();

// Dispatch all-completed event ONCE after all assets finish (only failed results)
var failedDetails = results.Where(r => !r.Success)
.Select(r => ((object)r.Url, r.ErrorMessage ?? "failed")).ToList();
DownloadProgressReporter.DispatchAllCompleted(
this,
results.All(r => r.Success),
failedDetails);

return new DownloadReport(
results,
totalBytes,
Expand Down
Loading
Loading