karin-plugin-qgroup-file2openlist 0.0.26 → 0.0.27
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/README.md +1 -0
- package/config/config.json +2 -1
- package/lib/apps/groupFiles.backup.js +3 -3
- package/lib/apps/groupFiles.js +3 -3
- package/lib/apps/groupSyncConfig.js +4 -4
- package/lib/apps/opCommands.js +2 -2
- package/lib/apps/ownerUi.js +2 -2
- package/lib/apps/scheduler.js +5 -5
- package/lib/{chunk-WQFR5LF3.js → chunk-5HRWYTEM.js} +1 -1
- package/lib/{chunk-5QW7XYQX.js → chunk-DDV3OZXB.js} +1 -1
- package/lib/{chunk-YVFRUAZO.js → chunk-GDVU2T6R.js} +286 -178
- package/lib/{chunk-I2I6ZPJU.js → chunk-P5FZDWLZ.js} +2 -2
- package/lib/{chunk-BWVJMEKK.js → chunk-R4MRR5TI.js} +8 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ TypeScript 插件开发流程现在更加简单,无需手动克隆模板仓库
|
|
|
51
51
|
- `openlistUsername` / `openlistPassword`:用于 WebDAV BasicAuth 登录
|
|
52
52
|
- `openlistTargetDir`:目标目录(例:`/挂载目录/QQ群文件`)
|
|
53
53
|
- `resourceLimits.transferConcurrency`:全局传输并发上限(所有下载+上传共享;生产环境建议 1,避免同时传输吃满内存;<=0 不限制)
|
|
54
|
+
- `resourceLimits.largeFileSpoolThresholdMB`:大文件落盘阈值(MB,默认 200;<=0 禁用;大文件会先下载到本地临时文件再上传,降低内存/缓冲压力)
|
|
54
55
|
- `groupSyncDefaults`:群同步默认策略(并发、单文件超时、重试等)
|
|
55
56
|
- `groupSyncTargets`:同步对象群配置(每群可单独覆盖目录/并发/同步时段等)
|
|
56
57
|
|
package/config/config.json
CHANGED
|
@@ -6,12 +6,12 @@ import "../chunk-PBBZ5KAD.js";
|
|
|
6
6
|
import "../chunk-QB3GSENE.js";
|
|
7
7
|
import {
|
|
8
8
|
handleGroupFileUploadedAutoBackup
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-R4MRR5TI.js";
|
|
10
|
+
import "../chunk-DDV3OZXB.js";
|
|
11
11
|
import {
|
|
12
12
|
backupOpenListToOpenListCore,
|
|
13
13
|
formatErrorMessage
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-GDVU2T6R.js";
|
|
15
15
|
import {
|
|
16
16
|
config
|
|
17
17
|
} from "../chunk-DA4U55JC.js";
|
package/lib/apps/groupFiles.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
exportGroupFilesToDisk,
|
|
3
3
|
syncGroupFilesToOpenListCore
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-R4MRR5TI.js";
|
|
5
|
+
import "../chunk-DDV3OZXB.js";
|
|
6
6
|
import {
|
|
7
7
|
formatErrorMessage,
|
|
8
8
|
normalizePosixPath
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-GDVU2T6R.js";
|
|
10
10
|
import {
|
|
11
11
|
config
|
|
12
12
|
} from "../chunk-DA4U55JC.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handleGroupSyncConfigCommand
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-P5FZDWLZ.js";
|
|
4
|
+
import "../chunk-R4MRR5TI.js";
|
|
5
|
+
import "../chunk-DDV3OZXB.js";
|
|
6
|
+
import "../chunk-GDVU2T6R.js";
|
|
7
7
|
import "../chunk-DA4U55JC.js";
|
|
8
8
|
import "../chunk-IZS467MR.js";
|
|
9
9
|
|
package/lib/apps/opCommands.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
resolveOpltMapping,
|
|
6
6
|
withOpltUser,
|
|
7
7
|
writeOpltData
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-5HRWYTEM.js";
|
|
9
9
|
import {
|
|
10
10
|
buildActionCard,
|
|
11
11
|
replyImages
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
normalizePosixPath,
|
|
21
21
|
readJsonSafe,
|
|
22
22
|
writeJsonSafe
|
|
23
|
-
} from "../chunk-
|
|
23
|
+
} from "../chunk-GDVU2T6R.js";
|
|
24
24
|
import {
|
|
25
25
|
config
|
|
26
26
|
} from "../chunk-DA4U55JC.js";
|
package/lib/apps/ownerUi.js
CHANGED
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
import "../chunk-QB3GSENE.js";
|
|
5
5
|
import {
|
|
6
6
|
readGroupSyncState
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-DDV3OZXB.js";
|
|
8
8
|
import {
|
|
9
9
|
backupOpenListToOpenListCore,
|
|
10
10
|
formatErrorMessage,
|
|
11
11
|
normalizePosixPath,
|
|
12
12
|
readJsonSafe,
|
|
13
13
|
writeJsonSafe
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-GDVU2T6R.js";
|
|
15
15
|
import "../chunk-DA4U55JC.js";
|
|
16
16
|
import {
|
|
17
17
|
dir
|
package/lib/apps/scheduler.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runNightlyOpltBackup
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-5HRWYTEM.js";
|
|
4
4
|
import {
|
|
5
5
|
runNightlyGroupBackup
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import "../chunk-
|
|
8
|
-
import "../chunk-
|
|
9
|
-
import "../chunk-
|
|
6
|
+
} from "../chunk-P5FZDWLZ.js";
|
|
7
|
+
import "../chunk-R4MRR5TI.js";
|
|
8
|
+
import "../chunk-DDV3OZXB.js";
|
|
9
|
+
import "../chunk-GDVU2T6R.js";
|
|
10
10
|
import {
|
|
11
11
|
config
|
|
12
12
|
} from "../chunk-DA4U55JC.js";
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
config
|
|
3
3
|
} from "./chunk-DA4U55JC.js";
|
|
4
|
+
import {
|
|
5
|
+
dir
|
|
6
|
+
} from "./chunk-IZS467MR.js";
|
|
4
7
|
|
|
5
8
|
// src/model/shared/path.ts
|
|
6
9
|
var normalizePosixPath = (inputPath, { ensureLeadingSlash = true, stripTrailingSlash = true } = {}) => {
|
|
@@ -49,9 +52,27 @@ var fetchTextSafely = async (res, maxLen = 500) => {
|
|
|
49
52
|
}
|
|
50
53
|
};
|
|
51
54
|
|
|
52
|
-
// src/model/
|
|
55
|
+
// src/model/shared/fsJson.ts
|
|
56
|
+
import fs from "fs";
|
|
53
57
|
import path from "path";
|
|
54
|
-
|
|
58
|
+
var ensureDir = (dirPath) => fs.mkdirSync(dirPath, { recursive: true });
|
|
59
|
+
var readJsonSafe = (filePath) => {
|
|
60
|
+
try {
|
|
61
|
+
if (!fs.existsSync(filePath)) return {};
|
|
62
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
63
|
+
return raw ? JSON.parse(raw) : {};
|
|
64
|
+
} catch {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var writeJsonSafe = (filePath, data) => {
|
|
69
|
+
ensureDir(path.dirname(filePath));
|
|
70
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/model/openlist/backup.ts
|
|
74
|
+
import path3 from "path";
|
|
75
|
+
import { logger as logger4 } from "node-karin";
|
|
55
76
|
|
|
56
77
|
// src/model/shared/concurrency.ts
|
|
57
78
|
var runWithConcurrency = async (items, concurrency, fn) => {
|
|
@@ -176,6 +197,7 @@ var buildOpenListAuthHeader = (username, password) => {
|
|
|
176
197
|
};
|
|
177
198
|
|
|
178
199
|
// src/model/openlist/api.ts
|
|
200
|
+
import fs3 from "fs";
|
|
179
201
|
import { Readable } from "stream";
|
|
180
202
|
import { setTimeout as sleep } from "timers/promises";
|
|
181
203
|
|
|
@@ -247,7 +269,85 @@ var withGlobalTransferLimit = async (label, fn) => {
|
|
|
247
269
|
}
|
|
248
270
|
};
|
|
249
271
|
|
|
272
|
+
// src/model/shared/transferSpool.ts
|
|
273
|
+
import fs2 from "fs";
|
|
274
|
+
import path2 from "path";
|
|
275
|
+
import { randomUUID } from "crypto";
|
|
276
|
+
import { pipeline } from "stream/promises";
|
|
277
|
+
var DEFAULT_LARGE_FILE_SPOOL_THRESHOLD_MB = 200;
|
|
278
|
+
var MAX_THRESHOLD_MB = 5e4;
|
|
279
|
+
var cachedThresholdBytes = DEFAULT_LARGE_FILE_SPOOL_THRESHOLD_MB * 1024 * 1024;
|
|
280
|
+
var cachedAt2 = 0;
|
|
281
|
+
var CACHE_MS2 = 1e3;
|
|
282
|
+
var clampInt2 = (value, min, max) => Math.min(max, Math.max(min, Math.floor(value)));
|
|
283
|
+
var parseContentLengthHeader = (value) => {
|
|
284
|
+
const raw = String(value ?? "").trim();
|
|
285
|
+
if (!raw) return void 0;
|
|
286
|
+
const n = Number(raw);
|
|
287
|
+
if (!Number.isFinite(n)) return void 0;
|
|
288
|
+
const v = Math.floor(n);
|
|
289
|
+
if (v <= 0) return void 0;
|
|
290
|
+
return v;
|
|
291
|
+
};
|
|
292
|
+
var getLargeFileSpoolThresholdBytes = () => {
|
|
293
|
+
const now = Date.now();
|
|
294
|
+
if (now - cachedAt2 < CACHE_MS2) return cachedThresholdBytes;
|
|
295
|
+
cachedAt2 = now;
|
|
296
|
+
try {
|
|
297
|
+
const cfg = config();
|
|
298
|
+
const raw = cfg?.resourceLimits?.largeFileSpoolThresholdMB;
|
|
299
|
+
if (raw === void 0 || raw === null || raw === "") {
|
|
300
|
+
cachedThresholdBytes = DEFAULT_LARGE_FILE_SPOOL_THRESHOLD_MB * 1024 * 1024;
|
|
301
|
+
return cachedThresholdBytes;
|
|
302
|
+
}
|
|
303
|
+
const n = typeof raw === "number" ? raw : Number(raw);
|
|
304
|
+
if (!Number.isFinite(n)) {
|
|
305
|
+
cachedThresholdBytes = DEFAULT_LARGE_FILE_SPOOL_THRESHOLD_MB * 1024 * 1024;
|
|
306
|
+
return cachedThresholdBytes;
|
|
307
|
+
}
|
|
308
|
+
if (n <= 0) {
|
|
309
|
+
cachedThresholdBytes = 0;
|
|
310
|
+
return cachedThresholdBytes;
|
|
311
|
+
}
|
|
312
|
+
const mb = clampInt2(n, 1, MAX_THRESHOLD_MB);
|
|
313
|
+
cachedThresholdBytes = mb * 1024 * 1024;
|
|
314
|
+
return cachedThresholdBytes;
|
|
315
|
+
} catch {
|
|
316
|
+
cachedThresholdBytes = DEFAULT_LARGE_FILE_SPOOL_THRESHOLD_MB * 1024 * 1024;
|
|
317
|
+
return cachedThresholdBytes;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
var shouldSpoolToDisk = (sizeBytes) => {
|
|
321
|
+
const threshold = getLargeFileSpoolThresholdBytes();
|
|
322
|
+
if (!threshold) return false;
|
|
323
|
+
if (typeof sizeBytes !== "number" || !Number.isFinite(sizeBytes)) return false;
|
|
324
|
+
return Math.floor(sizeBytes) >= threshold;
|
|
325
|
+
};
|
|
326
|
+
var safeFileName = (value) => {
|
|
327
|
+
return String(value ?? "").replaceAll("\0", "").replace(/[^\w.-]+/g, "_").replace(/^_+/, "").slice(0, 50) || "transfer";
|
|
328
|
+
};
|
|
329
|
+
var getTransferTmpDir = () => {
|
|
330
|
+
const tmpDir = path2.join(dir.DataDir, "transfer-tmp");
|
|
331
|
+
ensureDir(tmpDir);
|
|
332
|
+
return tmpDir;
|
|
333
|
+
};
|
|
334
|
+
var createTransferTempFilePath = (prefix) => {
|
|
335
|
+
const tmpDir = getTransferTmpDir();
|
|
336
|
+
const name = `${safeFileName(prefix)}-${Date.now()}-${randomUUID()}.tmp`;
|
|
337
|
+
return path2.join(tmpDir, name);
|
|
338
|
+
};
|
|
339
|
+
var streamToFile = async (readable, filePath, options) => {
|
|
340
|
+
await pipeline(readable, fs2.createWriteStream(filePath), { signal: options?.signal });
|
|
341
|
+
};
|
|
342
|
+
var safeUnlink = async (filePath) => {
|
|
343
|
+
try {
|
|
344
|
+
await fs2.promises.unlink(filePath);
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
250
349
|
// src/model/openlist/api.ts
|
|
350
|
+
import { logger as logger2 } from "node-karin";
|
|
251
351
|
var openlistApiReadJson = async (res) => {
|
|
252
352
|
try {
|
|
253
353
|
return await res.json();
|
|
@@ -441,12 +541,13 @@ var createOpenListApiDirEnsurer = (baseUrl, token, timeoutMs) => {
|
|
|
441
541
|
return { ensureDir: ensureDir2 };
|
|
442
542
|
};
|
|
443
543
|
var downloadAndUploadByOpenListApiPut = async (params) => {
|
|
444
|
-
const { sourceUrl, sourceHeaders, targetBaseUrl, targetToken, targetPath, timeoutMs } = params;
|
|
544
|
+
const { sourceUrl, sourceHeaders, targetBaseUrl, targetToken, targetPath, timeoutMs, expectedSize } = params;
|
|
445
545
|
const apiBaseUrl = buildOpenListApiBaseUrl(targetBaseUrl);
|
|
446
546
|
if (!apiBaseUrl) throw new Error("\u76EE\u6807 OpenList API \u5730\u5740\u4E0D\u6B63\u786E\uFF0C\u8BF7\u68C0\u67E5\u76EE\u6807 OpenList \u5730\u5740");
|
|
447
547
|
await withGlobalTransferLimit(`downloadAndUploadByOpenListApiPut:${targetPath}`, async () => {
|
|
448
548
|
const controller = new AbortController();
|
|
449
549
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
550
|
+
let tmpFilePath;
|
|
450
551
|
try {
|
|
451
552
|
let downloadRes;
|
|
452
553
|
try {
|
|
@@ -470,16 +571,28 @@ var downloadAndUploadByOpenListApiPut = async (params) => {
|
|
|
470
571
|
const authToken = String(targetToken ?? "").trim();
|
|
471
572
|
if (authToken) headers.Authorization = authToken;
|
|
472
573
|
const contentType = downloadRes.headers.get("content-type");
|
|
473
|
-
const contentLength = downloadRes.headers.get("content-length");
|
|
574
|
+
const contentLength = parseContentLengthHeader(downloadRes.headers.get("content-length"));
|
|
474
575
|
if (contentType) headers["Content-Type"] = contentType;
|
|
475
|
-
if (contentLength) headers["Content-Length"] = contentLength;
|
|
476
|
-
const
|
|
576
|
+
if (contentLength) headers["Content-Length"] = String(contentLength);
|
|
577
|
+
const sizeForDecision = typeof expectedSize === "number" && Number.isFinite(expectedSize) && expectedSize > 0 ? Math.floor(expectedSize) : contentLength;
|
|
578
|
+
const spoolToDisk = shouldSpoolToDisk(sizeForDecision);
|
|
477
579
|
let putRes;
|
|
478
580
|
try {
|
|
581
|
+
let body;
|
|
582
|
+
if (spoolToDisk) {
|
|
583
|
+
tmpFilePath = createTransferTempFilePath("openlist-api-put");
|
|
584
|
+
logger2.info(`[\u4F20\u8F93][\u843D\u76D8] \u68C0\u6D4B\u5230\u5927\u6587\u4EF6\uFF0C\u5C06\u5148\u4E0B\u8F7D\u843D\u76D8\u518D\u4E0A\u4F20(API): ${targetPath}`);
|
|
585
|
+
await streamToFile(Readable.fromWeb(downloadRes.body), tmpFilePath, { signal: controller.signal });
|
|
586
|
+
const stat = await fs3.promises.stat(tmpFilePath);
|
|
587
|
+
headers["Content-Length"] = String(stat.size);
|
|
588
|
+
body = fs3.createReadStream(tmpFilePath);
|
|
589
|
+
} else {
|
|
590
|
+
body = Readable.fromWeb(downloadRes.body);
|
|
591
|
+
}
|
|
479
592
|
putRes = await fetch(`${apiBaseUrl}/fs/put`, {
|
|
480
593
|
method: "PUT",
|
|
481
594
|
headers,
|
|
482
|
-
body
|
|
595
|
+
body,
|
|
483
596
|
// @ts-expect-error Node fetch streaming body requires duplex
|
|
484
597
|
duplex: "half",
|
|
485
598
|
redirect: "follow",
|
|
@@ -498,13 +611,21 @@ var downloadAndUploadByOpenListApiPut = async (params) => {
|
|
|
498
611
|
const msg = json?.message ? ` - ${json.message}` : "";
|
|
499
612
|
throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: code=${json.code}${msg}`);
|
|
500
613
|
}
|
|
614
|
+
} catch (error) {
|
|
615
|
+
try {
|
|
616
|
+
controller.abort();
|
|
617
|
+
} catch {
|
|
618
|
+
}
|
|
619
|
+
throw error;
|
|
501
620
|
} finally {
|
|
502
621
|
clearTimeout(timer);
|
|
622
|
+
if (tmpFilePath) await safeUnlink(tmpFilePath);
|
|
503
623
|
}
|
|
504
624
|
});
|
|
505
625
|
};
|
|
506
626
|
|
|
507
627
|
// src/model/openlist/webdav.ts
|
|
628
|
+
import fs4 from "fs";
|
|
508
629
|
import { Readable as Readable2 } from "stream";
|
|
509
630
|
import { setTimeout as sleep3 } from "timers/promises";
|
|
510
631
|
|
|
@@ -532,6 +653,7 @@ var createThrottleTransform = (bytesPerSec) => {
|
|
|
532
653
|
};
|
|
533
654
|
|
|
534
655
|
// src/model/openlist/webdav.ts
|
|
656
|
+
import { logger as logger3 } from "node-karin";
|
|
535
657
|
var isRetryableWebDavError = (error) => {
|
|
536
658
|
const msg = formatErrorMessage(error);
|
|
537
659
|
return /ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|ECONNREFUSED|UND_ERR|socket hang up/i.test(msg);
|
|
@@ -663,6 +785,7 @@ var copyWebDavToWebDav = async (params) => {
|
|
|
663
785
|
await withGlobalTransferLimit(`copyWebDavToWebDav:${sourcePath}=>${targetPath}`, async () => {
|
|
664
786
|
const controller = new AbortController();
|
|
665
787
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
788
|
+
let tmpFilePath;
|
|
666
789
|
try {
|
|
667
790
|
const sourceUrl = `${sourceDavBaseUrl}${encodePathForUrl(sourcePath)}`;
|
|
668
791
|
const targetUrl = `${targetDavBaseUrl}${encodePathForUrl(targetPath)}`;
|
|
@@ -688,16 +811,27 @@ var copyWebDavToWebDav = async (params) => {
|
|
|
688
811
|
if (!downloadRes.body) throw new Error("\u6E90\u7AEF\u8BFB\u53D6\u5931\u8D25: \u54CD\u5E94\u4F53\u4E3A\u7A7A");
|
|
689
812
|
const headers = { Authorization: targetAuth };
|
|
690
813
|
const contentType = downloadRes.headers.get("content-type");
|
|
691
|
-
const contentLength = downloadRes.headers.get("content-length");
|
|
814
|
+
const contentLength = parseContentLengthHeader(downloadRes.headers.get("content-length"));
|
|
692
815
|
if (contentType) headers["Content-Type"] = contentType;
|
|
693
|
-
if (contentLength) headers["Content-Length"] = contentLength;
|
|
694
|
-
const
|
|
816
|
+
if (contentLength) headers["Content-Length"] = String(contentLength);
|
|
817
|
+
const spoolToDisk = shouldSpoolToDisk(contentLength);
|
|
695
818
|
let putRes;
|
|
696
819
|
try {
|
|
820
|
+
let body;
|
|
821
|
+
if (spoolToDisk) {
|
|
822
|
+
tmpFilePath = createTransferTempFilePath("webdav-copy");
|
|
823
|
+
logger3.info(`[\u4F20\u8F93][\u843D\u76D8] WebDAV\u590D\u5236\u68C0\u6D4B\u5230\u5927\u6587\u4EF6\uFF0C\u5C06\u5148\u4E0B\u8F7D\u843D\u76D8\u518D\u4E0A\u4F20: ${sourcePath} -> ${targetPath}`);
|
|
824
|
+
await streamToFile(Readable2.fromWeb(downloadRes.body), tmpFilePath, { signal: controller.signal });
|
|
825
|
+
const stat = await fs4.promises.stat(tmpFilePath);
|
|
826
|
+
headers["Content-Length"] = String(stat.size);
|
|
827
|
+
body = fs4.createReadStream(tmpFilePath);
|
|
828
|
+
} else {
|
|
829
|
+
body = Readable2.fromWeb(downloadRes.body);
|
|
830
|
+
}
|
|
697
831
|
putRes = await fetch(targetUrl, {
|
|
698
832
|
method: "PUT",
|
|
699
833
|
headers,
|
|
700
|
-
body
|
|
834
|
+
body,
|
|
701
835
|
// @ts-expect-error Node fetch streaming body requires duplex
|
|
702
836
|
duplex: "half",
|
|
703
837
|
redirect: "follow",
|
|
@@ -711,8 +845,15 @@ var copyWebDavToWebDav = async (params) => {
|
|
|
711
845
|
const body = await fetchTextSafely(putRes);
|
|
712
846
|
throw new Error(`\u76EE\u6807\u7AEF\u5199\u5165\u5931\u8D25: ${putRes.status} ${putRes.statusText}${body ? ` - ${body}` : ""}`);
|
|
713
847
|
}
|
|
848
|
+
} catch (error) {
|
|
849
|
+
try {
|
|
850
|
+
controller.abort();
|
|
851
|
+
} catch {
|
|
852
|
+
}
|
|
853
|
+
throw error;
|
|
714
854
|
} finally {
|
|
715
855
|
clearTimeout(timer);
|
|
856
|
+
if (tmpFilePath) await safeUnlink(tmpFilePath);
|
|
716
857
|
}
|
|
717
858
|
});
|
|
718
859
|
};
|
|
@@ -768,10 +909,11 @@ var createWebDavDirEnsurer = (davBaseUrl, auth, timeoutMs) => {
|
|
|
768
909
|
return { ensureDir: ensureDir2 };
|
|
769
910
|
};
|
|
770
911
|
var downloadAndUploadByWebDav = async (params) => {
|
|
771
|
-
const { sourceUrl, sourceHeaders, targetUrl, auth, timeoutMs, rateLimitBytesPerSec } = params;
|
|
912
|
+
const { sourceUrl, sourceHeaders, targetUrl, auth, timeoutMs, rateLimitBytesPerSec, expectedSize } = params;
|
|
772
913
|
await withGlobalTransferLimit(`downloadAndUploadByWebDav:${targetUrl}`, async () => {
|
|
773
914
|
const controller = new AbortController();
|
|
774
915
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
916
|
+
let tmpFilePath;
|
|
775
917
|
try {
|
|
776
918
|
let downloadRes;
|
|
777
919
|
try {
|
|
@@ -793,14 +935,26 @@ var downloadAndUploadByWebDav = async (params) => {
|
|
|
793
935
|
Authorization: auth
|
|
794
936
|
};
|
|
795
937
|
const contentType = downloadRes.headers.get("content-type");
|
|
796
|
-
const contentLength = downloadRes.headers.get("content-length");
|
|
938
|
+
const contentLength = parseContentLengthHeader(downloadRes.headers.get("content-length"));
|
|
797
939
|
if (contentType) headers["Content-Type"] = contentType;
|
|
798
|
-
if (contentLength) headers["Content-Length"] = contentLength;
|
|
799
|
-
|
|
800
|
-
const
|
|
801
|
-
if (throttle) bodyStream = bodyStream.pipe(throttle);
|
|
940
|
+
if (contentLength) headers["Content-Length"] = String(contentLength);
|
|
941
|
+
const sizeForDecision = typeof expectedSize === "number" && Number.isFinite(expectedSize) && expectedSize > 0 ? Math.floor(expectedSize) : contentLength;
|
|
942
|
+
const spoolToDisk = shouldSpoolToDisk(sizeForDecision);
|
|
802
943
|
let putRes;
|
|
803
944
|
try {
|
|
945
|
+
let bodyStream;
|
|
946
|
+
if (spoolToDisk) {
|
|
947
|
+
tmpFilePath = createTransferTempFilePath("webdav-download-upload");
|
|
948
|
+
logger3.info(`[\u4F20\u8F93][\u843D\u76D8] \u68C0\u6D4B\u5230\u5927\u6587\u4EF6\uFF0C\u5C06\u5148\u4E0B\u8F7D\u843D\u76D8\u518D\u4E0A\u4F20: ${targetUrl}`);
|
|
949
|
+
await streamToFile(Readable2.fromWeb(downloadRes.body), tmpFilePath, { signal: controller.signal });
|
|
950
|
+
const stat = await fs4.promises.stat(tmpFilePath);
|
|
951
|
+
headers["Content-Length"] = String(stat.size);
|
|
952
|
+
bodyStream = fs4.createReadStream(tmpFilePath);
|
|
953
|
+
} else {
|
|
954
|
+
bodyStream = Readable2.fromWeb(downloadRes.body);
|
|
955
|
+
}
|
|
956
|
+
const throttle = createThrottleTransform(rateLimitBytesPerSec || 0);
|
|
957
|
+
if (throttle) bodyStream = bodyStream.pipe(throttle);
|
|
804
958
|
putRes = await fetch(targetUrl, {
|
|
805
959
|
method: "PUT",
|
|
806
960
|
headers,
|
|
@@ -819,8 +973,15 @@ var downloadAndUploadByWebDav = async (params) => {
|
|
|
819
973
|
const hint = putRes.status === 401 ? "\uFF08\u8D26\u53F7/\u5BC6\u7801\u9519\u8BEF\uFF0C\u6216\u672A\u5F00\u542F WebDAV\uFF09" : putRes.status === 403 ? "\uFF08\u6CA1\u6709 WebDAV \u7BA1\u7406/\u5199\u5165\u6743\u9650\uFF0C\u6216\u76EE\u6807\u76EE\u5F55\u4E0D\u53EF\u5199/\u4E0D\u5728\u7528\u6237\u53EF\u8BBF\u95EE\u8303\u56F4\uFF09" : "";
|
|
820
974
|
throw new Error(`\u4E0A\u4F20\u5931\u8D25: ${putRes.status} ${putRes.statusText}${hint}${body ? ` - ${body}` : ""}`);
|
|
821
975
|
}
|
|
976
|
+
} catch (error) {
|
|
977
|
+
try {
|
|
978
|
+
controller.abort();
|
|
979
|
+
} catch {
|
|
980
|
+
}
|
|
981
|
+
throw error;
|
|
822
982
|
} finally {
|
|
823
983
|
clearTimeout(timer);
|
|
984
|
+
if (tmpFilePath) await safeUnlink(tmpFilePath);
|
|
824
985
|
}
|
|
825
986
|
});
|
|
826
987
|
};
|
|
@@ -849,66 +1010,7 @@ var normalizeOpenListBackupTransport = (value, fallback) => {
|
|
|
849
1010
|
};
|
|
850
1011
|
var buildTargetPath = (targetRoot, sourcePath) => {
|
|
851
1012
|
const rel = String(sourcePath ?? "").replace(/^\/+/, "");
|
|
852
|
-
return normalizePosixPath(
|
|
853
|
-
};
|
|
854
|
-
var scanOpenListFiles = async (params) => {
|
|
855
|
-
const {
|
|
856
|
-
sourceTransport,
|
|
857
|
-
sourceBaseUrl,
|
|
858
|
-
sourceToken,
|
|
859
|
-
sourceAuth,
|
|
860
|
-
sourceDavBaseUrl,
|
|
861
|
-
srcDir,
|
|
862
|
-
targetRoot,
|
|
863
|
-
timeoutMs,
|
|
864
|
-
perPage,
|
|
865
|
-
scanConcurrency,
|
|
866
|
-
onProgress
|
|
867
|
-
} = params;
|
|
868
|
-
const normalizedSrcDir = normalizePosixPath(String(srcDir ?? "/"));
|
|
869
|
-
const normalizedTargetRoot = normalizePosixPath(String(targetRoot ?? "/"));
|
|
870
|
-
const scanConcurrencyMax = Math.max(1, Math.min(200, Math.floor(scanConcurrency) || 20));
|
|
871
|
-
const files = [];
|
|
872
|
-
let scannedDirs = 0;
|
|
873
|
-
const listDir = async (dirPath) => {
|
|
874
|
-
if (sourceTransport === "webdav") {
|
|
875
|
-
if (!sourceDavBaseUrl) throw new Error("\u6E90\u7AEF WebDAV \u5730\u5740\u4E0D\u6B63\u786E");
|
|
876
|
-
return await webdavPropfindListEntries({
|
|
877
|
-
davBaseUrl: sourceDavBaseUrl,
|
|
878
|
-
auth: sourceAuth,
|
|
879
|
-
dirPath,
|
|
880
|
-
timeoutMs
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
return await openlistApiListEntries({
|
|
884
|
-
baseUrl: sourceBaseUrl,
|
|
885
|
-
token: sourceToken,
|
|
886
|
-
dirPath,
|
|
887
|
-
timeoutMs,
|
|
888
|
-
perPage
|
|
889
|
-
});
|
|
890
|
-
};
|
|
891
|
-
const reportProgress = () => {
|
|
892
|
-
if (!onProgress) return;
|
|
893
|
-
onProgress({ scannedDirs, files: files.length });
|
|
894
|
-
};
|
|
895
|
-
let pendingDirs = [normalizedSrcDir];
|
|
896
|
-
while (pendingDirs.length) {
|
|
897
|
-
const batch = pendingDirs.splice(0, scanConcurrencyMax);
|
|
898
|
-
await runWithConcurrency(batch, scanConcurrencyMax, async (dirPath) => {
|
|
899
|
-
scannedDirs++;
|
|
900
|
-
const entries = await listDir(dirPath);
|
|
901
|
-
for (const it of entries) {
|
|
902
|
-
const sourcePath = normalizePosixPath(path.posix.join(dirPath, it.name));
|
|
903
|
-
const targetPath = buildTargetPath(normalizedTargetRoot, sourcePath);
|
|
904
|
-
if (it.isDir) pendingDirs.push(sourcePath);
|
|
905
|
-
else files.push({ sourcePath, targetPath });
|
|
906
|
-
}
|
|
907
|
-
reportProgress();
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
reportProgress();
|
|
911
|
-
return { files, scannedDirs };
|
|
1013
|
+
return normalizePosixPath(path3.posix.join(targetRoot, rel));
|
|
912
1014
|
};
|
|
913
1015
|
var existsOnTarget = async (params) => {
|
|
914
1016
|
const { targetTransport, targetDavBaseUrl, targetAuth, targetBaseUrl, getTargetToken, targetPath, timeoutMs } = params;
|
|
@@ -1047,7 +1149,7 @@ var backupOpenListToOpenListCore = async (params) => {
|
|
|
1047
1149
|
if (!targetDavBaseUrl) throw new Error("\u76EE\u6807\u7AEF OpenList \u5730\u5740\u4E0D\u6B63\u786E\uFF0C\u8BF7\u68C0\u67E5 openlistBaseUrl");
|
|
1048
1150
|
const normalizedSrcDir = normalizePosixPath(String(params.srcDir ?? "/"));
|
|
1049
1151
|
const normalizedTargetBaseDir = normalizePosixPath(String(params.toDir ?? cfg.openlistTargetDir ?? "/"));
|
|
1050
|
-
const targetRoot = params.appendHostDir === false ? normalizedTargetBaseDir : normalizePosixPath(
|
|
1152
|
+
const targetRoot = params.appendHostDir === false ? normalizedTargetBaseDir : normalizePosixPath(path3.posix.join(normalizedTargetBaseDir, safeHostDirName(sourceBaseUrl)));
|
|
1051
1153
|
const mode = params.mode ?? "incremental";
|
|
1052
1154
|
const transport = params.transport ?? normalizeOpenListBackupTransport(cfg?.openListBackupTransport, "auto");
|
|
1053
1155
|
const lockKey = `${sourceBaseUrl} -> ${targetBaseUrl}`;
|
|
@@ -1128,46 +1230,12 @@ var backupOpenListToOpenListCore = async (params) => {
|
|
|
1128
1230
|
const maxConcurrency = Math.max(1, Math.min(50, Math.floor(params.concurrency || 0) || 3));
|
|
1129
1231
|
let scannedDirs = 0;
|
|
1130
1232
|
let scannedFiles = 0;
|
|
1233
|
+
let ok = 0;
|
|
1234
|
+
let skipped = 0;
|
|
1235
|
+
let fail = 0;
|
|
1131
1236
|
const startAt = Date.now();
|
|
1132
|
-
|
|
1133
|
-
const elapsed = Math.floor((Date.now() - startAt) / 1e3);
|
|
1134
|
-
enqueueReport(`\u5907\u4EFD\u8FDB\u884C\u4E2D\uFF08${elapsed}s\uFF09
|
|
1135
|
-
\u9636\u6BB5\uFF1A\u626B\u63CF
|
|
1136
|
-
\u5DF2\u626B\u63CF\u76EE\u5F55\uFF1A${scannedDirs}
|
|
1137
|
-
\u5DF2\u53D1\u73B0\u6587\u4EF6\uFF1A${scannedFiles}`);
|
|
1138
|
-
}, 1e4);
|
|
1237
|
+
const maxFilesLimit = typeof params.maxFiles === "number" && Number.isFinite(params.maxFiles) && params.maxFiles > 0 ? Math.floor(params.maxFiles) : void 0;
|
|
1139
1238
|
const sourceAuth = hasSourceAuth ? buildOpenListAuthHeader(sourceUsername, sourcePassword) : "";
|
|
1140
|
-
const scanResult = await scanOpenListFiles({
|
|
1141
|
-
sourceTransport,
|
|
1142
|
-
sourceBaseUrl,
|
|
1143
|
-
sourceToken: await getSourceToken(),
|
|
1144
|
-
sourceAuth: sourceAuth || void 0,
|
|
1145
|
-
sourceDavBaseUrl,
|
|
1146
|
-
srcDir: normalizedSrcDir,
|
|
1147
|
-
targetRoot,
|
|
1148
|
-
timeoutMs: listTimeoutMs,
|
|
1149
|
-
perPage: listPerPage,
|
|
1150
|
-
scanConcurrency: scanConcurrencyMax,
|
|
1151
|
-
onProgress: (p) => {
|
|
1152
|
-
scannedDirs = p.scannedDirs;
|
|
1153
|
-
scannedFiles = p.files;
|
|
1154
|
-
}
|
|
1155
|
-
});
|
|
1156
|
-
const files = scanResult.files;
|
|
1157
|
-
if (typeof params.maxFiles === "number" && Number.isFinite(params.maxFiles) && params.maxFiles > 0) {
|
|
1158
|
-
files.splice(Math.floor(params.maxFiles));
|
|
1159
|
-
}
|
|
1160
|
-
if (!files.length) {
|
|
1161
|
-
enqueueReport("\u672A\u53D1\u73B0\u9700\u8981\u5907\u4EFD\u7684\u6587\u4EF6\u3002");
|
|
1162
|
-
return { ok: 0, skipped: 0, fail: 0 };
|
|
1163
|
-
}
|
|
1164
|
-
if (ticker) clearInterval(ticker);
|
|
1165
|
-
ticker = setInterval(() => {
|
|
1166
|
-
const elapsed = Math.floor((Date.now() - startAt) / 1e3);
|
|
1167
|
-
enqueueReport(`\u5907\u4EFD\u8FDB\u884C\u4E2D\uFF08${elapsed}s\uFF09
|
|
1168
|
-
\u9636\u6BB5\uFF1A\u590D\u5236
|
|
1169
|
-
\u5F85\u5904\u7406\uFF1A${files.length}`);
|
|
1170
|
-
}, 1e4);
|
|
1171
1239
|
enqueueReport([
|
|
1172
1240
|
"\u5F00\u59CB\u5907\u4EFD OpenList...",
|
|
1173
1241
|
`\u6E90\uFF1A${sourceBaseUrl}`,
|
|
@@ -1176,14 +1244,13 @@ var backupOpenListToOpenListCore = async (params) => {
|
|
|
1176
1244
|
`\u76EE\u6807\u76EE\u5F55\uFF1A${targetRoot}`,
|
|
1177
1245
|
`\u6A21\u5F0F\uFF1A${mode}`,
|
|
1178
1246
|
`\u4F20\u8F93\uFF1A${transport}`,
|
|
1179
|
-
`\
|
|
1247
|
+
`\u626B\u63CF\u5E76\u53D1\uFF1A${scanConcurrencyMax}`,
|
|
1248
|
+
`\u590D\u5236\u5E76\u53D1\uFF1A${maxConcurrency}`,
|
|
1249
|
+
`maxFiles\uFF1A${typeof maxFilesLimit === "number" ? maxFilesLimit : "-"}`
|
|
1180
1250
|
].join("\n"));
|
|
1181
|
-
let skipped = 0;
|
|
1182
|
-
let ok = 0;
|
|
1183
|
-
let fail = 0;
|
|
1184
1251
|
const targetDavBase = targetDavBaseUrl;
|
|
1185
1252
|
const ensureDirForPath = async (filePath) => {
|
|
1186
|
-
const dirPath = normalizePosixPath(
|
|
1253
|
+
const dirPath = normalizePosixPath(path3.posix.dirname(filePath));
|
|
1187
1254
|
targetTransport = await ensureTargetDir({
|
|
1188
1255
|
dirPath,
|
|
1189
1256
|
targetTransport,
|
|
@@ -1193,50 +1260,109 @@ var backupOpenListToOpenListCore = async (params) => {
|
|
|
1193
1260
|
getApiEnsurer: getTargetDirEnsurerApi
|
|
1194
1261
|
});
|
|
1195
1262
|
};
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
if (
|
|
1199
|
-
|
|
1263
|
+
const listDirEntries = async (dirPath) => {
|
|
1264
|
+
if (sourceTransport === "webdav") {
|
|
1265
|
+
if (!sourceDavBaseUrl) throw new Error("\u6E90\u7AEF WebDAV \u5730\u5740\u4E0D\u6B63\u786E");
|
|
1266
|
+
return await webdavPropfindListEntries({
|
|
1267
|
+
davBaseUrl: sourceDavBaseUrl,
|
|
1268
|
+
auth: sourceAuth,
|
|
1269
|
+
dirPath,
|
|
1270
|
+
timeoutMs: listTimeoutMs
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
return await openlistApiListEntries({
|
|
1274
|
+
baseUrl: sourceBaseUrl,
|
|
1275
|
+
token: await getSourceToken(),
|
|
1276
|
+
dirPath,
|
|
1277
|
+
timeoutMs: listTimeoutMs,
|
|
1278
|
+
perPage: listPerPage
|
|
1279
|
+
});
|
|
1280
|
+
};
|
|
1281
|
+
const executing = /* @__PURE__ */ new Set();
|
|
1282
|
+
const launchCopy = (file) => {
|
|
1283
|
+
const task = (async () => {
|
|
1284
|
+
const { sourcePath, targetPath } = file;
|
|
1285
|
+
try {
|
|
1286
|
+
if (mode === "incremental") {
|
|
1287
|
+
const exists = await existsOnTarget({
|
|
1288
|
+
targetTransport,
|
|
1289
|
+
targetDavBaseUrl: targetDavBase,
|
|
1290
|
+
targetAuth,
|
|
1291
|
+
targetBaseUrl,
|
|
1292
|
+
getTargetToken,
|
|
1293
|
+
targetPath,
|
|
1294
|
+
timeoutMs: listTimeoutMs
|
|
1295
|
+
});
|
|
1296
|
+
if (exists) {
|
|
1297
|
+
skipped++;
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
await ensureDirForPath(targetPath);
|
|
1302
|
+
await copyOpenListFile({
|
|
1303
|
+
sourceTransport,
|
|
1200
1304
|
targetTransport,
|
|
1305
|
+
sourceBaseUrl,
|
|
1306
|
+
sourceDavBaseUrl,
|
|
1307
|
+
sourceAuth,
|
|
1308
|
+
targetBaseUrl,
|
|
1201
1309
|
targetDavBaseUrl: targetDavBase,
|
|
1202
1310
|
targetAuth,
|
|
1203
|
-
|
|
1204
|
-
getTargetToken,
|
|
1311
|
+
sourcePath,
|
|
1205
1312
|
targetPath,
|
|
1206
|
-
|
|
1313
|
+
getSourceToken,
|
|
1314
|
+
getTargetToken,
|
|
1315
|
+
timeoutMs
|
|
1207
1316
|
});
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1317
|
+
ok++;
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
fail++;
|
|
1320
|
+
logger4.error(error);
|
|
1321
|
+
if (allowAutoFallback) {
|
|
1322
|
+
const msg = formatErrorMessage(error);
|
|
1323
|
+
if (targetTransport === "webdav" && /401|403|MKCOL|PUT/i.test(msg)) targetTransport = "api";
|
|
1324
|
+
else if (targetTransport === "api" && /fs\/put|fs\/mkdir|code=/i.test(msg)) targetTransport = "webdav";
|
|
1211
1325
|
}
|
|
1212
1326
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1327
|
+
})();
|
|
1328
|
+
executing.add(task);
|
|
1329
|
+
task.finally(() => executing.delete(task));
|
|
1330
|
+
return task;
|
|
1331
|
+
};
|
|
1332
|
+
ticker = setInterval(() => {
|
|
1333
|
+
const elapsed = Math.floor((Date.now() - startAt) / 1e3);
|
|
1334
|
+
enqueueReport([
|
|
1335
|
+
`\u5907\u4EFD\u8FDB\u884C\u4E2D\uFF08${elapsed}s\uFF09`,
|
|
1336
|
+
`\u5DF2\u626B\u63CF\u76EE\u5F55\uFF1A${scannedDirs}`,
|
|
1337
|
+
`\u5DF2\u53D1\u73B0\u6587\u4EF6\uFF1A${scannedFiles}${typeof maxFilesLimit === "number" ? `/${maxFilesLimit}` : ""}`,
|
|
1338
|
+
`\u8FDB\u5EA6\uFF1Aok=${ok} skip=${skipped} fail=${fail}`,
|
|
1339
|
+
`\u5E76\u53D1\uFF1AinFlight=${executing.size}/${maxConcurrency}`
|
|
1340
|
+
].join("\n"));
|
|
1341
|
+
}, 1e4);
|
|
1342
|
+
let pendingDirs = [normalizedSrcDir];
|
|
1343
|
+
while (pendingDirs.length) {
|
|
1344
|
+
const dirPath = pendingDirs.pop();
|
|
1345
|
+
scannedDirs++;
|
|
1346
|
+
const entries = await listDirEntries(dirPath);
|
|
1347
|
+
for (const it of entries) {
|
|
1348
|
+
const sourcePath = normalizePosixPath(path3.posix.join(dirPath, it.name));
|
|
1349
|
+
const targetPath = buildTargetPath(targetRoot, sourcePath);
|
|
1350
|
+
if (it.isDir) {
|
|
1351
|
+
pendingDirs.push(sourcePath);
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
scannedFiles++;
|
|
1355
|
+
if (typeof maxFilesLimit === "number" && scannedFiles > maxFilesLimit) {
|
|
1356
|
+
pendingDirs = [];
|
|
1357
|
+
break;
|
|
1358
|
+
}
|
|
1359
|
+
launchCopy({ sourcePath, targetPath });
|
|
1360
|
+
if (executing.size >= maxConcurrency) {
|
|
1361
|
+
await Promise.race(executing);
|
|
1237
1362
|
}
|
|
1238
1363
|
}
|
|
1239
|
-
}
|
|
1364
|
+
}
|
|
1365
|
+
await Promise.all(executing);
|
|
1240
1366
|
enqueueReport(`\u5907\u4EFD\u5B8C\u6210\uFF1A\u6210\u529F ${ok}\uFF0C\u8DF3\u8FC7 ${skipped}\uFF0C\u5931\u8D25 ${fail}`);
|
|
1241
1367
|
return { ok, skipped, fail };
|
|
1242
1368
|
} finally {
|
|
@@ -1245,24 +1371,6 @@ var backupOpenListToOpenListCore = async (params) => {
|
|
|
1245
1371
|
}
|
|
1246
1372
|
};
|
|
1247
1373
|
|
|
1248
|
-
// src/model/shared/fsJson.ts
|
|
1249
|
-
import fs from "fs";
|
|
1250
|
-
import path2 from "path";
|
|
1251
|
-
var ensureDir = (dirPath) => fs.mkdirSync(dirPath, { recursive: true });
|
|
1252
|
-
var readJsonSafe = (filePath) => {
|
|
1253
|
-
try {
|
|
1254
|
-
if (!fs.existsSync(filePath)) return {};
|
|
1255
|
-
const raw = fs.readFileSync(filePath, "utf8");
|
|
1256
|
-
return raw ? JSON.parse(raw) : {};
|
|
1257
|
-
} catch {
|
|
1258
|
-
return {};
|
|
1259
|
-
}
|
|
1260
|
-
};
|
|
1261
|
-
var writeJsonSafe = (filePath, data) => {
|
|
1262
|
-
ensureDir(path2.dirname(filePath));
|
|
1263
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
1264
|
-
};
|
|
1265
|
-
|
|
1266
1374
|
export {
|
|
1267
1375
|
buildOpenListDavBaseUrl,
|
|
1268
1376
|
buildOpenListAuthHeader,
|
|
@@ -1270,13 +1378,13 @@ export {
|
|
|
1270
1378
|
safePathSegment,
|
|
1271
1379
|
encodePathForUrl,
|
|
1272
1380
|
formatErrorMessage,
|
|
1381
|
+
ensureDir,
|
|
1382
|
+
readJsonSafe,
|
|
1383
|
+
writeJsonSafe,
|
|
1273
1384
|
webdavPropfindListNames,
|
|
1274
1385
|
createWebDavDirEnsurer,
|
|
1275
1386
|
downloadAndUploadByWebDav,
|
|
1276
1387
|
runWithConcurrency,
|
|
1277
1388
|
runWithAdaptiveConcurrency,
|
|
1278
|
-
backupOpenListToOpenListCore
|
|
1279
|
-
ensureDir,
|
|
1280
|
-
readJsonSafe,
|
|
1281
|
-
writeJsonSafe
|
|
1389
|
+
backupOpenListToOpenListCore
|
|
1282
1390
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
syncGroupFilesToOpenListCore
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-R4MRR5TI.js";
|
|
4
4
|
import {
|
|
5
5
|
normalizePosixPath,
|
|
6
6
|
readJsonSafe,
|
|
7
7
|
writeJsonSafe
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-GDVU2T6R.js";
|
|
9
9
|
import {
|
|
10
10
|
config
|
|
11
11
|
} from "./chunk-DA4U55JC.js";
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
readGroupSyncState,
|
|
3
3
|
withGroupSyncLock,
|
|
4
4
|
writeGroupSyncState
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-DDV3OZXB.js";
|
|
6
6
|
import {
|
|
7
7
|
buildOpenListAuthHeader,
|
|
8
8
|
buildOpenListDavBaseUrl,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
runWithConcurrency,
|
|
17
17
|
safePathSegment,
|
|
18
18
|
webdavPropfindListNames
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-GDVU2T6R.js";
|
|
20
20
|
import {
|
|
21
21
|
config,
|
|
22
22
|
time
|
|
@@ -508,13 +508,14 @@ var syncGroupFilesToOpenListCore = async (params) => {
|
|
|
508
508
|
const shouldRefreshUrl = (message) => {
|
|
509
509
|
return /403|URL已过期|url已过期|url可能已失效|需要重新获取|下载超时/.test(message);
|
|
510
510
|
};
|
|
511
|
-
const transferOne = async (sourceUrl, targetUrl) => {
|
|
511
|
+
const transferOne = async (sourceUrl, targetUrl, expectedSize) => {
|
|
512
512
|
await downloadAndUploadByWebDav({
|
|
513
513
|
sourceUrl,
|
|
514
514
|
targetUrl,
|
|
515
515
|
auth,
|
|
516
516
|
timeoutMs: transferTimeoutMs,
|
|
517
|
-
rateLimitBytesPerSec: effectiveRateLimitBytesPerSec || void 0
|
|
517
|
+
rateLimitBytesPerSec: effectiveRateLimitBytesPerSec || void 0,
|
|
518
|
+
expectedSize
|
|
518
519
|
});
|
|
519
520
|
};
|
|
520
521
|
report && await report("\u5F00\u59CB\u4E0B\u8F7D\u5E76\u4E0A\u4F20\u5230 OpenList\uFF0C\u8BF7\u7A0D\u5019..");
|
|
@@ -532,7 +533,7 @@ var syncGroupFilesToOpenListCore = async (params) => {
|
|
|
532
533
|
await dirEnsurer.ensureDir(remoteDir);
|
|
533
534
|
const currentUrl = item.url;
|
|
534
535
|
if (!currentUrl) throw new Error("\u7F3A\u5C11\u4E0B\u8F7D URL");
|
|
535
|
-
await transferOne(currentUrl, targetUrl);
|
|
536
|
+
await transferOne(currentUrl, targetUrl, item.size);
|
|
536
537
|
okCount++;
|
|
537
538
|
succeeded = true;
|
|
538
539
|
lastError = void 0;
|
|
@@ -817,7 +818,8 @@ var handleGroupFileUploadedAutoBackup = (e) => {
|
|
|
817
818
|
targetUrl,
|
|
818
819
|
auth,
|
|
819
820
|
timeoutMs: transferTimeoutMs,
|
|
820
|
-
rateLimitBytesPerSec: effectiveRateLimitBytesPerSec || void 0
|
|
821
|
+
rateLimitBytesPerSec: effectiveRateLimitBytesPerSec || void 0,
|
|
822
|
+
expectedSize: size
|
|
821
823
|
});
|
|
822
824
|
break;
|
|
823
825
|
} catch (error) {
|