@zenfs/core 0.1.0 → 0.2.0
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/ApiError.d.ts +51 -14
- package/dist/ApiError.js +60 -34
- package/dist/FileIndex.d.ts +32 -35
- package/dist/FileIndex.js +93 -109
- package/dist/backends/AsyncMirror.d.ts +42 -43
- package/dist/backends/AsyncMirror.js +146 -133
- package/dist/backends/AsyncStore.d.ts +29 -28
- package/dist/backends/AsyncStore.js +139 -189
- package/dist/backends/InMemory.d.ts +16 -13
- package/dist/backends/InMemory.js +29 -14
- package/dist/backends/Locked.d.ts +8 -28
- package/dist/backends/Locked.js +44 -148
- package/dist/backends/OverlayFS.d.ts +26 -34
- package/dist/backends/OverlayFS.js +208 -371
- package/dist/backends/SyncStore.d.ts +54 -72
- package/dist/backends/SyncStore.js +159 -161
- package/dist/backends/backend.d.ts +45 -29
- package/dist/backends/backend.js +83 -13
- package/dist/backends/index.d.ts +6 -7
- package/dist/backends/index.js +5 -6
- package/dist/browser.min.js +5 -7
- package/dist/browser.min.js.map +4 -4
- package/dist/emulation/callbacks.d.ts +36 -67
- package/dist/emulation/callbacks.js +90 -46
- package/dist/emulation/constants.js +1 -1
- package/dist/emulation/promises.d.ts +228 -129
- package/dist/emulation/promises.js +414 -172
- package/dist/emulation/shared.d.ts +10 -10
- package/dist/emulation/shared.js +18 -20
- package/dist/emulation/sync.d.ts +25 -25
- package/dist/emulation/sync.js +187 -73
- package/dist/file.d.ts +166 -170
- package/dist/file.js +199 -218
- package/dist/filesystem.d.ts +68 -241
- package/dist/filesystem.js +59 -383
- package/dist/index.d.ts +7 -44
- package/dist/index.js +13 -52
- package/dist/inode.d.ts +37 -28
- package/dist/inode.js +123 -65
- package/dist/stats.d.ts +21 -19
- package/dist/stats.js +35 -56
- package/dist/utils.d.ts +26 -9
- package/dist/utils.js +73 -102
- package/package.json +4 -3
|
@@ -1,104 +1,50 @@
|
|
|
1
1
|
import { dirname, basename, join, resolve } from '../emulation/path.js';
|
|
2
2
|
import { ApiError, ErrorCode } from '../ApiError.js';
|
|
3
3
|
import { W_OK, R_OK } from '../emulation/constants.js';
|
|
4
|
-
import { PreloadFile
|
|
5
|
-
import {
|
|
6
|
-
import Inode from '../inode.js';
|
|
4
|
+
import { PreloadFile } from '../file.js';
|
|
5
|
+
import { AsyncFileSystem } from '../filesystem.js';
|
|
6
|
+
import { randomIno, Inode } from '../inode.js';
|
|
7
7
|
import { FileType } from '../stats.js';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
this.prev = null;
|
|
14
|
-
this.next = null;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
// Adapted from https://chrisrng.svbtle.com/lru-cache-in-javascript
|
|
8
|
+
import { encode, decodeDirListing, encodeDirListing } from '../utils.js';
|
|
9
|
+
import { rootIno } from '../inode.js';
|
|
10
|
+
/**
|
|
11
|
+
* Last Recently Used cache
|
|
12
|
+
*/
|
|
18
13
|
class LRUCache {
|
|
19
14
|
constructor(limit) {
|
|
20
15
|
this.limit = limit;
|
|
21
|
-
this.
|
|
22
|
-
this.map = {};
|
|
23
|
-
this.head = null;
|
|
24
|
-
this.tail = null;
|
|
16
|
+
this.cache = [];
|
|
25
17
|
}
|
|
26
|
-
/**
|
|
27
|
-
* Change or add a new value in the cache
|
|
28
|
-
* We overwrite the entry if it already exists
|
|
29
|
-
*/
|
|
30
18
|
set(key, value) {
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
33
|
-
this.
|
|
34
|
-
this.remove(node.key);
|
|
19
|
+
const existingIndex = this.cache.findIndex(node => node.key === key);
|
|
20
|
+
if (existingIndex != -1) {
|
|
21
|
+
this.cache.splice(existingIndex, 1);
|
|
35
22
|
}
|
|
36
|
-
else {
|
|
37
|
-
|
|
38
|
-
delete this.map[this.tail.key];
|
|
39
|
-
this.size--;
|
|
40
|
-
this.tail = this.tail.prev;
|
|
41
|
-
this.tail.next = null;
|
|
42
|
-
}
|
|
23
|
+
else if (this.cache.length >= this.limit) {
|
|
24
|
+
this.cache.shift();
|
|
43
25
|
}
|
|
44
|
-
this.
|
|
26
|
+
this.cache.push({ key, value });
|
|
45
27
|
}
|
|
46
|
-
/* Retrieve a single entry from the cache */
|
|
47
28
|
get(key) {
|
|
48
|
-
|
|
49
|
-
const value = this.map[key].value;
|
|
50
|
-
const node = new LRUNode(key, value);
|
|
51
|
-
this.remove(key);
|
|
52
|
-
this.setHead(node);
|
|
53
|
-
return value;
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/* Remove a single entry from the cache */
|
|
60
|
-
remove(key) {
|
|
61
|
-
const node = this.map[key];
|
|
29
|
+
const node = this.cache.find(n => n.key === key);
|
|
62
30
|
if (!node) {
|
|
63
31
|
return;
|
|
64
32
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
else {
|
|
69
|
-
this.head = node.next;
|
|
70
|
-
}
|
|
71
|
-
if (node.next !== null) {
|
|
72
|
-
node.next.prev = node.prev;
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
this.tail = node.prev;
|
|
76
|
-
}
|
|
77
|
-
delete this.map[key];
|
|
78
|
-
this.size--;
|
|
79
|
-
}
|
|
80
|
-
/* Resets the entire cache - Argument limit is optional to be reset */
|
|
81
|
-
removeAll() {
|
|
82
|
-
this.size = 0;
|
|
83
|
-
this.map = {};
|
|
84
|
-
this.head = null;
|
|
85
|
-
this.tail = null;
|
|
33
|
+
// Move the accessed item to the end of the cache (most recently used)
|
|
34
|
+
this.set(key, node.value);
|
|
35
|
+
return node.value;
|
|
86
36
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.head.prev = node;
|
|
92
|
-
}
|
|
93
|
-
this.head = node;
|
|
94
|
-
if (this.tail === null) {
|
|
95
|
-
this.tail = node;
|
|
37
|
+
remove(key) {
|
|
38
|
+
const index = this.cache.findIndex(node => node.key === key);
|
|
39
|
+
if (index !== -1) {
|
|
40
|
+
this.cache.splice(index, 1);
|
|
96
41
|
}
|
|
97
|
-
|
|
98
|
-
|
|
42
|
+
}
|
|
43
|
+
reset() {
|
|
44
|
+
this.cache = [];
|
|
99
45
|
}
|
|
100
46
|
}
|
|
101
|
-
export class
|
|
47
|
+
export class AsyncFile extends PreloadFile {
|
|
102
48
|
constructor(_fs, _path, _flag, _stat, contents) {
|
|
103
49
|
super(_fs, _path, _flag, _stat, contents);
|
|
104
50
|
}
|
|
@@ -106,24 +52,29 @@ export class AsyncKeyValueFile extends PreloadFile {
|
|
|
106
52
|
if (!this.isDirty()) {
|
|
107
53
|
return;
|
|
108
54
|
}
|
|
109
|
-
await this.
|
|
55
|
+
await this.fs.sync(this.path, this._buffer, this.stats);
|
|
110
56
|
this.resetDirty();
|
|
111
57
|
}
|
|
58
|
+
syncSync() {
|
|
59
|
+
throw new ApiError(ErrorCode.ENOTSUP);
|
|
60
|
+
}
|
|
112
61
|
async close() {
|
|
113
62
|
this.sync();
|
|
114
63
|
}
|
|
64
|
+
closeSync() {
|
|
65
|
+
throw new ApiError(ErrorCode.ENOTSUP);
|
|
66
|
+
}
|
|
115
67
|
}
|
|
116
68
|
/**
|
|
117
69
|
* An "Asynchronous key-value file system". Stores data to/retrieves data from
|
|
118
70
|
* an underlying asynchronous key-value store.
|
|
119
71
|
*/
|
|
120
|
-
export class
|
|
121
|
-
|
|
122
|
-
return
|
|
72
|
+
export class AsyncStoreFileSystem extends AsyncFileSystem {
|
|
73
|
+
ready() {
|
|
74
|
+
return this._ready;
|
|
123
75
|
}
|
|
124
76
|
constructor(cacheSize) {
|
|
125
77
|
super();
|
|
126
|
-
this._cache = null;
|
|
127
78
|
if (cacheSize > 0) {
|
|
128
79
|
this._cache = new LRUCache(cacheSize);
|
|
129
80
|
}
|
|
@@ -137,42 +88,17 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
137
88
|
// INVARIANT: Ensure that the root exists.
|
|
138
89
|
await this.makeRootDirectory();
|
|
139
90
|
}
|
|
140
|
-
getName() {
|
|
141
|
-
return this.store.name();
|
|
142
|
-
}
|
|
143
|
-
isReadOnly() {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
supportsSymlinks() {
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
supportsProps() {
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
supportsSynch() {
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
91
|
/**
|
|
156
92
|
* Delete all contents stored in the file system.
|
|
157
93
|
*/
|
|
158
94
|
async empty() {
|
|
159
95
|
if (this._cache) {
|
|
160
|
-
this._cache.
|
|
96
|
+
this._cache.reset();
|
|
161
97
|
}
|
|
162
98
|
await this.store.clear();
|
|
163
99
|
// INVARIANT: Root always exists.
|
|
164
100
|
await this.makeRootDirectory();
|
|
165
101
|
}
|
|
166
|
-
async access(p, mode, cred) {
|
|
167
|
-
const tx = this.store.beginTransaction('readonly');
|
|
168
|
-
const inode = await this.findINode(tx, p);
|
|
169
|
-
if (!inode) {
|
|
170
|
-
throw ApiError.ENOENT(p);
|
|
171
|
-
}
|
|
172
|
-
if (!inode.toStats().hasAccess(mode, cred)) {
|
|
173
|
-
throw ApiError.EACCES(p);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
102
|
/**
|
|
177
103
|
* @todo Make rename compatible with the cache.
|
|
178
104
|
*/
|
|
@@ -181,12 +107,12 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
181
107
|
if (this._cache) {
|
|
182
108
|
// Clear and disable cache during renaming process.
|
|
183
109
|
this._cache = null;
|
|
184
|
-
c.
|
|
110
|
+
c.reset();
|
|
185
111
|
}
|
|
186
112
|
try {
|
|
187
113
|
const tx = this.store.beginTransaction('readwrite'), oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
|
|
188
114
|
// Remove oldPath from parent's directory listing.
|
|
189
|
-
oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx,
|
|
115
|
+
oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldDirNode, oldParent);
|
|
190
116
|
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
|
|
191
117
|
throw ApiError.EACCES(oldPath);
|
|
192
118
|
}
|
|
@@ -212,15 +138,15 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
212
138
|
}
|
|
213
139
|
else {
|
|
214
140
|
newDirNode = await this.findINode(tx, newParent);
|
|
215
|
-
newDirList = await this.getDirListing(tx,
|
|
141
|
+
newDirList = await this.getDirListing(tx, newDirNode, newParent);
|
|
216
142
|
}
|
|
217
143
|
if (newDirList[newName]) {
|
|
218
144
|
// If it's a file, delete it.
|
|
219
|
-
const newNameNode = await this.getINode(tx,
|
|
220
|
-
if (newNameNode.isFile()) {
|
|
145
|
+
const newNameNode = await this.getINode(tx, newDirList[newName], newPath);
|
|
146
|
+
if (newNameNode.toStats().isFile()) {
|
|
221
147
|
try {
|
|
222
|
-
await tx.
|
|
223
|
-
await tx.
|
|
148
|
+
await tx.remove(newNameNode.ino);
|
|
149
|
+
await tx.remove(newDirList[newName]);
|
|
224
150
|
}
|
|
225
151
|
catch (e) {
|
|
226
152
|
await tx.abort();
|
|
@@ -235,8 +161,8 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
235
161
|
newDirList[newName] = nodeId;
|
|
236
162
|
// Commit the two changed directory listings.
|
|
237
163
|
try {
|
|
238
|
-
await tx.put(oldDirNode.
|
|
239
|
-
await tx.put(newDirNode.
|
|
164
|
+
await tx.put(oldDirNode.ino, encodeDirListing(oldDirList), true);
|
|
165
|
+
await tx.put(newDirNode.ino, encodeDirListing(newDirList), true);
|
|
240
166
|
}
|
|
241
167
|
catch (e) {
|
|
242
168
|
await tx.abort();
|
|
@@ -253,6 +179,9 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
253
179
|
async stat(p, cred) {
|
|
254
180
|
const tx = this.store.beginTransaction('readonly');
|
|
255
181
|
const inode = await this.findINode(tx, p);
|
|
182
|
+
if (!inode) {
|
|
183
|
+
throw ApiError.ENOENT(p);
|
|
184
|
+
}
|
|
256
185
|
const stats = inode.toStats();
|
|
257
186
|
if (!stats.hasAccess(R_OK, cred)) {
|
|
258
187
|
throw ApiError.EACCES(p);
|
|
@@ -262,17 +191,17 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
262
191
|
async createFile(p, flag, mode, cred) {
|
|
263
192
|
const tx = this.store.beginTransaction('readwrite'), data = new Uint8Array(0), newFile = await this.commitNewFile(tx, p, FileType.FILE, mode, cred, data);
|
|
264
193
|
// Open the file.
|
|
265
|
-
return new
|
|
194
|
+
return new AsyncFile(this, p, flag, newFile.toStats(), data);
|
|
266
195
|
}
|
|
267
196
|
async openFile(p, flag, cred) {
|
|
268
|
-
const tx = this.store.beginTransaction('readonly'), node = await this.findINode(tx, p), data = await tx.get(node.
|
|
269
|
-
if (!node.toStats().hasAccess(flag.
|
|
197
|
+
const tx = this.store.beginTransaction('readonly'), node = await this.findINode(tx, p), data = await tx.get(node.ino);
|
|
198
|
+
if (!node.toStats().hasAccess(flag.mode, cred)) {
|
|
270
199
|
throw ApiError.EACCES(p);
|
|
271
200
|
}
|
|
272
|
-
if (data
|
|
201
|
+
if (!data) {
|
|
273
202
|
throw ApiError.ENOENT(p);
|
|
274
203
|
}
|
|
275
|
-
return new
|
|
204
|
+
return new AsyncFile(this, p, flag, node.toStats(), data);
|
|
276
205
|
}
|
|
277
206
|
async unlink(p, cred) {
|
|
278
207
|
return this.removeEntry(p, false, cred);
|
|
@@ -295,28 +224,22 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
295
224
|
if (!node.toStats().hasAccess(R_OK, cred)) {
|
|
296
225
|
throw ApiError.EACCES(p);
|
|
297
226
|
}
|
|
298
|
-
return Object.keys(await this.getDirListing(tx,
|
|
299
|
-
}
|
|
300
|
-
async chmod(p, mode, cred) {
|
|
301
|
-
const fd = await this.openFile(p, FileFlag.getFileFlag('r+'), cred);
|
|
302
|
-
await fd.chmod(mode);
|
|
227
|
+
return Object.keys(await this.getDirListing(tx, node, p));
|
|
303
228
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
async
|
|
309
|
-
// @todo Ensure mtime updates properly, and use that to determine if a data
|
|
310
|
-
// update is required.
|
|
229
|
+
/**
|
|
230
|
+
* Updated the inode and data node at the given path
|
|
231
|
+
* @todo Ensure mtime updates properly, and use that to determine if a data update is required.
|
|
232
|
+
*/
|
|
233
|
+
async sync(p, data, stats) {
|
|
311
234
|
const tx = this.store.beginTransaction('readwrite'),
|
|
312
235
|
// We use the _findInode helper because we actually need the INode id.
|
|
313
|
-
fileInodeId = await this._findINode(tx, dirname(p), basename(p)), fileInode = await this.getINode(tx,
|
|
236
|
+
fileInodeId = await this._findINode(tx, dirname(p), basename(p)), fileInode = await this.getINode(tx, fileInodeId, p), inodeChanged = fileInode.update(stats);
|
|
314
237
|
try {
|
|
315
238
|
// Sync data.
|
|
316
|
-
await tx.put(fileInode.
|
|
239
|
+
await tx.put(fileInode.ino, data, true);
|
|
317
240
|
// Sync metadata.
|
|
318
241
|
if (inodeChanged) {
|
|
319
|
-
await tx.put(fileInodeId, fileInode.
|
|
242
|
+
await tx.put(fileInodeId, fileInode.data, true);
|
|
320
243
|
}
|
|
321
244
|
}
|
|
322
245
|
catch (e) {
|
|
@@ -325,20 +248,45 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
325
248
|
}
|
|
326
249
|
await tx.commit();
|
|
327
250
|
}
|
|
251
|
+
async link(existing, newpath, cred) {
|
|
252
|
+
const tx = this.store.beginTransaction('readwrite'), existingDir = dirname(existing), existingDirNode = await this.findINode(tx, existingDir);
|
|
253
|
+
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
|
|
254
|
+
throw ApiError.EACCES(existingDir);
|
|
255
|
+
}
|
|
256
|
+
const newDir = dirname(newpath), newDirNode = await this.findINode(tx, newDir), newListing = await this.getDirListing(tx, newDirNode, newDir);
|
|
257
|
+
if (!newDirNode.toStats().hasAccess(W_OK, cred)) {
|
|
258
|
+
throw ApiError.EACCES(newDir);
|
|
259
|
+
}
|
|
260
|
+
const ino = await this._findINode(tx, existingDir, basename(existing));
|
|
261
|
+
const node = await this.getINode(tx, ino, existing);
|
|
262
|
+
if (!node.toStats().hasAccess(W_OK, cred)) {
|
|
263
|
+
throw ApiError.EACCES(newpath);
|
|
264
|
+
}
|
|
265
|
+
node.nlink++;
|
|
266
|
+
newListing[basename(newpath)] = ino;
|
|
267
|
+
try {
|
|
268
|
+
tx.put(ino, node.data, true);
|
|
269
|
+
tx.put(newDirNode.ino, encodeDirListing(newListing), true);
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
tx.abort();
|
|
273
|
+
throw e;
|
|
274
|
+
}
|
|
275
|
+
tx.commit();
|
|
276
|
+
}
|
|
328
277
|
/**
|
|
329
278
|
* Checks if the root directory exists. Creates it if it doesn't.
|
|
330
279
|
*/
|
|
331
280
|
async makeRootDirectory() {
|
|
332
281
|
const tx = this.store.beginTransaction('readwrite');
|
|
333
|
-
if ((await tx.get(
|
|
334
|
-
// Create new inode.
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
dirInode = new Inode(randomUUID(), 4096, 511 | FileType.DIRECTORY, currTime, currTime, currTime, 0, 0);
|
|
282
|
+
if ((await tx.get(rootIno)) === undefined) {
|
|
283
|
+
// Create new inode. o777, owned by root:root
|
|
284
|
+
const dirInode = new Inode();
|
|
285
|
+
dirInode.mode = 0o777 | FileType.DIRECTORY;
|
|
338
286
|
// If the root doesn't exist, the first random ID shouldn't exist,
|
|
339
287
|
// either.
|
|
340
|
-
await tx.put(dirInode.
|
|
341
|
-
await tx.put(
|
|
288
|
+
await tx.put(dirInode.ino, encode('{}'), false);
|
|
289
|
+
await tx.put(rootIno, dirInode.data, false);
|
|
342
290
|
await tx.commit();
|
|
343
291
|
}
|
|
344
292
|
}
|
|
@@ -364,14 +312,14 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
364
312
|
if (filename === '') {
|
|
365
313
|
// BASE CASE #1: Return the root's ID.
|
|
366
314
|
if (this._cache) {
|
|
367
|
-
this._cache.set(currentPath,
|
|
315
|
+
this._cache.set(currentPath, rootIno);
|
|
368
316
|
}
|
|
369
|
-
return
|
|
317
|
+
return rootIno;
|
|
370
318
|
}
|
|
371
319
|
else {
|
|
372
320
|
// BASE CASE #2: Find the item in the root node.
|
|
373
|
-
const inode = await this.getINode(tx,
|
|
374
|
-
const dirList = await this.getDirListing(tx,
|
|
321
|
+
const inode = await this.getINode(tx, rootIno, parent);
|
|
322
|
+
const dirList = await this.getDirListing(tx, inode, parent);
|
|
375
323
|
if (dirList[filename]) {
|
|
376
324
|
const id = dirList[filename];
|
|
377
325
|
if (this._cache) {
|
|
@@ -388,7 +336,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
388
336
|
// Get the parent directory's INode, and find the file in its directory
|
|
389
337
|
// listing.
|
|
390
338
|
const inode = await this.findINode(tx, parent, visited);
|
|
391
|
-
const dirList = await this.getDirListing(tx,
|
|
339
|
+
const dirList = await this.getDirListing(tx, inode, parent);
|
|
392
340
|
if (dirList[filename]) {
|
|
393
341
|
const id = dirList[filename];
|
|
394
342
|
if (this._cache) {
|
|
@@ -408,7 +356,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
408
356
|
*/
|
|
409
357
|
async findINode(tx, p, visited = new Set()) {
|
|
410
358
|
const id = await this._findINode(tx, dirname(p), basename(p), visited);
|
|
411
|
-
return this.getINode(tx,
|
|
359
|
+
return this.getINode(tx, id, p);
|
|
412
360
|
}
|
|
413
361
|
/**
|
|
414
362
|
* Given the ID of a node, retrieves the corresponding Inode.
|
|
@@ -416,35 +364,35 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
416
364
|
* @param p The corresponding path to the file (used for error messages).
|
|
417
365
|
* @param id The ID to look up.
|
|
418
366
|
*/
|
|
419
|
-
async getINode(tx,
|
|
367
|
+
async getINode(tx, id, p) {
|
|
420
368
|
const data = await tx.get(id);
|
|
421
369
|
if (!data) {
|
|
422
370
|
throw ApiError.ENOENT(p);
|
|
423
371
|
}
|
|
424
|
-
return Inode
|
|
372
|
+
return new Inode(data.buffer);
|
|
425
373
|
}
|
|
426
374
|
/**
|
|
427
375
|
* Given the Inode of a directory, retrieves the corresponding directory
|
|
428
376
|
* listing.
|
|
429
377
|
*/
|
|
430
|
-
async getDirListing(tx,
|
|
431
|
-
if (!inode.isDirectory()) {
|
|
378
|
+
async getDirListing(tx, inode, p) {
|
|
379
|
+
if (!inode.toStats().isDirectory()) {
|
|
432
380
|
throw ApiError.ENOTDIR(p);
|
|
433
381
|
}
|
|
434
|
-
const data = await tx.get(inode.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// the file system is corrupted.
|
|
382
|
+
const data = await tx.get(inode.ino);
|
|
383
|
+
if (!data) {
|
|
384
|
+
/*
|
|
385
|
+
Occurs when data is undefined, or corresponds to something other
|
|
386
|
+
than a directory listing. The latter should never occur unless
|
|
387
|
+
the file system is corrupted.
|
|
388
|
+
*/
|
|
442
389
|
throw ApiError.ENOENT(p);
|
|
443
390
|
}
|
|
391
|
+
return decodeDirListing(data);
|
|
444
392
|
}
|
|
445
393
|
/**
|
|
446
394
|
* Adds a new node under a random ID. Retries 5 times before giving up in
|
|
447
|
-
* the exceedingly unlikely chance that we try to reuse a random
|
|
395
|
+
* the exceedingly unlikely chance that we try to reuse a random ino.
|
|
448
396
|
*/
|
|
449
397
|
async addNewNode(tx, data) {
|
|
450
398
|
let retries = 0;
|
|
@@ -455,13 +403,13 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
455
403
|
}
|
|
456
404
|
else {
|
|
457
405
|
// Try again.
|
|
458
|
-
const
|
|
459
|
-
const committed = await tx.put(
|
|
406
|
+
const ino = randomIno();
|
|
407
|
+
const committed = await tx.put(ino, data, false);
|
|
460
408
|
if (!committed) {
|
|
461
409
|
return reroll();
|
|
462
410
|
}
|
|
463
411
|
else {
|
|
464
|
-
return
|
|
412
|
+
return ino;
|
|
465
413
|
}
|
|
466
414
|
}
|
|
467
415
|
};
|
|
@@ -478,7 +426,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
478
426
|
* @param data The data to store at the file's data node.
|
|
479
427
|
*/
|
|
480
428
|
async commitNewFile(tx, p, type, mode, cred, data) {
|
|
481
|
-
const parentDir = dirname(p), fname = basename(p), parentNode = await this.findINode(tx, parentDir), dirListing = await this.getDirListing(tx,
|
|
429
|
+
const parentDir = dirname(p), fname = basename(p), parentNode = await this.findINode(tx, parentDir), dirListing = await this.getDirListing(tx, parentNode, parentDir);
|
|
482
430
|
//Check that the creater has correct access
|
|
483
431
|
if (!parentNode.toStats().hasAccess(W_OK, cred)) {
|
|
484
432
|
throw ApiError.EACCES(p);
|
|
@@ -496,15 +444,17 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
496
444
|
}
|
|
497
445
|
try {
|
|
498
446
|
// Commit data.
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
447
|
+
const inode = new Inode();
|
|
448
|
+
inode.ino = await this.addNewNode(tx, data);
|
|
449
|
+
inode.mode = mode | type;
|
|
450
|
+
inode.uid = cred.uid;
|
|
451
|
+
inode.gid = cred.gid;
|
|
452
|
+
inode.size = data.length;
|
|
503
453
|
// Update and commit parent directory listing.
|
|
504
|
-
dirListing[fname] =
|
|
505
|
-
await tx.put(parentNode.
|
|
454
|
+
dirListing[fname] = await this.addNewNode(tx, inode.data);
|
|
455
|
+
await tx.put(parentNode.ino, encodeDirListing(dirListing), true);
|
|
506
456
|
await tx.commit();
|
|
507
|
-
return
|
|
457
|
+
return inode;
|
|
508
458
|
}
|
|
509
459
|
catch (e) {
|
|
510
460
|
tx.abort();
|
|
@@ -527,31 +477,31 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
527
477
|
if (this._cache) {
|
|
528
478
|
this._cache.remove(p);
|
|
529
479
|
}
|
|
530
|
-
const tx = this.store.beginTransaction('readwrite'), parent = dirname(p), parentNode = await this.findINode(tx, parent), parentListing = await this.getDirListing(tx,
|
|
480
|
+
const tx = this.store.beginTransaction('readwrite'), parent = dirname(p), parentNode = await this.findINode(tx, parent), parentListing = await this.getDirListing(tx, parentNode, parent), fileName = basename(p);
|
|
531
481
|
if (!parentListing[fileName]) {
|
|
532
482
|
throw ApiError.ENOENT(p);
|
|
533
483
|
}
|
|
534
|
-
const
|
|
484
|
+
const fileIno = parentListing[fileName];
|
|
535
485
|
// Get file inode.
|
|
536
|
-
const fileNode = await this.getINode(tx,
|
|
486
|
+
const fileNode = await this.getINode(tx, fileIno, p);
|
|
537
487
|
if (!fileNode.toStats().hasAccess(W_OK, cred)) {
|
|
538
488
|
throw ApiError.EACCES(p);
|
|
539
489
|
}
|
|
540
490
|
// Remove from directory listing of parent.
|
|
541
491
|
delete parentListing[fileName];
|
|
542
|
-
if (!isDir && fileNode.isDirectory()) {
|
|
492
|
+
if (!isDir && fileNode.toStats().isDirectory()) {
|
|
543
493
|
throw ApiError.EISDIR(p);
|
|
544
494
|
}
|
|
545
|
-
|
|
495
|
+
if (isDir && !fileNode.toStats().isDirectory()) {
|
|
546
496
|
throw ApiError.ENOTDIR(p);
|
|
547
497
|
}
|
|
548
498
|
try {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
499
|
+
await tx.put(parentNode.ino, encodeDirListing(parentListing), true);
|
|
500
|
+
if (--fileNode.nlink < 1) {
|
|
501
|
+
// remove file
|
|
502
|
+
await tx.remove(fileNode.ino);
|
|
503
|
+
await tx.remove(fileIno);
|
|
504
|
+
}
|
|
555
505
|
}
|
|
556
506
|
catch (e) {
|
|
557
507
|
await tx.abort();
|
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { Ino } from '../inode.js';
|
|
2
|
+
import { SyncStore, SimpleSyncStore, SyncRWTransaction, SyncStoreFileSystem } from './SyncStore.js';
|
|
3
|
+
import { type Backend } from './backend.js';
|
|
3
4
|
/**
|
|
4
|
-
* A simple in-memory
|
|
5
|
+
* A simple in-memory store
|
|
5
6
|
*/
|
|
6
|
-
export declare class InMemoryStore implements
|
|
7
|
+
export declare class InMemoryStore implements SyncStore, SimpleSyncStore {
|
|
8
|
+
name: string;
|
|
7
9
|
private store;
|
|
8
|
-
name
|
|
10
|
+
constructor(name?: string);
|
|
9
11
|
clear(): void;
|
|
10
|
-
beginTransaction(
|
|
11
|
-
get(key:
|
|
12
|
-
put(key:
|
|
13
|
-
|
|
12
|
+
beginTransaction(): SyncRWTransaction;
|
|
13
|
+
get(key: Ino): Uint8Array;
|
|
14
|
+
put(key: Ino, data: Uint8Array, overwrite: boolean): boolean;
|
|
15
|
+
remove(key: Ino): void;
|
|
14
16
|
}
|
|
17
|
+
export declare const InMemory: Backend;
|
|
15
18
|
/**
|
|
16
19
|
* A simple in-memory file system backed by an InMemoryStore.
|
|
17
20
|
* Files are not persisted across page loads.
|
|
18
21
|
*/
|
|
19
|
-
export declare class
|
|
20
|
-
static
|
|
21
|
-
static
|
|
22
|
-
static readonly
|
|
22
|
+
export declare class _InMemory extends SyncStoreFileSystem {
|
|
23
|
+
static isAvailable(): boolean;
|
|
24
|
+
static create: any;
|
|
25
|
+
static readonly options: {};
|
|
23
26
|
constructor();
|
|
24
27
|
}
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
var _a;
|
|
2
|
-
import { SimpleSyncRWTransaction,
|
|
3
|
-
import {
|
|
2
|
+
import { SimpleSyncRWTransaction, SyncStoreFileSystem } from './SyncStore.js';
|
|
3
|
+
import { createBackend } from './backend.js';
|
|
4
4
|
/**
|
|
5
|
-
* A simple in-memory
|
|
5
|
+
* A simple in-memory store
|
|
6
6
|
*/
|
|
7
7
|
export class InMemoryStore {
|
|
8
|
-
constructor() {
|
|
8
|
+
constructor(name = 'tmp') {
|
|
9
|
+
this.name = name;
|
|
9
10
|
this.store = new Map();
|
|
10
11
|
}
|
|
11
|
-
name() {
|
|
12
|
-
return InMemoryFileSystem.Name;
|
|
13
|
-
}
|
|
14
12
|
clear() {
|
|
15
13
|
this.store.clear();
|
|
16
14
|
}
|
|
17
|
-
beginTransaction(
|
|
15
|
+
beginTransaction() {
|
|
18
16
|
return new SimpleSyncRWTransaction(this);
|
|
19
17
|
}
|
|
20
18
|
get(key) {
|
|
@@ -27,20 +25,37 @@ export class InMemoryStore {
|
|
|
27
25
|
this.store.set(key, data);
|
|
28
26
|
return true;
|
|
29
27
|
}
|
|
30
|
-
|
|
28
|
+
remove(key) {
|
|
31
29
|
this.store.delete(key);
|
|
32
30
|
}
|
|
33
31
|
}
|
|
32
|
+
export const InMemory = {
|
|
33
|
+
name: 'InMemory',
|
|
34
|
+
isAvailable() {
|
|
35
|
+
return true;
|
|
36
|
+
},
|
|
37
|
+
options: {
|
|
38
|
+
name: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
description: 'The name of the store',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
create({ name }) {
|
|
44
|
+
return new SyncStoreFileSystem({ store: new InMemoryStore(name) });
|
|
45
|
+
},
|
|
46
|
+
};
|
|
34
47
|
/**
|
|
35
48
|
* A simple in-memory file system backed by an InMemoryStore.
|
|
36
49
|
* Files are not persisted across page loads.
|
|
37
50
|
*/
|
|
38
|
-
export class
|
|
51
|
+
export class _InMemory extends SyncStoreFileSystem {
|
|
52
|
+
static isAvailable() {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
39
55
|
constructor() {
|
|
40
56
|
super({ store: new InMemoryStore() });
|
|
41
57
|
}
|
|
42
58
|
}
|
|
43
|
-
_a =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
InMemoryFileSystem.Options = {};
|
|
59
|
+
_a = _InMemory;
|
|
60
|
+
_InMemory.create = createBackend.bind(_a);
|
|
61
|
+
_InMemory.options = {};
|