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
146 changes: 61 additions & 85 deletions src/Services/ClientGeneratorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,28 @@
namespace GeneralUpdate.Tools.Services;

/// <summary>
/// Generates client and upgrade console projects for simulation,
/// each with a minimal .csproj + Program.cs using dotnet run --project.
/// Generates single-file client.cs and upgrade.cs for simulation,
/// using dotnet run with #r directives (exact NuGet version).
/// </summary>
public class ClientGeneratorService
{
private const string ClientCsproj = """
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GeneralUpdate.ClientCore" Version="10.*" />
<PackageReference Include="GeneralUpdate.Core" Version="10.*" />
</ItemGroup>
</Project>
""";
private const string ClientTemplate = """
#r "nuget:GeneralUpdate.ClientCore,10.4.6"
#r "nuget:GeneralUpdate.Core,10.4.6"

using GeneralUpdate.ClientCore;
using GeneralUpdate.Common.Shared.Object;
using GeneralUpdate.Common.Internal.Event;

private const string ClientProgram = """
var log = (string msg) => Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {msg}");
var log = (string msg) => Console.WriteLine($"[{{DateTime.Now:HH:mm:ss}}] {{msg}}");

try
{
{{
log("Client started");
log("Install path: {0}");

var config = new GeneralUpdate.Common.Shared.Object.Configinfo
{
var config = new Configinfo
{{
ReportUrl = "{1}/Upgrade/Report",
UpdateUrl = "{1}/Upgrade/Verification",
AppName = "{2}",
Expand All @@ -46,114 +38,98 @@ public class ClientGeneratorService
UpgradeClientVersion = "{5}",
ProductId = "{6}",
AppSecretKey = "{7}",
};
}};

await new GeneralUpdate.ClientCore.GeneralClientBootstrap()
await new GeneralClientBootstrap()
.SetConfig(config)
.AddListenerMultiDownloadStatistics((_, e) =>
{
var v = e.Version as GeneralUpdate.Common.Shared.Object.VersionInfo;
log($"Download: {v?.Version} {e.ProgressPercentage}% {e.Speed}/s");
})
{{
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}");
})
{{
log(e.IsAllDownloadCompleted ? "All downloads completed" : $"Download failed: {{e.FailedVersions.Count}}");
}})
.AddListenerException((_, e) =>
{
log($"ERROR: {e.Exception}");
})
{{
log($"ERROR: {{e.Exception}}");
}})
.AddListenerUpdateInfo((_, e) =>
{
log($"Update info: Code={e.Info.Code}, Versions={e.Info.Body?.Count ?? 0}");
})
{{
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}");
{{
log($"FATAL: {{ex.Message}}");
Console.Error.WriteLine(ex);
Environment.Exit(1);
}
}}
""";

private const string UpgradeCsproj = """
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GeneralUpdate.Core" Version="10.*" />
<PackageReference Include="GeneralUpdate.ClientCore" Version="10.*" />
</ItemGroup>
</Project>
""";
private const string UpgradeTemplate = """
#r "nuget:GeneralUpdate.Core,10.4.6"
#r "nuget:GeneralUpdate.ClientCore,10.4.6"

private const string UpgradeProgram = """
var log = (string msg) => Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {msg}");
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 GeneralUpdate.Core.GeneralUpdateBootstrap()
await new GeneralUpdateBootstrap()
.AddListenerMultiDownloadStatistics((_, e) =>
{
{{
var v = e.Version as GeneralUpdate.Common.Shared.Object.VersionInfo;
log($"Download: {v?.Version} {e.ProgressPercentage}%");
})
log($"Download: {{v?.Version}} {{e.ProgressPercentage}}%");
}})
.AddListenerMultiAllDownloadCompleted((_, e) =>
{
{{
log(e.IsAllDownloadCompleted ? "Downloads done" : "Download failed");
})
}})
.AddListenerException((_, e) =>
{
log($"ERROR: {e.Exception}");
})
{{
log($"ERROR: {{e.Exception}}");
}})
.LaunchAsync();

log("Upgrade process finished successfully");
}
}}
catch (Exception ex)
{
log($"FATAL: {ex.Message}");
{{
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/
var clientDir = Path.Combine(outputDir, "client");
Directory.CreateDirectory(clientDir);
await File.WriteAllTextAsync(Path.Combine(clientDir, "client.csproj"), ClientCsproj, Encoding.UTF8);
await File.WriteAllTextAsync(Path.Combine(clientDir, "Program.cs"),
string.Format(ClientProgram,
await File.WriteAllTextAsync(Path.Combine(outputDir, "client.cs"),
string.Format(ClientTemplate,
EscapeForCSharp(config.AppDirectory),
serverUrl,
"upgrade.exe", // AppName
"client.exe", // MainAppName
"upgrade.cs",
"client.cs",
config.CurrentVersion,
"1.0.0.0", // upgrade client version
"1.0.0.0",
config.ProductId,
config.AppSecretKey),
Encoding.UTF8);

// upgrade/
var upgradeDir = Path.Combine(outputDir, "upgrade");
Directory.CreateDirectory(upgradeDir);
await File.WriteAllTextAsync(Path.Combine(upgradeDir, "upgrade.csproj"), UpgradeCsproj, Encoding.UTF8);
await File.WriteAllTextAsync(Path.Combine(upgradeDir, "Program.cs"),
string.Format(UpgradeProgram,
await File.WriteAllTextAsync(Path.Combine(outputDir, "upgrade.cs"),
string.Format(UpgradeTemplate,
EscapeForCSharp(config.AppDirectory)),
Encoding.UTF8);
}
Expand Down
18 changes: 9 additions & 9 deletions src/Services/SimulationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ public async Task<SimulationResult> RunAsync(
config.ServerPort = _server.Port;

// 4. Generate client/upgrade scripts
Log("STEP 4: Generating client/upgrade projects", progress);
Log("STEP 4: Generating client.cs and upgrade.cs", progress);
await _generator.GenerateAsync(config, config.OutputDirectory);
Log($" client/ → {config.OutputDirectory}/client", progress);
Log($" upgrade/ → {config.OutputDirectory}/upgrade", progress);
Log($" client.cs → {config.OutputDirectory}", progress);
Log($" upgrade.cs → {config.OutputDirectory}", progress);

// 5. Run client via dotnet run --project
Log("STEP 5: Running client (dotnet run --project client)", progress);
var clientResult = await RunDotNetProject(Path.Combine(config.OutputDirectory, "client"), ct);
// 5. Run client
Log("STEP 5: Running client (dotnet run client.cs)", progress);
var clientResult = await RunDotNetScript(config.OutputDirectory, "client.cs", ct);
Log(clientResult.Output, progress);

if (!clientResult.Success)
Expand Down Expand Up @@ -137,11 +137,11 @@ private void Validate(SimulateConfigModel config)
catch { throw new InvalidOperationException("dotnet CLI not found. Install .NET 10.0 SDK."); }
}

private async Task<(bool Success, string Output)> RunDotNetProject(string projectDir, CancellationToken ct)
private async Task<(bool Success, string Output)> RunDotNetScript(string workDir, string script, CancellationToken ct)
{
var psi = new ProcessStartInfo("dotnet", "run --project .")
var psi = new ProcessStartInfo("dotnet", $"run {script}")
{
WorkingDirectory = projectDir,
WorkingDirectory = workDir,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = System.Text.Encoding.UTF8,
Expand Down
Loading