vvvfs 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/index.html +4 -2
- package/index.ts +279 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,14 +42,17 @@ const vvvfs = new VVVFS("vvvfs", {
|
|
|
42
42
|
(async function () {
|
|
43
43
|
await vvvfs.reset(); // 重置文件系统
|
|
44
44
|
await vvvfs.init("UserName"); // 初始化文件系统
|
|
45
|
-
|
|
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
|
|
52
54
|
console.log(await vvvfs.readText("/home/user/Desktop/test.txt")); // 读取文本文件,读取文件还包括read(path: string): Blob | null和readJson(path: string): Record<string, any> | null方法
|
|
55
|
+
console.log(await vvvfs.readChunkText("/home/user/Desktop/test.txt", 0, 5)); // 读取文件块,返回string | null
|
|
53
56
|
await vvvfs.delete("/home/user/Desktop/test.txt"); // 删除文件,返回true和false
|
|
54
57
|
if (await vvvfs.exists("/home/user/Desktop")) {
|
|
55
58
|
// 判断文件或目录是否存在
|
|
@@ -83,6 +86,13 @@ const vvvfs = new VVVFS("vvvfs", {
|
|
|
83
86
|
|
|
84
87
|
## 更新日志
|
|
85
88
|
|
|
89
|
+
### 0.1.3
|
|
90
|
+
- 新增 `lock` 和 `unlock` 方法,可以锁定文件,防止其他代码访问该文件
|
|
91
|
+
|
|
92
|
+
### 0.1.2
|
|
93
|
+
|
|
94
|
+
- 新增 `readChunk` 和 `readChunkText` 方法,用于读取文件块
|
|
95
|
+
|
|
86
96
|
### 0.1.1
|
|
87
97
|
|
|
88
98
|
- 新增 `append` 和 `appendText` 方法,用于追加内容
|
package/index.html
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="
|
|
2
|
+
<html lang="zh-CN">
|
|
3
3
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="UTF-8">
|
|
@@ -17,16 +17,18 @@
|
|
|
17
17
|
await vvvfs.reset();
|
|
18
18
|
await vvvfs.init("IFTC");
|
|
19
19
|
console.log(vvvfs);
|
|
20
|
-
|
|
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"));
|
|
31
|
+
console.log("读取文件块", await vvvfs.readTextChunk("/test.txt", 0, 5));
|
|
30
32
|
console.log("复制文件", await vvvfs.copy("/test.txt", "/test2.txt"));
|
|
31
33
|
console.log("搜索文件", await vvvfs.search("/", "t"));
|
|
32
34
|
console.log("移动文件", await vvvfs.move("/test2.txt", "/test3.txt"));
|
package/index.ts
CHANGED
|
@@ -129,6 +129,22 @@ class VVVFSFile {
|
|
|
129
129
|
async readJSON() {
|
|
130
130
|
return await this._vvvfs.readJson(this._path);
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* 读取文件内容
|
|
134
|
+
* @param start 起始位置
|
|
135
|
+
* @param end 结束位置
|
|
136
|
+
*/
|
|
137
|
+
async readChunk(start: number, end: number) {
|
|
138
|
+
return await this._vvvfs.readChunk(this._path, start, end);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 读取文件内容
|
|
142
|
+
* @param start 起始位置
|
|
143
|
+
* @param end 结束位置
|
|
144
|
+
*/
|
|
145
|
+
async readTextChunk(start: number, end: number) {
|
|
146
|
+
return await this._vvvfs.readTextChunk(this._path, start, end);
|
|
147
|
+
}
|
|
132
148
|
/**
|
|
133
149
|
* 写入文件
|
|
134
150
|
* @param file 文件对象
|
|
@@ -230,15 +246,30 @@ class VVVFSFile {
|
|
|
230
246
|
async search(query: string) {
|
|
231
247
|
return await this._vvvfs.search(this._path, query);
|
|
232
248
|
}
|
|
233
|
-
|
|
234
|
-
|
|
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);
|
|
235
266
|
}
|
|
236
267
|
}
|
|
237
268
|
|
|
238
269
|
const version = packageJson.version;
|
|
239
270
|
class VVVFS {
|
|
240
271
|
static defaultDBName = "vvvfs";
|
|
241
|
-
|
|
272
|
+
#db: VVVFSDatabase;
|
|
242
273
|
options: VVVFSOptions;
|
|
243
274
|
/**
|
|
244
275
|
* 虚拟文件系统版本
|
|
@@ -251,7 +282,11 @@ class VVVFS {
|
|
|
251
282
|
/**
|
|
252
283
|
* 虚拟文件系统监听器
|
|
253
284
|
*/
|
|
254
|
-
watchers: Record<string, Array<(type: string) => Promise<boolean>>> = {};
|
|
285
|
+
#watchers: Record<string, Array<(type: string) => Promise<boolean>>> = {};
|
|
286
|
+
/**
|
|
287
|
+
* 锁定的文件
|
|
288
|
+
*/
|
|
289
|
+
#lockedFiles: Record<string, boolean> = {};
|
|
255
290
|
/**
|
|
256
291
|
* 创建虚拟文件系统
|
|
257
292
|
* @param name 虚拟文件系统名称
|
|
@@ -265,8 +300,8 @@ class VVVFS {
|
|
|
265
300
|
) {
|
|
266
301
|
this.options = options;
|
|
267
302
|
try {
|
|
268
|
-
this
|
|
269
|
-
this
|
|
303
|
+
this.#db = new Dexie(name || VVVFS.defaultDBName) as VVVFSDatabase;
|
|
304
|
+
this.#db.version(1).stores({
|
|
270
305
|
files: "++id, name, path, type, file, [name+path+type]",
|
|
271
306
|
});
|
|
272
307
|
} catch (error) {
|
|
@@ -404,7 +439,7 @@ class VVVFS {
|
|
|
404
439
|
try {
|
|
405
440
|
for (const file of linuxInitFiles) {
|
|
406
441
|
if (await this.exists(file.path)) continue;
|
|
407
|
-
await this
|
|
442
|
+
await this.#db.files.put(file);
|
|
408
443
|
}
|
|
409
444
|
} catch (error) {
|
|
410
445
|
console.error("初始化文件失败", error);
|
|
@@ -416,9 +451,9 @@ class VVVFS {
|
|
|
416
451
|
*/
|
|
417
452
|
async reset() {
|
|
418
453
|
try {
|
|
419
|
-
await this
|
|
420
|
-
this
|
|
421
|
-
this
|
|
454
|
+
await this.#db.delete();
|
|
455
|
+
this.#db = new Dexie(this.#db.name) as VVVFSDatabase;
|
|
456
|
+
this.#db.version(1).stores({
|
|
422
457
|
files: "++id, name, path, type, file, [name+path+type]",
|
|
423
458
|
});
|
|
424
459
|
} catch (error) {
|
|
@@ -433,8 +468,8 @@ class VVVFS {
|
|
|
433
468
|
async createFile(path: string) {
|
|
434
469
|
const targetPath = joinPath(path);
|
|
435
470
|
try {
|
|
436
|
-
if (this
|
|
437
|
-
for (const handler of this
|
|
471
|
+
if (this.#watchers[targetPath]) {
|
|
472
|
+
for (const handler of this.#watchers[targetPath]) {
|
|
438
473
|
if (await handler("create")) {
|
|
439
474
|
if (this.options.throwError) {
|
|
440
475
|
throw new VVVFSError("CreateFile", "创建文件失败:监听器取消了操作");
|
|
@@ -451,7 +486,7 @@ class VVVFS {
|
|
|
451
486
|
if (!(await this.exists(parent))) {
|
|
452
487
|
await this.createDir(parent);
|
|
453
488
|
}
|
|
454
|
-
await this
|
|
489
|
+
await this.#db.files.add({
|
|
455
490
|
name: name,
|
|
456
491
|
path: parent,
|
|
457
492
|
type: "file",
|
|
@@ -475,8 +510,8 @@ class VVVFS {
|
|
|
475
510
|
async createDir(path: string) {
|
|
476
511
|
const targetPath = joinPath(path);
|
|
477
512
|
try {
|
|
478
|
-
if (this
|
|
479
|
-
for (const handler of this
|
|
513
|
+
if (this.#watchers[targetPath]) {
|
|
514
|
+
for (const handler of this.#watchers[targetPath]) {
|
|
480
515
|
if (await handler("create")) {
|
|
481
516
|
if (this.options.throwError) {
|
|
482
517
|
throw new VVVFSError("CreateDir", "创建目录失败:监听器取消了操作");
|
|
@@ -492,7 +527,7 @@ class VVVFS {
|
|
|
492
527
|
const { name, parent } = parsePath(targetPath);
|
|
493
528
|
if (!(await this.exists(parent))) {
|
|
494
529
|
if (parent == "/") {
|
|
495
|
-
await this
|
|
530
|
+
await this.#db.files.add({
|
|
496
531
|
name: "",
|
|
497
532
|
path: "/",
|
|
498
533
|
type: "dir",
|
|
@@ -503,7 +538,7 @@ class VVVFS {
|
|
|
503
538
|
await this.createDir(parent);
|
|
504
539
|
}
|
|
505
540
|
}
|
|
506
|
-
await this
|
|
541
|
+
await this.#db.files.add({
|
|
507
542
|
name: name,
|
|
508
543
|
path: parent,
|
|
509
544
|
type: "dir",
|
|
@@ -527,7 +562,7 @@ class VVVFS {
|
|
|
527
562
|
const targetPath = joinPath(path);
|
|
528
563
|
const { name, parent } = parsePath(targetPath);
|
|
529
564
|
return (
|
|
530
|
-
(await this
|
|
565
|
+
(await this.#db.files
|
|
531
566
|
.where({
|
|
532
567
|
name: name,
|
|
533
568
|
path: parent,
|
|
@@ -549,8 +584,8 @@ class VVVFS {
|
|
|
549
584
|
async write(path: string, content: Blob) {
|
|
550
585
|
try {
|
|
551
586
|
const targetPath = joinPath(path);
|
|
552
|
-
if (this
|
|
553
|
-
for (const handler of this
|
|
587
|
+
if (this.#watchers[targetPath]) {
|
|
588
|
+
for (const handler of this.#watchers[targetPath]) {
|
|
554
589
|
if (await handler("write")) {
|
|
555
590
|
if (this.options.throwError) {
|
|
556
591
|
throw new VVVFSError("Write", "写入文件失败:监听器取消了操作");
|
|
@@ -559,6 +594,13 @@ class VVVFS {
|
|
|
559
594
|
}
|
|
560
595
|
}
|
|
561
596
|
}
|
|
597
|
+
if (this.#lockedFiles[targetPath]) {
|
|
598
|
+
console.warn("文件已被锁定");
|
|
599
|
+
if (this.options.throwError) {
|
|
600
|
+
throw new VVVFSError("Write", "文件已被锁定");
|
|
601
|
+
}
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
562
604
|
if (!(await this.exists(targetPath))) {
|
|
563
605
|
const success = await this.createFile(targetPath);
|
|
564
606
|
if (!success) {
|
|
@@ -580,9 +622,9 @@ class VVVFS {
|
|
|
580
622
|
const file = new File([content], name, {
|
|
581
623
|
type: mime.getType(targetPath) || "application/octet-stream",
|
|
582
624
|
});
|
|
583
|
-
const fileRecord = await this
|
|
625
|
+
const fileRecord = await this.#db.files.where({ name, path: parent }).first();
|
|
584
626
|
if (fileRecord) {
|
|
585
|
-
await this
|
|
627
|
+
await this.#db.files.put({
|
|
586
628
|
...fileRecord,
|
|
587
629
|
file: file,
|
|
588
630
|
});
|
|
@@ -647,8 +689,8 @@ class VVVFS {
|
|
|
647
689
|
async append(path: string, content: Blob) {
|
|
648
690
|
try {
|
|
649
691
|
const targetPath = joinPath(path);
|
|
650
|
-
if (this
|
|
651
|
-
for (const handler of this
|
|
692
|
+
if (this.#watchers[targetPath]) {
|
|
693
|
+
for (const handler of this.#watchers[targetPath]) {
|
|
652
694
|
if (await handler("append")) {
|
|
653
695
|
if (this.options.throwError) {
|
|
654
696
|
throw new VVVFSError("Append", "追加文件失败:监听器取消了操作");
|
|
@@ -657,6 +699,13 @@ class VVVFS {
|
|
|
657
699
|
}
|
|
658
700
|
}
|
|
659
701
|
}
|
|
702
|
+
if (this.#lockedFiles[targetPath]) {
|
|
703
|
+
console.warn("文件已被锁定");
|
|
704
|
+
if (this.options.throwError) {
|
|
705
|
+
throw new VVVFSError("Append", "文件已被锁定");
|
|
706
|
+
}
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
660
709
|
const existingFile = await this.read(targetPath);
|
|
661
710
|
if (existingFile) {
|
|
662
711
|
const blob = new Blob([existingFile, content], {
|
|
@@ -698,8 +747,8 @@ class VVVFS {
|
|
|
698
747
|
async read(path: string) {
|
|
699
748
|
try {
|
|
700
749
|
const targetPath = joinPath(path);
|
|
701
|
-
if (this
|
|
702
|
-
for (const handler of this
|
|
750
|
+
if (this.#watchers[targetPath]) {
|
|
751
|
+
for (const handler of this.#watchers[targetPath]) {
|
|
703
752
|
if (await handler("read")) {
|
|
704
753
|
if (this.options.throwError) {
|
|
705
754
|
throw new VVVFSError("Read", "读取文件失败:监听器取消了操作");
|
|
@@ -708,6 +757,13 @@ class VVVFS {
|
|
|
708
757
|
}
|
|
709
758
|
}
|
|
710
759
|
}
|
|
760
|
+
if (this.#lockedFiles[targetPath]) {
|
|
761
|
+
console.warn("文件已被锁定");
|
|
762
|
+
if (this.options.throwError) {
|
|
763
|
+
throw new VVVFSError("Read", "文件已被锁定");
|
|
764
|
+
}
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
711
767
|
if (!(await this.exists(targetPath))) {
|
|
712
768
|
console.warn("文件不存在");
|
|
713
769
|
if (this.options.throwError) {
|
|
@@ -716,7 +772,7 @@ class VVVFS {
|
|
|
716
772
|
return null;
|
|
717
773
|
}
|
|
718
774
|
const { name, parent } = parsePath(targetPath);
|
|
719
|
-
return (await this
|
|
775
|
+
return (await this.#db.files.where({ name, path: parent }).first())?.file;
|
|
720
776
|
} catch (error) {
|
|
721
777
|
console.error("读取文件失败", error);
|
|
722
778
|
if (this.options.throwError) {
|
|
@@ -777,6 +833,70 @@ class VVVFS {
|
|
|
777
833
|
return null;
|
|
778
834
|
}
|
|
779
835
|
}
|
|
836
|
+
/**
|
|
837
|
+
* 读取文件内容
|
|
838
|
+
* @param path 文件路径
|
|
839
|
+
* @param start 开始位置
|
|
840
|
+
* @param end 结束位置
|
|
841
|
+
*/
|
|
842
|
+
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
|
+
}
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* 读取文件内容
|
|
881
|
+
* @param path 文件路径
|
|
882
|
+
* @param start 读取开始位置
|
|
883
|
+
* @param end 读取结束位置
|
|
884
|
+
*/
|
|
885
|
+
async readTextChunk(path: string, start: number, end: number) {
|
|
886
|
+
try {
|
|
887
|
+
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
|
+
}
|
|
899
|
+
}
|
|
780
900
|
/**
|
|
781
901
|
* 判断是否是文件
|
|
782
902
|
* @param path 文件路径
|
|
@@ -785,7 +905,7 @@ class VVVFS {
|
|
|
785
905
|
try {
|
|
786
906
|
const targetPath = joinPath(path);
|
|
787
907
|
const { name, parent } = parsePath(targetPath);
|
|
788
|
-
return (await this
|
|
908
|
+
return (await this.#db.files.where({ name, path: parent, type: "file" }).count()) > 0;
|
|
789
909
|
} catch (error) {
|
|
790
910
|
console.error("判断文件类型失败", error);
|
|
791
911
|
if (this.options.throwError) {
|
|
@@ -802,7 +922,7 @@ class VVVFS {
|
|
|
802
922
|
try {
|
|
803
923
|
const targetPath = joinPath(path);
|
|
804
924
|
const { name, parent } = parsePath(targetPath);
|
|
805
|
-
return (await this
|
|
925
|
+
return (await this.#db.files.where({ path: parent, name, type: "dir" }).count()) > 0;
|
|
806
926
|
} catch (error) {
|
|
807
927
|
console.error("判断文件类型失败", error);
|
|
808
928
|
if (this.options.throwError) {
|
|
@@ -818,8 +938,8 @@ class VVVFS {
|
|
|
818
938
|
async list(path: string) {
|
|
819
939
|
try {
|
|
820
940
|
const targetPath = joinPath(path);
|
|
821
|
-
if (this
|
|
822
|
-
for (const handler of this
|
|
941
|
+
if (this.#watchers[targetPath]) {
|
|
942
|
+
for (const handler of this.#watchers[targetPath]) {
|
|
823
943
|
if (await handler("list")) {
|
|
824
944
|
if (this.options.throwError) {
|
|
825
945
|
throw new VVVFSError("List", "列出目录下的文件失败:监听器取消了操作");
|
|
@@ -828,6 +948,13 @@ class VVVFS {
|
|
|
828
948
|
}
|
|
829
949
|
}
|
|
830
950
|
}
|
|
951
|
+
if (this.#lockedFiles[targetPath]) {
|
|
952
|
+
console.warn("文件已被锁定");
|
|
953
|
+
if (this.options.throwError) {
|
|
954
|
+
throw new VVVFSError("List", "文件已被锁定");
|
|
955
|
+
}
|
|
956
|
+
return [];
|
|
957
|
+
}
|
|
831
958
|
if (!(await this.exists(targetPath))) {
|
|
832
959
|
console.warn("路径不存在");
|
|
833
960
|
if (this.options.throwError) {
|
|
@@ -842,7 +969,7 @@ class VVVFS {
|
|
|
842
969
|
}
|
|
843
970
|
return [];
|
|
844
971
|
}
|
|
845
|
-
return (await this
|
|
972
|
+
return (await this.#db.files.where({ path: targetPath }).toArray())
|
|
846
973
|
.map((file) => file.name)
|
|
847
974
|
.filter((item) => item != "");
|
|
848
975
|
} catch (error) {
|
|
@@ -862,8 +989,8 @@ class VVVFS {
|
|
|
862
989
|
try {
|
|
863
990
|
const sourcePath = joinPath(path);
|
|
864
991
|
const { name, parent } = parsePath(sourcePath);
|
|
865
|
-
if (this
|
|
866
|
-
for (const handler of this
|
|
992
|
+
if (this.#watchers[sourcePath]) {
|
|
993
|
+
for (const handler of this.#watchers[sourcePath]) {
|
|
867
994
|
if (await handler("rename")) {
|
|
868
995
|
if (this.options.throwError) {
|
|
869
996
|
throw new VVVFSError("Rename", "重命名文件失败:监听器取消了操作");
|
|
@@ -872,6 +999,13 @@ class VVVFS {
|
|
|
872
999
|
}
|
|
873
1000
|
}
|
|
874
1001
|
}
|
|
1002
|
+
if (this.#lockedFiles[sourcePath]) {
|
|
1003
|
+
console.warn("文件已被锁定");
|
|
1004
|
+
if (this.options.throwError) {
|
|
1005
|
+
throw new VVVFSError("Rename", "文件已被锁定");
|
|
1006
|
+
}
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
875
1009
|
if (!(await this.exists(sourcePath))) {
|
|
876
1010
|
console.warn("文件不存在");
|
|
877
1011
|
if (this.options.throwError) {
|
|
@@ -890,7 +1024,7 @@ class VVVFS {
|
|
|
890
1024
|
}
|
|
891
1025
|
return false;
|
|
892
1026
|
}
|
|
893
|
-
const fileRecord = await this
|
|
1027
|
+
const fileRecord = await this.#db.files.where({ path: parent, name }).first();
|
|
894
1028
|
if (!fileRecord) {
|
|
895
1029
|
console.warn("文件记录未找到");
|
|
896
1030
|
if (this.options.throwError) {
|
|
@@ -899,7 +1033,7 @@ class VVVFS {
|
|
|
899
1033
|
return false;
|
|
900
1034
|
}
|
|
901
1035
|
if (fileRecord.type === "dir") {
|
|
902
|
-
const descendants = await this
|
|
1036
|
+
const descendants = await this.#db.files
|
|
903
1037
|
.filter(
|
|
904
1038
|
(file) =>
|
|
905
1039
|
file.path === sourcePath || file.path.startsWith(sourcePath + "/"),
|
|
@@ -908,10 +1042,10 @@ class VVVFS {
|
|
|
908
1042
|
for (const descendant of descendants) {
|
|
909
1043
|
const relativePath = descendant.path.slice(sourcePath.length);
|
|
910
1044
|
const updatedPath = joinPath(newPath + relativePath);
|
|
911
|
-
await this
|
|
1045
|
+
await this.#db.files.update(descendant.id!, { path: updatedPath });
|
|
912
1046
|
}
|
|
913
1047
|
}
|
|
914
|
-
await this
|
|
1048
|
+
await this.#db.files.update(fileRecord.id!, {
|
|
915
1049
|
name: newName,
|
|
916
1050
|
path: parent,
|
|
917
1051
|
});
|
|
@@ -931,8 +1065,8 @@ class VVVFS {
|
|
|
931
1065
|
async delete(path: string) {
|
|
932
1066
|
try {
|
|
933
1067
|
const targetPath = joinPath(path);
|
|
934
|
-
if (this
|
|
935
|
-
for (const handler of this
|
|
1068
|
+
if (this.#watchers[targetPath]) {
|
|
1069
|
+
for (const handler of this.#watchers[targetPath]) {
|
|
936
1070
|
if (await handler("delete")) {
|
|
937
1071
|
if (this.options.throwError) {
|
|
938
1072
|
throw new VVVFSError("Delete", "删除文件失败:监听器取消了操作");
|
|
@@ -941,6 +1075,13 @@ class VVVFS {
|
|
|
941
1075
|
}
|
|
942
1076
|
}
|
|
943
1077
|
}
|
|
1078
|
+
if (this.#lockedFiles[targetPath]) {
|
|
1079
|
+
console.warn("文件已被锁定");
|
|
1080
|
+
if (this.options.throwError) {
|
|
1081
|
+
throw new VVVFSError("Delete", "文件已被锁定");
|
|
1082
|
+
}
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
944
1085
|
if (!(await this.exists(targetPath))) {
|
|
945
1086
|
console.warn("文件不存在");
|
|
946
1087
|
if (this.options.throwError) {
|
|
@@ -954,19 +1095,19 @@ class VVVFS {
|
|
|
954
1095
|
for (const file of files) {
|
|
955
1096
|
await this.delete(joinPath(targetPath, file));
|
|
956
1097
|
}
|
|
957
|
-
const dirRecord = await this
|
|
1098
|
+
const dirRecord = await this.#db.files
|
|
958
1099
|
.where({ name, path: parent, type: "dir" })
|
|
959
1100
|
.first();
|
|
960
1101
|
if (dirRecord) {
|
|
961
|
-
await this
|
|
1102
|
+
await this.#db.files.delete(dirRecord.id!);
|
|
962
1103
|
}
|
|
963
1104
|
return true;
|
|
964
1105
|
} else {
|
|
965
|
-
const fileRecord = await this
|
|
1106
|
+
const fileRecord = await this.#db.files
|
|
966
1107
|
.where({ name, path: parent, type: "file" })
|
|
967
1108
|
.first();
|
|
968
1109
|
if (fileRecord) {
|
|
969
|
-
await this
|
|
1110
|
+
await this.#db.files.delete(fileRecord.id!);
|
|
970
1111
|
}
|
|
971
1112
|
return true;
|
|
972
1113
|
}
|
|
@@ -987,8 +1128,8 @@ class VVVFS {
|
|
|
987
1128
|
try {
|
|
988
1129
|
const sourcePath = joinPath(path);
|
|
989
1130
|
const destinationPath = joinPath(newPath);
|
|
990
|
-
if (this
|
|
991
|
-
for (const handler of this
|
|
1131
|
+
if (this.#watchers[sourcePath]) {
|
|
1132
|
+
for (const handler of this.#watchers[sourcePath]) {
|
|
992
1133
|
if (await handler("move")) {
|
|
993
1134
|
if (this.options.throwError) {
|
|
994
1135
|
throw new VVVFSError("Move", "移动文件失败:监听器取消了操作");
|
|
@@ -997,6 +1138,13 @@ class VVVFS {
|
|
|
997
1138
|
}
|
|
998
1139
|
}
|
|
999
1140
|
}
|
|
1141
|
+
if (this.#lockedFiles[sourcePath]) {
|
|
1142
|
+
console.warn("文件已被锁定");
|
|
1143
|
+
if (this.options.throwError) {
|
|
1144
|
+
throw new VVVFSError("Move", "文件已被锁定");
|
|
1145
|
+
}
|
|
1146
|
+
return false;
|
|
1147
|
+
}
|
|
1000
1148
|
if (sourcePath === destinationPath) {
|
|
1001
1149
|
return true;
|
|
1002
1150
|
}
|
|
@@ -1029,20 +1177,20 @@ class VVVFS {
|
|
|
1029
1177
|
for (const child of children) {
|
|
1030
1178
|
await this.move(joinPath(sourcePath, child), joinPath(destinationPath, child));
|
|
1031
1179
|
}
|
|
1032
|
-
const dirRecord = await this
|
|
1180
|
+
const dirRecord = await this.#db.files
|
|
1033
1181
|
.where({ name, path: parent, type: "dir" })
|
|
1034
1182
|
.first();
|
|
1035
1183
|
if (dirRecord) {
|
|
1036
|
-
await this
|
|
1184
|
+
await this.#db.files.delete(dirRecord.id!);
|
|
1037
1185
|
}
|
|
1038
1186
|
return true;
|
|
1039
1187
|
} else {
|
|
1040
1188
|
await this.createDir(newParent);
|
|
1041
|
-
const fileRecord = await this
|
|
1189
|
+
const fileRecord = await this.#db.files
|
|
1042
1190
|
.where({ name, path: parent, type: "file" })
|
|
1043
1191
|
.first();
|
|
1044
1192
|
if (fileRecord) {
|
|
1045
|
-
await this
|
|
1193
|
+
await this.#db.files.update(fileRecord.id!, { name: newName, path: newParent });
|
|
1046
1194
|
return true;
|
|
1047
1195
|
}
|
|
1048
1196
|
return false;
|
|
@@ -1064,8 +1212,8 @@ class VVVFS {
|
|
|
1064
1212
|
try {
|
|
1065
1213
|
const sourcePath = joinPath(path);
|
|
1066
1214
|
const destinationPath = joinPath(newPath);
|
|
1067
|
-
if (this
|
|
1068
|
-
for (const handler of this
|
|
1215
|
+
if (this.#watchers[sourcePath]) {
|
|
1216
|
+
for (const handler of this.#watchers[sourcePath]) {
|
|
1069
1217
|
if (await handler("copy")) {
|
|
1070
1218
|
if (this.options.throwError) {
|
|
1071
1219
|
throw new VVVFSError("Copy", "复制文件失败:监听器取消了操作");
|
|
@@ -1074,6 +1222,13 @@ class VVVFS {
|
|
|
1074
1222
|
}
|
|
1075
1223
|
}
|
|
1076
1224
|
}
|
|
1225
|
+
if (this.#lockedFiles[sourcePath]) {
|
|
1226
|
+
console.warn("文件已被锁定");
|
|
1227
|
+
if (this.options.throwError) {
|
|
1228
|
+
throw new VVVFSError("Copy", "文件已被锁定");
|
|
1229
|
+
}
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1077
1232
|
if (await this.exists(destinationPath)) {
|
|
1078
1233
|
console.warn("目标文件已存在");
|
|
1079
1234
|
if (this.options.throwError) {
|
|
@@ -1105,11 +1260,11 @@ class VVVFS {
|
|
|
1105
1260
|
}
|
|
1106
1261
|
return true;
|
|
1107
1262
|
} else {
|
|
1108
|
-
const fileRecord = await this
|
|
1263
|
+
const fileRecord = await this.#db.files
|
|
1109
1264
|
.where({ name, path: parent, type: "file" })
|
|
1110
1265
|
.first();
|
|
1111
1266
|
if (fileRecord) {
|
|
1112
|
-
await this
|
|
1267
|
+
await this.#db.files.add({
|
|
1113
1268
|
name: newName,
|
|
1114
1269
|
path: newParent,
|
|
1115
1270
|
type: fileRecord.type,
|
|
@@ -1177,14 +1332,69 @@ class VVVFS {
|
|
|
1177
1332
|
return null;
|
|
1178
1333
|
}
|
|
1179
1334
|
}
|
|
1180
|
-
|
|
1335
|
+
/**
|
|
1336
|
+
* 监听文件
|
|
1337
|
+
* @param path 文件路径
|
|
1338
|
+
* @param handler 监听器
|
|
1339
|
+
*/
|
|
1340
|
+
watch(path: string, handler: (type: string) => Promise<boolean>) {
|
|
1341
|
+
path = joinPath(path);
|
|
1342
|
+
if (!this.#watchers[path]) {
|
|
1343
|
+
this.#watchers[path] = [];
|
|
1344
|
+
}
|
|
1345
|
+
this.#watchers[path].push(handler);
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* 锁定文件
|
|
1349
|
+
* @param path 文件路径
|
|
1350
|
+
*/
|
|
1351
|
+
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", "文件不存在");
|
|
1357
|
+
}
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
if (this.#lockedFiles[path]) {
|
|
1361
|
+
console.warn("文件已被锁定");
|
|
1362
|
+
if (this.options.throwError) {
|
|
1363
|
+
throw new VVVFSError("Lock", "文件已被锁定");
|
|
1364
|
+
}
|
|
1365
|
+
return false;
|
|
1366
|
+
}
|
|
1367
|
+
this.#lockedFiles[path] = true;
|
|
1368
|
+
return true;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* 解锁文件
|
|
1372
|
+
* @param path 文件路径
|
|
1373
|
+
*/
|
|
1374
|
+
async unlock(path: string) {
|
|
1181
1375
|
path = joinPath(path);
|
|
1182
|
-
if (!this.
|
|
1183
|
-
|
|
1376
|
+
if (!(await this.exists(path))) {
|
|
1377
|
+
console.warn("文件不存在");
|
|
1378
|
+
if (this.options.throwError) {
|
|
1379
|
+
throw new VVVFSError("Unlock", "文件不存在");
|
|
1380
|
+
}
|
|
1381
|
+
return false;
|
|
1184
1382
|
}
|
|
1185
|
-
this
|
|
1383
|
+
if (!this.#lockedFiles[path]) {
|
|
1384
|
+
console.warn("文件未被锁定");
|
|
1385
|
+
if (this.options.throwError) {
|
|
1386
|
+
throw new VVVFSError("Unlock", "文件未被锁定");
|
|
1387
|
+
}
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
delete this.#lockedFiles[path];
|
|
1391
|
+
return true;
|
|
1186
1392
|
}
|
|
1187
1393
|
}
|
|
1394
|
+
/**
|
|
1395
|
+
* 解析路径
|
|
1396
|
+
* @param path 路径
|
|
1397
|
+
*/
|
|
1188
1398
|
function parsePath(path: string) {
|
|
1189
1399
|
path = joinPath(path);
|
|
1190
1400
|
const oldParts = path.split("/");
|
|
@@ -1198,6 +1408,11 @@ function parsePath(path: string) {
|
|
|
1198
1408
|
if (joinPath(parent, name).length > 4096) throw new VVVFSError("ParsePath", "文件路径过长");
|
|
1199
1409
|
return { name, parent };
|
|
1200
1410
|
}
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* 合并路径
|
|
1414
|
+
* @param paths 路径
|
|
1415
|
+
*/
|
|
1201
1416
|
function joinPath(...paths: string[]) {
|
|
1202
1417
|
const segments = paths.map((p) => String(p)).filter((p) => p.length > 0);
|
|
1203
1418
|
if (segments.length === 0) return ".";
|
|
@@ -1229,4 +1444,10 @@ Object.defineProperty(VVVFS, "version", {
|
|
|
1229
1444
|
enumerable: true,
|
|
1230
1445
|
configurable: false,
|
|
1231
1446
|
});
|
|
1447
|
+
Object.defineProperty(VVVFS, "author", {
|
|
1448
|
+
value: "IFTC",
|
|
1449
|
+
writable: false,
|
|
1450
|
+
enumerable: true,
|
|
1451
|
+
configurable: false,
|
|
1452
|
+
});
|
|
1232
1453
|
(globalThis as any).VVVFS = VVVFS;
|