dsh-folia 4.15.1 → 4.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +11 -0
- package/lib/routes/site-media.js +49 -1
- package/lib/routes/skins.js +49 -7
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -18653,6 +18653,17 @@ window.__ModuleLoader__.load({
|
|
|
18653
18653
|
[s.version ? "v" + s.version : "", skinFmtBytes(s.bytes), s.hasHooks ? weT("含动效钩子") : "",
|
|
18654
18654
|
notListed ? weT("皮肤中心未收录") : ""]
|
|
18655
18655
|
.filter(Boolean).join(" · ")),
|
|
18656
|
+
// 未收录时把**原因直接印在卡片上**(不只放在 title 里):实测用户看不到悬浮提示,
|
|
18657
|
+
// 只能看到一句"未收录",于是只能来问"为什么"。
|
|
18658
|
+
notListed
|
|
18659
|
+
? React.createElement("div", {
|
|
18660
|
+
style: { fontSize: "11px", lineHeight: "1.5", color: "#ffd08a", opacity: 0.95, margin: "2px 0 4px" },
|
|
18661
|
+
}, weT("原因:{why}", {
|
|
18662
|
+
why: (Array.isArray(s.catalogWarnings) && s.catalogWarnings.length)
|
|
18663
|
+
? s.catalogWarnings.slice(0, 2).join(";")
|
|
18664
|
+
: weT("皮肤中心没有给出原因(常见于它还没重扫,或它的用户皮肤目录与我们的不同)"),
|
|
18665
|
+
}))
|
|
18666
|
+
: null,
|
|
18656
18667
|
React.createElement("div", { style: { display: "flex", gap: "6px", alignItems: "center" } },
|
|
18657
18668
|
// 「应用」= 写皮肤中心的 active + 广播 `dsh-skin-applied`,就地切换(不刷新页面)。
|
|
18658
18669
|
isActive
|
package/lib/routes/site-media.js
CHANGED
|
@@ -276,6 +276,47 @@ export function registerSiteMediaRoutes(webServer, c) {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
// ── 计数与统计:别的机器"慢"到底慢在哪,必须有可读的数字,而不是靠感觉 ──────────
|
|
280
|
+
// hits/misses 区分"没缓存所以每次都要下"与"缓存生效但网络慢";
|
|
281
|
+
// bytes/ms 给出每次都真实吞吐;writable 说明那台机器的缓存目录到底能不能写。
|
|
282
|
+
const stats = { hits: 0, misses: 0, bytes: 0, ms: 0, failures: 0, since: Date.now() };
|
|
283
|
+
|
|
284
|
+
disposers.push(webServer.register({
|
|
285
|
+
kind: 'exact',
|
|
286
|
+
path: `${BASE}/site-media-stats`,
|
|
287
|
+
handler: (req, res) => {
|
|
288
|
+
let total = 0;
|
|
289
|
+
let files = 0;
|
|
290
|
+
try {
|
|
291
|
+
const dir = cacheRoot();
|
|
292
|
+
for (const name of readdirSync(dir)) {
|
|
293
|
+
if (name.endsWith('.part') || name.endsWith('.part.json')) continue;
|
|
294
|
+
try { total += statSync(join(dir, name)).size; files += 1; } catch { /* ignore */ }
|
|
295
|
+
}
|
|
296
|
+
} catch { /* 目录不存在即 0 */ }
|
|
297
|
+
const dirRoot = cacheRoot();
|
|
298
|
+
const body = JSON.stringify({
|
|
299
|
+
ok: true,
|
|
300
|
+
root: dirRoot,
|
|
301
|
+
writable: ensureCache(dirRoot),
|
|
302
|
+
files,
|
|
303
|
+
bytesOnDisk: total,
|
|
304
|
+
capBytes: capBytes(),
|
|
305
|
+
hits: stats.hits,
|
|
306
|
+
misses: stats.misses,
|
|
307
|
+
failures: stats.failures,
|
|
308
|
+
avgMBps: stats.ms > 0 ? Number(((stats.bytes / 1048576) / (stats.ms / 1000)).toFixed(2)) : null,
|
|
309
|
+
uptimeSec: Math.round((Date.now() - stats.since) / 1000),
|
|
310
|
+
});
|
|
311
|
+
try {
|
|
312
|
+
res.statusCode = 200;
|
|
313
|
+
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
314
|
+
res.setHeader('cache-control', 'no-store');
|
|
315
|
+
res.end(body);
|
|
316
|
+
} catch { /* ignore */ }
|
|
317
|
+
},
|
|
318
|
+
}));
|
|
319
|
+
|
|
279
320
|
disposers.push(webServer.register({
|
|
280
321
|
kind: 'prefix',
|
|
281
322
|
// 路径尾部只是给客户端留一个"像文件名"的段(客户端按 `url.split('/').pop()` 取 token 做
|
|
@@ -301,6 +342,7 @@ export function registerSiteMediaRoutes(webServer, c) {
|
|
|
301
342
|
const abs = join(dir, key);
|
|
302
343
|
|
|
303
344
|
if (ensureCache(dir) && existsSync(abs)) {
|
|
345
|
+
stats.hits += 1;
|
|
304
346
|
touch(abs);
|
|
305
347
|
// 与 miss 对称的可观测头:页签/诊断据此区分"网络慢"与"我们没有缓存"。
|
|
306
348
|
try { res.setHeader('x-folia-cache', 'hit'); } catch { /* ignore */ }
|
|
@@ -317,7 +359,13 @@ export function registerSiteMediaRoutes(webServer, c) {
|
|
|
317
359
|
redirect(res, u);
|
|
318
360
|
return;
|
|
319
361
|
}
|
|
320
|
-
|
|
362
|
+
stats.misses += 1;
|
|
363
|
+
const t0 = Date.now();
|
|
364
|
+
const task = download(u, key, req, res, headOnly).then((ok) => {
|
|
365
|
+
stats.ms += Date.now() - t0;
|
|
366
|
+
if (ok) { try { stats.bytes += statSync(abs).size; } catch { /* ignore */ } }
|
|
367
|
+
return ok;
|
|
368
|
+
}).catch((e) => { stats.failures += 1; throw e; });
|
|
321
369
|
inflight.set(key, task);
|
|
322
370
|
let ok = false;
|
|
323
371
|
try { ok = await task; } finally { inflight.delete(key); }
|
package/lib/routes/skins.js
CHANGED
|
@@ -39,8 +39,44 @@ const SKIP_EXEC_RE = /\.(exe|dll|bat|cmd|com|scr|ps1|sh|msi|vbs|wsf|hta|lnk|jar)
|
|
|
39
39
|
function dshHome() {
|
|
40
40
|
return process.env.DSH_HOME || join(process.env.APPDATA || homedir(), 'dsh-desktop', 'harness');
|
|
41
41
|
}
|
|
42
|
-
/**
|
|
43
|
-
function skinsRoot() { return join(
|
|
42
|
+
/** 皮肤根:**皮肤中心真正在用的那个目录**下的 `skins`(见 skinCenterHome 的发现逻辑)。 */
|
|
43
|
+
function skinsRoot() { return join(skinCenterHome().root, 'skins'); }
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* **发现皮肤中心真正在用的那个数据目录**(而不是假定它就是我们的 `$DSH_HOME`)。
|
|
47
|
+
*
|
|
48
|
+
* 为什么必须发现:皮肤中心解析用户皮肤目录走的是它自己的 `resolveHarnessPaths()` ——
|
|
49
|
+
* 优先 `$DSH_HOME`,其次安装布局,**最后才是 `~/.dsh`**;而我们这边用的是
|
|
50
|
+
* `$DSH_HOME || %APPDATA%/dsh-desktop/harness`。两者在"便携安装 / 没设 DSH_HOME"的机器上
|
|
51
|
+
* 会指向**不同目录**:我们把皮肤装进 harness/skins,它却去 `~/.dsh/skins` 找 ⇒ 它列表里
|
|
52
|
+
* 只剩内置那一套,用户点哪套都无效(实测症状:只剩鲸鱼娘/blue-fantasy)。
|
|
53
|
+
*
|
|
54
|
+
* 判据不看猜:**它自己会把 `skin-center-active.json` 写在 `<它的用户皮肤目录>/..`**,
|
|
55
|
+
* 所以"哪个候选目录里存在这个文件、且最近被写过",那个就是它在用的目录。
|
|
56
|
+
* 拿不到任何线索时退回我们自己的目录(也就是今天的行为,不会更差)。
|
|
57
|
+
*/
|
|
58
|
+
const homeCache = { at: 0, value: null };
|
|
59
|
+
function skinCenterHome(force) {
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
if (!force && homeCache.value && now - homeCache.at < 30000) return homeCache.value;
|
|
62
|
+
const candidates = [];
|
|
63
|
+
const push = (root) => { if (root) candidates.push(root); };
|
|
64
|
+
push(dshHome());
|
|
65
|
+
push(join(homedir(), '.dsh'));
|
|
66
|
+
if (process.env.DSH_SKINS_DIR) push(join(process.env.DSH_SKINS_DIR, '..'));
|
|
67
|
+
let best = null;
|
|
68
|
+
for (const root of candidates) {
|
|
69
|
+
const statePath = join(root, 'skin-center-active.json');
|
|
70
|
+
const at = (() => { try { return statSync(statePath).mtimeMs; } catch { return 0; } })();
|
|
71
|
+
if (!best || at > best.at) best = { root, statePath, at };
|
|
72
|
+
}
|
|
73
|
+
// 没有任何 state 文件时,退回"我们的根":安装路径保持不变,行为与之前一致。
|
|
74
|
+
const chosen = (best && best.at > 0) ? best : { root: dshHome(), statePath: activeStatePathFallback(), at: 0 };
|
|
75
|
+
homeCache.at = now;
|
|
76
|
+
homeCache.value = chosen;
|
|
77
|
+
return chosen;
|
|
78
|
+
}
|
|
79
|
+
function activeStatePathFallback() { return join(dshHome(), 'skin-center-active.json'); }
|
|
44
80
|
|
|
45
81
|
/**
|
|
46
82
|
* 皮肤中心持久化"当前皮肤"的那个文件。
|
|
@@ -52,7 +88,7 @@ function skinsRoot() { return join(dshHome(), 'skins'); }
|
|
|
52
88
|
* 并且客户端每 2 秒轮询一次 GET 收敛 —— 所以写这个文件等价于"替它记下选择",可靠且即时。
|
|
53
89
|
* 语义上只改 `active` 一个键,其余原样保留。
|
|
54
90
|
*/
|
|
55
|
-
function activeStatePath() { return
|
|
91
|
+
function activeStatePath() { return skinCenterHome().statePath; }
|
|
56
92
|
|
|
57
93
|
/** 把 `active` 写进皮肤中心的持久层(保留其它字段;原子替换,失败不破坏原文件)。 */
|
|
58
94
|
function writeActiveStateFile(id) {
|
|
@@ -433,10 +469,11 @@ function normalizeSkinManifest(manifestPath, base) {
|
|
|
433
469
|
const cssPath = str(doc.contributes.stylesheet);
|
|
434
470
|
const cssExists = cssPath && (() => { try { return statSync(join(dir, cssPath)).isFile(); } catch { return false; } })();
|
|
435
471
|
if (!cssExists) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
472
|
+
const pick = (() => {
|
|
473
|
+
try {
|
|
474
|
+
return readdirSync(dir).find((n) => n.toLowerCase().endsWith('.css') && statSync(join(dir, n)).isFile()) || '';
|
|
475
|
+
} catch { return ''; }
|
|
476
|
+
})();
|
|
440
477
|
if (pick) { doc.contributes.stylesheet = pick; fixes.push('stylesheet'); }
|
|
441
478
|
else { delete doc.contributes.stylesheet; fixes.push('stylesheet-missing'); }
|
|
442
479
|
}
|
|
@@ -518,6 +555,11 @@ export function registerSkinsRoutes(webServer, c) {
|
|
|
518
555
|
skinCenterDisabled: market.skinCenterDisabled,
|
|
519
556
|
skinCenterMarketKnown: market.known,
|
|
520
557
|
skinCenterDetail: skinCenterProbe.detail || '',
|
|
558
|
+
// 诊断用:我们**实际读写**的目录 vs 皮肤中心自己的数据根。
|
|
559
|
+
// 便携安装 / 未设 DSH_HOME 的机器上两者曾不一致(它去 `~/.dsh` 找,我们写进 harness)
|
|
560
|
+
// ⇒ 它的列表里只剩内置款、点哪套都无效。现在由 skinCenterHome() 发现并统一,这里透出结果。
|
|
561
|
+
skinsRoot: skinsRoot(),
|
|
562
|
+
skinCenterRoot: skinCenterHome().root,
|
|
521
563
|
// 版本线索:我们随身携带的是 0.4.12(engines 要求 dsh >= 0.2.1-rc.3)。
|
|
522
564
|
// 低于该内核的机器会被市场 keep off —— 页签据此告诉用户"要么升级桌面端、要么别指望它"。
|
|
523
565
|
skinCenterHint: skinCenter === 'present'
|