@zenfs/core 0.11.2 → 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 +19 -32
- package/dist/backends/fetch.js +38 -85
- 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/browser.min.js +4 -4
- package/dist/browser.min.js.map +4 -4
- package/dist/file.d.ts +1 -1
- package/dist/file.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/stats.d.ts +10 -10
- package/package.json +1 -1
- package/scripts/make-index.js +31 -19
- package/src/backends/fetch.ts +42 -101
- 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/file.ts +2 -2
- package/src/index.ts +1 -1
- 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/file.d.ts
CHANGED
|
@@ -357,7 +357,7 @@ export declare class PreloadFile<FS extends FileSystem> extends File {
|
|
|
357
357
|
* For the filesystems which do not sync to anything..
|
|
358
358
|
*/
|
|
359
359
|
export declare class NoSyncFile<T extends FileSystem> extends PreloadFile<T> {
|
|
360
|
-
constructor(
|
|
360
|
+
constructor(fs: T, path: string, flag: string, stats: Stats, contents?: Uint8Array);
|
|
361
361
|
/**
|
|
362
362
|
* Asynchronous sync. Doesn't do anything, simply calls the cb.
|
|
363
363
|
*/
|
package/dist/file.js
CHANGED
|
@@ -474,8 +474,8 @@ export class PreloadFile extends File {
|
|
|
474
474
|
* For the filesystems which do not sync to anything..
|
|
475
475
|
*/
|
|
476
476
|
export class NoSyncFile extends PreloadFile {
|
|
477
|
-
constructor(
|
|
478
|
-
super(
|
|
477
|
+
constructor(fs, path, flag, stats, contents) {
|
|
478
|
+
super(fs, path, flag, stats, contents);
|
|
479
479
|
}
|
|
480
480
|
/**
|
|
481
481
|
* Asynchronous sync. Doesn't do anything, simply calls the cb.
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export * from './error.js';
|
|
|
2
2
|
export * from './backends/port/fs.js';
|
|
3
3
|
export * from './backends/fetch.js';
|
|
4
4
|
export * from './backends/memory.js';
|
|
5
|
-
export * from './backends/
|
|
5
|
+
export * from './backends/index/fs.js';
|
|
6
6
|
export * from './backends/locked.js';
|
|
7
7
|
export * from './backends/overlay.js';
|
|
8
8
|
export * from './backends/store/fs.js';
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export * from './error.js';
|
|
|
2
2
|
export * from './backends/port/fs.js';
|
|
3
3
|
export * from './backends/fetch.js';
|
|
4
4
|
export * from './backends/memory.js';
|
|
5
|
-
export * from './backends/
|
|
5
|
+
export * from './backends/index/fs.js';
|
|
6
6
|
export * from './backends/locked.js';
|
|
7
7
|
export * from './backends/overlay.js';
|
|
8
8
|
export * from './backends/store/fs.js';
|
package/dist/stats.d.ts
CHANGED
|
@@ -12,45 +12,45 @@ export declare enum FileType {
|
|
|
12
12
|
/**
|
|
13
13
|
*
|
|
14
14
|
*/
|
|
15
|
-
export interface StatsLike {
|
|
15
|
+
export interface StatsLike<T extends number | bigint = number | bigint> {
|
|
16
16
|
/**
|
|
17
17
|
* Size of the item in bytes.
|
|
18
18
|
* For directories/symlinks, this is normally the size of the struct that represents the item.
|
|
19
19
|
*/
|
|
20
|
-
size:
|
|
20
|
+
size: T;
|
|
21
21
|
/**
|
|
22
22
|
* Unix-style file mode (e.g. 0o644) that includes the item type
|
|
23
23
|
* Type of the item can be FILE, DIRECTORY, SYMLINK, or SOCKET
|
|
24
24
|
*/
|
|
25
|
-
mode:
|
|
25
|
+
mode: T;
|
|
26
26
|
/**
|
|
27
27
|
* time of last access, in milliseconds since epoch
|
|
28
28
|
*/
|
|
29
|
-
atimeMs:
|
|
29
|
+
atimeMs: T;
|
|
30
30
|
/**
|
|
31
31
|
* time of last modification, in milliseconds since epoch
|
|
32
32
|
*/
|
|
33
|
-
mtimeMs:
|
|
33
|
+
mtimeMs: T;
|
|
34
34
|
/**
|
|
35
35
|
* time of last time file status was changed, in milliseconds since epoch
|
|
36
36
|
*/
|
|
37
|
-
ctimeMs:
|
|
37
|
+
ctimeMs: T;
|
|
38
38
|
/**
|
|
39
39
|
* time of file creation, in milliseconds since epoch
|
|
40
40
|
*/
|
|
41
|
-
birthtimeMs:
|
|
41
|
+
birthtimeMs: T;
|
|
42
42
|
/**
|
|
43
43
|
* the id of the user that owns the file
|
|
44
44
|
*/
|
|
45
|
-
uid:
|
|
45
|
+
uid: T;
|
|
46
46
|
/**
|
|
47
47
|
* the id of the group that owns the file
|
|
48
48
|
*/
|
|
49
|
-
gid:
|
|
49
|
+
gid: T;
|
|
50
50
|
/**
|
|
51
51
|
* the ino
|
|
52
52
|
*/
|
|
53
|
-
ino:
|
|
53
|
+
ino: T;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
56
|
* Provides information about a particular entry in the file system.
|
package/package.json
CHANGED
package/scripts/make-index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { parseArgs } from 'util';
|
|
3
3
|
import { statSync, readdirSync, writeFileSync } from 'fs';
|
|
4
4
|
import { join } from 'path/posix';
|
|
5
|
-
import { resolve } from 'path';
|
|
5
|
+
import { relative, resolve } from 'path';
|
|
6
6
|
import { minimatch } from 'minimatch';
|
|
7
7
|
|
|
8
8
|
const { values: options, positionals } = parseArgs({
|
|
@@ -38,10 +38,12 @@ if (options.quiet && options.verbose) {
|
|
|
38
38
|
process.exit();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function
|
|
41
|
+
function fixSlash(path) {
|
|
42
42
|
return path.replaceAll('\\', '/');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
const resolvedRoot = root || '.';
|
|
46
|
+
|
|
45
47
|
const colors = {
|
|
46
48
|
reset: 0,
|
|
47
49
|
black: 30,
|
|
@@ -66,28 +68,31 @@ function color(color, text) {
|
|
|
66
68
|
return `\x1b[${colors[color]}m${text}\x1b[0m`;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
const entries = new Map();
|
|
72
|
+
|
|
73
|
+
function computeEntries(path) {
|
|
70
74
|
try {
|
|
71
|
-
if (options.
|
|
75
|
+
if (options.ignore.some(pattern => minimatch(path, pattern))) {
|
|
76
|
+
if (!options.quiet) console.log(`${color('yellow', 'skip')} ${path}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
72
80
|
const stats = statSync(path);
|
|
81
|
+
entries.set('/' + relative(resolvedRoot, path), stats);
|
|
73
82
|
|
|
74
83
|
if (stats.isFile()) {
|
|
75
|
-
if (options.verbose)
|
|
76
|
-
|
|
84
|
+
if (options.verbose) {
|
|
85
|
+
console.log(`${color('green', 'file')} ${path}`);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
77
88
|
}
|
|
78
89
|
|
|
79
|
-
const entries = {};
|
|
80
90
|
for (const file of readdirSync(path)) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
entries[file] = listing(full, seen);
|
|
91
|
+
computeEntries(join(path, file));
|
|
92
|
+
}
|
|
93
|
+
if (options.verbose) {
|
|
94
|
+
console.log(`${color('bright_green', ' dir')} ${path}`);
|
|
88
95
|
}
|
|
89
|
-
if (options.verbose) console.log(`${color('bright_green', ' dir')} ${path}`);
|
|
90
|
-
return entries;
|
|
91
96
|
} catch (e) {
|
|
92
97
|
if (!options.quiet) {
|
|
93
98
|
console.log(`${color('red', 'fail')} ${path}: ${e.message}`);
|
|
@@ -95,7 +100,14 @@ function listing(path, seen = new Set()) {
|
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
if (!options.quiet)
|
|
103
|
+
computeEntries(resolvedRoot);
|
|
104
|
+
if (!options.quiet) {
|
|
105
|
+
console.log('Generated listing for ' + fixSlash(resolve(root)));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const index = {
|
|
109
|
+
version: 1,
|
|
110
|
+
entries: Object.fromEntries(entries),
|
|
111
|
+
};
|
|
100
112
|
|
|
101
|
-
writeFileSync(options.output, JSON.stringify(
|
|
113
|
+
writeFileSync(options.output, JSON.stringify(index));
|
package/src/backends/fetch.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { NoSyncFile } from '../file.js';
|
|
1
|
+
import { Errno, ErrnoError } from '../error.js';
|
|
3
2
|
import type { FileSystemMetadata } from '../filesystem.js';
|
|
4
3
|
import { Stats } from '../stats.js';
|
|
5
|
-
import { type ListingTree, FileIndex, type IndexFileInode, AsyncIndexFS } from './Index.js';
|
|
6
4
|
import type { Backend } from './backend.js';
|
|
5
|
+
import { IndexFS } from './index/fs.js';
|
|
6
|
+
import type { IndexData } from './index/index.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Asynchronously download a file as a buffer or a JSON object.
|
|
@@ -37,20 +37,6 @@ async function fetchFile<T extends object>(path: string, type: 'buffer' | 'json'
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/**
|
|
41
|
-
* Asynchronously retrieves the size of the given file in bytes.
|
|
42
|
-
* @hidden
|
|
43
|
-
*/
|
|
44
|
-
async function fetchSize(path: string): Promise<number> {
|
|
45
|
-
const response = await fetch(path, { method: 'HEAD' }).catch(e => {
|
|
46
|
-
throw new ErrnoError(Errno.EIO, e.message);
|
|
47
|
-
});
|
|
48
|
-
if (!response.ok) {
|
|
49
|
-
throw new ErrnoError(Errno.EIO, 'fetch failed: HEAD response returned code ' + response.status);
|
|
50
|
-
}
|
|
51
|
-
return parseInt(response.headers.get('Content-Length') || '-1', 10);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
40
|
/**
|
|
55
41
|
* Configuration options for FetchFS.
|
|
56
42
|
*/
|
|
@@ -59,7 +45,7 @@ export interface FetchOptions {
|
|
|
59
45
|
* URL to a file index as a JSON file or the file index object itself.
|
|
60
46
|
* Defaults to `index.json`.
|
|
61
47
|
*/
|
|
62
|
-
index?: string |
|
|
48
|
+
index?: string | IndexData;
|
|
63
49
|
|
|
64
50
|
/** Used as the URL prefix for fetched files.
|
|
65
51
|
* Default: Fetch files relative to the index.
|
|
@@ -68,59 +54,48 @@ export interface FetchOptions {
|
|
|
68
54
|
}
|
|
69
55
|
|
|
70
56
|
/**
|
|
71
|
-
* A simple filesystem backed by HTTP using the fetch API.
|
|
57
|
+
* A simple filesystem backed by HTTP using the `fetch` API.
|
|
72
58
|
*
|
|
73
59
|
*
|
|
74
|
-
*
|
|
60
|
+
* Index objects look like the following:
|
|
75
61
|
*
|
|
76
62
|
* ```json
|
|
77
63
|
* {
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* }
|
|
85
|
-
* }
|
|
64
|
+
* "version": 1,
|
|
65
|
+
* "entries": {
|
|
66
|
+
* "/home": { ... },
|
|
67
|
+
* "/home/jvilk": { ... },
|
|
68
|
+
* "/home/james": { ... }
|
|
69
|
+
* }
|
|
86
70
|
* }
|
|
87
71
|
* ```
|
|
88
72
|
*
|
|
89
|
-
*
|
|
73
|
+
* Each entry contains the stats associated with the file.
|
|
90
74
|
*/
|
|
91
|
-
export class FetchFS extends
|
|
92
|
-
public readonly
|
|
75
|
+
export class FetchFS extends IndexFS {
|
|
76
|
+
public readonly baseUrl: string;
|
|
93
77
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
protected async _initialize(index: string | ListingTree): Promise<void> {
|
|
97
|
-
if (typeof index != 'string') {
|
|
98
|
-
this._index = FileIndex.FromListing(index);
|
|
78
|
+
public async ready(): Promise<void> {
|
|
79
|
+
if (this._isInitialized) {
|
|
99
80
|
return;
|
|
100
81
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
82
|
+
await super.ready();
|
|
83
|
+
/**
|
|
84
|
+
* Iterate over all of the files and cache their contents
|
|
85
|
+
*/
|
|
86
|
+
for (const [path, stats] of this.index.files()) {
|
|
87
|
+
await this.getData(path, stats);
|
|
107
88
|
}
|
|
108
89
|
}
|
|
109
90
|
|
|
110
|
-
public async ready(): Promise<void> {
|
|
111
|
-
await this._init;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
91
|
constructor({ index = 'index.json', baseUrl = '' }: FetchOptions) {
|
|
115
|
-
super(
|
|
92
|
+
super(typeof index != 'string' ? index : fetchFile<IndexData>(index, 'json'));
|
|
116
93
|
|
|
117
94
|
// prefix url must end in a directory separator.
|
|
118
95
|
if (baseUrl.at(-1) != '/') {
|
|
119
96
|
baseUrl += '/';
|
|
120
97
|
}
|
|
121
|
-
this.
|
|
122
|
-
|
|
123
|
-
this._init = this._initialize(index);
|
|
98
|
+
this.baseUrl = baseUrl;
|
|
124
99
|
}
|
|
125
100
|
|
|
126
101
|
public metadata(): FileSystemMetadata {
|
|
@@ -131,76 +106,42 @@ export class FetchFS extends AsyncIndexFS<Stats> {
|
|
|
131
106
|
};
|
|
132
107
|
}
|
|
133
108
|
|
|
134
|
-
public empty(): void {
|
|
135
|
-
for (const file of this._index.files()) {
|
|
136
|
-
delete file.data!.fileData;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
109
|
/**
|
|
141
|
-
*
|
|
110
|
+
* Preload the given file into the index.
|
|
142
111
|
* @param path
|
|
143
112
|
* @param buffer
|
|
144
113
|
*/
|
|
145
|
-
public
|
|
146
|
-
const
|
|
147
|
-
if (!
|
|
114
|
+
public preload(path: string, buffer: Uint8Array): void {
|
|
115
|
+
const stats = this.index.get(path);
|
|
116
|
+
if (!stats) {
|
|
148
117
|
throw ErrnoError.With('ENOENT', path, 'preloadFile');
|
|
149
118
|
}
|
|
150
|
-
if (!
|
|
119
|
+
if (!stats.isFile()) {
|
|
151
120
|
throw ErrnoError.With('EISDIR', path, 'preloadFile');
|
|
152
121
|
}
|
|
153
|
-
const stats = inode.data!;
|
|
154
122
|
stats.size = buffer.length;
|
|
155
123
|
stats.fileData = buffer;
|
|
156
124
|
}
|
|
157
125
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
stats.size = await this._fetchSize(path);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return stats;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
protected async openFileInode(inode: IndexFileInode<Stats>, path: string, flag: string): Promise<NoSyncFile<this>> {
|
|
169
|
-
const stats = inode.data!;
|
|
170
|
-
// Use existing file contents. This maintains the previously-used flag.
|
|
126
|
+
/**
|
|
127
|
+
* @todo Be lazier about actually requesting the data?
|
|
128
|
+
*/
|
|
129
|
+
protected async getData(path: string, stats: Stats): Promise<Uint8Array> {
|
|
171
130
|
if (stats.fileData) {
|
|
172
|
-
return
|
|
131
|
+
return stats.fileData;
|
|
173
132
|
}
|
|
174
|
-
|
|
175
|
-
const data = await this.
|
|
176
|
-
// we don't initially have file sizes
|
|
177
|
-
stats.size = data.length;
|
|
133
|
+
|
|
134
|
+
const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer');
|
|
178
135
|
stats.fileData = data;
|
|
179
|
-
return
|
|
136
|
+
return data;
|
|
180
137
|
}
|
|
181
138
|
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
139
|
+
protected getDataSync(path: string, stats: Stats): Uint8Array {
|
|
140
|
+
if (stats.fileData) {
|
|
141
|
+
return stats.fileData;
|
|
185
142
|
}
|
|
186
|
-
return this.prefixUrl + filePath;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Asynchronously download the given file.
|
|
191
|
-
*/
|
|
192
|
-
protected _fetchFile(path: string, type: 'buffer'): Promise<Uint8Array>;
|
|
193
|
-
protected _fetchFile(path: string, type: 'json'): Promise<object>;
|
|
194
|
-
protected _fetchFile(path: string, type: 'buffer' | 'json'): Promise<object>;
|
|
195
|
-
protected _fetchFile(path: string, type: 'buffer' | 'json'): Promise<object> {
|
|
196
|
-
return fetchFile(this._getRemotePath(path), type);
|
|
197
|
-
}
|
|
198
143
|
|
|
199
|
-
|
|
200
|
-
* Only requests the HEAD content, for the file size.
|
|
201
|
-
*/
|
|
202
|
-
protected _fetchSize(path: string): Promise<number> {
|
|
203
|
-
return fetchSize(this._getRemotePath(path));
|
|
144
|
+
throw new ErrnoError(Errno.ENODATA, '', path, 'getData');
|
|
204
145
|
}
|
|
205
146
|
}
|
|
206
147
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Cred } from '../../cred.js';
|
|
2
|
+
import { ErrnoError, Errno } from '../../error.js';
|
|
3
|
+
import { NoSyncFile, isWriteable, flagToMode } from '../../file.js';
|
|
4
|
+
import { Readonly, FileSystem } from '../../filesystem.js';
|
|
5
|
+
import type { Stats } from '../../stats.js';
|
|
6
|
+
import { decode } from '../../utils.js';
|
|
7
|
+
import { Index, IndexData } from './index.js';
|
|
8
|
+
|
|
9
|
+
export abstract class IndexFS extends Readonly(FileSystem) {
|
|
10
|
+
protected index: Index = new Index();
|
|
11
|
+
|
|
12
|
+
protected _isInitialized: boolean = false;
|
|
13
|
+
|
|
14
|
+
public async ready(): Promise<void> {
|
|
15
|
+
await super.ready();
|
|
16
|
+
if (this._isInitialized) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
this.index.fromJSON(await this.indexData);
|
|
20
|
+
this._isInitialized = true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
constructor(private indexData: IndexData | Promise<IndexData>) {
|
|
24
|
+
super();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public async reloadFiles(): Promise<void> {
|
|
28
|
+
for (const [path, stats] of this.index.files()) {
|
|
29
|
+
delete stats.fileData;
|
|
30
|
+
stats.fileData = await this.getData(path, stats);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public reloadFilesSync(): void {
|
|
35
|
+
for (const [path, stats] of this.index.files()) {
|
|
36
|
+
delete stats.fileData;
|
|
37
|
+
stats.fileData = this.getDataSync(path, stats);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public async stat(path: string): Promise<Stats> {
|
|
42
|
+
return this.statSync(path);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public statSync(path: string): Stats {
|
|
46
|
+
if (!this.index.has(path)) {
|
|
47
|
+
throw ErrnoError.With('ENOENT', path, 'stat');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return this.index.get(path)!;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public async openFile(path: string, flag: string, cred: Cred): Promise<NoSyncFile<this>> {
|
|
54
|
+
if (isWriteable(flag)) {
|
|
55
|
+
// You can't write to files on this file system.
|
|
56
|
+
throw new ErrnoError(Errno.EPERM, path);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if the path exists, and is a file.
|
|
60
|
+
const stats = this.index.get(path);
|
|
61
|
+
|
|
62
|
+
if (!stats) {
|
|
63
|
+
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!stats.hasAccess(flagToMode(flag), cred)) {
|
|
67
|
+
throw ErrnoError.With('EACCES', path, 'openFile');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : await this.getData(path, stats));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public openFileSync(path: string, flag: string, cred: Cred): NoSyncFile<this> {
|
|
74
|
+
if (isWriteable(flag)) {
|
|
75
|
+
// You can't write to files on this file system.
|
|
76
|
+
throw new ErrnoError(Errno.EPERM, path);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check if the path exists, and is a file.
|
|
80
|
+
const stats = this.index.get(path);
|
|
81
|
+
|
|
82
|
+
if (!stats) {
|
|
83
|
+
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!stats.hasAccess(flagToMode(flag), cred)) {
|
|
87
|
+
throw ErrnoError.With('EACCES', path, 'openFile');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : this.getDataSync(path, stats));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public async readdir(path: string): Promise<string[]> {
|
|
94
|
+
return this.readdirSync(path);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public readdirSync(path: string): string[] {
|
|
98
|
+
// Check if it exists.
|
|
99
|
+
const stats = this.index.get(path);
|
|
100
|
+
if (!stats) {
|
|
101
|
+
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!stats.isDirectory()) {
|
|
105
|
+
throw ErrnoError.With('ENOTDIR', path, 'readdir');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return JSON.parse(decode(stats.fileData));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
protected abstract getData(path: string, stats: Stats): Promise<Uint8Array>;
|
|
112
|
+
protected abstract getDataSync(path: string, stats: Stats): Uint8Array;
|
|
113
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { isJSON } from 'utilium';
|
|
2
|
+
import { Errno, ErrnoError } from '../../error.js';
|
|
3
|
+
import { Stats, StatsLike } from '../../stats.js';
|
|
4
|
+
import { encode } from '../../utils.js';
|
|
5
|
+
import { basename, dirname } from '../../emulation/path.js';
|
|
6
|
+
|
|
7
|
+
export interface IndexData {
|
|
8
|
+
version: 1;
|
|
9
|
+
entries: Record<string, StatsLike<number>>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const version = 1;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* An index of files
|
|
16
|
+
*/
|
|
17
|
+
export class Index extends Map<string, Stats> {
|
|
18
|
+
public constructor() {
|
|
19
|
+
super();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Convience method
|
|
24
|
+
*/
|
|
25
|
+
public files(): Map<string, Stats> {
|
|
26
|
+
const files = new Map<string, Stats>();
|
|
27
|
+
for (const [path, stats] of this) {
|
|
28
|
+
if (stats.isFile()) {
|
|
29
|
+
files.set(path, stats);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return files;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Converts the index to JSON
|
|
37
|
+
*/
|
|
38
|
+
public toJSON(): IndexData {
|
|
39
|
+
return {
|
|
40
|
+
version,
|
|
41
|
+
entries: Object.fromEntries(this),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Converts the index to a string
|
|
47
|
+
*/
|
|
48
|
+
public toString(): string {
|
|
49
|
+
return JSON.stringify(this.toJSON());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns the files in the directory `dir`.
|
|
54
|
+
* This is expensive so it is only called once per directory.
|
|
55
|
+
*/
|
|
56
|
+
protected dirEntries(dir: string): string[] {
|
|
57
|
+
const entries = [];
|
|
58
|
+
for (const entry of this.keys()) {
|
|
59
|
+
if (dirname(entry) == dir) {
|
|
60
|
+
entries.push(basename(entry));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return entries;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Loads the index from JSON data
|
|
68
|
+
*/
|
|
69
|
+
public fromJSON(json: IndexData): void {
|
|
70
|
+
if (json.version != version) {
|
|
71
|
+
throw new ErrnoError(Errno.EINVAL, 'Index version mismatch');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.clear();
|
|
75
|
+
|
|
76
|
+
for (const [path, data] of Object.entries(json.entries)) {
|
|
77
|
+
const stats = new Stats(data);
|
|
78
|
+
if (stats.isDirectory()) {
|
|
79
|
+
stats.fileData = encode(JSON.stringify(this.dirEntries(path)));
|
|
80
|
+
}
|
|
81
|
+
this.set(path, stats);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parses an index from a string
|
|
87
|
+
*/
|
|
88
|
+
public static parse(data: string): Index {
|
|
89
|
+
if (!isJSON(data)) {
|
|
90
|
+
throw new ErrnoError(Errno.EINVAL, 'Invalid JSON');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const json = JSON.parse(data);
|
|
94
|
+
const index = new Index();
|
|
95
|
+
index.fromJSON(json);
|
|
96
|
+
return index;
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/file.ts
CHANGED
|
@@ -701,8 +701,8 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
701
701
|
* For the filesystems which do not sync to anything..
|
|
702
702
|
*/
|
|
703
703
|
export class NoSyncFile<T extends FileSystem> extends PreloadFile<T> {
|
|
704
|
-
constructor(
|
|
705
|
-
super(
|
|
704
|
+
constructor(fs: T, path: string, flag: string, stats: Stats, contents?: Uint8Array) {
|
|
705
|
+
super(fs, path, flag, stats, contents);
|
|
706
706
|
}
|
|
707
707
|
/**
|
|
708
708
|
* Asynchronous sync. Doesn't do anything, simply calls the cb.
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ export * from './error.js';
|
|
|
2
2
|
export * from './backends/port/fs.js';
|
|
3
3
|
export * from './backends/fetch.js';
|
|
4
4
|
export * from './backends/memory.js';
|
|
5
|
-
export * from './backends/
|
|
5
|
+
export * from './backends/index/fs.js';
|
|
6
6
|
export * from './backends/locked.js';
|
|
7
7
|
export * from './backends/overlay.js';
|
|
8
8
|
export * from './backends/store/fs.js';
|