From 2ac816dc9c5ecef851d64278155f5befd5e98b81 Mon Sep 17 00:00:00 2001 From: MickaelK Date: Thu, 2 Jan 2025 09:09:00 +1100 Subject: [PATCH] feature (table): table opener app --- config/mime.json | 2 + public/assets/helpers/loader_wasm.js | 172 ++++++++++++++++++ .../assets/pages/filespage/ctrl_filesystem.js | 6 +- .../pages/viewerpage/application_table.js | 41 +++-- .../viewerpage/application_table/Makefile | 2 + .../viewerpage/application_table/loader.js | 24 +++ .../application_table/loader_dbase.js | 26 +++ .../application_table/loader_symbol.c | 88 +++++++++ .../application_table/loader_symbol.js | 45 +++++ .../application_table/loader_symbol.wasm | Bin 0 -> 15659 bytes public/assets/pages/viewerpage/mimetype.js | 2 +- public/package.json | 3 + 12 files changed, 387 insertions(+), 24 deletions(-) create mode 100644 public/assets/helpers/loader_wasm.js create mode 100644 public/assets/pages/viewerpage/application_table/Makefile create mode 100644 public/assets/pages/viewerpage/application_table/loader.js create mode 100644 public/assets/pages/viewerpage/application_table/loader_dbase.js create mode 100644 public/assets/pages/viewerpage/application_table/loader_symbol.c create mode 100644 public/assets/pages/viewerpage/application_table/loader_symbol.js create mode 100755 public/assets/pages/viewerpage/application_table/loader_symbol.wasm diff --git a/config/mime.json b/config/mime.json index d0b0ceea..514c68f3 100644 --- a/config/mime.json +++ b/config/mime.json @@ -4,6 +4,7 @@ "3gp": "video/3gpp", "3gpp": "video/3gpp", "7z": "application/x-7z-compressed", + "a": "application/x-archive", "ai": "application/pdf", "aif": "audio/x-aiff", "aiff": "audio/x-aiff", @@ -169,6 +170,7 @@ "war": "application/java-archive", "wav": "audio/wave", "wave": "audio/wave", + "wasm": "application/wasm", "wbmp": "image/vnd.wap.wbmp", "webm": "video/webm", "webp": "image/webp", diff --git a/public/assets/helpers/loader_wasm.js b/public/assets/helpers/loader_wasm.js new file mode 100644 index 00000000..768f2712 --- /dev/null +++ b/public/assets/helpers/loader_wasm.js @@ -0,0 +1,172 @@ +export default async function(baseURL, path) { + const wasi = new Wasi(); + const wasm = await WebAssembly.instantiateStreaming( + fetch(new URL(path, baseURL)), { + wasi_snapshot_preview1: { + ...wasi, + }, + env: { + ...wasi, + ...syscalls, + }, + }, + ); + wasi.instance = wasm.instance; + return wasm; +} + +const FS = {}; +let nextFd = 0; +writeFS(new Uint8Array(), 0); // stdin +writeFS(new Uint8Array(), 1); // stdout +writeFS(new Uint8Array(), 2); // stderr +if (nextFd !== 3) throw new Error("Unexpected next fd"); + +export function writeFS(buffer, fd) { + if (fd === undefined) fd = nextFd; + else if (!(buffer instanceof Uint8Array)) throw new Error("can only write Uint8Array"); + + FS[fd] = { + buffer, + position: 0, + }; + nextFd += 1; + return nextFd - 1; +} + +export function readFS(fd) { + if (fd < 3) throw new Error("cannot read from stdin, stdout or stderr"); + const file = FS[fd]; + if (!file) throw new Error("file does not exist"); + return file.buffer; +} + +export const syscalls = { + __syscall_fcntl64: (fd, cmd, varargs) => { + console.log(`Stubbed __syscall_fcntl64 called with fd=${fd}, cmd=${cmd}, varargs=${varargs}`); + return -1; + }, + __syscall_ioctl: (fd, op, varargs) => { + switch (op) { + case 21523: + break; + default: + console.log(`Stubbed __syscall_ioctl called with fd=${fd}, request=${op}, varargs=${varargs}`); + } + return 0; + }, +}; + +export class Wasi { + #instance; + + constructor() { + this.fd_read = this.fd_read.bind(this); + this.fd_write = this.fd_write.bind(this); + this.fd_seek = this.fd_seek.bind(this); + this.fd_close = this.fd_close.bind(this); + } + + set instance(val) { + this.#instance = val; + } + + fd_write(fd, iovs, iovs_len, nwritten) { + if (!FS[fd]) { + console.error(`Invalid fd: ${fd}`); + return -1; + } + let output = FS[fd].buffer; + const ioVecArray = new Uint32Array(this.#instance.exports.memory.buffer, iovs, iovs_len * 2); + const memory = new Uint8Array(this.#instance.exports.memory.buffer); + let totalBytesWritten = 0; + for (let i = 0; i < iovs_len * 2; i += 2) { + const sub = memory.subarray( + (ioVecArray[i] || 0), + (ioVecArray[i] || 0) + (ioVecArray[i+1] || 0), + ); + const tmp = new Uint8Array(output.byteLength + sub.byteLength); + tmp.set(output, 0); + tmp.set(sub, output.byteLength); + output = tmp; + totalBytesWritten += ioVecArray[i+1] || 0; + } + const dataView = new DataView(this.#instance.exports.memory.buffer); + dataView.setUint32(nwritten, totalBytesWritten, true); + + FS[fd].buffer = output; + if (fd < 3 && fd >= 0) { + const msg = fd === 1 ? "stdout" : fd === 2 ? "stderr" : "stdxx"; + console.log(msg + ": " + (new TextDecoder()).decode(output)); + FS[fd].buffer = new ArrayBuffer(0); + } + return 0; + } + + fd_read(fd, iovs, iovs_len, nread) { + const file = FS[fd]; + if (!file) { + console.error(`Invalid fd: ${fd}`); + return -1; + } + + const ioVecArray = new Uint32Array(this.#instance.exports.memory.buffer, iovs, iovs_len * 2); + const memory = new Uint8Array(this.#instance.exports.memory.buffer); + let totalBytesRead = 0; + for (let i = 0; i < iovs_len * 2; i += 2) { + const offset = ioVecArray[i]; + const length = ioVecArray[i + 1] || 0; + const bytesToRead = Math.min( + length, + file.buffer.length - file.position, + ); + if (bytesToRead <= 0) { + break; + } + memory.set( + file.buffer.subarray(file.position, file.position + bytesToRead), + offset, + ); + file.position += bytesToRead; + totalBytesRead += bytesToRead; + } + + const dataView = new DataView(this.#instance.exports.memory.buffer); + dataView.setUint32(nread, totalBytesRead, true); + return 0; + } + + fd_seek(fd, offsetBigInt, whence) { + const offset = Number(offsetBigInt); + const file = FS[fd]; + if (!file) { + console.error(`Invalid FD: ${fd}`); + return -1; + } + switch (whence) { + case 0: // SEEK_SET + file.position = offset; + break; + case 1: // SEEK_CUR + file.position += offset; + break; + case 2: // SEEK_END + file.position = file.buffer.length + offset; + break; + default: + console.log(`fd_seek called with fd=${fd}, offset=${offset}, position=${file.position} whence=${whence}`); + const error = new Error("fd_seek trace"); + console.log("Invalid whence", error.stack); + return -1; + } + return 0; + } + + fd_close(fd) { + if (!FS[fd]) { + console.error(`Invalid FD: ${fd}`); + return -1; + } + return 0; + } +} diff --git a/public/assets/pages/filespage/ctrl_filesystem.js b/public/assets/pages/filespage/ctrl_filesystem.js index cfa131f3..b098a623 100644 --- a/public/assets/pages/filespage/ctrl_filesystem.js +++ b/public/assets/pages/filespage/ctrl_filesystem.js @@ -181,9 +181,9 @@ export default async function(render) { $list, virtual, }) => ( - virtual ? - rxjs.fromEvent($page.closest(".scroll-y"), "scroll", { passive: true }) : - rxjs.EMPTY + virtual + ? rxjs.fromEvent($page.closest(".scroll-y"), "scroll", { passive: true }) + : rxjs.EMPTY ).pipe( rxjs.map((e) => { // 0-------------0-----------1-----------2-----------3 .... diff --git a/public/assets/pages/viewerpage/application_table.js b/public/assets/pages/viewerpage/application_table.js index 0d89b07b..7b772dc4 100644 --- a/public/assets/pages/viewerpage/application_table.js +++ b/public/assets/pages/viewerpage/application_table.js @@ -3,11 +3,14 @@ import rxjs, { effect } from "../../lib/rx.js"; import { qs, qsa } from "../../lib/dom.js"; import ajax from "../../lib/ajax.js"; import { loadCSS } from "../../helpers/loader.js"; +import t from "../../locales/index.js"; import ctrlError from "../ctrl_error.js"; import { renderMenubar, buttonDownload } from "./component_menubar.js"; +import { getLoader } from "./application_table/loader.js"; +import { transition } from "./common.js"; -export default async function(render, { getDownloadUrl = nop, getFilename = nop, hasMenubar = true }) { +export default async function(render, { mime, getDownloadUrl = nop, getFilename = nop, hasMenubar = true }) { const $page = createElement(`
@@ -33,29 +36,21 @@ export default async function(render, { getDownloadUrl = nop, getFilename = nop, // feature: initial render const init$ = ajax({ url: getDownloadUrl(), responseType: "arraybuffer" }).pipe( rxjs.mergeMap(async({ response }) => { - const module = await import("../../lib/vendor/shp-to-geojson.browser.js"); - const data = new module.DBase(module.Buffer.from(response)); - - const dummy = []; - // const dummy = new Array(50).fill({}).map((_, i) => ({ - // fieldName: "dummy"+i, - // fieldLength: 4, - // })); - data.properties = data.properties.concat(dummy); + const table = new (await getLoader(mime))(response); // build head const $tr = createElement(`
`); - data.properties.forEach(({ fieldName, fieldLength }) => { + table.getHeader().forEach(({ name, size }) => { const $th = createElement(` -
- ${fieldName} +
+ ${name}
`); let ascending = null; qs($th, "img").onclick = (e) => { ascending = !ascending; - sortBy(qsa($dom.tbody, `.tr [data-column="${fieldName}"]`), ascending); + sortBy(qsa($dom.tbody, `.tr [data-column="${name}"]`), ascending); qsa(e.target.closest(".tr"), "img").forEach(($img) => { $img.style.transform = "rotate(0deg)"; }); @@ -66,18 +61,24 @@ export default async function(render, { getDownloadUrl = nop, getFilename = nop, $dom.thead.appendChild($tr); // build body - for (let i =0; i { const $tr = createElement(`
`); - data.properties.forEach(({ fieldName, fieldLength }) => { + table.getHeader().forEach(({ name, size }) => { $tr.appendChild(createElement(` -
- ${row[fieldName] || "-"} +
+ ${obj[name] || "-"}
`)); }); $dom.tbody.appendChild($tr); - } + }); + if (body.length === 0) $dom.tbody.appendChild(createElement(` +

+ ${t("Empty")} +

+ `)); + transition($dom.tbody.parentElement); }), rxjs.share(), rxjs.catchError(ctrlError()), diff --git a/public/assets/pages/viewerpage/application_table/Makefile b/public/assets/pages/viewerpage/application_table/Makefile new file mode 100644 index 00000000..9a7ddbc9 --- /dev/null +++ b/public/assets/pages/viewerpage/application_table/Makefile @@ -0,0 +1,2 @@ +all: + emcc loader_symbol.c -o loader_symbol.wasm -O2 --no-entry diff --git a/public/assets/pages/viewerpage/application_table/loader.js b/public/assets/pages/viewerpage/application_table/loader.js new file mode 100644 index 00000000..4a638d3e --- /dev/null +++ b/public/assets/pages/viewerpage/application_table/loader.js @@ -0,0 +1,24 @@ +// import loaderDBase from "./loader_dbase.js"; +// import loaderSymbol from "./loader_symbol.js"; + +class ITable { + contructor() {} + getHeader() { throw new Error("NOT_IMPLEMENTED"); } + getBody() { throw new Error("NOT_IMPLEMENTED"); } +} + +export async function getLoader(mime) { + let module = null; + switch (mime) { + case "application/dbf": + module = await import("./loader_dbase.js"); + break; + case "application/x-archive": + module = await import("./loader_symbol.js"); + break; + default: + throw new TypeError(`unsupported mimetype '${mime}'`); + } + + return module.default(ITable); +} diff --git a/public/assets/pages/viewerpage/application_table/loader_dbase.js b/public/assets/pages/viewerpage/application_table/loader_dbase.js new file mode 100644 index 00000000..d112b19e --- /dev/null +++ b/public/assets/pages/viewerpage/application_table/loader_dbase.js @@ -0,0 +1,26 @@ +export default async function(ITable) { + const module = await import("../../../lib/vendor/shp-to-geojson.browser.js"); + + return class TableImpl extends ITable { + constructor(response) { + super(); + this.data = new module.DBase(module.Buffer.from(response)); + } + + getHeader() { + return this.data.properties.map(({ fieldName, fieldLength }) => ({ + name: fieldName, + size: fieldLength, + })); + } + + getBody() { + const body = []; + for (let i =0; i +#include +#include +#include + +#define ARMAG "!\n" +#define SARMAG sizeof(ARMAG) - 1 +#define AR_HDR_SIZE 60 + +struct ar_hdr { + char name[16]; + char timestamp[12]; + char owner[6]; + char group[6]; + char mode[8]; + char size[10]; + char fmag[2]; +}; + +EMSCRIPTEN_KEEPALIVE int execute(int fdinput, int fdoutput) { + if (fdinput == 0) { + fprintf(stderr, "ERROR - missing input %d\n", fdinput); + return 1; + } + if (fdoutput == 0) { + fprintf(stderr, "ERROR - missing input %d\n", fdoutput); + return 1; + } + + FILE* finput = fdopen(fdinput, "rb"); + if (!finput) { + fprintf(stderr, "ERROR - cannot open input file\n"); + return 1; + } + FILE* foutput = fdopen(fdoutput, "wb"); + if (!foutput) { + fprintf(stderr, "ERROR - cannot open output file\n"); + fclose(finput); + return 1; + } + + char magic[SARMAG]; + size_t c = fread(magic, 1, SARMAG, finput); + if (c == 0) { + fprintf(stderr, "ERROR count=%zu error=%d\n", c, ferror(finput)); + fclose(finput); + fclose(foutput); + return 1; + } + if (strncmp(magic, ARMAG, SARMAG) != 0) { + fprintf(stderr, "ERROR file is not of the expected shape"); + fclose(finput); + fclose(foutput); + return 1; + } + + struct ar_hdr header; + while (fread(&header, 1, AR_HDR_SIZE, finput) == AR_HDR_SIZE) { + if (strncmp(header.fmag, "`\n", 2) != 0) { + fprintf(stderr, "Invalid header format.\n"); + break; + } + long size = strtol(header.size, NULL, 10); + char filename[17] = {0}; + strncpy(filename, header.name, 16); + for (int i = strlen(filename) - 1; i >= 0; i--) { + if (filename[i] == ' ' || filename[i] == '/') { + filename[i] = '\0'; + } + } + if (strlen(filename) > 0) fprintf( + foutput, + "%.16s,%ld,%ld,%ld,%ld,%ld\n", + filename, + strtol(header.timestamp, NULL, 10), + strtol(header.owner, NULL, 10), + strtol(header.group, NULL, 10), + strtol(header.mode, NULL, 10), + size, + ); + fseek(finput, (size + 1) & ~1, SEEK_CUR); + } + + fflush(foutput); + fclose(foutput); + fclose(finput); + return 0; +} diff --git a/public/assets/pages/viewerpage/application_table/loader_symbol.js b/public/assets/pages/viewerpage/application_table/loader_symbol.js new file mode 100644 index 00000000..2c0c9d3e --- /dev/null +++ b/public/assets/pages/viewerpage/application_table/loader_symbol.js @@ -0,0 +1,45 @@ +import assert from "../../../lib/assert.js"; +import loadWASM, { writeFS, readFS } from "../../../helpers/loader_wasm.js"; + +export default async function(ITable) { + const { instance } = await loadWASM(import.meta.url, "./loader_symbol.wasm"); + + return class TableImpl extends ITable { + constructor(response) { + super(); + const fdIn = writeFS(new Uint8Array(response)); + const fdOut = writeFS(new Uint8Array([])); + const res = assert.truthy(instance.exports["execute"])(fdIn, fdOut); + if (res !== 0) throw new Error(`WASM exited with code=${res}`); + const buffer = readFS(fdOut); + + this.header = [ + { name: "Mode", size: 3 }, + { name: "Timestamp", size: 6 }, + { name: "Size", size: 4 }, + { name: "Owner", size: 6 }, + { name: "Group", size: 6 }, + { name: "Object", size: 40 }, + ]; + this.rows = new TextDecoder().decode(buffer).trimRight().split("\n").map((line) => { + const row = line.split(","); + return { + "Object": row[0], + "Timestamp": row[1], + "Size": row[5], + "Mode": row[4], + "Owner": row[2], + "Group": row[3], + }; + }); + } + + getHeader() { + return this.header; + } + + getBody() { + return this.rows; + } + }; +} diff --git a/public/assets/pages/viewerpage/application_table/loader_symbol.wasm b/public/assets/pages/viewerpage/application_table/loader_symbol.wasm new file mode 100755 index 0000000000000000000000000000000000000000..058618fe56a5fd830e73d313e7260eb35006917a GIT binary patch literal 15659 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 literal 0 HcmV?d00001 diff --git a/public/assets/pages/viewerpage/mimetype.js b/public/assets/pages/viewerpage/mimetype.js index e8e66cf3..37f9757b 100644 --- a/public/assets/pages/viewerpage/mimetype.js +++ b/public/assets/pages/viewerpage/mimetype.js @@ -34,7 +34,7 @@ export function opener(file = "", mimes) { return ["3d", { mime }]; } else if (mime === "application/x-url") { return ["url", { mime }]; - } else if (["application/dbf"].indexOf(mime) !== -1) { + } else if (["application/dbf", "application/x-archive"].indexOf(mime) !== -1) { return ["table", { mime }]; } else if (type === "application" && mime !== "application/text") { return ["download", { mime }]; diff --git a/public/package.json b/public/package.json index 4248f521..9056bb4e 100644 --- a/public/package.json +++ b/public/package.json @@ -130,6 +130,9 @@ ], "new-cap": [ "off" + ], + "accessor-pairs": [ + "off" ] } },