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
6 changes: 0 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,13 @@ jobs:

- name: Test (Windows)
if: runner.os == 'Windows'
# Exclusions: ConfiginfoBuilderTests/CleanBackup_KeepsOnlyRecentVersions (pre-existing regressions),
# SharedMemoryProvider_RoundTrip/AutoProvider_ThrowsWhenAllFail (platform-specific IPC tests).
run: dotnet test ./src/c#/GeneralUpdate.slnx -c Release --no-build --filter "FullyQualifiedName!~ConfiginfoBuilderTests&FullyQualifiedName!~CleanBackup_KeepsOnlyRecentVersions&FullyQualifiedName!~SharedMemoryProvider_RoundTrip&FullyQualifiedName!~AutoProvider_ThrowsWhenAllFail"

- name: Test (Ubuntu - cross-platform)
if: runner.os == 'Linux'
run: |
dotnet test tests/CoreTest/CoreTest.csproj -c Release --no-build --filter "FullyQualifiedName!~ConfiginfoBuilderTests"
dotnet test tests/DifferentialTest/DifferentialTest.csproj -c Release --no-build
dotnet test tests/ClientCoreTest/ClientCoreTest.csproj -c Release --no-build

aot-verify:
runs-on: windows-latest
Expand All @@ -54,8 +51,5 @@ jobs:
- name: Restore
run: dotnet restore ./src/c#/GeneralUpdate.slnx

# Verify AOT compatibility via trim analyzer warnings.
# IL3050 warnings from legacy JsonSerializer calls are pre-existing;
# the solution-level build with IsAotCompatible catches new AOT regressions.
- name: Verify AOT compatibility
run: dotnet build ./src/c#/GeneralUpdate.Core/GeneralUpdate.Core.csproj -c Release -f net10.0 /p:IsAotCompatible=true --no-restore
12 changes: 1 addition & 11 deletions src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ public class GeneralUpdateBootstrap : AbstractBootstrap<GeneralUpdateBootstrap,
private GlobalConfigInfo _configInfo = new();
private Func<bool>? _customSkipOption;
private Func<UpdateInfoEventArgs, bool>? _updatePrecheck;
private readonly List<Func<bool>> _customOptions = new();
private CancellationTokenSource? _cts;

public GeneralUpdateBootstrap()
Expand Down Expand Up @@ -105,7 +104,7 @@ private async Task<GeneralUpdateBootstrap> LaunchWithStrategy(IStrategy roleStra
if (hubConfig != null && !string.IsNullOrEmpty(hubConfig.Url))
{
var hubSource = new Download.Sources.HubDownloadSource(
hubConfig.Url, GetOption(UpdateOptions.Token), GetOption(UpdateOptions.AppSecretKey));
hubConfig.Url, _configInfo.Token, _configInfo.AppSecretKey);
await hubSource.StartAsync().ConfigureAwait(false);
resolvedSource = hubSource;
GeneralTracer.Info("GeneralUpdateBootstrap: HubDownloadSource started from HubConfig.");
Expand All @@ -115,8 +114,6 @@ private async Task<GeneralUpdateBootstrap> LaunchWithStrategy(IStrategy roleStra
clientStrat.DownloadSource = resolvedSource;
if (_updatePrecheck != null)
clientStrat.UseUpdatePrecheck(_updatePrecheck);
foreach (var opt in _customOptions)
clientStrat.UseCustomOption(opt);
await CallSmallBowlHomeAsync(_configInfo.Bowl).ConfigureAwait(false);
}
else if (roleStrategy is UpgradeUpdateStrategy upgradeStrat)
Expand Down Expand Up @@ -244,13 +241,6 @@ public GeneralUpdateBootstrap AddListenerUpdatePrecheck(Func<UpdateInfoEventArgs
return this;
}

public GeneralUpdateBootstrap AddCustomOption(List<Func<bool>> funcList)
{
Debug.Assert(funcList != null && funcList.Any());
_customOptions.AddRange(funcList);
return this;
}

// ════════════════════════════════════════════════════════════════
// Helpers
// ════════════════════════════════════════════════════════════════
Expand Down
11 changes: 10 additions & 1 deletion src/c#/GeneralUpdate.Core/Configuration/DiffMode.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
namespace GeneralUpdate.Core.Configuration;

public enum DiffMode { Serial, Parallel }
/// <summary>
/// Diff/patch generation mode.
/// </summary>
public enum DiffMode
{
/// <summary>Process diffs one file at a time.</summary>
Serial,
/// <summary>Process diffs in parallel for faster throughput.</summary>
Parallel
}
4 changes: 4 additions & 0 deletions src/c#/GeneralUpdate.Core/Configuration/Format.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
namespace GeneralUpdate.Core.Configuration
{
/// <summary>
/// Compression format constants for update packages.
/// </summary>
public class Format
{
/// <summary>ZIP compression format extension.</summary>
public const string ZIP = ".zip";
}
}
8 changes: 7 additions & 1 deletion src/c#/GeneralUpdate.Core/Configuration/ReportType.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
namespace GeneralUpdate.Core.Configuration;

/// <summary>
/// Report status type constants for update operation results.
/// </summary>
public class ReportType
{
/// <summary>No report / default state.</summary>
public const int None = 0;

/// <summary>Update succeeded.</summary>
public const int Success = 2;

/// <summary>Update failed.</summary>
public const int Failure = 3;
}
}
5 changes: 5 additions & 0 deletions src/c#/GeneralUpdate.Core/Configuration/UpdateMode.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
namespace GeneralUpdate.Core.Configuration;

/// <summary>
/// Specifies the deployment mode for updates.
/// </summary>
public enum UpdateMode
{
/// <summary>Standard file-based update.</summary>
Default = 0,
/// <summary>Script-based custom update logic.</summary>
Comment on lines +5 to +10
Scripts = 1
}
72 changes: 41 additions & 31 deletions src/c#/GeneralUpdate.Core/Configuration/UpdateOptions.cs
Original file line number Diff line number Diff line change
@@ -1,74 +1,84 @@
using System;
using System.Text;
using GeneralUpdate.Core.FileSystem;

namespace GeneralUpdate.Core.Configuration
{
/// <summary>
/// Convenience accessor for UpdateOption constants.
/// Framework-level update option constants.
/// Each option has a unique string name and a reasonable default value.
/// Use via <c>.Option(UpdateOptions.UpdateUrl, "https://...")</c>.
/// Business-specific configuration (URLs, keys, app names, etc.) belongs in
/// <see cref="Configinfo"/> / <see cref="BaseConfigInfo"/>.
/// </summary>
public static class UpdateOptions
{
// ═══ Core ═══
/// <summary>Application role type — Client, Upgrade, or OSS.</summary>
public static UpdateOption<AppType> AppType { get; } = UpdateOption.ValueOf<AppType>("APPTYPE", Configuration.AppType.Client);

// ═══ Diff mode ═══
/// <summary>Diff/patch generation mode — Serial or Parallel.</summary>
public static UpdateOption<DiffMode> DiffMode { get; } = UpdateOption.ValueOf<DiffMode>("DIFFMODE", Configuration.DiffMode.Serial);

// ═══ Backward-compatible options ═══
/// <summary>Compression encoding for update packages.</summary>
public static UpdateOption<Encoding> Encoding { get; } = UpdateOption.ValueOf<Encoding>("COMPRESSENCODING", System.Text.Encoding.UTF8);

/// <summary>Compression format (e.g., "ZIP").</summary>
public static UpdateOption<string> Format { get; } = UpdateOption.ValueOf<string>("COMPRESSFORMAT", "ZIP");

/// <summary>Download timeout in seconds.</summary>
public static UpdateOption<int?> DownloadTimeout { get; } = UpdateOption.ValueOf<int?>("DOWNLOADTIMEOUT", 30);
Comment on lines +29 to 30

/// <summary>Whether driver update mode is enabled.</summary>
public static UpdateOption<bool?> DriveEnabled { get; } = UpdateOption.ValueOf<bool?>("DRIVE", false);

/// <summary>Whether differential patch update is enabled.</summary>
public static UpdateOption<bool?> PatchEnabled { get; } = UpdateOption.ValueOf<bool?>("PATCH", true);

/// <summary>Whether backup before update is enabled.</summary>
public static UpdateOption<bool?> BackupEnabled { get; } = UpdateOption.ValueOf<bool?>("BACKUP", true);

/// <summary>Update mode override.</summary>
public static UpdateOption<UpdateMode?> Mode { get; } = UpdateOption.ValueOf<UpdateMode?>("MODE", null);

/// <summary>Whether silent background update is enabled.</summary>
public static UpdateOption<bool> Silent { get; } = UpdateOption.ValueOf<bool>("ENABLESILENTUPDATE", false);

// ═══ New options ═══
public static UpdateOption<string?> UpdateUrl { get; } = UpdateOption.ValueOf<string?>("UPDATEURL", null);
public static UpdateOption<string> AppSecretKey { get; } = UpdateOption.ValueOf<string>("APPSECRETKEY", string.Empty);
public static UpdateOption<string> AppName { get; } = UpdateOption.ValueOf<string>("APPNAME", string.Empty);
public static UpdateOption<string> MainAppName { get; } = UpdateOption.ValueOf<string>("MAINAPPNAME", string.Empty);
public static UpdateOption<string> InstallPath { get; } = UpdateOption.ValueOf<string>("INSTALLPATH", AppContext.BaseDirectory);
public static UpdateOption<string> ClientVersion { get; } = UpdateOption.ValueOf<string>("CLIENTVERSION", string.Empty);
public static UpdateOption<string?> UpgradeClientVersion { get; } = UpdateOption.ValueOf<string?>("UPGRADECLIENTVERSION", null);
public static UpdateOption<PlatformType?> Platform { get; } = UpdateOption.ValueOf<PlatformType?>("PLATFORM", null);
// ═══ Silent mode ═══
/// <summary>Whether silent updates auto-install without user intervention.</summary>
public static UpdateOption<bool> SilentAutoInstall { get; } = UpdateOption.ValueOf<bool>("SILENTAUTOINSTALL", false);

/// <summary>Polling interval in minutes for silent update checks.</summary>
public static UpdateOption<int> SilentPollIntervalMinutes { get; } = UpdateOption.ValueOf<int>("SILENTPOLLINTERVALMINUTES", 60);

// ═══ Concurrency & Resume ═══
/// <summary>Maximum concurrent download operations.</summary>
public static UpdateOption<int> MaxConcurrency { get; } = UpdateOption.ValueOf<int>("MAXCONCURRENCY", 3);

/// <summary>Whether download resume is enabled.</summary>
public static UpdateOption<bool> EnableResume { get; } = UpdateOption.ValueOf<bool>("ENABLERESUME", true);

// ═══ Resilience ═══
/// <summary>Number of retry attempts for failed operations.</summary>
public static UpdateOption<int> RetryCount { get; } = UpdateOption.ValueOf<int>("RETRYCOUNT", 3);

/// <summary>Whether checksum verification is performed after download.</summary>
public static UpdateOption<bool> VerifyChecksum { get; } = UpdateOption.ValueOf<bool>("VERIFYCHECKSUM", true);
public static UpdateOption<string?> ReportUrl { get; } = UpdateOption.ValueOf<string?>("REPORTURL", null);
public static UpdateOption<string?> ProductId { get; } = UpdateOption.ValueOf<string?>("PRODUCTID", null);
public static UpdateOption<string?> PermissionScript { get; } = UpdateOption.ValueOf<string?>("PERMISSIONSCRIPT", null);
public static UpdateOption<string?> Scheme { get; } = UpdateOption.ValueOf<string?>("SCHEME", null);
public static UpdateOption<string?> Token { get; } = UpdateOption.ValueOf<string?>("TOKEN", null);

/// <summary>Initial retry interval for exponential back-off. Default 1 second.</summary>
public static UpdateOption<TimeSpan> RetryInterval { get; } = UpdateOption.ValueOf<TimeSpan>("RETRYINTERVAL", TimeSpan.FromSeconds(1));

// ═══ OSS ═══
/// <summary>Object Storage Service provider type.</summary>
public static UpdateOption<OssProvider?> OSSProvider { get; } = UpdateOption.ValueOf<OssProvider?>("OSSPROVIDER", null);

/// <summary>OSS bucket region identifier.</summary>
public static UpdateOption<string?> OSSBucketRegion { get; } = UpdateOption.ValueOf<string?>("OSSBUCKETREGION", null);

// ═══ Blacklist ═══
/// <summary>Blacklist configuration for files and directories to exclude from updates.</summary>
public static UpdateOption<BlackListConfig> BlackList { get; } = UpdateOption.ValueOf<BlackListConfig>("BLACKLIST", BlackListConfig.Empty);

// ═══ Watchdog ═══
/// <summary>Bowl (crash monitor / watchdog) executable path.</summary>
public static UpdateOption<string?> Bowl { get; } = UpdateOption.ValueOf<string?>("BOWL", null);

// ═══ Logging & Script ═══
/// <summary>Remote update log / changelog URL.</summary>
public static UpdateOption<string?> UpdateLogUrl { get; } = UpdateOption.ValueOf<string?>("UPDATELOGURL", null);
/// <summary>Custom execution script path for pre/post-update actions.</summary>
public static UpdateOption<string?> Script { get; } = UpdateOption.ValueOf<string?>("SCRIPT", null);

// ═══ Retry ═══
/// <summary>Initial retry interval for exponential backoff. Default 1 second.</summary>
public static UpdateOption<TimeSpan> RetryInterval { get; } = UpdateOption.ValueOf<TimeSpan>("RETRYINTERVAL", TimeSpan.FromSeconds(1));

// ═══ SignalR Hub ═══
/// <summary>SignalR Hub configuration for push-based updates.</summary>
public static UpdateOption<HubConfig?> Hub { get; } = UpdateOption.ValueOf<HubConfig?>("HUB", null);
Expand Down
1 change: 0 additions & 1 deletion src/c#/GeneralUpdate.Core/Strategy/AbstractStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using GeneralUpdate.Core.Pipeline;
using GeneralUpdate.Core;
using GeneralUpdate.Core.Configuration;
using GeneralUpdate.Core.Configuration;
using GeneralUpdate.Core.Network;

namespace GeneralUpdate.Core.Strategy
Expand Down
22 changes: 0 additions & 22 deletions src/c#/GeneralUpdate.Core/Strategy/ClientUpdateStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public class ClientUpdateStrategy : IStrategy
private GlobalConfigInfo? _configInfo;
private IStrategy? _osStrategy;
private Func<UpdateInfoEventArgs, bool>? _updatePrecheck;
private readonly List<Func<bool>> _customOptions = new();
private readonly Download.Abstractions.IDownloadOrchestrator? _orchestrator;
private readonly DiffMode _diffMode = DiffMode.Serial;

Expand All @@ -57,7 +56,6 @@ public async Task ExecuteAsync()
{
GeneralTracer.Debug("ClientUpdateStrategy.ExecuteAsync start.");
await CallSmallBowlHomeAsync(_configInfo.Bowl);
ExecuteCustomOptions();
await ExecuteWorkflowAsync();
}
catch (Exception ex)
Expand Down Expand Up @@ -87,13 +85,6 @@ public ClientUpdateStrategy UseUpdatePrecheck(Func<UpdateInfoEventArgs, bool> fu
return this;
}

/// <summary>Register custom pre-update operations.</summary>
public ClientUpdateStrategy UseCustomOption(Func<bool> func)
{
_customOptions.Add(func);
return this;
}

#region Workflow

private async Task ExecuteWorkflowAsync()
Expand Down Expand Up @@ -299,19 +290,6 @@ private async Task CallSmallBowlHomeAsync(string processName)
}
}

private void ExecuteCustomOptions()
{
foreach (var option in _customOptions)
{
if (!option.Invoke())
{
var ex = new Exception("Custom option execution failed.");
GeneralTracer.Error("ExecuteCustomOptions failed.", ex);
EventManager.Instance.Dispatch(this, new ExceptionEventArgs(ex, ex.Message));
}
}
}

// ════════════════════════════════════════════════════════════════
// Hooks & Reporter safe wrappers
// ════════════════════════════════════════════════════════════════
Expand Down
66 changes: 2 additions & 64 deletions src/c#/GeneralUpdate.Core/Strategy/LinuxStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
using System;
using System.Diagnostics;
using System.IO;
using GeneralUpdate.Core.FileSystem;
using GeneralUpdate.Core;
using GeneralUpdate.Core.Event;
using GeneralUpdate.Core.Pipeline;
using GeneralUpdate.Core.Strategy;
using GeneralUpdate.Core;
using GeneralUpdate.Core.Configuration;
using GeneralUpdate.Core.Event;
using GeneralUpdate.Core.Pipeline;

namespace GeneralUpdate.Core.Strategy;
Expand Down Expand Up @@ -59,8 +54,7 @@ public override void StartApp()
if (string.IsNullOrEmpty(mainAppPath))
throw new Exception($"Can't find the app {mainAppPath}!");

GeneralTracer.Info($"GeneralUpdate.Core.LinuxStrategy.StartApp: executing startup script then launching main app={mainAppPath}");
ExecuteScript();
GeneralTracer.Info($"GeneralUpdate.Core.LinuxStrategy.StartApp: launching main app={mainAppPath}");
Process.Start(mainAppPath);
GeneralTracer.Info("GeneralUpdate.Core.LinuxStrategy.StartApp: main app launched successfully.");
}
Expand All @@ -78,60 +72,4 @@ public override void StartApp()
}
}

/// <summary>
/// Executes the user-specified script.
/// </summary>
private void ExecuteScript()
{
try
{
// Check if the script path is valid (_configinfo should come from the base class configuration)
if (string.IsNullOrEmpty(_configinfo.Script) || !File.Exists(_configinfo.Script))
{
GeneralTracer.Info("No valid script path specified, skipping script execution");
return;
}

GeneralTracer.Info($"Starting to execute script: {_configinfo.Script}");

// Start process to execute Linux script (using bash)
var processStartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"-c \"{_configinfo.Script}\"", // Execute the script
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

using var process = Process.Start(processStartInfo);
if (process == null)
{
GeneralTracer.Error("Failed to start script process");
return;
}

// Read script output logs
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
process.WaitForExit(); // Wait for the script to finish execution

if (!string.IsNullOrEmpty(output))
GeneralTracer.Info($"Script output: {output}");

if (!string.IsNullOrEmpty(error))
GeneralTracer.Warn($"Script warning: {error}");

if (process.ExitCode != 0)
throw new Exception($"Script execution failed, exit code: {process.ExitCode}");

GeneralTracer.Info("Script executed successfully");
}
catch (Exception ex)
{
GeneralTracer.Error("An exception occurred while executing the script", ex);
EventManager.Instance.Dispatch(this, new ExceptionEventArgs(ex, "Script execution failed"));
}
}
}
3 changes: 0 additions & 3 deletions src/c#/GeneralUpdate.Core/Strategy/WindowsStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
using GeneralUpdate.Core;
using GeneralUpdate.Core.Event;
using GeneralUpdate.Core.Pipeline;
using GeneralUpdate.Core.Strategy;
using GeneralUpdate.Core;
using GeneralUpdate.Core.Configuration;
using GeneralUpdate.Core.Pipeline;

namespace GeneralUpdate.Core.Strategy
{
Expand Down
Loading
Loading