vvvfs 0.1.2 → 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.
Files changed (4) hide show
  1. package/README.md +11 -1
  2. package/index.html +2 -1
  3. package/index.ts +306 -594
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -42,10 +42,12 @@ const vvvfs = new VVVFS("vvvfs", {
42
42
  (async function () {
43
43
  await vvvfs.reset(); // 重置文件系统
44
44
  await vvvfs.init("UserName"); // 初始化文件系统
45
- await vvvfs.watch("/home/user/Desktop/test.txt", (type) => {
45
+ vvvfs.watch("/home/user/Desktop/test.txt", (type) => {
46
46
  console.log(type); // 打印文件操作的类型
47
47
  return true; // 返回true或false,表示是否取消该操作
48
48
  });
49
+ await vvvfs.lock("/home/user/Desktop/test.txt"); // 锁定文件,防止其他代码访问该文件
50
+ await vvvfs.unlock("/home/user/Desktop/test.txt"); // 解锁文件
49
51
  await vvvfs.createDir("/home/user/Desktop"); // 创建目录,返回true和false
50
52
  await vvvfs.writeText("/home/user/Desktop/test.txt", "Hello World!"); // 写入文本文件,写入文件还包括write(path: string, content: Blob)和writeJson(path: string, content: Record<string, any>)方法,返回true和false
51
53
  await vvvfs.appendText("/home/user/Desktop/test.txt", "Hello World!"); // 追加文本文件,返回true和false
@@ -84,6 +86,14 @@ const vvvfs = new VVVFS("vvvfs", {
84
86
 
85
87
  ## 更新日志
86
88
 
89
+ ### 0.1.4
90
+
91
+ - 使用 AI 优化代码
92
+
93
+ ### 0.1.3
94
+
95
+ - 新增 `lock` 和 `unlock` 方法,可以锁定文件,防止其他代码访问该文件
96
+
87
97
  ### 0.1.2
88
98
 
89
99
  - 新增 `readChunk` 和 `readChunkText` 方法,用于读取文件块
package/index.html CHANGED
@@ -17,13 +17,14 @@
17
17
  await vvvfs.reset();
18
18
  await vvvfs.init("IFTC");
19
19
  console.log(vvvfs);
20
- await vvvfs.watch("/test.txt", (type) => {
20
+ vvvfs.watch("/test.txt", (type) => {
21
21
  console.log(type);
22
22
  // return true;
23
23
  });
24
24
  console.log("创建文件", await vvvfs.createFile("/test.txt"));
25
25
  console.log("写入文件", await vvvfs.writeText("/test.txt", "Hello World"));
26
26
  console.log("追加文件", await vvvfs.appendText("/test.txt", " - Appended"));
27
+ // await vvvfs.lock("/test.txt");
27
28
  console.log("文件是否存在", await vvvfs.exists("/test.txt"));
28
29
  console.log("文件是否存在", await vvvfs.exists("/test2.txt"));
29
30
  console.log("读取文件", await vvvfs.readText("/test.txt"));
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, any>, format: boolean = true) {
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
  /**
@@ -246,12 +246,28 @@ class VVVFSFile {
246
246
  async search(query: string) {
247
247
  return await this._vvvfs.search(this._path, query);
248
248
  }
249
- async watch(handler: (type: string) => Promise<boolean>) {
250
- return await this._vvvfs.watch(this._path, handler);
249
+ /**
250
+ * 监听文件
251
+ */
252
+ watch(handler: (type: string) => Promise<boolean>) {
253
+ return this._vvvfs.watch(this._path, handler);
254
+ }
255
+ /**
256
+ * 锁定文件
257
+ */
258
+ async lock() {
259
+ return await this._vvvfs.lock(this._path);
260
+ }
261
+ /**
262
+ * 解锁文件
263
+ */
264
+ async unlock() {
265
+ return await this._vvvfs.unlock(this._path);
251
266
  }
252
267
  }
253
268
 
254
269
  const version = packageJson.version;
270
+
255
271
  class VVVFS {
256
272
  static defaultDBName = "vvvfs";
257
273
  #db: VVVFSDatabase;
@@ -268,6 +284,69 @@ class VVVFS {
268
284
  * 虚拟文件系统监听器
269
285
  */
270
286
  #watchers: Record<string, Array<(type: string) => Promise<boolean>>> = {};
287
+ /**
288
+ * 锁定的文件
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
+
271
350
  /**
272
351
  * 创建虚拟文件系统
273
352
  * @param name 虚拟文件系统名称
@@ -295,128 +374,23 @@ class VVVFS {
295
374
  * @description 将Linux的系统初始文件初始化到数据库中
296
375
  */
297
376
  async init(user?: string) {
298
- const linuxInitFiles = [
299
- {
300
- name: "root",
301
- path: "/",
302
- type: "dir",
303
- file: new File([], "root"),
304
- },
305
- {
306
- name: "boot",
307
- path: "/",
308
- type: "dir",
309
- file: new File([], "boot"),
310
- },
311
- {
312
- name: "bin",
313
- path: "/",
314
- type: "dir",
315
- file: new File([], "bin"),
316
- },
317
- {
318
- name: "dev",
319
- path: "/",
320
- type: "dir",
321
- file: new File([], "dev"),
322
- },
323
- {
324
- name: "etc",
325
- path: "/",
326
- type: "dir",
327
- file: new File([], "etc"),
328
- },
329
- {
330
- name: "home",
331
- path: "/",
332
- type: "dir",
333
- file: new File([], "home"),
334
- },
335
- {
336
- name: user || "root",
337
- path: "/home",
338
- type: "dir",
339
- file: new File([], "home"),
340
- },
341
- {
342
- name: "lib",
343
- path: "/",
344
- type: "dir",
345
- file: new File([], "lib"),
346
- },
347
- {
348
- name: "lib64",
349
- path: "/",
350
- type: "dir",
351
- file: new File([], "lib64"),
352
- },
353
- {
354
- name: "media",
355
- path: "/",
356
- type: "dir",
357
- file: new File([], "media"),
358
- },
359
- {
360
- name: "mnt",
361
- path: "/",
362
- type: "dir",
363
- file: new File([], "mnt"),
364
- },
365
- {
366
- name: "opt",
367
- path: "/",
368
- type: "dir",
369
- file: new File([], "opt"),
370
- },
371
- {
372
- name: "proc",
373
- path: "/",
374
- type: "dir",
375
- file: new File([], "proc"),
376
- },
377
- {
378
- name: "run",
379
- path: "/",
380
- type: "dir",
381
- file: new File([], "run"),
382
- },
383
- {
384
- name: "sbin",
385
- path: "/",
386
- type: "dir",
387
- file: new File([], "sbin"),
388
- },
389
- {
390
- name: "srv",
391
- path: "/",
392
- type: "dir",
393
- file: new File([], "srv"),
394
- },
395
- {
396
- name: "sys",
397
- path: "/",
398
- type: "dir",
399
- file: new File([], "sys"),
400
- },
401
- {
402
- name: "tmp",
403
- path: "/",
404
- type: "dir",
405
- file: new File([], "tmp"),
406
- },
407
- {
408
- name: "usr",
409
- path: "/",
410
- type: "dir",
411
- file: new File([], "usr"),
412
- },
413
- {
414
- name: "var",
415
- path: "/",
416
- type: "dir",
417
- file: new File([], "var"),
418
- },
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",
419
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
+ });
420
394
  try {
421
395
  for (const file of linuxInitFiles) {
422
396
  if (await this.exists(file.path)) continue;
@@ -447,18 +421,9 @@ class VVVFS {
447
421
  * @param path 文件路径
448
422
  */
449
423
  async createFile(path: string) {
450
- const targetPath = joinPath(path);
451
- try {
452
- if (this.#watchers[targetPath]) {
453
- for (const handler of this.#watchers[targetPath]) {
454
- if (await handler("create")) {
455
- if (this.options.throwError) {
456
- throw new VVVFSError("CreateFile", "创建文件失败:监听器取消了操作");
457
- }
458
- return false;
459
- }
460
- }
461
- }
424
+ return this.#withErrorHandling("createFile", async () => {
425
+ const targetPath = joinPath(path);
426
+ if (!(await this.#checkAccess(targetPath, "create"))) return false;
462
427
  if (await this.exists(targetPath)) {
463
428
  console.warn("文件已存在");
464
429
  return true;
@@ -468,7 +433,7 @@ class VVVFS {
468
433
  await this.createDir(parent);
469
434
  }
470
435
  await this.#db.files.add({
471
- name: name,
436
+ name,
472
437
  path: parent,
473
438
  type: "file",
474
439
  file: new File([], name, {
@@ -476,147 +441,90 @@ class VVVFS {
476
441
  }),
477
442
  });
478
443
  return true;
479
- } catch (error) {
480
- console.error("创建文件失败", error);
481
- if (this.options.throwError) {
482
- throw new VVVFSError("CreateFile", "创建文件失败" + error);
483
- }
484
- return false;
485
- }
444
+ }, false);
486
445
  }
487
446
  /**
488
447
  * 创建目录
489
448
  * @param path 目录路径
490
449
  */
491
450
  async createDir(path: string) {
492
- const targetPath = joinPath(path);
493
- try {
494
- if (this.#watchers[targetPath]) {
495
- for (const handler of this.#watchers[targetPath]) {
496
- if (await handler("create")) {
497
- if (this.options.throwError) {
498
- throw new VVVFSError("CreateDir", "创建目录失败:监听器取消了操作");
499
- }
500
- return false;
501
- }
502
- }
503
- }
451
+ return this.#withErrorHandling("createDir", async () => {
452
+ const targetPath = joinPath(path);
453
+ if (!(await this.#checkAccess(targetPath, "create"))) return false;
504
454
  if (await this.exists(targetPath)) {
505
455
  console.warn("目录已存在");
506
456
  return true;
507
457
  }
508
458
  const { name, parent } = parsePath(targetPath);
509
459
  if (!(await this.exists(parent))) {
510
- if (parent == "/") {
460
+ if (parent === "/") {
511
461
  await this.#db.files.add({
512
462
  name: "",
513
463
  path: "/",
514
464
  type: "dir",
515
465
  file: new File([], ""),
516
466
  });
517
- if (name == "") return true;
467
+ if (name === "") return true;
518
468
  } else {
519
469
  await this.createDir(parent);
520
470
  }
521
471
  }
522
472
  await this.#db.files.add({
523
- name: name,
473
+ name,
524
474
  path: parent,
525
475
  type: "dir",
526
476
  file: new File([], name),
527
477
  });
528
478
  return true;
529
- } catch (error) {
530
- console.error("创建目录失败", error);
531
- if (this.options.throwError) {
532
- throw new VVVFSError("CreateDir", "创建目录失败" + error);
533
- }
534
- return false;
535
- }
479
+ }, false);
536
480
  }
537
481
  /**
538
482
  * 判断文件是否存在
539
483
  * @param path 文件路径
540
484
  */
541
485
  async exists(path: string) {
542
- try {
543
- const targetPath = joinPath(path);
544
- const { name, parent } = parsePath(targetPath);
486
+ return this.#withErrorHandling("exists", async () => {
487
+ const { name, parent } = parsePath(path);
545
488
  return (
546
489
  (await this.#db.files
547
- .where({
548
- name: name,
549
- path: parent,
550
- })
490
+ .where({ name, path: parent })
551
491
  .count()) > 0
552
492
  );
553
- } catch (error) {
554
- console.error("判断文件是否存在失败", error);
555
- if (this.options.throwError) {
556
- throw new VVVFSError("Exists", "判断文件是否存在失败" + error);
557
- }
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("路径已存在");
558
505
  return false;
559
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;
560
517
  }
561
518
  /**
562
519
  * 读取文件内容
563
520
  * @param path 文件路径
564
521
  */
565
522
  async write(path: string, content: Blob) {
566
- try {
523
+ return this.#withErrorHandling("write", async () => {
567
524
  const targetPath = joinPath(path);
568
- if (this.#watchers[targetPath]) {
569
- for (const handler of this.#watchers[targetPath]) {
570
- if (await handler("write")) {
571
- if (this.options.throwError) {
572
- throw new VVVFSError("Write", "写入文件失败:监听器取消了操作");
573
- }
574
- return false;
575
- }
576
- }
577
- }
578
- if (!(await this.exists(targetPath))) {
579
- const success = await this.createFile(targetPath);
580
- if (!success) {
581
- console.warn("创建文件失败");
582
- if (this.options.throwError) {
583
- throw new VVVFSError("Write", "创建文件失败");
584
- }
585
- return false;
586
- }
587
- }
588
- if (await this.isDir(targetPath)) {
589
- console.warn("路径已存在");
590
- if (this.options.throwError) {
591
- throw new VVVFSError("Write", "路径已存在");
592
- }
593
- return false;
594
- }
595
- const { name, parent } = parsePath(targetPath);
596
- const file = new File([content], name, {
597
- type: mime.getType(targetPath) || "application/octet-stream",
598
- });
599
- const fileRecord = await this.#db.files.where({ name, path: parent }).first();
600
- if (fileRecord) {
601
- await this.#db.files.put({
602
- ...fileRecord,
603
- file: file,
604
- });
605
- return true;
606
- } else {
607
- console.warn("文件记录未找到,无法写入内容");
608
- if (this.options.throwError) {
609
- throw new VVVFSError("Write", "文件记录未找到,无法写入内容");
610
- }
611
- return false;
612
- }
613
- } catch (error) {
614
- console.error("写入文件失败", error);
615
- if (this.options.throwError) {
616
- throw new VVVFSError("Write", "写入文件失败" + error);
617
- }
618
- return false;
619
- }
525
+ if (!(await this.#checkAccess(targetPath, "write"))) return false;
526
+ return await this.#internalWrite(targetPath, content);
527
+ }, false);
620
528
  }
621
529
  /**
622
530
  * 写入文本内容
@@ -624,16 +532,10 @@ class VVVFS {
624
532
  * @param content 文本内容
625
533
  */
626
534
  async writeText(path: string, content: string) {
627
- try {
535
+ return this.#withErrorHandling("writeText", async () => {
628
536
  const blob = new Blob([content], { type: "text/plain" });
629
537
  return await this.write(path, blob);
630
- } catch (error) {
631
- console.error("写入文件失败", error);
632
- if (this.options.throwError) {
633
- throw new VVVFSError("Write", "写入文件失败" + error);
634
- }
635
- return false;
636
- }
538
+ }, false);
637
539
  }
638
540
  /**
639
541
  * 写入JSON内容
@@ -641,19 +543,13 @@ class VVVFS {
641
543
  * @param content JSON内容
642
544
  * @param format 是否格式化
643
545
  */
644
- async writeJson(path: string, content: Record<string, any>, format: boolean = true) {
645
- try {
546
+ async writeJson(path: string, content: Record<string, unknown>, format: boolean = true) {
547
+ return this.#withErrorHandling("writeJson", async () => {
646
548
  return await this.writeText(
647
549
  path,
648
550
  JSON.stringify(content, null, format ? 4 : undefined),
649
551
  );
650
- } catch (error) {
651
- console.error("写入文件失败", error);
652
- if (this.options.throwError) {
653
- throw new VVVFSError("Write", "写入文件失败" + error);
654
- }
655
- return false;
656
- }
552
+ }, false);
657
553
  }
658
554
  /**
659
555
  * 追加内容
@@ -661,34 +557,18 @@ class VVVFS {
661
557
  * @param content 追加内容
662
558
  */
663
559
  async append(path: string, content: Blob) {
664
- try {
560
+ return this.#withErrorHandling("append", async () => {
665
561
  const targetPath = joinPath(path);
666
- if (this.#watchers[targetPath]) {
667
- for (const handler of this.#watchers[targetPath]) {
668
- if (await handler("append")) {
669
- if (this.options.throwError) {
670
- throw new VVVFSError("Append", "追加文件失败:监听器取消了操作");
671
- }
672
- return false;
673
- }
674
- }
675
- }
676
- const existingFile = await this.read(targetPath);
562
+ if (!(await this.#checkAccess(targetPath, "append"))) return false;
563
+ const existingFile = await this.#internalRead(targetPath);
677
564
  if (existingFile) {
678
565
  const blob = new Blob([existingFile, content], {
679
566
  type: "application/octet-stream",
680
567
  });
681
- return await this.write(targetPath, blob);
682
- } else {
683
- return await this.write(targetPath, content);
568
+ return await this.#internalWrite(targetPath, blob);
684
569
  }
685
- } catch (error) {
686
- console.error("追加文件失败", error);
687
- if (this.options.throwError) {
688
- throw new VVVFSError("Append", "追加文件失败" + error);
689
- }
690
- return false;
691
- }
570
+ return await this.#internalWrite(targetPath, content);
571
+ }, false);
692
572
  }
693
573
  /**
694
574
  * 追加文本内容
@@ -696,102 +576,58 @@ class VVVFS {
696
576
  * @param content 追加内容
697
577
  */
698
578
  async appendText(path: string, content: string) {
699
- try {
579
+ return this.#withErrorHandling("appendText", async () => {
700
580
  const blob = new Blob([content], { type: "text/plain" });
701
581
  return await this.append(path, blob);
702
- } catch (error) {
703
- console.error("追加文件失败", error);
704
- if (this.options.throwError) {
705
- throw new VVVFSError("Append", "追加文件失败" + error);
706
- }
707
- return false;
708
- }
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;
709
591
  }
710
592
  /**
711
593
  * 读取文件内容
712
594
  * @param path 文件路径
713
595
  */
714
596
  async read(path: string) {
715
- try {
597
+ return this.#withErrorHandling("read", async () => {
716
598
  const targetPath = joinPath(path);
717
- if (this.#watchers[targetPath]) {
718
- for (const handler of this.#watchers[targetPath]) {
719
- if (await handler("read")) {
720
- if (this.options.throwError) {
721
- throw new VVVFSError("Read", "读取文件失败:监听器取消了操作");
722
- }
723
- return null;
724
- }
725
- }
726
- }
727
- if (!(await this.exists(targetPath))) {
728
- console.warn("文件不存在");
729
- if (this.options.throwError) {
730
- throw new VVVFSError("Read", "文件不存在");
731
- }
732
- return null;
733
- }
734
- const { name, parent } = parsePath(targetPath);
735
- return (await this.#db.files.where({ name, path: parent }).first())?.file;
736
- } catch (error) {
737
- console.error("读取文件失败", error);
738
- if (this.options.throwError) {
739
- throw new VVVFSError("Read", "读取文件失败" + error);
740
- }
741
- return null;
742
- }
599
+ if (!(await this.#checkAccess(targetPath, "read"))) return null;
600
+ return await this.#internalRead(targetPath);
601
+ }, null as File | null);
743
602
  }
744
603
  /**
745
604
  * 读取文件内容
746
605
  * @param path 文件路径
747
606
  */
748
607
  async readText(path: string) {
749
- try {
608
+ return this.#withErrorHandling("readText", async () => {
750
609
  const file = await this.read(path);
751
- if (file) {
752
- return await file.text();
753
- } else {
754
- console.warn("文件不存在");
755
- if (this.options.throwError) {
756
- throw new VVVFSError("Read", "文件不存在");
757
- }
758
- return null;
759
- }
760
- } catch (error) {
761
- console.error("读取文件失败", error);
762
- if (this.options.throwError) {
763
- throw new VVVFSError("Read", "读取文件失败" + error);
764
- }
765
- return null;
766
- }
610
+ return file ? await file.text() : null;
611
+ }, null as string | null);
767
612
  }
768
613
  /**
769
614
  * 读取JSON内容
770
615
  * @param path 文件路径
771
616
  */
772
617
  async readJson(path: string) {
773
- try {
618
+ return this.#withErrorHandling("readJson", async () => {
774
619
  const text = await this.readText(path);
775
- if (text) {
776
- try {
777
- return JSON.parse(text);
778
- } catch (error) {
779
- console.error("解析JSON失败", error);
780
- if (this.options.throwError) {
781
- throw new VVVFSError("Read", "解析JSON失败" + error);
782
- }
783
- 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);
784
627
  }
785
- } else {
786
628
  return null;
787
629
  }
788
- } catch (error) {
789
- console.error("读取文件失败", error);
790
- if (this.options.throwError) {
791
- throw new VVVFSError("Read", "读取文件失败" + error);
792
- }
793
- return null;
794
- }
630
+ }, null as Record<string, unknown> | null);
795
631
  }
796
632
  /**
797
633
  * 读取文件内容
@@ -800,34 +636,10 @@ class VVVFS {
800
636
  * @param end 结束位置
801
637
  */
802
638
  async readChunk(path: string, start: number, end: number) {
803
- try {
804
- const targetPath = joinPath(path);
805
- if (this.#watchers[targetPath]) {
806
- for (const handler of this.#watchers[targetPath]) {
807
- if (await handler("read")) {
808
- if (this.options.throwError) {
809
- throw new VVVFSError("Read", "读取文件失败:监听器取消了操作");
810
- }
811
- return null;
812
- }
813
- }
814
- }
815
- if (!(await this.exists(targetPath))) {
816
- console.warn("文件不存在");
817
- if (this.options.throwError) {
818
- throw new VVVFSError("Read", "文件不存在");
819
- }
820
- return null;
821
- }
822
- const file = await this.read(targetPath);
823
- return file?.slice(start, end) || null;
824
- } catch (error) {
825
- console.error("读取文件失败", error);
826
- if (this.options.throwError) {
827
- throw new VVVFSError("Read", "读取文件失败" + error);
828
- }
829
- return null;
830
- }
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);
831
643
  }
832
644
  /**
833
645
  * 读取文件内容
@@ -836,95 +648,51 @@ class VVVFS {
836
648
  * @param end 读取结束位置
837
649
  */
838
650
  async readTextChunk(path: string, start: number, end: number) {
839
- try {
651
+ return this.#withErrorHandling("readTextChunk", async () => {
840
652
  const chunk = await this.readChunk(path, start, end);
841
- if (chunk) {
842
- return await chunk.text();
843
- } else {
844
- return null;
845
- }
846
- } catch (error) {
847
- console.error("读取文件失败", error);
848
- if (this.options.throwError) {
849
- throw new VVVFSError("Read", "读取文件失败" + error);
850
- }
851
- }
653
+ return chunk ? await chunk.text() : null;
654
+ }, null as string | null);
852
655
  }
853
656
  /**
854
657
  * 判断是否是文件
855
658
  * @param path 文件路径
856
659
  */
857
660
  async isFile(path: string) {
858
- try {
859
- const targetPath = joinPath(path);
860
- const { name, parent } = parsePath(targetPath);
661
+ return this.#withErrorHandling("isFile", async () => {
662
+ const { name, parent } = parsePath(path);
861
663
  return (await this.#db.files.where({ name, path: parent, type: "file" }).count()) > 0;
862
- } catch (error) {
863
- console.error("判断文件类型失败", error);
864
- if (this.options.throwError) {
865
- throw new VVVFSError("IsFile", "判断文件类型失败" + error);
866
- }
867
- return false;
868
- }
664
+ }, false);
869
665
  }
870
666
  /**
871
667
  * 判断是否是目录
872
668
  * @param path 文件路径
873
669
  */
874
670
  async isDir(path: string) {
875
- try {
876
- const targetPath = joinPath(path);
877
- const { name, parent } = parsePath(targetPath);
878
- return (await this.#db.files.where({ path: parent, name, type: "dir" }).count()) > 0;
879
- } catch (error) {
880
- console.error("判断文件类型失败", error);
881
- if (this.options.throwError) {
882
- throw new VVVFSError("IsDir", "判断文件类型失败" + error);
883
- }
884
- return false;
885
- }
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);
886
675
  }
887
676
  /**
888
677
  * 列出目录下的文件
889
678
  * @param path 目录路径
890
679
  */
891
680
  async list(path: string) {
892
- try {
681
+ return this.#withErrorHandling("list", async () => {
893
682
  const targetPath = joinPath(path);
894
- if (this.#watchers[targetPath]) {
895
- for (const handler of this.#watchers[targetPath]) {
896
- if (await handler("list")) {
897
- if (this.options.throwError) {
898
- throw new VVVFSError("List", "列出目录下的文件失败:监听器取消了操作");
899
- }
900
- return [];
901
- }
902
- }
903
- }
683
+ if (!(await this.#checkAccess(targetPath, "list"))) return [];
904
684
  if (!(await this.exists(targetPath))) {
905
685
  console.warn("路径不存在");
906
- if (this.options.throwError) {
907
- throw new VVVFSError("List", "路径不存在");
908
- }
909
686
  return [];
910
687
  }
911
688
  if (!(await this.isDir(targetPath))) {
912
689
  console.warn("路径不是目录");
913
- if (this.options.throwError) {
914
- throw new VVVFSError("List", "路径不是目录");
915
- }
916
690
  return [];
917
691
  }
918
692
  return (await this.#db.files.where({ path: targetPath }).toArray())
919
693
  .map((file) => file.name)
920
- .filter((item) => item != "");
921
- } catch (error) {
922
- console.error("列出目录下的文件失败", error);
923
- if (this.options.throwError) {
924
- throw new VVVFSError("List", "列出目录下的文件失败" + error);
925
- }
926
- return [];
927
- }
694
+ .filter((item) => item !== "");
695
+ }, [] as string[]);
928
696
  }
929
697
  /**
930
698
  * 重命名文件
@@ -932,43 +700,23 @@ class VVVFS {
932
700
  * @param newName 新文件名
933
701
  */
934
702
  async rename(path: string, newName: string) {
935
- try {
703
+ return this.#withErrorHandling("rename", async () => {
936
704
  const sourcePath = joinPath(path);
937
- const { name, parent } = parsePath(sourcePath);
938
- if (this.#watchers[sourcePath]) {
939
- for (const handler of this.#watchers[sourcePath]) {
940
- if (await handler("rename")) {
941
- if (this.options.throwError) {
942
- throw new VVVFSError("Rename", "重命名文件失败:监听器取消了操作");
943
- }
944
- return false;
945
- }
946
- }
947
- }
705
+ if (!(await this.#checkAccess(sourcePath, "rename"))) return false;
948
706
  if (!(await this.exists(sourcePath))) {
949
707
  console.warn("文件不存在");
950
- if (this.options.throwError) {
951
- throw new VVVFSError("Rename", "文件不存在");
952
- }
953
708
  return false;
954
709
  }
710
+ const { name, parent } = parsePath(sourcePath);
955
711
  const newPath = joinPath(parent, newName);
956
- if (newPath === sourcePath) {
957
- return true;
958
- }
712
+ if (newPath === sourcePath) return true;
959
713
  if (await this.exists(newPath)) {
960
714
  console.warn("目标文件已存在");
961
- if (this.options.throwError) {
962
- throw new VVVFSError("Rename", "目标文件已存在");
963
- }
964
715
  return false;
965
716
  }
966
717
  const fileRecord = await this.#db.files.where({ path: parent, name }).first();
967
718
  if (!fileRecord) {
968
719
  console.warn("文件记录未找到");
969
- if (this.options.throwError) {
970
- throw new VVVFSError("Rename", "文件记录未找到");
971
- }
972
720
  return false;
973
721
  }
974
722
  if (fileRecord.type === "dir") {
@@ -984,41 +732,20 @@ class VVVFS {
984
732
  await this.#db.files.update(descendant.id!, { path: updatedPath });
985
733
  }
986
734
  }
987
- await this.#db.files.update(fileRecord.id!, {
988
- name: newName,
989
- path: parent,
990
- });
735
+ await this.#db.files.update(fileRecord.id!, { name: newName, path: parent });
991
736
  return true;
992
- } catch (e) {
993
- console.error(e);
994
- if (this.options.throwError) {
995
- throw new VVVFSError("Rename", "重命名文件失败" + e);
996
- }
997
- return false;
998
- }
737
+ }, false);
999
738
  }
1000
739
  /**
1001
740
  * 删除文件
1002
741
  * @param path 文件路径
1003
742
  */
1004
743
  async delete(path: string) {
1005
- try {
744
+ return this.#withErrorHandling("delete", async () => {
1006
745
  const targetPath = joinPath(path);
1007
- if (this.#watchers[targetPath]) {
1008
- for (const handler of this.#watchers[targetPath]) {
1009
- if (await handler("delete")) {
1010
- if (this.options.throwError) {
1011
- throw new VVVFSError("Delete", "删除文件失败:监听器取消了操作");
1012
- }
1013
- return false;
1014
- }
1015
- }
1016
- }
746
+ if (!(await this.#checkAccess(targetPath, "delete"))) return false;
1017
747
  if (!(await this.exists(targetPath))) {
1018
748
  console.warn("文件不存在");
1019
- if (this.options.throwError) {
1020
- throw new VVVFSError("Delete", "文件不存在");
1021
- }
1022
749
  return false;
1023
750
  }
1024
751
  const { name, parent } = parsePath(targetPath);
@@ -1034,22 +761,15 @@ class VVVFS {
1034
761
  await this.#db.files.delete(dirRecord.id!);
1035
762
  }
1036
763
  return true;
1037
- } else {
1038
- const fileRecord = await this.#db.files
1039
- .where({ name, path: parent, type: "file" })
1040
- .first();
1041
- if (fileRecord) {
1042
- await this.#db.files.delete(fileRecord.id!);
1043
- }
1044
- return true;
1045
764
  }
1046
- } catch (e) {
1047
- console.error(e);
1048
- if (this.options.throwError) {
1049
- throw new VVVFSError("Delete", "删除文件失败" + e);
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!);
1050
770
  }
1051
- return false;
1052
- }
771
+ return true;
772
+ }, false);
1053
773
  }
1054
774
  /**
1055
775
  * 移动文件
@@ -1057,41 +777,21 @@ class VVVFS {
1057
777
  * @param newPath 新路径
1058
778
  */
1059
779
  async move(path: string, newPath: string) {
1060
- try {
780
+ return this.#withErrorHandling("move", async () => {
1061
781
  const sourcePath = joinPath(path);
1062
782
  const destinationPath = joinPath(newPath);
1063
- if (this.#watchers[sourcePath]) {
1064
- for (const handler of this.#watchers[sourcePath]) {
1065
- if (await handler("move")) {
1066
- if (this.options.throwError) {
1067
- throw new VVVFSError("Move", "移动文件失败:监听器取消了操作");
1068
- }
1069
- return false;
1070
- }
1071
- }
1072
- }
1073
- if (sourcePath === destinationPath) {
1074
- return true;
1075
- }
783
+ if (!(await this.#checkAccess(sourcePath, "move"))) return false;
784
+ if (sourcePath === destinationPath) return true;
1076
785
  if (await this.exists(destinationPath)) {
1077
786
  console.warn("目标文件已存在");
1078
- if (this.options.throwError) {
1079
- throw new VVVFSError("Move", "目标文件已存在");
1080
- }
1081
787
  return false;
1082
788
  }
1083
789
  if (!(await this.exists(sourcePath))) {
1084
790
  console.warn("源文件不存在");
1085
- if (this.options.throwError) {
1086
- throw new VVVFSError("Move", "源文件不存在");
1087
- }
1088
791
  return false;
1089
792
  }
1090
793
  if (destinationPath.startsWith(sourcePath + "/")) {
1091
794
  console.warn("目标路径是源路径的子路径");
1092
- if (this.options.throwError) {
1093
- throw new VVVFSError("Move", "目标路径是源路径的子路径");
1094
- }
1095
795
  return false;
1096
796
  }
1097
797
  const { name, parent } = parsePath(sourcePath);
@@ -1109,24 +809,17 @@ class VVVFS {
1109
809
  await this.#db.files.delete(dirRecord.id!);
1110
810
  }
1111
811
  return true;
1112
- } else {
1113
- await this.createDir(newParent);
1114
- const fileRecord = await this.#db.files
1115
- .where({ name, path: parent, type: "file" })
1116
- .first();
1117
- if (fileRecord) {
1118
- await this.#db.files.update(fileRecord.id!, { name: newName, path: newParent });
1119
- return true;
1120
- }
1121
- return false;
1122
812
  }
1123
- } catch (e) {
1124
- console.error(e);
1125
- if (this.options.throwError) {
1126
- throw new VVVFSError("Move", "移动文件失败" + e);
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;
1127
820
  }
1128
821
  return false;
1129
- }
822
+ }, false);
1130
823
  }
1131
824
  /**
1132
825
  * 复制文件
@@ -1134,38 +827,20 @@ class VVVFS {
1134
827
  * @param newPath 新路径
1135
828
  */
1136
829
  async copy(path: string, newPath: string) {
1137
- try {
830
+ return this.#withErrorHandling("copy", async () => {
1138
831
  const sourcePath = joinPath(path);
1139
832
  const destinationPath = joinPath(newPath);
1140
- if (this.#watchers[sourcePath]) {
1141
- for (const handler of this.#watchers[sourcePath]) {
1142
- if (await handler("copy")) {
1143
- if (this.options.throwError) {
1144
- throw new VVVFSError("Copy", "复制文件失败:监听器取消了操作");
1145
- }
1146
- return false;
1147
- }
1148
- }
1149
- }
833
+ if (!(await this.#checkAccess(sourcePath, "copy"))) return false;
1150
834
  if (await this.exists(destinationPath)) {
1151
835
  console.warn("目标文件已存在");
1152
- if (this.options.throwError) {
1153
- throw new VVVFSError("Copy", "目标文件已存在");
1154
- }
1155
836
  return false;
1156
837
  }
1157
838
  if (!(await this.exists(sourcePath))) {
1158
839
  console.warn("源文件不存在");
1159
- if (this.options.throwError) {
1160
- throw new VVVFSError("Copy", "源文件不存在");
1161
- }
1162
840
  return false;
1163
841
  }
1164
842
  if (destinationPath.startsWith(sourcePath + "/")) {
1165
843
  console.warn("目标路径是源路径的子路径");
1166
- if (this.options.throwError) {
1167
- throw new VVVFSError("Copy", "目标路径是源路径的子路径");
1168
- }
1169
844
  return false;
1170
845
  }
1171
846
  const { name, parent } = parsePath(sourcePath);
@@ -1177,28 +852,21 @@ class VVVFS {
1177
852
  await this.copy(joinPath(sourcePath, child), joinPath(destinationPath, child));
1178
853
  }
1179
854
  return true;
1180
- } else {
1181
- const fileRecord = await this.#db.files
1182
- .where({ name, path: parent, type: "file" })
1183
- .first();
1184
- if (fileRecord) {
1185
- await this.#db.files.add({
1186
- name: newName,
1187
- path: newParent,
1188
- type: fileRecord.type,
1189
- file: fileRecord.file,
1190
- });
1191
- return true;
1192
- }
1193
- return false;
1194
855
  }
1195
- } catch (e) {
1196
- console.error(e);
1197
- if (this.options.throwError) {
1198
- throw new VVVFSError("Copy", "复制文件失败" + e);
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;
1199
867
  }
1200
868
  return false;
1201
- }
869
+ }, false);
1202
870
  }
1203
871
  /**
1204
872
  * 搜索文件
@@ -1206,58 +874,91 @@ class VVVFS {
1206
874
  * @param query 查询字符串
1207
875
  */
1208
876
  async search(basePath: string, query: string) {
1209
- try {
877
+ return this.#withErrorHandling("search", async () => {
1210
878
  if (!(await this.isDir(basePath))) {
1211
879
  console.warn("基础路径不是目录");
1212
- if (this.options.throwError) {
1213
- throw new VVVFSError("Search", "基础路径不是目录");
1214
- }
1215
880
  return null;
1216
881
  }
1217
882
  const that = this;
1218
883
  const dirs = await this.list(basePath);
1219
- return await search(basePath, dirs);
1220
- async function search(parent: string, files: string[]) {
884
+ return await searchRecursive(basePath, dirs);
885
+ async function searchRecursive(parent: string, files: string[]) {
1221
886
  const result: string[] = [];
1222
- console.log(files);
1223
887
  for (const file of files) {
1224
- if (file == "") continue;
1225
- console.log(file);
1226
- if (await that.isDir(joinPath(parent, file))) {
888
+ if (file === "") continue;
889
+ const fullPath = joinPath(parent, file);
890
+ if (await that.isDir(fullPath)) {
1227
891
  if (file.includes(query)) {
1228
- result.push(joinPath(parent, file) + "/");
892
+ result.push(fullPath + "/");
1229
893
  }
1230
894
  result.push(
1231
- ...(await search(
1232
- joinPath(parent, file),
1233
- await that.list(joinPath(parent, file)),
1234
- )),
895
+ ...(await searchRecursive(fullPath, await that.list(fullPath))),
1235
896
  );
1236
- } else if (await that.isFile(joinPath(parent, file))) {
897
+ } else if (await that.isFile(fullPath)) {
1237
898
  if (file.includes(query)) {
1238
- console.log(joinPath(parent, file));
1239
- result.push(joinPath(parent, file));
899
+ result.push(fullPath);
1240
900
  }
1241
901
  }
1242
902
  }
1243
903
  return result;
1244
904
  }
1245
- } catch (e) {
1246
- console.error(e);
1247
- if (this.options.throwError) {
1248
- throw new VVVFSError("Search", "搜索文件失败" + e);
1249
- }
1250
- return null;
1251
- }
905
+ }, null as string[] | null);
1252
906
  }
1253
- async watch(path: string, handler: (type: string) => Promise<boolean>) {
907
+ /**
908
+ * 监听文件
909
+ * @param path 文件路径
910
+ * @param handler 监听器
911
+ */
912
+ watch(path: string, handler: (type: string) => Promise<boolean>) {
1254
913
  path = joinPath(path);
1255
914
  if (!this.#watchers[path]) {
1256
915
  this.#watchers[path] = [];
1257
916
  }
1258
917
  this.#watchers[path].push(handler);
1259
918
  }
919
+ /**
920
+ * 锁定文件
921
+ * @param path 文件路径
922
+ */
923
+ async lock(path: string) {
924
+ return this.#withErrorHandling("lock", async () => {
925
+ path = joinPath(path);
926
+ if (!(await this.exists(path))) {
927
+ console.warn("文件不存在");
928
+ return false;
929
+ }
930
+ if (this.#lockedFiles[path]) {
931
+ console.warn("文件已被锁定");
932
+ return false;
933
+ }
934
+ this.#lockedFiles[path] = true;
935
+ return true;
936
+ }, false);
937
+ }
938
+ /**
939
+ * 解锁文件
940
+ * @param path 文件路径
941
+ */
942
+ async unlock(path: string) {
943
+ return this.#withErrorHandling("unlock", async () => {
944
+ path = joinPath(path);
945
+ if (!(await this.exists(path))) {
946
+ console.warn("文件不存在");
947
+ return false;
948
+ }
949
+ if (!this.#lockedFiles[path]) {
950
+ console.warn("文件未被锁定");
951
+ return false;
952
+ }
953
+ delete this.#lockedFiles[path];
954
+ return true;
955
+ }, false);
956
+ }
1260
957
  }
958
+ /**
959
+ * 解析路径
960
+ * @param path 路径
961
+ */
1261
962
  function parsePath(path: string) {
1262
963
  path = joinPath(path);
1263
964
  const oldParts = path.split("/");
@@ -1271,12 +972,17 @@ function parsePath(path: string) {
1271
972
  if (joinPath(parent, name).length > 4096) throw new VVVFSError("ParsePath", "文件路径过长");
1272
973
  return { name, parent };
1273
974
  }
975
+
976
+ /**
977
+ * 合并路径
978
+ * @param paths 路径
979
+ */
1274
980
  function joinPath(...paths: string[]) {
1275
981
  const segments = paths.map((p) => String(p)).filter((p) => p.length > 0);
1276
982
  if (segments.length === 0) return ".";
1277
983
  const isAbsolute = segments[0].startsWith("/");
1278
984
  const parts = segments.join("/").split("/");
1279
- const stack = [];
985
+ const stack: string[] = [];
1280
986
  for (const part of parts) {
1281
987
  if (part === "" || part === ".") {
1282
988
  continue;
@@ -1302,4 +1008,10 @@ Object.defineProperty(VVVFS, "version", {
1302
1008
  enumerable: true,
1303
1009
  configurable: false,
1304
1010
  });
1305
- (globalThis as any).VVVFS = VVVFS;
1011
+ Object.defineProperty(VVVFS, "author", {
1012
+ value: "IFTC",
1013
+ writable: false,
1014
+ enumerable: true,
1015
+ configurable: false,
1016
+ });
1017
+ (globalThis as any).VVVFS = VVVFS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vvvfs",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "一个使用IndexDB实现的虚拟文件系统",
5
5
  "keywords": [
6
6
  "Virtual",