js-dos 7.4.9 → 8.1.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.
Files changed (155) hide show
  1. package/.github/workflows/build.yml +51 -0
  2. package/.gitmodules +4 -0
  3. package/README.deployment.md +11 -0
  4. package/README.md +29 -89
  5. package/index.html +106 -0
  6. package/package.json +46 -71
  7. package/postcss.config.cjs +8 -0
  8. package/src/app.css +14 -0
  9. package/src/base.css +10 -0
  10. package/src/components/checkbox.tsx +41 -0
  11. package/src/components/close-button.tsx +17 -0
  12. package/src/components/components.css +28 -0
  13. package/src/components/dos-option-checkbox.tsx +94 -0
  14. package/src/components/dos-option-select.tsx +108 -0
  15. package/src/components/dos-option-slider.tsx +35 -0
  16. package/src/components/error.tsx +16 -0
  17. package/src/components/loading.tsx +26 -0
  18. package/src/components/lock.tsx +10 -0
  19. package/src/components/select.tsx +33 -0
  20. package/src/components/slider.tsx +69 -0
  21. package/src/download-file.ts +18 -0
  22. package/src/frame/account-frame.tsx +156 -0
  23. package/src/frame/editor/defaults.ts +419 -0
  24. package/src/frame/editor/editor-conf-frame.tsx +226 -0
  25. package/src/frame/editor/editor-frame.css +55 -0
  26. package/src/frame/editor/editor-fs-frame.tsx +314 -0
  27. package/src/frame/fat-drives-frame.tsx +135 -0
  28. package/src/frame/frame.css +119 -0
  29. package/src/frame/frame.tsx +34 -0
  30. package/src/frame/image-rendering-frame.tsx +8 -0
  31. package/src/frame/network-frame.tsx +157 -0
  32. package/src/frame/quick-save-frame.tsx +96 -0
  33. package/src/frame/settings-frame.tsx +14 -0
  34. package/src/frame/stats-frame.tsx +87 -0
  35. package/src/host/bundle-storage.ts +91 -0
  36. package/src/host/fullscreen.ts +35 -0
  37. package/src/host/lcache.ts +203 -0
  38. package/src/host/lstorage.ts +156 -0
  39. package/src/i18n.ts +179 -0
  40. package/src/index.css +7 -0
  41. package/src/load.ts +132 -0
  42. package/src/login/login.css +9 -0
  43. package/src/login/login.tsx +77 -0
  44. package/src/main.tsx +197 -0
  45. package/src/public/types.ts +41 -0
  46. package/src/sidebar/account-button.tsx +33 -0
  47. package/src/sidebar/cloud-save-button.tsx +89 -0
  48. package/src/sidebar/diskette-icon.tsx +14 -0
  49. package/src/sidebar/fullscreen-button.tsx +44 -0
  50. package/src/sidebar/network-button.tsx +39 -0
  51. package/src/sidebar/sidebar-button.tsx +244 -0
  52. package/src/sidebar/sidebar.css +47 -0
  53. package/src/sidebar/sidebar.tsx +30 -0
  54. package/src/sockdrive/LICENSE +674 -0
  55. package/src/sockdrive/README.md +45 -0
  56. package/src/sockdrive/include/sockdrive.h +23 -0
  57. package/src/sockdrive/js/.eslintrc.json +43 -0
  58. package/src/sockdrive/js/package.json +29 -0
  59. package/src/sockdrive/js/src/fatfs/chains.js +318 -0
  60. package/src/sockdrive/js/src/fatfs/dir.js +385 -0
  61. package/src/sockdrive/js/src/fatfs/helpers.js +210 -0
  62. package/src/sockdrive/js/src/fatfs/index.js +606 -0
  63. package/src/sockdrive/js/src/fatfs/structs.js +255 -0
  64. package/src/sockdrive/js/src/fatfs/vol.js +166 -0
  65. package/src/sockdrive/js/src/sockdrive/cache.ts +133 -0
  66. package/src/sockdrive/js/src/sockdrive/drive.ts +457 -0
  67. package/src/sockdrive/js/src/sockdrive/lru.ts +221 -0
  68. package/src/sockdrive/js/src/sockdrive/types.ts +14 -0
  69. package/src/sockdrive/js/src/sockdrive-fat.d.ts +64 -0
  70. package/src/sockdrive/js/src/sockdrive-fat.ts +226 -0
  71. package/src/sockdrive/js/src/sockdrive-native.ts +100 -0
  72. package/src/sockdrive/js/src/test/test.ts +288 -0
  73. package/src/sockdrive/js/tsconfig.json +11 -0
  74. package/src/sockdrive/js/webpack.config.js +68 -0
  75. package/src/sockdrive/js/yarn.lock +1808 -0
  76. package/src/store/auth.ts +115 -0
  77. package/src/store/dos.ts +305 -0
  78. package/src/store/editor.ts +61 -0
  79. package/src/store/init.ts +48 -0
  80. package/src/store/storage.ts +32 -0
  81. package/src/store/ui.ts +186 -0
  82. package/src/store.tsx +108 -0
  83. package/src/ui.tsx +104 -0
  84. package/src/v8/changes.ts +56 -0
  85. package/src/v8/config.ts +18 -0
  86. package/src/v8/subscriptions.ts +62 -0
  87. package/src/vite-env.d.ts +2 -0
  88. package/src/window/dos/controls/keyboard.ts +32 -0
  89. package/src/window/dos/controls/keys.ts +331 -0
  90. package/src/window/dos/controls/mouse/mount.ts +99 -0
  91. package/src/window/dos/controls/mouse/mouse-locked.ts +51 -0
  92. package/src/window/dos/controls/mouse/mouse-not-locked.ts +84 -0
  93. package/src/window/dos/controls/mouse/mouse-swipe.ts +57 -0
  94. package/src/window/dos/controls/mouse/pointer.ts +85 -0
  95. package/src/window/dos/controls/mouse.ts +21 -0
  96. package/src/window/dos/dos-dhry2.tsx +191 -0
  97. package/src/window/dos/dos-runtime.tsx +214 -0
  98. package/src/window/dos/dos-window.tsx +84 -0
  99. package/src/window/dos/render/canvas.ts +65 -0
  100. package/src/window/dos/render/resize.ts +31 -0
  101. package/src/window/dos/render/webgl.ts +173 -0
  102. package/src/window/dos/sound/audio-node.ts +148 -0
  103. package/src/window/error-window.tsx +9 -0
  104. package/src/window/loading-window.tsx +54 -0
  105. package/src/window/prerun-window.tsx +123 -0
  106. package/src/window/select-window.tsx +118 -0
  107. package/src/window/window.css +78 -0
  108. package/src/window/window.tsx +49 -0
  109. package/src/ws/ws-sockdrive.ts +101 -0
  110. package/src/ws/ws-transport-layer.ts +455 -0
  111. package/tailwind.config.cjs +48 -0
  112. package/tsconfig.json +32 -0
  113. package/tsconfig.node.json +9 -0
  114. package/vite.config.ts +22 -0
  115. package/LICENSE +0 -339
  116. package/dist/emulators-ui-loader.png +0 -0
  117. package/dist/js-dos.css +0 -1690
  118. package/dist/js-dos.js +0 -17
  119. package/dist/js-dos.js.map +0 -1
  120. package/dist/types/src/backend/v7/latency.d.ts +0 -8
  121. package/dist/types/src/backend/v7/personal.d.ts +0 -2
  122. package/dist/types/src/backend/v7/v7-config.d.ts +0 -16
  123. package/dist/types/src/components/action-bar.d.ts +0 -2
  124. package/dist/types/src/components/action-hide.d.ts +0 -4
  125. package/dist/types/src/components/action-save-or-exit.d.ts +0 -4
  126. package/dist/types/src/components/client.d.ts +0 -6
  127. package/dist/types/src/components/controls.d.ts +0 -8
  128. package/dist/types/src/components/cpu-control.d.ts +0 -2
  129. package/dist/types/src/components/horizontal-slider.d.ts +0 -13
  130. package/dist/types/src/components/region.d.ts +0 -6
  131. package/dist/types/src/components/scale-control.d.ts +0 -3
  132. package/dist/types/src/components/sensitivity-control.d.ts +0 -3
  133. package/dist/types/src/components/sidebar/latency-info.d.ts +0 -7
  134. package/dist/types/src/components/sidebar/main.d.ts +0 -2
  135. package/dist/types/src/components/sidebar/networking.d.ts +0 -2
  136. package/dist/types/src/components/sidebar/token/token-add-time.d.ts +0 -2
  137. package/dist/types/src/components/sidebar/token/token-select.d.ts +0 -2
  138. package/dist/types/src/components/sidebar/token/token.d.ts +0 -17
  139. package/dist/types/src/components/sidebar.d.ts +0 -2
  140. package/dist/types/src/components/sync-control.d.ts +0 -2
  141. package/dist/types/src/components/tip.d.ts +0 -2
  142. package/dist/types/src/components/vertical-slider.d.ts +0 -12
  143. package/dist/types/src/components/volume-control.d.ts +0 -3
  144. package/dist/types/src/dom.d.ts +0 -8
  145. package/dist/types/src/hardware-transport-layer.d.ts +0 -18
  146. package/dist/types/src/icons.d.ts +0 -95
  147. package/dist/types/src/player-app.d.ts +0 -50
  148. package/dist/types/src/player.d.ts +0 -29
  149. package/dist/types/src/request.d.ts +0 -1
  150. package/dist/types/src/xhr.d.ts +0 -6
  151. package/dist/types/v7-services/src/personal.d.ts +0 -3
  152. package/dist/wdosbox.js +0 -55
  153. package/dist/wdosbox.js.symbols +0 -3174
  154. package/dist/wdosbox.wasm +0 -0
  155. package/examples/digger/index.html +0 -71
@@ -0,0 +1,606 @@
1
+ const events = require("events");
2
+ const streams = require("stream");
3
+ const fifolock = require("fifolock");
4
+ const S = require("./structs.js");
5
+ const _ = require("./helpers.js");
6
+
7
+ // _.log.level = _.log.DBG;
8
+
9
+ exports.createFileSystem = function(volume, opts, cb) {
10
+ if (typeof opts === "function") {
11
+ cb = opts;
12
+ opts = null;
13
+ }
14
+ opts = _.extend({
15
+ // c.f. https://www.kernel.org/doc/Documentation/filesystems/vfat.txt
16
+ ro: false,
17
+ noatime: true,
18
+ modmode: 0o111, // or `0o7000`
19
+ umask: ("umask" in process) ? process.umask() : 0o022,
20
+ uid: ("getuid" in process) ? process.getuid() : 0,
21
+ gid: ("getgid" in process) ? process.getgid() : 0,
22
+ }, opts);
23
+ if (!volume.writeSectors) opts.ro = true;
24
+ if (opts.ro) opts.noatime = true; // natch
25
+
26
+ const fs = new events.EventEmitter();
27
+ let vol = null;
28
+ const dir = require("./dir.js");
29
+ const c = require("./chains.js");
30
+ const q = fifolock();
31
+
32
+ const GROUP = (_.log.level < _.log.INFO) ? q.TRANSACTION_WRAPPER.bind({
33
+ postAcquire: function(proceed) {
34
+ _.log(_.log.DBG, "=== Starting GROUP ===");
35
+ proceed();
36
+ },
37
+ preRelease: function(finish) {
38
+ _.log(_.log.DBG, "=== Finishing GROUP ===");
39
+ finish();
40
+ },
41
+ }) : q.TRANSACTION_WRAPPER;
42
+
43
+ q.acquire(function(unlock) { // because of this, callers can start before 'ready'
44
+ const d = _.allocBuffer(volume.sectorSize);
45
+ volume.readSectors(0, d, function(e) {
46
+ if (e) fs.emit("error", e);
47
+ else {
48
+ try {
49
+ init(d);
50
+ } catch (e) {
51
+ fs.emit("error", e);
52
+ unlock();
53
+ return;
54
+ }
55
+ fs.emit("ready");
56
+ }
57
+ unlock();
58
+ });
59
+ });
60
+
61
+ if (cb) fs.on("error", cb).on("ready", () => cb("ready", null));
62
+
63
+ function init(bootSector) {
64
+ vol = require("./vol.js").init(volume, opts, bootSector);
65
+ fs._dirIterator = dir.iterator.bind(dir);
66
+
67
+ const entryInfoByPath = {};
68
+ const baseEntry = {
69
+ _refs: 0,
70
+ _record: function() {
71
+ entryInfoByPath[this.path] = this;
72
+ if (this.parent) this.parent.retain();
73
+ return this;
74
+ },
75
+ retain: function() {
76
+ if (!this._refs) this._record();
77
+ this._refs += 1;
78
+ return this;
79
+ },
80
+ release: function() {
81
+ this._refs -= 1;
82
+ if (!this._refs) this._rescind();
83
+ },
84
+ _rescind: function() {
85
+ if (this.parent) this.parent.release();
86
+ delete entryInfoByPath[this.path];
87
+ },
88
+ };
89
+ fs._createSharedEntry = function(path, entry, chain, parent) {
90
+ return _.extend(Object.create(baseEntry), {
91
+ _refs: 0, // WORKAROUND: https://github.com/tessel/beta/issues/455
92
+ path: path,
93
+ entry: entry,
94
+ chain: chain,
95
+ parent: parent,
96
+ }).retain();
97
+ };
98
+ fs._createSharedEntry("/", { Attr: { directory: true } }, vol.rootDirectoryChain);
99
+ fs._sharedEntryForSteps = function(steps, opts, cb) { // NOTE: may `cb` before returning!
100
+ const path = steps.join("/") || "/";
101
+ const name = steps.pop(); // n.b.
102
+ const info = entryInfoByPath[path];
103
+ if (info) cb(null, info.retain());
104
+ else {
105
+ fs._sharedEntryForSteps(steps, {}, function(e, parentInfo) { // n.b. `steps` don't include `name`
106
+ if (e) cb(e);
107
+ else if (!parentInfo.entry.Attr.directory) cb(S.err.NOTDIR());
108
+ else {
109
+ dir.findInDirectory(vol, parentInfo.chain, name, opts, function(e, entry) {
110
+ if (e && !opts.prepareForCreate) cb(e);
111
+ else if (e) cb(e, { missingChild: _.extend(entry, { name: name }), parent: parentInfo });
112
+ else cb(null, fs._createSharedEntry(path, entry, vol.chainForCluster(entry._firstCluster), parentInfo));
113
+ });
114
+ }
115
+ });
116
+ }
117
+ };
118
+
119
+ fs._updateEntry = dir.updateEntry.bind(dir, vol);
120
+ fs._makeStat = dir.makeStat.bind(dir, vol);
121
+ fs._addFile = dir.addFile.bind(dir, vol);
122
+ fs._initDir = dir.init.bind(dir, vol);
123
+ }
124
+
125
+
126
+ /** ** ---- CORE API ---- ****/
127
+
128
+ // NOTE: we really don't share namespace, but avoid first three anyway…
129
+ const fileDescriptors = [null, null, null];
130
+
131
+ fs.open = function(path, flags, mode, cb, _n_) {
132
+ if (typeof mode === "function") {
133
+ _n_ = cb;
134
+ cb = mode;
135
+ mode = 0o666;
136
+ }
137
+ cb = GROUP(cb, function() {
138
+ const _fd = { flags: null, entry: null, chain: null, pos: 0 };
139
+ const f = _.parseFlags(flags);
140
+ if (vol.opts.ro && (f.write || f.create || f.truncate)) return _.delayedCall(cb, S.err.ROFS());
141
+ else _fd.flags = f;
142
+
143
+ fs._sharedEntryForSteps(_.absoluteSteps(path), { prepareForCreate: f.create }, function(e, info) {
144
+ if (e && !(e.code === "NOENT" && f.create && info)) cb(e);
145
+ else if (e) {
146
+ fs._addFile(info.parent.chain, info.missingChild, { dir: f._openDir }, function(e, newEntry, newChain) {
147
+ if (e) cb(e);
148
+ else finish(fs._createSharedEntry(_.absolutePath(path), newEntry, newChain, info.parent));
149
+ });
150
+ } else if (info && f.exclusive) cb(S.err.EXIST());
151
+ else if (info.entry.Attr.directory && !f._openDir) cb(S.err.ISDIR());
152
+ else if (f.write && info.entry.Attr.readonly) cb(S.err.ACCES());
153
+ else finish(info);
154
+ function finish(fileInfo) {
155
+ const fd = fileDescriptors.push(_fd)-1;
156
+ _fd.info = fileInfo;
157
+ _fd.entry = fileInfo.entry;
158
+ _fd.chain = fileInfo.chain;
159
+ if (f.append) _fd.pos = _fd.entry._size;
160
+ if (f._openDir) _fd.chain.cacheAdvice = "WILLNEED";
161
+ if (f.truncate && _fd.entry._size) {
162
+ fs.ftruncate(fd, 0, function(e) {
163
+ cb(e, fd);
164
+ }, "_nested_");
165
+ } else _.delayedCall(cb, null, fd); // (delay in case fs._sharedEntryForSteps all cached!)
166
+ }
167
+ });
168
+ }, (_n_ === "_nested_"));
169
+ };
170
+
171
+ fs.fstat = function(fd, cb, _n_) {
172
+ cb = GROUP(cb, function() {
173
+ const _fd = fileDescriptors[fd];
174
+ if (!_fd || !_fd.flags.read) _.delayedCall(cb, S.err.BADF());
175
+ else _.delayedCall(cb, null, fs._makeStat(_fd.entry));
176
+ }, (_n_ === "_nested_"));
177
+ };
178
+
179
+ fs.futimes = function(fd, atime, mtime, cb, _n_) {
180
+ cb = GROUP(cb, function() {
181
+ const _fd = fileDescriptors[fd];
182
+ if (!_fd || !_fd.flags.write) _.delayedCall(cb, S.err.BADF());
183
+ // NOTE: ctime would get touched on POSIX; but we map that to create time!
184
+ else fs._updateEntry(_fd.entry, { atime: atime||true, mtime: mtime||true }, cb);
185
+ }, (_n_ === "_nested_"));
186
+ };
187
+
188
+ fs.fchmod = function(fd, mode, cb, _n_) {
189
+ cb = GROUP(cb, function() {
190
+ const _fd = fileDescriptors[fd];
191
+ if (!_fd || !_fd.flags.write) _.delayedCall(cb, S.err.BADF());
192
+ else {
193
+ mode &= S._I._chmoddable;
194
+ if (_fd.entry.Attr.directory) mode |= S._I.FDIR;
195
+ else if (!_fd.entry.Attr.volume_id) mode |= S._I.FREG;
196
+ fs._updateEntry(_fd.entry, { mode: mode }, cb);
197
+ }
198
+ }, (_n_ === "_nested_"));
199
+ };
200
+
201
+ fs.read = function(fd, buf, off, len, pos, cb, _n_) {
202
+ cb = GROUP(cb, function() {
203
+ const _fd = fileDescriptors[fd];
204
+ if (!_fd || !_fd.flags.read) return _.delayedCall(cb, S.err.BADF());
205
+
206
+ const _pos = (pos === null) ? _fd.pos : pos;
207
+ const _len = Math.min(len, _fd.entry._size - _pos);
208
+ const _buf = buf.slice(off, off+_len);
209
+ _fd.chain.readFromPosition(_pos, _buf, function(e, bytes, slice) {
210
+ if (_.workaroundTessel380) _buf.copy(buf, off); // WORKAROUND: https://github.com/tessel/beta/issues/380
211
+ _fd.pos = _pos + bytes;
212
+ if (e || vol.opts.noatime) finish(e);
213
+ else fs._updateEntry(_fd.entry, { atime: true }, finish);
214
+ function finish(e) {
215
+ cb(e, bytes, buf);
216
+ }
217
+ });
218
+ }, (_n_ === "_nested_"));
219
+ };
220
+
221
+ fs._readdir = function(fd, cb, _n_) {
222
+ cb = GROUP(cb, function() {
223
+ const _fd = fileDescriptors[fd];
224
+ if (!_fd || !_fd.flags.read) return _.delayedCall(cb, S.err.BADF());
225
+
226
+ const entryNames = [];
227
+ const getNextEntry = fs._dirIterator(_fd.chain);
228
+ function processNext() {
229
+ getNextEntry(function(e, d) {
230
+ if (e) cb(e);
231
+ else if (!d && !entryNames.length) cb(null, entryNames); // WORKAROUND: https://github.com/tessel/beta/issues/435
232
+ else if (!d) cb(null, entryNames.sort()); // NOTE: sort not required, but… [simplifies tests for starters!]
233
+ else {
234
+ if (d._name !== "." && d._name !== "..") entryNames.push(d._name);
235
+ processNext();
236
+ }
237
+ });
238
+ }
239
+ processNext();
240
+ }, (_n_ === "_nested_"));
241
+ };
242
+
243
+ fs._mkdir = function(fd, cb, _n_) {
244
+ cb = GROUP(cb, function() {
245
+ const _fd = fileDescriptors[fd];
246
+ if (!_fd || !_fd.flags.write) _.delayedCall(cb, S.err.BADF());
247
+ else fs._initDir(_fd.info, cb);
248
+ }, (_n_ === "_nested_"));
249
+ };
250
+
251
+ fs.write = function(fd, buf, off, len, pos, cb, _n_) {
252
+ cb = GROUP(cb, function() {
253
+ const _fd = fileDescriptors[fd];
254
+ if (!_fd || !_fd.flags.write) return _.delayedCall(cb, S.err.BADF());
255
+
256
+ let _pos = (pos === null || _fd.flags.append) ? _fd.pos : pos;
257
+ let _buf = buf.slice(off, off+len);
258
+ if (_pos > _fd.entry._size) {
259
+ // TODO: handle huge jumps by zeroing clusters individually?
260
+ const padLen = _pos - _fd.entry._size;
261
+ const padBuf = _.allocBuffer(padLen + _buf.length);
262
+ padBuf.fill(0x00, 0, padLen);
263
+ _buf.copy(padBuf, padLen);
264
+ _pos = _fd.entry._size;
265
+ _buf = padBuf;
266
+ }
267
+ _fd.chain.writeToPosition(_pos, _buf, function(e) {
268
+ _fd.pos = _pos + len;
269
+ const newSize = Math.max(_fd.entry._size, _fd.pos);
270
+ const newInfo = { size: newSize, _touch: true };
271
+ fs._updateEntry(_fd.entry, newInfo, function(ee) {
272
+ cb(e||ee, len, buf);
273
+ });
274
+ });
275
+ }, (_n_ === "_nested_"));
276
+ };
277
+
278
+ fs.ftruncate = function(fd, len, cb, _n_) {
279
+ cb = GROUP(cb, function() {
280
+ const _fd = fileDescriptors[fd];
281
+ if (!_fd || !_fd.flags.write) return _.delayedCall(cb, S.err.BADF());
282
+
283
+ const newStats = { size: len, _touch: true };
284
+ // NOTE: we order operations for best state in case of only partial success
285
+ if (len === _fd.entry._size) _.delayedCall(cb);
286
+ else if (len < _fd.entry._size) {
287
+ fs._updateEntry(_fd.entry, newStats, function(e) {
288
+ if (e) cb(e);
289
+ else _fd.chain.truncate(Math.ceil(len / _fd.chain.sectorSize), cb);
290
+ });
291
+ } // TODO: handle huge file expansions without as much memory pressure
292
+ else {
293
+ _fd.chain.writeToPosition(_fd.entry._size, _.allocBuffer(len-_fd.entry._size, 0x00), function(e) {
294
+ if (e) cb(e);
295
+ else fs._updateEntry(_fd.entry, newStats, cb);
296
+ });
297
+ }
298
+ }, (_n_ === "_nested_"));
299
+ };
300
+
301
+ // 'NORMAL', 'SEQUENTIAL', 'RANDOM', 'WILLNEED', 'DONTNEED', 'NOREUSE'
302
+ fs._fadviseSync = function(fd, off, len, advice) {
303
+ if (off !== 0 || len !== 0) throw Error("Cache advise can currently be given only for whole file!");
304
+ const _fd = fileDescriptors[fd];
305
+ if (!_fd) throw S.err.BADF();
306
+ else _fd.chain.cacheAdvice = advice;
307
+ };
308
+
309
+ fs.fsync = function(fd, cb) {
310
+ // NOTE: we'll need to flush write cache here once we have one…
311
+ const _fd = fileDescriptors[fd];
312
+ if (!_fd) _.delayedCall(cb, S.err.BADF());
313
+ else _.delayedCall(cb);
314
+ };
315
+
316
+ fs.close = function(fd, cb) {
317
+ const _fd = fileDescriptors[fd];
318
+ if (!_fd) _.delayedCall(cb, S.err.BADF());
319
+ else setTimeout(_fd.info.release.bind(_fd.info), 500), _.delayedCall(cb, fileDescriptors[fd] = null);
320
+ };
321
+
322
+
323
+ /* STREAM WRAPPERS */
324
+
325
+ let workaroundTessel436;
326
+ try {
327
+ new require("stream").Readable({ encoding: "utf8" });
328
+ new streams.Readable({ encoding: "utf8" });
329
+ } catch (e) {
330
+ workaroundTessel436 = true;
331
+ }
332
+
333
+ function _createStream(StreamType, path, opts) {
334
+ // [NOT REALLY A] WORKAROUND: https://github.com/tessel/beta/issues/436
335
+ if (workaroundTessel436 && "encoding" in opts) {
336
+ console.warn("Tessel does not currently support encoding option for Readable streams, discarding!");
337
+ delete opts.encoding;
338
+ }
339
+
340
+ let fd = (opts.fd !== null) ? opts.fd : "_opening_";
341
+ let pos = opts.start;
342
+ const stream = new StreamType(opts);
343
+
344
+ if (fd === "_opening_") {
345
+ fs.open(path, opts.flags, opts.mode, function(e, _fd) {
346
+ if (e) {
347
+ fd = "_open_error_";
348
+ stream.emit("error", e);
349
+ } else {
350
+ fd = _fd;
351
+ fs._fadviseSync(fd, 0, 0, "SEQUENTIAL");
352
+ stream.emit("open", fd);
353
+ }
354
+ });
355
+ }
356
+
357
+ function autoClose(tombstone) { // NOTE: assumes caller will clear `fd`
358
+ if (opts.autoClose) {
359
+ fs.close(fd, function(e) {
360
+ if (e) stream.emit("error", e);
361
+ else stream.emit("close");
362
+ });
363
+ }
364
+ fd = tombstone;
365
+ }
366
+
367
+ if (StreamType === streams.Readable) {
368
+ stream._read = function(n) {
369
+ let buf;
370
+ // TODO: optimize to fetch at least a full sector regardless of `n`…
371
+ n = Math.min(n, opts.end-pos);
372
+ if (fd === "_opening_") {
373
+ stream.once("open", function() {
374
+ stream._read(n);
375
+ });
376
+ } else if (pos > opts.end) stream.push(null);
377
+ else if (n > 0) {
378
+ buf = _.allocBuffer(n), fs.read(fd, buf, 0, n, pos, function(e, n, d) {
379
+ if (e) {
380
+ autoClose("_read_error_");
381
+ stream.emit("error", e);
382
+ } else stream.push((n) ? d.slice(0, n) : null);
383
+ }), pos += n;
384
+ } else stream.push(null);
385
+ };
386
+
387
+ stream.once("end", function() {
388
+ autoClose("_ended_");
389
+ });
390
+ } else if (StreamType === streams.Writable) {
391
+ stream.bytesWritten = 0;
392
+
393
+ stream._write = function(data, _enc, cb) {
394
+ if (fd === "_opening_") {
395
+ stream.once("open", function() {
396
+ stream._write(data, null, cb);
397
+ });
398
+ } else {
399
+ fs.write(fd, data, 0, data.length, pos, function(e, n) {
400
+ if (e) {
401
+ autoClose("_write_error_");
402
+ cb(e);
403
+ } else {
404
+ stream.bytesWritten += n;
405
+ cb();
406
+ }
407
+ }), pos += data.length;
408
+ }
409
+ };
410
+
411
+ stream.once("finish", function() {
412
+ autoClose("_finished_");
413
+ });
414
+ }
415
+
416
+ return stream;
417
+ }
418
+
419
+ fs.createReadStream = function(path, opts) {
420
+ return _createStream(streams.Readable, path, _.extend({
421
+ start: 0,
422
+ end: Infinity,
423
+ flags: "r",
424
+ mode: 0o666,
425
+ encoding: null,
426
+ fd: null, // ??? see https://github.com/joyent/node/issues/7708
427
+ autoClose: true,
428
+ }, opts));
429
+ };
430
+
431
+ fs.createWriteStream = function(path, opts) {
432
+ return _createStream(streams.Writable, path, _.extend({
433
+ start: 0,
434
+ flags: "w",
435
+ mode: 0o666,
436
+ // encoding: null, // see https://github.com/joyent/node/issues/7710
437
+ fd: null, // ??? see https://github.com/joyent/node/issues/7708
438
+ autoClose: true,
439
+ }, opts, { decodeStrings: true, objectMode: false }));
440
+ };
441
+
442
+
443
+ /* PATH WRAPPERS (albeit the only public interface for some folder operations) */
444
+
445
+ function _fdOperation(path, opts, fn, cb) {
446
+ cb = GROUP(cb, function() {
447
+ opts.advice || (opts.advice = "NORMAL");
448
+ fs.open(path, opts.flag, function(e, fd) {
449
+ if (e) cb(e);
450
+ else {
451
+ fs._fadviseSync(fd, 0, 0, opts.advice), fn(fd, function() {
452
+ const ctx = this; const args = arguments;
453
+ fs.close(fd, function(closeErr) {
454
+ cb.apply(ctx, args);
455
+ }, "_nested_");
456
+ });
457
+ }
458
+ }, "_nested_");
459
+ });
460
+ }
461
+
462
+ fs.stat = fs.lstat = function(path, cb) {
463
+ _fdOperation(path, { flag: "r" }, function(fd, cb) {
464
+ fs.fstat(fd, cb, "_nested_");
465
+ }, cb);
466
+ };
467
+
468
+ fs.exists = function(path, cb) {
469
+ fs.stat(path, function(err) {
470
+ cb(err ? false : true);
471
+ });
472
+ };
473
+
474
+ fs.readFile = function(path, opts, cb) {
475
+ if (typeof opts === "function") {
476
+ cb = opts;
477
+ opts = {};
478
+ }
479
+ opts.flag || (opts.flag = "r");
480
+ opts.advice || (opts.advice = "NOREUSE");
481
+ _fdOperation(path, opts, function(fd, cb) {
482
+ fs.fstat(fd, function(e, stat) {
483
+ if (e) return cb(e);
484
+ else {
485
+ const buffer = _.allocBuffer(stat.size);
486
+ fs.read(fd, buffer, 0, buffer.length, null, function(e) {
487
+ if (e) cb(e);
488
+ else cb(null, (opts.encoding) ? buffer.toString(opts.encoding) : buffer);
489
+ }, "_nested_");
490
+ }
491
+ }, "_nested_");
492
+ }, cb);
493
+ };
494
+
495
+ fs.writeFile = function(path, data, opts, cb) {
496
+ if (typeof opts === "function") {
497
+ cb = opts;
498
+ opts = {};
499
+ }
500
+ opts.flag || (opts.flag = "w");
501
+ opts.advice || (opts.advice = "NOREUSE");
502
+ _fdOperation(path, opts, function(fd, cb) {
503
+ if (typeof data === "string") data = _.bufferFrom(data, opts.encoding || "utf8");
504
+ fs.write(fd, data, 0, data.length, null, function(e) {
505
+ cb(e);
506
+ }, "_nested_");
507
+ }, cb);
508
+ };
509
+
510
+ fs.appendFile = function(path, data, opts, cb) {
511
+ if (typeof opts === "function") {
512
+ cb = opts;
513
+ opts = {};
514
+ }
515
+ opts.flag || (opts.flag = "a");
516
+ fs.writeFile(path, data, opts, cb);
517
+ };
518
+
519
+ fs.truncate = function(path, len, cb) {
520
+ _fdOperation(path, { flag: "r+" }, function(fd, cb) {
521
+ fs.ftruncate(fd, len, cb, "_nested_");
522
+ }, cb);
523
+ };
524
+
525
+ fs.readdir = function(path, cb) {
526
+ _fdOperation(path, { flag: "\\r" }, function(fd, cb) {
527
+ fs._readdir(fd, cb, "_nested_");
528
+ }, cb);
529
+ };
530
+
531
+ fs.mkdir = function(path, mode, cb) {
532
+ if (typeof mode === "function") {
533
+ cb = mode;
534
+ mode = 0o777;
535
+ }
536
+ _fdOperation(path, { flag: "\\wx" }, function(fd, cb) {
537
+ fs._mkdir(fd, cb, "_nested_");
538
+ }, cb);
539
+ };
540
+
541
+ fs.utimes = function(path, atime, mtime, cb) {
542
+ _fdOperation(path, { flag: "r+" }, function(fd, cb) {
543
+ fs.futimes(fd, atime, mtime, cb, "_nested_");
544
+ }, cb);
545
+ };
546
+
547
+ fs.chmod = fs.lchmod = function(path, mode, cb) {
548
+ _fdOperation(path, { flag: "\\r+" }, function(fd, cb) {
549
+ fs.fchmod(fd, mode, cb, "_nested_");
550
+ }, cb);
551
+ };
552
+
553
+ fs.chown = fs.lchown = function(path, uid, gid, cb) {
554
+ _fdOperation(path, { flag: "\\r+" }, function(fd, cb) {
555
+ fs.fchown(fd, uid, gid, cb, "_nested_");
556
+ }, cb);
557
+ };
558
+
559
+
560
+ /* STUBS */
561
+
562
+ fs.link = function(src, dst, cb) {
563
+ // NOTE: theoretically we _could_ do hard links [with untracked `stat.nlink` count…]
564
+ _.delayedCall(cb, S.err.NOSYS());
565
+ };
566
+
567
+ fs.symlink = function(src, dst, type, cb) {
568
+ if (typeof type === "function") {
569
+ cb = type;
570
+ type = null;
571
+ }
572
+ _.delayedCall(cb, S.err.NOSYS());
573
+ };
574
+
575
+ fs.readlink = function(path, cb) {
576
+ _fdOperation(path, { flag: "\\r" }, function(fd, cb) {
577
+ // the named file is *never* a symbolic link…
578
+ // NOTE: we still use _fdOperation for catching e.g. NOENT/NOTDIR errors…
579
+ cb(S.err.INVAL());
580
+ }, cb);
581
+ };
582
+
583
+ fs.realpath = function(path, cache, cb) {
584
+ if (typeof cache === "function") {
585
+ cb = cache;
586
+ cache = null;
587
+ }
588
+ if (cache) _.delayedCall(cb, S.err.NOSYS()); // TODO: what would be involved here?
589
+ else {
590
+ _fdOperation(path, { flag: "\\r" }, function(fd, cb) {
591
+ cb(null, _.absolutePath(path));
592
+ }, cb);
593
+ }
594
+ };
595
+
596
+ fs.fchown = function(fd, uid, gid, cb, _n_) {
597
+ cb = GROUP(cb, function() {
598
+ const _fd = fileDescriptors[fd];
599
+ if (!_fd || !_fd.flags.write) _.delayedCall(cb, S.err.BADF());
600
+ else _.delayedCall(cb, S.err.NOSYS());
601
+ }, (_n_ === "_nested_"));
602
+ };
603
+
604
+
605
+ return fs;
606
+ };