vvvfs 0.1.3 → 0.1.4
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 +5 -0
- package/index.ts +248 -684
- package/package.json +1 -1
package/README.md
CHANGED
package/index.ts
CHANGED
|
@@ -163,7 +163,7 @@ class VVVFSFile {
|
|
|
163
163
|
* 写入文件JSON内容
|
|
164
164
|
* @param json 文件JSON内容
|
|
165
165
|
*/
|
|
166
|
-
async writeJSON(json: Record<string,
|
|
166
|
+
async writeJSON(json: Record<string, unknown>, format: boolean = true) {
|
|
167
167
|
return await this._vvvfs.writeJson(this._path, json, format);
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
@@ -267,6 +267,7 @@ class VVVFSFile {
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
const version = packageJson.version;
|
|
270
|
+
|
|
270
271
|
class VVVFS {
|
|
271
272
|
static defaultDBName = "vvvfs";
|
|
272
273
|
#db: VVVFSDatabase;
|
|
@@ -287,6 +288,65 @@ class VVVFS {
|
|
|
287
288
|
* 锁定的文件
|
|
288
289
|
*/
|
|
289
290
|
#lockedFiles: Record<string, boolean> = {};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 检查文件访问权限(监听器和锁定状态)
|
|
294
|
+
* @param path 文件路径
|
|
295
|
+
* @param operation 操作类型
|
|
296
|
+
* @returns 是否允许访问
|
|
297
|
+
*/
|
|
298
|
+
async #checkAccess(path: string, operation: string): Promise<boolean> {
|
|
299
|
+
if (this.#watchers[path]) {
|
|
300
|
+
for (const handler of this.#watchers[path]) {
|
|
301
|
+
if (await handler(operation)) {
|
|
302
|
+
if (this.options.throwError) {
|
|
303
|
+
throw new VVVFSError(
|
|
304
|
+
operation.charAt(0).toUpperCase() + operation.slice(1),
|
|
305
|
+
`${operation}操作被监听器取消`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (this.#lockedFiles[path]) {
|
|
313
|
+
if (this.options.throwError) {
|
|
314
|
+
throw new VVVFSError(
|
|
315
|
+
operation.charAt(0).toUpperCase() + operation.slice(1),
|
|
316
|
+
"文件已被锁定",
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 错误处理包装器
|
|
326
|
+
* @param operation 操作名称
|
|
327
|
+
* @param fn 操作函数
|
|
328
|
+
* @param fallback 失败时的默认返回值
|
|
329
|
+
*/
|
|
330
|
+
async #withErrorHandling<T>(
|
|
331
|
+
operation: string,
|
|
332
|
+
fn: () => Promise<T>,
|
|
333
|
+
fallback: T,
|
|
334
|
+
): Promise<T> {
|
|
335
|
+
try {
|
|
336
|
+
return await fn();
|
|
337
|
+
} catch (error) {
|
|
338
|
+
if (error instanceof VVVFSError) throw error;
|
|
339
|
+
console.error(`${operation}失败`, error);
|
|
340
|
+
if (this.options.throwError) {
|
|
341
|
+
throw new VVVFSError(
|
|
342
|
+
operation.charAt(0).toUpperCase() + operation.slice(1),
|
|
343
|
+
`${operation}失败${error}`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
return fallback;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
290
350
|
/**
|
|
291
351
|
* 创建虚拟文件系统
|
|
292
352
|
* @param name 虚拟文件系统名称
|
|
@@ -314,128 +374,23 @@ class VVVFS {
|
|
|
314
374
|
* @description 将Linux的系统初始文件初始化到数据库中
|
|
315
375
|
*/
|
|
316
376
|
async init(user?: string) {
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
type: "dir",
|
|
322
|
-
file: new File([], "root"),
|
|
323
|
-
},
|
|
324
|
-
{
|
|
325
|
-
name: "boot",
|
|
326
|
-
path: "/",
|
|
327
|
-
type: "dir",
|
|
328
|
-
file: new File([], "boot"),
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
name: "bin",
|
|
332
|
-
path: "/",
|
|
333
|
-
type: "dir",
|
|
334
|
-
file: new File([], "bin"),
|
|
335
|
-
},
|
|
336
|
-
{
|
|
337
|
-
name: "dev",
|
|
338
|
-
path: "/",
|
|
339
|
-
type: "dir",
|
|
340
|
-
file: new File([], "dev"),
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
name: "etc",
|
|
344
|
-
path: "/",
|
|
345
|
-
type: "dir",
|
|
346
|
-
file: new File([], "etc"),
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
name: "home",
|
|
350
|
-
path: "/",
|
|
351
|
-
type: "dir",
|
|
352
|
-
file: new File([], "home"),
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
name: user || "root",
|
|
356
|
-
path: "/home",
|
|
357
|
-
type: "dir",
|
|
358
|
-
file: new File([], "home"),
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
name: "lib",
|
|
362
|
-
path: "/",
|
|
363
|
-
type: "dir",
|
|
364
|
-
file: new File([], "lib"),
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
name: "lib64",
|
|
368
|
-
path: "/",
|
|
369
|
-
type: "dir",
|
|
370
|
-
file: new File([], "lib64"),
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
name: "media",
|
|
374
|
-
path: "/",
|
|
375
|
-
type: "dir",
|
|
376
|
-
file: new File([], "media"),
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
name: "mnt",
|
|
380
|
-
path: "/",
|
|
381
|
-
type: "dir",
|
|
382
|
-
file: new File([], "mnt"),
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
name: "opt",
|
|
386
|
-
path: "/",
|
|
387
|
-
type: "dir",
|
|
388
|
-
file: new File([], "opt"),
|
|
389
|
-
},
|
|
390
|
-
{
|
|
391
|
-
name: "proc",
|
|
392
|
-
path: "/",
|
|
393
|
-
type: "dir",
|
|
394
|
-
file: new File([], "proc"),
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
name: "run",
|
|
398
|
-
path: "/",
|
|
399
|
-
type: "dir",
|
|
400
|
-
file: new File([], "run"),
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
name: "sbin",
|
|
404
|
-
path: "/",
|
|
405
|
-
type: "dir",
|
|
406
|
-
file: new File([], "sbin"),
|
|
407
|
-
},
|
|
408
|
-
{
|
|
409
|
-
name: "srv",
|
|
410
|
-
path: "/",
|
|
411
|
-
type: "dir",
|
|
412
|
-
file: new File([], "srv"),
|
|
413
|
-
},
|
|
414
|
-
{
|
|
415
|
-
name: "sys",
|
|
416
|
-
path: "/",
|
|
417
|
-
type: "dir",
|
|
418
|
-
file: new File([], "sys"),
|
|
419
|
-
},
|
|
420
|
-
{
|
|
421
|
-
name: "tmp",
|
|
422
|
-
path: "/",
|
|
423
|
-
type: "dir",
|
|
424
|
-
file: new File([], "tmp"),
|
|
425
|
-
},
|
|
426
|
-
{
|
|
427
|
-
name: "usr",
|
|
428
|
-
path: "/",
|
|
429
|
-
type: "dir",
|
|
430
|
-
file: new File([], "usr"),
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
name: "var",
|
|
434
|
-
path: "/",
|
|
435
|
-
type: "dir",
|
|
436
|
-
file: new File([], "var"),
|
|
437
|
-
},
|
|
377
|
+
const linuxDirs = [
|
|
378
|
+
"root", "boot", "bin", "dev", "etc", "home",
|
|
379
|
+
"lib", "lib64", "media", "mnt", "opt", "proc",
|
|
380
|
+
"run", "sbin", "srv", "sys", "tmp", "usr", "var",
|
|
438
381
|
];
|
|
382
|
+
const linuxInitFiles = linuxDirs.map((name) => ({
|
|
383
|
+
name,
|
|
384
|
+
path: "/",
|
|
385
|
+
type: "dir" as const,
|
|
386
|
+
file: new File([], name),
|
|
387
|
+
}));
|
|
388
|
+
linuxInitFiles.push({
|
|
389
|
+
name: user || "root",
|
|
390
|
+
path: "/home",
|
|
391
|
+
type: "dir" as const,
|
|
392
|
+
file: new File([], "home"),
|
|
393
|
+
});
|
|
439
394
|
try {
|
|
440
395
|
for (const file of linuxInitFiles) {
|
|
441
396
|
if (await this.exists(file.path)) continue;
|
|
@@ -466,18 +421,9 @@ class VVVFS {
|
|
|
466
421
|
* @param path 文件路径
|
|
467
422
|
*/
|
|
468
423
|
async createFile(path: string) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if (this.#
|
|
472
|
-
for (const handler of this.#watchers[targetPath]) {
|
|
473
|
-
if (await handler("create")) {
|
|
474
|
-
if (this.options.throwError) {
|
|
475
|
-
throw new VVVFSError("CreateFile", "创建文件失败:监听器取消了操作");
|
|
476
|
-
}
|
|
477
|
-
return false;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
424
|
+
return this.#withErrorHandling("createFile", async () => {
|
|
425
|
+
const targetPath = joinPath(path);
|
|
426
|
+
if (!(await this.#checkAccess(targetPath, "create"))) return false;
|
|
481
427
|
if (await this.exists(targetPath)) {
|
|
482
428
|
console.warn("文件已存在");
|
|
483
429
|
return true;
|
|
@@ -487,7 +433,7 @@ class VVVFS {
|
|
|
487
433
|
await this.createDir(parent);
|
|
488
434
|
}
|
|
489
435
|
await this.#db.files.add({
|
|
490
|
-
name
|
|
436
|
+
name,
|
|
491
437
|
path: parent,
|
|
492
438
|
type: "file",
|
|
493
439
|
file: new File([], name, {
|
|
@@ -495,154 +441,90 @@ class VVVFS {
|
|
|
495
441
|
}),
|
|
496
442
|
});
|
|
497
443
|
return true;
|
|
498
|
-
}
|
|
499
|
-
console.error("创建文件失败", error);
|
|
500
|
-
if (this.options.throwError) {
|
|
501
|
-
throw new VVVFSError("CreateFile", "创建文件失败" + error);
|
|
502
|
-
}
|
|
503
|
-
return false;
|
|
504
|
-
}
|
|
444
|
+
}, false);
|
|
505
445
|
}
|
|
506
446
|
/**
|
|
507
447
|
* 创建目录
|
|
508
448
|
* @param path 目录路径
|
|
509
449
|
*/
|
|
510
450
|
async createDir(path: string) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
if (this.#
|
|
514
|
-
for (const handler of this.#watchers[targetPath]) {
|
|
515
|
-
if (await handler("create")) {
|
|
516
|
-
if (this.options.throwError) {
|
|
517
|
-
throw new VVVFSError("CreateDir", "创建目录失败:监听器取消了操作");
|
|
518
|
-
}
|
|
519
|
-
return false;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
451
|
+
return this.#withErrorHandling("createDir", async () => {
|
|
452
|
+
const targetPath = joinPath(path);
|
|
453
|
+
if (!(await this.#checkAccess(targetPath, "create"))) return false;
|
|
523
454
|
if (await this.exists(targetPath)) {
|
|
524
455
|
console.warn("目录已存在");
|
|
525
456
|
return true;
|
|
526
457
|
}
|
|
527
458
|
const { name, parent } = parsePath(targetPath);
|
|
528
459
|
if (!(await this.exists(parent))) {
|
|
529
|
-
if (parent
|
|
460
|
+
if (parent === "/") {
|
|
530
461
|
await this.#db.files.add({
|
|
531
462
|
name: "",
|
|
532
463
|
path: "/",
|
|
533
464
|
type: "dir",
|
|
534
465
|
file: new File([], ""),
|
|
535
466
|
});
|
|
536
|
-
if (name
|
|
467
|
+
if (name === "") return true;
|
|
537
468
|
} else {
|
|
538
469
|
await this.createDir(parent);
|
|
539
470
|
}
|
|
540
471
|
}
|
|
541
472
|
await this.#db.files.add({
|
|
542
|
-
name
|
|
473
|
+
name,
|
|
543
474
|
path: parent,
|
|
544
475
|
type: "dir",
|
|
545
476
|
file: new File([], name),
|
|
546
477
|
});
|
|
547
478
|
return true;
|
|
548
|
-
}
|
|
549
|
-
console.error("创建目录失败", error);
|
|
550
|
-
if (this.options.throwError) {
|
|
551
|
-
throw new VVVFSError("CreateDir", "创建目录失败" + error);
|
|
552
|
-
}
|
|
553
|
-
return false;
|
|
554
|
-
}
|
|
479
|
+
}, false);
|
|
555
480
|
}
|
|
556
481
|
/**
|
|
557
482
|
* 判断文件是否存在
|
|
558
483
|
* @param path 文件路径
|
|
559
484
|
*/
|
|
560
485
|
async exists(path: string) {
|
|
561
|
-
|
|
562
|
-
const
|
|
563
|
-
const { name, parent } = parsePath(targetPath);
|
|
486
|
+
return this.#withErrorHandling("exists", async () => {
|
|
487
|
+
const { name, parent } = parsePath(path);
|
|
564
488
|
return (
|
|
565
489
|
(await this.#db.files
|
|
566
|
-
.where({
|
|
567
|
-
name: name,
|
|
568
|
-
path: parent,
|
|
569
|
-
})
|
|
490
|
+
.where({ name, path: parent })
|
|
570
491
|
.count()) > 0
|
|
571
492
|
);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
493
|
+
}, false);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* 内部写入(跳过访问检查,供内部方法调用)
|
|
497
|
+
*/
|
|
498
|
+
async #internalWrite(targetPath: string, content: Blob): Promise<boolean> {
|
|
499
|
+
if (!(await this.exists(targetPath))) {
|
|
500
|
+
const success = await this.createFile(targetPath);
|
|
501
|
+
if (!success) return false;
|
|
502
|
+
}
|
|
503
|
+
if (await this.isDir(targetPath)) {
|
|
504
|
+
console.warn("路径已存在");
|
|
577
505
|
return false;
|
|
578
506
|
}
|
|
507
|
+
const { name, parent } = parsePath(targetPath);
|
|
508
|
+
const file = new File([content], name, {
|
|
509
|
+
type: mime.getType(targetPath) || "application/octet-stream",
|
|
510
|
+
});
|
|
511
|
+
const fileRecord = await this.#db.files.where({ name, path: parent }).first();
|
|
512
|
+
if (fileRecord) {
|
|
513
|
+
await this.#db.files.put({ ...fileRecord, file });
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
return false;
|
|
579
517
|
}
|
|
580
518
|
/**
|
|
581
519
|
* 读取文件内容
|
|
582
520
|
* @param path 文件路径
|
|
583
521
|
*/
|
|
584
522
|
async write(path: string, content: Blob) {
|
|
585
|
-
|
|
523
|
+
return this.#withErrorHandling("write", async () => {
|
|
586
524
|
const targetPath = joinPath(path);
|
|
587
|
-
if (this.#
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
if (this.options.throwError) {
|
|
591
|
-
throw new VVVFSError("Write", "写入文件失败:监听器取消了操作");
|
|
592
|
-
}
|
|
593
|
-
return false;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
if (this.#lockedFiles[targetPath]) {
|
|
598
|
-
console.warn("文件已被锁定");
|
|
599
|
-
if (this.options.throwError) {
|
|
600
|
-
throw new VVVFSError("Write", "文件已被锁定");
|
|
601
|
-
}
|
|
602
|
-
return false;
|
|
603
|
-
}
|
|
604
|
-
if (!(await this.exists(targetPath))) {
|
|
605
|
-
const success = await this.createFile(targetPath);
|
|
606
|
-
if (!success) {
|
|
607
|
-
console.warn("创建文件失败");
|
|
608
|
-
if (this.options.throwError) {
|
|
609
|
-
throw new VVVFSError("Write", "创建文件失败");
|
|
610
|
-
}
|
|
611
|
-
return false;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
if (await this.isDir(targetPath)) {
|
|
615
|
-
console.warn("路径已存在");
|
|
616
|
-
if (this.options.throwError) {
|
|
617
|
-
throw new VVVFSError("Write", "路径已存在");
|
|
618
|
-
}
|
|
619
|
-
return false;
|
|
620
|
-
}
|
|
621
|
-
const { name, parent } = parsePath(targetPath);
|
|
622
|
-
const file = new File([content], name, {
|
|
623
|
-
type: mime.getType(targetPath) || "application/octet-stream",
|
|
624
|
-
});
|
|
625
|
-
const fileRecord = await this.#db.files.where({ name, path: parent }).first();
|
|
626
|
-
if (fileRecord) {
|
|
627
|
-
await this.#db.files.put({
|
|
628
|
-
...fileRecord,
|
|
629
|
-
file: file,
|
|
630
|
-
});
|
|
631
|
-
return true;
|
|
632
|
-
} else {
|
|
633
|
-
console.warn("文件记录未找到,无法写入内容");
|
|
634
|
-
if (this.options.throwError) {
|
|
635
|
-
throw new VVVFSError("Write", "文件记录未找到,无法写入内容");
|
|
636
|
-
}
|
|
637
|
-
return false;
|
|
638
|
-
}
|
|
639
|
-
} catch (error) {
|
|
640
|
-
console.error("写入文件失败", error);
|
|
641
|
-
if (this.options.throwError) {
|
|
642
|
-
throw new VVVFSError("Write", "写入文件失败" + error);
|
|
643
|
-
}
|
|
644
|
-
return false;
|
|
645
|
-
}
|
|
525
|
+
if (!(await this.#checkAccess(targetPath, "write"))) return false;
|
|
526
|
+
return await this.#internalWrite(targetPath, content);
|
|
527
|
+
}, false);
|
|
646
528
|
}
|
|
647
529
|
/**
|
|
648
530
|
* 写入文本内容
|
|
@@ -650,16 +532,10 @@ class VVVFS {
|
|
|
650
532
|
* @param content 文本内容
|
|
651
533
|
*/
|
|
652
534
|
async writeText(path: string, content: string) {
|
|
653
|
-
|
|
535
|
+
return this.#withErrorHandling("writeText", async () => {
|
|
654
536
|
const blob = new Blob([content], { type: "text/plain" });
|
|
655
537
|
return await this.write(path, blob);
|
|
656
|
-
}
|
|
657
|
-
console.error("写入文件失败", error);
|
|
658
|
-
if (this.options.throwError) {
|
|
659
|
-
throw new VVVFSError("Write", "写入文件失败" + error);
|
|
660
|
-
}
|
|
661
|
-
return false;
|
|
662
|
-
}
|
|
538
|
+
}, false);
|
|
663
539
|
}
|
|
664
540
|
/**
|
|
665
541
|
* 写入JSON内容
|
|
@@ -667,19 +543,13 @@ class VVVFS {
|
|
|
667
543
|
* @param content JSON内容
|
|
668
544
|
* @param format 是否格式化
|
|
669
545
|
*/
|
|
670
|
-
async writeJson(path: string, content: Record<string,
|
|
671
|
-
|
|
546
|
+
async writeJson(path: string, content: Record<string, unknown>, format: boolean = true) {
|
|
547
|
+
return this.#withErrorHandling("writeJson", async () => {
|
|
672
548
|
return await this.writeText(
|
|
673
549
|
path,
|
|
674
550
|
JSON.stringify(content, null, format ? 4 : undefined),
|
|
675
551
|
);
|
|
676
|
-
}
|
|
677
|
-
console.error("写入文件失败", error);
|
|
678
|
-
if (this.options.throwError) {
|
|
679
|
-
throw new VVVFSError("Write", "写入文件失败" + error);
|
|
680
|
-
}
|
|
681
|
-
return false;
|
|
682
|
-
}
|
|
552
|
+
}, false);
|
|
683
553
|
}
|
|
684
554
|
/**
|
|
685
555
|
* 追加内容
|
|
@@ -687,41 +557,18 @@ class VVVFS {
|
|
|
687
557
|
* @param content 追加内容
|
|
688
558
|
*/
|
|
689
559
|
async append(path: string, content: Blob) {
|
|
690
|
-
|
|
560
|
+
return this.#withErrorHandling("append", async () => {
|
|
691
561
|
const targetPath = joinPath(path);
|
|
692
|
-
if (this.#
|
|
693
|
-
|
|
694
|
-
if (await handler("append")) {
|
|
695
|
-
if (this.options.throwError) {
|
|
696
|
-
throw new VVVFSError("Append", "追加文件失败:监听器取消了操作");
|
|
697
|
-
}
|
|
698
|
-
return false;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
if (this.#lockedFiles[targetPath]) {
|
|
703
|
-
console.warn("文件已被锁定");
|
|
704
|
-
if (this.options.throwError) {
|
|
705
|
-
throw new VVVFSError("Append", "文件已被锁定");
|
|
706
|
-
}
|
|
707
|
-
return false;
|
|
708
|
-
}
|
|
709
|
-
const existingFile = await this.read(targetPath);
|
|
562
|
+
if (!(await this.#checkAccess(targetPath, "append"))) return false;
|
|
563
|
+
const existingFile = await this.#internalRead(targetPath);
|
|
710
564
|
if (existingFile) {
|
|
711
565
|
const blob = new Blob([existingFile, content], {
|
|
712
566
|
type: "application/octet-stream",
|
|
713
567
|
});
|
|
714
|
-
return await this
|
|
715
|
-
} else {
|
|
716
|
-
return await this.write(targetPath, content);
|
|
717
|
-
}
|
|
718
|
-
} catch (error) {
|
|
719
|
-
console.error("追加文件失败", error);
|
|
720
|
-
if (this.options.throwError) {
|
|
721
|
-
throw new VVVFSError("Append", "追加文件失败" + error);
|
|
568
|
+
return await this.#internalWrite(targetPath, blob);
|
|
722
569
|
}
|
|
723
|
-
return
|
|
724
|
-
}
|
|
570
|
+
return await this.#internalWrite(targetPath, content);
|
|
571
|
+
}, false);
|
|
725
572
|
}
|
|
726
573
|
/**
|
|
727
574
|
* 追加文本内容
|
|
@@ -729,109 +576,58 @@ class VVVFS {
|
|
|
729
576
|
* @param content 追加内容
|
|
730
577
|
*/
|
|
731
578
|
async appendText(path: string, content: string) {
|
|
732
|
-
|
|
579
|
+
return this.#withErrorHandling("appendText", async () => {
|
|
733
580
|
const blob = new Blob([content], { type: "text/plain" });
|
|
734
581
|
return await this.append(path, blob);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
582
|
+
}, false);
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* 内部读取(跳过访问检查,供内部方法调用)
|
|
586
|
+
*/
|
|
587
|
+
async #internalRead(targetPath: string): Promise<File | null> {
|
|
588
|
+
if (!(await this.exists(targetPath))) return null;
|
|
589
|
+
const { name, parent } = parsePath(targetPath);
|
|
590
|
+
return (await this.#db.files.where({ name, path: parent }).first())?.file ?? null;
|
|
742
591
|
}
|
|
743
592
|
/**
|
|
744
593
|
* 读取文件内容
|
|
745
594
|
* @param path 文件路径
|
|
746
595
|
*/
|
|
747
596
|
async read(path: string) {
|
|
748
|
-
|
|
597
|
+
return this.#withErrorHandling("read", async () => {
|
|
749
598
|
const targetPath = joinPath(path);
|
|
750
|
-
if (this.#
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (this.options.throwError) {
|
|
754
|
-
throw new VVVFSError("Read", "读取文件失败:监听器取消了操作");
|
|
755
|
-
}
|
|
756
|
-
return null;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
if (this.#lockedFiles[targetPath]) {
|
|
761
|
-
console.warn("文件已被锁定");
|
|
762
|
-
if (this.options.throwError) {
|
|
763
|
-
throw new VVVFSError("Read", "文件已被锁定");
|
|
764
|
-
}
|
|
765
|
-
return null;
|
|
766
|
-
}
|
|
767
|
-
if (!(await this.exists(targetPath))) {
|
|
768
|
-
console.warn("文件不存在");
|
|
769
|
-
if (this.options.throwError) {
|
|
770
|
-
throw new VVVFSError("Read", "文件不存在");
|
|
771
|
-
}
|
|
772
|
-
return null;
|
|
773
|
-
}
|
|
774
|
-
const { name, parent } = parsePath(targetPath);
|
|
775
|
-
return (await this.#db.files.where({ name, path: parent }).first())?.file;
|
|
776
|
-
} catch (error) {
|
|
777
|
-
console.error("读取文件失败", error);
|
|
778
|
-
if (this.options.throwError) {
|
|
779
|
-
throw new VVVFSError("Read", "读取文件失败" + error);
|
|
780
|
-
}
|
|
781
|
-
return null;
|
|
782
|
-
}
|
|
599
|
+
if (!(await this.#checkAccess(targetPath, "read"))) return null;
|
|
600
|
+
return await this.#internalRead(targetPath);
|
|
601
|
+
}, null as File | null);
|
|
783
602
|
}
|
|
784
603
|
/**
|
|
785
604
|
* 读取文件内容
|
|
786
605
|
* @param path 文件路径
|
|
787
606
|
*/
|
|
788
607
|
async readText(path: string) {
|
|
789
|
-
|
|
608
|
+
return this.#withErrorHandling("readText", async () => {
|
|
790
609
|
const file = await this.read(path);
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
} else {
|
|
794
|
-
console.warn("文件不存在");
|
|
795
|
-
if (this.options.throwError) {
|
|
796
|
-
throw new VVVFSError("Read", "文件不存在");
|
|
797
|
-
}
|
|
798
|
-
return null;
|
|
799
|
-
}
|
|
800
|
-
} catch (error) {
|
|
801
|
-
console.error("读取文件失败", error);
|
|
802
|
-
if (this.options.throwError) {
|
|
803
|
-
throw new VVVFSError("Read", "读取文件失败" + error);
|
|
804
|
-
}
|
|
805
|
-
return null;
|
|
806
|
-
}
|
|
610
|
+
return file ? await file.text() : null;
|
|
611
|
+
}, null as string | null);
|
|
807
612
|
}
|
|
808
613
|
/**
|
|
809
614
|
* 读取JSON内容
|
|
810
615
|
* @param path 文件路径
|
|
811
616
|
*/
|
|
812
617
|
async readJson(path: string) {
|
|
813
|
-
|
|
618
|
+
return this.#withErrorHandling("readJson", async () => {
|
|
814
619
|
const text = await this.readText(path);
|
|
815
|
-
if (text)
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
return null;
|
|
620
|
+
if (!text) return null;
|
|
621
|
+
try {
|
|
622
|
+
return JSON.parse(text);
|
|
623
|
+
} catch (error) {
|
|
624
|
+
console.error("解析JSON失败", error);
|
|
625
|
+
if (this.options.throwError) {
|
|
626
|
+
throw new VVVFSError("Read", "解析JSON失败" + error);
|
|
824
627
|
}
|
|
825
|
-
} else {
|
|
826
628
|
return null;
|
|
827
629
|
}
|
|
828
|
-
}
|
|
829
|
-
console.error("读取文件失败", error);
|
|
830
|
-
if (this.options.throwError) {
|
|
831
|
-
throw new VVVFSError("Read", "读取文件失败" + error);
|
|
832
|
-
}
|
|
833
|
-
return null;
|
|
834
|
-
}
|
|
630
|
+
}, null as Record<string, unknown> | null);
|
|
835
631
|
}
|
|
836
632
|
/**
|
|
837
633
|
* 读取文件内容
|
|
@@ -840,41 +636,10 @@ class VVVFS {
|
|
|
840
636
|
* @param end 结束位置
|
|
841
637
|
*/
|
|
842
638
|
async readChunk(path: string, start: number, end: number) {
|
|
843
|
-
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
if (await handler("read")) {
|
|
848
|
-
if (this.options.throwError) {
|
|
849
|
-
throw new VVVFSError("Read", "读取文件失败:监听器取消了操作");
|
|
850
|
-
}
|
|
851
|
-
return null;
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
if (this.#lockedFiles[targetPath]) {
|
|
856
|
-
console.warn("文件已被锁定");
|
|
857
|
-
if (this.options.throwError) {
|
|
858
|
-
throw new VVVFSError("Read", "文件已被锁定");
|
|
859
|
-
}
|
|
860
|
-
return null;
|
|
861
|
-
}
|
|
862
|
-
if (!(await this.exists(targetPath))) {
|
|
863
|
-
console.warn("文件不存在");
|
|
864
|
-
if (this.options.throwError) {
|
|
865
|
-
throw new VVVFSError("Read", "文件不存在");
|
|
866
|
-
}
|
|
867
|
-
return null;
|
|
868
|
-
}
|
|
869
|
-
const file = await this.read(targetPath);
|
|
870
|
-
return file?.slice(start, end) || null;
|
|
871
|
-
} catch (error) {
|
|
872
|
-
console.error("读取文件失败", error);
|
|
873
|
-
if (this.options.throwError) {
|
|
874
|
-
throw new VVVFSError("Read", "读取文件失败" + error);
|
|
875
|
-
}
|
|
876
|
-
return null;
|
|
877
|
-
}
|
|
639
|
+
return this.#withErrorHandling("readChunk", async () => {
|
|
640
|
+
const file = await this.read(path);
|
|
641
|
+
return file?.slice(start, end) ?? null;
|
|
642
|
+
}, null as Blob | null);
|
|
878
643
|
}
|
|
879
644
|
/**
|
|
880
645
|
* 读取文件内容
|
|
@@ -883,102 +648,51 @@ class VVVFS {
|
|
|
883
648
|
* @param end 读取结束位置
|
|
884
649
|
*/
|
|
885
650
|
async readTextChunk(path: string, start: number, end: number) {
|
|
886
|
-
|
|
651
|
+
return this.#withErrorHandling("readTextChunk", async () => {
|
|
887
652
|
const chunk = await this.readChunk(path, start, end);
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
} else {
|
|
891
|
-
return null;
|
|
892
|
-
}
|
|
893
|
-
} catch (error) {
|
|
894
|
-
console.error("读取文件失败", error);
|
|
895
|
-
if (this.options.throwError) {
|
|
896
|
-
throw new VVVFSError("Read", "读取文件失败" + error);
|
|
897
|
-
}
|
|
898
|
-
}
|
|
653
|
+
return chunk ? await chunk.text() : null;
|
|
654
|
+
}, null as string | null);
|
|
899
655
|
}
|
|
900
656
|
/**
|
|
901
657
|
* 判断是否是文件
|
|
902
658
|
* @param path 文件路径
|
|
903
659
|
*/
|
|
904
660
|
async isFile(path: string) {
|
|
905
|
-
|
|
906
|
-
const
|
|
907
|
-
const { name, parent } = parsePath(targetPath);
|
|
661
|
+
return this.#withErrorHandling("isFile", async () => {
|
|
662
|
+
const { name, parent } = parsePath(path);
|
|
908
663
|
return (await this.#db.files.where({ name, path: parent, type: "file" }).count()) > 0;
|
|
909
|
-
}
|
|
910
|
-
console.error("判断文件类型失败", error);
|
|
911
|
-
if (this.options.throwError) {
|
|
912
|
-
throw new VVVFSError("IsFile", "判断文件类型失败" + error);
|
|
913
|
-
}
|
|
914
|
-
return false;
|
|
915
|
-
}
|
|
664
|
+
}, false);
|
|
916
665
|
}
|
|
917
666
|
/**
|
|
918
667
|
* 判断是否是目录
|
|
919
668
|
* @param path 文件路径
|
|
920
669
|
*/
|
|
921
670
|
async isDir(path: string) {
|
|
922
|
-
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
} catch (error) {
|
|
927
|
-
console.error("判断文件类型失败", error);
|
|
928
|
-
if (this.options.throwError) {
|
|
929
|
-
throw new VVVFSError("IsDir", "判断文件类型失败" + error);
|
|
930
|
-
}
|
|
931
|
-
return false;
|
|
932
|
-
}
|
|
671
|
+
return this.#withErrorHandling("isDir", async () => {
|
|
672
|
+
const { name, parent } = parsePath(path);
|
|
673
|
+
return (await this.#db.files.where({ name, path: parent, type: "dir" }).count()) > 0;
|
|
674
|
+
}, false);
|
|
933
675
|
}
|
|
934
676
|
/**
|
|
935
677
|
* 列出目录下的文件
|
|
936
678
|
* @param path 目录路径
|
|
937
679
|
*/
|
|
938
680
|
async list(path: string) {
|
|
939
|
-
|
|
681
|
+
return this.#withErrorHandling("list", async () => {
|
|
940
682
|
const targetPath = joinPath(path);
|
|
941
|
-
if (this.#
|
|
942
|
-
for (const handler of this.#watchers[targetPath]) {
|
|
943
|
-
if (await handler("list")) {
|
|
944
|
-
if (this.options.throwError) {
|
|
945
|
-
throw new VVVFSError("List", "列出目录下的文件失败:监听器取消了操作");
|
|
946
|
-
}
|
|
947
|
-
return [];
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
if (this.#lockedFiles[targetPath]) {
|
|
952
|
-
console.warn("文件已被锁定");
|
|
953
|
-
if (this.options.throwError) {
|
|
954
|
-
throw new VVVFSError("List", "文件已被锁定");
|
|
955
|
-
}
|
|
956
|
-
return [];
|
|
957
|
-
}
|
|
683
|
+
if (!(await this.#checkAccess(targetPath, "list"))) return [];
|
|
958
684
|
if (!(await this.exists(targetPath))) {
|
|
959
685
|
console.warn("路径不存在");
|
|
960
|
-
if (this.options.throwError) {
|
|
961
|
-
throw new VVVFSError("List", "路径不存在");
|
|
962
|
-
}
|
|
963
686
|
return [];
|
|
964
687
|
}
|
|
965
688
|
if (!(await this.isDir(targetPath))) {
|
|
966
689
|
console.warn("路径不是目录");
|
|
967
|
-
if (this.options.throwError) {
|
|
968
|
-
throw new VVVFSError("List", "路径不是目录");
|
|
969
|
-
}
|
|
970
690
|
return [];
|
|
971
691
|
}
|
|
972
692
|
return (await this.#db.files.where({ path: targetPath }).toArray())
|
|
973
693
|
.map((file) => file.name)
|
|
974
|
-
.filter((item) => item
|
|
975
|
-
}
|
|
976
|
-
console.error("列出目录下的文件失败", error);
|
|
977
|
-
if (this.options.throwError) {
|
|
978
|
-
throw new VVVFSError("List", "列出目录下的文件失败" + error);
|
|
979
|
-
}
|
|
980
|
-
return [];
|
|
981
|
-
}
|
|
694
|
+
.filter((item) => item !== "");
|
|
695
|
+
}, [] as string[]);
|
|
982
696
|
}
|
|
983
697
|
/**
|
|
984
698
|
* 重命名文件
|
|
@@ -986,50 +700,23 @@ class VVVFS {
|
|
|
986
700
|
* @param newName 新文件名
|
|
987
701
|
*/
|
|
988
702
|
async rename(path: string, newName: string) {
|
|
989
|
-
|
|
703
|
+
return this.#withErrorHandling("rename", async () => {
|
|
990
704
|
const sourcePath = joinPath(path);
|
|
991
|
-
|
|
992
|
-
if (this.#watchers[sourcePath]) {
|
|
993
|
-
for (const handler of this.#watchers[sourcePath]) {
|
|
994
|
-
if (await handler("rename")) {
|
|
995
|
-
if (this.options.throwError) {
|
|
996
|
-
throw new VVVFSError("Rename", "重命名文件失败:监听器取消了操作");
|
|
997
|
-
}
|
|
998
|
-
return false;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
if (this.#lockedFiles[sourcePath]) {
|
|
1003
|
-
console.warn("文件已被锁定");
|
|
1004
|
-
if (this.options.throwError) {
|
|
1005
|
-
throw new VVVFSError("Rename", "文件已被锁定");
|
|
1006
|
-
}
|
|
1007
|
-
return false;
|
|
1008
|
-
}
|
|
705
|
+
if (!(await this.#checkAccess(sourcePath, "rename"))) return false;
|
|
1009
706
|
if (!(await this.exists(sourcePath))) {
|
|
1010
707
|
console.warn("文件不存在");
|
|
1011
|
-
if (this.options.throwError) {
|
|
1012
|
-
throw new VVVFSError("Rename", "文件不存在");
|
|
1013
|
-
}
|
|
1014
708
|
return false;
|
|
1015
709
|
}
|
|
710
|
+
const { name, parent } = parsePath(sourcePath);
|
|
1016
711
|
const newPath = joinPath(parent, newName);
|
|
1017
|
-
if (newPath === sourcePath)
|
|
1018
|
-
return true;
|
|
1019
|
-
}
|
|
712
|
+
if (newPath === sourcePath) return true;
|
|
1020
713
|
if (await this.exists(newPath)) {
|
|
1021
714
|
console.warn("目标文件已存在");
|
|
1022
|
-
if (this.options.throwError) {
|
|
1023
|
-
throw new VVVFSError("Rename", "目标文件已存在");
|
|
1024
|
-
}
|
|
1025
715
|
return false;
|
|
1026
716
|
}
|
|
1027
717
|
const fileRecord = await this.#db.files.where({ path: parent, name }).first();
|
|
1028
718
|
if (!fileRecord) {
|
|
1029
719
|
console.warn("文件记录未找到");
|
|
1030
|
-
if (this.options.throwError) {
|
|
1031
|
-
throw new VVVFSError("Rename", "文件记录未找到");
|
|
1032
|
-
}
|
|
1033
720
|
return false;
|
|
1034
721
|
}
|
|
1035
722
|
if (fileRecord.type === "dir") {
|
|
@@ -1045,48 +732,20 @@ class VVVFS {
|
|
|
1045
732
|
await this.#db.files.update(descendant.id!, { path: updatedPath });
|
|
1046
733
|
}
|
|
1047
734
|
}
|
|
1048
|
-
await this.#db.files.update(fileRecord.id!, {
|
|
1049
|
-
name: newName,
|
|
1050
|
-
path: parent,
|
|
1051
|
-
});
|
|
735
|
+
await this.#db.files.update(fileRecord.id!, { name: newName, path: parent });
|
|
1052
736
|
return true;
|
|
1053
|
-
}
|
|
1054
|
-
console.error(e);
|
|
1055
|
-
if (this.options.throwError) {
|
|
1056
|
-
throw new VVVFSError("Rename", "重命名文件失败" + e);
|
|
1057
|
-
}
|
|
1058
|
-
return false;
|
|
1059
|
-
}
|
|
737
|
+
}, false);
|
|
1060
738
|
}
|
|
1061
739
|
/**
|
|
1062
740
|
* 删除文件
|
|
1063
741
|
* @param path 文件路径
|
|
1064
742
|
*/
|
|
1065
743
|
async delete(path: string) {
|
|
1066
|
-
|
|
744
|
+
return this.#withErrorHandling("delete", async () => {
|
|
1067
745
|
const targetPath = joinPath(path);
|
|
1068
|
-
if (this.#
|
|
1069
|
-
for (const handler of this.#watchers[targetPath]) {
|
|
1070
|
-
if (await handler("delete")) {
|
|
1071
|
-
if (this.options.throwError) {
|
|
1072
|
-
throw new VVVFSError("Delete", "删除文件失败:监听器取消了操作");
|
|
1073
|
-
}
|
|
1074
|
-
return false;
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
if (this.#lockedFiles[targetPath]) {
|
|
1079
|
-
console.warn("文件已被锁定");
|
|
1080
|
-
if (this.options.throwError) {
|
|
1081
|
-
throw new VVVFSError("Delete", "文件已被锁定");
|
|
1082
|
-
}
|
|
1083
|
-
return false;
|
|
1084
|
-
}
|
|
746
|
+
if (!(await this.#checkAccess(targetPath, "delete"))) return false;
|
|
1085
747
|
if (!(await this.exists(targetPath))) {
|
|
1086
748
|
console.warn("文件不存在");
|
|
1087
|
-
if (this.options.throwError) {
|
|
1088
|
-
throw new VVVFSError("Delete", "文件不存在");
|
|
1089
|
-
}
|
|
1090
749
|
return false;
|
|
1091
750
|
}
|
|
1092
751
|
const { name, parent } = parsePath(targetPath);
|
|
@@ -1102,22 +761,15 @@ class VVVFS {
|
|
|
1102
761
|
await this.#db.files.delete(dirRecord.id!);
|
|
1103
762
|
}
|
|
1104
763
|
return true;
|
|
1105
|
-
} else {
|
|
1106
|
-
const fileRecord = await this.#db.files
|
|
1107
|
-
.where({ name, path: parent, type: "file" })
|
|
1108
|
-
.first();
|
|
1109
|
-
if (fileRecord) {
|
|
1110
|
-
await this.#db.files.delete(fileRecord.id!);
|
|
1111
|
-
}
|
|
1112
|
-
return true;
|
|
1113
764
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
765
|
+
const fileRecord = await this.#db.files
|
|
766
|
+
.where({ name, path: parent, type: "file" })
|
|
767
|
+
.first();
|
|
768
|
+
if (fileRecord) {
|
|
769
|
+
await this.#db.files.delete(fileRecord.id!);
|
|
1118
770
|
}
|
|
1119
|
-
return
|
|
1120
|
-
}
|
|
771
|
+
return true;
|
|
772
|
+
}, false);
|
|
1121
773
|
}
|
|
1122
774
|
/**
|
|
1123
775
|
* 移动文件
|
|
@@ -1125,48 +777,21 @@ class VVVFS {
|
|
|
1125
777
|
* @param newPath 新路径
|
|
1126
778
|
*/
|
|
1127
779
|
async move(path: string, newPath: string) {
|
|
1128
|
-
|
|
780
|
+
return this.#withErrorHandling("move", async () => {
|
|
1129
781
|
const sourcePath = joinPath(path);
|
|
1130
782
|
const destinationPath = joinPath(newPath);
|
|
1131
|
-
if (this.#
|
|
1132
|
-
|
|
1133
|
-
if (await handler("move")) {
|
|
1134
|
-
if (this.options.throwError) {
|
|
1135
|
-
throw new VVVFSError("Move", "移动文件失败:监听器取消了操作");
|
|
1136
|
-
}
|
|
1137
|
-
return false;
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
if (this.#lockedFiles[sourcePath]) {
|
|
1142
|
-
console.warn("文件已被锁定");
|
|
1143
|
-
if (this.options.throwError) {
|
|
1144
|
-
throw new VVVFSError("Move", "文件已被锁定");
|
|
1145
|
-
}
|
|
1146
|
-
return false;
|
|
1147
|
-
}
|
|
1148
|
-
if (sourcePath === destinationPath) {
|
|
1149
|
-
return true;
|
|
1150
|
-
}
|
|
783
|
+
if (!(await this.#checkAccess(sourcePath, "move"))) return false;
|
|
784
|
+
if (sourcePath === destinationPath) return true;
|
|
1151
785
|
if (await this.exists(destinationPath)) {
|
|
1152
786
|
console.warn("目标文件已存在");
|
|
1153
|
-
if (this.options.throwError) {
|
|
1154
|
-
throw new VVVFSError("Move", "目标文件已存在");
|
|
1155
|
-
}
|
|
1156
787
|
return false;
|
|
1157
788
|
}
|
|
1158
789
|
if (!(await this.exists(sourcePath))) {
|
|
1159
790
|
console.warn("源文件不存在");
|
|
1160
|
-
if (this.options.throwError) {
|
|
1161
|
-
throw new VVVFSError("Move", "源文件不存在");
|
|
1162
|
-
}
|
|
1163
791
|
return false;
|
|
1164
792
|
}
|
|
1165
793
|
if (destinationPath.startsWith(sourcePath + "/")) {
|
|
1166
794
|
console.warn("目标路径是源路径的子路径");
|
|
1167
|
-
if (this.options.throwError) {
|
|
1168
|
-
throw new VVVFSError("Move", "目标路径是源路径的子路径");
|
|
1169
|
-
}
|
|
1170
795
|
return false;
|
|
1171
796
|
}
|
|
1172
797
|
const { name, parent } = parsePath(sourcePath);
|
|
@@ -1184,24 +809,17 @@ class VVVFS {
|
|
|
1184
809
|
await this.#db.files.delete(dirRecord.id!);
|
|
1185
810
|
}
|
|
1186
811
|
return true;
|
|
1187
|
-
} else {
|
|
1188
|
-
await this.createDir(newParent);
|
|
1189
|
-
const fileRecord = await this.#db.files
|
|
1190
|
-
.where({ name, path: parent, type: "file" })
|
|
1191
|
-
.first();
|
|
1192
|
-
if (fileRecord) {
|
|
1193
|
-
await this.#db.files.update(fileRecord.id!, { name: newName, path: newParent });
|
|
1194
|
-
return true;
|
|
1195
|
-
}
|
|
1196
|
-
return false;
|
|
1197
812
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
813
|
+
await this.createDir(newParent);
|
|
814
|
+
const fileRecord = await this.#db.files
|
|
815
|
+
.where({ name, path: parent, type: "file" })
|
|
816
|
+
.first();
|
|
817
|
+
if (fileRecord) {
|
|
818
|
+
await this.#db.files.update(fileRecord.id!, { name: newName, path: newParent });
|
|
819
|
+
return true;
|
|
1202
820
|
}
|
|
1203
821
|
return false;
|
|
1204
|
-
}
|
|
822
|
+
}, false);
|
|
1205
823
|
}
|
|
1206
824
|
/**
|
|
1207
825
|
* 复制文件
|
|
@@ -1209,45 +827,20 @@ class VVVFS {
|
|
|
1209
827
|
* @param newPath 新路径
|
|
1210
828
|
*/
|
|
1211
829
|
async copy(path: string, newPath: string) {
|
|
1212
|
-
|
|
830
|
+
return this.#withErrorHandling("copy", async () => {
|
|
1213
831
|
const sourcePath = joinPath(path);
|
|
1214
832
|
const destinationPath = joinPath(newPath);
|
|
1215
|
-
if (this.#
|
|
1216
|
-
for (const handler of this.#watchers[sourcePath]) {
|
|
1217
|
-
if (await handler("copy")) {
|
|
1218
|
-
if (this.options.throwError) {
|
|
1219
|
-
throw new VVVFSError("Copy", "复制文件失败:监听器取消了操作");
|
|
1220
|
-
}
|
|
1221
|
-
return false;
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
if (this.#lockedFiles[sourcePath]) {
|
|
1226
|
-
console.warn("文件已被锁定");
|
|
1227
|
-
if (this.options.throwError) {
|
|
1228
|
-
throw new VVVFSError("Copy", "文件已被锁定");
|
|
1229
|
-
}
|
|
1230
|
-
return false;
|
|
1231
|
-
}
|
|
833
|
+
if (!(await this.#checkAccess(sourcePath, "copy"))) return false;
|
|
1232
834
|
if (await this.exists(destinationPath)) {
|
|
1233
835
|
console.warn("目标文件已存在");
|
|
1234
|
-
if (this.options.throwError) {
|
|
1235
|
-
throw new VVVFSError("Copy", "目标文件已存在");
|
|
1236
|
-
}
|
|
1237
836
|
return false;
|
|
1238
837
|
}
|
|
1239
838
|
if (!(await this.exists(sourcePath))) {
|
|
1240
839
|
console.warn("源文件不存在");
|
|
1241
|
-
if (this.options.throwError) {
|
|
1242
|
-
throw new VVVFSError("Copy", "源文件不存在");
|
|
1243
|
-
}
|
|
1244
840
|
return false;
|
|
1245
841
|
}
|
|
1246
842
|
if (destinationPath.startsWith(sourcePath + "/")) {
|
|
1247
843
|
console.warn("目标路径是源路径的子路径");
|
|
1248
|
-
if (this.options.throwError) {
|
|
1249
|
-
throw new VVVFSError("Copy", "目标路径是源路径的子路径");
|
|
1250
|
-
}
|
|
1251
844
|
return false;
|
|
1252
845
|
}
|
|
1253
846
|
const { name, parent } = parsePath(sourcePath);
|
|
@@ -1259,28 +852,21 @@ class VVVFS {
|
|
|
1259
852
|
await this.copy(joinPath(sourcePath, child), joinPath(destinationPath, child));
|
|
1260
853
|
}
|
|
1261
854
|
return true;
|
|
1262
|
-
} else {
|
|
1263
|
-
const fileRecord = await this.#db.files
|
|
1264
|
-
.where({ name, path: parent, type: "file" })
|
|
1265
|
-
.first();
|
|
1266
|
-
if (fileRecord) {
|
|
1267
|
-
await this.#db.files.add({
|
|
1268
|
-
name: newName,
|
|
1269
|
-
path: newParent,
|
|
1270
|
-
type: fileRecord.type,
|
|
1271
|
-
file: fileRecord.file,
|
|
1272
|
-
});
|
|
1273
|
-
return true;
|
|
1274
|
-
}
|
|
1275
|
-
return false;
|
|
1276
855
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
856
|
+
const fileRecord = await this.#db.files
|
|
857
|
+
.where({ name, path: parent, type: "file" })
|
|
858
|
+
.first();
|
|
859
|
+
if (fileRecord) {
|
|
860
|
+
await this.#db.files.add({
|
|
861
|
+
name: newName,
|
|
862
|
+
path: newParent,
|
|
863
|
+
type: fileRecord.type,
|
|
864
|
+
file: fileRecord.file,
|
|
865
|
+
});
|
|
866
|
+
return true;
|
|
1281
867
|
}
|
|
1282
868
|
return false;
|
|
1283
|
-
}
|
|
869
|
+
}, false);
|
|
1284
870
|
}
|
|
1285
871
|
/**
|
|
1286
872
|
* 搜索文件
|
|
@@ -1288,49 +874,35 @@ class VVVFS {
|
|
|
1288
874
|
* @param query 查询字符串
|
|
1289
875
|
*/
|
|
1290
876
|
async search(basePath: string, query: string) {
|
|
1291
|
-
|
|
877
|
+
return this.#withErrorHandling("search", async () => {
|
|
1292
878
|
if (!(await this.isDir(basePath))) {
|
|
1293
879
|
console.warn("基础路径不是目录");
|
|
1294
|
-
if (this.options.throwError) {
|
|
1295
|
-
throw new VVVFSError("Search", "基础路径不是目录");
|
|
1296
|
-
}
|
|
1297
880
|
return null;
|
|
1298
881
|
}
|
|
1299
882
|
const that = this;
|
|
1300
883
|
const dirs = await this.list(basePath);
|
|
1301
|
-
return await
|
|
1302
|
-
async function
|
|
884
|
+
return await searchRecursive(basePath, dirs);
|
|
885
|
+
async function searchRecursive(parent: string, files: string[]) {
|
|
1303
886
|
const result: string[] = [];
|
|
1304
|
-
console.log(files);
|
|
1305
887
|
for (const file of files) {
|
|
1306
|
-
if (file
|
|
1307
|
-
|
|
1308
|
-
if (await that.isDir(
|
|
888
|
+
if (file === "") continue;
|
|
889
|
+
const fullPath = joinPath(parent, file);
|
|
890
|
+
if (await that.isDir(fullPath)) {
|
|
1309
891
|
if (file.includes(query)) {
|
|
1310
|
-
result.push(
|
|
892
|
+
result.push(fullPath + "/");
|
|
1311
893
|
}
|
|
1312
894
|
result.push(
|
|
1313
|
-
...(await
|
|
1314
|
-
joinPath(parent, file),
|
|
1315
|
-
await that.list(joinPath(parent, file)),
|
|
1316
|
-
)),
|
|
895
|
+
...(await searchRecursive(fullPath, await that.list(fullPath))),
|
|
1317
896
|
);
|
|
1318
|
-
} else if (await that.isFile(
|
|
897
|
+
} else if (await that.isFile(fullPath)) {
|
|
1319
898
|
if (file.includes(query)) {
|
|
1320
|
-
|
|
1321
|
-
result.push(joinPath(parent, file));
|
|
899
|
+
result.push(fullPath);
|
|
1322
900
|
}
|
|
1323
901
|
}
|
|
1324
902
|
}
|
|
1325
903
|
return result;
|
|
1326
904
|
}
|
|
1327
|
-
}
|
|
1328
|
-
console.error(e);
|
|
1329
|
-
if (this.options.throwError) {
|
|
1330
|
-
throw new VVVFSError("Search", "搜索文件失败" + e);
|
|
1331
|
-
}
|
|
1332
|
-
return null;
|
|
1333
|
-
}
|
|
905
|
+
}, null as string[] | null);
|
|
1334
906
|
}
|
|
1335
907
|
/**
|
|
1336
908
|
* 监听文件
|
|
@@ -1349,46 +921,38 @@ class VVVFS {
|
|
|
1349
921
|
* @param path 文件路径
|
|
1350
922
|
*/
|
|
1351
923
|
async lock(path: string) {
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
924
|
+
return this.#withErrorHandling("lock", async () => {
|
|
925
|
+
path = joinPath(path);
|
|
926
|
+
if (!(await this.exists(path))) {
|
|
927
|
+
console.warn("文件不存在");
|
|
928
|
+
return false;
|
|
1357
929
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
console.warn("文件已被锁定");
|
|
1362
|
-
if (this.options.throwError) {
|
|
1363
|
-
throw new VVVFSError("Lock", "文件已被锁定");
|
|
930
|
+
if (this.#lockedFiles[path]) {
|
|
931
|
+
console.warn("文件已被锁定");
|
|
932
|
+
return false;
|
|
1364
933
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
return true;
|
|
934
|
+
this.#lockedFiles[path] = true;
|
|
935
|
+
return true;
|
|
936
|
+
}, false);
|
|
1369
937
|
}
|
|
1370
938
|
/**
|
|
1371
939
|
* 解锁文件
|
|
1372
940
|
* @param path 文件路径
|
|
1373
941
|
*/
|
|
1374
942
|
async unlock(path: string) {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
943
|
+
return this.#withErrorHandling("unlock", async () => {
|
|
944
|
+
path = joinPath(path);
|
|
945
|
+
if (!(await this.exists(path))) {
|
|
946
|
+
console.warn("文件不存在");
|
|
947
|
+
return false;
|
|
1380
948
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
console.warn("文件未被锁定");
|
|
1385
|
-
if (this.options.throwError) {
|
|
1386
|
-
throw new VVVFSError("Unlock", "文件未被锁定");
|
|
949
|
+
if (!this.#lockedFiles[path]) {
|
|
950
|
+
console.warn("文件未被锁定");
|
|
951
|
+
return false;
|
|
1387
952
|
}
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
return true;
|
|
953
|
+
delete this.#lockedFiles[path];
|
|
954
|
+
return true;
|
|
955
|
+
}, false);
|
|
1392
956
|
}
|
|
1393
957
|
}
|
|
1394
958
|
/**
|
|
@@ -1418,7 +982,7 @@ function joinPath(...paths: string[]) {
|
|
|
1418
982
|
if (segments.length === 0) return ".";
|
|
1419
983
|
const isAbsolute = segments[0].startsWith("/");
|
|
1420
984
|
const parts = segments.join("/").split("/");
|
|
1421
|
-
const stack = [];
|
|
985
|
+
const stack: string[] = [];
|
|
1422
986
|
for (const part of parts) {
|
|
1423
987
|
if (part === "" || part === ".") {
|
|
1424
988
|
continue;
|
|
@@ -1450,4 +1014,4 @@ Object.defineProperty(VVVFS, "author", {
|
|
|
1450
1014
|
enumerable: true,
|
|
1451
1015
|
configurable: false,
|
|
1452
1016
|
});
|
|
1453
|
-
(globalThis as any).VVVFS = VVVFS;
|
|
1017
|
+
(globalThis as any).VVVFS = VVVFS;
|