@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/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";
|
package/dist/backends/fetch.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Stats } from '../stats.js';
|
|
4
|
-
import { FileIndex, AsyncIndexFS } from './Index.js';
|
|
1
|
+
import { Errno, ErrnoError } from '../error.js';
|
|
2
|
+
import { IndexFS } from './index/fs.js';
|
|
5
3
|
async function fetchFile(path, type) {
|
|
6
4
|
const response = await fetch(path).catch(e => {
|
|
7
5
|
throw new ErrnoError(Errno.EIO, e.message);
|
|
@@ -24,64 +22,44 @@ async function fetchFile(path, 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(e => {
|
|
32
|
-
throw new ErrnoError(Errno.EIO, e.message);
|
|
33
|
-
});
|
|
34
|
-
if (!response.ok) {
|
|
35
|
-
throw new ErrnoError(Errno.EIO, 'fetch failed: HEAD response returned code ' + response.status);
|
|
36
|
-
}
|
|
37
|
-
return parseInt(response.headers.get('Content-Length') || '-1', 10);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* A simple filesystem backed by HTTP using the fetch API.
|
|
25
|
+
* A simple filesystem backed by HTTP using the `fetch` API.
|
|
41
26
|
*
|
|
42
27
|
*
|
|
43
|
-
*
|
|
28
|
+
* Index objects look like the following:
|
|
44
29
|
*
|
|
45
30
|
* ```json
|
|
46
31
|
* {
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* }
|
|
54
|
-
* }
|
|
32
|
+
* "version": 1,
|
|
33
|
+
* "entries": {
|
|
34
|
+
* "/home": { ... },
|
|
35
|
+
* "/home/jvilk": { ... },
|
|
36
|
+
* "/home/james": { ... }
|
|
37
|
+
* }
|
|
55
38
|
* }
|
|
56
39
|
* ```
|
|
57
40
|
*
|
|
58
|
-
*
|
|
41
|
+
* Each entry contains the stats associated with the file.
|
|
59
42
|
*/
|
|
60
|
-
export class FetchFS extends
|
|
61
|
-
async
|
|
62
|
-
if (
|
|
63
|
-
this._index = FileIndex.FromListing(index);
|
|
43
|
+
export class FetchFS extends IndexFS {
|
|
44
|
+
async ready() {
|
|
45
|
+
if (this._isInitialized) {
|
|
64
46
|
return;
|
|
65
47
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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);
|
|
72
54
|
}
|
|
73
55
|
}
|
|
74
|
-
async ready() {
|
|
75
|
-
await this._init;
|
|
76
|
-
}
|
|
77
56
|
constructor({ index = 'index.json', baseUrl = '' }) {
|
|
78
|
-
super(
|
|
57
|
+
super(typeof index != 'string' ? index : fetchFile(index, 'json'));
|
|
79
58
|
// prefix url must end in a directory separator.
|
|
80
59
|
if (baseUrl.at(-1) != '/') {
|
|
81
60
|
baseUrl += '/';
|
|
82
61
|
}
|
|
83
|
-
this.
|
|
84
|
-
this._init = this._initialize(index);
|
|
62
|
+
this.baseUrl = baseUrl;
|
|
85
63
|
}
|
|
86
64
|
metadata() {
|
|
87
65
|
return {
|
|
@@ -90,63 +68,38 @@ export class FetchFS extends AsyncIndexFS {
|
|
|
90
68
|
readonly: true,
|
|
91
69
|
};
|
|
92
70
|
}
|
|
93
|
-
empty() {
|
|
94
|
-
for (const file of this._index.files()) {
|
|
95
|
-
delete file.data.fileData;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
71
|
/**
|
|
99
|
-
*
|
|
72
|
+
* Preload the given file into the index.
|
|
100
73
|
* @param path
|
|
101
74
|
* @param buffer
|
|
102
75
|
*/
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
if (!
|
|
76
|
+
preload(path, buffer) {
|
|
77
|
+
const stats = this.index.get(path);
|
|
78
|
+
if (!stats) {
|
|
106
79
|
throw ErrnoError.With('ENOENT', path, 'preloadFile');
|
|
107
80
|
}
|
|
108
|
-
if (!
|
|
81
|
+
if (!stats.isFile()) {
|
|
109
82
|
throw ErrnoError.With('EISDIR', path, 'preloadFile');
|
|
110
83
|
}
|
|
111
|
-
const stats = inode.data;
|
|
112
84
|
stats.size = buffer.length;
|
|
113
85
|
stats.fileData = buffer;
|
|
114
86
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
stats.size = await this._fetchSize(path);
|
|
120
|
-
}
|
|
121
|
-
return stats;
|
|
122
|
-
}
|
|
123
|
-
async openFileInode(inode, path, flag) {
|
|
124
|
-
const stats = inode.data;
|
|
125
|
-
// 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) {
|
|
126
91
|
if (stats.fileData) {
|
|
127
|
-
return
|
|
92
|
+
return stats.fileData;
|
|
128
93
|
}
|
|
129
|
-
|
|
130
|
-
const data = await this._fetchFile(path, 'buffer');
|
|
131
|
-
// we don't initially have file sizes
|
|
132
|
-
stats.size = data.length;
|
|
94
|
+
const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer');
|
|
133
95
|
stats.fileData = data;
|
|
134
|
-
return
|
|
96
|
+
return data;
|
|
135
97
|
}
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
|
|
98
|
+
getDataSync(path, stats) {
|
|
99
|
+
if (stats.fileData) {
|
|
100
|
+
return stats.fileData;
|
|
139
101
|
}
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
_fetchFile(path, type) {
|
|
143
|
-
return fetchFile(this._getRemotePath(path), type);
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Only requests the HEAD content, for the file size.
|
|
147
|
-
*/
|
|
148
|
-
_fetchSize(path) {
|
|
149
|
-
return fetchSize(this._getRemotePath(path));
|
|
102
|
+
throw new ErrnoError(Errno.ENODATA, '', path, 'getData');
|
|
150
103
|
}
|
|
151
104
|
}
|
|
152
105
|
export const Fetch = {
|
|
@@ -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
|
+
}
|