@statewalker/webrun-files 0.3.1 → 0.4.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/cjs/index.cjs +564 -0
- package/dist/esm/index.js +551 -0
- package/dist/files-api.d.ts +44 -0
- package/dist/files-api.d.ts.map +1 -0
- package/dist/impl/index.d.ts +6 -0
- package/dist/impl/index.d.ts.map +1 -0
- package/dist/impl/mem-files-api.d.ts +13 -0
- package/dist/impl/mem-files-api.d.ts.map +1 -0
- package/dist/impl/node-files-api.d.ts +24 -0
- package/dist/impl/node-files-api.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/collect-stream.d.ts +16 -0
- package/dist/utils/collect-stream.d.ts.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/normalize-path.d.ts +21 -0
- package/dist/utils/normalize-path.d.ts.map +1 -0
- package/dist/utils/path-utils.d.ts +20 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/package.json +29 -31
- package/src/files-api.ts +167 -0
- package/src/impl/index.ts +6 -0
- package/src/impl/mem-files-api.ts +306 -0
- package/src/impl/node-files-api.ts +261 -0
- package/src/index.ts +35 -0
- package/src/types.ts +68 -0
- package/src/utils/collect-stream.ts +35 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/normalize-path.ts +34 -0
- package/src/utils/path-utils.ts +46 -0
- package/CHANGELOG.md +0 -2
- package/README.md +0 -17
- package/dist/index-umd.js +0 -1054
- package/dist/index-umd.min.js +0 -2
- package/dist/index.js +0 -1033
- package/dist/index.min.js +0 -2
- package/index.js +0 -1
- package/src/BrowserFilesApi.js +0 -335
- package/src/FilesApi.js +0 -133
- package/src/MemFilesApi.js +0 -129
- package/src/NodeFilesApi.js +0 -222
- package/src/addFilesApiLogger.js +0 -25
- package/src/getMimeType.js +0 -19
- package/src/index.js +0 -10
- package/src/mimeTypes.js +0 -96
- package/src/normalizePath.js +0 -8
- package/src/notImplemented.js +0 -3
- package/src/openBrowserFilesApi.js +0 -67
package/dist/index-umd.js
DELETED
|
@@ -1,1054 +0,0 @@
|
|
|
1
|
-
// @statewalker/webrun-files v0.3.1 https://github.com/statewalker/webrun-files Copyright 2024 Mikhail Kotelnikov
|
|
2
|
-
(function (global, factory) {
|
|
3
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
4
|
-
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
5
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.statewalker = global.statewalker || {}, global.statewalker["webrun-files"] = global.statewalker["webrun-files"] || {})));
|
|
6
|
-
})(this, (function (exports) { 'use strict';
|
|
7
|
-
|
|
8
|
-
function normalizePath(filePath) {
|
|
9
|
-
const segments = filePath.split("/").filter((s) => !!s && s !== ".");
|
|
10
|
-
if (segments.length === 0) {
|
|
11
|
-
segments.push("");
|
|
12
|
-
}
|
|
13
|
-
segments.unshift("");
|
|
14
|
-
return segments.join("/");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function notImplemented(){
|
|
18
|
-
throw new Error('Not implemented.');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/*
|
|
22
|
-
* FileInfo
|
|
23
|
-
* - `kind` - (`file`|`directory`) kind of the returned entity
|
|
24
|
-
* - `name` - the local name of this file entry (the last segment of the path)
|
|
25
|
-
* - `path` - path to the file from the root of the "file system"
|
|
26
|
-
* - `type` - an optional mime type of the file content; it is not defined for directories
|
|
27
|
-
* - `size` - size of the file (only for files)
|
|
28
|
-
* - `lastModified` - modification time as a long integer
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* This is a common interface providing access to methods dealing with files.
|
|
33
|
-
*/
|
|
34
|
-
class FilesApi {
|
|
35
|
-
constructor(options) {
|
|
36
|
-
this.options = options || {};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Returns an async generator providing information about files (FileInfo[]).
|
|
41
|
-
* Paths corresponding to directories has the "/" trailing symbol.
|
|
42
|
-
* @param {Object|String} file path to the folder or an object returned by the `list` or `info` methods
|
|
43
|
-
* @param {Object} options optional parameters
|
|
44
|
-
* @param {Boolean} options.recursive this flag defines if the returned generator
|
|
45
|
-
* should give access to all child directories and files or just direct children
|
|
46
|
-
* @return {AsyncGenerator<FileInfo>} generator returning information about
|
|
47
|
-
* individual file entries
|
|
48
|
-
*/
|
|
49
|
-
async *list(file, options = {}) {
|
|
50
|
-
yield notImplemented();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Returns information about an individual file.
|
|
55
|
-
* @param {Object|String} file path to the file or an object returned by
|
|
56
|
-
* the `list` or `info` methods
|
|
57
|
-
* @return {FileInfo} information about the file corresponding to the specified
|
|
58
|
-
* path or null if there is no such a file
|
|
59
|
-
*/
|
|
60
|
-
async stats(file) {
|
|
61
|
-
return notImplemented();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Removes file or folder corresponding to the specified path.
|
|
66
|
-
* All children of this entry will be removed as well.
|
|
67
|
-
* @param {Object|String} file path to the file to remove or an object
|
|
68
|
-
* returned by the `list` or `info` methods
|
|
69
|
-
* @return {Boolean} return true if the file was removed
|
|
70
|
-
*/
|
|
71
|
-
async remove(file) {
|
|
72
|
-
return notImplemented();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Creates and returns a write stream for the file with the specified path.
|
|
77
|
-
* @param {Object|String} file path to the file or an object returned by
|
|
78
|
-
* the `list` or `info` methods
|
|
79
|
-
* @param {AsyncGenerator|AsyncIterator} content async generator function providing the binary
|
|
80
|
-
* file content to store
|
|
81
|
-
* @return {any} result of the action execution
|
|
82
|
-
*/
|
|
83
|
-
async write(file, content) {
|
|
84
|
-
return notImplemented();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Creates an returns an AsyncIterator returning byte blocks with the file content.
|
|
89
|
-
* @param {Object|String} file path to the file or an object returned by
|
|
90
|
-
* the `list` or `info` methods
|
|
91
|
-
* @param {object} options read parameters
|
|
92
|
-
* @param {number} options.start starting read position; default value is 0
|
|
93
|
-
* @param {number} options.bufferSize the optional size of returned chunks buffers
|
|
94
|
-
*/
|
|
95
|
-
async *read(file, options) {
|
|
96
|
-
yield notImplemented();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Copies a file from one path to another.
|
|
101
|
-
* @param {String} fromPath path to the source file
|
|
102
|
-
* @param {String} toPath path to the target file
|
|
103
|
-
* @param {Object} options additional options
|
|
104
|
-
* @param {Store} options.recursive if this flag is true then subfolders are also copied
|
|
105
|
-
* @param {Store} options.target optional target store where data should be copied
|
|
106
|
-
* @return {Boolean} if the file was successfully copied
|
|
107
|
-
*/
|
|
108
|
-
async copy(source, target, options = {}) {
|
|
109
|
-
const doCopy = async (sourcePath, targetPath, sourceFile) => {
|
|
110
|
-
const suffix = sourceFile.path.substring(sourcePath.length);
|
|
111
|
-
targetPath = `${targetPath}${suffix}`;
|
|
112
|
-
const content = this.read(sourceFile);
|
|
113
|
-
return await this.write(targetPath, content);
|
|
114
|
-
};
|
|
115
|
-
const recursive = options.recursive === undefined || !!options.recursive;
|
|
116
|
-
const sourceFile = await this.stats(source);
|
|
117
|
-
if (sourceFile.kind === "directory") {
|
|
118
|
-
for await (const file of this.list(source, { ...options, recursive })) {
|
|
119
|
-
if (file.kind === "directory") continue;
|
|
120
|
-
await doCopy(source, target, file);
|
|
121
|
-
}
|
|
122
|
-
} else {
|
|
123
|
-
await doCopy(source, target, sourceFile);
|
|
124
|
-
}
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Moves files from the initial position to the target path.
|
|
130
|
-
* The default implementation creates a new file copy in the new location
|
|
131
|
-
* and removes the old file.
|
|
132
|
-
*
|
|
133
|
-
* @param {String} fromPath path to the source file
|
|
134
|
-
* @param {String} toPath path to the target file
|
|
135
|
-
* @param {Object} options additional options
|
|
136
|
-
* @param {Store} options.target optional target store where data should be moved
|
|
137
|
-
* @return {Boolean} if the file was successfully moved
|
|
138
|
-
*/
|
|
139
|
-
async move(fromPath, toPath, options = {}) {
|
|
140
|
-
if (!await this.copy(fromPath, toPath, options)) return false;
|
|
141
|
-
await this.remove(fromPath);
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
normalizePath(file) {
|
|
146
|
-
let filePath = typeof file === "object" ? file.path : file + "";
|
|
147
|
-
return normalizePath(filePath);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// https://docs.w3cub.com/http/basics_of_http/mime_types/complete_list_of_mime_types
|
|
152
|
-
const mimeTypes = [
|
|
153
|
-
{ "extensions": ["aac"], "mime": "audio/aac", "description": "AAC audio" },
|
|
154
|
-
{ "extensions": ["abw"], "mime": "application/x-abiword", "description": "AbiWord document" },
|
|
155
|
-
{ "extensions": ["arc"], "mime": "application/x-freearc", "description": "Archive document (multiple files embedded)" },
|
|
156
|
-
{ "extensions": ["avi"], "mime": "video/x-msvideo", "description": "AVI: Audio Video Interleave" },
|
|
157
|
-
{ "extensions": ["azw"], "mime": "application/vnd.amazon.ebook", "description": "Amazon Kindle eBook format" },
|
|
158
|
-
{ "extensions": ["bin"], "mime": "application/octet-stream", "description": "Any kind of binary data" },
|
|
159
|
-
{ "extensions": ["bmp"], "mime": "image/bmp", "description": "Windows OS/2 Bitmap Graphics" },
|
|
160
|
-
{ "extensions": ["bz"], "mime": "application/x-bzip", "description": "BZip archive" },
|
|
161
|
-
{ "extensions": ["bz2"], "mime": "application/x-bzip2", "description": "BZip2 archive" },
|
|
162
|
-
{ "extensions": ["csh"], "mime": "application/x-csh", "description": "C-Shell script" },
|
|
163
|
-
{ "extensions": ["css"], "mime": "text/css", "description": "Cascading Style Sheets (CSS)" },
|
|
164
|
-
{ "extensions": ["csv"], "mime": "text/csv", "description": "Comma-separated values (CSV)" },
|
|
165
|
-
{ "extensions": ["doc"], "mime": "application/msword", "description": "Microsoft Word" },
|
|
166
|
-
{ "extensions": ["docx"], "mime": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "description": "Microsoft Word (OpenXML)" },
|
|
167
|
-
{ "extensions": ["eot"], "mime": "application/vnd.ms-fontobject", "description": "MS Embedded OpenType fonts" },
|
|
168
|
-
{ "extensions": ["epub"], "mime": "application/epub+zip", "description": "Electronic publication (EPUB)" },
|
|
169
|
-
{ "extensions": ["gz"], "mime": "application/gzip", "description": "GZip Compressed Archive" },
|
|
170
|
-
{ "extensions": ["gif"], "mime": "image/gif", "description": "Graphics Interchange Format (GIF)" },
|
|
171
|
-
{ "extensions": ["htm", "html"], "mime": "text/html", "description": "HyperText Markup Language (HTML)" },
|
|
172
|
-
{ "extensions": ["ico"], "mime": "image/vnd.microsoft.icon", "description": "Icon format" },
|
|
173
|
-
{ "extensions": ["ics"], "mime": "text/calendar", "description": "iCalendar format" },
|
|
174
|
-
{ "extensions": ["jar"], "mime": "application/java-archive", "description": "Java Archive (JAR)" },
|
|
175
|
-
{ "extensions": ["jpg", "jpeg"], "mime": "image/jpeg", "description": "JPEG images" },
|
|
176
|
-
{ "extensions": ["js"], "mime": "text/javascript", "description": "JavaScript per the following specifications: https://html.spec.whatwg.org/multipage/#scriptingLanguages, https://html.spec.whatwg.org/multipage/#dependencies:willful-violation, https://datatracker.ietf.org/doc/draft-ietf-dispatch-javascript-mjs/" },
|
|
177
|
-
{ "extensions": ["json"], "mime": "application/json", "description": "JSON format" },
|
|
178
|
-
{ "extensions": ["jsonld"], "mime": "application/ld+json", "description": "JSON-LD format" },
|
|
179
|
-
{ "extensions": ["mid", "midi"], "mime": "application/ld+json", "description": "Musical Instrument Digital Interface (MIDI) audio/midi audio/x-midi" },
|
|
180
|
-
{ "extensions": ["mjs"], "mime": "text/javascript", "description": "JavaScript module" },
|
|
181
|
-
{ "extensions": ["mp3"], "mime": "audio/mpeg", "description": "MP3 audio" },
|
|
182
|
-
{ "extensions": ["md"], "mime": "text/markdown", "description": "Markdown" },
|
|
183
|
-
{ "extensions": ["mpeg"], "mime": "video/mpeg", "description": "MPEG Video" },
|
|
184
|
-
{ "extensions": ["mpkg"], "mime": "application/vnd.apple.installer+xml", "description": "Apple Installer Package" },
|
|
185
|
-
{ "extensions": ["odp"], "mime": "application/vnd.oasis.opendocument.presentation", "description": "OpenDocument presentation document" },
|
|
186
|
-
{ "extensions": ["ods"], "mime": "application/vnd.oasis.opendocument.spreadsheet", "description": "OpenDocument spreadsheet document" },
|
|
187
|
-
{ "extensions": ["odt"], "mime": "application/vnd.oasis.opendocument.text", "description": "OpenDocument text document" },
|
|
188
|
-
{ "extensions": ["oga"], "mime": "audio/ogg", "description": "OGG audio" },
|
|
189
|
-
{ "extensions": ["ogv"], "mime": "video/ogg", "description": "OGG video" },
|
|
190
|
-
{ "extensions": ["ogx"], "mime": "application/ogg", "description": "OGG" },
|
|
191
|
-
{ "extensions": ["opus"], "mime": "audio/opus", "description": "Opus audio" },
|
|
192
|
-
{ "extensions": ["otf"], "mime": "font/otf", "description": "OpenType font" },
|
|
193
|
-
{ "extensions": ["png"], "mime": "image/png", "description": "Portable Network Graphics" },
|
|
194
|
-
{ "extensions": ["pdf"], "mime": "application/pdf", "description": "Adobe Portable Document Format (PDF)" },
|
|
195
|
-
{ "extensions": ["php"], "mime": "application/x-httpd-php", "description": "Hypertext Preprocessor (Personal Home Page)" },
|
|
196
|
-
{ "extensions": ["ppt"], "mime": "application/vnd.ms-powerpoint", "description": "Microsoft PowerPoint" },
|
|
197
|
-
{ "extensions": ["pptx"], "mime": "application/vnd.openxmlformats-officedocument.presentationml.presentation", "description": "Microsoft PowerPoint (OpenXML)" },
|
|
198
|
-
{ "extensions": ["rar"], "mime": "application/vnd.rar", "description": "RAR archive" },
|
|
199
|
-
{ "extensions": ["rtf"], "mime": "application/rtf", "description": "Rich Text Format (RTF)" },
|
|
200
|
-
{ "extensions": ["sh"], "mime": "application/x-sh", "description": "Bourne shell script" },
|
|
201
|
-
{ "extensions": ["svg"], "mime": "image/svg+xml", "description": "Scalable Vector Graphics (SVG)" },
|
|
202
|
-
{ "extensions": ["swf"], "mime": "application/x-shockwave-flash", "description": "Small web format (SWF) or Adobe Flash document" },
|
|
203
|
-
{ "extensions": ["tar"], "mime": "application/x-tar", "description": "Tape Archive (TAR)" },
|
|
204
|
-
{ "extensions": ["tif", "tiff"], "mime": "image/tiff", "description": "Tagged Image File Format (TIFF)" },
|
|
205
|
-
// { "extensions": ["ts"], "mime": "video/mp2t", "description": "MPEG transport stream" },
|
|
206
|
-
{ "extensions": ["ttf"], "mime": "font/ttf", "description": "TrueType Font" },
|
|
207
|
-
{ "extensions": ["txt"], "mime": "text/plain", "description": "Text, (generally ASCII or ISO 8859-n)" },
|
|
208
|
-
{ "extensions": ["vsd"], "mime": "application/vnd.visio", "description": "Microsoft Visio" },
|
|
209
|
-
{ "extensions": ["wav"], "mime": "audio/wav", "description": "Waveform Audio Format" },
|
|
210
|
-
{ "extensions": ["weba"], "mime": "audio/webm", "description": "WEBM audio" },
|
|
211
|
-
{ "extensions": ["webm"], "mime": "video/webm", "description": "WEBM video" },
|
|
212
|
-
{ "extensions": ["webp"], "mime": "image/webp", "description": "WEBP image" },
|
|
213
|
-
{ "extensions": ["woff"], "mime": "font/woff", "description": "Web Open Font Format (WOFF)" },
|
|
214
|
-
{ "extensions": ["woff2"], "mime": "font/woff2", "description": "Web Open Font Format (WOFF)" },
|
|
215
|
-
{ "extensions": ["xhtml"], "mime": "application/xhtml+xml", "description": "XHTML" },
|
|
216
|
-
{ "extensions": ["xls"], "mime": "application/vnd.ms-excel", "description": "Microsoft Excel" },
|
|
217
|
-
{ "extensions": ["xlsx"], "mime": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "description": "Microsoft Excel (OpenXML)" },
|
|
218
|
-
{ "extensions": ["xml"], "mime": "application/xml", "description": "XML; application/xml - if not readable from casual users (RFC 3023, section 3); text/xml - if readable from casual users (RFC 3023, section 3)" },
|
|
219
|
-
{ "extensions": ["xul"], "mime": "application/vnd.mozilla.xul+xml", "description": "XUL" },
|
|
220
|
-
{ "extensions": ["zip"], "mime": "application/zip", "description": "ZIP archive" },
|
|
221
|
-
{ "extensions": ["3gp"], "mime": "video/3gpp", "description": "3GPP audio/video container" },
|
|
222
|
-
{ "extensions": ["3g2"], "mime": "video/3gpp2", "description": "3GPP2 audio/video container; audio/3gpp if it doesn't contain video; audio/3gpp2 if it doesn't contain video" },
|
|
223
|
-
{ "extensions": ["7z"], "mime": "application/x-7z-compressed", "description": "7-zip archive" },
|
|
224
|
-
|
|
225
|
-
{ "extensions": ["jsx"], "mime": "text/jsx", "description": "JavaScript Syntax Extension / JavaScript XML format" },
|
|
226
|
-
{ "extensions": ["ts"], "mime": "text/ts", "description": "TypeScript format" },
|
|
227
|
-
{ "extensions": ["tsx"], "mime": "text/tsx", "description": "TypeScript Syntax Extension / TypeScript XML format" },
|
|
228
|
-
|
|
229
|
-
];
|
|
230
|
-
|
|
231
|
-
const mimeTypesDescriptions = mimeTypes.reduce((index, t) => {
|
|
232
|
-
index[t.mime] = t.description;
|
|
233
|
-
return index;
|
|
234
|
-
}, {});
|
|
235
|
-
|
|
236
|
-
const extensionToMimeType = mimeTypes.reduce((index, t) => {
|
|
237
|
-
for (const ext of t.extensions) {
|
|
238
|
-
index[ext] = t.mime;
|
|
239
|
-
}
|
|
240
|
-
return index;
|
|
241
|
-
}, {});
|
|
242
|
-
|
|
243
|
-
const mimeTypeToExtensions = mimeTypes.reduce((index, t) => {
|
|
244
|
-
index[t.mime] = t.extensions;
|
|
245
|
-
return index;
|
|
246
|
-
}, {});
|
|
247
|
-
|
|
248
|
-
// import mimeDb from 'mime-db';
|
|
249
|
-
// import mimeTypes from 'mime-types';
|
|
250
|
-
|
|
251
|
-
function getMimeType(url) {
|
|
252
|
-
url = url || '';
|
|
253
|
-
const originalUrl = url;
|
|
254
|
-
url = url.split('?').shift(); // Remove queries (if any)
|
|
255
|
-
url = url.split('/').pop(); // Keep only the last segment of the path
|
|
256
|
-
const extension = url.split('.').pop(); // Get extension
|
|
257
|
-
let mimeType = extensionToMimeType[extension];
|
|
258
|
-
if (!mimeType) {
|
|
259
|
-
mimeType =
|
|
260
|
-
originalUrl.match(/api\.observablehq\.com/) || originalUrl.match(/\.ojs$/)
|
|
261
|
-
? 'application/javascript-observable'
|
|
262
|
-
: 'application/binary';
|
|
263
|
-
}
|
|
264
|
-
return mimeType;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
class MemFilesApi extends FilesApi {
|
|
268
|
-
constructor(options) {
|
|
269
|
-
super(options);
|
|
270
|
-
this.index = {
|
|
271
|
-
"/" : {
|
|
272
|
-
"kind": "directory",
|
|
273
|
-
"path": "/",
|
|
274
|
-
"name": ""
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async *list(file, { recursive = false } = {}) {
|
|
280
|
-
await this._init();
|
|
281
|
-
const filePath = this.normalizePath(file);
|
|
282
|
-
const paths = this._getPaths();
|
|
283
|
-
for (const path of paths) {
|
|
284
|
-
if (path.indexOf(filePath) !== 0 || path === filePath) continue;
|
|
285
|
-
const suffix = path.substring(filePath.length + 1);
|
|
286
|
-
const segments = suffix.split("/");
|
|
287
|
-
if (recursive || segments.length === 1) {
|
|
288
|
-
yield this._getFileInfo(path);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async stats(file) {
|
|
294
|
-
await this._init();
|
|
295
|
-
const filePath = this.normalizePath(file);
|
|
296
|
-
return this._getFileInfo(filePath);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async remove(file) {
|
|
300
|
-
await this._init();
|
|
301
|
-
const fileInfo = await this.stats(file);
|
|
302
|
-
if (!fileInfo) return false;
|
|
303
|
-
for await (const { path } of this.list(fileInfo, { recursive: true })) {
|
|
304
|
-
if (path !== "/") delete this.index[path];
|
|
305
|
-
}
|
|
306
|
-
delete this.index[fileInfo.path];
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async write(file, content) {
|
|
310
|
-
await this._init();
|
|
311
|
-
await this._doWrite(file, content);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
async *read(file /* { start = 0, bufferSize = 1024 * 8 } = {} */) {
|
|
315
|
-
await this._init();
|
|
316
|
-
const filePath = this.normalizePath(file);
|
|
317
|
-
const fileInfo = this.index[filePath];
|
|
318
|
-
if (!fileInfo || fileInfo.kind !== "file") return ; // throw new Error("Not a file");
|
|
319
|
-
const content = fileInfo.content;
|
|
320
|
-
yield* content;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// TODO:
|
|
324
|
-
// async copy(fromPath, toPath, options = {}) { ... }
|
|
325
|
-
|
|
326
|
-
// TODO:
|
|
327
|
-
// async move(fromPath, toPath, options = {}) { ... }
|
|
328
|
-
|
|
329
|
-
async _init() {
|
|
330
|
-
return this._initPromise = this._initPromise ||
|
|
331
|
-
Promise.resolve().then(() => this._addEntries(this.options.files));
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
async _doWrite(file, content) {
|
|
335
|
-
const filePath = this.normalizePath(file);
|
|
336
|
-
const segments = filePath.split("/").filter((s) => !!s);
|
|
337
|
-
const pathSegments = [""];
|
|
338
|
-
let info;
|
|
339
|
-
for (let i = 0; i < segments.length; i++) {
|
|
340
|
-
const name = segments[i];
|
|
341
|
-
pathSegments.push(name);
|
|
342
|
-
const path = pathSegments.join("/");
|
|
343
|
-
info = this.index[path] = this.index[path] || {
|
|
344
|
-
path,
|
|
345
|
-
name,
|
|
346
|
-
kind: "directory",
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
info.kind = "file";
|
|
350
|
-
info.content = [];
|
|
351
|
-
info.type = getMimeType(info.path);
|
|
352
|
-
info.size = 0;
|
|
353
|
-
info.lastModified = Date.now();
|
|
354
|
-
if (typeof content === "function") content = content();
|
|
355
|
-
for await (let chunk of content) {
|
|
356
|
-
chunk = new Uint8Array(chunk);
|
|
357
|
-
info.content.push(chunk);
|
|
358
|
-
info.size += chunk.length;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async _addEntries(index = {}) {
|
|
363
|
-
for (let [path, content] of Object.entries(index)) {
|
|
364
|
-
if (typeof content === "string") {
|
|
365
|
-
content = [new TextEncoder().encode(content)];
|
|
366
|
-
} else if (content instanceof Uint8Array) {
|
|
367
|
-
content = [content];
|
|
368
|
-
}
|
|
369
|
-
await this._doWrite(path, content);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
_getPaths() {
|
|
374
|
-
return this._getIndexPaths(this.index);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
_getIndexPaths(index) {
|
|
378
|
-
return Object.keys(index).sort(compare);
|
|
379
|
-
function compare(a, b) {
|
|
380
|
-
return a > b ? 1 : a < b ? -1 : 0;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
_getFileInfo(path) {
|
|
385
|
-
const fileInfo = this.index[path];
|
|
386
|
-
if (!fileInfo) return null;
|
|
387
|
-
const info = { ...fileInfo };
|
|
388
|
-
delete info.content;
|
|
389
|
-
return info;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
class NodeFilesApi extends FilesApi {
|
|
395
|
-
constructor(options) {
|
|
396
|
-
super(options);
|
|
397
|
-
if (!this.fs) throw new Error("File System is not defined.");
|
|
398
|
-
if (!this.rootDir) throw new Error("Root directory is not defined.");
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
get rootDirSegments() {
|
|
402
|
-
if (!this._rootDirSegments) {
|
|
403
|
-
this._rootDirSegments = this._toPathSegments(
|
|
404
|
-
this.options.rootDir || "./",
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
return this._rootDirSegments;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
get rootDir() {
|
|
411
|
-
if (!this._rootDir) {
|
|
412
|
-
this._rootDir = this.rootDirSegments.join("/");
|
|
413
|
-
}
|
|
414
|
-
return this._rootDir;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
get fs() {
|
|
418
|
-
return this.options.fs;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
async _init() {
|
|
422
|
-
return this._initialized = this._initialized ||
|
|
423
|
-
(async () => {
|
|
424
|
-
const dirPath = this.rootDir;
|
|
425
|
-
return await mkdir(this.fs, dirPath);
|
|
426
|
-
})();
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
async *list(file, { recursive = false } = {}) {
|
|
430
|
-
await this._init();
|
|
431
|
-
const filePath = this._expandPath(file);
|
|
432
|
-
const fileInfo = await this._stats(filePath);
|
|
433
|
-
if (!fileInfo || fileInfo.kind !== "directory") return;
|
|
434
|
-
for await (let f of list(this.fs, filePath, recursive)) {
|
|
435
|
-
yield this._getFileInfo(f.path, f.stat);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
async stats(file) {
|
|
440
|
-
await this._init();
|
|
441
|
-
const filePath = this._expandPath(file);
|
|
442
|
-
return this._stats(filePath);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
async remove(file) {
|
|
446
|
-
await this._init();
|
|
447
|
-
const filePath = this._expandPath(file);
|
|
448
|
-
return await remove(this.fs, filePath);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
async write(file, content) {
|
|
452
|
-
await this._init();
|
|
453
|
-
const filePath = this._expandPath(file);
|
|
454
|
-
const dir = pathDirname(filePath);
|
|
455
|
-
await this.fs.mkdir(dir, { recursive: true });
|
|
456
|
-
let handle;
|
|
457
|
-
try {
|
|
458
|
-
handle = await this.fs.open(filePath, "w");
|
|
459
|
-
if (typeof content === "function") content = content();
|
|
460
|
-
await handle.writeFile(content);
|
|
461
|
-
} finally {
|
|
462
|
-
handle && await handle.close();
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
async *read(file, { start = 0, bufferSize = 1024 * 8 } = {}) {
|
|
467
|
-
await this._init();
|
|
468
|
-
const filePath = this._expandPath(file);
|
|
469
|
-
let handle;
|
|
470
|
-
try {
|
|
471
|
-
handle = await this.fs.open(filePath, "r");
|
|
472
|
-
const offset = 0;
|
|
473
|
-
for (let position = start, bytesRead = 0; true; position += bytesRead) {
|
|
474
|
-
const buffer = new Uint8ClampedArray(bufferSize);
|
|
475
|
-
const result = await handle.read(
|
|
476
|
-
buffer,
|
|
477
|
-
offset,
|
|
478
|
-
bufferSize,
|
|
479
|
-
position,
|
|
480
|
-
);
|
|
481
|
-
bytesRead = result.bytesRead;
|
|
482
|
-
if (bytesRead === 0) break;
|
|
483
|
-
yield bytesRead < bufferSize
|
|
484
|
-
? result.buffer.slice(0, bytesRead)
|
|
485
|
-
: result.buffer;
|
|
486
|
-
}
|
|
487
|
-
} finally {
|
|
488
|
-
handle && await handle.close();
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// TODO:
|
|
493
|
-
// async copy(fromPath, toPath, options = {}) { ... }
|
|
494
|
-
|
|
495
|
-
// TODO:
|
|
496
|
-
// async move(fromPath, toPath, options = {}) { ... }
|
|
497
|
-
|
|
498
|
-
async _stats(filePath) {
|
|
499
|
-
try {
|
|
500
|
-
const stat = await this.fs.stat(filePath);
|
|
501
|
-
return this._getFileInfo(filePath, stat);
|
|
502
|
-
} catch (err) {
|
|
503
|
-
if (err.code !== "ENOENT") throw err;
|
|
504
|
-
return null;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
_getFileInfo(path, stat) {
|
|
509
|
-
const segments = this._toPathSegments(path).slice(
|
|
510
|
-
this.rootDirSegments.length,
|
|
511
|
-
);
|
|
512
|
-
segments.unshift("");
|
|
513
|
-
const name = segments[segments.length - 1];
|
|
514
|
-
path = segments.join("/");
|
|
515
|
-
// const name = path.split("/").filter((s) => !!s).pop() || "";
|
|
516
|
-
let info = {
|
|
517
|
-
path,
|
|
518
|
-
name,
|
|
519
|
-
kind: "",
|
|
520
|
-
};
|
|
521
|
-
if (!stat.isFile()) {
|
|
522
|
-
info.kind = "directory";
|
|
523
|
-
} else {
|
|
524
|
-
info.size = stat.size || 0;
|
|
525
|
-
info.type = getMimeType(path);
|
|
526
|
-
info.kind = "file";
|
|
527
|
-
info.lastModified = new Date(stat.mtime).getTime();
|
|
528
|
-
}
|
|
529
|
-
return info;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
_toPathSegments(filePath) {
|
|
533
|
-
filePath = this.normalizePath(filePath);
|
|
534
|
-
const segments = filePath.split("/"); // .filter((s) => !!s);
|
|
535
|
-
// if (segments.length === 0) segments.push("");
|
|
536
|
-
// segments.unshift("");
|
|
537
|
-
return segments;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
_expandPath(file) {
|
|
541
|
-
const filePath = typeof file === "object" ? file.path : file + "";
|
|
542
|
-
const segments = this._toPathSegments(filePath).filter((s) => !!s);
|
|
543
|
-
// segments.shift(); // The first segment is always empty
|
|
544
|
-
segments.unshift(...this.rootDirSegments);
|
|
545
|
-
return segments.join("/");
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function pathDirname(path) {
|
|
550
|
-
const segments = path.split("/");
|
|
551
|
-
segments.pop();
|
|
552
|
-
return segments.join("/");
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function pathJoin(...paths) {
|
|
556
|
-
let segments = paths
|
|
557
|
-
.map((path) => path.split("/"))
|
|
558
|
-
.reduce((result, segments) => (result.push(...segments), result), []);
|
|
559
|
-
const firstSegment = segments.shift();
|
|
560
|
-
segments = segments.filter((s) => !!s && s !== ".");
|
|
561
|
-
segments.unshift(firstSegment);
|
|
562
|
-
return segments.join("/");
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
async function mkdir(fs, dir) {
|
|
566
|
-
try {
|
|
567
|
-
const stat = await fs.stat(dir);
|
|
568
|
-
if (stat && stat.isDirectory()) return true;
|
|
569
|
-
} catch (error) {
|
|
570
|
-
// Just ignore
|
|
571
|
-
}
|
|
572
|
-
const parent = pathDirname(dir);
|
|
573
|
-
if (dir === parent) return false;
|
|
574
|
-
await mkdir(fs, parent);
|
|
575
|
-
await fs.mkdir(dir, { recursive: true });
|
|
576
|
-
return true;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
async function remove(fs, path) {
|
|
580
|
-
try {
|
|
581
|
-
let stat = await fs.stat(path);
|
|
582
|
-
if (stat.isDirectory()) {
|
|
583
|
-
for await (let file of list(fs, path, false)) {
|
|
584
|
-
await remove(fs, file.path);
|
|
585
|
-
}
|
|
586
|
-
await fs.rmdir(path);
|
|
587
|
-
} else {
|
|
588
|
-
await fs.unlink(path);
|
|
589
|
-
}
|
|
590
|
-
} catch (err) {
|
|
591
|
-
if (err.code !== "ENOENT") throw err;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
async function* list(fs, filePath, recursive) {
|
|
596
|
-
try {
|
|
597
|
-
const stat = await fs.stat(filePath);
|
|
598
|
-
if (stat.isDirectory()) {
|
|
599
|
-
const array = await fs.readdir(filePath);
|
|
600
|
-
for (const name of array.sort()) {
|
|
601
|
-
const f = pathJoin(filePath, name);
|
|
602
|
-
let stat = await fs.stat(f);
|
|
603
|
-
yield { path: f, stat };
|
|
604
|
-
if (recursive && stat.isDirectory()) {
|
|
605
|
-
yield* list(fs, f, true);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
} catch (err) {
|
|
610
|
-
if (err.code !== "ENOENT") throw err;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
/**
|
|
615
|
-
* Stateless adapter for the "File and Directory Entries API".
|
|
616
|
-
* See https://developer.mozilla.org/en-US/docs/Web/API/File_and_Directory_Entries_API
|
|
617
|
-
*
|
|
618
|
-
* ## File and Directory Entries API
|
|
619
|
-
* The File and Directory Entries API simulates a local file system
|
|
620
|
-
* this web apps can navigate within and access files in. You can
|
|
621
|
-
* develop apps which read, write, and create files and/or directories
|
|
622
|
-
* in a virtual, sandboxed file system.
|
|
623
|
-
*/
|
|
624
|
-
class AdapterForFilesAndDirectoryEntriesApi {
|
|
625
|
-
static canApply(handle) {
|
|
626
|
-
return ((handle?.isDirectory || handle?.isFile) &&
|
|
627
|
-
(typeof handle?.name === "string"));
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
async *listDirectoryChildren(handle) {
|
|
631
|
-
let dirReader = handle.createReader();
|
|
632
|
-
while (true) {
|
|
633
|
-
const list = await new Promise((resolve, reject) =>
|
|
634
|
-
dirReader.readEntries(resolve, reject)
|
|
635
|
-
);
|
|
636
|
-
if (!list.length) break;
|
|
637
|
-
yield* list;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
isDirectoryHandle(handle) {
|
|
642
|
-
return handle?.isDirectory;
|
|
643
|
-
}
|
|
644
|
-
isFileHandle(handle) {
|
|
645
|
-
return handle?.isFile;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
async getSubdirectoryHandle(dirHandle, directoryName, create) {
|
|
649
|
-
return new Promise((resolve, reject) =>
|
|
650
|
-
dirHandle.getDirectory(
|
|
651
|
-
directoryName,
|
|
652
|
-
{ create },
|
|
653
|
-
resolve,
|
|
654
|
-
reject,
|
|
655
|
-
)
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
async getFileHandle(dirHandle, fileName, create = true) {
|
|
660
|
-
return new Promise((resolve, reject) =>
|
|
661
|
-
dirHandle.getFile(
|
|
662
|
-
fileName,
|
|
663
|
-
{ create },
|
|
664
|
-
resolve,
|
|
665
|
-
reject,
|
|
666
|
-
)
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
async removeDirectoryEntry(dirHandle, handle) {
|
|
671
|
-
return new Promise((resolve, reject) => handle.remove(resolve, reject));
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
async getFile(handle) {
|
|
675
|
-
const file = await new Promise((resolve, reject) =>
|
|
676
|
-
handle.file(resolve, reject)
|
|
677
|
-
);
|
|
678
|
-
return file;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
async getFileWriteStream(handle) {
|
|
682
|
-
throw new Error("Not implemented");
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* Stateless adapter for the "File System API" and "File System Access API".
|
|
688
|
-
* See:
|
|
689
|
-
* - https://developer.mozilla.org/en-US/docs/Web/API/File_System_API
|
|
690
|
-
* - https://wicg.github.io/file-system-access/
|
|
691
|
-
*/
|
|
692
|
-
class AdapterForFileSystemApi {
|
|
693
|
-
static canApply(handle) {
|
|
694
|
-
return (handle?.kind === "file" || handle?.kind === "directory");
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
async *listDirectoryChildren(handle) {
|
|
698
|
-
yield* handle.values();
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
isDirectoryHandle(handle) {
|
|
702
|
-
return handle?.kind === "directory";
|
|
703
|
-
}
|
|
704
|
-
isFileHandle(handle) {
|
|
705
|
-
return handle?.kind === "file";
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
async getSubdirectoryHandle(dirHandle, directoryName, create) {
|
|
709
|
-
return await dirHandle.getDirectoryHandle(directoryName, { create });
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
async getFileHandle(dirHandle, fileName, create = true) {
|
|
713
|
-
return await dirHandle.getFileHandle(fileName, { create });
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
async removeDirectoryEntry(dirHandle, handle) {
|
|
717
|
-
return await dirHandle.removeEntry(handle.name, { recursive: true });
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
async getFile(handle) {
|
|
721
|
-
return await handle.getFile();
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
async getFileWriteStream(handle) {
|
|
725
|
-
return await handle.createWritable(); // Write the contents of the file to the stream.
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
class BrowserFilesApi extends FilesApi {
|
|
730
|
-
constructor(options) {
|
|
731
|
-
super(options);
|
|
732
|
-
if (!this.rootHandle) {
|
|
733
|
-
throw new Error("Root directory handle is not defined");
|
|
734
|
-
}
|
|
735
|
-
if (!this.adapter) {
|
|
736
|
-
throw new Error(
|
|
737
|
-
"Can not define a browser adapter for the specified root handle",
|
|
738
|
-
);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
get rootHandle() {
|
|
743
|
-
return this.options.rootHandle || this.options.rootHandler;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
get adapter() {
|
|
747
|
-
if (!this._adapter) {
|
|
748
|
-
const handle = this.rootHandle;
|
|
749
|
-
let adapter = AdapterForFilesAndDirectoryEntriesApi.canApply(handle)
|
|
750
|
-
? new AdapterForFilesAndDirectoryEntriesApi()
|
|
751
|
-
: AdapterForFileSystemApi.canApply(handle)
|
|
752
|
-
? new AdapterForFileSystemApi()
|
|
753
|
-
: null;
|
|
754
|
-
this._adapter = adapter;
|
|
755
|
-
}
|
|
756
|
-
return this._adapter;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
async *list(file, { recursive = false } = {}) {
|
|
760
|
-
const segments = this._getPathSegments(file);
|
|
761
|
-
const handle = await this._getHandle(segments);
|
|
762
|
-
if (!handle) return;
|
|
763
|
-
const that = this;
|
|
764
|
-
if (this.adapter.isDirectoryHandle(handle)) {
|
|
765
|
-
// yield await this._getFileInfo(segments, handle);
|
|
766
|
-
yield* listChildren(segments, handle, recursive);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
async function* listChildren(segments, dirHandle, recursive) {
|
|
770
|
-
for await (const handle of that._listDirectoryContent(dirHandle)) {
|
|
771
|
-
yield await that._getFileInfo([...segments, handle.name], handle);
|
|
772
|
-
if (recursive && that.adapter.isDirectoryHandle(handle)) {
|
|
773
|
-
yield* listChildren(
|
|
774
|
-
[...segments, handle.name],
|
|
775
|
-
handle,
|
|
776
|
-
recursive,
|
|
777
|
-
);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
async stats(file) {
|
|
784
|
-
const segments = this._getPathSegments(file);
|
|
785
|
-
const handle = await this._getHandle(segments);
|
|
786
|
-
return handle ? await this._getFileInfo(segments, handle) : null;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
async remove(file) {
|
|
790
|
-
const doRemove = async (dirHandle, handle) => {
|
|
791
|
-
let result = true;
|
|
792
|
-
if (this.adapter.isDirectoryHandle(handle)) {
|
|
793
|
-
const children = [];
|
|
794
|
-
for await (let child of this.adapter.listDirectoryChildren(handle)) {
|
|
795
|
-
children.push(child);
|
|
796
|
-
}
|
|
797
|
-
for (let child of children) {
|
|
798
|
-
result = result && await doRemove(handle, child);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
if (dirHandle) {
|
|
802
|
-
result &= await this.adapter.removeDirectoryEntry(dirHandle, handle);
|
|
803
|
-
}
|
|
804
|
-
return result;
|
|
805
|
-
};
|
|
806
|
-
|
|
807
|
-
const segments = this._getPathSegments(file);
|
|
808
|
-
const handle = await this._getHandle(segments);
|
|
809
|
-
segments.pop();
|
|
810
|
-
const dirHandle = await this._getHandle(segments);
|
|
811
|
-
return doRemove(dirHandle !== handle ? dirHandle : null, handle);
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
async write(file, content) {
|
|
815
|
-
const segments = this._getPathSegments(file);
|
|
816
|
-
const fileName = segments.pop();
|
|
817
|
-
const dirHandle = await this._getDirectoryHandle(segments, true);
|
|
818
|
-
const fileHandle = await this.adapter.getFileHandle(
|
|
819
|
-
dirHandle,
|
|
820
|
-
fileName,
|
|
821
|
-
true,
|
|
822
|
-
);
|
|
823
|
-
const writable = await this.adapter.getFileWriteStream(fileHandle);
|
|
824
|
-
try {
|
|
825
|
-
if (typeof content === "function") content = content();
|
|
826
|
-
for await (let block of content) {
|
|
827
|
-
await writable.write(block.buffer ? block.buffer : block); // Close the file and write the contents to disk.
|
|
828
|
-
}
|
|
829
|
-
} finally {
|
|
830
|
-
await writable.close();
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
async *read(file /* { start = 0, bufferSize = 1024 * 8 } = {} */) {
|
|
835
|
-
const segments = this._getPathSegments(file);
|
|
836
|
-
|
|
837
|
-
const fileName = segments.pop();
|
|
838
|
-
const dirHandle = await this._getDirectoryHandle(segments, false);
|
|
839
|
-
const fileHandle = await this.adapter.getFileHandle(
|
|
840
|
-
dirHandle,
|
|
841
|
-
fileName,
|
|
842
|
-
false,
|
|
843
|
-
);
|
|
844
|
-
if (!fileHandle) return;
|
|
845
|
-
const fileData = await this.adapter.getFile(fileHandle);
|
|
846
|
-
const stream = fileData.stream();
|
|
847
|
-
const reader = stream.getReader();
|
|
848
|
-
try {
|
|
849
|
-
let chunk;
|
|
850
|
-
while (chunk = await reader.read()) {
|
|
851
|
-
if (chunk.done) break;
|
|
852
|
-
yield await chunk.value;
|
|
853
|
-
}
|
|
854
|
-
} finally {
|
|
855
|
-
await reader.cancel && reader.cancel();
|
|
856
|
-
// stream.cancel && stream.cancel();
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// TODO:
|
|
861
|
-
// async copy(fromPath, toPath, options = {}) { ... }
|
|
862
|
-
|
|
863
|
-
// TODO:
|
|
864
|
-
// async move(fromPath, toPath, options = {}) { ... }
|
|
865
|
-
|
|
866
|
-
// ---------------------------------------------------------
|
|
867
|
-
// Private methods
|
|
868
|
-
|
|
869
|
-
async _getFileInfo(segments, handle) {
|
|
870
|
-
segments = [...segments];
|
|
871
|
-
const name = segments[segments.length - 1];
|
|
872
|
-
segments.unshift("");
|
|
873
|
-
const path = segments.join("/");
|
|
874
|
-
const info = {
|
|
875
|
-
kind: "",
|
|
876
|
-
path,
|
|
877
|
-
name,
|
|
878
|
-
};
|
|
879
|
-
if (this.adapter.isDirectoryHandle(handle)) {
|
|
880
|
-
info.kind = "directory";
|
|
881
|
-
} else {
|
|
882
|
-
info.kind = "file";
|
|
883
|
-
|
|
884
|
-
const file = await this.adapter.getFile(handle);
|
|
885
|
-
info.size = file.size;
|
|
886
|
-
info.type = getMimeType(path);
|
|
887
|
-
info.lastModified = file.lastModified;
|
|
888
|
-
}
|
|
889
|
-
return info;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
_getPathSegments(file) {
|
|
893
|
-
let filePath = typeof file === "object" ? file.path : file + "";
|
|
894
|
-
// filePath = this.normalizePath(filePath);
|
|
895
|
-
const segments = filePath.split("/").filter((s) => !!s);
|
|
896
|
-
return segments;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
async _getHandle(segments) {
|
|
900
|
-
const parentDirSegments = [...segments];
|
|
901
|
-
const name = parentDirSegments.pop();
|
|
902
|
-
const create = false;
|
|
903
|
-
try {
|
|
904
|
-
const dirHandle = await this._getDirectoryHandle(
|
|
905
|
-
parentDirSegments,
|
|
906
|
-
create,
|
|
907
|
-
);
|
|
908
|
-
try {
|
|
909
|
-
if (!name) return dirHandle;
|
|
910
|
-
return await this.adapter.getSubdirectoryHandle(
|
|
911
|
-
dirHandle,
|
|
912
|
-
name,
|
|
913
|
-
create,
|
|
914
|
-
);
|
|
915
|
-
} catch (error) {
|
|
916
|
-
return await this.adapter.getFileHandle(dirHandle, name, create);
|
|
917
|
-
}
|
|
918
|
-
} catch (error) {
|
|
919
|
-
return undefined;
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
async _getDirectoryHandle(segments, create = true) {
|
|
924
|
-
segments = [...segments];
|
|
925
|
-
let dirHandle = this.rootHandle;
|
|
926
|
-
for (let directoryName of segments) {
|
|
927
|
-
dirHandle = await this.adapter.getSubdirectoryHandle(
|
|
928
|
-
dirHandle,
|
|
929
|
-
directoryName,
|
|
930
|
-
create,
|
|
931
|
-
);
|
|
932
|
-
if (!dirHandle) break;
|
|
933
|
-
}
|
|
934
|
-
return dirHandle;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
async *_listDirectoryContent(handle) {
|
|
938
|
-
let entries = [];
|
|
939
|
-
for await (const entry of this.adapter.listDirectoryChildren(handle)) {
|
|
940
|
-
entries.push(entry);
|
|
941
|
-
}
|
|
942
|
-
entries = entries.sort((a, b) => b.name > a.name ? 1 : -1);
|
|
943
|
-
yield* entries;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
function addFilesApiLogger({
|
|
948
|
-
filesApi,
|
|
949
|
-
log = console.log,
|
|
950
|
-
wrapper = {},
|
|
951
|
-
}) {
|
|
952
|
-
return ["list", "stats", "remove", "write", "read", "copy", "move"].reduce(
|
|
953
|
-
(wrapper, methodName) => {
|
|
954
|
-
const method = filesApi[methodName].bind(filesApi);
|
|
955
|
-
wrapper[methodName] = (...args) => {
|
|
956
|
-
let message = { method: methodName, stage : "enter", args };
|
|
957
|
-
try {
|
|
958
|
-
log(message);
|
|
959
|
-
return message.result = method.call(filesApi, ...args);
|
|
960
|
-
} catch (e) {
|
|
961
|
-
message.error = e;
|
|
962
|
-
} finally {
|
|
963
|
-
message.stage = "exit";
|
|
964
|
-
log(message);
|
|
965
|
-
}
|
|
966
|
-
};
|
|
967
|
-
return wrapper;
|
|
968
|
-
},
|
|
969
|
-
wrapper,
|
|
970
|
-
);
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
async function openBrowserFilesApi({
|
|
974
|
-
handlerKey = "root-dir",
|
|
975
|
-
readwrite = true,
|
|
976
|
-
// Methods below could be replaced by persistent implementation
|
|
977
|
-
// using the "idb-keyval" package (methods "get", "set", "del").
|
|
978
|
-
index = {},
|
|
979
|
-
get = async (key) => index[key],
|
|
980
|
-
set = async (key, handler) => index[key] = handler,
|
|
981
|
-
del = async (key) => delete index[key],
|
|
982
|
-
} = {}) {
|
|
983
|
-
let rootHandler = await get(handlerKey);
|
|
984
|
-
if (!rootHandler) {
|
|
985
|
-
rootHandler = await window.showDirectoryPicker();
|
|
986
|
-
await set(handlerKey, rootHandler);
|
|
987
|
-
}
|
|
988
|
-
if (!(await verifyPermission(rootHandler, readwrite))) {
|
|
989
|
-
throw new Error("Access was not granted");
|
|
990
|
-
}
|
|
991
|
-
if (!(await isHandlerAccessible(rootHandler))) {
|
|
992
|
-
// await set(handlerKey, null);
|
|
993
|
-
await del(handlerKey);
|
|
994
|
-
throw new Error("Can not access to the folder. Please try again.");
|
|
995
|
-
}
|
|
996
|
-
return new BrowserFilesApi({ rootHandler });
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
async function isHandlerAccessible(fileHandle) {
|
|
1000
|
-
// We need to perform a real read operation with the handler
|
|
1001
|
-
// to check if the file/directory still exists.
|
|
1002
|
-
let exists = false;
|
|
1003
|
-
try {
|
|
1004
|
-
if (fileHandle.kind === "file") {
|
|
1005
|
-
await fileHandle.getFile();
|
|
1006
|
-
} else {
|
|
1007
|
-
for await (let item of fileHandle.values()) {
|
|
1008
|
-
break;
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
exists = true;
|
|
1012
|
-
} catch (error) {
|
|
1013
|
-
console.log(error, fileHandle);
|
|
1014
|
-
exists = false;
|
|
1015
|
-
}
|
|
1016
|
-
return exists;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
async function verifyPermission(fileHandle, readWrite) {
|
|
1020
|
-
const options = {};
|
|
1021
|
-
if (readWrite) {
|
|
1022
|
-
options.mode = "readwrite";
|
|
1023
|
-
}
|
|
1024
|
-
let granted = false;
|
|
1025
|
-
// Check if permission was already granted. If so, return true.
|
|
1026
|
-
if ((await fileHandle.queryPermission(options)) === "granted") {
|
|
1027
|
-
granted = true;
|
|
1028
|
-
}
|
|
1029
|
-
// Request permission. If the user grants permission, return true.
|
|
1030
|
-
if (
|
|
1031
|
-
!granted && (await fileHandle.requestPermission(options)) === "granted"
|
|
1032
|
-
) {
|
|
1033
|
-
granted = true;
|
|
1034
|
-
}
|
|
1035
|
-
// The user didn't grant permission, so return false.
|
|
1036
|
-
return granted;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
exports.BrowserFilesApi = BrowserFilesApi;
|
|
1040
|
-
exports.FilesApi = FilesApi;
|
|
1041
|
-
exports.MemFilesApi = MemFilesApi;
|
|
1042
|
-
exports.NodeFilesApi = NodeFilesApi;
|
|
1043
|
-
exports.addFilesApiLogger = addFilesApiLogger;
|
|
1044
|
-
exports.extensionToMimeType = extensionToMimeType;
|
|
1045
|
-
exports.getMimeType = getMimeType;
|
|
1046
|
-
exports.isHandlerAccessible = isHandlerAccessible;
|
|
1047
|
-
exports.mimeTypeToExtensions = mimeTypeToExtensions;
|
|
1048
|
-
exports.mimeTypes = mimeTypes;
|
|
1049
|
-
exports.mimeTypesDescriptions = mimeTypesDescriptions;
|
|
1050
|
-
exports.normalizePath = normalizePath;
|
|
1051
|
-
exports.openBrowserFilesApi = openBrowserFilesApi;
|
|
1052
|
-
exports.verifyPermission = verifyPermission;
|
|
1053
|
-
|
|
1054
|
-
}));
|