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

/// <summary>
/// Generates single-file client.cs and upgrade.cs for simulation,
/// using dotnet-run (#r nuget:...) without project files.
/// Generates client and upgrade console projects for simulation,
/// each with a minimal .csproj + Program.cs using dotnet run --project.
/// </summary>
public class ClientGeneratorService
{
private const string ClientTemplate = """
#r "nuget: GeneralUpdate.ClientCore"
#r "nuget: GeneralUpdate.Core"

using GeneralUpdate.ClientCore;
using GeneralUpdate.Common.Shared.Object;
using GeneralUpdate.Common.Internal.Event;
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>
Comment on lines +23 to +27
""";

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

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

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

await new GeneralClientBootstrap()
await new GeneralUpdate.ClientCore.GeneralClientBootstrap()
.SetConfig(config)
.AddListenerMultiDownloadStatistics((_, e) =>
{{
var v = e.Version as VersionInfo;
log($"Download: {{v?.Version}} {{e.ProgressPercentage}}% {{e.Speed}}/s");
}})
{
var v = e.Version as GeneralUpdate.Common.Shared.Object.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 UpgradeTemplate = """
#r "nuget: GeneralUpdate.Core"
#r "nuget: GeneralUpdate.ClientCore"

using GeneralUpdate.Core;
using GeneralUpdate.Common.Shared;
using GeneralUpdate.Common.Internal.Event;
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.*" />
Comment on lines +91 to +92
</ItemGroup>
</Project>
""";

var log = (string msg) => Console.WriteLine($"[{{DateTime.Now:HH:mm:ss}}] {{msg}}");
private const string UpgradeProgram = """
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()
await new GeneralUpdate.Core.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.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,
// 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,
EscapeForCSharp(config.AppDirectory),
serverUrl,
"upgrade.exe", // AppName
"client.exe", // MainAppName
config.CurrentVersion,
"1.0.0.0", // upgrade client version
config.ProductId,
config.AppSecretKey),
Encoding.UTF8);
Comment on lines +139 to 149

// upgrade.cs
var upgradeCode = string.Format(UpgradeTemplate,
EscapeForCSharp(config.AppDirectory));

await File.WriteAllTextAsync(
Path.Combine(outputDir, "upgrade.cs"),
upgradeCode,
// 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,
EscapeForCSharp(config.AppDirectory)),
Comment on lines +156 to +157
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.cs and upgrade.cs", progress);
Log("STEP 4: Generating client/upgrade projects", progress);
await _generator.GenerateAsync(config, config.OutputDirectory);
Log($" client.cs → {config.OutputDirectory}", progress);
Log($" upgrade.cs → {config.OutputDirectory}", progress);
Log($" client/ → {config.OutputDirectory}/client", progress);
Log($" upgrade/ → {config.OutputDirectory}/upgrade", progress);

// 5. Run client
Log("STEP 5: Running client (dotnet run client.cs)", progress);
var clientResult = await RunDotNetScript(config.OutputDirectory, "client.cs", ct);
// 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);
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)> RunDotNetScript(string workDir, string script, CancellationToken ct)
private async Task<(bool Success, string Output)> RunDotNetProject(string projectDir, CancellationToken ct)
{
var psi = new ProcessStartInfo("dotnet", $"run {script}")
var psi = new ProcessStartInfo("dotnet", "run --project .")
{
WorkingDirectory = workDir,
WorkingDirectory = projectDir,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = System.Text.Encoding.UTF8,
Expand Down