@zenfs/core 0.11.1 → 0.12.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/backends/fetch.d.ts +20 -33
- package/dist/backends/fetch.js +48 -93
- package/dist/backends/index/fs.d.ts +49 -0
- package/dist/backends/index/fs.js +86 -0
- package/dist/backends/index/index.d.ts +37 -0
- package/dist/backends/index/index.js +82 -0
- package/dist/backends/locked.d.ts +1 -1
- package/dist/backends/locked.js +34 -34
- package/dist/backends/memory.d.ts +1 -1
- package/dist/backends/port/fs.d.ts +1 -1
- package/dist/backends/port/fs.js +2 -1
- package/dist/backends/port/rpc.js +3 -1
- package/dist/backends/store/fs.d.ts +5 -5
- package/dist/backends/store/fs.js +1 -1
- package/dist/browser.min.js +4 -4
- package/dist/browser.min.js.map +4 -4
- package/dist/emulation/promises.js +1 -2
- package/dist/emulation/sync.js +24 -40
- package/dist/file.d.ts +1 -1
- package/dist/file.js +2 -2
- package/dist/filesystem.d.ts +6 -6
- package/dist/filesystem.js +11 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mutex.d.ts +1 -1
- package/dist/mutex.js +11 -11
- package/dist/stats.d.ts +10 -10
- package/package.json +1 -1
- package/scripts/make-index.js +39 -24
- package/src/backends/fetch.ts +52 -110
- package/src/backends/index/fs.ts +113 -0
- package/src/backends/index/index.ts +98 -0
- package/src/backends/index/readme.md +3 -0
- package/src/backends/locked.ts +34 -34
- package/src/backends/memory.ts +1 -1
- package/src/backends/port/fs.ts +2 -1
- package/src/backends/port/rpc.ts +2 -1
- package/src/backends/store/fs.ts +5 -5
- package/src/emulation/promises.ts +1 -2
- package/src/emulation/sync.ts +23 -41
- package/src/file.ts +2 -2
- package/src/filesystem.ts +18 -17
- package/src/index.ts +1 -1
- package/src/mutex.ts +11 -11
- package/src/stats.ts +10 -10
- package/dist/backends/Index.d.ts +0 -204
- package/dist/backends/Index.js +0 -410
- package/src/backends/Index.ts +0 -504
package/dist/backends/fetch.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { NoSyncFile } from '../file.js';
|
|
2
1
|
import type { FileSystemMetadata } from '../filesystem.js';
|
|
3
2
|
import { Stats } from '../stats.js';
|
|
4
|
-
import {
|
|
3
|
+
import { IndexFS } from './index/fs.js';
|
|
4
|
+
import type { IndexData } from './index/index.js';
|
|
5
5
|
/**
|
|
6
6
|
* Configuration options for FetchFS.
|
|
7
7
|
*/
|
|
@@ -10,60 +10,47 @@ export interface FetchOptions {
|
|
|
10
10
|
* URL to a file index as a JSON file or the file index object itself.
|
|
11
11
|
* Defaults to `index.json`.
|
|
12
12
|
*/
|
|
13
|
-
index?: string |
|
|
13
|
+
index?: string | IndexData;
|
|
14
14
|
/** Used as the URL prefix for fetched files.
|
|
15
15
|
* Default: Fetch files relative to the index.
|
|
16
16
|
*/
|
|
17
17
|
baseUrl?: string;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* A simple filesystem backed by HTTP using the fetch API.
|
|
20
|
+
* A simple filesystem backed by HTTP using the `fetch` API.
|
|
21
21
|
*
|
|
22
22
|
*
|
|
23
|
-
*
|
|
23
|
+
* Index objects look like the following:
|
|
24
24
|
*
|
|
25
25
|
* ```json
|
|
26
26
|
* {
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* }
|
|
34
|
-
* }
|
|
27
|
+
* "version": 1,
|
|
28
|
+
* "entries": {
|
|
29
|
+
* "/home": { ... },
|
|
30
|
+
* "/home/jvilk": { ... },
|
|
31
|
+
* "/home/james": { ... }
|
|
32
|
+
* }
|
|
35
33
|
* }
|
|
36
34
|
* ```
|
|
37
35
|
*
|
|
38
|
-
*
|
|
36
|
+
* Each entry contains the stats associated with the file.
|
|
39
37
|
*/
|
|
40
|
-
export declare class FetchFS extends
|
|
41
|
-
readonly
|
|
42
|
-
protected _init: Promise<void>;
|
|
43
|
-
protected _initialize(index: string | ListingTree): Promise<void>;
|
|
38
|
+
export declare class FetchFS extends IndexFS {
|
|
39
|
+
readonly baseUrl: string;
|
|
44
40
|
ready(): Promise<void>;
|
|
45
41
|
constructor({ index, baseUrl }: FetchOptions);
|
|
46
42
|
metadata(): FileSystemMetadata;
|
|
47
|
-
empty(): void;
|
|
48
43
|
/**
|
|
49
|
-
*
|
|
44
|
+
* Preload the given file into the index.
|
|
50
45
|
* @param path
|
|
51
46
|
* @param buffer
|
|
52
47
|
*/
|
|
53
|
-
|
|
54
|
-
protected statFileInode(inode: IndexFileInode<Stats>, path: string): Promise<Stats>;
|
|
55
|
-
protected openFileInode(inode: IndexFileInode<Stats>, path: string, flag: string): Promise<NoSyncFile<this>>;
|
|
56
|
-
private _getRemotePath;
|
|
48
|
+
preload(path: string, buffer: Uint8Array): void;
|
|
57
49
|
/**
|
|
58
|
-
*
|
|
50
|
+
* @todo Be lazier about actually requesting the data?
|
|
59
51
|
*/
|
|
60
|
-
protected
|
|
61
|
-
protected
|
|
62
|
-
protected _fetchFile(path: string, type: 'buffer' | 'json'): Promise<object>;
|
|
63
|
-
/**
|
|
64
|
-
* Only requests the HEAD content, for the file size.
|
|
65
|
-
*/
|
|
66
|
-
protected _fetchSize(path: string): Promise<number>;
|
|
52
|
+
protected getData(path: string, stats: Stats): Promise<Uint8Array>;
|
|
53
|
+
protected getDataSync(path: string, stats: Stats): Uint8Array;
|
|
67
54
|
}
|
|
68
55
|
export declare const Fetch: {
|
|
69
56
|
readonly name: "Fetch";
|
|
@@ -71,7 +58,7 @@ export declare const Fetch: {
|
|
|
71
58
|
readonly index: {
|
|
72
59
|
readonly type: readonly ["string", "object"];
|
|
73
60
|
readonly required: false;
|
|
74
|
-
readonly description: "URL to a file index as a JSON file or the file index object itself, generated with the
|
|
61
|
+
readonly description: "URL to a file index as a JSON file or the file index object itself, generated with the make-index script. Defaults to `index.json`.";
|
|
75
62
|
};
|
|
76
63
|
readonly baseUrl: {
|
|
77
64
|
readonly type: "string";
|
package/dist/backends/fetch.js
CHANGED
|
@@ -1,85 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Stats } from '../stats.js';
|
|
4
|
-
import { FileIndex, AsyncIndexFS } from './Index.js';
|
|
5
|
-
/**
|
|
6
|
-
* @hidden
|
|
7
|
-
*/
|
|
8
|
-
function convertError(e) {
|
|
9
|
-
throw new ErrnoError(Errno.EIO, e.message);
|
|
10
|
-
}
|
|
1
|
+
import { Errno, ErrnoError } from '../error.js';
|
|
2
|
+
import { IndexFS } from './index/fs.js';
|
|
11
3
|
async function fetchFile(path, type) {
|
|
12
|
-
const response = await fetch(path).catch(
|
|
4
|
+
const response = await fetch(path).catch(e => {
|
|
5
|
+
throw new ErrnoError(Errno.EIO, e.message);
|
|
6
|
+
});
|
|
13
7
|
if (!response.ok) {
|
|
14
8
|
throw new ErrnoError(Errno.EIO, 'fetch failed: response returned code ' + response.status);
|
|
15
9
|
}
|
|
16
10
|
switch (type) {
|
|
17
11
|
case 'buffer':
|
|
18
|
-
const arrayBuffer = await response.arrayBuffer().catch(
|
|
12
|
+
const arrayBuffer = await response.arrayBuffer().catch(e => {
|
|
13
|
+
throw new ErrnoError(Errno.EIO, e.message);
|
|
14
|
+
});
|
|
19
15
|
return new Uint8Array(arrayBuffer);
|
|
20
16
|
case 'json':
|
|
21
|
-
return response.json().catch(
|
|
17
|
+
return response.json().catch(e => {
|
|
18
|
+
throw new ErrnoError(Errno.EIO, e.message);
|
|
19
|
+
});
|
|
22
20
|
default:
|
|
23
21
|
throw new ErrnoError(Errno.EINVAL, 'Invalid download type: ' + type);
|
|
24
22
|
}
|
|
25
23
|
}
|
|
26
24
|
/**
|
|
27
|
-
*
|
|
28
|
-
* @hidden
|
|
29
|
-
*/
|
|
30
|
-
async function fetchSize(path) {
|
|
31
|
-
const response = await fetch(path, { method: 'HEAD' }).catch(convertError);
|
|
32
|
-
if (!response.ok) {
|
|
33
|
-
throw new ErrnoError(Errno.EIO, 'fetch failed: HEAD response returned code ' + response.status);
|
|
34
|
-
}
|
|
35
|
-
return parseInt(response.headers.get('Content-Length') || '-1', 10);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* A simple filesystem backed by HTTP using the fetch API.
|
|
25
|
+
* A simple filesystem backed by HTTP using the `fetch` API.
|
|
39
26
|
*
|
|
40
27
|
*
|
|
41
|
-
*
|
|
28
|
+
* Index objects look like the following:
|
|
42
29
|
*
|
|
43
30
|
* ```json
|
|
44
31
|
* {
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* }
|
|
52
|
-
* }
|
|
32
|
+
* "version": 1,
|
|
33
|
+
* "entries": {
|
|
34
|
+
* "/home": { ... },
|
|
35
|
+
* "/home/jvilk": { ... },
|
|
36
|
+
* "/home/james": { ... }
|
|
37
|
+
* }
|
|
53
38
|
* }
|
|
54
39
|
* ```
|
|
55
40
|
*
|
|
56
|
-
*
|
|
41
|
+
* Each entry contains the stats associated with the file.
|
|
57
42
|
*/
|
|
58
|
-
export class FetchFS extends
|
|
59
|
-
async
|
|
60
|
-
if (
|
|
61
|
-
this._index = FileIndex.FromListing(index);
|
|
43
|
+
export class FetchFS extends IndexFS {
|
|
44
|
+
async ready() {
|
|
45
|
+
if (this._isInitialized) {
|
|
62
46
|
return;
|
|
63
47
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
48
|
+
await super.ready();
|
|
49
|
+
/**
|
|
50
|
+
* Iterate over all of the files and cache their contents
|
|
51
|
+
*/
|
|
52
|
+
for (const [path, stats] of this.index.files()) {
|
|
53
|
+
await this.getData(path, stats);
|
|
67
54
|
}
|
|
68
|
-
catch (e) {
|
|
69
|
-
throw new ErrnoError(Errno.EINVAL, 'Invalid or unavailable file listing tree');
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
async ready() {
|
|
73
|
-
await this._init;
|
|
74
55
|
}
|
|
75
56
|
constructor({ index = 'index.json', baseUrl = '' }) {
|
|
76
|
-
super(
|
|
57
|
+
super(typeof index != 'string' ? index : fetchFile(index, 'json'));
|
|
77
58
|
// prefix url must end in a directory separator.
|
|
78
59
|
if (baseUrl.at(-1) != '/') {
|
|
79
60
|
baseUrl += '/';
|
|
80
61
|
}
|
|
81
|
-
this.
|
|
82
|
-
this._init = this._initialize(index);
|
|
62
|
+
this.baseUrl = baseUrl;
|
|
83
63
|
}
|
|
84
64
|
metadata() {
|
|
85
65
|
return {
|
|
@@ -88,63 +68,38 @@ export class FetchFS extends AsyncIndexFS {
|
|
|
88
68
|
readonly: true,
|
|
89
69
|
};
|
|
90
70
|
}
|
|
91
|
-
empty() {
|
|
92
|
-
for (const file of this._index.files()) {
|
|
93
|
-
delete file.data.fileData;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
71
|
/**
|
|
97
|
-
*
|
|
72
|
+
* Preload the given file into the index.
|
|
98
73
|
* @param path
|
|
99
74
|
* @param buffer
|
|
100
75
|
*/
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
if (!
|
|
76
|
+
preload(path, buffer) {
|
|
77
|
+
const stats = this.index.get(path);
|
|
78
|
+
if (!stats) {
|
|
104
79
|
throw ErrnoError.With('ENOENT', path, 'preloadFile');
|
|
105
80
|
}
|
|
106
|
-
if (!
|
|
81
|
+
if (!stats.isFile()) {
|
|
107
82
|
throw ErrnoError.With('EISDIR', path, 'preloadFile');
|
|
108
83
|
}
|
|
109
|
-
const stats = inode.data;
|
|
110
84
|
stats.size = buffer.length;
|
|
111
85
|
stats.fileData = buffer;
|
|
112
86
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
stats.size = await this._fetchSize(path);
|
|
118
|
-
}
|
|
119
|
-
return stats;
|
|
120
|
-
}
|
|
121
|
-
async openFileInode(inode, path, flag) {
|
|
122
|
-
const stats = inode.data;
|
|
123
|
-
// Use existing file contents. This maintains the previously-used flag.
|
|
87
|
+
/**
|
|
88
|
+
* @todo Be lazier about actually requesting the data?
|
|
89
|
+
*/
|
|
90
|
+
async getData(path, stats) {
|
|
124
91
|
if (stats.fileData) {
|
|
125
|
-
return
|
|
92
|
+
return stats.fileData;
|
|
126
93
|
}
|
|
127
|
-
|
|
128
|
-
const data = await this._fetchFile(path, 'buffer');
|
|
129
|
-
// we don't initially have file sizes
|
|
130
|
-
stats.size = data.length;
|
|
94
|
+
const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer');
|
|
131
95
|
stats.fileData = data;
|
|
132
|
-
return
|
|
96
|
+
return data;
|
|
133
97
|
}
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
98
|
+
getDataSync(path, stats) {
|
|
99
|
+
if (stats.fileData) {
|
|
100
|
+
return stats.fileData;
|
|
137
101
|
}
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
_fetchFile(path, type) {
|
|
141
|
-
return fetchFile(this._getRemotePath(path), type);
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Only requests the HEAD content, for the file size.
|
|
145
|
-
*/
|
|
146
|
-
_fetchSize(path) {
|
|
147
|
-
return fetchSize(this._getRemotePath(path));
|
|
102
|
+
throw new ErrnoError(Errno.ENODATA, '', path, 'getData');
|
|
148
103
|
}
|
|
149
104
|
}
|
|
150
105
|
export const Fetch = {
|
|
@@ -153,7 +108,7 @@ export const Fetch = {
|
|
|
153
108
|
index: {
|
|
154
109
|
type: ['string', 'object'],
|
|
155
110
|
required: false,
|
|
156
|
-
description: 'URL to a file index as a JSON file or the file index object itself, generated with the
|
|
111
|
+
description: 'URL to a file index as a JSON file or the file index object itself, generated with the make-index script. Defaults to `index.json`.',
|
|
157
112
|
},
|
|
158
113
|
baseUrl: {
|
|
159
114
|
type: 'string',
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Cred } from '../../cred.js';
|
|
2
|
+
import { NoSyncFile } from '../../file.js';
|
|
3
|
+
import { FileSystem } from '../../filesystem.js';
|
|
4
|
+
import type { Stats } from '../../stats.js';
|
|
5
|
+
import { Index, IndexData } from './index.js';
|
|
6
|
+
declare const IndexFS_base: (abstract new (...args: any[]) => {
|
|
7
|
+
metadata(): import("../../filesystem.js").FileSystemMetadata;
|
|
8
|
+
rename(oldPath: string, newPath: string, cred: Cred): Promise<void>;
|
|
9
|
+
renameSync(oldPath: string, newPath: string, cred: Cred): void;
|
|
10
|
+
createFile(path: string, flag: string, mode: number, cred: Cred): Promise<import("../../file.js").File>;
|
|
11
|
+
createFileSync(path: string, flag: string, mode: number, cred: Cred): import("../../file.js").File;
|
|
12
|
+
unlink(path: string, cred: Cred): Promise<void>;
|
|
13
|
+
unlinkSync(path: string, cred: Cred): void;
|
|
14
|
+
rmdir(path: string, cred: Cred): Promise<void>;
|
|
15
|
+
rmdirSync(path: string, cred: Cred): void;
|
|
16
|
+
mkdir(path: string, mode: number, cred: Cred): Promise<void>;
|
|
17
|
+
mkdirSync(path: string, mode: number, cred: Cred): void;
|
|
18
|
+
link(srcpath: string, dstpath: string, cred: Cred): Promise<void>;
|
|
19
|
+
linkSync(srcpath: string, dstpath: string, cred: Cred): void;
|
|
20
|
+
sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
|
|
21
|
+
syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
|
|
22
|
+
ready(): Promise<void>;
|
|
23
|
+
stat(path: string, cred: Cred): Promise<Stats>;
|
|
24
|
+
statSync(path: string, cred: Cred): Stats;
|
|
25
|
+
openFile(path: string, flag: string, cred: Cred): Promise<import("../../file.js").File>;
|
|
26
|
+
openFileSync(path: string, flag: string, cred: Cred): import("../../file.js").File;
|
|
27
|
+
readdir(path: string, cred: Cred): Promise<string[]>;
|
|
28
|
+
readdirSync(path: string, cred: Cred): string[];
|
|
29
|
+
exists(path: string, cred: Cred): Promise<boolean>;
|
|
30
|
+
existsSync(path: string, cred: Cred): boolean;
|
|
31
|
+
}) & typeof FileSystem;
|
|
32
|
+
export declare abstract class IndexFS extends IndexFS_base {
|
|
33
|
+
private indexData;
|
|
34
|
+
protected index: Index;
|
|
35
|
+
protected _isInitialized: boolean;
|
|
36
|
+
ready(): Promise<void>;
|
|
37
|
+
constructor(indexData: IndexData | Promise<IndexData>);
|
|
38
|
+
reloadFiles(): Promise<void>;
|
|
39
|
+
reloadFilesSync(): void;
|
|
40
|
+
stat(path: string): Promise<Stats>;
|
|
41
|
+
statSync(path: string): Stats;
|
|
42
|
+
openFile(path: string, flag: string, cred: Cred): Promise<NoSyncFile<this>>;
|
|
43
|
+
openFileSync(path: string, flag: string, cred: Cred): NoSyncFile<this>;
|
|
44
|
+
readdir(path: string): Promise<string[]>;
|
|
45
|
+
readdirSync(path: string): string[];
|
|
46
|
+
protected abstract getData(path: string, stats: Stats): Promise<Uint8Array>;
|
|
47
|
+
protected abstract getDataSync(path: string, stats: Stats): Uint8Array;
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ErrnoError, Errno } from '../../error.js';
|
|
2
|
+
import { NoSyncFile, isWriteable, flagToMode } from '../../file.js';
|
|
3
|
+
import { Readonly, FileSystem } from '../../filesystem.js';
|
|
4
|
+
import { decode } from '../../utils.js';
|
|
5
|
+
import { Index } from './index.js';
|
|
6
|
+
export class IndexFS extends Readonly(FileSystem) {
|
|
7
|
+
async ready() {
|
|
8
|
+
await super.ready();
|
|
9
|
+
if (this._isInitialized) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
this.index.fromJSON(await this.indexData);
|
|
13
|
+
this._isInitialized = true;
|
|
14
|
+
}
|
|
15
|
+
constructor(indexData) {
|
|
16
|
+
super();
|
|
17
|
+
this.indexData = indexData;
|
|
18
|
+
this.index = new Index();
|
|
19
|
+
this._isInitialized = false;
|
|
20
|
+
}
|
|
21
|
+
async reloadFiles() {
|
|
22
|
+
for (const [path, stats] of this.index.files()) {
|
|
23
|
+
delete stats.fileData;
|
|
24
|
+
stats.fileData = await this.getData(path, stats);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
reloadFilesSync() {
|
|
28
|
+
for (const [path, stats] of this.index.files()) {
|
|
29
|
+
delete stats.fileData;
|
|
30
|
+
stats.fileData = this.getDataSync(path, stats);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async stat(path) {
|
|
34
|
+
return this.statSync(path);
|
|
35
|
+
}
|
|
36
|
+
statSync(path) {
|
|
37
|
+
if (!this.index.has(path)) {
|
|
38
|
+
throw ErrnoError.With('ENOENT', path, 'stat');
|
|
39
|
+
}
|
|
40
|
+
return this.index.get(path);
|
|
41
|
+
}
|
|
42
|
+
async openFile(path, flag, cred) {
|
|
43
|
+
if (isWriteable(flag)) {
|
|
44
|
+
// You can't write to files on this file system.
|
|
45
|
+
throw new ErrnoError(Errno.EPERM, path);
|
|
46
|
+
}
|
|
47
|
+
// Check if the path exists, and is a file.
|
|
48
|
+
const stats = this.index.get(path);
|
|
49
|
+
if (!stats) {
|
|
50
|
+
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
51
|
+
}
|
|
52
|
+
if (!stats.hasAccess(flagToMode(flag), cred)) {
|
|
53
|
+
throw ErrnoError.With('EACCES', path, 'openFile');
|
|
54
|
+
}
|
|
55
|
+
return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : await this.getData(path, stats));
|
|
56
|
+
}
|
|
57
|
+
openFileSync(path, flag, cred) {
|
|
58
|
+
if (isWriteable(flag)) {
|
|
59
|
+
// You can't write to files on this file system.
|
|
60
|
+
throw new ErrnoError(Errno.EPERM, path);
|
|
61
|
+
}
|
|
62
|
+
// Check if the path exists, and is a file.
|
|
63
|
+
const stats = this.index.get(path);
|
|
64
|
+
if (!stats) {
|
|
65
|
+
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
66
|
+
}
|
|
67
|
+
if (!stats.hasAccess(flagToMode(flag), cred)) {
|
|
68
|
+
throw ErrnoError.With('EACCES', path, 'openFile');
|
|
69
|
+
}
|
|
70
|
+
return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : this.getDataSync(path, stats));
|
|
71
|
+
}
|
|
72
|
+
async readdir(path) {
|
|
73
|
+
return this.readdirSync(path);
|
|
74
|
+
}
|
|
75
|
+
readdirSync(path) {
|
|
76
|
+
// Check if it exists.
|
|
77
|
+
const stats = this.index.get(path);
|
|
78
|
+
if (!stats) {
|
|
79
|
+
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
80
|
+
}
|
|
81
|
+
if (!stats.isDirectory()) {
|
|
82
|
+
throw ErrnoError.With('ENOTDIR', path, 'readdir');
|
|
83
|
+
}
|
|
84
|
+
return JSON.parse(decode(stats.fileData));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Stats, StatsLike } from '../../stats.js';
|
|
2
|
+
export interface IndexData {
|
|
3
|
+
version: 1;
|
|
4
|
+
entries: Record<string, StatsLike<number>>;
|
|
5
|
+
}
|
|
6
|
+
export declare const version = 1;
|
|
7
|
+
/**
|
|
8
|
+
* An index of files
|
|
9
|
+
*/
|
|
10
|
+
export declare class Index extends Map<string, Stats> {
|
|
11
|
+
constructor();
|
|
12
|
+
/**
|
|
13
|
+
* Convience method
|
|
14
|
+
*/
|
|
15
|
+
files(): Map<string, Stats>;
|
|
16
|
+
/**
|
|
17
|
+
* Converts the index to JSON
|
|
18
|
+
*/
|
|
19
|
+
toJSON(): IndexData;
|
|
20
|
+
/**
|
|
21
|
+
* Converts the index to a string
|
|
22
|
+
*/
|
|
23
|
+
toString(): string;
|
|
24
|
+
/**
|
|
25
|
+
* Returns the files in the directory `dir`.
|
|
26
|
+
* This is expensive so it is only called once per directory.
|
|
27
|
+
*/
|
|
28
|
+
protected dirEntries(dir: string): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Loads the index from JSON data
|
|
31
|
+
*/
|
|
32
|
+
fromJSON(json: IndexData): void;
|
|
33
|
+
/**
|
|
34
|
+
* Parses an index from a string
|
|
35
|
+
*/
|
|
36
|
+
static parse(data: string): Index;
|
|
37
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { isJSON } from 'utilium';
|
|
2
|
+
import { Errno, ErrnoError } from '../../error.js';
|
|
3
|
+
import { Stats } from '../../stats.js';
|
|
4
|
+
import { encode } from '../../utils.js';
|
|
5
|
+
import { basename, dirname } from '../../emulation/path.js';
|
|
6
|
+
export const version = 1;
|
|
7
|
+
/**
|
|
8
|
+
* An index of files
|
|
9
|
+
*/
|
|
10
|
+
export class Index extends Map {
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Convience method
|
|
16
|
+
*/
|
|
17
|
+
files() {
|
|
18
|
+
const files = new Map();
|
|
19
|
+
for (const [path, stats] of this) {
|
|
20
|
+
if (stats.isFile()) {
|
|
21
|
+
files.set(path, stats);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return files;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Converts the index to JSON
|
|
28
|
+
*/
|
|
29
|
+
toJSON() {
|
|
30
|
+
return {
|
|
31
|
+
version,
|
|
32
|
+
entries: Object.fromEntries(this),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Converts the index to a string
|
|
37
|
+
*/
|
|
38
|
+
toString() {
|
|
39
|
+
return JSON.stringify(this.toJSON());
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Returns the files in the directory `dir`.
|
|
43
|
+
* This is expensive so it is only called once per directory.
|
|
44
|
+
*/
|
|
45
|
+
dirEntries(dir) {
|
|
46
|
+
const entries = [];
|
|
47
|
+
for (const entry of this.keys()) {
|
|
48
|
+
if (dirname(entry) == dir) {
|
|
49
|
+
entries.push(basename(entry));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return entries;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Loads the index from JSON data
|
|
56
|
+
*/
|
|
57
|
+
fromJSON(json) {
|
|
58
|
+
if (json.version != version) {
|
|
59
|
+
throw new ErrnoError(Errno.EINVAL, 'Index version mismatch');
|
|
60
|
+
}
|
|
61
|
+
this.clear();
|
|
62
|
+
for (const [path, data] of Object.entries(json.entries)) {
|
|
63
|
+
const stats = new Stats(data);
|
|
64
|
+
if (stats.isDirectory()) {
|
|
65
|
+
stats.fileData = encode(JSON.stringify(this.dirEntries(path)));
|
|
66
|
+
}
|
|
67
|
+
this.set(path, stats);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Parses an index from a string
|
|
72
|
+
*/
|
|
73
|
+
static parse(data) {
|
|
74
|
+
if (!isJSON(data)) {
|
|
75
|
+
throw new ErrnoError(Errno.EINVAL, 'Invalid JSON');
|
|
76
|
+
}
|
|
77
|
+
const json = JSON.parse(data);
|
|
78
|
+
const index = new Index();
|
|
79
|
+
index.fromJSON(json);
|
|
80
|
+
return index;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -14,7 +14,7 @@ import type { Stats } from '../stats.js';
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class LockedFS<FS extends FileSystem> implements FileSystem {
|
|
16
16
|
readonly fs: FS;
|
|
17
|
-
private
|
|
17
|
+
private mutex;
|
|
18
18
|
constructor(fs: FS);
|
|
19
19
|
ready(): Promise<void>;
|
|
20
20
|
metadata(): FileSystemMetadata;
|