@zenfs/core 0.1.0 → 0.2.1
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 +40 -29
- package/dist/backends/AsyncStore.js +144 -192
- 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,73 +52,55 @@ 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
|
-
constructor(cacheSize) {
|
|
76
|
+
constructor({ store, cacheSize }) {
|
|
125
77
|
super();
|
|
126
|
-
this._cache = null;
|
|
127
78
|
if (cacheSize > 0) {
|
|
128
79
|
this._cache = new LRUCache(cacheSize);
|
|
129
80
|
}
|
|
81
|
+
this._ready = this._initialize(store);
|
|
130
82
|
}
|
|
131
83
|
/**
|
|
132
84
|
* Initializes the file system. Typically called by subclasses' async
|
|
133
85
|
* constructors.
|
|
134
86
|
*/
|
|
135
|
-
async
|
|
136
|
-
this.store = store;
|
|
87
|
+
async _initialize(store) {
|
|
88
|
+
this.store = await store;
|
|
137
89
|
// INVARIANT: Ensure that the root exists.
|
|
138
90
|
await this.makeRootDirectory();
|
|
139
|
-
|
|
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;
|
|
91
|
+
return this;
|
|
154
92
|
}
|
|
155
93
|
/**
|
|
156
94
|
* Delete all contents stored in the file system.
|
|
157
95
|
*/
|
|
158
96
|
async empty() {
|
|
159
97
|
if (this._cache) {
|
|
160
|
-
this._cache.
|
|
98
|
+
this._cache.reset();
|
|
161
99
|
}
|
|
162
100
|
await this.store.clear();
|
|
163
101
|
// INVARIANT: Root always exists.
|
|
164
102
|
await this.makeRootDirectory();
|
|
165
103
|
}
|
|
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
104
|
/**
|
|
177
105
|
* @todo Make rename compatible with the cache.
|
|
178
106
|
*/
|
|
@@ -181,12 +109,12 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
181
109
|
if (this._cache) {
|
|
182
110
|
// Clear and disable cache during renaming process.
|
|
183
111
|
this._cache = null;
|
|
184
|
-
c.
|
|
112
|
+
c.reset();
|
|
185
113
|
}
|
|
186
114
|
try {
|
|
187
115
|
const tx = this.store.beginTransaction('readwrite'), oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
|
|
188
116
|
// Remove oldPath from parent's directory listing.
|
|
189
|
-
oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx,
|
|
117
|
+
oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldDirNode, oldParent);
|
|
190
118
|
if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
|
|
191
119
|
throw ApiError.EACCES(oldPath);
|
|
192
120
|
}
|
|
@@ -212,15 +140,15 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
212
140
|
}
|
|
213
141
|
else {
|
|
214
142
|
newDirNode = await this.findINode(tx, newParent);
|
|
215
|
-
newDirList = await this.getDirListing(tx,
|
|
143
|
+
newDirList = await this.getDirListing(tx, newDirNode, newParent);
|
|
216
144
|
}
|
|
217
145
|
if (newDirList[newName]) {
|
|
218
146
|
// If it's a file, delete it.
|
|
219
|
-
const newNameNode = await this.getINode(tx,
|
|
220
|
-
if (newNameNode.isFile()) {
|
|
147
|
+
const newNameNode = await this.getINode(tx, newDirList[newName], newPath);
|
|
148
|
+
if (newNameNode.toStats().isFile()) {
|
|
221
149
|
try {
|
|
222
|
-
await tx.
|
|
223
|
-
await tx.
|
|
150
|
+
await tx.remove(newNameNode.ino);
|
|
151
|
+
await tx.remove(newDirList[newName]);
|
|
224
152
|
}
|
|
225
153
|
catch (e) {
|
|
226
154
|
await tx.abort();
|
|
@@ -235,8 +163,8 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
235
163
|
newDirList[newName] = nodeId;
|
|
236
164
|
// Commit the two changed directory listings.
|
|
237
165
|
try {
|
|
238
|
-
await tx.put(oldDirNode.
|
|
239
|
-
await tx.put(newDirNode.
|
|
166
|
+
await tx.put(oldDirNode.ino, encodeDirListing(oldDirList), true);
|
|
167
|
+
await tx.put(newDirNode.ino, encodeDirListing(newDirList), true);
|
|
240
168
|
}
|
|
241
169
|
catch (e) {
|
|
242
170
|
await tx.abort();
|
|
@@ -253,6 +181,9 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
253
181
|
async stat(p, cred) {
|
|
254
182
|
const tx = this.store.beginTransaction('readonly');
|
|
255
183
|
const inode = await this.findINode(tx, p);
|
|
184
|
+
if (!inode) {
|
|
185
|
+
throw ApiError.ENOENT(p);
|
|
186
|
+
}
|
|
256
187
|
const stats = inode.toStats();
|
|
257
188
|
if (!stats.hasAccess(R_OK, cred)) {
|
|
258
189
|
throw ApiError.EACCES(p);
|
|
@@ -262,17 +193,17 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
262
193
|
async createFile(p, flag, mode, cred) {
|
|
263
194
|
const tx = this.store.beginTransaction('readwrite'), data = new Uint8Array(0), newFile = await this.commitNewFile(tx, p, FileType.FILE, mode, cred, data);
|
|
264
195
|
// Open the file.
|
|
265
|
-
return new
|
|
196
|
+
return new AsyncFile(this, p, flag, newFile.toStats(), data);
|
|
266
197
|
}
|
|
267
198
|
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.
|
|
199
|
+
const tx = this.store.beginTransaction('readonly'), node = await this.findINode(tx, p), data = await tx.get(node.ino);
|
|
200
|
+
if (!node.toStats().hasAccess(flag.mode, cred)) {
|
|
270
201
|
throw ApiError.EACCES(p);
|
|
271
202
|
}
|
|
272
|
-
if (data
|
|
203
|
+
if (!data) {
|
|
273
204
|
throw ApiError.ENOENT(p);
|
|
274
205
|
}
|
|
275
|
-
return new
|
|
206
|
+
return new AsyncFile(this, p, flag, node.toStats(), data);
|
|
276
207
|
}
|
|
277
208
|
async unlink(p, cred) {
|
|
278
209
|
return this.removeEntry(p, false, cred);
|
|
@@ -295,28 +226,22 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
295
226
|
if (!node.toStats().hasAccess(R_OK, cred)) {
|
|
296
227
|
throw ApiError.EACCES(p);
|
|
297
228
|
}
|
|
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);
|
|
229
|
+
return Object.keys(await this.getDirListing(tx, node, p));
|
|
303
230
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
async
|
|
309
|
-
// @todo Ensure mtime updates properly, and use that to determine if a data
|
|
310
|
-
// update is required.
|
|
231
|
+
/**
|
|
232
|
+
* Updated the inode and data node at the given path
|
|
233
|
+
* @todo Ensure mtime updates properly, and use that to determine if a data update is required.
|
|
234
|
+
*/
|
|
235
|
+
async sync(p, data, stats) {
|
|
311
236
|
const tx = this.store.beginTransaction('readwrite'),
|
|
312
237
|
// 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,
|
|
238
|
+
fileInodeId = await this._findINode(tx, dirname(p), basename(p)), fileInode = await this.getINode(tx, fileInodeId, p), inodeChanged = fileInode.update(stats);
|
|
314
239
|
try {
|
|
315
240
|
// Sync data.
|
|
316
|
-
await tx.put(fileInode.
|
|
241
|
+
await tx.put(fileInode.ino, data, true);
|
|
317
242
|
// Sync metadata.
|
|
318
243
|
if (inodeChanged) {
|
|
319
|
-
await tx.put(fileInodeId, fileInode.
|
|
244
|
+
await tx.put(fileInodeId, fileInode.data, true);
|
|
320
245
|
}
|
|
321
246
|
}
|
|
322
247
|
catch (e) {
|
|
@@ -325,20 +250,45 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
325
250
|
}
|
|
326
251
|
await tx.commit();
|
|
327
252
|
}
|
|
253
|
+
async link(existing, newpath, cred) {
|
|
254
|
+
const tx = this.store.beginTransaction('readwrite'), existingDir = dirname(existing), existingDirNode = await this.findINode(tx, existingDir);
|
|
255
|
+
if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
|
|
256
|
+
throw ApiError.EACCES(existingDir);
|
|
257
|
+
}
|
|
258
|
+
const newDir = dirname(newpath), newDirNode = await this.findINode(tx, newDir), newListing = await this.getDirListing(tx, newDirNode, newDir);
|
|
259
|
+
if (!newDirNode.toStats().hasAccess(W_OK, cred)) {
|
|
260
|
+
throw ApiError.EACCES(newDir);
|
|
261
|
+
}
|
|
262
|
+
const ino = await this._findINode(tx, existingDir, basename(existing));
|
|
263
|
+
const node = await this.getINode(tx, ino, existing);
|
|
264
|
+
if (!node.toStats().hasAccess(W_OK, cred)) {
|
|
265
|
+
throw ApiError.EACCES(newpath);
|
|
266
|
+
}
|
|
267
|
+
node.nlink++;
|
|
268
|
+
newListing[basename(newpath)] = ino;
|
|
269
|
+
try {
|
|
270
|
+
tx.put(ino, node.data, true);
|
|
271
|
+
tx.put(newDirNode.ino, encodeDirListing(newListing), true);
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
tx.abort();
|
|
275
|
+
throw e;
|
|
276
|
+
}
|
|
277
|
+
tx.commit();
|
|
278
|
+
}
|
|
328
279
|
/**
|
|
329
280
|
* Checks if the root directory exists. Creates it if it doesn't.
|
|
330
281
|
*/
|
|
331
282
|
async makeRootDirectory() {
|
|
332
283
|
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);
|
|
284
|
+
if ((await tx.get(rootIno)) === undefined) {
|
|
285
|
+
// Create new inode. o777, owned by root:root
|
|
286
|
+
const dirInode = new Inode();
|
|
287
|
+
dirInode.mode = 0o777 | FileType.DIRECTORY;
|
|
338
288
|
// If the root doesn't exist, the first random ID shouldn't exist,
|
|
339
289
|
// either.
|
|
340
|
-
await tx.put(dirInode.
|
|
341
|
-
await tx.put(
|
|
290
|
+
await tx.put(dirInode.ino, encode('{}'), false);
|
|
291
|
+
await tx.put(rootIno, dirInode.data, false);
|
|
342
292
|
await tx.commit();
|
|
343
293
|
}
|
|
344
294
|
}
|
|
@@ -364,14 +314,14 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
364
314
|
if (filename === '') {
|
|
365
315
|
// BASE CASE #1: Return the root's ID.
|
|
366
316
|
if (this._cache) {
|
|
367
|
-
this._cache.set(currentPath,
|
|
317
|
+
this._cache.set(currentPath, rootIno);
|
|
368
318
|
}
|
|
369
|
-
return
|
|
319
|
+
return rootIno;
|
|
370
320
|
}
|
|
371
321
|
else {
|
|
372
322
|
// BASE CASE #2: Find the item in the root node.
|
|
373
|
-
const inode = await this.getINode(tx,
|
|
374
|
-
const dirList = await this.getDirListing(tx,
|
|
323
|
+
const inode = await this.getINode(tx, rootIno, parent);
|
|
324
|
+
const dirList = await this.getDirListing(tx, inode, parent);
|
|
375
325
|
if (dirList[filename]) {
|
|
376
326
|
const id = dirList[filename];
|
|
377
327
|
if (this._cache) {
|
|
@@ -388,7 +338,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
388
338
|
// Get the parent directory's INode, and find the file in its directory
|
|
389
339
|
// listing.
|
|
390
340
|
const inode = await this.findINode(tx, parent, visited);
|
|
391
|
-
const dirList = await this.getDirListing(tx,
|
|
341
|
+
const dirList = await this.getDirListing(tx, inode, parent);
|
|
392
342
|
if (dirList[filename]) {
|
|
393
343
|
const id = dirList[filename];
|
|
394
344
|
if (this._cache) {
|
|
@@ -408,7 +358,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
408
358
|
*/
|
|
409
359
|
async findINode(tx, p, visited = new Set()) {
|
|
410
360
|
const id = await this._findINode(tx, dirname(p), basename(p), visited);
|
|
411
|
-
return this.getINode(tx,
|
|
361
|
+
return this.getINode(tx, id, p);
|
|
412
362
|
}
|
|
413
363
|
/**
|
|
414
364
|
* Given the ID of a node, retrieves the corresponding Inode.
|
|
@@ -416,35 +366,35 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
416
366
|
* @param p The corresponding path to the file (used for error messages).
|
|
417
367
|
* @param id The ID to look up.
|
|
418
368
|
*/
|
|
419
|
-
async getINode(tx,
|
|
369
|
+
async getINode(tx, id, p) {
|
|
420
370
|
const data = await tx.get(id);
|
|
421
371
|
if (!data) {
|
|
422
372
|
throw ApiError.ENOENT(p);
|
|
423
373
|
}
|
|
424
|
-
return Inode
|
|
374
|
+
return new Inode(data.buffer);
|
|
425
375
|
}
|
|
426
376
|
/**
|
|
427
377
|
* Given the Inode of a directory, retrieves the corresponding directory
|
|
428
378
|
* listing.
|
|
429
379
|
*/
|
|
430
|
-
async getDirListing(tx,
|
|
431
|
-
if (!inode.isDirectory()) {
|
|
380
|
+
async getDirListing(tx, inode, p) {
|
|
381
|
+
if (!inode.toStats().isDirectory()) {
|
|
432
382
|
throw ApiError.ENOTDIR(p);
|
|
433
383
|
}
|
|
434
|
-
const data = await tx.get(inode.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// the file system is corrupted.
|
|
384
|
+
const data = await tx.get(inode.ino);
|
|
385
|
+
if (!data) {
|
|
386
|
+
/*
|
|
387
|
+
Occurs when data is undefined, or corresponds to something other
|
|
388
|
+
than a directory listing. The latter should never occur unless
|
|
389
|
+
the file system is corrupted.
|
|
390
|
+
*/
|
|
442
391
|
throw ApiError.ENOENT(p);
|
|
443
392
|
}
|
|
393
|
+
return decodeDirListing(data);
|
|
444
394
|
}
|
|
445
395
|
/**
|
|
446
396
|
* 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
|
|
397
|
+
* the exceedingly unlikely chance that we try to reuse a random ino.
|
|
448
398
|
*/
|
|
449
399
|
async addNewNode(tx, data) {
|
|
450
400
|
let retries = 0;
|
|
@@ -455,13 +405,13 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
455
405
|
}
|
|
456
406
|
else {
|
|
457
407
|
// Try again.
|
|
458
|
-
const
|
|
459
|
-
const committed = await tx.put(
|
|
408
|
+
const ino = randomIno();
|
|
409
|
+
const committed = await tx.put(ino, data, false);
|
|
460
410
|
if (!committed) {
|
|
461
411
|
return reroll();
|
|
462
412
|
}
|
|
463
413
|
else {
|
|
464
|
-
return
|
|
414
|
+
return ino;
|
|
465
415
|
}
|
|
466
416
|
}
|
|
467
417
|
};
|
|
@@ -478,7 +428,7 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
478
428
|
* @param data The data to store at the file's data node.
|
|
479
429
|
*/
|
|
480
430
|
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,
|
|
431
|
+
const parentDir = dirname(p), fname = basename(p), parentNode = await this.findINode(tx, parentDir), dirListing = await this.getDirListing(tx, parentNode, parentDir);
|
|
482
432
|
//Check that the creater has correct access
|
|
483
433
|
if (!parentNode.toStats().hasAccess(W_OK, cred)) {
|
|
484
434
|
throw ApiError.EACCES(p);
|
|
@@ -496,15 +446,17 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
496
446
|
}
|
|
497
447
|
try {
|
|
498
448
|
// Commit data.
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
449
|
+
const inode = new Inode();
|
|
450
|
+
inode.ino = await this.addNewNode(tx, data);
|
|
451
|
+
inode.mode = mode | type;
|
|
452
|
+
inode.uid = cred.uid;
|
|
453
|
+
inode.gid = cred.gid;
|
|
454
|
+
inode.size = data.length;
|
|
503
455
|
// Update and commit parent directory listing.
|
|
504
|
-
dirListing[fname] =
|
|
505
|
-
await tx.put(parentNode.
|
|
456
|
+
dirListing[fname] = await this.addNewNode(tx, inode.data);
|
|
457
|
+
await tx.put(parentNode.ino, encodeDirListing(dirListing), true);
|
|
506
458
|
await tx.commit();
|
|
507
|
-
return
|
|
459
|
+
return inode;
|
|
508
460
|
}
|
|
509
461
|
catch (e) {
|
|
510
462
|
tx.abort();
|
|
@@ -527,31 +479,31 @@ export class AsyncKeyValueFileSystem extends BaseFileSystem {
|
|
|
527
479
|
if (this._cache) {
|
|
528
480
|
this._cache.remove(p);
|
|
529
481
|
}
|
|
530
|
-
const tx = this.store.beginTransaction('readwrite'), parent = dirname(p), parentNode = await this.findINode(tx, parent), parentListing = await this.getDirListing(tx,
|
|
482
|
+
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
483
|
if (!parentListing[fileName]) {
|
|
532
484
|
throw ApiError.ENOENT(p);
|
|
533
485
|
}
|
|
534
|
-
const
|
|
486
|
+
const fileIno = parentListing[fileName];
|
|
535
487
|
// Get file inode.
|
|
536
|
-
const fileNode = await this.getINode(tx,
|
|
488
|
+
const fileNode = await this.getINode(tx, fileIno, p);
|
|
537
489
|
if (!fileNode.toStats().hasAccess(W_OK, cred)) {
|
|
538
490
|
throw ApiError.EACCES(p);
|
|
539
491
|
}
|
|
540
492
|
// Remove from directory listing of parent.
|
|
541
493
|
delete parentListing[fileName];
|
|
542
|
-
if (!isDir && fileNode.isDirectory()) {
|
|
494
|
+
if (!isDir && fileNode.toStats().isDirectory()) {
|
|
543
495
|
throw ApiError.EISDIR(p);
|
|
544
496
|
}
|
|
545
|
-
|
|
497
|
+
if (isDir && !fileNode.toStats().isDirectory()) {
|
|
546
498
|
throw ApiError.ENOTDIR(p);
|
|
547
499
|
}
|
|
548
500
|
try {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
501
|
+
await tx.put(parentNode.ino, encodeDirListing(parentListing), true);
|
|
502
|
+
if (--fileNode.nlink < 1) {
|
|
503
|
+
// remove file
|
|
504
|
+
await tx.remove(fileNode.ino);
|
|
505
|
+
await tx.remove(fileIno);
|
|
506
|
+
}
|
|
555
507
|
}
|
|
556
508
|
catch (e) {
|
|
557
509
|
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
|
}
|