@zenfs/dom 0.0.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/backends/FileSystemAccess.d.ts +45 -0
- package/dist/backends/FileSystemAccess.js +263 -0
- package/dist/backends/HTTPRequest.d.ts +88 -0
- package/dist/backends/HTTPRequest.js +223 -0
- package/dist/backends/IndexedDB.d.ts +64 -0
- package/dist/backends/IndexedDB.js +234 -0
- package/dist/backends/Storage.d.ts +40 -0
- package/dist/backends/Storage.js +75 -0
- package/dist/backends/Worker.d.ts +79 -0
- package/dist/backends/Worker.js +169 -0
- package/dist/browser.min.js +9 -0
- package/dist/browser.min.js.map +7 -0
- package/dist/fetch.d.ts +17 -0
- package/dist/fetch.js +52 -0
- package/dist/file_index.d.ts +154 -0
- package/dist/file_index.js +342 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/license.md +19 -0
- package/package.json +60 -0
- package/readme.md +52 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import { Cred } from '@zenfs/core/cred.js';
|
|
3
|
+
import { File, FileFlag, PreloadFile } from '@zenfs/core/file.js';
|
|
4
|
+
import { BaseFileSystem, FileSystemMetadata } from '@zenfs/core/filesystem.js';
|
|
5
|
+
import { Stats } from '@zenfs/core/stats.js';
|
|
6
|
+
import { type BackendOptions } from '@zenfs/core/backends/backend.js';
|
|
7
|
+
declare global {
|
|
8
|
+
interface FileSystemDirectoryHandle {
|
|
9
|
+
[Symbol.iterator](): IterableIterator<[string, FileSystemHandle]>;
|
|
10
|
+
entries(): IterableIterator<[string, FileSystemHandle]>;
|
|
11
|
+
keys(): IterableIterator<string>;
|
|
12
|
+
values(): IterableIterator<FileSystemHandle>;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
interface FileSystemAccessFileSystemOptions {
|
|
16
|
+
handle: FileSystemDirectoryHandle;
|
|
17
|
+
}
|
|
18
|
+
export declare class FileSystemAccessFile extends PreloadFile<FileSystemAccessFileSystem> implements File {
|
|
19
|
+
constructor(_fs: FileSystemAccessFileSystem, _path: string, _flag: FileFlag, _stat: Stats, contents?: Buffer);
|
|
20
|
+
sync(): Promise<void>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export declare class FileSystemAccessFileSystem extends BaseFileSystem {
|
|
24
|
+
static readonly Name = "FileSystemAccess";
|
|
25
|
+
static Create: any;
|
|
26
|
+
static readonly Options: BackendOptions;
|
|
27
|
+
static isAvailable(): boolean;
|
|
28
|
+
private _handles;
|
|
29
|
+
constructor({ handle }: FileSystemAccessFileSystemOptions);
|
|
30
|
+
get metadata(): FileSystemMetadata;
|
|
31
|
+
_sync(p: string, data: Buffer, stats: Stats, cred: Cred): Promise<void>;
|
|
32
|
+
rename(oldPath: string, newPath: string, cred: Cred): Promise<void>;
|
|
33
|
+
writeFile(fname: string, data: any, encoding: string | null, flag: FileFlag, mode: number, cred: Cred, createFile?: boolean): Promise<void>;
|
|
34
|
+
createFile(p: string, flag: FileFlag, mode: number, cred: Cred): Promise<File>;
|
|
35
|
+
stat(path: string, cred: Cred): Promise<Stats>;
|
|
36
|
+
exists(p: string, cred: Cred): Promise<boolean>;
|
|
37
|
+
openFile(path: string, flags: FileFlag, cred: Cred): Promise<File>;
|
|
38
|
+
unlink(path: string, cred: Cred): Promise<void>;
|
|
39
|
+
rmdir(path: string, cred: Cred): Promise<void>;
|
|
40
|
+
mkdir(p: string, mode: any, cred: Cred): Promise<void>;
|
|
41
|
+
readdir(path: string, cred: Cred): Promise<string[]>;
|
|
42
|
+
private newFile;
|
|
43
|
+
private getHandle;
|
|
44
|
+
}
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
11
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
12
|
+
var m = o[Symbol.asyncIterator], i;
|
|
13
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
14
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
15
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
16
|
+
};
|
|
17
|
+
var _a;
|
|
18
|
+
import { basename, dirname, join } from 'path';
|
|
19
|
+
import { ApiError, ErrorCode } from '@zenfs/core/ApiError.js';
|
|
20
|
+
import { Cred } from '@zenfs/core/cred.js';
|
|
21
|
+
import { FileFlag, PreloadFile } from '@zenfs/core/file.js';
|
|
22
|
+
import { BaseFileSystem } from '@zenfs/core/filesystem.js';
|
|
23
|
+
import { Stats, FileType } from '@zenfs/core/stats.js';
|
|
24
|
+
import { CreateBackend } from '@zenfs/core/backends/backend.js';
|
|
25
|
+
import { Buffer } from 'buffer';
|
|
26
|
+
const handleError = (path = '', error) => {
|
|
27
|
+
if (error.name === 'NotFoundError') {
|
|
28
|
+
throw ApiError.ENOENT(path);
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
};
|
|
32
|
+
export class FileSystemAccessFile extends PreloadFile {
|
|
33
|
+
constructor(_fs, _path, _flag, _stat, contents) {
|
|
34
|
+
super(_fs, _path, _flag, _stat, contents);
|
|
35
|
+
}
|
|
36
|
+
sync() {
|
|
37
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
38
|
+
if (this.isDirty()) {
|
|
39
|
+
yield this._fs._sync(this.getPath(), this.getBuffer(), this.getStats(), Cred.Root);
|
|
40
|
+
this.resetDirty();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
close() {
|
|
45
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
yield this.sync();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export class FileSystemAccessFileSystem extends BaseFileSystem {
|
|
51
|
+
static isAvailable() {
|
|
52
|
+
return typeof FileSystemHandle === 'function';
|
|
53
|
+
}
|
|
54
|
+
constructor({ handle }) {
|
|
55
|
+
super();
|
|
56
|
+
this._handles = new Map();
|
|
57
|
+
this._handles.set('/', handle);
|
|
58
|
+
}
|
|
59
|
+
get metadata() {
|
|
60
|
+
return Object.assign(Object.assign({}, super.metadata), { name: _a.Name });
|
|
61
|
+
}
|
|
62
|
+
_sync(p, data, stats, cred) {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
const currentStats = yield this.stat(p, cred);
|
|
65
|
+
if (stats.mtime !== currentStats.mtime) {
|
|
66
|
+
yield this.writeFile(p, data, null, FileFlag.getFileFlag('w'), currentStats.mode, cred);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
rename(oldPath, newPath, cred) {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
try {
|
|
73
|
+
const handle = yield this.getHandle(oldPath);
|
|
74
|
+
if (handle instanceof FileSystemDirectoryHandle) {
|
|
75
|
+
const files = yield this.readdir(oldPath, cred);
|
|
76
|
+
yield this.mkdir(newPath, 'wx', cred);
|
|
77
|
+
if (files.length === 0) {
|
|
78
|
+
yield this.unlink(oldPath, cred);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
for (const file of files) {
|
|
82
|
+
yield this.rename(join(oldPath, file), join(newPath, file), cred);
|
|
83
|
+
yield this.unlink(oldPath, cred);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (handle instanceof FileSystemFileHandle) {
|
|
88
|
+
const oldFile = yield handle.getFile(), destFolder = yield this.getHandle(dirname(newPath));
|
|
89
|
+
if (destFolder instanceof FileSystemDirectoryHandle) {
|
|
90
|
+
const newFile = yield destFolder.getFileHandle(basename(newPath), { create: true });
|
|
91
|
+
const writable = yield newFile.createWritable();
|
|
92
|
+
const buffer = yield oldFile.arrayBuffer();
|
|
93
|
+
yield writable.write(buffer);
|
|
94
|
+
writable.close();
|
|
95
|
+
yield this.unlink(oldPath, cred);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
handleError(oldPath, err);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
writeFile(fname, data, encoding, flag, mode, cred, createFile) {
|
|
105
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
106
|
+
const handle = yield this.getHandle(dirname(fname));
|
|
107
|
+
if (handle instanceof FileSystemDirectoryHandle) {
|
|
108
|
+
const file = yield handle.getFileHandle(basename(fname), { create: true });
|
|
109
|
+
const writable = yield file.createWritable();
|
|
110
|
+
yield writable.write(data);
|
|
111
|
+
yield writable.close();
|
|
112
|
+
//return createFile ? this.newFile(fname, flag, data) : undefined;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
createFile(p, flag, mode, cred) {
|
|
117
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
118
|
+
yield this.writeFile(p, Buffer.alloc(0), null, flag, mode, cred, true);
|
|
119
|
+
return this.openFile(p, flag, cred);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
stat(path, cred) {
|
|
123
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
124
|
+
const handle = yield this.getHandle(path);
|
|
125
|
+
if (!handle) {
|
|
126
|
+
throw ApiError.FileError(ErrorCode.EINVAL, path);
|
|
127
|
+
}
|
|
128
|
+
if (handle instanceof FileSystemDirectoryHandle) {
|
|
129
|
+
return new Stats(FileType.DIRECTORY, 4096);
|
|
130
|
+
}
|
|
131
|
+
if (handle instanceof FileSystemFileHandle) {
|
|
132
|
+
const { lastModified, size } = yield handle.getFile();
|
|
133
|
+
return new Stats(FileType.FILE, size, undefined, undefined, lastModified);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
exists(p, cred) {
|
|
138
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
139
|
+
try {
|
|
140
|
+
yield this.getHandle(p);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
openFile(path, flags, cred) {
|
|
149
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
150
|
+
const handle = yield this.getHandle(path);
|
|
151
|
+
if (handle instanceof FileSystemFileHandle) {
|
|
152
|
+
const file = yield handle.getFile();
|
|
153
|
+
const buffer = yield file.arrayBuffer();
|
|
154
|
+
return this.newFile(path, flags, buffer, file.size, file.lastModified);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
unlink(path, cred) {
|
|
159
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
160
|
+
const handle = yield this.getHandle(dirname(path));
|
|
161
|
+
if (handle instanceof FileSystemDirectoryHandle) {
|
|
162
|
+
try {
|
|
163
|
+
yield handle.removeEntry(basename(path), { recursive: true });
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
handleError(path, e);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
rmdir(path, cred) {
|
|
172
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
173
|
+
return this.unlink(path, cred);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
mkdir(p, mode, cred) {
|
|
177
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
178
|
+
const overwrite = mode && mode.flag && mode.flag.includes('w') && !mode.flag.includes('x');
|
|
179
|
+
const existingHandle = yield this.getHandle(p);
|
|
180
|
+
if (existingHandle && !overwrite) {
|
|
181
|
+
throw ApiError.EEXIST(p);
|
|
182
|
+
}
|
|
183
|
+
const handle = yield this.getHandle(dirname(p));
|
|
184
|
+
if (handle instanceof FileSystemDirectoryHandle) {
|
|
185
|
+
yield handle.getDirectoryHandle(basename(p), { create: true });
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
readdir(path, cred) {
|
|
190
|
+
var _b, e_1, _c, _d;
|
|
191
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
192
|
+
const handle = yield this.getHandle(path);
|
|
193
|
+
if (!(handle instanceof FileSystemDirectoryHandle)) {
|
|
194
|
+
throw ApiError.ENOTDIR(path);
|
|
195
|
+
}
|
|
196
|
+
const _keys = [];
|
|
197
|
+
try {
|
|
198
|
+
for (var _e = true, _f = __asyncValues(handle.keys()), _g; _g = yield _f.next(), _b = _g.done, !_b; _e = true) {
|
|
199
|
+
_d = _g.value;
|
|
200
|
+
_e = false;
|
|
201
|
+
const key = _d;
|
|
202
|
+
_keys.push(join(path, key));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
206
|
+
finally {
|
|
207
|
+
try {
|
|
208
|
+
if (!_e && !_b && (_c = _f.return)) yield _c.call(_f);
|
|
209
|
+
}
|
|
210
|
+
finally { if (e_1) throw e_1.error; }
|
|
211
|
+
}
|
|
212
|
+
return _keys;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
newFile(path, flag, data, size, lastModified) {
|
|
216
|
+
return new FileSystemAccessFile(this, path, flag, new Stats(FileType.FILE, size || 0, undefined, undefined, lastModified || new Date().getTime()), Buffer.from(data));
|
|
217
|
+
}
|
|
218
|
+
getHandle(path) {
|
|
219
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
220
|
+
if (this._handles.has(path)) {
|
|
221
|
+
return this._handles.get(path);
|
|
222
|
+
}
|
|
223
|
+
let walkedPath = '/';
|
|
224
|
+
const [, ...pathParts] = path.split('/');
|
|
225
|
+
const getHandleParts = ([pathPart, ...remainingPathParts]) => __awaiter(this, void 0, void 0, function* () {
|
|
226
|
+
const walkingPath = join(walkedPath, pathPart);
|
|
227
|
+
const continueWalk = (handle) => {
|
|
228
|
+
walkedPath = walkingPath;
|
|
229
|
+
this._handles.set(walkedPath, handle);
|
|
230
|
+
if (remainingPathParts.length === 0) {
|
|
231
|
+
return this._handles.get(path);
|
|
232
|
+
}
|
|
233
|
+
getHandleParts(remainingPathParts);
|
|
234
|
+
};
|
|
235
|
+
const handle = this._handles.get(walkedPath);
|
|
236
|
+
try {
|
|
237
|
+
return yield continueWalk(yield handle.getDirectoryHandle(pathPart));
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (error.name === 'TypeMismatchError') {
|
|
241
|
+
try {
|
|
242
|
+
return yield continueWalk(yield handle.getFileHandle(pathPart));
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
handleError(walkingPath, err);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (error.message === 'Name is not allowed.') {
|
|
249
|
+
throw new ApiError(ErrorCode.ENOENT, error.message, walkingPath);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
handleError(walkingPath, error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
yield getHandleParts(pathParts);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
_a = FileSystemAccessFileSystem;
|
|
261
|
+
FileSystemAccessFileSystem.Name = 'FileSystemAccess';
|
|
262
|
+
FileSystemAccessFileSystem.Create = CreateBackend.bind(_a);
|
|
263
|
+
FileSystemAccessFileSystem.Options = {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import { BaseFileSystem, FileContents, FileSystemMetadata } from '@zenfs/core/filesystem.js';
|
|
3
|
+
import { File, FileFlag } from '@zenfs/core/file.js';
|
|
4
|
+
import { Stats } from '@zenfs/core/stats.js';
|
|
5
|
+
import { Cred } from '@zenfs/core/cred.js';
|
|
6
|
+
import { type BackendOptions } from '@zenfs/core/backends/backend.js';
|
|
7
|
+
export interface HTTPRequestIndex {
|
|
8
|
+
[key: string]: string;
|
|
9
|
+
}
|
|
10
|
+
export declare namespace HTTPRequest {
|
|
11
|
+
/**
|
|
12
|
+
* Configuration options for a HTTPRequest file system.
|
|
13
|
+
*/
|
|
14
|
+
interface Options {
|
|
15
|
+
/**
|
|
16
|
+
* URL to a file index as a JSON file or the file index object itself, generated with the make_http_index script.
|
|
17
|
+
* Defaults to `index.json`.
|
|
18
|
+
*/
|
|
19
|
+
index?: string | HTTPRequestIndex;
|
|
20
|
+
/** Used as the URL prefix for fetched files.
|
|
21
|
+
* Default: Fetch files relative to the index.
|
|
22
|
+
*/
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A simple filesystem backed by HTTP downloads. You must create a directory listing using the
|
|
28
|
+
* `make_http_index` tool provided by ZenFS.
|
|
29
|
+
*
|
|
30
|
+
* If you install ZenFS globally with `npm i -g zenfs`, you can generate a listing by
|
|
31
|
+
* running `make_http_index` in your terminal in the directory you would like to index:
|
|
32
|
+
*
|
|
33
|
+
* ```
|
|
34
|
+
* make_http_index > index.json
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* Listings objects look like the following:
|
|
38
|
+
*
|
|
39
|
+
* ```json
|
|
40
|
+
* {
|
|
41
|
+
* "home": {
|
|
42
|
+
* "jvilk": {
|
|
43
|
+
* "someFile.txt": null,
|
|
44
|
+
* "someDir": {
|
|
45
|
+
* // Empty directory
|
|
46
|
+
* }
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* *This example has the folder `/home/jvilk` with subfile `someFile.txt` and subfolder `someDir`.*
|
|
53
|
+
*/
|
|
54
|
+
export declare class HTTPRequest extends BaseFileSystem {
|
|
55
|
+
static readonly Name = "HTTPRequest";
|
|
56
|
+
static Create: any;
|
|
57
|
+
static readonly Options: BackendOptions;
|
|
58
|
+
static isAvailable(): boolean;
|
|
59
|
+
readonly prefixUrl: string;
|
|
60
|
+
private _index;
|
|
61
|
+
private _requestFileInternal;
|
|
62
|
+
private _requestFileSizeInternal;
|
|
63
|
+
constructor({ index, baseUrl }: HTTPRequest.Options);
|
|
64
|
+
get metadata(): FileSystemMetadata;
|
|
65
|
+
empty(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Special HTTPFS function: Preload the given file into the index.
|
|
68
|
+
* @param [String] path
|
|
69
|
+
* @param [ZenFS.Buffer] buffer
|
|
70
|
+
*/
|
|
71
|
+
preloadFile(path: string, buffer: Buffer): void;
|
|
72
|
+
stat(path: string, cred: Cred): Promise<Stats>;
|
|
73
|
+
open(path: string, flags: FileFlag, mode: number, cred: Cred): Promise<File>;
|
|
74
|
+
readdir(path: string, cred: Cred): Promise<string[]>;
|
|
75
|
+
/**
|
|
76
|
+
* We have the entire file as a buffer; optimize readFile.
|
|
77
|
+
*/
|
|
78
|
+
readFile(fname: string, encoding: BufferEncoding, flag: FileFlag, cred: Cred): Promise<FileContents>;
|
|
79
|
+
private _getHTTPPath;
|
|
80
|
+
/**
|
|
81
|
+
* Asynchronously download the given file.
|
|
82
|
+
*/
|
|
83
|
+
private _requestFile;
|
|
84
|
+
/**
|
|
85
|
+
* Only requests the HEAD content, for the file size.
|
|
86
|
+
*/
|
|
87
|
+
private _requestFileSize;
|
|
88
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var _a;
|
|
11
|
+
import { BaseFileSystem } from '@zenfs/core/filesystem.js';
|
|
12
|
+
import { ApiError, ErrorCode } from '@zenfs/core/ApiError.js';
|
|
13
|
+
import { copyingSlice } from '@zenfs/core/utils.js';
|
|
14
|
+
import { ActionType, NoSyncFile } from '@zenfs/core/file.js';
|
|
15
|
+
import { Stats } from '@zenfs/core/stats.js';
|
|
16
|
+
import { fetchIsAvailable, fetchFile, fetchFileSize } from '../fetch.js';
|
|
17
|
+
import { FileIndex, isFileInode, isDirInode } from '../file_index.js';
|
|
18
|
+
import { CreateBackend } from '@zenfs/core/backends/backend.js';
|
|
19
|
+
import { R_OK } from '@zenfs/core/emulation/constants.js';
|
|
20
|
+
/**
|
|
21
|
+
* A simple filesystem backed by HTTP downloads. You must create a directory listing using the
|
|
22
|
+
* `make_http_index` tool provided by ZenFS.
|
|
23
|
+
*
|
|
24
|
+
* If you install ZenFS globally with `npm i -g zenfs`, you can generate a listing by
|
|
25
|
+
* running `make_http_index` in your terminal in the directory you would like to index:
|
|
26
|
+
*
|
|
27
|
+
* ```
|
|
28
|
+
* make_http_index > index.json
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Listings objects look like the following:
|
|
32
|
+
*
|
|
33
|
+
* ```json
|
|
34
|
+
* {
|
|
35
|
+
* "home": {
|
|
36
|
+
* "jvilk": {
|
|
37
|
+
* "someFile.txt": null,
|
|
38
|
+
* "someDir": {
|
|
39
|
+
* // Empty directory
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* *This example has the folder `/home/jvilk` with subfile `someFile.txt` and subfolder `someDir`.*
|
|
47
|
+
*/
|
|
48
|
+
export class HTTPRequest extends BaseFileSystem {
|
|
49
|
+
static isAvailable() {
|
|
50
|
+
return fetchIsAvailable;
|
|
51
|
+
}
|
|
52
|
+
constructor({ index, baseUrl = '' }) {
|
|
53
|
+
super();
|
|
54
|
+
if (!index) {
|
|
55
|
+
index = 'index.json';
|
|
56
|
+
}
|
|
57
|
+
const indexRequest = typeof index == 'string' ? fetchFile(index, 'json') : Promise.resolve(index);
|
|
58
|
+
this._ready = indexRequest.then(data => {
|
|
59
|
+
this._index = FileIndex.fromListing(data);
|
|
60
|
+
return this;
|
|
61
|
+
});
|
|
62
|
+
// prefix_url must end in a directory separator.
|
|
63
|
+
if (baseUrl.length > 0 && baseUrl.charAt(baseUrl.length - 1) !== '/') {
|
|
64
|
+
baseUrl = baseUrl + '/';
|
|
65
|
+
}
|
|
66
|
+
this.prefixUrl = baseUrl;
|
|
67
|
+
this._requestFileInternal = fetchFile;
|
|
68
|
+
this._requestFileSizeInternal = fetchFileSize;
|
|
69
|
+
}
|
|
70
|
+
get metadata() {
|
|
71
|
+
return Object.assign(Object.assign({}, super.metadata), { name: _a.Name, readonly: true });
|
|
72
|
+
}
|
|
73
|
+
empty() {
|
|
74
|
+
this._index.fileIterator(function (file) {
|
|
75
|
+
file.fileData = null;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Special HTTPFS function: Preload the given file into the index.
|
|
80
|
+
* @param [String] path
|
|
81
|
+
* @param [ZenFS.Buffer] buffer
|
|
82
|
+
*/
|
|
83
|
+
preloadFile(path, buffer) {
|
|
84
|
+
const inode = this._index.getInode(path);
|
|
85
|
+
if (isFileInode(inode)) {
|
|
86
|
+
if (inode === null) {
|
|
87
|
+
throw ApiError.ENOENT(path);
|
|
88
|
+
}
|
|
89
|
+
const stats = inode.getData();
|
|
90
|
+
stats.size = buffer.length;
|
|
91
|
+
stats.fileData = buffer;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
throw ApiError.EISDIR(path);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
stat(path, cred) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
const inode = this._index.getInode(path);
|
|
100
|
+
if (inode === null) {
|
|
101
|
+
throw ApiError.ENOENT(path);
|
|
102
|
+
}
|
|
103
|
+
if (!inode.toStats().hasAccess(R_OK, cred)) {
|
|
104
|
+
throw ApiError.EACCES(path);
|
|
105
|
+
}
|
|
106
|
+
let stats;
|
|
107
|
+
if (isFileInode(inode)) {
|
|
108
|
+
stats = inode.getData();
|
|
109
|
+
// At this point, a non-opened file will still have default stats from the listing.
|
|
110
|
+
if (stats.size < 0) {
|
|
111
|
+
stats.size = yield this._requestFileSize(path);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (isDirInode(inode)) {
|
|
115
|
+
stats = inode.getStats();
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
throw ApiError.FileError(ErrorCode.EINVAL, path);
|
|
119
|
+
}
|
|
120
|
+
return stats;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
open(path, flags, mode, cred) {
|
|
124
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
125
|
+
// INVARIANT: You can't write to files on this file system.
|
|
126
|
+
if (flags.isWriteable()) {
|
|
127
|
+
throw new ApiError(ErrorCode.EPERM, path);
|
|
128
|
+
}
|
|
129
|
+
// Check if the path exists, and is a file.
|
|
130
|
+
const inode = this._index.getInode(path);
|
|
131
|
+
if (inode === null) {
|
|
132
|
+
throw ApiError.ENOENT(path);
|
|
133
|
+
}
|
|
134
|
+
if (!inode.toStats().hasAccess(flags.getMode(), cred)) {
|
|
135
|
+
throw ApiError.EACCES(path);
|
|
136
|
+
}
|
|
137
|
+
if (isFileInode(inode) || isDirInode(inode)) {
|
|
138
|
+
switch (flags.pathExistsAction()) {
|
|
139
|
+
case ActionType.THROW_EXCEPTION:
|
|
140
|
+
case ActionType.TRUNCATE_FILE:
|
|
141
|
+
throw ApiError.EEXIST(path);
|
|
142
|
+
case ActionType.NOP:
|
|
143
|
+
if (isDirInode(inode)) {
|
|
144
|
+
const stats = inode.getStats();
|
|
145
|
+
return new NoSyncFile(this, path, flags, stats, stats.fileData || undefined);
|
|
146
|
+
}
|
|
147
|
+
const stats = inode.getData();
|
|
148
|
+
// Use existing file contents.
|
|
149
|
+
// XXX: Uh, this maintains the previously-used flag.
|
|
150
|
+
if (stats.fileData) {
|
|
151
|
+
return new NoSyncFile(this, path, flags, Stats.clone(stats), stats.fileData);
|
|
152
|
+
}
|
|
153
|
+
// @todo be lazier about actually requesting the file
|
|
154
|
+
const buffer = yield this._requestFile(path, 'buffer');
|
|
155
|
+
// we don't initially have file sizes
|
|
156
|
+
stats.size = buffer.length;
|
|
157
|
+
stats.fileData = buffer;
|
|
158
|
+
return new NoSyncFile(this, path, flags, Stats.clone(stats), buffer);
|
|
159
|
+
default:
|
|
160
|
+
throw new ApiError(ErrorCode.EINVAL, 'Invalid FileMode object.');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
throw ApiError.EPERM(path);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
readdir(path, cred) {
|
|
169
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
170
|
+
return this.readdirSync(path, cred);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* We have the entire file as a buffer; optimize readFile.
|
|
175
|
+
*/
|
|
176
|
+
readFile(fname, encoding, flag, cred) {
|
|
177
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
178
|
+
// Get file.
|
|
179
|
+
const fd = yield this.open(fname, flag, 0o644, cred);
|
|
180
|
+
try {
|
|
181
|
+
const fdCast = fd;
|
|
182
|
+
const fdBuff = fdCast.getBuffer();
|
|
183
|
+
if (encoding === null) {
|
|
184
|
+
return copyingSlice(fdBuff);
|
|
185
|
+
}
|
|
186
|
+
return fdBuff.toString(encoding);
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
yield fd.close();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
_getHTTPPath(filePath) {
|
|
194
|
+
if (filePath.charAt(0) === '/') {
|
|
195
|
+
filePath = filePath.slice(1);
|
|
196
|
+
}
|
|
197
|
+
return this.prefixUrl + filePath;
|
|
198
|
+
}
|
|
199
|
+
_requestFile(p, type) {
|
|
200
|
+
return this._requestFileInternal(this._getHTTPPath(p), type);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Only requests the HEAD content, for the file size.
|
|
204
|
+
*/
|
|
205
|
+
_requestFileSize(path) {
|
|
206
|
+
return this._requestFileSizeInternal(this._getHTTPPath(path));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
_a = HTTPRequest;
|
|
210
|
+
HTTPRequest.Name = 'HTTPRequest';
|
|
211
|
+
HTTPRequest.Create = CreateBackend.bind(_a);
|
|
212
|
+
HTTPRequest.Options = {
|
|
213
|
+
index: {
|
|
214
|
+
type: ['string', 'object'],
|
|
215
|
+
optional: true,
|
|
216
|
+
description: 'URL to a file index as a JSON file or the file index object itself, generated with the make_http_index script. Defaults to `index.json`.',
|
|
217
|
+
},
|
|
218
|
+
baseUrl: {
|
|
219
|
+
type: 'string',
|
|
220
|
+
optional: true,
|
|
221
|
+
description: 'Used as the URL prefix for fetched files. Default: Fetch files relative to the index.',
|
|
222
|
+
},
|
|
223
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import { AsyncKeyValueROTransaction, AsyncKeyValueRWTransaction, AsyncKeyValueStore, AsyncKeyValueFileSystem } from '@zenfs/core/backends/AsyncStore.js';
|
|
3
|
+
import { type BackendOptions } from '@zenfs/core/backends/backend.js';
|
|
4
|
+
/**
|
|
5
|
+
* @hidden
|
|
6
|
+
*/
|
|
7
|
+
export declare class IndexedDBROTransaction implements AsyncKeyValueROTransaction {
|
|
8
|
+
tx: IDBTransaction;
|
|
9
|
+
store: IDBObjectStore;
|
|
10
|
+
constructor(tx: IDBTransaction, store: IDBObjectStore);
|
|
11
|
+
get(key: string): Promise<Buffer>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @hidden
|
|
15
|
+
*/
|
|
16
|
+
export declare class IndexedDBRWTransaction extends IndexedDBROTransaction implements AsyncKeyValueRWTransaction, AsyncKeyValueROTransaction {
|
|
17
|
+
constructor(tx: IDBTransaction, store: IDBObjectStore);
|
|
18
|
+
/**
|
|
19
|
+
* @todo return false when add has a key conflict (no error)
|
|
20
|
+
*/
|
|
21
|
+
put(key: string, data: Buffer, overwrite: boolean): Promise<boolean>;
|
|
22
|
+
del(key: string): Promise<void>;
|
|
23
|
+
commit(): Promise<void>;
|
|
24
|
+
abort(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare class IndexedDBStore implements AsyncKeyValueStore {
|
|
27
|
+
private db;
|
|
28
|
+
private storeName;
|
|
29
|
+
static Create(storeName: string, indexedDB: IDBFactory): Promise<IndexedDBStore>;
|
|
30
|
+
constructor(db: IDBDatabase, storeName: string);
|
|
31
|
+
name(): string;
|
|
32
|
+
clear(): Promise<void>;
|
|
33
|
+
beginTransaction(type: 'readonly'): AsyncKeyValueROTransaction;
|
|
34
|
+
beginTransaction(type: 'readwrite'): AsyncKeyValueRWTransaction;
|
|
35
|
+
}
|
|
36
|
+
export declare namespace IndexedDBFileSystem {
|
|
37
|
+
/**
|
|
38
|
+
* Configuration options for the IndexedDB file system.
|
|
39
|
+
*/
|
|
40
|
+
interface Options {
|
|
41
|
+
/**
|
|
42
|
+
* The name of this file system. You can have multiple IndexedDB file systems operating at once, but each must have a different name.
|
|
43
|
+
*/
|
|
44
|
+
storeName?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The size of the inode cache. Defaults to 100. A size of 0 or below disables caching.
|
|
47
|
+
*/
|
|
48
|
+
cacheSize?: number;
|
|
49
|
+
/**
|
|
50
|
+
* The IDBFactory to use. Defaults to `globalThis.indexedDB`.
|
|
51
|
+
*/
|
|
52
|
+
idbFactory?: IDBFactory;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A file system that uses the IndexedDB key value file system.
|
|
57
|
+
*/
|
|
58
|
+
export declare class IndexedDBFileSystem extends AsyncKeyValueFileSystem {
|
|
59
|
+
static readonly Name = "IndexedDB";
|
|
60
|
+
static Create: any;
|
|
61
|
+
static readonly Options: BackendOptions;
|
|
62
|
+
static isAvailable(idbFactory?: IDBFactory): boolean;
|
|
63
|
+
constructor({ cacheSize, storeName, idbFactory }: IndexedDBFileSystem.Options);
|
|
64
|
+
}
|