From 02ece2ebdaab017911347fdfa24d6ec0c113dd73 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Wed, 20 May 2026 23:00:07 +0800 Subject: [PATCH] feat: add ClientGeneratorService for single-file client/upgrade code - Generates client.cs and upgrade.cs using #r nuget: inline references - Client uses GeneralUpdate.ClientCore to poll for updates - Upgrade uses GeneralUpdate.Core to apply patches - Templates replace placeholders (version, URL, keys, paths) - Added ServerPort to SimulateConfigModel --- src/Models/SimulateConfigModel.cs | 3 + src/Services/ClientGeneratorService.cs | 147 +++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 src/Services/ClientGeneratorService.cs diff --git a/src/Models/SimulateConfigModel.cs b/src/Models/SimulateConfigModel.cs index 5e4c804..3044d17 100644 --- a/src/Models/SimulateConfigModel.cs +++ b/src/Models/SimulateConfigModel.cs @@ -33,4 +33,7 @@ public partial class SimulateConfigModel : ObservableObject /// Directory where client.cs / upgrade.cs and server are generated. [ObservableProperty] private string _outputDirectory = string.Empty; + + /// Server port assigned at runtime (set by SimulationService). + public int ServerPort { get; set; } = 5000; } diff --git a/src/Services/ClientGeneratorService.cs b/src/Services/ClientGeneratorService.cs new file mode 100644 index 0000000..3e00699 --- /dev/null +++ b/src/Services/ClientGeneratorService.cs @@ -0,0 +1,147 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using GeneralUpdate.Tools.Models; + +namespace GeneralUpdate.Tools.Services; + +/// +/// Generates single-file client.cs and upgrade.cs for simulation, +/// using dotnet-run (#r nuget:...) without project files. +/// +public class ClientGeneratorService +{ + private const string ClientTemplate = """ +#r "nuget: GeneralUpdate.ClientCore, 10.*" +#r "nuget: GeneralUpdate.Core, 10.*" + +using GeneralUpdate.ClientCore; +using GeneralUpdate.Common.Shared.Object; +using GeneralUpdate.Common.Internal.Event; + +var log = (string msg) => Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {msg}"); + +try +{{ + log("Client started"); + log("Install path: {0}"); + + var config = new Configinfo + {{ + ReportUrl = "{1}/Upgrade/Report", + UpdateUrl = "{1}/Upgrade/Verification", + AppName = "{2}", + MainAppName = "{3}", + InstallPath = @"{0}", + ClientVersion = "{4}", + UpgradeClientVersion = "{5}", + ProductId = "{6}", + AppSecretKey = "{7}", + }}; + + await new GeneralClientBootstrap() + .SetConfig(config) + .AddListenerMultiDownloadStatistics((_, e) => + {{ + var v = e.Version as VersionInfo; + log($"Download: {{v?.Version}} {{e.ProgressPercentage}}% {{e.Speed}}/s"); + }}) + .AddListenerMultiAllDownloadCompleted((_, e) => + {{ + log(e.IsAllDownloadCompleted ? "All downloads completed" : $"Download failed: {{e.FailedVersions.Count}}"); + }}) + .AddListenerException((_, e) => + {{ + log($"ERROR: {{e.Exception}}"); + }}) + .AddListenerUpdateInfo((_, e) => + {{ + log($"Update info: Code={{e.Info.Code}}, Versions={{e.Info.Body?.Count ?? 0}}"); + }}) + .LaunchAsync(); + + log("Update process completed"); +}} +catch (Exception ex) +{{ + log($"FATAL: {{ex.Message}}"); + Console.Error.WriteLine(ex); + Environment.Exit(1); +}} +"""; + + private const string UpgradeTemplate = """ +#r "nuget: GeneralUpdate.Core, 10.*" +#r "nuget: GeneralUpdate.ClientCore, 10.*" + +using GeneralUpdate.Core; +using GeneralUpdate.Common.Shared; +using GeneralUpdate.Common.Internal.Event; + +var log = (string msg) => Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {msg}"); + +try +{{ + log("Upgrade process started"); + log("Working directory: " + Environment.CurrentDirectory); + + await new GeneralUpdateBootstrap() + .AddListenerMultiDownloadStatistics((_, e) => + {{ + var v = e.Version as GeneralUpdate.Common.Shared.Object.VersionInfo; + log($"Download: {{v?.Version}} {{e.ProgressPercentage}}%"); + }}) + .AddListenerMultiAllDownloadCompleted((_, e) => + {{ + log(e.IsAllDownloadCompleted ? "Downloads done" : "Download failed"); + }}) + .AddListenerException((_, e) => + {{ + log($"ERROR: {{e.Exception}}"); + }}) + .LaunchAsync(); + + log("Upgrade process finished successfully"); +}} +catch (Exception ex) +{{ + log($"FATAL: {{ex.Message}}"); + Console.Error.WriteLine(ex); + Environment.Exit(1); +}} +"""; + + public async Task GenerateAsync(SimulateConfigModel config, string outputDir) + { + var serverUrl = $"http://127.0.0.1:{config.ServerPort}"; + + // client.cs + var clientCode = string.Format(ClientTemplate, + EscapeForCSharp(config.AppDirectory), + serverUrl, + "upgrade.cs", // AppName - the upgrade process + "client.cs", // MainAppName + config.CurrentVersion, + "1.0.0.0", // upgrade client version + config.ProductId, + config.AppSecretKey); + + await File.WriteAllTextAsync( + Path.Combine(outputDir, "client.cs"), + clientCode, + Encoding.UTF8); + + // upgrade.cs + var upgradeCode = string.Format(UpgradeTemplate, + EscapeForCSharp(config.AppDirectory)); + + await File.WriteAllTextAsync( + Path.Combine(outputDir, "upgrade.cs"), + upgradeCode, + Encoding.UTF8); + } + + private static string EscapeForCSharp(string s) => + s.Replace(@"\", @"\\").Replace("\"", "\\\""); +}