@dxos/random-access-storage 0.4.3 → 0.4.4-main.b30fc36
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/dist/lib/browser/index.mjs +85 -43
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +12 -1
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/browser/web-fs.d.ts +14 -1
- package/dist/types/src/browser/web-fs.d.ts.map +1 -1
- package/dist/types/src/common/memory-storage.d.ts +2 -1
- package/dist/types/src/common/memory-storage.d.ts.map +1 -1
- package/dist/types/src/testing/storage.blueprint-test.d.ts +1 -1
- package/dist/types/src/testing/storage.blueprint-test.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/browser/web-fs.ts +64 -46
- package/src/common/memory-storage.ts +14 -1
- package/src/node/node-storage.ts +2 -2
- package/src/testing/storage.blueprint-test.ts +96 -7
package/src/browser/web-fs.ts
CHANGED
|
@@ -13,18 +13,12 @@ import { TimeSeriesCounter, trace } from '@dxos/tracing';
|
|
|
13
13
|
|
|
14
14
|
import { Directory, type DiskInfo, type File, type Storage, StorageType, getFullPath } from '../common';
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* When handles weren't shared by different WebFS instances, browser tests
|
|
18
|
-
* were sporadically failing with NonReadableError on test cases like:
|
|
19
|
-
* webFs1.createFile->createWritable->write->close
|
|
20
|
-
* webFs2.createFile->read
|
|
21
|
-
* Presumably due to some handle-level write buffering.
|
|
22
|
-
*/
|
|
23
|
-
const files = new Map<string, WebFile>();
|
|
24
16
|
/**
|
|
25
17
|
* Web file systems.
|
|
26
18
|
*/
|
|
27
19
|
export class WebFS implements Storage {
|
|
20
|
+
private readonly _files = new Map<string, WebFile>();
|
|
21
|
+
|
|
28
22
|
readonly type = StorageType.WEBFS;
|
|
29
23
|
|
|
30
24
|
protected _root?: FileSystemDirectoryHandle;
|
|
@@ -32,13 +26,13 @@ export class WebFS implements Storage {
|
|
|
32
26
|
constructor(public readonly path: string) {}
|
|
33
27
|
|
|
34
28
|
public get size() {
|
|
35
|
-
return
|
|
29
|
+
return this._files.size;
|
|
36
30
|
}
|
|
37
31
|
|
|
38
32
|
private _getFiles(path: string): Map<string, WebFile> {
|
|
39
33
|
const fullName = this._getFullFilename(this.path, path);
|
|
40
34
|
return new Map(
|
|
41
|
-
[...
|
|
35
|
+
[...this._files.entries()].filter(([path, file]) => {
|
|
42
36
|
return path.includes(fullName) && !file.destroyed;
|
|
43
37
|
}),
|
|
44
38
|
);
|
|
@@ -88,19 +82,19 @@ export class WebFS implements Storage {
|
|
|
88
82
|
getOrCreateFile: (...args) => this.getOrCreateFile(...args),
|
|
89
83
|
remove: () => this._delete(sub),
|
|
90
84
|
onFlush: async () => {
|
|
91
|
-
await Promise.all(Array.from(this._getFiles(sub)).map(([
|
|
85
|
+
await Promise.all(Array.from(this._getFiles(sub)).map(([_, file]) => file.flush()));
|
|
92
86
|
},
|
|
93
87
|
});
|
|
94
88
|
}
|
|
95
89
|
|
|
96
90
|
getOrCreateFile(path: string, filename: string, opts?: any): File {
|
|
97
91
|
const fullName = this._getFullFilename(path, filename);
|
|
98
|
-
const existingFile =
|
|
92
|
+
const existingFile = this._files.get(fullName);
|
|
99
93
|
if (existingFile) {
|
|
100
94
|
return existingFile;
|
|
101
95
|
}
|
|
102
96
|
const file = this._createFile(fullName);
|
|
103
|
-
|
|
97
|
+
this._files.set(fullName, file);
|
|
104
98
|
return file;
|
|
105
99
|
}
|
|
106
100
|
|
|
@@ -109,7 +103,7 @@ export class WebFS implements Storage {
|
|
|
109
103
|
fileName: fullName,
|
|
110
104
|
file: this._initialize().then((root) => root.getFileHandle(fullName, { create: true })),
|
|
111
105
|
destroy: async () => {
|
|
112
|
-
|
|
106
|
+
this._files.delete(fullName);
|
|
113
107
|
const root = await this._initialize();
|
|
114
108
|
return root.removeEntry(fullName);
|
|
115
109
|
},
|
|
@@ -120,7 +114,7 @@ export class WebFS implements Storage {
|
|
|
120
114
|
await Promise.all(
|
|
121
115
|
Array.from(this._getFiles(path)).map(async ([path, file]) => {
|
|
122
116
|
await file.destroy().catch((err: any) => log.warn(err));
|
|
123
|
-
|
|
117
|
+
this._files.delete(path);
|
|
124
118
|
}),
|
|
125
119
|
);
|
|
126
120
|
}
|
|
@@ -128,22 +122,28 @@ export class WebFS implements Storage {
|
|
|
128
122
|
async reset() {
|
|
129
123
|
await this._initialize();
|
|
130
124
|
for await (const filename of await (this._root as any).keys()) {
|
|
131
|
-
|
|
132
|
-
|
|
125
|
+
await this._root!.removeEntry(filename, { recursive: true }).catch((err: any) =>
|
|
126
|
+
log.warn('failed to remove an entry', { filename, err }),
|
|
127
|
+
);
|
|
128
|
+
this._files.delete(filename);
|
|
133
129
|
}
|
|
134
130
|
this._root = undefined;
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
async close() {
|
|
138
|
-
await Promise.all(
|
|
134
|
+
await Promise.all(
|
|
135
|
+
Array.from(this._files.values()).map((file) => {
|
|
136
|
+
return file.close().catch((e) => log.warn('failed to close a file', { file: file.fileName, e }));
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
private _getFullFilename(path: string, filename?: string) {
|
|
142
142
|
// Replace slashes with underscores. Because we can't have slashes in filenames in Browser File Handle API.
|
|
143
143
|
if (filename) {
|
|
144
|
-
return getFullPath(path, filename).
|
|
144
|
+
return getFullPath(path, filename).replace(/\//g, '_');
|
|
145
145
|
} else {
|
|
146
|
-
return path.
|
|
146
|
+
return path.replace(/\//g, '_');
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
@@ -158,7 +158,7 @@ export class WebFS implements Storage {
|
|
|
158
158
|
(async () => {
|
|
159
159
|
switch (entry.kind) {
|
|
160
160
|
case 'file':
|
|
161
|
-
used += await (entry as FileSystemFileHandle).getFile().then((f) =>
|
|
161
|
+
used += await (entry as FileSystemFileHandle).getFile().then((f) => f.size);
|
|
162
162
|
break;
|
|
163
163
|
case 'directory':
|
|
164
164
|
await recurse(entry as FileSystemDirectoryHandle);
|
|
@@ -182,7 +182,7 @@ export class WebFS implements Storage {
|
|
|
182
182
|
// @trace.resource()
|
|
183
183
|
export class WebFile extends EventEmitter implements File {
|
|
184
184
|
@trace.info()
|
|
185
|
-
|
|
185
|
+
readonly fileName: string;
|
|
186
186
|
|
|
187
187
|
private readonly _fileHandle: Promise<FileSystemFileHandle>;
|
|
188
188
|
private readonly _destroy: () => Promise<void>;
|
|
@@ -195,8 +195,12 @@ export class WebFile extends EventEmitter implements File {
|
|
|
195
195
|
private _loadBufferPromise: Promise<void> | null = null;
|
|
196
196
|
|
|
197
197
|
private _flushScheduled = false;
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
private _flushPromise: Promise<void> = Promise.resolve();
|
|
199
|
+
/**
|
|
200
|
+
* Used to discard unnecessary scheduled flushes.
|
|
201
|
+
* If _flushNow() is called with a lower sequence number it should early exit.
|
|
202
|
+
*/
|
|
203
|
+
private _flushSequence = 0;
|
|
200
204
|
|
|
201
205
|
//
|
|
202
206
|
// Metrics
|
|
@@ -235,7 +239,7 @@ export class WebFile extends EventEmitter implements File {
|
|
|
235
239
|
destroy: () => Promise<void>;
|
|
236
240
|
}) {
|
|
237
241
|
super();
|
|
238
|
-
this.
|
|
242
|
+
this.fileName = fileName;
|
|
239
243
|
this._fileHandle = file;
|
|
240
244
|
this._destroy = destroy;
|
|
241
245
|
|
|
@@ -284,10 +288,11 @@ export class WebFile extends EventEmitter implements File {
|
|
|
284
288
|
}
|
|
285
289
|
|
|
286
290
|
// Do not call directly, use _flushLater or _flushNow.
|
|
287
|
-
private async _flushCache() {
|
|
288
|
-
if (this.destroyed) {
|
|
291
|
+
private async _flushCache(sequence: number) {
|
|
292
|
+
if (this.destroyed || sequence < this._flushSequence) {
|
|
289
293
|
return;
|
|
290
294
|
}
|
|
295
|
+
this._flushSequence = sequence + 1;
|
|
291
296
|
|
|
292
297
|
this._flushes.inc();
|
|
293
298
|
|
|
@@ -305,35 +310,25 @@ export class WebFile extends EventEmitter implements File {
|
|
|
305
310
|
return;
|
|
306
311
|
}
|
|
307
312
|
|
|
313
|
+
const sequence = this._flushSequence;
|
|
308
314
|
setTimeout(async () => {
|
|
309
315
|
// Making sure only one flush can run at a time.
|
|
310
|
-
const promiseBefore = this._flushPromise;
|
|
311
316
|
await this._flushPromise;
|
|
312
317
|
this._flushScheduled = false;
|
|
313
|
-
|
|
314
|
-
// _flushNow might have been called. In that case we don't want to run the flush again.
|
|
315
|
-
if (promiseBefore !== this._flushPromise) {
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
this._flushPromise = this._flushCache().catch((err) => log.warn(err));
|
|
318
|
+
this._flushPromise = this._flushCache(sequence).catch((err) => log.warn(err));
|
|
320
319
|
});
|
|
321
320
|
|
|
322
321
|
this._flushScheduled = true;
|
|
323
322
|
}
|
|
324
323
|
|
|
325
324
|
private async _flushNow() {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
.catch((err) => log.warn(err));
|
|
329
|
-
|
|
325
|
+
await this._flushPromise;
|
|
326
|
+
this._flushPromise = this._flushCache(this._flushSequence).catch((err) => log.warn(err));
|
|
330
327
|
await this._flushPromise;
|
|
331
328
|
}
|
|
332
329
|
|
|
333
330
|
async read(offset: number, size: number) {
|
|
334
|
-
|
|
335
|
-
throw new Error('Read of a destroyed file');
|
|
336
|
-
}
|
|
331
|
+
this.assertNotDestroyed('Read');
|
|
337
332
|
|
|
338
333
|
this._operations.inc();
|
|
339
334
|
this._reads.inc();
|
|
@@ -353,6 +348,8 @@ export class WebFile extends EventEmitter implements File {
|
|
|
353
348
|
}
|
|
354
349
|
|
|
355
350
|
async write(offset: number, data: Buffer) {
|
|
351
|
+
this.assertNotDestroyed('Write');
|
|
352
|
+
|
|
356
353
|
this._operations.inc();
|
|
357
354
|
this._writes.inc();
|
|
358
355
|
this._writeBytes.inc(data.length);
|
|
@@ -376,9 +373,11 @@ export class WebFile extends EventEmitter implements File {
|
|
|
376
373
|
}
|
|
377
374
|
|
|
378
375
|
async del(offset: number, size: number) {
|
|
376
|
+
this.assertNotDestroyed('Del');
|
|
377
|
+
|
|
379
378
|
this._operations.inc();
|
|
380
379
|
|
|
381
|
-
if (offset < 0 || size
|
|
380
|
+
if (offset < 0 || size <= 0) {
|
|
382
381
|
return;
|
|
383
382
|
}
|
|
384
383
|
|
|
@@ -399,6 +398,8 @@ export class WebFile extends EventEmitter implements File {
|
|
|
399
398
|
}
|
|
400
399
|
|
|
401
400
|
async stat() {
|
|
401
|
+
this.assertNotDestroyed('Truncate');
|
|
402
|
+
|
|
402
403
|
this._operations.inc();
|
|
403
404
|
|
|
404
405
|
// NOTE: This will load all data from the file just to get it's size. While this is a lot of overhead, this works ok for out use cases.
|
|
@@ -413,6 +414,8 @@ export class WebFile extends EventEmitter implements File {
|
|
|
413
414
|
}
|
|
414
415
|
|
|
415
416
|
async truncate(offset: number) {
|
|
417
|
+
this.assertNotDestroyed('Truncate');
|
|
418
|
+
|
|
416
419
|
this._operations.inc();
|
|
417
420
|
|
|
418
421
|
if (!this._buffer) {
|
|
@@ -426,17 +429,32 @@ export class WebFile extends EventEmitter implements File {
|
|
|
426
429
|
}
|
|
427
430
|
|
|
428
431
|
async flush() {
|
|
432
|
+
this.assertNotDestroyed('Flush');
|
|
433
|
+
|
|
429
434
|
await this._flushNow();
|
|
430
435
|
}
|
|
431
436
|
|
|
437
|
+
/**
|
|
438
|
+
* It's best to avoid using this method as it doesn't really close a file.
|
|
439
|
+
* We could update the _opened flag and add a guard like for destroyed, but this would break
|
|
440
|
+
* the FileSystemFileHandle sharing required for browser tests to run, where writes are
|
|
441
|
+
* not immediately visible if using different file handles.
|
|
442
|
+
*/
|
|
432
443
|
async close(): Promise<void> {
|
|
433
444
|
await this._flushNow();
|
|
434
445
|
}
|
|
435
446
|
|
|
436
447
|
@synchronized
|
|
437
448
|
async destroy() {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
449
|
+
if (!this.destroyed) {
|
|
450
|
+
this.destroyed = true;
|
|
451
|
+
return await this._destroy();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private assertNotDestroyed(operation: string) {
|
|
456
|
+
if (this.destroyed) {
|
|
457
|
+
throw new Error(`${operation} on a destroyed or closed file`);
|
|
458
|
+
}
|
|
441
459
|
}
|
|
442
460
|
}
|
|
@@ -8,7 +8,7 @@ import { type Callback, type RandomAccessStorage } from 'random-access-storage';
|
|
|
8
8
|
import { arrayToBuffer } from '@dxos/util';
|
|
9
9
|
|
|
10
10
|
import { AbstractStorage } from './abstract-storage';
|
|
11
|
-
import { StorageType } from './storage';
|
|
11
|
+
import { type DiskInfo, StorageType } from './storage';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Storage interface implementation for RAM.
|
|
@@ -42,4 +42,17 @@ export class MemoryStorage extends AbstractStorage {
|
|
|
42
42
|
|
|
43
43
|
return file;
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
async getDiskInfo(): Promise<DiskInfo> {
|
|
47
|
+
let used = 0;
|
|
48
|
+
|
|
49
|
+
for (const file of this._files.values()) {
|
|
50
|
+
const size = (file as any).length;
|
|
51
|
+
used += Number.isNaN(size) ? 0 : size;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
used,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
45
58
|
}
|
package/src/node/node-storage.ts
CHANGED
|
@@ -69,11 +69,11 @@ export class NodeStorage extends AbstractStorage implements Storage {
|
|
|
69
69
|
const recurse = async (path: string) => {
|
|
70
70
|
const pathStats = await stat(path);
|
|
71
71
|
|
|
72
|
-
used += pathStats.size;
|
|
73
|
-
|
|
74
72
|
if (pathStats.isDirectory()) {
|
|
75
73
|
const entries = await readdir(path);
|
|
76
74
|
await Promise.all(entries.map((entry) => recurse(join(path, entry))));
|
|
75
|
+
} else {
|
|
76
|
+
used += pathStats.size;
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
79
|
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// Copyright 2021 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as uuid from 'uuid';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
6
7
|
|
|
7
8
|
import { asyncTimeout } from '@dxos/async';
|
|
8
9
|
|
|
9
|
-
import {
|
|
10
|
+
import { type File, type Storage, StorageType } from '../common';
|
|
10
11
|
|
|
11
|
-
export const randomText = () =>
|
|
12
|
+
export const randomText = () => uuid.v4();
|
|
12
13
|
|
|
13
14
|
export const storageTests = (testGroupName: StorageType, createStorage: () => Storage) => {
|
|
14
15
|
const writeAndCheck = async (file: File, data: Buffer, offset = 0) => {
|
|
@@ -51,7 +52,7 @@ export const storageTests = (testGroupName: StorageType, createStorage: () => St
|
|
|
51
52
|
const directory = storage.createDirectory(directoryName);
|
|
52
53
|
|
|
53
54
|
const count = 10;
|
|
54
|
-
const files = [...Array(count)].map((
|
|
55
|
+
const files = [...Array(count)].map(() => directory.getOrCreateFile(randomText()));
|
|
55
56
|
|
|
56
57
|
{
|
|
57
58
|
// Create and check files amount.
|
|
@@ -173,7 +174,7 @@ export const storageTests = (testGroupName: StorageType, createStorage: () => St
|
|
|
173
174
|
const dir1 = storage.createDirectory('dir1');
|
|
174
175
|
const dir2 = storage.createDirectory('dir2');
|
|
175
176
|
|
|
176
|
-
const fileName =
|
|
177
|
+
const fileName = randomText();
|
|
177
178
|
const buffer1 = Buffer.from(randomText());
|
|
178
179
|
const buffer2 = Buffer.from(randomText());
|
|
179
180
|
|
|
@@ -195,7 +196,7 @@ export const storageTests = (testGroupName: StorageType, createStorage: () => St
|
|
|
195
196
|
const dir = storage.createDirectory('directory');
|
|
196
197
|
const subDir = dir.createDirectory('subDirectory');
|
|
197
198
|
|
|
198
|
-
const file = subDir.getOrCreateFile(
|
|
199
|
+
const file = subDir.getOrCreateFile(randomText());
|
|
199
200
|
const buffer = Buffer.from(randomText());
|
|
200
201
|
await file.write(0, buffer);
|
|
201
202
|
|
|
@@ -204,10 +205,82 @@ export const storageTests = (testGroupName: StorageType, createStorage: () => St
|
|
|
204
205
|
await file.close();
|
|
205
206
|
});
|
|
206
207
|
|
|
208
|
+
test('all writes are flushed', async (t) => {
|
|
209
|
+
if (testGroupName === StorageType.RAM) {
|
|
210
|
+
t.skip();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const fileName = randomText();
|
|
214
|
+
{
|
|
215
|
+
const storage = createStorage();
|
|
216
|
+
const directory = storage.createDirectory();
|
|
217
|
+
const file = directory.getOrCreateFile(fileName);
|
|
218
|
+
await file.write(0, Buffer.alloc(10, '0'));
|
|
219
|
+
for (let i = 1; i <= 9; i++) {
|
|
220
|
+
void file.write(i, Buffer.from(String(i)));
|
|
221
|
+
}
|
|
222
|
+
await file.close();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
{
|
|
226
|
+
const storage = createStorage();
|
|
227
|
+
const directory = storage.createDirectory();
|
|
228
|
+
const file = directory.getOrCreateFile(fileName);
|
|
229
|
+
const allContent = await file.read(0, (await file.stat()).size);
|
|
230
|
+
expect(allContent.toString()).toBe('0123456789');
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('flush interleaved with write', async (t) => {
|
|
235
|
+
if (testGroupName === StorageType.RAM) {
|
|
236
|
+
t.skip();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const fileName = randomText();
|
|
240
|
+
{
|
|
241
|
+
const storage = createStorage();
|
|
242
|
+
const directory = storage.createDirectory();
|
|
243
|
+
const file = directory.getOrCreateFile(fileName);
|
|
244
|
+
await file.write(0, Buffer.alloc(3, '0'));
|
|
245
|
+
await file.write(1, Buffer.from('1'));
|
|
246
|
+
void file.flush?.();
|
|
247
|
+
await file.write(2, Buffer.from('2'));
|
|
248
|
+
await file.close();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
{
|
|
252
|
+
const storage = createStorage();
|
|
253
|
+
const directory = storage.createDirectory();
|
|
254
|
+
const file = directory.getOrCreateFile(fileName);
|
|
255
|
+
const allContent = await file.read(0, (await file.stat()).size);
|
|
256
|
+
expect(allContent.toString()).toBe('012');
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('operations on a destroyed file are rejected', async () => {
|
|
261
|
+
const storage = createStorage();
|
|
262
|
+
const directory = storage.createDirectory();
|
|
263
|
+
const file = directory.getOrCreateFile(randomText());
|
|
264
|
+
|
|
265
|
+
const buffer = Buffer.from(randomText());
|
|
266
|
+
await writeAndCheck(file, buffer);
|
|
267
|
+
await file.destroy();
|
|
268
|
+
|
|
269
|
+
expect(file.destroyed).toBeTruthy();
|
|
270
|
+
await expect(file.destroy()).resolves.toBeUndefined();
|
|
271
|
+
await expect(file.read(0, 1)).rejects.toThrow();
|
|
272
|
+
await expect(file.write(0, buffer)).rejects.toThrow();
|
|
273
|
+
await expect(file.del(0, 1)).rejects.toThrow();
|
|
274
|
+
await expect(file.stat()).rejects.toThrow();
|
|
275
|
+
if (file.truncate) {
|
|
276
|
+
await expect(file.truncate(0)).rejects.toThrow();
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
207
280
|
test('delete directory', async () => {
|
|
208
281
|
const storage = createStorage();
|
|
209
282
|
const directory = storage.createDirectory();
|
|
210
|
-
const file = directory.getOrCreateFile(
|
|
283
|
+
const file = directory.getOrCreateFile(randomText());
|
|
211
284
|
|
|
212
285
|
const buffer = Buffer.from(randomText());
|
|
213
286
|
await writeAndCheck(file, buffer);
|
|
@@ -364,5 +437,21 @@ export const storageTests = (testGroupName: StorageType, createStorage: () => St
|
|
|
364
437
|
const entries = await directory.list();
|
|
365
438
|
expect(entries).toEqual(expect.arrayContaining(FILES));
|
|
366
439
|
});
|
|
440
|
+
|
|
441
|
+
test('getDiskInfo returns correct size', async (t) => {
|
|
442
|
+
const storage = createStorage();
|
|
443
|
+
if (storage.type === StorageType.IDB) {
|
|
444
|
+
t.skip();
|
|
445
|
+
}
|
|
446
|
+
await storage.reset();
|
|
447
|
+
const directory = storage.createDirectory('dir');
|
|
448
|
+
const file = directory.getOrCreateFile(randomText());
|
|
449
|
+
const content = 'Hello, world!';
|
|
450
|
+
await file.write(0, Buffer.from(content));
|
|
451
|
+
await file.close();
|
|
452
|
+
await expect(storage.getDiskInfo?.() ?? Promise.resolve(-1)).resolves.toEqual({
|
|
453
|
+
used: content.length,
|
|
454
|
+
});
|
|
455
|
+
});
|
|
367
456
|
});
|
|
368
457
|
};
|