@dxos/random-access-storage 0.4.2 → 0.4.3-main.085f4d3
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 +109 -107
- 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 +15 -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 +88 -78
- 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,69 +310,55 @@ 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();
|
|
340
335
|
this._readBytes.inc(size);
|
|
341
336
|
|
|
342
|
-
|
|
343
|
-
await this._loadBufferGuarded();
|
|
344
|
-
invariant(this._buffer);
|
|
345
|
-
}
|
|
337
|
+
const buffer = await this.requireBuffer();
|
|
346
338
|
|
|
347
|
-
if (offset + size >
|
|
339
|
+
if (offset + size > buffer.length) {
|
|
348
340
|
throw new Error('Read out of bounds');
|
|
349
341
|
}
|
|
350
342
|
|
|
351
343
|
// Copy data into a new buffer.
|
|
352
|
-
return Buffer.from(
|
|
344
|
+
return Buffer.from(buffer.slice(offset, offset + size));
|
|
353
345
|
}
|
|
354
346
|
|
|
355
347
|
async write(offset: number, data: Buffer) {
|
|
348
|
+
this.assertNotDestroyed('Write');
|
|
349
|
+
|
|
356
350
|
this._operations.inc();
|
|
357
351
|
this._writes.inc();
|
|
358
352
|
this._writeBytes.inc(data.length);
|
|
359
353
|
|
|
360
|
-
|
|
361
|
-
await this._loadBufferGuarded();
|
|
362
|
-
invariant(this._buffer);
|
|
363
|
-
}
|
|
354
|
+
const buffer = await this.requireBuffer();
|
|
364
355
|
|
|
365
|
-
if (offset + data.length <=
|
|
366
|
-
|
|
356
|
+
if (offset + data.length <= buffer.length) {
|
|
357
|
+
buffer.set(data, offset);
|
|
367
358
|
} else {
|
|
368
359
|
// TODO(dmaretskyi): Optimize re-allocations.
|
|
369
360
|
const newCache = new Uint8Array(offset + data.length);
|
|
370
|
-
newCache.set(
|
|
361
|
+
newCache.set(buffer);
|
|
371
362
|
newCache.set(data, offset);
|
|
372
363
|
this._buffer = newCache;
|
|
373
364
|
}
|
|
@@ -376,67 +367,86 @@ export class WebFile extends EventEmitter implements File {
|
|
|
376
367
|
}
|
|
377
368
|
|
|
378
369
|
async del(offset: number, size: number) {
|
|
370
|
+
this.assertNotDestroyed('Del');
|
|
371
|
+
|
|
379
372
|
this._operations.inc();
|
|
380
373
|
|
|
381
|
-
if (offset < 0 || size
|
|
374
|
+
if (offset < 0 || size <= 0) {
|
|
382
375
|
return;
|
|
383
376
|
}
|
|
384
377
|
|
|
385
|
-
|
|
386
|
-
await this._loadBufferGuarded();
|
|
387
|
-
invariant(this._buffer);
|
|
388
|
-
}
|
|
378
|
+
const buffer = await this.requireBuffer();
|
|
389
379
|
|
|
390
380
|
let leftoverSize = 0;
|
|
391
|
-
if (offset + size <
|
|
392
|
-
leftoverSize =
|
|
393
|
-
|
|
381
|
+
if (offset + size < buffer.length) {
|
|
382
|
+
leftoverSize = buffer.length - (offset + size);
|
|
383
|
+
buffer.set(buffer.slice(offset + size, offset + size + leftoverSize), offset);
|
|
394
384
|
}
|
|
395
385
|
|
|
396
|
-
this._buffer =
|
|
386
|
+
this._buffer = buffer.slice(0, offset + leftoverSize);
|
|
397
387
|
|
|
398
388
|
this._flushLater();
|
|
399
389
|
}
|
|
400
390
|
|
|
401
391
|
async stat() {
|
|
392
|
+
this.assertNotDestroyed('Truncate');
|
|
393
|
+
|
|
402
394
|
this._operations.inc();
|
|
403
395
|
|
|
404
396
|
// 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.
|
|
405
|
-
|
|
406
|
-
await this._loadBufferGuarded();
|
|
407
|
-
invariant(this._buffer);
|
|
408
|
-
}
|
|
397
|
+
const buffer = await this.requireBuffer();
|
|
409
398
|
|
|
410
399
|
return {
|
|
411
|
-
size:
|
|
400
|
+
size: buffer.length,
|
|
412
401
|
};
|
|
413
402
|
}
|
|
414
403
|
|
|
415
404
|
async truncate(offset: number) {
|
|
416
|
-
this.
|
|
405
|
+
this.assertNotDestroyed('Truncate');
|
|
417
406
|
|
|
418
|
-
|
|
419
|
-
await this._loadBufferGuarded();
|
|
420
|
-
invariant(this._buffer);
|
|
421
|
-
}
|
|
407
|
+
this._operations.inc();
|
|
422
408
|
|
|
423
|
-
this._buffer = this.
|
|
409
|
+
this._buffer = (await this.requireBuffer()).slice(0, offset);
|
|
424
410
|
|
|
425
411
|
this._flushLater();
|
|
426
412
|
}
|
|
427
413
|
|
|
428
414
|
async flush() {
|
|
415
|
+
this.assertNotDestroyed('Flush');
|
|
416
|
+
|
|
429
417
|
await this._flushNow();
|
|
430
418
|
}
|
|
431
419
|
|
|
420
|
+
/**
|
|
421
|
+
* It's best to avoid using this method as it doesn't really close a file.
|
|
422
|
+
* We could update the _opened flag and add a guard like for destroyed, but this would break
|
|
423
|
+
* the FileSystemFileHandle sharing required for browser tests to run, where writes are
|
|
424
|
+
* not immediately visible if using different file handles.
|
|
425
|
+
*/
|
|
432
426
|
async close(): Promise<void> {
|
|
433
427
|
await this._flushNow();
|
|
434
428
|
}
|
|
435
429
|
|
|
436
430
|
@synchronized
|
|
437
431
|
async destroy() {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
432
|
+
if (!this.destroyed) {
|
|
433
|
+
this.destroyed = true;
|
|
434
|
+
await this._flushNow();
|
|
435
|
+
return await this._destroy();
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private assertNotDestroyed(operation: string) {
|
|
440
|
+
if (this.destroyed) {
|
|
441
|
+
throw new Error(`${operation} on a destroyed or closed file`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private async requireBuffer(): Promise<Uint8Array> {
|
|
446
|
+
if (!this._buffer) {
|
|
447
|
+
await this._loadBufferGuarded();
|
|
448
|
+
invariant(this._buffer);
|
|
449
|
+
}
|
|
450
|
+
return this._buffer;
|
|
441
451
|
}
|
|
442
452
|
}
|
|
@@ -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
|
};
|