diff --git a/src/Models/SimulateConfigModel.cs b/src/Models/SimulateConfigModel.cs
index 9d20228..64832c4 100644
--- a/src/Models/SimulateConfigModel.cs
+++ b/src/Models/SimulateConfigModel.cs
@@ -17,5 +17,4 @@ public partial class SimulateConfigModel : ObservableObject
[ObservableProperty] private string _productId = "2d974e2a-31e6-4887-9bb1-b4689e98c77a";
[ObservableProperty] private string _outputDirectory = string.Empty;
public int ServerPort { get; set; } = 5000;
- public bool ServerRunning { get; set; }
}
diff --git a/src/Services/SimulationService.cs b/src/Services/SimulationService.cs
index 4b89f82..d7155cc 100644
--- a/src/Services/SimulationService.cs
+++ b/src/Services/SimulationService.cs
@@ -14,60 +14,51 @@ public class SimulationService
{
private readonly ClientGeneratorService _generator = new();
private readonly LocalUpdateServer _server = new();
+ private readonly StringBuilder _fullLog = new();
private int _timeoutSeconds = 120;
- public string ServerBaseUrl => _server.BaseUrl;
-
- ///
- /// Generate scripts and start the local update server. Server stays running until StopServerAsync is called.
- ///
- public async Task StartServerAsync(SimulateConfigModel config, IProgress? progress = null)
- {
- Log("STEP 1: Validating inputs", progress);
- Validate(config);
-
- Log("STEP 2: Generating client.csx and upgrade.csx", progress);
- await _generator.GenerateAsync(config, config.OutputDirectory);
- Log($" client.csx → {config.OutputDirectory}", progress);
- Log($" upgrade.csx → {config.OutputDirectory}", progress);
-
- Log("STEP 3: Starting local server", progress);
- var serverPatchDir = Path.Combine(config.OutputDirectory, ".server");
- Directory.CreateDirectory(serverPatchDir);
- var patchName = Path.GetFileName(config.PatchFilePath);
- var patchDest = Path.Combine(serverPatchDir, patchName);
- File.Copy(config.PatchFilePath, patchDest, true);
-
- var hash = ComputeQuickHash(patchDest);
- LocalUpdateServerFiles.Register(patchName, patchDest);
- _server.Updates.Add((config.CurrentVersion, config.TargetVersion, hash, patchDest, config.AppType));
-
- await _server.StartAsync(config.ServerPort);
- config.ServerPort = _server.Port;
- config.ServerRunning = true;
- Log($" Server running on {_server.BaseUrl}", progress);
- }
-
- ///
- /// Stop the local update server.
- ///
- public async Task StopServerAsync()
- {
- await _server.DisposeAsync();
- LocalUpdateServerFiles.Clear();
- }
-
- ///
- /// Run the client script and return results. Server must already be running.
- ///
- public async Task RunClientAsync(SimulateConfigModel config, IProgress? progress = null, CancellationToken ct = default)
+ public async Task RunAsync(
+ SimulateConfigModel config,
+ IProgress? progress = null,
+ CancellationToken ct = default)
{
var result = new SimulationResult();
var sw = Stopwatch.StartNew();
try
{
- Log("Running client (dotnet script client.csx)", progress);
+ // 1. Validate
+ Log("STEP 1: Validating inputs", progress);
+ Validate(config);
+
+ // 2. Prepare output
+ Log($"STEP 2: Preparing {config.OutputDirectory}", progress);
+ Directory.CreateDirectory(config.OutputDirectory);
+
+ // 3. Generate scripts
+ Log("STEP 3: Generating client.csx and upgrade.csx", progress);
+ await _generator.GenerateAsync(config, config.OutputDirectory);
+ Log($" client.csx → {config.OutputDirectory}", progress);
+ Log($" upgrade.csx → {config.OutputDirectory}", progress);
+
+ // 4. Start server
+ Log("STEP 4: Starting local server", progress);
+ var serverPatchDir = Path.Combine(config.OutputDirectory, ".server");
+ Directory.CreateDirectory(serverPatchDir);
+ var patchName = Path.GetFileName(config.PatchFilePath);
+ var patchDest = Path.Combine(serverPatchDir, patchName);
+ File.Copy(config.PatchFilePath, patchDest, true);
+
+ var hash = ComputeQuickHash(patchDest);
+ LocalUpdateServerFiles.Register(patchName, patchDest);
+ _server.Updates.Add((config.CurrentVersion, config.TargetVersion, hash, patchDest, config.AppType));
+
+ await _server.StartAsync(config.ServerPort);
+ Log($" Server running on {_server.BaseUrl}", progress);
+ config.ServerPort = _server.Port;
+
+ // 5. Run client
+ Log("STEP 5: Running client (dotnet script client.csx)", progress);
var clientResult = await RunDotNetScript(config.OutputDirectory, "client.csx", ct);
Log(clientResult.Output, progress);
@@ -78,24 +69,30 @@ public async Task RunClientAsync(SimulateConfigModel config, I
return result;
}
- Log("Client completed", progress);
- await Task.Delay(2000, ct);
+ Log(" Client completed successfully", progress);
- var fileCount = Directory.GetFiles(config.AppDirectory, "*", SearchOption.AllDirectories).Length;
- result.Notes.Add($"Files in app directory after update: {fileCount}");
+ // 6. Verify
+ Log("STEP 6: Verifying update result", progress);
+ await Task.Delay(2000, ct);
+ VerifyUpdateResult(config, result);
result.Success = true;
result.Elapsed = sw.Elapsed;
- Log($"Simulation complete ({sw.Elapsed.TotalSeconds:F1}s)", progress);
+ Log($"✅ Simulation complete ({sw.Elapsed.TotalSeconds:F1}s)", progress);
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
- Log($"Simulation failed: {ex.Message}", progress);
+ Log($"❌ Simulation failed: {ex.Message}", progress);
+ }
+ finally
+ {
+ try { await _server.DisposeAsync(); } catch { }
+ LocalUpdateServerFiles.Clear();
+ result.FullLog = _fullLog.ToString();
}
- result.FullLog = "";
return result;
}
@@ -140,6 +137,16 @@ private void Validate(SimulateConfigModel config)
return (!hasError && p.ExitCode == 0, outputStr);
}
+ private void VerifyUpdateResult(SimulateConfigModel config, SimulationResult result)
+ {
+ var deleteFile = Path.Combine(config.AppDirectory, "delete_files.json");
+ if (File.Exists(deleteFile))
+ result.Notes.Add("delete_files.json still present - HandleDeleteList may not have run");
+
+ var fileCount = Directory.GetFiles(config.AppDirectory, "*", SearchOption.AllDirectories).Length;
+ result.Notes.Add($"Files in app directory after update: {fileCount}");
+ }
+
private static string ComputeQuickHash(string filePath)
{
using var sha = System.Security.Cryptography.SHA256.Create();
@@ -147,7 +154,12 @@ private static string ComputeQuickHash(string filePath)
return BitConverter.ToString(sha.ComputeHash(fs)).Replace("-", "").ToLowerInvariant();
}
- private static void Log(string msg, IProgress? progress) => progress?.Report($"[{DateTime.Now:HH:mm:ss}] {msg}");
+ private void Log(string msg, IProgress? progress)
+ {
+ var line = $"[{DateTime.Now:HH:mm:ss}] {msg}";
+ _fullLog.AppendLine(line);
+ progress?.Report(line);
+ }
}
public class SimulationResult
diff --git a/src/ViewModels/SimulateViewModel.cs b/src/ViewModels/SimulateViewModel.cs
index d0a3f87..2943674 100644
--- a/src/ViewModels/SimulateViewModel.cs
+++ b/src/ViewModels/SimulateViewModel.cs
@@ -84,7 +84,7 @@ public int AppTypeIndex
[RelayCommand] async Task SelectOutputDir() { var p = await PickFolder(_loc["Sim.SelectOutput"]); if (p != null) Config.OutputDirectory = p; }
[RelayCommand]
- async Task StartServer()
+ async Task StartSimulation()
{
if (string.IsNullOrWhiteSpace(Config.AppDirectory)) { Status = _loc["Sim.ValidateDirs"]; return; }
if (string.IsNullOrWhiteSpace(Config.PatchFilePath)) { Status = _loc["Sim.ValidateDirs"]; return; }
@@ -93,34 +93,13 @@ async Task StartServer()
IsRunning = true; Log.Clear(); Status = _loc["Sim.Starting"];
try
{
- await _sim.StartServerAsync(Config, new Progress(L));
- Status = $"Server: {_sim.ServerBaseUrl}";
- L($"Server running on {_sim.ServerBaseUrl}");
- L($"Manual: dotnet script client.csx");
- }
- catch (Exception ex) { Status = $"Error: {ex.Message}"; L($"FATAL: {ex}"); }
- finally { IsRunning = false; }
- }
-
- [RelayCommand]
- async Task StopServer()
- {
- await _sim.StopServerAsync();
- Status = _loc["Patch.Ready"];
- L("Server stopped");
- }
-
- [RelayCommand]
- async Task RunClient()
- {
- if (!Config.ServerRunning) { Status = "Server not running"; return; }
- IsRunning = true; Status = "Running client...";
- try
- {
- var result = await _sim.RunClientAsync(Config, new Progress(L));
+ var progress = new Progress(L);
+ var result = await _sim.RunAsync(Config, progress);
if (result.Success)
{
Status = _loc.T("Sim.Completed", result.Elapsed.TotalSeconds);
+ L($"Result: {(result.Success ? "PASS" : "FAIL")}");
+ foreach (var note in result.Notes) L($" Note: {note}");
var reportPath = await _report.GenerateAsync(Config, result, Config.OutputDirectory);
L(_loc.T("Sim.Report", reportPath));
}
diff --git a/src/Views/SimulateView.axaml b/src/Views/SimulateView.axaml
index 3965cc0..ebe2137 100644
--- a/src/Views/SimulateView.axaml
+++ b/src/Views/SimulateView.axaml
@@ -81,24 +81,10 @@
-
-
-
-
-
-
+
+