@statewalker/webrun-files 0.2.0 → 0.3.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/index-umd.js +239 -69
- package/dist/index-umd.min.js +2 -2
- package/dist/index.js +239 -69
- package/dist/index.min.js +2 -2
- package/package.json +2 -2
- package/src/BrowserFilesApi.js +238 -67
package/src/BrowserFilesApi.js
CHANGED
|
@@ -1,30 +1,165 @@
|
|
|
1
1
|
import FilesApi from "./FilesApi.js";
|
|
2
2
|
import getMimeType from "./getMimeType.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Stateless adapter for the "File and Directory Entries API".
|
|
6
|
+
* See https://developer.mozilla.org/en-US/docs/Web/API/File_and_Directory_Entries_API
|
|
7
|
+
*
|
|
8
|
+
* ## File and Directory Entries API
|
|
9
|
+
* The File and Directory Entries API simulates a local file system
|
|
10
|
+
* this web apps can navigate within and access files in. You can
|
|
11
|
+
* develop apps which read, write, and create files and/or directories
|
|
12
|
+
* in a virtual, sandboxed file system.
|
|
13
|
+
*/
|
|
14
|
+
class AdapterForFilesAndDirectoryEntriesApi {
|
|
15
|
+
static canApply(handle) {
|
|
16
|
+
return ((handle?.isDirectory || handle?.isFile) &&
|
|
17
|
+
(typeof handle?.name === "string"));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async *listDirectoryChildren(handle) {
|
|
21
|
+
let dirReader = handle.createReader();
|
|
22
|
+
while (true) {
|
|
23
|
+
const list = await new Promise((resolve, reject) =>
|
|
24
|
+
dirReader.readEntries(resolve, reject)
|
|
25
|
+
);
|
|
26
|
+
if (!list.length) break;
|
|
27
|
+
yield* list;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
isDirectoryHandle(handle) {
|
|
32
|
+
return handle?.isDirectory;
|
|
33
|
+
}
|
|
34
|
+
isFileHandle(handle) {
|
|
35
|
+
return handle?.isFile;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getSubdirectoryHandle(dirHandle, directoryName, create) {
|
|
39
|
+
return new Promise((resolve, reject) =>
|
|
40
|
+
dirHandle.getDirectory(
|
|
41
|
+
directoryName,
|
|
42
|
+
{ create },
|
|
43
|
+
resolve,
|
|
44
|
+
reject,
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async getFileHandle(dirHandle, fileName, create = true) {
|
|
50
|
+
return new Promise((resolve, reject) =>
|
|
51
|
+
dirHandle.getFile(
|
|
52
|
+
fileName,
|
|
53
|
+
{ create },
|
|
54
|
+
resolve,
|
|
55
|
+
reject,
|
|
56
|
+
)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async removeDirectoryEntry(dirHandle, handle) {
|
|
61
|
+
return new Promise((resolve, reject) => handle.remove(resolve, reject));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getFile(handle) {
|
|
65
|
+
const file = await new Promise((resolve, reject) =>
|
|
66
|
+
handle.file(resolve, reject)
|
|
67
|
+
);
|
|
68
|
+
return file;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async getFileWriteStream(handle) {
|
|
72
|
+
throw new Error("Not implemented");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Stateless adapter for the "File System API" and "File System Access API".
|
|
78
|
+
* See:
|
|
79
|
+
* - https://developer.mozilla.org/en-US/docs/Web/API/File_System_API
|
|
80
|
+
* - https://wicg.github.io/file-system-access/
|
|
81
|
+
*/
|
|
82
|
+
class AdapterForFileSystemApi {
|
|
83
|
+
static canApply(handle) {
|
|
84
|
+
return (handle?.kind === "file" || handle?.kind === "directory");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async *listDirectoryChildren(handle) {
|
|
88
|
+
yield* handle.values();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
isDirectoryHandle(handle) {
|
|
92
|
+
return handle?.kind === "directory";
|
|
93
|
+
}
|
|
94
|
+
isFileHandle(handle) {
|
|
95
|
+
return handle?.kind === "file";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getSubdirectoryHandle(dirHandle, directoryName, create) {
|
|
99
|
+
return await dirHandle.getDirectoryHandle(directoryName, { create });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async getFileHandle(dirHandle, fileName, create = true) {
|
|
103
|
+
return await dirHandle.getFileHandle(fileName, { create });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async removeDirectoryEntry(dirHandle, handle) {
|
|
107
|
+
return await dirHandle.removeEntry(handle.name, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async getFile(handle) {
|
|
111
|
+
return await handle.getFile();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getFileWriteStream(handle) {
|
|
115
|
+
return await handle.createWritable(); // Write the contents of the file to the stream.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
4
119
|
export default class BrowserFilesApi extends FilesApi {
|
|
5
120
|
constructor(options) {
|
|
6
121
|
super(options);
|
|
7
|
-
if (!this.
|
|
8
|
-
throw new Error("Root directory
|
|
122
|
+
if (!this.rootHandle) {
|
|
123
|
+
throw new Error("Root directory handle is not defined");
|
|
124
|
+
}
|
|
125
|
+
if (!this.adapter) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
"Can not define a browser adapter for the specified root handle",
|
|
128
|
+
);
|
|
9
129
|
}
|
|
10
130
|
}
|
|
11
131
|
|
|
12
|
-
get
|
|
13
|
-
return this.options.rootHandler;
|
|
132
|
+
get rootHandle() {
|
|
133
|
+
return this.options.rootHandle || this.options.rootHandler;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
get adapter() {
|
|
137
|
+
if (!this._adapter) {
|
|
138
|
+
const handle = this.rootHandle;
|
|
139
|
+
let adapter = AdapterForFilesAndDirectoryEntriesApi.canApply(handle)
|
|
140
|
+
? new AdapterForFilesAndDirectoryEntriesApi()
|
|
141
|
+
: AdapterForFileSystemApi.canApply(handle)
|
|
142
|
+
? new AdapterForFileSystemApi()
|
|
143
|
+
: null;
|
|
144
|
+
this._adapter = adapter;
|
|
145
|
+
}
|
|
146
|
+
return this._adapter;
|
|
14
147
|
}
|
|
15
148
|
|
|
16
149
|
async *list(file, { recursive = false } = {}) {
|
|
17
150
|
const segments = this._getPathSegments(file);
|
|
18
|
-
const handle = await this.
|
|
151
|
+
const handle = await this._getHandle(segments);
|
|
152
|
+
if (!handle) return;
|
|
19
153
|
const that = this;
|
|
20
|
-
if (handle
|
|
154
|
+
if (this.adapter.isDirectoryHandle(handle)) {
|
|
155
|
+
// yield await this._getFileInfo(segments, handle);
|
|
21
156
|
yield* listChildren(segments, handle, recursive);
|
|
22
157
|
}
|
|
23
158
|
|
|
24
159
|
async function* listChildren(segments, dirHandle, recursive) {
|
|
25
|
-
for await (const handle of
|
|
160
|
+
for await (const handle of that._listDirectoryContent(dirHandle)) {
|
|
26
161
|
yield await that._getFileInfo([...segments, handle.name], handle);
|
|
27
|
-
if (recursive && handle
|
|
162
|
+
if (recursive && that.adapter.isDirectoryHandle(handle)) {
|
|
28
163
|
yield* listChildren(
|
|
29
164
|
[...segments, handle.name],
|
|
30
165
|
handle,
|
|
@@ -37,32 +172,45 @@ export default class BrowserFilesApi extends FilesApi {
|
|
|
37
172
|
|
|
38
173
|
async stats(file) {
|
|
39
174
|
const segments = this._getPathSegments(file);
|
|
40
|
-
const handle = await this.
|
|
175
|
+
const handle = await this._getHandle(segments);
|
|
41
176
|
return handle ? await this._getFileInfo(segments, handle) : null;
|
|
42
177
|
}
|
|
43
178
|
|
|
44
179
|
async remove(file) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
180
|
+
const doRemove = async (dirHandle, handle) => {
|
|
181
|
+
let result = true;
|
|
182
|
+
if (this.adapter.isDirectoryHandle(handle)) {
|
|
183
|
+
const children = [];
|
|
184
|
+
for await (let child of this.adapter.listDirectoryChildren(handle)) {
|
|
185
|
+
children.push(child);
|
|
186
|
+
}
|
|
187
|
+
for (let child of children) {
|
|
188
|
+
result = result && await doRemove(handle, child);
|
|
189
|
+
}
|
|
54
190
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
191
|
+
if (dirHandle) {
|
|
192
|
+
result &= await this.adapter.removeDirectoryEntry(dirHandle, handle);
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const segments = this._getPathSegments(file);
|
|
198
|
+
const handle = await this._getHandle(segments);
|
|
199
|
+
segments.pop();
|
|
200
|
+
const dirHandle = await this._getHandle(segments);
|
|
201
|
+
return doRemove(dirHandle !== handle ? dirHandle : null, handle);
|
|
60
202
|
}
|
|
61
203
|
|
|
62
204
|
async write(file, content) {
|
|
63
205
|
const segments = this._getPathSegments(file);
|
|
64
|
-
const
|
|
65
|
-
const
|
|
206
|
+
const fileName = segments.pop();
|
|
207
|
+
const dirHandle = await this._getDirectoryHandle(segments, true);
|
|
208
|
+
const fileHandle = await this.adapter.getFileHandle(
|
|
209
|
+
dirHandle,
|
|
210
|
+
fileName,
|
|
211
|
+
true,
|
|
212
|
+
);
|
|
213
|
+
const writable = await this.adapter.getFileWriteStream(fileHandle);
|
|
66
214
|
try {
|
|
67
215
|
if (typeof content === "function") content = content();
|
|
68
216
|
for await (let block of content) {
|
|
@@ -75,18 +223,26 @@ export default class BrowserFilesApi extends FilesApi {
|
|
|
75
223
|
|
|
76
224
|
async *read(file /* { start = 0, bufferSize = 1024 * 8 } = {} */) {
|
|
77
225
|
const segments = this._getPathSegments(file);
|
|
78
|
-
|
|
226
|
+
|
|
227
|
+
const fileName = segments.pop();
|
|
228
|
+
const dirHandle = await this._getDirectoryHandle(segments, false);
|
|
229
|
+
const fileHandle = await this.adapter.getFileHandle(
|
|
230
|
+
dirHandle,
|
|
231
|
+
fileName,
|
|
232
|
+
false,
|
|
233
|
+
);
|
|
79
234
|
if (!fileHandle) return;
|
|
80
|
-
const fileData = await
|
|
235
|
+
const fileData = await this.adapter.getFile(fileHandle);
|
|
81
236
|
const stream = fileData.stream();
|
|
82
237
|
const reader = stream.getReader();
|
|
83
238
|
try {
|
|
84
239
|
let chunk;
|
|
85
|
-
while (
|
|
86
|
-
|
|
240
|
+
while (chunk = await reader.read()) {
|
|
241
|
+
if (chunk.done) break;
|
|
242
|
+
yield await chunk.value;
|
|
87
243
|
}
|
|
88
244
|
} finally {
|
|
89
|
-
reader.cancel && reader.cancel();
|
|
245
|
+
await reader.cancel && reader.cancel();
|
|
90
246
|
// stream.cancel && stream.cancel();
|
|
91
247
|
}
|
|
92
248
|
}
|
|
@@ -97,41 +253,8 @@ export default class BrowserFilesApi extends FilesApi {
|
|
|
97
253
|
// TODO:
|
|
98
254
|
// async move(fromPath, toPath, options = {}) { ... }
|
|
99
255
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
handle = await this._getFileHandler(segments, create);
|
|
104
|
-
} catch (e) {
|
|
105
|
-
try {
|
|
106
|
-
handle = await this._getDirectoryHandler(segments, create);
|
|
107
|
-
} catch (e) {
|
|
108
|
-
// Just ignore it
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return handle;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async _getDirectoryHandler(segments, create = true) {
|
|
115
|
-
segments = [...segments];
|
|
116
|
-
let dirHandle = this.rootHandler;
|
|
117
|
-
for (let directoryName of segments) {
|
|
118
|
-
dirHandle = await dirHandle.getDirectoryHandle(directoryName, { create });
|
|
119
|
-
}
|
|
120
|
-
return dirHandle;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async _getFileHandler(segments, create = true) {
|
|
124
|
-
segments = [...segments];
|
|
125
|
-
const filename = segments.pop();
|
|
126
|
-
let dirHandle = this.rootHandler;
|
|
127
|
-
for (let directoryName of segments) {
|
|
128
|
-
dirHandle = await dirHandle.getDirectoryHandle(directoryName, {
|
|
129
|
-
create: true,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
const fileHandler = await dirHandle.getFileHandle(filename, { create });
|
|
133
|
-
return fileHandler;
|
|
134
|
-
}
|
|
256
|
+
// ---------------------------------------------------------
|
|
257
|
+
// Private methods
|
|
135
258
|
|
|
136
259
|
async _getFileInfo(segments, handle) {
|
|
137
260
|
segments = [...segments];
|
|
@@ -143,11 +266,12 @@ export default class BrowserFilesApi extends FilesApi {
|
|
|
143
266
|
path,
|
|
144
267
|
name,
|
|
145
268
|
};
|
|
146
|
-
if (handle
|
|
269
|
+
if (this.adapter.isDirectoryHandle(handle)) {
|
|
147
270
|
info.kind = "directory";
|
|
148
271
|
} else {
|
|
149
272
|
info.kind = "file";
|
|
150
|
-
|
|
273
|
+
|
|
274
|
+
const file = await this.adapter.getFile(handle);
|
|
151
275
|
info.size = file.size;
|
|
152
276
|
info.type = getMimeType(path);
|
|
153
277
|
info.lastModified = file.lastModified;
|
|
@@ -161,4 +285,51 @@ export default class BrowserFilesApi extends FilesApi {
|
|
|
161
285
|
const segments = filePath.split("/").filter((s) => !!s);
|
|
162
286
|
return segments;
|
|
163
287
|
}
|
|
288
|
+
|
|
289
|
+
async _getHandle(segments) {
|
|
290
|
+
const parentDirSegments = [...segments];
|
|
291
|
+
const name = parentDirSegments.pop();
|
|
292
|
+
const create = false;
|
|
293
|
+
try {
|
|
294
|
+
const dirHandle = await this._getDirectoryHandle(
|
|
295
|
+
parentDirSegments,
|
|
296
|
+
create,
|
|
297
|
+
);
|
|
298
|
+
try {
|
|
299
|
+
if (!name) return dirHandle;
|
|
300
|
+
return await this.adapter.getSubdirectoryHandle(
|
|
301
|
+
dirHandle,
|
|
302
|
+
name,
|
|
303
|
+
create,
|
|
304
|
+
);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
return await this.adapter.getFileHandle(dirHandle, name, create);
|
|
307
|
+
}
|
|
308
|
+
} catch (error) {
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async _getDirectoryHandle(segments, create = true) {
|
|
314
|
+
segments = [...segments];
|
|
315
|
+
let dirHandle = this.rootHandle;
|
|
316
|
+
for (let directoryName of segments) {
|
|
317
|
+
dirHandle = await this.adapter.getSubdirectoryHandle(
|
|
318
|
+
dirHandle,
|
|
319
|
+
directoryName,
|
|
320
|
+
create,
|
|
321
|
+
);
|
|
322
|
+
if (!dirHandle) break;
|
|
323
|
+
}
|
|
324
|
+
return dirHandle;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async *_listDirectoryContent(handle) {
|
|
328
|
+
let entries = [];
|
|
329
|
+
for await (const entry of this.adapter.listDirectoryChildren(handle)) {
|
|
330
|
+
entries.push(entry);
|
|
331
|
+
}
|
|
332
|
+
entries = entries.sort((a, b) => b.name > a.name ? 1 : -1);
|
|
333
|
+
yield* entries;
|
|
334
|
+
}
|
|
164
335
|
}
|