From 0552ae2951ce73c13d36bee72d749696586c4f55 Mon Sep 17 00:00:00 2001 From: Alice R Date: Tue, 1 Oct 2019 12:05:59 -0600 Subject: [PATCH] Various Emscripten frontend fixes (#199) * Prevent browsers from blocking various shortcuts * Fix O_TRUNC and localstorage empty files, add unlink and rmdir * Clean up setattr size implementation * Improve write performance for large files * Add more function keys for good measure (Opera uses F10...) * Remove existence check from unlink (already done by lookup) * Inline shortcut event handler * Remove unused code in CompositeStorage set/remove methods * Increase minimum vfs buffer size --- arch/emscripten/web/src/index.js | 21 +++++++ arch/emscripten/web/src/storage.js | 44 +++++++++++++- arch/emscripten/web/src/storage_emscripten.js | 59 ++++++++++++++++--- 3 files changed, 113 insertions(+), 11 deletions(-) diff --git a/arch/emscripten/web/src/index.js b/arch/emscripten/web/src/index.js index 8e2440d30..a2455d637 100644 --- a/arch/emscripten/web/src/index.js +++ b/arch/emscripten/web/src/index.js @@ -77,6 +77,27 @@ window.MzxrunInitialize = function(options) { e.preventDefault(); }, false); + /* Disable the default functions for several common MegaZeux shortcuts. + * FIXME: Opera defaults for left alt and F3 can't be disabled this way (as of 63). + */ + document.body.addEventListener('keydown', event => { + let key = event.key.toUpperCase(); + if ((event.altKey && ( + key == 'C' // Select char + || key == 'D' // Delete file/directory + || key == 'N' // New directory + || key == 'R' // Rename file/directory + )) + || key == 'F1' // Help + || key == 'F2' // Settings + || key == 'F3' // Save, Load World + || key == 'F4' // Load save + || key == 'F9' // Quicksave + || key == 'F10' // Quickload Save + + ) event.preventDefault() + }) + try { if (!options.path) throw "Missing option: path!"; if (!options.files) throw "Missing option: files!"; diff --git a/arch/emscripten/web/src/storage.js b/arch/emscripten/web/src/storage.js index 1646439ec..d3ba4519d 100644 --- a/arch/emscripten/web/src/storage.js +++ b/arch/emscripten/web/src/storage.js @@ -60,6 +60,12 @@ class InMemoryStorage { this.map[key] = value; return true; } + + remove(key) { + if (this.readonly) return false; + delete this.map[key]; + return true; + } } class CompositeStorage { @@ -98,13 +104,20 @@ class CompositeStorage { } set(key, value) { - let promise = Promise.resolve(false); for (var p = this.providers.length - 1; p >= 0; p--) { let provider = this.providers[p]; if (provider.set(key, value)) return true; } return false; } + + remove(key) { + for (var p = this.providers.length - 1; p >= 0; p--) { + let provider = this.providers[p]; + if (provider.remove(key)) return true; + } + return false; + } } class AsyncStorageWrapper extends InMemoryStorage { @@ -139,6 +152,15 @@ class AsyncStorageWrapper extends InMemoryStorage { return false; } } + + remove(key) { + if (super.remove(key)) { + this.parent.remove(key); + return true; + } else { + return false; + } + } } class BrowserBackedStorage { @@ -153,7 +175,7 @@ class BrowserBackedStorage { get(key) { const result = this.storage.getItem(this.prefix + key); - if (result) { + if (result !== null) { return result.split(",").map(s => parseInt(s)); } else { return null; @@ -175,6 +197,11 @@ class BrowserBackedStorage { this.storage.setItem(this.prefix + key, value.join(",")); return true; } + + remove(key) { + this.storage.removeItem(this.prefix + key); + return true; + } } class IndexedDbBackedAsyncStorage { @@ -247,6 +274,19 @@ class IndexedDbBackedAsyncStorage { } }); } + + remove(key) { + const transaction = this.database.transaction(["files"], "readwrite"); + return new Promise((resolve, reject) => { + const request = transaction.objectStore("files").delete(key); + request.onsuccess = event => { + resolve(true); + } + request.onerror = event => { + resolve(false); + } + }); + } } export function createBrowserBackedStorage(storage, dbName) { diff --git a/arch/emscripten/web/src/storage_emscripten.js b/arch/emscripten/web/src/storage_emscripten.js index 8e62c653d..6b926f758 100644 --- a/arch/emscripten/web/src/storage_emscripten.js +++ b/arch/emscripten/web/src/storage_emscripten.js @@ -21,6 +21,7 @@ const EPERM = 1; const ENOENT = 2; const EINVAL = 22; +const ENOTEMPTY = 39; const O_CREAT = 0x40; const O_TRUNC = 0x200; const S_IFDIR = 0x4000; @@ -30,7 +31,7 @@ const S_IFMT = 0xF000; function vfs_get_type(vfs, path) { if (path.length == 0) return "dir"; let contents = vfs.get(path); - if (contents) { + if (contents !== null) { return "file"; } else { const list = vfs.list(a => a.startsWith(path)); @@ -39,8 +40,15 @@ function vfs_get_type(vfs, path) { return "empty"; } +function vfs_next_power_of_two(n) { + var i = 4096; + while (i < n) i *= 2; + return i; +} + function vfs_expand_array(array, newLength) { if (newLength <= array.length) return array; + newLength = vfs_next_power_of_two(newLength); var newArrayBuffer = new ArrayBuffer(newLength); var newArray = new Uint8Array(newArrayBuffer); @@ -109,6 +117,18 @@ export function wrapStorageForEmscripten(vfs) { node.node_ops.setattr = (n, attr) => { if (attr.mode !== undefined) n.mode = attr.mode; + if (attr.size !== undefined) { + // Used to implement O_TRUNC by the Emscripten FS API. + // console.log("FS setattr size " + n.vfs_path + " " + attr.size); + let old_data = vfs.get(n.vfs_path); + let new_data = new Uint8Array(attr.size); + if (attr.size > 0 && old_data) { + // Note: not sure sizes > 0 will reach here from the FS API... + new_data.set(old_data.slice(0, attr.size)); + } + if (!vfs.set(n.vfs_path, new_data)) + throw new FS.ErrnoError(EPERM); + } } node.stream_ops.llseek = (stream, offset, whence) => { @@ -119,7 +139,7 @@ export function wrapStorageForEmscripten(vfs) { return offset + stream.position; case 2: if (stream.vfs_data) - return offset + stream.vfs_data.length; + return offset + stream.vfs_data_length; else return offset; default: @@ -141,7 +161,23 @@ export function wrapStorageForEmscripten(vfs) { return wrap.createNode(parent, '/' + node.vfs_path + name, mode, dev); }; node.node_ops.rename = (oldNode, newDir, newName) => { - throw "FS TODO rename " + newName; + console.log("FS FIXME rename " + newName); + throw new FS.ErrnoError(EPERM); + }; + node.node_ops.unlink = (parent, name) => { + // console.log("FS unlink " + name); + if (!vfs.remove(node.vfs_path + name)) + throw new FS.ErrnoError(EPERM); + }; + node.node_ops.rmdir = (parent, name) => { + // console.log("FS rmdir " + name); + const path = node.vfs_path + name; + const list = vfs.list(a => a.startsWith(path)); + for (var i = 0; i < list.length; i++) { + var entry = list[i].substring(path.length); + if (entry.length != 0) + throw new FS.ErrnoError(ENOTEMPTY); + } }; node.node_ops.readdir = (node) => { const path = node.vfs_path; @@ -183,18 +219,20 @@ export function wrapStorageForEmscripten(vfs) { } } } + stream.vfs_data_length = stream.vfs_data.length; } node.stream_ops.close = (stream) => { // console.log("FS close " + node.vfs_path); if (stream.vfs_data && stream.vfs_modified) { - vfs.set(node.vfs_path, stream.vfs_data); + let length = Math.min(stream.vfs_data.length, stream.vfs_data_length); + vfs.set(node.vfs_path, stream.vfs_data.subarray(0, length)); } } node.stream_ops.read = (stream, buffer, bufOffset, dataLength, dataOffset) => { // console.log("FS read " + node.vfs_path + " " + dataOffset + " " + dataLength); - const size = Math.min(dataLength, stream.vfs_data.length - dataOffset); + const size = Math.min(dataLength, stream.vfs_data_length - dataOffset); if (size > 8 && buffer.set && stream.vfs_data.subarray) { buffer.set(stream.vfs_data.subarray(dataOffset, dataOffset + size), bufOffset); } else { @@ -208,7 +246,7 @@ export function wrapStorageForEmscripten(vfs) { node.stream_ops.write = (stream, buffer, bufOffset, dataLength, dataOffset) => { // console.log("FS write " + node.vfs_path + " " + dataOffset + " " + dataLength); if (dataLength <= 0) return 0; - stream.vfs_data = vfs_expand_array(stream.vfs_data, Math.max(stream.vfs_data.length, dataOffset + dataLength)) + stream.vfs_data = vfs_expand_array(stream.vfs_data, dataOffset + dataLength); const size = dataLength; if (size > 8 && stream.vfs_data.set && buffer.subarray) { stream.vfs_data.set(buffer.subarray(bufOffset, bufOffset + size), dataOffset); @@ -217,18 +255,21 @@ export function wrapStorageForEmscripten(vfs) { stream.vfs_data[dataOffset + i] = buffer[bufOffset + i]; } } + stream.vfs_data_length = Math.max(dataOffset + dataLength, stream.vfs_data_length); stream.vfs_modified = true; return dataLength; } node.stream_ops.allocate = (stream, offset, length) => { - const oldLength = stream.vfs_data.length; stream.vfs_data = vfs_expand_array(stream.vfs_data, offset + length); - if (oldLength != stream.vfs_data.length) stream.vfs_modified = true; + if (offset + length > stream.vfs_data_length) { + stream.vfs_data_length = offset + length; + stream.vfs_modified = true; + } } } return node; } }; return wrap; -} \ No newline at end of file +}