js-dos 8.1.5 → 8.1.7
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/emulators/emulators.js +2 -0
- package/dist/emulators/emulators.js.map +1 -0
- package/dist/emulators/types/build.d.ts +28 -0
- package/dist/emulators/types/dos/bundle/dos-bundle.d.ts +20 -0
- package/dist/emulators/types/dos/dosbox/ts/direct.d.ts +3 -0
- package/dist/emulators/types/dos/dosbox/ts/worker.d.ts +3 -0
- package/dist/emulators/types/emulators.d.ts +76 -0
- package/dist/emulators/types/http.d.ts +9 -0
- package/dist/emulators/types/impl/ci-impl.d.ts +29 -0
- package/dist/emulators/types/impl/emulators-impl.d.ts +26 -0
- package/dist/emulators/types/impl/modules.d.ts +38 -0
- package/dist/emulators/types/libzip/libzip.d.ts +16 -0
- package/dist/emulators/types/protocol/messages-queue.d.ts +8 -0
- package/dist/emulators/types/protocol/protocol.d.ts +128 -0
- package/dist/emulators/wdosbox-x.js +55 -0
- package/dist/emulators/wdosbox-x.js.symbols +8760 -0
- package/dist/emulators/wdosbox-x.wasm +0 -0
- package/dist/emulators/wdosbox.js +55 -0
- package/dist/emulators/wdosbox.js.symbols +3001 -0
- package/dist/emulators/wdosbox.wasm +0 -0
- package/dist/emulators/wlibzip.js +21 -0
- package/dist/emulators/wlibzip.js.symbols +268 -0
- package/dist/emulators/wlibzip.wasm +0 -0
- package/dist/js-dos.css +1 -0
- package/dist/js-dos.js +22 -0
- package/dist/js-dos.js.map +1 -0
- package/package.json +1 -1
- package/release.zip +0 -0
- package/.eslintrc.json +0 -43
- package/.github/workflows/build.yml +0 -51
- package/.gitmodules +0 -4
- package/index.html +0 -110
- package/postcss.config.cjs +0 -8
- package/src/app.css +0 -14
- package/src/base.css +0 -10
- package/src/components/checkbox.tsx +0 -41
- package/src/components/close-button.tsx +0 -17
- package/src/components/components.css +0 -28
- package/src/components/dos-option-checkbox.tsx +0 -94
- package/src/components/dos-option-select.tsx +0 -108
- package/src/components/dos-option-slider.tsx +0 -35
- package/src/components/error.tsx +0 -16
- package/src/components/loading.tsx +0 -26
- package/src/components/lock.tsx +0 -10
- package/src/components/select.tsx +0 -33
- package/src/components/slider.tsx +0 -69
- package/src/download-file.ts +0 -18
- package/src/frame/account-frame.tsx +0 -156
- package/src/frame/editor/defaults.ts +0 -419
- package/src/frame/editor/editor-conf-frame.tsx +0 -227
- package/src/frame/editor/editor-frame.css +0 -55
- package/src/frame/editor/editor-fs-frame.tsx +0 -314
- package/src/frame/fat-drives-frame.tsx +0 -199
- package/src/frame/frame.css +0 -119
- package/src/frame/frame.tsx +0 -34
- package/src/frame/image-rendering-frame.tsx +0 -8
- package/src/frame/network-frame.tsx +0 -157
- package/src/frame/quick-save-frame.tsx +0 -98
- package/src/frame/settings-frame.tsx +0 -14
- package/src/frame/stats-frame.tsx +0 -87
- package/src/host/bundle-storage.ts +0 -91
- package/src/host/fullscreen.ts +0 -35
- package/src/host/lcache.ts +0 -203
- package/src/host/lstorage.ts +0 -156
- package/src/i18n.ts +0 -185
- package/src/index.css +0 -7
- package/src/load.ts +0 -132
- package/src/login/login.css +0 -9
- package/src/login/login.tsx +0 -77
- package/src/main.tsx +0 -197
- package/src/public/types.ts +0 -45
- package/src/sidebar/account-button.tsx +0 -33
- package/src/sidebar/cloud-save-button.tsx +0 -89
- package/src/sidebar/diskette-icon.tsx +0 -14
- package/src/sidebar/fullscreen-button.tsx +0 -44
- package/src/sidebar/network-button.tsx +0 -39
- package/src/sidebar/sidebar-button.tsx +0 -244
- package/src/sidebar/sidebar.css +0 -47
- package/src/sidebar/sidebar.tsx +0 -30
- package/src/sockdrive/LICENSE +0 -674
- package/src/sockdrive/README.md +0 -45
- package/src/sockdrive/include/sockdrive.h +0 -23
- package/src/sockdrive/js/.eslintrc.json +0 -43
- package/src/sockdrive/js/package.json +0 -29
- package/src/sockdrive/js/src/fatfs/chains.js +0 -318
- package/src/sockdrive/js/src/fatfs/dir.js +0 -385
- package/src/sockdrive/js/src/fatfs/helpers.js +0 -210
- package/src/sockdrive/js/src/fatfs/index.js +0 -606
- package/src/sockdrive/js/src/fatfs/structs.js +0 -255
- package/src/sockdrive/js/src/fatfs/vol.js +0 -166
- package/src/sockdrive/js/src/sockdrive/cache.ts +0 -56
- package/src/sockdrive/js/src/sockdrive/drive.ts +0 -471
- package/src/sockdrive/js/src/sockdrive/lru.ts +0 -221
- package/src/sockdrive/js/src/sockdrive/types.ts +0 -14
- package/src/sockdrive/js/src/sockdrive-fat.d.ts +0 -64
- package/src/sockdrive/js/src/sockdrive-fat.ts +0 -226
- package/src/sockdrive/js/src/sockdrive-native.ts +0 -100
- package/src/sockdrive/js/src/test/test.ts +0 -289
- package/src/sockdrive/js/tsconfig.json +0 -11
- package/src/sockdrive/js/webpack.config.js +0 -68
- package/src/sockdrive/js/yarn.lock +0 -1808
- package/src/store/auth.ts +0 -115
- package/src/store/dos.ts +0 -305
- package/src/store/editor.ts +0 -61
- package/src/store/init.ts +0 -48
- package/src/store/storage.ts +0 -32
- package/src/store/ui.ts +0 -191
- package/src/store.tsx +0 -116
- package/src/ui.tsx +0 -140
- package/src/v8/changes.ts +0 -56
- package/src/v8/config.ts +0 -20
- package/src/v8/subscriptions.ts +0 -62
- package/src/vite-env.d.ts +0 -5
- package/src/window/dos/controls/keyboard.ts +0 -32
- package/src/window/dos/controls/keys.ts +0 -331
- package/src/window/dos/controls/mouse/mount.ts +0 -99
- package/src/window/dos/controls/mouse/mouse-locked.ts +0 -51
- package/src/window/dos/controls/mouse/mouse-not-locked.ts +0 -84
- package/src/window/dos/controls/mouse/mouse-swipe.ts +0 -57
- package/src/window/dos/controls/mouse/pointer.ts +0 -85
- package/src/window/dos/controls/mouse.ts +0 -21
- package/src/window/dos/dos-dhry2.tsx +0 -191
- package/src/window/dos/dos-runtime.tsx +0 -216
- package/src/window/dos/dos-window.tsx +0 -91
- package/src/window/dos/render/canvas.ts +0 -65
- package/src/window/dos/render/resize.ts +0 -31
- package/src/window/dos/render/webgl.ts +0 -173
- package/src/window/dos/sound/audio-node.ts +0 -148
- package/src/window/error-window.tsx +0 -9
- package/src/window/loading-window.tsx +0 -54
- package/src/window/prerun-window.tsx +0 -121
- package/src/window/select-window.tsx +0 -118
- package/src/window/window.css +0 -78
- package/src/window/window.tsx +0 -49
- package/src/ws/ws-sockdrive.ts +0 -123
- package/src/ws/ws-transport-layer.ts +0 -468
- package/tailwind.config.cjs +0 -48
- package/tsconfig.json +0 -32
- package/tsconfig.node.json +0 -9
- package/vite.config.ts +0 -25
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
const S = require("./structs.js");
|
|
2
|
-
const _ = require("./helpers.js");
|
|
3
|
-
|
|
4
|
-
const dir = exports;
|
|
5
|
-
|
|
6
|
-
dir.iterator = function(dirChain, opts) {
|
|
7
|
-
opts || (opts = {});
|
|
8
|
-
|
|
9
|
-
const cache = { buffer: null, n: null };
|
|
10
|
-
function getSectorBuffer(n, cb) {
|
|
11
|
-
if (cache.n === n) cb(null, cache.buffer);
|
|
12
|
-
else {
|
|
13
|
-
cache.n = cache.buffer = null, dirChain.readSectors(n, _.allocBuffer(dirChain.sectorSize), function(e, d) {
|
|
14
|
-
if (e) cb(e);
|
|
15
|
-
else if (!d) return cb(null, null);
|
|
16
|
-
else {
|
|
17
|
-
cache.n = n;
|
|
18
|
-
cache.buffer = d;
|
|
19
|
-
getSectorBuffer(n, cb);
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let secIdx = 0;
|
|
26
|
-
const off = { bytes: 0 };
|
|
27
|
-
let long = null;
|
|
28
|
-
function getNextEntry(cb) {
|
|
29
|
-
if (off.bytes >= dirChain.sectorSize) {
|
|
30
|
-
secIdx += 1;
|
|
31
|
-
off.bytes -= dirChain.sectorSize;
|
|
32
|
-
}
|
|
33
|
-
const entryPos = { chain: dirChain, sector: secIdx, offset: off.bytes };
|
|
34
|
-
getSectorBuffer(secIdx, function(e, buf) {
|
|
35
|
-
if (e) return cb(S.err.IO());
|
|
36
|
-
else if (!buf) return cb(null, null, entryPos);
|
|
37
|
-
|
|
38
|
-
const entryIdx = off.bytes;
|
|
39
|
-
const signalByte = buf[entryIdx];
|
|
40
|
-
if (signalByte === S.entryDoneFlag) return cb(null, null, entryPos);
|
|
41
|
-
else if (signalByte === S.entryFreeFlag) {
|
|
42
|
-
off.bytes += S.dirEntry.size;
|
|
43
|
-
long = null;
|
|
44
|
-
if (opts.includeFree) return cb(null, { _free: true, _pos: entryPos }, entryPos);
|
|
45
|
-
else return getNextEntry(cb); // usually just skip these
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const attrByte = buf[entryIdx+S.dirEntry.fields.Attr.offset];
|
|
49
|
-
const entryType = (attrByte === S.longDirFlag) ? S.longDirEntry : S.dirEntry_simple;
|
|
50
|
-
const entry = entryType.valueFromBytes(buf, off);
|
|
51
|
-
entry._pos = entryPos;
|
|
52
|
-
_.log(_.log.DBG, "entry:", entry, secIdx, entryIdx);
|
|
53
|
-
if (entryType === S.longDirEntry) {
|
|
54
|
-
let firstEntry;
|
|
55
|
-
if (entry.Ord & S.lastLongFlag) {
|
|
56
|
-
firstEntry = true;
|
|
57
|
-
entry.Ord &= ~S.lastLongFlag;
|
|
58
|
-
long = {
|
|
59
|
-
name: -1,
|
|
60
|
-
sum: entry.Chksum,
|
|
61
|
-
_rem: entry.Ord-1,
|
|
62
|
-
_arr: [],
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
if (firstEntry || long && entry.Chksum === long.sum && entry.Ord === long._rem--) {
|
|
66
|
-
const S_lde_f = S.longDirEntry.fields;
|
|
67
|
-
let namepart = entry.Name1;
|
|
68
|
-
if (entry.Name1.length === S_lde_f.Name1.size/2) {
|
|
69
|
-
namepart += entry.Name2;
|
|
70
|
-
if (entry.Name2.length === S_lde_f.Name2.size/2) {
|
|
71
|
-
namepart += entry.Name3;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
long._arr.push(namepart);
|
|
75
|
-
if (!long._rem) {
|
|
76
|
-
long.name = long._arr.reverse().join("");
|
|
77
|
-
delete long._arr;
|
|
78
|
-
delete long._rem;
|
|
79
|
-
}
|
|
80
|
-
} else long = null;
|
|
81
|
-
} else if ((attrByte & 0x08) === 0) { // NOTE: checks `!entry.Attr.volume_id`
|
|
82
|
-
let bestName = null;
|
|
83
|
-
if (long && long.name) {
|
|
84
|
-
const pos = entryIdx + S.dirEntry.fields["Name"].offset;
|
|
85
|
-
const sum = _.checksumName(buf, pos);
|
|
86
|
-
if (sum === long.sum) bestName = long.name;
|
|
87
|
-
}
|
|
88
|
-
if (!bestName) {
|
|
89
|
-
if (signalByte === S.entryIsE5Flag) entry.Name.filename = "\u00E5"+entry.Name.filename.slice(1);
|
|
90
|
-
|
|
91
|
-
const nam = entry.Name.filename.replace(/ +$/, "");
|
|
92
|
-
const ext = entry.Name.extension.replace(/ +$/, "");
|
|
93
|
-
// TODO: lowercase bits http://en.wikipedia.org/wiki/8.3_filename#Compatibility
|
|
94
|
-
// via NTRes, bits 0x08 and 0x10 http://www.fdos.org/kernel/fatplus.txt.1
|
|
95
|
-
bestName = (ext) ? nam+"."+ext : nam;
|
|
96
|
-
}
|
|
97
|
-
entry._name = bestName;
|
|
98
|
-
|
|
99
|
-
// OPTIMIZATION: avoid processing any fields for non-matching entries
|
|
100
|
-
// TODO: we could make this automatic via getters, but…?
|
|
101
|
-
const _entryBuffer = buf.slice(off.bytes-S.dirEntry.size, off.bytes);
|
|
102
|
-
entry._full = function() {
|
|
103
|
-
const _entry = S.dirEntry.valueFromBytes(_entryBuffer);
|
|
104
|
-
_.extend(entry, _entry);
|
|
105
|
-
entry._size = entry.FileSize;
|
|
106
|
-
entry._firstCluster = (entry.FstClusHI << 16) + entry.FstClusLO;
|
|
107
|
-
return entry;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
long = null;
|
|
111
|
-
return cb(null, entry, entryPos);
|
|
112
|
-
} else long = null;
|
|
113
|
-
getNextEntry(cb);
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function iter(cb) {
|
|
118
|
-
getNextEntry(cb);
|
|
119
|
-
return iter; // TODO: previous value can't be re-used, so why make caller re-assign?
|
|
120
|
-
}
|
|
121
|
-
return iter;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
function _updateEntry(vol, entry, newStats) {
|
|
125
|
-
if ("size" in newStats) entry._size = entry.FileSize = newStats.size;
|
|
126
|
-
|
|
127
|
-
if ("_touch" in newStats) newStats.archive = newStats.atime = newStats.mtime = true;
|
|
128
|
-
if ("archive" in newStats) entry.Attr.archive = true; // TODO: also via newStats.mode?
|
|
129
|
-
|
|
130
|
-
let _now;
|
|
131
|
-
function applyDate(d, prefix, timeToo, tenthToo) {
|
|
132
|
-
if (d === true) d = _now || (_now = new Date());
|
|
133
|
-
entry[prefix+"Date"] = { year: d.getFullYear()-1980, month: d.getMonth()+1, day: d.getDate() };
|
|
134
|
-
if (timeToo) {
|
|
135
|
-
entry[prefix+"Time"] = { hours: d.getHours(), minutes: d.getMinutes(), seconds_2: d.getSeconds()>>>1 };
|
|
136
|
-
if (tenthToo) {
|
|
137
|
-
const msec = (d.getSeconds() % 2)*1000 + d.getMilliseconds();
|
|
138
|
-
entry[prefix+"TimeTenth"] = Math.floor(msec / 100);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if ("ctime" in newStats) applyDate(newStats.ctime, "Crt", true, true);
|
|
143
|
-
if ("mtime" in newStats) applyDate(newStats.mtime, "Wrt", true);
|
|
144
|
-
if ("atime" in newStats) applyDate(newStats.atime, "LstAcc");
|
|
145
|
-
|
|
146
|
-
if ("mode" in newStats) {
|
|
147
|
-
entry.Attr.directory = (newStats.mode & S._I.FDIR) ? true : false;
|
|
148
|
-
entry.Attr.volume_id = (newStats.mode & S._I.FREG) ? false : true;
|
|
149
|
-
if (vol.opts.modmode === 0o111) {
|
|
150
|
-
entry.Attr.archive = (newStats.mode & S._I.XUSR) ? true : false;
|
|
151
|
-
entry.Attr.system = (newStats.mode & S._I.XGRP) ? true : false;
|
|
152
|
-
entry.Attr.hidden = (newStats.mode & S._I.XOTH) ? true : false;
|
|
153
|
-
entry.Attr.readonly = (newStats.mode & S._I.WUSR) ? false : true;
|
|
154
|
-
} else if (vol.opts.modmode === 0o7000) {
|
|
155
|
-
entry.Attr.archive = (newStats.mode & S._I.SVTX) ? true : false;
|
|
156
|
-
entry.Attr.system = (newStats.mode & S._I.SGID) ? true : false;
|
|
157
|
-
entry.Attr.hidden = (newStats.mode & S._I.SUID) ? true : false;
|
|
158
|
-
entry.Attr.readonly = (
|
|
159
|
-
newStats.mode & S._I.WUSR ||
|
|
160
|
-
newStats.mode & S._I.WGRP ||
|
|
161
|
-
newStats.mode & S._I.WOTH
|
|
162
|
-
) ? false : true;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if ("firstCluster" in newStats) {
|
|
167
|
-
entry.FstClusLO = newStats.firstCluster & 0xFFFF;
|
|
168
|
-
entry.FstClusHI = newStats.firstCluster >>> 16;
|
|
169
|
-
entry._firstCluster = newStats.firstCluster;
|
|
170
|
-
}
|
|
171
|
-
return entry;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
dir.makeStat = function(vol, entry) {
|
|
175
|
-
const stats = {}; // TODO: return an actual `instanceof fs.Stat` somehow?
|
|
176
|
-
|
|
177
|
-
stats.isFile = function() {
|
|
178
|
-
return (!entry.Attr.volume_id && !entry.Attr.directory);
|
|
179
|
-
};
|
|
180
|
-
stats.isDirectory = function() {
|
|
181
|
-
return entry.Attr.directory;
|
|
182
|
-
};
|
|
183
|
-
stats.isBlockDevice = function() {
|
|
184
|
-
return false;
|
|
185
|
-
};
|
|
186
|
-
stats.isCharacterDevice = function() {
|
|
187
|
-
return false;
|
|
188
|
-
};
|
|
189
|
-
stats.isSymbolicLink = function() {
|
|
190
|
-
return false;
|
|
191
|
-
};
|
|
192
|
-
stats.isFIFO = function() {
|
|
193
|
-
return false;
|
|
194
|
-
};
|
|
195
|
-
stats.isSocket = function() {
|
|
196
|
-
return false;
|
|
197
|
-
};
|
|
198
|
-
stats.size = entry.FileSize;
|
|
199
|
-
stats.blksize = vol._sectorsPerCluster*vol._sectorSize;
|
|
200
|
-
stats.blocks = Math.ceil(stats.size / stats.blksize) || 1;
|
|
201
|
-
stats.nlink = 1;
|
|
202
|
-
|
|
203
|
-
stats.mode = S._I.RUSR | S._I.RGRP | S._I.ROTH;
|
|
204
|
-
if (!entry.Attr.readonly) stats.mode |= S._I.WUSR | S._I.WGRP | S._I.WOTH;
|
|
205
|
-
if (entry.Attr.directory) stats.mode |= S._I.FDIR;
|
|
206
|
-
else if (!entry.Attr.volume_id) stats.mode |= S._I.FREG;
|
|
207
|
-
// NOTE: discussion at https://github.com/natevw/fatfs/issues/7
|
|
208
|
-
if (vol.opts.modmode === 0o111) {
|
|
209
|
-
// expose using executable bits, like Samba
|
|
210
|
-
if (entry.Attr.archive) stats.mode |= S._I.XUSR;
|
|
211
|
-
if (entry.Attr.system) stats.mode |= S._I.XGRP;
|
|
212
|
-
if (entry.Attr.hidden) stats.mode |= S._I.XOTH;
|
|
213
|
-
} else if (vol.opts.modmode === 0o7000) {
|
|
214
|
-
// expose using setXid/sticky bits, like MKS
|
|
215
|
-
if (entry.Attr.archive) stats.mode |= S._I.SVTX;
|
|
216
|
-
if (entry.Attr.system) stats.mode |= S._I.SGID;
|
|
217
|
-
if (entry.Attr.hidden) stats.mode |= S._I.SUID;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
stats.mode &= ~vol.opts.umask;
|
|
221
|
-
stats.uid = vol.opts.uid;
|
|
222
|
-
stats.gid = vol.opts.gid;
|
|
223
|
-
|
|
224
|
-
function extractDate(prefix) {
|
|
225
|
-
const date = entry[prefix+"Date"];
|
|
226
|
-
const time = entry[prefix+"Time"] || { hours: 0, minutes: 0, seconds_2: 0 };
|
|
227
|
-
let secs = time.seconds_2 * 2;
|
|
228
|
-
let sect = entry[prefix+"TimeTenth"] || 0;
|
|
229
|
-
if (sect > 100) {
|
|
230
|
-
secs += 1;
|
|
231
|
-
sect -= 100;
|
|
232
|
-
}
|
|
233
|
-
return new Date(date.year+1980, date.month-1, date.day, time.hours, time.minutes, secs, sect*100);
|
|
234
|
-
}
|
|
235
|
-
stats.atime = extractDate("LstAcc");
|
|
236
|
-
stats.mtime = extractDate("Wrt");
|
|
237
|
-
stats.ctime = extractDate("Crt");
|
|
238
|
-
|
|
239
|
-
entry = { // keep immutable copy (with only the fields we need)
|
|
240
|
-
Attr: _.extend({}, entry.Attr),
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
return stats;
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
dir.init = function(vol, dirInfo, cb) {
|
|
247
|
-
const dirChain = dirInfo.chain;
|
|
248
|
-
const isRootDir = ("numSectors" in dirChain); // HACK: all others would be a clusterChain
|
|
249
|
-
const initialCluster = _.allocBuffer(dirChain.sectorSize*vol._sectorsPerCluster);
|
|
250
|
-
const entriesOffset = { bytes: 0 };
|
|
251
|
-
initialCluster.fill(0);
|
|
252
|
-
function writeEntry(name, clusterNum) {
|
|
253
|
-
while (name.length < 8) name += " ";
|
|
254
|
-
S.dirEntry.bytesFromValue(_updateEntry(vol, {
|
|
255
|
-
Name: { filename: name, extension: " " },
|
|
256
|
-
Attr: { directory: true },
|
|
257
|
-
}, { firstCluster: clusterNum, _touch: true, ctime: true }), initialCluster, entriesOffset);
|
|
258
|
-
}
|
|
259
|
-
if (!isRootDir) {
|
|
260
|
-
writeEntry(".", dirChain.firstCluster);
|
|
261
|
-
writeEntry("..", dirInfo.parent.chain.firstCluster);
|
|
262
|
-
};
|
|
263
|
-
dirChain.writeToPosition(0, initialCluster, cb);
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
dir.addFile = function(vol, dirChain, entryInfo, opts, cb) {
|
|
267
|
-
if (typeof opts === "function") {
|
|
268
|
-
cb = opts;
|
|
269
|
-
opts = {};
|
|
270
|
-
}
|
|
271
|
-
var name = entryInfo.name;
|
|
272
|
-
const entries = []; let mainEntry;
|
|
273
|
-
entries.push(mainEntry = {
|
|
274
|
-
Name: _.shortname(name),
|
|
275
|
-
Attr: { directory: opts.dir||false },
|
|
276
|
-
_name: name,
|
|
277
|
-
});
|
|
278
|
-
if (1 || mainEntry.Name._lossy) { // HACK: always write long names until `._lossy` is more useful!
|
|
279
|
-
const workaroundTessel427 = ("\uFFFF".length !== 1);
|
|
280
|
-
if (workaroundTessel427) throw Error("Your JS runtime does not have proper Unicode string support. (If Tessel, is your firmware up-to-date?)");
|
|
281
|
-
|
|
282
|
-
// name entries should be 0x0000-terminated and 0xFFFF-filled
|
|
283
|
-
const S_lde_f = S.longDirEntry.fields;
|
|
284
|
-
const ENTRY_CHUNK_LEN = (S_lde_f.Name1.size + S_lde_f.Name2.size + S_lde_f.Name3.size)/2;
|
|
285
|
-
const partialLen = name.length % ENTRY_CHUNK_LEN;
|
|
286
|
-
let paddingNeeded = partialLen && (ENTRY_CHUNK_LEN - partialLen);
|
|
287
|
-
if (paddingNeeded--) name += "\u0000";
|
|
288
|
-
while (paddingNeeded-- > 0) name += "\uFFFF";
|
|
289
|
-
// now fill in as many entries as it takes
|
|
290
|
-
let off = 0;
|
|
291
|
-
let ord = 1;
|
|
292
|
-
while (off < name.length) {
|
|
293
|
-
entries.push({
|
|
294
|
-
Ord: ord++,
|
|
295
|
-
Name1: name.slice(off, off+=S_lde_f.Name1.size/2),
|
|
296
|
-
Attr_raw: S.longDirFlag,
|
|
297
|
-
Chksum: null,
|
|
298
|
-
Name2: name.slice(off, off+=S_lde_f.Name2.size/2),
|
|
299
|
-
Name3: name.slice(off, off+=S_lde_f.Name3.size/2),
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
entries[entries.length - 1].Ord |= S.lastLongFlag;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (entryInfo.tail) {
|
|
306
|
-
var name = mainEntry.Name.filename;
|
|
307
|
-
const suffix = "~"+entryInfo.tail;
|
|
308
|
-
const endIdx = name.indexOf(" ");
|
|
309
|
-
const sufIdx = (~endIdx) ? Math.min(endIdx, name.length-suffix.length) : name.length-suffix.length;
|
|
310
|
-
if (sufIdx < 0) return cb(S.err.NAMETOOLONG()); // TODO: would EXIST be more correct?
|
|
311
|
-
mainEntry.Name.filename = name.slice(0, sufIdx)+suffix+name.slice(sufIdx+suffix.length);
|
|
312
|
-
_.log(_.log.DBG, "Shortname amended to:", mainEntry.Name);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
vol.allocateInFAT(dirChain.firstCluster || 2, function(e, fileCluster) {
|
|
316
|
-
if (e) return cb(e);
|
|
317
|
-
|
|
318
|
-
const nameBuf = S.dirEntry.fields["Name"].bytesFromValue(mainEntry.Name);
|
|
319
|
-
const nameSum = _.checksumName(nameBuf);
|
|
320
|
-
// TODO: finalize initial properties… (via `opts.mode` instead?)
|
|
321
|
-
_updateEntry(vol, mainEntry, { firstCluster: fileCluster, size: 0, ctime: true, _touch: true });
|
|
322
|
-
mainEntry._pos = _.adjustedPos(vol, entryInfo.target, S.dirEntry.size*(entries.length-1));
|
|
323
|
-
entries.slice(1).forEach(function(entry) {
|
|
324
|
-
entry.Chksum = nameSum;
|
|
325
|
-
});
|
|
326
|
-
entries.reverse();
|
|
327
|
-
if (entryInfo.lastEntry) entries.push({});
|
|
328
|
-
|
|
329
|
-
const entriesData = _.allocBuffer(S.dirEntry.size*entries.length);
|
|
330
|
-
const dataOffset = { bytes: 0 };
|
|
331
|
-
entries.forEach(function(entry) {
|
|
332
|
-
const entryType = ("Ord" in entry) ? S.longDirEntry : S.dirEntry;
|
|
333
|
-
entryType.bytesFromValue(entry, entriesData, dataOffset);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
_.log(_.log.DBG, "Writing", entriesData.length, "byte directory entry", mainEntry, "into", dirChain.toJSON(), "at", entryInfo.target);
|
|
337
|
-
dirChain.writeToPosition(entryInfo.target, entriesData, function(e) {
|
|
338
|
-
// TODO: if we get error, what/should we clean up?
|
|
339
|
-
if (e) cb(e);
|
|
340
|
-
else cb(null, mainEntry, vol.chainForCluster(fileCluster, dirChain));
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
dir.findInDirectory = function(vol, dirChain, name, opts, cb) {
|
|
346
|
-
const matchName = name.toUpperCase();
|
|
347
|
-
const tailName = (opts.prepareForCreate) ? _.shortname(name) : null;
|
|
348
|
-
let maxTail = 0;
|
|
349
|
-
|
|
350
|
-
function processNext(next) {
|
|
351
|
-
next = next(function(e, d, entryPos) {
|
|
352
|
-
if (e) cb(e);
|
|
353
|
-
else if (!d) cb(S.err.NOENT(), { tail: maxTail, target: entryPos, lastEntry: true });
|
|
354
|
-
else if (d._free) processNext(next); // TODO: look for long enough reusable run
|
|
355
|
-
else if (d._name.toUpperCase() === matchName) return cb(null, d._full());
|
|
356
|
-
else if (!opts.prepareForCreate) processNext(next);
|
|
357
|
-
else {
|
|
358
|
-
let dNum = 1;
|
|
359
|
-
let dName = d.Name.filename;
|
|
360
|
-
const dTail = dName.match(/(.*)~(\d+)/);
|
|
361
|
-
if (dTail) {
|
|
362
|
-
dNum = +dTail[2];
|
|
363
|
-
dName = dTail[1];
|
|
364
|
-
}
|
|
365
|
-
if (tailName.extension === d.Name.extension &&
|
|
366
|
-
tailName.filename.indexOf(dName) === 0) {
|
|
367
|
-
maxTail = Math.max(dNum+1, maxTail);
|
|
368
|
-
}
|
|
369
|
-
processNext(next);
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
processNext(dir.iterator(dirChain, { includeFree: (0 && opts.prepareForCreate) }));
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
dir.updateEntry = function(vol, entry, newStats, cb) {
|
|
377
|
-
if (!entry._pos || !entry._pos.chain) throw Error("Entry source unknown!");
|
|
378
|
-
|
|
379
|
-
const entryPos = entry._pos;
|
|
380
|
-
const newEntry = _updateEntry(vol, entry, newStats);
|
|
381
|
-
const data = S.dirEntry.bytesFromValue(newEntry);
|
|
382
|
-
_.log(_.log.DBG, "UPDATING ENTRY", newStats, newEntry, entryPos, data);
|
|
383
|
-
// TODO: if write fails, then entry becomes corrupt!
|
|
384
|
-
entryPos.chain.writeToPosition(entryPos, data, cb);
|
|
385
|
-
};
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
const S = require("./structs.js");
|
|
2
|
-
const _xok = require("xok");
|
|
3
|
-
|
|
4
|
-
// ponyfills for older node.js
|
|
5
|
-
exports.allocBuffer = Buffer.alloc || function(len, val) {
|
|
6
|
-
const b = Buffer(len);
|
|
7
|
-
if (arguments.length > 1) b.fill(val);
|
|
8
|
-
return b;
|
|
9
|
-
};
|
|
10
|
-
exports.bufferFrom = Buffer.from || function(arg0, arg1) {
|
|
11
|
-
return (arguments.length > 1) ? Buffer(arg0, arg1) : Buffer(arg0);
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// flag for WORKAROUND: https://github.com/tessel/beta/issues/380
|
|
15
|
-
exports.workaroundTessel380 = !Buffer.from && function() {
|
|
16
|
-
const b = Buffer([0]);
|
|
17
|
-
const s = b.slice(0);
|
|
18
|
-
return ((s[0] = 0xFF) !== b[0]);
|
|
19
|
-
}();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// WORKAROUND: https://github.com/tessel/beta/issues/433
|
|
23
|
-
let oldslice;
|
|
24
|
-
if (!Buffer.alloc && Buffer(5).slice(10).length < 0) {
|
|
25
|
-
oldslice = Buffer.prototype.slice, Buffer.prototype.slice = function(s, e) {
|
|
26
|
-
if (s > this.length) s = this.length;
|
|
27
|
-
// ~WORKAROUND: https://github.com/tessel/beta/issues/434
|
|
28
|
-
return (arguments.length > 1) ? oldslice.call(this, s, e) : oldslice.call(this, s);
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
exports.absoluteSteps = function(path) {
|
|
33
|
-
const steps = [];
|
|
34
|
-
path.split("/").forEach(function(str) {
|
|
35
|
-
// NOTE: these should actually be fine, just wasteful…
|
|
36
|
-
if (str === "..") steps.pop();
|
|
37
|
-
else if (str && str !== ".") steps.push(str);
|
|
38
|
-
});
|
|
39
|
-
return steps.map(exports.longname);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
exports.absolutePath = function(path) {
|
|
43
|
-
return "/"+exports.absoluteSteps(path).join("/");
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
exports.parseFlags = function(flags) {
|
|
47
|
-
// read, write, append, create, truncate, exclusive
|
|
48
|
-
let info; let _dir; // NOTE: there might be more clever ways to "parse", but…
|
|
49
|
-
if (flags[0] === "\\") {
|
|
50
|
-
// internal flag used internally to `fs.open` directories without `S.err.ISDIR()`
|
|
51
|
-
flags = flags.slice(1);
|
|
52
|
-
_dir = true;
|
|
53
|
-
}
|
|
54
|
-
switch (flags) {
|
|
55
|
-
case "r": info = { read: true, write: false, create: false }; break;
|
|
56
|
-
case "r+": info = { read: true, write: true, create: false }; break;
|
|
57
|
-
case "rs": info = { read: true, write: false, create: false, sync: true }; break;
|
|
58
|
-
case "rs+": info = { read: true, write: true, create: false, sync: true }; break;
|
|
59
|
-
case "w": info = { read: false, write: true, create: true, truncate: true }; break;
|
|
60
|
-
case "wx": info = { read: false, write: true, create: true, exclusive: true }; break;
|
|
61
|
-
case "w+": info = { read: true, write: true, create: true, truncate: true }; break;
|
|
62
|
-
case "wx+": info = { read: true, write: true, create: true, exclusive: true }; break;
|
|
63
|
-
case "a": info = { read: false, write: true, create: true, append: true }; break;
|
|
64
|
-
case "ax": info = { read: false, write: true, create: true, append: true, exclusive: true }; break;
|
|
65
|
-
case "a+": info = { read: true, write: true, create: true, append: true }; break;
|
|
66
|
-
case "ax+": info = { read: true, write: true, create: true, append: true, exclusive: true }; break;
|
|
67
|
-
default: throw Error("Uknown mode: "+flags); // TODO: throw as `S.err.INVAL`
|
|
68
|
-
}
|
|
69
|
-
if (info.sync) throw Error("Mode not implemented."); // TODO: what would this require of us?
|
|
70
|
-
if (_dir) info._openDir = true;
|
|
71
|
-
return info;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// TODO: these are great candidates for special test coverage!
|
|
76
|
-
const _snInvalid = /[^A-Z0-9$%'-_@~`!(){}^#&.]/g; // NOTE: '.' is not valid but we split it away
|
|
77
|
-
exports.shortname = function(name) {
|
|
78
|
-
let lossy = false;
|
|
79
|
-
// TODO: support preservation of case for otherwise non-lossy name!
|
|
80
|
-
name = name.toUpperCase().replace(/ /g, "").replace(/^\.+/, "");
|
|
81
|
-
name = name.replace(_snInvalid, function() {
|
|
82
|
-
lossy = true;
|
|
83
|
-
return "_";
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const parts = name.split(".");
|
|
87
|
-
let basis3 = parts.pop();
|
|
88
|
-
let basis8 = parts.join("");
|
|
89
|
-
if (!parts.length) {
|
|
90
|
-
basis8 = basis3;
|
|
91
|
-
basis3 = " ";
|
|
92
|
-
}
|
|
93
|
-
if (basis8.length > 8) {
|
|
94
|
-
basis8 = basis8.slice(0, 8);
|
|
95
|
-
// NOTE: technically, spec's "lossy conversion" flag is NOT set by excess length.
|
|
96
|
-
// But since lossy conversion and truncated names both need a numeric tail…
|
|
97
|
-
lossy = true;
|
|
98
|
-
} else while (basis8.length < 8) basis8 += " ";
|
|
99
|
-
if (basis3.length > 3) {
|
|
100
|
-
basis3 = basis3.slice(0, 3);
|
|
101
|
-
lossy = true;
|
|
102
|
-
} else while (basis3.length < 3) basis3 += " ";
|
|
103
|
-
|
|
104
|
-
return { filename: basis8, extension: basis3, _lossy: lossy };
|
|
105
|
-
return { basis: [basis8, basis3], lossy: lossy };
|
|
106
|
-
};
|
|
107
|
-
// shortname("autoexec.bat") => {basis:['AUTOEXEC','BAT'],lossy:false}
|
|
108
|
-
// shortname("autoexecutable.batch") => {basis:['AUTOEXEC','BAT'],lossy:true}
|
|
109
|
-
// TODO: OS X stores `shortname("._.Trashes")` as ['~1', 'TRA'] — should we?
|
|
110
|
-
|
|
111
|
-
const _lnInvalid = /[^a-zA-Z0-9$%'-_@~`!(){}^#&.+,;=[\] ]/g;
|
|
112
|
-
exports.longname = function(name) {
|
|
113
|
-
name = name.trim().replace(/\.+$/, "").replace(_lnInvalid, function(c) {
|
|
114
|
-
if (c.length > 1) throw Error("Internal problem: unexpected match length!");
|
|
115
|
-
if (c.charCodeAt(0) > 127) return c;
|
|
116
|
-
else throw Error("Invalid character "+JSON.stringify(c)+" in name.");
|
|
117
|
-
lossy = true;
|
|
118
|
-
return "_";
|
|
119
|
-
});
|
|
120
|
-
if (name.length > 255) throw Error("Name is too long.");
|
|
121
|
-
return name;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
function nameChkSum(sum, c) {
|
|
125
|
-
return ((sum & 1) ? 0x80 : 0) + (sum >>> 1) + c & 0xFF;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// WORKAROUND: https://github.com/tessel/beta/issues/335
|
|
129
|
-
function reduceBuffer(buf, start, end, fn, res) {
|
|
130
|
-
// NOTE: does not handle missing `res` like Array.prototype.reduce would
|
|
131
|
-
for (let i = start; i < end; ++i) {
|
|
132
|
-
res = fn(res, buf[i]);
|
|
133
|
-
}
|
|
134
|
-
return res;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
exports.checksumName = function(buf, off) {
|
|
138
|
-
off || (off = 0);
|
|
139
|
-
const len = S.dirEntry.fields["Name"].size;
|
|
140
|
-
return reduceBuffer(buf, off, off+len, nameChkSum, 0);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
/* comparing C rounding trick from FAT spec with Math.ceil
|
|
145
|
-
function tryBoth(d) {
|
|
146
|
-
var a = ((D.RootEntCnt * 32) + (D.BytsPerSec - 1)) / D.BytsPerSec >>> 0,
|
|
147
|
-
b = Math.ceil((D.RootEntCnt * 32) / D.BytsPerSec);
|
|
148
|
-
if (b !== a) console.log("try", b, a, (b === a) ? '' : '*');
|
|
149
|
-
return (b === a);
|
|
150
|
-
}
|
|
151
|
-
// BytsPerSec — "may take on only the following values: 512, 1024, 2048 or 4096"
|
|
152
|
-
[512, 1024, 2048, 4096].forEach(function (bps) {
|
|
153
|
-
// RootEntCnt — "a count that when multiplied by 32 results in an even multiple of BPB_BytsPerSec"
|
|
154
|
-
for (var evenMultiplier = 0; evenMultiplier < 1024*1024*16; evenMultiplier += 2) {
|
|
155
|
-
var rec = (bps * evenMultiplier) / 32;
|
|
156
|
-
tryBoth({RootEntCnt:rec, BytsPerSec:bps});
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
*/
|
|
160
|
-
|
|
161
|
-
exports.fmtHex = function(n, ff) {
|
|
162
|
-
return (1+ff+n).toString(16).slice(1);
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
exports.delayedCall = function(fn) {
|
|
166
|
-
if (!fn) throw Error("No function provided!"); // debug aid
|
|
167
|
-
const ctx = this;
|
|
168
|
-
const args = Array.prototype.slice.call(arguments, 1);
|
|
169
|
-
// @caiiiycuk: TODO invesitgate, is setImmediate acually needed?
|
|
170
|
-
// console.warn("setImmediate emulation, please remote it!");
|
|
171
|
-
// setTimeout(function() {
|
|
172
|
-
fn.apply(ctx, args);
|
|
173
|
-
// }, 4);
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
exports.adjustedPos = function(vol, pos, bytes) {
|
|
177
|
-
const _pos = {
|
|
178
|
-
chain: pos.chain,
|
|
179
|
-
sector: pos.sector,
|
|
180
|
-
offset: pos.offset + bytes,
|
|
181
|
-
}; const secSize = vol._sectorSize;
|
|
182
|
-
while (_pos.offset >= secSize) {
|
|
183
|
-
_pos.sector += 1;
|
|
184
|
-
_pos.offset -= secSize;
|
|
185
|
-
}
|
|
186
|
-
return _pos;
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
exports.extend = _xok;
|
|
190
|
-
|
|
191
|
-
let _prevDbg = Date.now();
|
|
192
|
-
const _thresh = 50;
|
|
193
|
-
|
|
194
|
-
function log(level) {
|
|
195
|
-
if (level < log.level) return;
|
|
196
|
-
|
|
197
|
-
const now = Date.now();
|
|
198
|
-
const diff = now - _prevDbg;
|
|
199
|
-
arguments[0] = ((diff < _thresh) ? " " : "") + diff.toFixed(0) + "ms";
|
|
200
|
-
console.log.apply(console, arguments);
|
|
201
|
-
_prevDbg = now;
|
|
202
|
-
}
|
|
203
|
-
log.DBG = -4;
|
|
204
|
-
log.INFO = -3;
|
|
205
|
-
log.WARN = -2;
|
|
206
|
-
log.ERR = -1;
|
|
207
|
-
|
|
208
|
-
log.level = log.WARN;
|
|
209
|
-
|
|
210
|
-
exports.log = log;
|