@dxos/random-access-storage 0.4.3-main.dda98d3 → 0.4.3-main.e5bbcda
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 +107 -109
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +1 -12
- 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 +1 -15
- package/dist/types/src/browser/web-fs.d.ts.map +1 -1
- package/dist/types/src/common/memory-storage.d.ts +1 -2
- 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 +78 -88
- package/src/common/memory-storage.ts +1 -14
- package/src/node/node-storage.ts +2 -2
- package/src/testing/storage.blueprint-test.ts +7 -96
package/src/browser/web-fs.ts
CHANGED
|
@@ -13,12 +13,18 @@ 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>();
|
|
16
24
|
/**
|
|
17
25
|
* Web file systems.
|
|
18
26
|
*/
|
|
19
27
|
export class WebFS implements Storage {
|
|
20
|
-
private readonly _files = new Map<string, WebFile>();
|
|
21
|
-
|
|
22
28
|
readonly type = StorageType.WEBFS;
|
|
23
29
|
|
|
24
30
|
protected _root?: FileSystemDirectoryHandle;
|
|
@@ -26,13 +32,13 @@ export class WebFS implements Storage {
|
|
|
26
32
|
constructor(public readonly path: string) {}
|
|
27
33
|
|
|
28
34
|
public get size() {
|
|
29
|
-
return
|
|
35
|
+
return files.size;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
private _getFiles(path: string): Map<string, WebFile> {
|
|
33
39
|
const fullName = this._getFullFilename(this.path, path);
|
|
34
40
|
return new Map(
|
|
35
|
-
[...
|
|
41
|
+
[...files.entries()].filter(([path, file]) => {
|
|
36
42
|
return path.includes(fullName) && !file.destroyed;
|
|
37
43
|
}),
|
|
38
44
|
);
|
|
@@ -82,19 +88,19 @@ export class WebFS implements Storage {
|
|
|
82
88
|
getOrCreateFile: (...args) => this.getOrCreateFile(...args),
|
|
83
89
|
remove: () => this._delete(sub),
|
|
84
90
|
onFlush: async () => {
|
|
85
|
-
await Promise.all(Array.from(this._getFiles(sub)).map(([
|
|
91
|
+
await Promise.all(Array.from(this._getFiles(sub)).map(([path, file]) => file.flush()));
|
|
86
92
|
},
|
|
87
93
|
});
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
getOrCreateFile(path: string, filename: string, opts?: any): File {
|
|
91
97
|
const fullName = this._getFullFilename(path, filename);
|
|
92
|
-
const existingFile =
|
|
98
|
+
const existingFile = files.get(fullName);
|
|
93
99
|
if (existingFile) {
|
|
94
100
|
return existingFile;
|
|
95
101
|
}
|
|
96
102
|
const file = this._createFile(fullName);
|
|
97
|
-
|
|
103
|
+
files.set(fullName, file);
|
|
98
104
|
return file;
|
|
99
105
|
}
|
|
100
106
|
|
|
@@ -103,7 +109,7 @@ export class WebFS implements Storage {
|
|
|
103
109
|
fileName: fullName,
|
|
104
110
|
file: this._initialize().then((root) => root.getFileHandle(fullName, { create: true })),
|
|
105
111
|
destroy: async () => {
|
|
106
|
-
|
|
112
|
+
files.delete(fullName);
|
|
107
113
|
const root = await this._initialize();
|
|
108
114
|
return root.removeEntry(fullName);
|
|
109
115
|
},
|
|
@@ -114,7 +120,7 @@ export class WebFS implements Storage {
|
|
|
114
120
|
await Promise.all(
|
|
115
121
|
Array.from(this._getFiles(path)).map(async ([path, file]) => {
|
|
116
122
|
await file.destroy().catch((err: any) => log.warn(err));
|
|
117
|
-
|
|
123
|
+
files.delete(path);
|
|
118
124
|
}),
|
|
119
125
|
);
|
|
120
126
|
}
|
|
@@ -122,28 +128,22 @@ export class WebFS implements Storage {
|
|
|
122
128
|
async reset() {
|
|
123
129
|
await this._initialize();
|
|
124
130
|
for await (const filename of await (this._root as any).keys()) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
);
|
|
128
|
-
this._files.delete(filename);
|
|
131
|
+
files.delete(filename);
|
|
132
|
+
await this._root!.removeEntry(filename, { recursive: true }).catch((err: any) => log.warn(err));
|
|
129
133
|
}
|
|
130
134
|
this._root = undefined;
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
async close() {
|
|
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
|
-
);
|
|
138
|
+
await Promise.all(Array.from(files.values()).map((file) => file.close()));
|
|
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).split('/').join('_');
|
|
145
145
|
} else {
|
|
146
|
-
return path.
|
|
146
|
+
return path.split('/').join('_');
|
|
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) => f.size);
|
|
161
|
+
used += await (entry as FileSystemFileHandle).getFile().then((f) => (used += 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
|
-
readonly
|
|
185
|
+
private readonly _fileName: string;
|
|
186
186
|
|
|
187
187
|
private readonly _fileHandle: Promise<FileSystemFileHandle>;
|
|
188
188
|
private readonly _destroy: () => Promise<void>;
|
|
@@ -195,12 +195,8 @@ export class WebFile extends EventEmitter implements File {
|
|
|
195
195
|
private _loadBufferPromise: Promise<void> | null = null;
|
|
196
196
|
|
|
197
197
|
private _flushScheduled = false;
|
|
198
|
-
|
|
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;
|
|
198
|
+
|
|
199
|
+
private _flushPromise: Promise<void> | null = null;
|
|
204
200
|
|
|
205
201
|
//
|
|
206
202
|
// Metrics
|
|
@@ -239,7 +235,7 @@ export class WebFile extends EventEmitter implements File {
|
|
|
239
235
|
destroy: () => Promise<void>;
|
|
240
236
|
}) {
|
|
241
237
|
super();
|
|
242
|
-
this.
|
|
238
|
+
this._fileName = fileName;
|
|
243
239
|
this._fileHandle = file;
|
|
244
240
|
this._destroy = destroy;
|
|
245
241
|
|
|
@@ -288,11 +284,10 @@ export class WebFile extends EventEmitter implements File {
|
|
|
288
284
|
}
|
|
289
285
|
|
|
290
286
|
// Do not call directly, use _flushLater or _flushNow.
|
|
291
|
-
private async _flushCache(
|
|
292
|
-
if (this.destroyed
|
|
287
|
+
private async _flushCache() {
|
|
288
|
+
if (this.destroyed) {
|
|
293
289
|
return;
|
|
294
290
|
}
|
|
295
|
-
this._flushSequence = sequence + 1;
|
|
296
291
|
|
|
297
292
|
this._flushes.inc();
|
|
298
293
|
|
|
@@ -310,55 +305,69 @@ export class WebFile extends EventEmitter implements File {
|
|
|
310
305
|
return;
|
|
311
306
|
}
|
|
312
307
|
|
|
313
|
-
const sequence = this._flushSequence;
|
|
314
308
|
setTimeout(async () => {
|
|
315
309
|
// Making sure only one flush can run at a time.
|
|
310
|
+
const promiseBefore = this._flushPromise;
|
|
316
311
|
await this._flushPromise;
|
|
317
312
|
this._flushScheduled = false;
|
|
318
|
-
|
|
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));
|
|
319
320
|
});
|
|
320
321
|
|
|
321
322
|
this._flushScheduled = true;
|
|
322
323
|
}
|
|
323
324
|
|
|
324
325
|
private async _flushNow() {
|
|
325
|
-
|
|
326
|
-
|
|
326
|
+
this._flushPromise = (this._flushPromise ?? Promise.resolve())
|
|
327
|
+
.then(() => this._flushCache())
|
|
328
|
+
.catch((err) => log.warn(err));
|
|
329
|
+
|
|
327
330
|
await this._flushPromise;
|
|
328
331
|
}
|
|
329
332
|
|
|
330
333
|
async read(offset: number, size: number) {
|
|
331
|
-
this.
|
|
334
|
+
if (this.destroyed) {
|
|
335
|
+
throw new Error('Read of a destroyed file');
|
|
336
|
+
}
|
|
332
337
|
|
|
333
338
|
this._operations.inc();
|
|
334
339
|
this._reads.inc();
|
|
335
340
|
this._readBytes.inc(size);
|
|
336
341
|
|
|
337
|
-
|
|
342
|
+
if (!this._buffer) {
|
|
343
|
+
await this._loadBufferGuarded();
|
|
344
|
+
invariant(this._buffer);
|
|
345
|
+
}
|
|
338
346
|
|
|
339
|
-
if (offset + size >
|
|
347
|
+
if (offset + size > this._buffer.length) {
|
|
340
348
|
throw new Error('Read out of bounds');
|
|
341
349
|
}
|
|
342
350
|
|
|
343
351
|
// Copy data into a new buffer.
|
|
344
|
-
return Buffer.from(
|
|
352
|
+
return Buffer.from(this._buffer.slice(offset, offset + size));
|
|
345
353
|
}
|
|
346
354
|
|
|
347
355
|
async write(offset: number, data: Buffer) {
|
|
348
|
-
this.assertNotDestroyed('Write');
|
|
349
|
-
|
|
350
356
|
this._operations.inc();
|
|
351
357
|
this._writes.inc();
|
|
352
358
|
this._writeBytes.inc(data.length);
|
|
353
359
|
|
|
354
|
-
|
|
360
|
+
if (!this._buffer) {
|
|
361
|
+
await this._loadBufferGuarded();
|
|
362
|
+
invariant(this._buffer);
|
|
363
|
+
}
|
|
355
364
|
|
|
356
|
-
if (offset + data.length <=
|
|
357
|
-
|
|
365
|
+
if (offset + data.length <= this._buffer.length) {
|
|
366
|
+
this._buffer.set(data, offset);
|
|
358
367
|
} else {
|
|
359
368
|
// TODO(dmaretskyi): Optimize re-allocations.
|
|
360
369
|
const newCache = new Uint8Array(offset + data.length);
|
|
361
|
-
newCache.set(
|
|
370
|
+
newCache.set(this._buffer);
|
|
362
371
|
newCache.set(data, offset);
|
|
363
372
|
this._buffer = newCache;
|
|
364
373
|
}
|
|
@@ -367,86 +376,67 @@ export class WebFile extends EventEmitter implements File {
|
|
|
367
376
|
}
|
|
368
377
|
|
|
369
378
|
async del(offset: number, size: number) {
|
|
370
|
-
this.assertNotDestroyed('Del');
|
|
371
|
-
|
|
372
379
|
this._operations.inc();
|
|
373
380
|
|
|
374
|
-
if (offset < 0 || size
|
|
381
|
+
if (offset < 0 || size < 0) {
|
|
375
382
|
return;
|
|
376
383
|
}
|
|
377
384
|
|
|
378
|
-
|
|
385
|
+
if (!this._buffer) {
|
|
386
|
+
await this._loadBufferGuarded();
|
|
387
|
+
invariant(this._buffer);
|
|
388
|
+
}
|
|
379
389
|
|
|
380
390
|
let leftoverSize = 0;
|
|
381
|
-
if (offset + size <
|
|
382
|
-
leftoverSize =
|
|
383
|
-
|
|
391
|
+
if (offset + size < this._buffer.length) {
|
|
392
|
+
leftoverSize = this._buffer.length - (offset + size);
|
|
393
|
+
this._buffer.set(this._buffer.slice(offset + size, offset + size + leftoverSize), offset);
|
|
384
394
|
}
|
|
385
395
|
|
|
386
|
-
this._buffer =
|
|
396
|
+
this._buffer = this._buffer.slice(0, offset + leftoverSize);
|
|
387
397
|
|
|
388
398
|
this._flushLater();
|
|
389
399
|
}
|
|
390
400
|
|
|
391
401
|
async stat() {
|
|
392
|
-
this.assertNotDestroyed('Truncate');
|
|
393
|
-
|
|
394
402
|
this._operations.inc();
|
|
395
403
|
|
|
396
404
|
// 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.
|
|
397
|
-
|
|
405
|
+
if (!this._buffer) {
|
|
406
|
+
await this._loadBufferGuarded();
|
|
407
|
+
invariant(this._buffer);
|
|
408
|
+
}
|
|
398
409
|
|
|
399
410
|
return {
|
|
400
|
-
size:
|
|
411
|
+
size: this._buffer.length,
|
|
401
412
|
};
|
|
402
413
|
}
|
|
403
414
|
|
|
404
415
|
async truncate(offset: number) {
|
|
405
|
-
this.assertNotDestroyed('Truncate');
|
|
406
|
-
|
|
407
416
|
this._operations.inc();
|
|
408
417
|
|
|
409
|
-
|
|
418
|
+
if (!this._buffer) {
|
|
419
|
+
await this._loadBufferGuarded();
|
|
420
|
+
invariant(this._buffer);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this._buffer = this._buffer.slice(0, offset);
|
|
410
424
|
|
|
411
425
|
this._flushLater();
|
|
412
426
|
}
|
|
413
427
|
|
|
414
428
|
async flush() {
|
|
415
|
-
this.assertNotDestroyed('Flush');
|
|
416
|
-
|
|
417
429
|
await this._flushNow();
|
|
418
430
|
}
|
|
419
431
|
|
|
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
|
-
*/
|
|
426
432
|
async close(): Promise<void> {
|
|
427
433
|
await this._flushNow();
|
|
428
434
|
}
|
|
429
435
|
|
|
430
436
|
@synchronized
|
|
431
437
|
async destroy() {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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;
|
|
438
|
+
await this._flushNow();
|
|
439
|
+
this.destroyed = true;
|
|
440
|
+
return await this._destroy();
|
|
451
441
|
}
|
|
452
442
|
}
|
|
@@ -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 {
|
|
11
|
+
import { StorageType } from './storage';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Storage interface implementation for RAM.
|
|
@@ -42,17 +42,4 @@ 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
|
-
}
|
|
58
45
|
}
|
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
|
+
|
|
72
74
|
if (pathStats.isDirectory()) {
|
|
73
75
|
const entries = await readdir(path);
|
|
74
76
|
await Promise.all(entries.map((entry) => recurse(join(path, entry))));
|
|
75
|
-
} else {
|
|
76
|
-
used += pathStats.size;
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
79
|
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
// Copyright 2021 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import { describe, expect, test } from 'vitest';
|
|
5
|
+
import { expect, describe, test } from 'vitest';
|
|
7
6
|
|
|
8
7
|
import { asyncTimeout } from '@dxos/async';
|
|
9
8
|
|
|
10
|
-
import { type File, type Storage
|
|
9
|
+
import { StorageType, type File, type Storage } from '../common';
|
|
11
10
|
|
|
12
|
-
export const randomText = () =>
|
|
11
|
+
export const randomText = () => Math.random().toString(36).substring(2);
|
|
13
12
|
|
|
14
13
|
export const storageTests = (testGroupName: StorageType, createStorage: () => Storage) => {
|
|
15
14
|
const writeAndCheck = async (file: File, data: Buffer, offset = 0) => {
|
|
@@ -52,7 +51,7 @@ export const storageTests = (testGroupName: StorageType, createStorage: () => St
|
|
|
52
51
|
const directory = storage.createDirectory(directoryName);
|
|
53
52
|
|
|
54
53
|
const count = 10;
|
|
55
|
-
const files = [...Array(count)].map(() => directory.getOrCreateFile(randomText()));
|
|
54
|
+
const files = [...Array(count)].map((name) => directory.getOrCreateFile(randomText()));
|
|
56
55
|
|
|
57
56
|
{
|
|
58
57
|
// Create and check files amount.
|
|
@@ -174,7 +173,7 @@ export const storageTests = (testGroupName: StorageType, createStorage: () => St
|
|
|
174
173
|
const dir1 = storage.createDirectory('dir1');
|
|
175
174
|
const dir2 = storage.createDirectory('dir2');
|
|
176
175
|
|
|
177
|
-
const fileName =
|
|
176
|
+
const fileName = 'file';
|
|
178
177
|
const buffer1 = Buffer.from(randomText());
|
|
179
178
|
const buffer2 = Buffer.from(randomText());
|
|
180
179
|
|
|
@@ -196,7 +195,7 @@ export const storageTests = (testGroupName: StorageType, createStorage: () => St
|
|
|
196
195
|
const dir = storage.createDirectory('directory');
|
|
197
196
|
const subDir = dir.createDirectory('subDirectory');
|
|
198
197
|
|
|
199
|
-
const file = subDir.getOrCreateFile(
|
|
198
|
+
const file = subDir.getOrCreateFile('file');
|
|
200
199
|
const buffer = Buffer.from(randomText());
|
|
201
200
|
await file.write(0, buffer);
|
|
202
201
|
|
|
@@ -205,82 +204,10 @@ export const storageTests = (testGroupName: StorageType, createStorage: () => St
|
|
|
205
204
|
await file.close();
|
|
206
205
|
});
|
|
207
206
|
|
|
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
|
-
|
|
280
207
|
test('delete directory', async () => {
|
|
281
208
|
const storage = createStorage();
|
|
282
209
|
const directory = storage.createDirectory();
|
|
283
|
-
const file = directory.getOrCreateFile(
|
|
210
|
+
const file = directory.getOrCreateFile('file');
|
|
284
211
|
|
|
285
212
|
const buffer = Buffer.from(randomText());
|
|
286
213
|
await writeAndCheck(file, buffer);
|
|
@@ -437,21 +364,5 @@ export const storageTests = (testGroupName: StorageType, createStorage: () => St
|
|
|
437
364
|
const entries = await directory.list();
|
|
438
365
|
expect(entries).toEqual(expect.arrayContaining(FILES));
|
|
439
366
|
});
|
|
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
|
-
});
|
|
456
367
|
});
|
|
457
368
|
};
|