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.
- package/README.md +11 -1
- package/index.html +2 -1
- package/index.ts +306 -594
- 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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
250
|
-
|
|
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
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
if (this.#
|
|
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
|
|
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
|
-
}
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
if (this.#
|
|
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
|
|
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
|
|
473
|
+
name,
|
|
524
474
|
path: parent,
|
|
525
475
|
type: "dir",
|
|
526
476
|
file: new File([], name),
|
|
527
477
|
});
|
|
528
478
|
return true;
|
|
529
|
-
}
|
|
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
|
-
|
|
543
|
-
const
|
|
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
|
-
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
|
|
523
|
+
return this.#withErrorHandling("write", async () => {
|
|
567
524
|
const targetPath = joinPath(path);
|
|
568
|
-
if (this.#
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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,
|
|
645
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
560
|
+
return this.#withErrorHandling("append", async () => {
|
|
665
561
|
const targetPath = joinPath(path);
|
|
666
|
-
if (this.#
|
|
667
|
-
|
|
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
|
|
682
|
-
} else {
|
|
683
|
-
return await this.write(targetPath, content);
|
|
568
|
+
return await this.#internalWrite(targetPath, blob);
|
|
684
569
|
}
|
|
685
|
-
|
|
686
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
|
|
597
|
+
return this.#withErrorHandling("read", async () => {
|
|
716
598
|
const targetPath = joinPath(path);
|
|
717
|
-
if (this.#
|
|
718
|
-
|
|
719
|
-
|
|
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
|
-
|
|
608
|
+
return this.#withErrorHandling("readText", async () => {
|
|
750
609
|
const file = await this.read(path);
|
|
751
|
-
|
|
752
|
-
|
|
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
|
-
|
|
618
|
+
return this.#withErrorHandling("readJson", async () => {
|
|
774
619
|
const text = await this.readText(path);
|
|
775
|
-
if (text)
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
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
|
-
|
|
651
|
+
return this.#withErrorHandling("readTextChunk", async () => {
|
|
840
652
|
const chunk = await this.readChunk(path, start, end);
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
|
|
859
|
-
const
|
|
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
|
-
}
|
|
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
|
-
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
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
|
-
|
|
681
|
+
return this.#withErrorHandling("list", async () => {
|
|
893
682
|
const targetPath = joinPath(path);
|
|
894
|
-
if (this.#
|
|
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
|
-
}
|
|
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
|
-
|
|
703
|
+
return this.#withErrorHandling("rename", async () => {
|
|
936
704
|
const sourcePath = joinPath(path);
|
|
937
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
744
|
+
return this.#withErrorHandling("delete", async () => {
|
|
1006
745
|
const targetPath = joinPath(path);
|
|
1007
|
-
if (this.#
|
|
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
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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
|
|
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
|
-
|
|
780
|
+
return this.#withErrorHandling("move", async () => {
|
|
1061
781
|
const sourcePath = joinPath(path);
|
|
1062
782
|
const destinationPath = joinPath(newPath);
|
|
1063
|
-
if (this.#
|
|
1064
|
-
|
|
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
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
-
|
|
830
|
+
return this.#withErrorHandling("copy", async () => {
|
|
1138
831
|
const sourcePath = joinPath(path);
|
|
1139
832
|
const destinationPath = joinPath(newPath);
|
|
1140
|
-
if (this.#
|
|
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
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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
|
-
|
|
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
|
|
1220
|
-
async function
|
|
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
|
|
1225
|
-
|
|
1226
|
-
if (await that.isDir(
|
|
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(
|
|
892
|
+
result.push(fullPath + "/");
|
|
1229
893
|
}
|
|
1230
894
|
result.push(
|
|
1231
|
-
...(await
|
|
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(
|
|
897
|
+
} else if (await that.isFile(fullPath)) {
|
|
1237
898
|
if (file.includes(query)) {
|
|
1238
|
-
|
|
1239
|
-
result.push(joinPath(parent, file));
|
|
899
|
+
result.push(fullPath);
|
|
1240
900
|
}
|
|
1241
901
|
}
|
|
1242
902
|
}
|
|
1243
903
|
return result;
|
|
1244
904
|
}
|
|
1245
|
-
}
|
|
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
|
-
|
|
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
|
-
(
|
|
1011
|
+
Object.defineProperty(VVVFS, "author", {
|
|
1012
|
+
value: "IFTC",
|
|
1013
|
+
writable: false,
|
|
1014
|
+
enumerable: true,
|
|
1015
|
+
configurable: false,
|
|
1016
|
+
});
|
|
1017
|
+
(globalThis as any).VVVFS = VVVFS;
|