1
0
silverbullet/plugos/sqlite/deno-sqlite/browser/vfs.js

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 };
}