mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-18 09:04:19 +01:00
opfs for sync in one commit!
This commit is contained in:
239
bindings/javascript/packages/browser-common/index.ts
Normal file
239
bindings/javascript/packages/browser-common/index.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
function getUint8ArrayFromMemory(memory: WebAssembly.Memory, ptr: number, len: number): Uint8Array {
|
||||
ptr = ptr >>> 0;
|
||||
return new Uint8Array(memory.buffer).subarray(ptr, ptr + len);
|
||||
}
|
||||
|
||||
function getStringFromMemory(memory: WebAssembly.Memory, ptr: number, len: number): string {
|
||||
const shared = getUint8ArrayFromMemory(memory, ptr, len);
|
||||
const copy = new Uint8Array(shared.length);
|
||||
copy.set(shared);
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
return decoder.decode(copy);
|
||||
}
|
||||
|
||||
interface BrowserImports {
|
||||
is_web_worker(): boolean;
|
||||
lookup_file(ptr: number, len: number): number;
|
||||
read(handle: number, ptr: number, len: number, offset: number): number;
|
||||
write(handle: number, ptr: number, len: number, offset: number): number;
|
||||
sync(handle: number): number;
|
||||
truncate(handle: number, len: number): number;
|
||||
size(handle: number): number;
|
||||
}
|
||||
|
||||
function panic(name): never {
|
||||
throw new Error(`method ${name} must be invoked only from the main thread`);
|
||||
}
|
||||
|
||||
const MainDummyImports: BrowserImports = {
|
||||
is_web_worker: function (): boolean {
|
||||
return false;
|
||||
},
|
||||
lookup_file: function (ptr: number, len: number): number {
|
||||
panic("lookup_file")
|
||||
},
|
||||
read: function (handle: number, ptr: number, len: number, offset: number): number {
|
||||
panic("read")
|
||||
},
|
||||
write: function (handle: number, ptr: number, len: number, offset: number): number {
|
||||
panic("write")
|
||||
},
|
||||
sync: function (handle: number): number {
|
||||
panic("sync")
|
||||
},
|
||||
truncate: function (handle: number, len: number): number {
|
||||
panic("truncate")
|
||||
},
|
||||
size: function (handle: number): number {
|
||||
panic("size")
|
||||
}
|
||||
};
|
||||
|
||||
function workerImports(opfs: OpfsDirectory, memory: WebAssembly.Memory): BrowserImports {
|
||||
return {
|
||||
is_web_worker: function (): boolean {
|
||||
return true;
|
||||
},
|
||||
lookup_file: function (ptr: number, len: number): number {
|
||||
try {
|
||||
const handle = opfs.lookupFileHandle(getStringFromMemory(memory, ptr, len));
|
||||
return handle == null ? -404 : handle;
|
||||
} catch (e) {
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
read: function (handle: number, ptr: number, len: number, offset: number): number {
|
||||
try {
|
||||
return opfs.read(handle, getUint8ArrayFromMemory(memory, ptr, len), offset);
|
||||
} catch (e) {
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
write: function (handle: number, ptr: number, len: number, offset: number): number {
|
||||
try {
|
||||
return opfs.write(handle, getUint8ArrayFromMemory(memory, ptr, len), offset)
|
||||
} catch (e) {
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
sync: function (handle: number): number {
|
||||
try {
|
||||
opfs.sync(handle);
|
||||
return 0;
|
||||
} catch (e) {
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
truncate: function (handle: number, len: number): number {
|
||||
try {
|
||||
opfs.truncate(handle, len);
|
||||
return 0;
|
||||
} catch (e) {
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
size: function (handle: number): number {
|
||||
try {
|
||||
return opfs.size(handle);
|
||||
} catch (e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OpfsDirectory {
|
||||
fileByPath: Map<String, { handle: number, sync: FileSystemSyncAccessHandle }>;
|
||||
fileByHandle: Map<number, FileSystemSyncAccessHandle>;
|
||||
fileHandleNo: number;
|
||||
|
||||
constructor() {
|
||||
this.fileByPath = new Map();
|
||||
this.fileByHandle = new Map();
|
||||
this.fileHandleNo = 0;
|
||||
}
|
||||
|
||||
async registerFile(path: string) {
|
||||
if (this.fileByPath.has(path)) {
|
||||
return;
|
||||
}
|
||||
const opfsRoot = await navigator.storage.getDirectory();
|
||||
const opfsHandle = await opfsRoot.getFileHandle(path, { create: true });
|
||||
const opfsSync = await opfsHandle.createSyncAccessHandle();
|
||||
this.fileHandleNo += 1;
|
||||
this.fileByPath.set(path, { handle: this.fileHandleNo, sync: opfsSync });
|
||||
this.fileByHandle.set(this.fileHandleNo, opfsSync);
|
||||
}
|
||||
|
||||
async unregisterFile(path: string) {
|
||||
const file = this.fileByPath.get(path);
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
this.fileByPath.delete(path);
|
||||
this.fileByHandle.delete(file.handle);
|
||||
file.sync.close();
|
||||
}
|
||||
lookupFileHandle(path: string): number | null {
|
||||
try {
|
||||
const file = this.fileByPath.get(path);
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
return file.handle;
|
||||
} catch (e) {
|
||||
console.error('lookupFile', path, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
read(handle: number, buffer: Uint8Array, offset: number): number {
|
||||
try {
|
||||
const file = this.fileByHandle.get(handle);
|
||||
const result = file.read(buffer, { at: Number(offset) });
|
||||
return result;
|
||||
} catch (e) {
|
||||
console.error('read', handle, buffer.length, offset, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
write(handle: number, buffer: Uint8Array, offset: number): number {
|
||||
try {
|
||||
const file = this.fileByHandle.get(handle);
|
||||
const result = file.write(buffer, { at: Number(offset) });
|
||||
return result;
|
||||
} catch (e) {
|
||||
console.error('write', handle, buffer.length, offset, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
sync(handle: number) {
|
||||
try {
|
||||
const file = this.fileByHandle.get(handle);
|
||||
file.flush();
|
||||
} catch (e) {
|
||||
console.error('sync', handle, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
truncate(handle: number, size: number) {
|
||||
try {
|
||||
const file = this.fileByHandle.get(handle);
|
||||
const result = file.truncate(size);
|
||||
return result;
|
||||
} catch (e) {
|
||||
console.error('truncate', handle, size, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
size(handle: number): number {
|
||||
try {
|
||||
const file = this.fileByHandle.get(handle);
|
||||
const size = file.getSize()
|
||||
return size;
|
||||
} catch (e) {
|
||||
console.error('size', handle, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var workerRequestId = 0;
|
||||
function waitForWorkerResponse(worker: Worker, id: number): Promise<any> {
|
||||
let waitResolve, waitReject;
|
||||
const callback = msg => {
|
||||
if (msg.data.id == id) {
|
||||
if (msg.data.error != null) {
|
||||
waitReject(msg.data.error)
|
||||
} else {
|
||||
waitResolve()
|
||||
}
|
||||
cleanup();
|
||||
}
|
||||
};
|
||||
const cleanup = () => worker.removeEventListener("message", callback);
|
||||
|
||||
worker.addEventListener("message", callback);
|
||||
const result = new Promise((resolve, reject) => {
|
||||
waitResolve = resolve;
|
||||
waitReject = reject;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function registerFileAtWorker(worker: Worker, path: string): Promise<void> {
|
||||
workerRequestId += 1;
|
||||
const currentId = workerRequestId;
|
||||
const promise = waitForWorkerResponse(worker, currentId);
|
||||
worker.postMessage({ __turso__: "register", path: path, id: currentId });
|
||||
return promise;
|
||||
}
|
||||
|
||||
function unregisterFileAtWorker(worker: Worker, path: string): Promise<void> {
|
||||
workerRequestId += 1;
|
||||
const currentId = workerRequestId;
|
||||
const promise = waitForWorkerResponse(worker, currentId);
|
||||
worker.postMessage({ __turso__: "unregister", path: path, id: currentId });
|
||||
return promise;
|
||||
}
|
||||
|
||||
export { OpfsDirectory, workerImports, MainDummyImports, waitForWorkerResponse, registerFileAtWorker, unregisterFileAtWorker }
|
||||
Reference in New Issue
Block a user