Skip to content

Commit 702f1eb

Browse files
author
JusterZhu
committed
fix: resolve all 6 Copilot review comments on PR #7
1. MinimalIntegration.cs: Fix System.Version comment accuracy 2. RealDownloadService.cs: Accept _updateUrl as-is (no /Upgrade/Verification append), add tenantId, use TryGetProperty for response parsing, add productId param, lowercase platform values 3. NamedPipeIPC.cs: Use Environment.ProcessId for unique pipe name, enforce timeoutMs via linked CancellationTokenSource with TimeoutException 4. clinerules/05: Fix curl appType from 1 to 0, fix manifest path
1 parent 023e8de commit 702f1eb

4 files changed

Lines changed: 59 additions & 34 deletions

File tree

.claude/skills/generalupdate-advanced/templates/NamedPipeIPC.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,30 @@
2020
/// </summary>
2121
public class NamedPipeIpcProvider : IAsyncDisposable
2222
{
23-
private const string PipeName = "GeneralUpdate_IPC_";
23+
// Unique pipe name per client process — prevents collisions between parallel instances
24+
private readonly string _pipeName = $"GeneralUpdate_IPC_{Environment.ProcessId}";
2425
private NamedPipeServerStream? _server;
2526
private NamedPipeClientStream? _client;
2627
private readonly CancellationTokenSource _cts = new();
2728

2829
/// <summary>Called by Client process: create server pipe, wait for Upgrade.</summary>
2930
public async Task<string> ServerWaitAsync(int timeoutMs = 30000)
3031
{
31-
_server = new NamedPipeServerStream(PipeName, PipeDirection.InOut,
32+
_server = new NamedPipeServerStream(_pipeName, PipeDirection.InOut,
3233
maxNumberOfServerInstances: 1, TransmissionMode.Byte, PipeOptions.Asynchronous);
33-
await _server.WaitForConnectionAsync(_cts.Token);
34-
return PipeName;
34+
35+
// Enforce caller-supplied timeout via CancellationTokenSource
36+
using var timeoutCts = new CancellationTokenSource(timeoutMs);
37+
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, _cts.Token);
38+
try
39+
{
40+
await _server.WaitForConnectionAsync(linkedCts.Token);
41+
}
42+
catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested)
43+
{
44+
throw new TimeoutException($"Upgrade process did not connect within {timeoutMs}ms");
45+
}
46+
return _pipeName;
3547
}
3648

3749
/// <summary>Called by Upgrade process: connect to Client pipe.</summary>

.claude/skills/generalupdate-init/templates/MinimalIntegration.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ public static async Task<bool> RunAsync()
4747
* 3. 文件放错目录(必须和 .exe 同级)
4848
* 4. UpdatePath 写成了相对路径但当前目录不对
4949
*
50-
* ⚠️ 版本号规则:必须使用 4 段式,如 "1.0.0.0"
51-
* "1.0" 被 System.Version 解析为 1.0.0.0(Build=-1, Revision=-1)。
52-
* 但服务器返回 1.0.0.0Build=0, Revision=0),
53-
* new Version("1.0") < new Version("1.0.0.0") 成立,
54-
* 导致版本比较误判为"有更新"→ 无限升级循环。
55-
* 务必始终使用 4 段式版本号。
50+
* ⚠️ Version format: Must use 4 segments, e.g. "1.0.0.0"
51+
* new Version("1.0") parses to 1.0.-1.-1 (Build=-1, Revision=-1),
52+
* NOT 1.0.0.0. When the server returns 1.0.0.0 (Build=0, Revision=0),
53+
* new Version("1.0") < new Version("1.0.0.0") is TRUE,
54+
* causing a false "update available" detection → infinite upgrade loop.
55+
* ALWAYS use 4-segment version strings everywhere.
5656
*
5757
* ⚠️ 路径规则:
5858
* InstallPath: 可写 "."(表示 exe 所在目录)或绝对路径

.claude/skills/generalupdate-ui/templates/RealDownloadService.cs

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,17 @@ public class RealDownloadService : IDownloadService
4545
private readonly string _updateUrl;
4646
private readonly string _secretKey;
4747
private readonly AppType _appType;
48+
private readonly string _productId;
4849
private CancellationTokenSource? _cts;
4950
private int _retryCount;
5051
private const int MaxRetries = 3;
5152

52-
public RealDownloadService(string updateUrl, string secretKey, AppType appType = AppType.Client)
53+
public RealDownloadService(string updateUrl, string secretKey, AppType appType = AppType.Client, string productId = "unknown")
5354
{
5455
_updateUrl = updateUrl;
5556
_secretKey = secretKey;
5657
_appType = appType;
58+
_productId = productId;
5759

5860
CurrentStatistics = new DownloadStatistics
5961
{
@@ -132,39 +134,56 @@ private async Task RunCheckAsync(CancellationToken token)
132134

133135
try
134136
{
135-
// 直接调用服务端 /Upgrade/Verification API 仅检查版本
136-
// 不触发 LaunchAsync(避免启动完整下载+升级流程)
137137
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
138138

139139
var payload = JsonSerializer.Serialize(new
140140
{
141141
appKey = _secretKey,
142142
appType = (int)_appType,
143143
clientVersion = GetCurrentVersion(),
144-
productId = GetProductId(),
145-
platform = GetPlatform()
144+
productId = _productId,
145+
platform = GetPlatform(),
146+
tenantId = "default"
146147
});
147148

149+
// Use _updateUrl as-is; it should already point to the full Verification endpoint
148150
var response = await client.PostAsync(
149-
_updateUrl.TrimEnd('/') + "/Upgrade/Verification",
151+
_updateUrl,
150152
new StringContent(payload, Encoding.UTF8, "application/json"),
151153
token);
152154

153155
if (!response.IsSuccessStatusCode)
154156
{
155-
ErrorOccurred?.Invoke($"服务器返回 {(int)response.StatusCode}");
157+
ErrorOccurred?.Invoke($"Server returned {(int)response.StatusCode}");
156158
UpdateState(DownloadStatus.DownloadError);
157159
return;
158160
}
159161

160162
var json = await response.Content.ReadAsStringAsync(token);
161163
using var doc = JsonDocument.Parse(json);
162-
var body = doc.RootElement.GetProperty("body");
164+
165+
// Check for server error envelope first: { code, message, body }
166+
if (doc.RootElement.TryGetProperty("code", out var codeProp) &&
167+
codeProp.GetInt32() != 200)
168+
{
169+
var msg = doc.RootElement.TryGetProperty("message", out var msgProp)
170+
? msgProp.GetString() ?? "Unknown server error"
171+
: "Unknown server error";
172+
ErrorOccurred?.Invoke($"Server error: {msg}");
173+
UpdateState(DownloadStatus.DownloadError);
174+
return;
175+
}
176+
177+
if (!doc.RootElement.TryGetProperty("body", out var body) || body.GetArrayLength() == 0)
178+
{
179+
UpdateState(DownloadStatus.AlreadyLatest);
180+
return;
181+
}
182+
163183
var hasUpdate = body.GetArrayLength() > 0;
164184

165185
if (hasUpdate)
166186
{
167-
// 读取第一个版本的信息填充统计
168187
var first = body[0];
169188
CurrentStatistics.Version = first.TryGetProperty("version", out var v) ? v.GetString() : null;
170189
CurrentStatistics.TotalBytesToReceive = first.TryGetProperty("size", out var s) ? s.GetInt64() : 0;
@@ -178,12 +197,12 @@ private async Task RunCheckAsync(CancellationToken token)
178197
}
179198
catch (OperationCanceledException)
180199
{
181-
ErrorOccurred?.Invoke("检查已取消");
200+
ErrorOccurred?.Invoke("Check cancelled");
182201
UpdateState(DownloadStatus.Idle);
183202
}
184203
catch (Exception ex)
185204
{
186-
ErrorOccurred?.Invoke($"检查更新失败: {ex.Message}");
205+
ErrorOccurred?.Invoke($"Check failed: {ex.Message}");
187206
UpdateState(DownloadStatus.DownloadError);
188207
}
189208
}
@@ -194,18 +213,12 @@ private static string GetCurrentVersion()
194213
?.GetName()?.Version?.ToString(4) ?? "1.0.0.0";
195214
}
196215

197-
private static string GetProductId()
198-
{
199-
// 从 manifest 中读取,简化实现
200-
return "unknown";
201-
}
202-
203216
private static string GetPlatform()
204217
{
205-
if (OperatingSystem.IsWindows()) return "Windows";
206-
if (OperatingSystem.IsLinux()) return "Linux";
207-
if (OperatingSystem.IsMacOS()) return "MacOS";
208-
return "Unknown";
218+
if (OperatingSystem.IsWindows()) return "windows";
219+
if (OperatingSystem.IsLinux()) return "linux";
220+
if (OperatingSystem.IsMacOS()) return "macos";
221+
return "unknown";
209222
}
210223

211224
private async Task RunDownloadAsync(CancellationToken token)

.clinerules/05-generalupdate-troubleshoot.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ tags: ["dotnet", "generalupdate"]
6969

7070
```bash
7171
# 测试 API 是否可访问
72-
curl -v -X POST "http://your-server/Upgrade/Verification" -H "Content-Type: application/json" -d '{"appKey":"xxx","appType":1,"clientVersion":"1.0.0.1","productId":"xxx","platform":"windows","tenantId":"default"}'
72+
curl -v -X POST "http://your-server/Upgrade/Verification" -H "Content-Type: application/json" -d '{"appKey":"xxx","appType":0,"clientVersion":"1.0.0.1","productId":"xxx","platform":"windows","tenantId":"default"}'
7373

74-
# 验证 manifest.json 内容
75-
cat /path/to/app/manifest.json | jq .
74+
# 验证 generalupdate.manifest.json 内容
75+
cat /path/to/app/generalupdate.manifest.json | jq .
7676

7777
# 检查 UpgradeApp 是否存在
7878
ls -la /path/to/app/Upgrade/Upgrade.exe

0 commit comments

Comments
 (0)