vvvfs 0.0.1 → 0.0.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/index.ts CHANGED
@@ -2,6 +2,20 @@ import { Dexie, Table } from "dexie";
2
2
  import packageJson from "./package.json" with { type: "json" };
3
3
  import mime from "mime";
4
4
 
5
+ /**
6
+ * 虚拟文件系统
7
+ * @author IFTC
8
+ * @description 虚拟文件系统
9
+ */
10
+
11
+ /**
12
+ * 文件记录
13
+ * @param id 文件id
14
+ * @param name 文件名
15
+ * @param path 文件路径
16
+ * @param type 文件类型(file或dir)
17
+ * @param file 文件对象
18
+ */
5
19
  export interface FileRecord {
6
20
  id?: number;
7
21
  name: string;
@@ -10,14 +24,40 @@ export interface FileRecord {
10
24
  file: File | null;
11
25
  }
12
26
 
27
+ /**
28
+ * 虚拟文件系统数据库
29
+ * @param files 文件存储数据表
30
+ */
13
31
  export interface VVVFSDatabase extends Dexie {
14
32
  files: Table<FileRecord, number>;
15
33
  }
16
34
 
35
+ /**
36
+ * 虚拟文件系统配置项
37
+ * @param throwError 是否抛出错误
38
+ */
39
+ export interface VVVFSOptions {
40
+ throwError: boolean;
41
+ }
42
+
43
+ /**
44
+ * 虚拟文件系统错误类
45
+ */
46
+ class VVVFSError extends Error {
47
+ /**
48
+ * @param type 错误类型
49
+ * @param message 错误信息
50
+ */
51
+ constructor(type: string, message: string) {
52
+ super(message);
53
+ this.name = "VVVFS" + type + "Error";
54
+ }
55
+ }
56
+
17
57
  const version = packageJson.version;
18
58
  class VVVFS {
19
59
  private db: VVVFSDatabase;
20
- options: Record<string, any>;
60
+ options: VVVFSOptions;
21
61
  /**
22
62
  * 虚拟文件系统版本
23
63
  */
@@ -27,22 +67,173 @@ class VVVFS {
27
67
  * @param name 虚拟文件系统名称
28
68
  * @param options 配置项
29
69
  */
30
- constructor(name = "vvvfs", options = {}) {
70
+ constructor(
71
+ name = "vvvfs",
72
+ options = {
73
+ throwError: false,
74
+ },
75
+ ) {
31
76
  this.options = options;
32
- this.db = new Dexie(name) as VVVFSDatabase;
33
- this.db.version(1).stores({
34
- files: "++id, name, path, type, file",
35
- });
77
+ try {
78
+ this.db = new Dexie(name) as VVVFSDatabase;
79
+ this.db.version(1).stores({
80
+ files: "++id, name, path, type, file",
81
+ });
82
+ } catch (error) {
83
+ console.error("创建数据库失败", error);
84
+ throw new VVVFSError("CreateDatabase", "创建数据库失败");
85
+ }
86
+ }
87
+ /**
88
+ * 初始化虚拟文件系统
89
+ * @description 将Linux的系统初始文件初始化到数据库中
90
+ */
91
+ async init(user?: string) {
92
+ const linuxInitFiles = [
93
+ {
94
+ name: "root",
95
+ path: "/",
96
+ type: "dir",
97
+ file: new File([], "root"),
98
+ },
99
+ {
100
+ name: "boot",
101
+ path: "/",
102
+ type: "dir",
103
+ file: new File([], "boot"),
104
+ },
105
+ {
106
+ name: "bin",
107
+ path: "/",
108
+ type: "dir",
109
+ file: new File([], "bin"),
110
+ },
111
+ {
112
+ name: "dev",
113
+ path: "/",
114
+ type: "dir",
115
+ file: new File([], "dev"),
116
+ },
117
+ {
118
+ name: "etc",
119
+ path: "/",
120
+ type: "dir",
121
+ file: new File([], "etc"),
122
+ },
123
+ {
124
+ name: "home",
125
+ path: "/",
126
+ type: "dir",
127
+ file: new File([], "home"),
128
+ },
129
+ {
130
+ name: user || "root",
131
+ path: "/home",
132
+ type: "dir",
133
+ file: new File([], "home"),
134
+ },
135
+ {
136
+ name: "lib",
137
+ path: "/",
138
+ type: "dir",
139
+ file: new File([], "lib"),
140
+ },
141
+ {
142
+ name: "lib64",
143
+ path: "/",
144
+ type: "dir",
145
+ file: new File([], "lib64"),
146
+ },
147
+ {
148
+ name: "media",
149
+ path: "/",
150
+ type: "dir",
151
+ file: new File([], "media"),
152
+ },
153
+ {
154
+ name: "mnt",
155
+ path: "/",
156
+ type: "dir",
157
+ file: new File([], "mnt"),
158
+ },
159
+ {
160
+ name: "opt",
161
+ path: "/",
162
+ type: "dir",
163
+ file: new File([], "opt"),
164
+ },
165
+ {
166
+ name: "proc",
167
+ path: "/",
168
+ type: "dir",
169
+ file: new File([], "proc"),
170
+ },
171
+ {
172
+ name: "run",
173
+ path: "/",
174
+ type: "dir",
175
+ file: new File([], "run"),
176
+ },
177
+ {
178
+ name: "sbin",
179
+ path: "/",
180
+ type: "dir",
181
+ file: new File([], "sbin"),
182
+ },
183
+ {
184
+ name: "srv",
185
+ path: "/",
186
+ type: "dir",
187
+ file: new File([], "srv"),
188
+ },
189
+ {
190
+ name: "sys",
191
+ path: "/",
192
+ type: "dir",
193
+ file: new File([], "sys"),
194
+ },
195
+ {
196
+ name: "tmp",
197
+ path: "/",
198
+ type: "dir",
199
+ file: new File([], "tmp"),
200
+ },
201
+ {
202
+ name: "usr",
203
+ path: "/",
204
+ type: "dir",
205
+ file: new File([], "usr"),
206
+ },
207
+ {
208
+ name: "var",
209
+ path: "/",
210
+ type: "dir",
211
+ file: new File([], "var"),
212
+ },
213
+ ];
214
+ try {
215
+ for (const file of linuxInitFiles) {
216
+ await this.db.files.put(file);
217
+ }
218
+ } catch (error) {
219
+ console.error("初始化文件失败", error);
220
+ throw new VVVFSError("InitFiles", "初始化文件失败" + error);
221
+ }
36
222
  }
37
223
  /**
38
224
  * 重置虚拟文件系统
39
225
  */
40
226
  async reset() {
41
- await this.db.delete();
42
- this.db = new Dexie(this.db.name) as VVVFSDatabase;
43
- this.db.version(1).stores({
44
- files: "++id, name, path, type, file",
45
- });
227
+ try {
228
+ await this.db.delete();
229
+ this.db = new Dexie(this.db.name) as VVVFSDatabase;
230
+ this.db.version(1).stores({
231
+ files: "++id, name, path, type, file",
232
+ });
233
+ } catch (error) {
234
+ console.error("重置数据库失败", error);
235
+ throw new VVVFSError("ResetDatabase", "重置数据库失败" + error);
236
+ }
46
237
  }
47
238
  /**
48
239
  * 创建文件
@@ -58,7 +249,7 @@ class VVVFS {
58
249
  if (!(await this.exists(parent))) {
59
250
  await this.createDir(parent);
60
251
  }
61
- console.log(name, parent);
252
+ // console.log(name, parent);
62
253
  await this.db.files.add({
63
254
  name: name,
64
255
  path: parent,
@@ -70,6 +261,9 @@ class VVVFS {
70
261
  return true;
71
262
  } catch (error) {
72
263
  console.error("创建文件失败", error);
264
+ if (this.options.throwError) {
265
+ throw new VVVFSError("CreateFile", "创建文件失败" + error);
266
+ }
73
267
  return false;
74
268
  }
75
269
  }
@@ -107,6 +301,9 @@ class VVVFS {
107
301
  return true;
108
302
  } catch (error) {
109
303
  console.error("创建目录失败", error);
304
+ if (this.options.throwError) {
305
+ throw new VVVFSError("CreateDir", "创建目录失败" + error);
306
+ }
110
307
  return false;
111
308
  }
112
309
  }
@@ -115,33 +312,47 @@ class VVVFS {
115
312
  * @param path 文件路径
116
313
  */
117
314
  async exists(path: string) {
118
- const { name, parent } = parsePath(path);
119
- return (
120
- (await this.db.files
121
- .where({
122
- name: name,
123
- path: parent,
124
- })
125
- .count()) > 0
126
- );
315
+ try {
316
+ const { name, parent } = parsePath(path);
317
+ return (
318
+ (await this.db.files
319
+ .where({
320
+ name: name,
321
+ path: parent,
322
+ })
323
+ .count()) > 0
324
+ );
325
+ } catch (error) {
326
+ console.error("判断文件是否存在失败", error);
327
+ if (this.options.throwError) {
328
+ throw new VVVFSError("Exists", "判断文件是否存在失败" + error);
329
+ }
330
+ return false;
331
+ }
127
332
  }
128
333
  /**
129
334
  * 读取文件内容
130
335
  * @param path 文件路径
131
336
  */
132
337
  async write(path: string, content: Blob) {
133
- if (!(await this.exists(path))) {
134
- const success = await this.createFile(path);
135
- if (!success) {
136
- console.warn("创建文件失败");
137
- return false;
138
- }
139
- }
140
338
  try {
339
+ if (!(await this.exists(path))) {
340
+ const success = await this.createFile(path);
341
+ if (!success) {
342
+ console.warn("创建文件失败");
343
+ if (this.options.throwError) {
344
+ throw new VVVFSError("Write", "创建文件失败");
345
+ }
346
+ return false;
347
+ }
348
+ }
141
349
  const { name, parent } = parsePath(path);
142
350
  console.log(name, parent);
143
351
  if (await this.isDir(parent + "/" + name)) {
144
352
  console.warn("路径已存在");
353
+ if (this.options.throwError) {
354
+ throw new VVVFSError("Write", "路径已存在");
355
+ }
145
356
  return false;
146
357
  }
147
358
  const file = new File([content], name, {
@@ -157,10 +368,16 @@ class VVVFS {
157
368
  return true;
158
369
  } else {
159
370
  console.warn("文件记录未找到,无法写入内容");
371
+ if (this.options.throwError) {
372
+ throw new VVVFSError("Write", "文件记录未找到,无法写入内容");
373
+ }
160
374
  return false;
161
375
  }
162
376
  } catch (error) {
163
377
  console.error("写入文件失败", error);
378
+ if (this.options.throwError) {
379
+ throw new VVVFSError("Write", "写入文件失败" + error);
380
+ }
164
381
  return false;
165
382
  }
166
383
  }
@@ -170,8 +387,16 @@ class VVVFS {
170
387
  * @param content 文本内容
171
388
  */
172
389
  async writeText(path: string, content: string) {
173
- const blob = new Blob([content], { type: "text/plain" });
174
- return await this.write(path, blob);
390
+ try {
391
+ const blob = new Blob([content], { type: "text/plain" });
392
+ return await this.write(path, blob);
393
+ } catch (error) {
394
+ console.error("写入文件失败", error);
395
+ if (this.options.throwError) {
396
+ throw new VVVFSError("Write", "写入文件失败" + error);
397
+ }
398
+ return false;
399
+ }
175
400
  }
176
401
  /**
177
402
  * 写入JSON内容
@@ -180,22 +405,39 @@ class VVVFS {
180
405
  * @param format 是否格式化
181
406
  */
182
407
  async writeJson(path: string, content: Record<string, any>, format?: boolean) {
183
- return await this.writeText(path, JSON.stringify(content, null, format ? 4 : undefined));
408
+ try {
409
+ return await this.writeText(
410
+ path,
411
+ JSON.stringify(content, null, format ? 4 : undefined),
412
+ );
413
+ } catch (error) {
414
+ console.error("写入文件失败", error);
415
+ if (this.options.throwError) {
416
+ throw new VVVFSError("Write", "写入文件失败" + error);
417
+ }
418
+ return false;
419
+ }
184
420
  }
185
421
  /**
186
422
  * 读取文件内容
187
423
  * @param path 文件路径
188
424
  */
189
425
  async read(path: string) {
190
- if (!(await this.exists(path))) {
191
- console.warn("文件不存在");
192
- return null;
193
- }
194
426
  try {
427
+ if (!(await this.exists(path))) {
428
+ console.warn("文件不存在");
429
+ if (this.options.throwError) {
430
+ throw new VVVFSError("Read", "文件不存在");
431
+ }
432
+ return null;
433
+ }
195
434
  const { name, parent } = parsePath(path);
196
435
  return (await this.db.files.where({ name, path: parent }).first())?.file;
197
436
  } catch (error) {
198
437
  console.error("读取文件失败", error);
438
+ if (this.options.throwError) {
439
+ throw new VVVFSError("Read", "读取文件失败" + error);
440
+ }
199
441
  return null;
200
442
  }
201
443
  }
@@ -204,10 +446,22 @@ class VVVFS {
204
446
  * @param path 文件路径
205
447
  */
206
448
  async readText(path: string) {
207
- const file = await this.read(path);
208
- if (file) {
209
- return await file.text();
210
- } else {
449
+ try {
450
+ const file = await this.read(path);
451
+ if (file) {
452
+ return await file.text();
453
+ } else {
454
+ console.warn("文件不存在");
455
+ if (this.options.throwError) {
456
+ throw new VVVFSError("Read", "文件不存在");
457
+ }
458
+ return null;
459
+ }
460
+ } catch (error) {
461
+ console.error("读取文件失败", error);
462
+ if (this.options.throwError) {
463
+ throw new VVVFSError("Read", "读取文件失败" + error);
464
+ }
211
465
  return null;
212
466
  }
213
467
  }
@@ -216,15 +470,26 @@ class VVVFS {
216
470
  * @param path 文件路径
217
471
  */
218
472
  async readJson(path: string) {
219
- const text = await this.readText(path);
220
- if (text) {
221
- try {
222
- return JSON.parse(text);
223
- } catch (error) {
224
- console.error("解析JSON失败", error);
473
+ try {
474
+ const text = await this.readText(path);
475
+ if (text) {
476
+ try {
477
+ return JSON.parse(text);
478
+ } catch (error) {
479
+ console.error("解析JSON失败", error);
480
+ if (this.options.throwError) {
481
+ throw new VVVFSError("Read", "解析JSON失败" + error);
482
+ }
483
+ return null;
484
+ }
485
+ } else {
225
486
  return null;
226
487
  }
227
- } else {
488
+ } catch (error) {
489
+ console.error("读取文件失败", error);
490
+ if (this.options.throwError) {
491
+ throw new VVVFSError("Read", "读取文件失败" + error);
492
+ }
228
493
  return null;
229
494
  }
230
495
  }
@@ -233,24 +498,48 @@ class VVVFS {
233
498
  * @param path 文件路径
234
499
  */
235
500
  async isFile(path: string) {
236
- const { name, parent } = parsePath(path);
237
- return (await this.db.files.where({ name, path: parent, type: "file" }).count()) > 0;
501
+ try {
502
+ const { name, parent } = parsePath(path);
503
+ return (await this.db.files.where({ name, path: parent, type: "file" }).count()) > 0;
504
+ } catch (error) {
505
+ console.error("判断文件类型失败", error);
506
+ if (this.options.throwError) {
507
+ throw new VVVFSError("IsFile", "判断文件类型失败" + error);
508
+ }
509
+ return false;
510
+ }
238
511
  }
239
512
  /**
240
513
  * 判断是否是目录
241
514
  * @param path 文件路径
242
515
  */
243
516
  async isDir(path: string) {
244
- const { name, parent } = parsePath(path);
245
- return (await this.db.files.where({ path: parent, name, type: "dir" }).count()) > 0;
517
+ try {
518
+ const { name, parent } = parsePath(path);
519
+ return (await this.db.files.where({ path: parent, name, type: "dir" }).count()) > 0;
520
+ } catch (error) {
521
+ console.error("判断文件类型失败", error);
522
+ if (this.options.throwError) {
523
+ throw new VVVFSError("IsDir", "判断文件类型失败" + error);
524
+ }
525
+ return false;
526
+ }
246
527
  }
247
528
  /**
248
529
  * 列出目录下的文件
249
530
  * @param path 目录路径
250
531
  */
251
532
  async list(path: string) {
252
- const { name, parent } = parsePath(path);
253
- return (await this.db.files.where({ path: parent }).toArray()).map((file) => file.name);
533
+ try {
534
+ const { name, parent } = parsePath(path);
535
+ return (await this.db.files.where({ path: parent }).toArray()).map((file) => file.name).filter((item) => item != "");
536
+ } catch (error) {
537
+ console.error("列出目录下的文件失败", error);
538
+ if (this.options.throwError) {
539
+ throw new VVVFSError("List", "列出目录下的文件失败" + error);
540
+ }
541
+ return [];
542
+ }
254
543
  }
255
544
  /**
256
545
  * 重命名文件
@@ -258,8 +547,14 @@ class VVVFS {
258
547
  * @param newName 新文件名
259
548
  */
260
549
  async rename(path: string, newName: string) {
261
- if (!(await this.exists(path))) throw new Error("File not exists");
262
550
  try {
551
+ if (!(await this.exists(path))) {
552
+ console.warn("文件不存在");
553
+ if (this.options.throwError) {
554
+ throw new VVVFSError("Rename", "文件不存在");
555
+ }
556
+ return false;
557
+ }
263
558
  const { name, parent } = parsePath(path);
264
559
  const file = await this.db.files.get({ path, name });
265
560
  await this.db.files.put({
@@ -271,6 +566,9 @@ class VVVFS {
271
566
  return true;
272
567
  } catch (e) {
273
568
  console.error(e);
569
+ if (this.options.throwError) {
570
+ throw new VVVFSError("Rename", "重命名文件失败" + e);
571
+ }
274
572
  return false;
275
573
  }
276
574
  }
@@ -279,8 +577,14 @@ class VVVFS {
279
577
  * @param path 文件路径
280
578
  */
281
579
  async delete(path: string) {
282
- if (!(await this.exists(path))) return false;
283
580
  try {
581
+ if (!(await this.exists(path))) {
582
+ console.warn("文件不存在");
583
+ if (this.options.throwError) {
584
+ throw new VVVFSError("Delete", "文件不存在");
585
+ }
586
+ return false;
587
+ }
284
588
  const { name, parent } = parsePath(path);
285
589
  if (await this.isDir(path)) {
286
590
  const files = await this.list(path);
@@ -301,6 +605,9 @@ class VVVFS {
301
605
  }
302
606
  } catch (e) {
303
607
  console.error(e);
608
+ if (this.options.throwError) {
609
+ throw new VVVFSError("Delete", "删除文件失败" + e);
610
+ }
304
611
  return false;
305
612
  }
306
613
  }
@@ -310,9 +617,21 @@ class VVVFS {
310
617
  * @param newPath 新路径
311
618
  */
312
619
  async move(path: string, newPath: string) {
313
- if (await this.exists(newPath)) return false;
314
- if (!(await this.exists(path))) return false;
315
620
  try {
621
+ if (await this.exists(newPath)) {
622
+ console.warn("目标文件已存在");
623
+ if (this.options.throwError) {
624
+ throw new VVVFSError("Move", "目标文件已存在");
625
+ }
626
+ return false;
627
+ }
628
+ if (!(await this.exists(path))) {
629
+ console.warn("源文件不存在");
630
+ if (this.options.throwError) {
631
+ throw new VVVFSError("Move", "源文件不存在");
632
+ }
633
+ return false;
634
+ }
316
635
  const { name, parent } = parsePath(path);
317
636
  const { name: newName, parent: newParent } = parsePath(newPath);
318
637
  await this.createDir(newParent);
@@ -329,6 +648,9 @@ class VVVFS {
329
648
  return true;
330
649
  } catch (e) {
331
650
  console.error(e);
651
+ if (this.options.throwError) {
652
+ throw new VVVFSError("Move", "移动文件失败" + e);
653
+ }
332
654
  return false;
333
655
  }
334
656
  }
@@ -338,9 +660,21 @@ class VVVFS {
338
660
  * @param newPath 新路径
339
661
  */
340
662
  async copy(path: string, newPath: string) {
341
- if (await this.exists(newPath)) return false;
342
- if (!(await this.exists(path))) return false;
343
663
  try {
664
+ if (await this.exists(newPath)) {
665
+ console.warn("目标文件已存在");
666
+ if (this.options.throwError) {
667
+ throw new VVVFSError("Copy", "目标文件已存在");
668
+ }
669
+ return false;
670
+ }
671
+ if (!(await this.exists(path))) {
672
+ console.warn("源文件不存在");
673
+ if (this.options.throwError) {
674
+ throw new VVVFSError("Copy", "源文件不存在");
675
+ }
676
+ return false;
677
+ }
344
678
  const { name, parent } = parsePath(path);
345
679
  const { name: newName, parent: newParent } = parsePath(newPath);
346
680
  await this.createDir(newParent);
@@ -356,6 +690,9 @@ class VVVFS {
356
690
  return true;
357
691
  } catch (e) {
358
692
  console.error(e);
693
+ if (this.options.throwError) {
694
+ throw new VVVFSError("Copy", "复制文件失败" + e);
695
+ }
359
696
  return false;
360
697
  }
361
698
  }
@@ -365,33 +702,47 @@ class VVVFS {
365
702
  * @param query 查询字符串
366
703
  */
367
704
  async search(basePath: string, query: string) {
368
- if (!await this.isDir(basePath)) return null;
369
- const that = this;
370
705
  try {
706
+ if (!(await this.isDir(basePath))) {
707
+ console.warn("基础路径不是目录");
708
+ if (this.options.throwError) {
709
+ throw new VVVFSError("Search", "基础路径不是目录");
710
+ }
711
+ return null;
712
+ }
713
+ const that = this;
371
714
  const dirs = await this.list(basePath);
372
715
  return await search(basePath, dirs);
373
- } catch (e) {
374
- console.error(e);
375
- return [];
376
- }
377
- async function search(parent: string, files: string[]) {
378
- const result: string[] = [];
379
- console.log(files)
380
- for (const file of files) {
381
- if (file == "") continue;
382
- console.log(file);
383
- if (await that.isDir(joinPath(parent, file))) {
384
- if (file.includes(query)) {
385
- result.push(joinPath(parent, file) + "/");
386
- }
387
- result.push(...await search(joinPath(parent, file), await that.list(joinPath(parent, file))));
388
- } else {
389
- if (file.includes(query)) {
390
- result.push(joinPath(parent, file));
716
+ async function search(parent: string, files: string[]) {
717
+ const result: string[] = [];
718
+ console.log(files);
719
+ for (const file of files) {
720
+ if (file == "") continue;
721
+ console.log(file);
722
+ if (await that.isDir(joinPath(parent, file))) {
723
+ if (file.includes(query)) {
724
+ result.push(joinPath(parent, file) + "/");
725
+ }
726
+ result.push(
727
+ ...(await search(
728
+ joinPath(parent, file),
729
+ await that.list(joinPath(parent, file)),
730
+ )),
731
+ );
732
+ } else {
733
+ if (file.includes(query)) {
734
+ result.push(joinPath(parent, file));
735
+ }
391
736
  }
392
737
  }
738
+ return result;
393
739
  }
394
- return result;
740
+ } catch (e) {
741
+ console.error(e);
742
+ if (this.options.throwError) {
743
+ throw new VVVFSError("Search", "搜索文件失败" + e);
744
+ }
745
+ return null;
395
746
  }
396
747
  }
397
748
  }
@@ -400,11 +751,12 @@ function parsePath(path: string) {
400
751
  const oldParts = path.split("/");
401
752
  const parts: string[] = [];
402
753
  for (let i = 0; i < oldParts.length; i++) {
754
+ if (oldParts[i].length > 255) throw new VVVFSError("ParsePath", "文件名过长");
403
755
  if (oldParts[i]) parts.push(oldParts[i]);
404
756
  }
405
757
  const name = parts.pop() || "";
406
758
  const parent = "/" + parts.join("/");
407
- if ((parent + "/" + name).length > 255) throw new Error("文件名过长");
759
+ if (joinPath(parent, name).length > 4096) throw new VVVFSError("ParsePath", "文件路径过长");
408
760
  return { name, parent };
409
761
  }
410
762
  function joinPath(...paths: string[]) {
@@ -433,7 +785,7 @@ function joinPath(...paths: string[]) {
433
785
  return result || (isAbsolute ? "/" : ".");
434
786
  }
435
787
  Object.defineProperty(VVVFS, "version", {
436
- value: packageJson.version,
788
+ value: version,
437
789
  writable: false,
438
790
  enumerable: true,
439
791
  configurable: false,