vvvfs 0.1.8 → 0.1.9

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 CHANGED
@@ -96,6 +96,12 @@ const vvvfs = new VVVFS("vvvfs", {
96
96
 
97
97
  ## 更新日志
98
98
 
99
+ ### 0.1.9
100
+
101
+ - 新增 `saveFile` 方法,用于保存文件
102
+ - 新增 `downloadFile` 方法,用于下载文件并保存到IndexedDB(支持`fetch`和`xhr`两种模式,以及下载进度、错误和成功事件)
103
+ - `VVVFS.File` 类新增 `save` 和 `download` 方法
104
+
99
105
  ### 0.1.8
100
106
 
101
107
  - 新增 `writeJsonValue` 和 `readJsonValue` 方法,用于读取和写入JSON值(键支持字符串、数字数组下标以及嵌套路径,如 `["a", "b"]`)
@@ -273,6 +279,12 @@ Using the `VVVFS.File` class:
273
279
 
274
280
  ## Changelog
275
281
 
282
+ ### 0.1.9
283
+
284
+ - Added the `saveFile` method for saving files
285
+ - Added the `downloadFile` method for downloading files to IndexedDB (supports both `fetch` and `xhr` modes, as well as progress, error, and success callbacks)
286
+ - Added the `save` and `download` methods to the `VVVFS.File` class
287
+
276
288
  ### 0.1.8
277
289
 
278
290
  - Added the `writeJsonValue` and `readJsonValue` methods for reading and writing JSON values (keys support strings, numeric array indexes, and nested paths such as `["a", "b"]`)
package/index.ts CHANGED
@@ -44,6 +44,22 @@ export interface VVVFSOptions {
44
44
  init: boolean | string;
45
45
  }
46
46
 
47
+ /**
48
+ * 虚拟文件系统下载配置项
49
+ * @param headers 请求头
50
+ * @param mode 下载模式(fetch或xhr)
51
+ * @param onProgress 下载进度事件
52
+ * @param onError 下载出错事件
53
+ * @param onSuccess 下载成功事件
54
+ */
55
+ export interface VVVFSDownloadOptions {
56
+ headers?: Record<string, string>;
57
+ mode?: "fetch" | "xhr";
58
+ onProgress?: (progress: number) => void;
59
+ onError?: (error: any) => void;
60
+ onSuccess?: (success: boolean) => void;
61
+ }
62
+
47
63
  /**
48
64
  * 虚拟文件系统错误类
49
65
  */
@@ -289,6 +305,21 @@ class VVVFSFile {
289
305
  async isLocked() {
290
306
  return await this._vvvfs.isLocked(this._path);
291
307
  }
308
+ /**
309
+ * 保存文件
310
+ * @param name 文件名
311
+ */
312
+ async save(name: string) {
313
+ return await this._vvvfs.saveFile(this._path, name);
314
+ }
315
+ /**
316
+ * 下载文件
317
+ * @param url 文件链接
318
+ * @param options 下载配置项
319
+ */
320
+ async download(url: string | URL | Request, options: VVVFSDownloadOptions = {}) {
321
+ return await this._vvvfs.downloadFile(url, this._path, options);
322
+ }
292
323
  }
293
324
 
294
325
  /**
@@ -1297,6 +1328,166 @@ class VVVFS {
1297
1328
  return await this.#isLocked(targetPath);
1298
1329
  }, false);
1299
1330
  }
1331
+ /**
1332
+ * 保存文件
1333
+ * @param path 文件路径
1334
+ * @param name 文件名
1335
+ */
1336
+ async saveFile(path: string, name?: string) {
1337
+ return this.#withErrorHandling("saveFile", async () => {
1338
+ const targetPath = joinPath(path);
1339
+ if (!(await this.#checkAccess(targetPath, "read"))) return false;
1340
+ const file = await this.#internalRead(targetPath);
1341
+ if (!file) {
1342
+ console.warn("文件不存在");
1343
+ return false;
1344
+ }
1345
+ const a = document.createElement("a");
1346
+ a.href = URL.createObjectURL(file);
1347
+ a.download = name || Path.basename(targetPath);
1348
+ a.click();
1349
+ URL.revokeObjectURL(a.href);
1350
+ return true;
1351
+ }, false);
1352
+ }
1353
+ /**
1354
+ * 下载文件
1355
+ * @param url 文件链接
1356
+ * @param path 保存路径
1357
+ * @param options 下载配置项
1358
+ */
1359
+ async downloadFile(
1360
+ url: string | URL | Request,
1361
+ path: string,
1362
+ options: VVVFSDownloadOptions = {},
1363
+ ) {
1364
+ return this.#withErrorHandling("downloadFile", async () => {
1365
+ const targetPath = joinPath(path);
1366
+ if (!(await this.#checkAccess(targetPath, "write"))) return false;
1367
+ const content =
1368
+ (options.mode || "fetch") === "xhr"
1369
+ ? await this.#downloadWithXHR(url, options)
1370
+ : await this.#downloadWithFetch(url, options);
1371
+ if (!content) return false;
1372
+ const blob = new Blob([content], {
1373
+ type: mime.getType(targetPath) || "application/octet-stream",
1374
+ });
1375
+ const success = await this.#internalWrite(targetPath, blob);
1376
+ if (success && options.onSuccess) {
1377
+ options.onSuccess(success);
1378
+ }
1379
+ return success;
1380
+ }, false);
1381
+ }
1382
+ /**
1383
+ * 使用fetch下载文件
1384
+ * @param url 文件链接
1385
+ * @param options 下载配置项
1386
+ */
1387
+ async #downloadWithFetch(
1388
+ url: string | URL | Request,
1389
+ options: VVVFSDownloadOptions,
1390
+ ): Promise<Blob | null> {
1391
+ const request =
1392
+ url instanceof Request
1393
+ ? url
1394
+ : new Request(url, { headers: options.headers });
1395
+ const response = await fetch(request);
1396
+ if (!response.ok) {
1397
+ const error = new Error(`下载失败,HTTP ${response.status}`);
1398
+ if (options.onError) {
1399
+ options.onError(error);
1400
+ return null;
1401
+ }
1402
+ throw error;
1403
+ }
1404
+ const total = Number(response.headers.get("Content-Length") || 0);
1405
+ if (options.onProgress && response.body && total > 0) {
1406
+ const reader = response.body.getReader();
1407
+ const chunks: Uint8Array<ArrayBuffer>[] = [];
1408
+ let loaded = 0;
1409
+ while (true) {
1410
+ const { done, value } = await reader.read();
1411
+ if (done) break;
1412
+ const chunk = value as Uint8Array<ArrayBuffer>;
1413
+ chunks.push(chunk);
1414
+ loaded += chunk.length;
1415
+ options.onProgress(Math.round((loaded / total) * 100));
1416
+ }
1417
+ return new Blob(chunks);
1418
+ }
1419
+ return await response.blob();
1420
+ }
1421
+ /**
1422
+ * 使用XHR下载文件
1423
+ * @param url 文件链接
1424
+ * @param options 下载配置项
1425
+ */
1426
+ #downloadWithXHR(
1427
+ url: string | URL | Request,
1428
+ options: VVVFSDownloadOptions,
1429
+ ): Promise<Blob | null> {
1430
+ return new Promise((resolve, reject) => {
1431
+ const requestUrl =
1432
+ url instanceof URL
1433
+ ? url.href
1434
+ : url instanceof Request
1435
+ ? url.url
1436
+ : url;
1437
+ const headers = url instanceof Request ? url.headers : options.headers;
1438
+ const xhr = new XMLHttpRequest();
1439
+ xhr.open("GET", requestUrl);
1440
+ if (headers instanceof Headers) {
1441
+ headers.forEach((value, key) => {
1442
+ xhr.setRequestHeader(key, value);
1443
+ });
1444
+ } else if (headers) {
1445
+ for (const [key, value] of Object.entries(headers)) {
1446
+ xhr.setRequestHeader(key, value);
1447
+ }
1448
+ }
1449
+ xhr.responseType = "blob";
1450
+ xhr.onprogress = (event) => {
1451
+ if (options.onProgress && event.total > 0) {
1452
+ options.onProgress(
1453
+ Math.round((event.loaded / event.total) * 100),
1454
+ );
1455
+ }
1456
+ };
1457
+ xhr.onload = () => {
1458
+ if (xhr.status >= 200 && xhr.status < 300) {
1459
+ resolve(xhr.response as Blob);
1460
+ } else {
1461
+ const error = new Error(`下载失败,HTTP ${xhr.status}`);
1462
+ if (options.onError) {
1463
+ options.onError(error);
1464
+ resolve(null);
1465
+ } else {
1466
+ reject(error);
1467
+ }
1468
+ }
1469
+ };
1470
+ xhr.onerror = () => {
1471
+ const error = new Error("下载失败");
1472
+ if (options.onError) {
1473
+ options.onError(error);
1474
+ resolve(null);
1475
+ } else {
1476
+ reject(error);
1477
+ }
1478
+ };
1479
+ try {
1480
+ xhr.send();
1481
+ } catch (error) {
1482
+ if (options.onError) {
1483
+ options.onError(error);
1484
+ resolve(null);
1485
+ } else {
1486
+ reject(error);
1487
+ }
1488
+ }
1489
+ });
1490
+ }
1300
1491
  }
1301
1492
  /**
1302
1493
  * 解析路径
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vvvfs",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "一个使用IndexDB实现的虚拟文件系统",
5
5
  "keywords": [
6
6
  "Virtual",
package/types/index.d.ts CHANGED
@@ -62,6 +62,22 @@ export interface VVVFSOptions {
62
62
  init: boolean | string;
63
63
  }
64
64
 
65
+ /**
66
+ * 虚拟文件系统下载配置项
67
+ * @param headers 请求头
68
+ * @param mode 下载模式(fetch或xhr)
69
+ * @param onProgress 下载进度事件
70
+ * @param onError 下载出错事件
71
+ * @param onSuccess 下载成功事件
72
+ */
73
+ export interface VVVFSDownloadOptions {
74
+ headers?: Record<string, string>;
75
+ mode?: "fetch" | "xhr";
76
+ onProgress?: (progress: number) => void;
77
+ onError?: (error: any) => void;
78
+ onSuccess?: (success: boolean) => void;
79
+ }
80
+
65
81
  /**
66
82
  * 虚拟文件系统错误类
67
83
  */
@@ -225,6 +241,17 @@ export class VVVFSFile {
225
241
  * 判断文件是否已锁定
226
242
  */
227
243
  isLocked(): Promise<boolean>;
244
+ /**
245
+ * 保存文件
246
+ * @param name 文件名
247
+ */
248
+ save(name: string): Promise<boolean>;
249
+ /**
250
+ * 下载文件
251
+ * @param url 文件链接
252
+ * @param options 下载配置项
253
+ */
254
+ download(url: string | URL | Request, options?: VVVFSDownloadOptions): Promise<boolean>;
228
255
  }
229
256
 
230
257
  /**
@@ -532,6 +559,23 @@ export class VVVFS {
532
559
  * @param path 文件路径
533
560
  */
534
561
  isLocked(path: string): Promise<boolean>;
562
+ /**
563
+ * 保存文件
564
+ * @param path 文件路径
565
+ * @param name 文件名
566
+ */
567
+ saveFile(path: string, name?: string): Promise<boolean>;
568
+ /**
569
+ * 下载文件
570
+ * @param url 文件链接
571
+ * @param path 保存路径
572
+ * @param options 下载配置项
573
+ */
574
+ downloadFile(
575
+ url: string | URL | Request,
576
+ path: string,
577
+ options?: VVVFSDownloadOptions,
578
+ ): Promise<boolean>;
535
579
  }
536
580
 
537
581
  /**