vvvfs 0.1.3 → 0.1.5

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