GeneralUpdate Documentation

repository·master·Indexed 21 days ago

https://github.com/generallibrary/generalupdate

A cross-platform, UI-agnostic auto-upgrade component for .NET applications supporting Windows, macOS, and Linux. It features a three-layer architecture (Entry, Role, and OS strategies) and a dual-engine system for downloading and delta patching. The library implements a robust update workflow using Client and Upgrade process roles, IPC-based communication, and a hybrid Chain-to-Full fallback mechanism to ensure reliable updates with optimized bandwidth.

Tokens
12.5K
Snippets
16
Records
28
Agent score
73%

What's inside GeneralUpdate

  1. Overview of GeneralUpdate

    master

    GeneralUpdate is an open-source, cross-platform automatic application upgrade component for .NET applications. It is built on .NET Standard 2.0 and licensed under the Apache 2.0 protocol.

    Key features include:

    • Cross-platform support: Works on Windows, macOS, and Linux.
    • UI Agnostic: It does not depend on any specific UI framework, which minimizes resource consumption during the update process.
    • Integration Ready: Provides one-click startup examples to help developers quickly integrate automatic upgrade capabilities into their existing applications.
  2. Understand Client vs Upgrade Process Roles

    master

    The update system operates using two distinct process roles, determined by the AppType.

    Client Process

    • AppType: Client
    • Entry Strategy: ClientStrategy
    • Responsibilities: Performs server version checks, downloads all required packages, writes the IPC file, launches the Upgrade process, and then exits.
    • Typical Entry Point: MyApp.exe

    Upgrade Process

    • AppType: Upgrade
    • Entry Strategy: UpdateStrategy
    • Responsibilities: Reads the encrypted IPC file to obtain version information, runs the middleware pipeline to update the main application files, writes the manifest, launches the main application, and then exits.
    • Typical Entry Point: Updater.exe

    Note: The Upgrade process requires no command-line arguments or configuration files because all necessary data is passed via the IPC file.

  3. Understand the IPC communication between Client and Upgrade processes

    master

    GeneralUpdate uses Inter-Process Communication (IPC) to transfer configuration and update data from the Client process to the Upgrade process.

    The Workflow:

    1. Client Side: The ConfigurationMapper maps data to a ProcessContract, which is then JSON serialized (using source-generated context), encrypted via AES-256-CBC, and written to a file at %TEMP%/GeneralUpdate/ipc/process_info.enc.
    2. Handover: The Client launches the Upgrade process using LaunchUpgradeProcessAsync().
    3. Upgrade Side: The GeneralUpdateBootstrap constructor calls InitializeFromEnvironment(), which uses EncryptedFileProcessContractProvider.Receive() to read, decrypt, and deserialize the file back into a ProcessContract. The encrypted file is automatically deleted after reading.

    This mechanism ensures that the Upgrade process remains lightweight and does not require network access.

    flowchart LR
        subgraph CLIENT_IPC["Client 进程"]
            C1["ConfigurationMapper\n.MapToProcessContract()"]
            C2["JSON 序列化\n(source-generated context)"]
            C3["AES-256-CBC 加密"]
            C4["写入文件\n%TEMP%/GeneralUpdate/ipc/process_info.enc"]
        end
    
        C1 --> C2 --> C3 --> C4
    
        C4 -- "LaunchUpgradeProcessAsync() 拉起" --> UPGRADE_IPC
    
        subgraph UPGRADE_IPC["Upgrade 进程"]
            U1["GeneralUpdateBootstrap 构造函数\nInitializeFromEnvironment()"]
            U2["EncryptedFileProcessContractProvider\n.Receive()"]
            U3["读取加密文件内容"]
            U4["AES-256-CBC 解密"]
            U5["JSON 反序列化 → ProcessContract"]
            U6["文件自动删除"]
            U7["映射回 UpdateContext"]
        end
    
        U1 --> U2 --> U3 --> U4 --> U5 --> U6 --> U7
  4. Understand the UpdateStrategy execution flow in the Upgrade process

    master

    The UpdateStrategy governs the execution of the Upgrade process. Unlike the Client, it does not perform network requests; it relies entirely on data provided via the IPC file.

    Execution Steps:

    1. Initialization: GeneralUpdateBootstrap reads the IPC file. If invalid, it exits.
    2. Execution: LaunchAsync(AppType.Upgrade) is called, which creates the OS strategy and runs OnBeforeUpdateAsync hooks.
    3. Pipeline: It iterates through each client version, running the middleware pipeline for each.
    4. Manifest Update: If AllPackagesSucceeded is true, it writes the new version back to generalupdate.manifest.json.
    5. Post-Update: It runs OnAfterUpdate and OnBeforeStartApp hooks, then calls _osStrategy.StartAppAsync() to launch the main application.
    6. Exit: The process exits via GracefulExit.CurrentProcessAsync().

    Safety Mechanism: If AllPackagesSucceeded is false, the manifest is not updated and the main application is not launched. This prevents an infinite update loop, allowing the Client to retry the update on its next launch.

    flowchart TB
        subgraph UPGRADE_EXEC["UpdateStrategy 执行流程"]
            START["GeneralUpdateBootstrap 构造\nInitializeFromEnvironment() 读 IPC"] --> OK{"IPC 文件有效?"}
            OK -- No --> EXIT_WAIT["退出等待\n(不是 Upgrade 进程)"]
            OK -- Yes --> LAUNCH["LaunchAsync(AppType.Upgrade)"]
    
            LAUNCH --> OS_CREATE["_osStrategy.Create(_configInfo)"]
    
            OS_CREATE --> HOOK_BEFORE["OnBeforeUpdateAsync 钩子"]
            HOOK_BEFORE --> HOOK_R{"返回 false?"}
            HOOK_R -- Yes --> EXIT_CANCEL["取消更新"]
            HOOK_R -- No --> EXECUTE["_osStrategy.ExecuteAsync()"]
    
            EXECUTE --> PIPELINE["对每个 Client 版本\n循环跑中间件管道"]
    
            PIPELINE --> CHK_RESULT{"AllPackagesSucceeded?"}
    
            CHK_RESULT -- Yes --> WRITE_MANIFEST["写回 generalupdate.manifest.json\n更新 ClientVersion"]
    
            WRITE_MANIFEST --> HOOK_AFTER["OnAfterUpdate 钩子"]
    
            HOOK_AFTER --> HOOK_START["OnBeforeStartApp 钩子"]
            HOOK_START --> LAUNCH_APP["_osStrategy.StartAppAsync()\n拉起主程序"]
    
            LAUNCH_APP --> GRACEFUL["GracefulExit.CurrentProcessAsync()\nUpgrade 进程退出"]
    
            CHK_RESULT -- No --> SKIP_LAUNCH["跳过主程序启动\n防止更新循环"]
        end
  5. DefaultDownloadOrchestrator architecture

    master

    The DefaultDownloadOrchestrator implements the IDownloadOrchestrator interface and is composed of several pluggable components:

    • IDownloadPolicy: Handles retry logic and backoff algorithms.
    • IDownloadExecutor: Performs the actual HTTP download operations.
    • IDownloadPipeline: Handles post-download tasks, such as SHA256 checksum verification.

    Developers can replace the default implementation with a custom Orchestrator if specific download behaviors are required.

  6. Understand the GeneralUpdate.Core Three-Layer Architecture

    master

    GeneralUpdate.Core uses a hierarchical scheduling system combined with two specialized engines to manage application updates. The architecture is divided into three layers of scheduling and two engine layers:

    Scheduling Layers

    1. Entry Scheduling (GeneralUpdateBootstrap): The entry point that dispatches tasks to different role strategies based on the AppType.
    2. Role Strategies:
      • ClientStrategy: Handles downloading and scheduling.
      • UpdateStrategy: Handles reading IPC files and applying updates.
      • OssStrategy: Handles OSS-specific modes.
    3. OS Strategies: Platform-specific implementations including WindowsStrategy, LinuxStrategy, and MacStrategy.

    Engine Layers

    • Download Engine (DefaultDownloadOrchestrator): Manages downloading with retry policies.
    • Diff Engine (DiffPipeline + HDiffPatch): Manages parallel patch application and delta processing.

    Core Design Principles

    • Unified Client Download: The Client process downloads all necessary packages (Client, Upgrade, Chain, and Full ZIPs) to %TEMP%/main_temp/ in one go.
    • Upgrade-Only Application: The Upgrade process performs no network requests. It receives version information via encrypted IPC files and only executes the middleware pipeline.
    • Chain-to-Full Fallback: If a Chain (delta) package fails to apply, the system automatically retries using the pre-downloaded Full package without requiring a new server request.
    • Middleware Pipeline: Each version undergoes an independent Hash → Compress → Patch pipeline for modularity and testability.
  7. How the Chain→Full fallback mechanism works

    master

    The Chain→Full fallback is a high-level fault tolerance mechanism implemented in AbstractStrategy.ExecuteAsync(). It allows the system to recover when a Chain package (a differential update) fails to apply.

    Fallback Workflow

    1. The engine attempts to execute the Chain package pipeline (Hash → Compress → Patch).
    2. If the Chain update fails and the version configuration includes a FallbackFullName:
      • A new PipelineContext is built using the Full package specified by FallbackFullName.
      • The PatchMiddleware is skipped, and the engine runs a full update (Hash → Compress) instead.
    3. If the fallback succeeds, the engine records the fallbackEffectiveVersion.

    Version Tracking and Skipping

    To prevent redundant updates, once a fallback to a Full package is successful, the engine tracks the version. Any subsequent Chain packages in the update list that have a version less than or equal to the fallbackEffectiveVersion are automatically skipped, as they are already covered by the full update.

    // Logic from AbstractStrategy.cs demonstrating fallback tracking
    SemVersion? fallbackEffectiveVersion = null;
    
    foreach (var version in _configinfo.UpdateVersions)
    {
        // Skip Chain packages if they are covered by a previous Full fallback
        if (fallbackEffectiveVersion != null
            && version.PackageType == (int)PackageType.Chain
            && versionSv <= fallbackEffectiveVersion)
        {
            continue;
        }
    
        try
        {
            await pipelineBuilder.Build(); // Attempt Chain
        }
        catch when (version.PackageType == Chain && FallbackFullName != null)
        {
            // Chain failed -> Retry with Full package
            await fallbackBuilder.Build();
            fallbackEffectiveVersion = ffv;
        }
    }
  8. How GeneralUpdateBootstrap handles IPC detection

    master

    The GeneralUpdateBootstrap class serves as a dual-purpose entry point. Its constructor automatically detects if it is running as an Upgrade process by attempting to read an encrypted IPC file from the environment.

    Execution Flow

    1. Constructor Call: new GeneralUpdateBootstrap() triggers InitializeFromEnvironment().
    2. IPC Probing: The system uses EncryptedFileProcessContractProvider to look for a contract file at %TEMP%/GeneralUpdate/ipc/process_info.enc.
    3. Branching Logic:
      • If no IPC file is found: The instance is treated as a Client process. The developer must manually set configuration using .SetConfig() or .SetSource() before calling .LaunchAsync(AppType.Client).
      • If an IPC file is found: The instance is treated as an Upgrade process. The internal configuration (_configInfo) is automatically populated from the IPC file (including UpdateAppName, InstallPath, and UpdateVersions). The developer simply calls .LaunchAsync(AppType.Upgrade).

    Code Example: Client vs Upgrade Initialization

    Client Process (Manual Config):

    // In your main application
    var bootstrap = new GeneralUpdateBootstrap();
    bootstrap.SetConfig(myRequest);
    await bootstrap.LaunchAsync(AppType.Client);

    Upgrade Process (Automatic Config via IPC):

    // In your updater/upgrade executable
    // No manual config needed; constructor reads the IPC file automatically
    var bootstrap = new GeneralUpdateBootstrap();
    await bootstrap.LaunchAsync(AppType.Upgrade);
    public GeneralUpdateBootstrap()
    {
        InitializeFromEnvironment(); // 读取加密 IPC 文件
    }
    
    void InitializeFromEnvironment()
    {
        var provider = new EncryptedFileProcessContractProvider();
        var contract = provider.Receive();  // 读 %TEMP%/GeneralUpdate/ipc/process_info.enc
    
        if (contract == null) return; // 没有 IPC 文件 → 这不是一个 Upgrade 进程
    
        // 读到 IPC 文件 → 说明是 Upgrade 进程
        _configInfo.UpdateAppName = contract.AppName;
        _configInfo.InstallPath = contract.InstallPath;
        _configInfo.UpdateVersions = contract.UpdateVersions;
        // ... 其他字段
    }
  9. Understand the Middleware Pipeline (Hash → Compress → Patch)

    master

    The core processing pattern in GeneralUpdate.Core is a middleware pipeline. Each version processed undergoes a complete pipeline execution. The pipeline is constructed by OS-specific strategies (e.g., WindowsStrategy) and consists of three primary stages:

    1. HashMiddleware: Performs integrity verification by calculating the SHA256 of the ZIP file and comparing it against the expected hash. A mismatch throws a CryptographicException.
    2. CompressMiddleware: Handles decompression. The behavior depends on the PackageType and PatchEnabled status:
      • Full Package: Decompresses directly to the SourcePath (the installation directory), performing a full overwrite.
      • Chain Package (Patch Enabled): Decompresses to a temporary PatchPath (e.g., %TEMP%/patchs/<version-name>/) to prepare for differential patching.
      • Chain Package (Patch Disabled): Falls back to decompressing directly to the SourcePath (full overwrite).
    3. PatchMiddleware: If the package is a Chain type and patching is enabled, this middleware invokes the DiffPipeline to apply differential updates from the PatchPath to the SourcePath using atomic replacement.

    Failure of a single version in the pipeline does not stop the processing of other versions, but the AllPackagesSucceeded flag will indicate if any version failed.

    // Example of how a pipeline is constructed via an OS Strategy
    protected override PipelineBuilder BuildPipeline(PipelineContext context)
    {
        var needsPatch = context.Get<bool>("PatchEnabled") 
                        && context.Get<int>("PackageType") != (int)PackageType.Full;
    
        return new PipelineBuilder(context)
            .UseMiddleware<HashMiddleware>()       // 1. Integrity check
            .UseMiddleware<CompressMiddleware>()   // 2. Decompression
            .UseMiddlewareIf<PatchMiddleware>(     // 3. Differential patching
                needsPatch: needsPatch);
    }
  10. Understand error recovery and rollback logic in GeneralUpdate.Core

    master

    GeneralUpdate.Core implements a multi-layered error recovery strategy to ensure update stability. The system handles failures differently depending on the stage and the package type (Chain vs. Full).

    Error Scenarios and Handling

    Error ScenarioCapture LocationHandling MethodConsequence
    Download FailureClientStrategy.DownloadAndApplyAsync()Check FailedCount > 0, throw exceptionBubbles to ExecuteAsync() catch $\rightarrow$ triggers error hooks + failure reporting
    Chain Package Pipeline Failure (with FallbackFull)AbstractStrategy.ExecuteAsync() catchRebuild PipelineContext, set PackageType=Full, re-run Hash $\rightarrow$ CompressUpdate eventually succeeds; fallbackEffectiveVersion records the fallback version
    Chain Package Pipeline Failure (without FallbackFull)AbstractStrategy.ExecuteAsync() catchSet AllPackagesSucceeded=false, trigger HandleExecuteException; if no previous version succeeded, call TryRollback()Version fails; proceed to next version
    Fallback Full also failsAbstractStrategy.ExecuteAsync() catch (inner try)Set AllPackagesSucceeded=falseVersion fails; proceed to next version
    Upgrade Package Failure (Both scenarios)ClientStrategy.cs Both branchesAbort IPC sending + Upgrade process startupPrevents Upgrade process from receiving an invalid TempPath
    Upgrade Process Pipeline FailureUpdateStrategy.ExecuteAsync()Set AllPackagesSucceeded=false, skip manifest write-back, skip main program startupNext Client startup will re-detect the update
    Rollback FailureAbstractStrategy.TryRollback()Log only, do not blockInstallation directory may be in an inconsistent state
    ZIP Hash MismatchHashMiddlewareThrow CryptographicExceptionPipeline for that version fails immediately, triggering Chain $\rightarrow$ Full fallback or version failure
    File LockedIpcEncryption.DecryptFromFile()Catch IOException, return nullIPC file is not ready; Upgrade process waits or exits

    Rollback Logic

    Rollback is only attempted if no versions in the current batch have been successfully applied. This prevents a rollback from undoing a successful update and causing a cross-version downgrade.

    If _appliedAnyVersion is false, StorageManager.Restore(backupDir, _configInfo.InstallPath) is called to restore files from the .backups/ directory.

    // AbstractStrategy.cs:504-532
    private void TryRollback()
    {
        // Only called when "no version in the current batch has succeeded"
        // If a version has already been successfully applied and overwritten files,
        // rollback would undo valid work, causing a cross-version downgrade.
    
        if (!_appliedAnyVersion)
        {
            // Attempt to restore from .backups/
            StorageManager.Restore(backupDir, _configInfo.InstallPath);
        }
    }
  11. FallbackFull matching rules in DownloadPlanBuilder

    master

    When the DownloadPlanBuilder decides to use the Chain + Fallback mode (because the Chain size is less than 80% of the Full package size), it matches each Chain package with a fallback Full package using these rules:

    1. Same AppType: The AppType must match.
    2. Version Compatibility: The Full package version must be greater than or equal to the Chain package version (Full.Version >= Chain.Version).
    3. Minimal Version: It selects the smallest compatible version (the first one found when ordered by version ascending) to minimize download size.

    If a match is found, the Chain package is augmented with the following metadata:

    • FallbackFullName
    • FallbackFullUrl
    • FallbackFullHash
    • FallbackFullVersion
    // DownloadPlanBuilder.cs:204-237
    var chainWithFallback = chainCandidates.Select(chain =>
    {
        // 找到同 AppType、版本 >= chain 版本的最小 Full 包
        var match = fullCandidates
            .Where(f => f.AppType == chain.AppType)
            .OrderBy(f => f.Version)           // 最小版本优先
            .FirstOrDefault(f => f.Version >= chain.Version);
    
        if (match != null) {
            // 给这个 Chain 包附上 FallbackFull 元信息
            return chain with
            {
                FallbackFullName    = match.Name,
                FallbackFullUrl     = match.Url,
                FallbackFullHash    = match.SHA256,
                FallbackFullVersion = match.Version
            };
        }
        return chain; // 没有匹配的 Full 包,那就没有回退能力
    });
  12. Understand the ClientStrategy update workflow

    master

    ClientStrategy is the primary orchestration component responsible for the complete update lifecycle, from version checking to launching the upgrade process.

    High-Level Workflow

    1. Conflict Cleanup: Terminates any existing upgrade processes (e.g., Bowl) to release file locks.
    2. Version Check: Queries the server via HttpDownloadSource.ListAsync() to retrieve a list of available DownloadAsset objects.
    3. Scenario Determination: Based on whether the Main application or the Upgrade client needs updates, it selects one of four UpdateScenario modes:
      • None: No update required.
      • UpgradeOnly: Only the upgrade client is updated.
      • MainOnly: Only the main application is updated (requires IPC communication).
      • Both: Both the main application and the upgrade client are updated.
    4. Backup: Backs up the installation directory to .backups/ (keeping the 3 most recent versions) while respecting a default blacklist (e.g., .git, node_modules, bin).
    5. Download: Downloads all required assets (Chain packages and Fallback Full packages) in a single batch to a temporary directory (%TEMP%/main_temp/).
    6. Execution: Applies the updates based on the determined scenario.

    Update Scenarios

    ScenarioAction
    UpgradeOnlyUses ApplyUpgradePackagesAsync() to update the upgrade client in-place via _osStrategy.ExecuteAsync().
    MainOnlyWrites an AES-encrypted IPC file to %TEMP%/GeneralUpdate/ipc/process_info.enc using SendProcessIpc(), then calls LaunchUpgradeProcessAsync() to trigger the upgrade process.
    BothFirst performs an in-place upgrade of the current process via ApplyUpgradePackagesAsync(). If successful, it writes the IPC file and launches the upgrade process. If the in-place upgrade fails, the process aborts to prevent infinite update loops.
    // Example of how scenarios are determined internally
    var scenario = (_configInfo.IsMainUpdate, _configInfo.IsUpgradeUpdate) switch
    {
        (false, false) => UpdateScenario.None,
        (false, true)  => UpdateScenario.UpgradeOnly,
        (true, false)  => UpdateScenario.MainOnly,
        (true, true)   => UpdateScenario.Both,
    };