209 lines
5.8 KiB
JavaScript
209 lines
5.8 KiB
JavaScript
|
import { getStr } from "../src/wasm.ts";
|
||
|
|
||
|
const DB_NAME = "sqlitevfs";
|
||
|
const LOADED_FILES = new Map();
|
||
|
const OPEN_FILES = new Map();
|
||
|
|
||
|
function nextRid() {
|
||
|
const rid = (nextRid?.LAST_RID ?? 0) + 1;
|
||
|
nextRid.LAST_RID = rid;
|
||
|
return rid;
|
||
|
}
|
||
|
|
||
|
function getOpenFile(rid) {
|
||
|
if (!OPEN_FILES.has(rid)) {
|
||
|
throw new Error(`Resource ID ${rid} does not exist.`);
|
||
|
}
|
||
|
return OPEN_FILES.get(rid);
|
||
|
}
|
||
|
|
||
|
const MIN_GROW_BYTES = 2048;
|
||
|
const MAX_GROW_BYTES = 65536;
|
||
|
|
||
|
class Buffer {
|
||
|
constructor(data) {
|
||
|
this._data = data ?? new Uint8Array();
|
||
|
this._size = this._data.length;
|
||
|
}
|
||
|
|
||
|
get size() {
|
||
|
return this._size;
|
||
|
}
|
||
|
|
||
|
read(offset, buffer) {
|
||
|
if (offset >= this._size) return 0;
|
||
|
const toCopy = this._data.subarray(
|
||
|
offset,
|
||
|
Math.min(this._size, offset + buffer.length),
|
||
|
);
|
||
|
buffer.set(toCopy);
|
||
|
return toCopy.length;
|
||
|
}
|
||
|
|
||
|
reserve(capacity) {
|
||
|
if (this._data.length >= capacity) return;
|
||
|
const neededBytes = capacity - this._data.length;
|
||
|
const growBy = Math.min(
|
||
|
MAX_GROW_BYTES,
|
||
|
Math.max(MIN_GROW_BYTES, this._data.length),
|
||
|
);
|
||
|
const newArray = new Uint8Array(
|
||
|
this._data.length + Math.max(growBy, neededBytes),
|
||
|
);
|
||
|
newArray.set(this._data);
|
||
|
this._data = newArray;
|
||
|
}
|
||
|
|
||
|
write(offset, buffer) {
|
||
|
this.reserve(offset + buffer.length);
|
||
|
this._data.set(buffer, offset);
|
||
|
this._size = Math.max(this._size, offset + buffer.length);
|
||
|
return buffer.length;
|
||
|
}
|
||
|
|
||
|
truncate(size) {
|
||
|
this._size = size;
|
||
|
}
|
||
|
|
||
|
toUint8Array() {
|
||
|
return this._data.subarray(0, this._size);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const indexedDB = window.indexedDB || window.mozIndexedDB ||
|
||
|
window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
|
||
|
|
||
|
// Web browser indexedDB database
|
||
|
const database = new Promise((resolve, reject) => {
|
||
|
const request = indexedDB.open(DB_NAME, 1);
|
||
|
request.onupgradeneeded = () =>
|
||
|
request.result.createObjectStore("files", { keyPath: "name" });
|
||
|
request.onsuccess = () => resolve(request.result);
|
||
|
request.onerror = () => reject(request.error);
|
||
|
});
|
||
|
|
||
|
export async function loadFile(fileName) {
|
||
|
const db = await database;
|
||
|
const file = await new Promise((resolve, reject) => {
|
||
|
const store = db.transaction("files", "readonly").objectStore("files");
|
||
|
const request = store.get(fileName);
|
||
|
request.onsuccess = () => resolve(request.result);
|
||
|
request.onerror = () => reject(request.error);
|
||
|
});
|
||
|
if (file != null && !LOADED_FILES.has(fileName)) {
|
||
|
const buffer = new Buffer(file.data);
|
||
|
LOADED_FILES.set(fileName, buffer);
|
||
|
return buffer;
|
||
|
} else if (LOADED_FILES.has(fileName)) {
|
||
|
return LOADED_FILES.get(fileName);
|
||
|
} else {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function syncFile(fileName, data) {
|
||
|
const db = await database;
|
||
|
await new Promise((resolve, reject) => {
|
||
|
const store = db.transaction("files", "readwrite").objectStore("files");
|
||
|
const request = store.put({ name: fileName, data });
|
||
|
request.onsuccess = () => resolve();
|
||
|
request.onerror = () => reject(request.error);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
async function deleteFile(fileName) {
|
||
|
const db = await database;
|
||
|
await new Promise((resolve, reject) => {
|
||
|
const store = db.transaction("files", "readwrite").objectStore("files");
|
||
|
const request = store.delete(fileName);
|
||
|
request.onsuccess = () => resolve();
|
||
|
request.onerror = () => reject(request.error);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
export async function writeFile(fileName, data) {
|
||
|
await syncFile(fileName, data);
|
||
|
if (LOADED_FILES.has(fileName)) {
|
||
|
const buffer = LOADED_FILES.get(fileName);
|
||
|
buffer.truncate(0);
|
||
|
buffer.write(0, data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Closure to return an environment that links
|
||
|
// the current wasm context. This is a modified
|
||
|
// version, suitable for use within browsers.
|
||
|
export default function env(inst) {
|
||
|
const env = {
|
||
|
js_print: (str_ptr) => {
|
||
|
const text = getStr(inst.exports, str_ptr);
|
||
|
console.log(text[text.length - 1] === "\n" ? text.slice(0, -1) : text);
|
||
|
},
|
||
|
js_open: (path_ptr, mode, _flags) => {
|
||
|
if (mode === 1 /* temp file */) {
|
||
|
const rid = nextRid();
|
||
|
OPEN_FILES.set(rid, { path: null, buffer: new Buffer() });
|
||
|
return rid;
|
||
|
} else if (mode === 0 /* regular file */) {
|
||
|
const path = getStr(inst.exports, path_ptr);
|
||
|
const buffer = LOADED_FILES.get(path) ?? new Buffer();
|
||
|
if (!LOADED_FILES.has(path)) LOADED_FILES.set(path, buffer);
|
||
|
const rid = nextRid();
|
||
|
OPEN_FILES.set(rid, { path, buffer });
|
||
|
return rid;
|
||
|
}
|
||
|
},
|
||
|
js_close: (rid) => {
|
||
|
OPEN_FILES.delete(rid);
|
||
|
},
|
||
|
js_delete: (path_ptr) => {
|
||
|
const path = getStr(inst.exports, path_ptr);
|
||
|
LOADED_FILES.delete(path);
|
||
|
deleteFile(path);
|
||
|
},
|
||
|
js_read: (rid, buffer_ptr, offset, amount) => {
|
||
|
const buffer = new Uint8Array(
|
||
|
inst.exports.memory.buffer,
|
||
|
buffer_ptr,
|
||
|
amount,
|
||
|
);
|
||
|
const file = getOpenFile(rid);
|
||
|
return file.buffer.read(offset, buffer);
|
||
|
},
|
||
|
js_write: (rid, buffer_ptr, offset, amount) => {
|
||
|
const buffer = new Uint8Array(
|
||
|
inst.exports.memory.buffer,
|
||
|
buffer_ptr,
|
||
|
amount,
|
||
|
);
|
||
|
const file = getOpenFile(rid);
|
||
|
return file.buffer.write(offset, buffer);
|
||
|
},
|
||
|
js_truncate: (rid, size) => {
|
||
|
getOpenFile(rid).buffer.truncate(size);
|
||
|
},
|
||
|
js_sync: (rid) => {
|
||
|
const file = getOpenFile(rid);
|
||
|
if (file.path != null) syncFile(file.path, file.buffer.toUint8Array());
|
||
|
},
|
||
|
js_size: (rid) => {
|
||
|
return getOpenFile(rid).buffer.size;
|
||
|
},
|
||
|
js_lock: (_rid, _exclusive) => {},
|
||
|
js_unlock: (_rid) => {},
|
||
|
js_time: () => {
|
||
|
return Date.now();
|
||
|
},
|
||
|
js_timezone: () => {
|
||
|
return (new Date()).getTimezoneOffset();
|
||
|
},
|
||
|
js_exists: (path_ptr) => {
|
||
|
const path = getStr(inst.exports, path_ptr);
|
||
|
return LOADED_FILES.has(path) ? 1 : 0;
|
||
|
},
|
||
|
js_access: (_path_ptr) => 1,
|
||
|
};
|
||
|
|
||
|
return { env };
|
||
|
}
|