Skip to content

Commit

Permalink
Backup and restore FS
Browse files Browse the repository at this point in the history
  • Loading branch information
pipe01 committed Jun 26, 2024
1 parent b2de76a commit 4414aa6
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 58 deletions.
15 changes: 10 additions & 5 deletions frontend/wasm/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type FileInfo = {
type: "file" | "dir";
};

export type MessageToWorkerType =
export type MessageToWorkerType = { messageId?: number } & (
{ type: "setCanvas", data: OffscreenCanvas } |
{ type: "start", data: void } |
{ type: "stop", data: void } |
Expand All @@ -16,17 +16,22 @@ export type MessageToWorkerType =
{ type: "setProgram", data: ArrayBuffer } |
{ type: "readDir", data: string } |
{ type: "readFile", data: string } |
{ type: "createDir", data: string };
{ type: "createDir", data: string } |
{ type: "backupFS", data: void } |
{ type: "restoreFS", data: ArrayBuffer }
);

export type MessageFromWorkerType =
export type MessageFromWorkerType = { replyToId?: number } & (
{ type: "ready", data: void } |
{ type: "done", data: void } |
{ type: "error", data: { message: string | undefined, stack: string | undefined, string: string | undefined } } |
{ type: "dirFiles", data: FileInfo[] } |
{ type: "fileData", data: { path: string, data: ArrayBuffer } } |
{ type: "createdDir", data: string } |
{ type: "running", data: boolean } |
{ type: "rttFound", data: void } |
{ type: "rttData", data: string } |
{ type: "lcdSleeping", data: boolean } |
{ type: "cpuSleeping", data: boolean } |
{ type: "performance", data: { loopTime: number, ips: number, totalSRAM: number } };
{ type: "performance", data: { loopTime: number, ips: number, totalSRAM: number } } |
{ type: "backupData", data: ArrayBuffer }
);
95 changes: 51 additions & 44 deletions frontend/wasm/src/components/FileBrowser.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<template lang="pug">
div
button.btn.btn-primary(@click="refresh" :disabled="!isInitialized") Refresh
button.btn.btn-secondary.ms-2(@click="createFolder") Create folder
button.btn.btn-info(@click="refresh" :disabled="!isInitialized") Refresh
button.btn.btn-primary.ms-2(@click="createFolder") Create folder
button.btn.btn-secondary.ms-2(@click="createBackup") Backup FS
button.btn.btn-secondary.ms-2(@click="restoreBackup") Restore FS

.mt-2.fs-5 /{{ currentPath }}

Expand All @@ -15,8 +17,8 @@ div
<script lang="ts" setup>
import { ref } from 'vue';
import type { FileInfo, MessageFromWorkerType } from '@/common';
import { downloadURL, joinLFSPaths, sendMessage } from '@/utils';
import type { FileInfo } from '@/common';
import { downloadBuffer, joinLFSPaths, sendMessageAndWait } from '@/utils';
const props = defineProps<{
worker: Worker,
Expand All @@ -32,21 +34,16 @@ const files = ref<FileInfo[]>([]);
const currentPath = ref("");
function refresh() {
const listener = (event: MessageEvent) => {
const { type, data } = event.data as MessageFromWorkerType;
async function refresh(emitLoad = true) {
if (emitLoad)
emit("loadStart");
if (type === "dirFiles") {
files.value = data;
props.worker.removeEventListener("message", listener);
emit("loadEnd");
}
};
const data = await sendMessageAndWait(props.worker, "readDir", currentPath.value, "dirFiles")
emit("loadStart");
if (emitLoad)
emit("loadEnd");
props.worker.addEventListener("message", listener);
sendMessage(props.worker, "readDir", currentPath.value);
files.value = data;
}
function navigate(dir: FileInfo) {
Expand All @@ -68,48 +65,58 @@ function navigate(dir: FileInfo) {
refresh();
}
function openFile(file: FileInfo) {
const listener = (event: MessageEvent) => {
const { type, data } = event.data as MessageFromWorkerType;
async function openFile(file: FileInfo) {
emit("loadStart");
const { data } = await sendMessageAndWait(props.worker, "readFile", file.fullPath, "fileData")
if (type == "fileData" && data.path == file.fullPath) {
props.worker.removeEventListener("message", listener);
emit("loadEnd");
emit("loadEnd");
const blob = new Blob([data.data], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
downloadBuffer(data, file.name);
}
async function createFolder() {
const folderName = prompt("Enter folder name");
if (folderName) {
const path = joinLFSPaths(currentPath.value, folderName);
downloadURL(url, file.name);
emit("loadStart");
setTimeout(() => URL.revokeObjectURL(url), 1000);
}
};
await sendMessageAndWait(props.worker, "createDir", path);
refresh(false);
emit("loadEnd");
}
}
async function createBackup() {
emit("loadStart");
props.worker.addEventListener("message", listener);
sendMessage(props.worker, "readFile", file.fullPath);
const data = await sendMessageAndWait(props.worker, "backupFS", undefined, "backupData");
emit("loadEnd");
downloadBuffer(data, "backup.bin");
}
function createFolder() {
const folderName = prompt("Enter folder name");
async function restoreBackup() {
const input = document.createElement('input');
input.type = 'file';
input.onchange = () => {
const reader = new FileReader();
reader.onload = async () => {
emit("loadStart");
if (folderName) {
const path = joinLFSPaths(currentPath.value, folderName);
await sendMessageAndWait(props.worker, "restoreFS", reader.result as ArrayBuffer, undefined);
const listener = (event: MessageEvent) => {
const { type, data } = event.data as MessageFromWorkerType;
refresh(false);
if (type == "createdDir" && data == path) {
props.worker.removeEventListener("message", listener);
emit("loadEnd");
refresh();
}
emit("loadEnd");
};
props.worker.addEventListener("message", listener);
sendMessage(props.worker, "createDir", path);
reader.readAsArrayBuffer(input.files![0]);
}
input.click();
}
</script>

Expand Down
41 changes: 38 additions & 3 deletions frontend/wasm/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { customRef, onUnmounted, type Ref } from "vue";
import type { MessageToWorkerType } from "./common";
import type { MessageFromWorkerType, MessageToWorkerType } from "./common";

export function useAverage(interval: number): Ref<number> {
let sum = 0;
Expand Down Expand Up @@ -33,10 +33,36 @@ export function useAverage(interval: number): Ref<number> {
}

export function sendMessage<Type extends MessageToWorkerType["type"]>(worker: Worker, type: Type, data: Extract<MessageToWorkerType, { type: Type }>["data"], transfer?: Transferable[]) {
const messageId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);

if (transfer)
worker.postMessage({ type, data }, transfer);
worker.postMessage({ type, data, messageId }, transfer);
else
worker.postMessage({ type, data });
worker.postMessage({ type, data, messageId });

return messageId;
}

// I'm sorry
export function sendMessageAndWait<Type extends MessageToWorkerType["type"], ReplyType extends MessageFromWorkerType["type"]>(
worker: Worker, type: Type,
data: Extract<MessageToWorkerType, { type: Type }>["data"], replyType: ReplyType = "done" as ReplyType, transfer?: Transferable[]):
Promise<Extract<MessageFromWorkerType, { type: ReplyType }>["data"]> {
return new Promise(resolve => {
let messageId: number;

const listener = (e: MessageEvent) => {
const message = e.data as MessageFromWorkerType;

if (message.type == replyType && message.replyToId == messageId) {
worker.removeEventListener("message", listener);
resolve(message.data as any);
}
};

worker.addEventListener("message", listener);
messageId = sendMessage(worker, type, data as any, transfer);
});
}

export function downloadURL(url: string, filename: string) {
Expand All @@ -48,6 +74,15 @@ export function downloadURL(url: string, filename: string) {
a.remove();
}

export function downloadBuffer(data: BlobPart, filename: string) {
const blob = new Blob([data], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);

downloadURL(url, filename);

setTimeout(() => URL.revokeObjectURL(url), 1000);
}

export function joinLFSPaths(...paths: string[]) {
return paths.join("/").replace(/^\//, "").replace(/\/+/g, "/");
}
41 changes: 35 additions & 6 deletions frontend/wasm/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { joinLFSPaths } from "./utils.js";

const iterations = 700000;

const fsStart = 0x0B4000;
const fsEnd = 0x400000;

type PromiseResult<T> = T extends Promise<infer U> ? U : T;

type Module = PromiseResult<ReturnType<typeof createModule>>;
Expand All @@ -19,8 +22,8 @@ function pointerAdd(ptr: Pointer, offset: number) {
return numberToPointer(pointerToNumber(ptr) + offset);
}

function sendMessage<Type extends MessageFromWorkerType["type"]>(type: Type, data: Extract<MessageFromWorkerType, { type: Type }>["data"]) {
postMessage({ type, data });
function sendMessage<Type extends MessageFromWorkerType["type"]>(type: Type, data: Extract<MessageFromWorkerType, { type: Type }>["data"], replyTo?: number) {
postMessage({ type, data, replyToId: replyTo });
}

class Emulator {
Expand Down Expand Up @@ -188,7 +191,7 @@ class Emulator {

private useLFS<T>(fn: (lfs: LFS) => T) {
const bufferPtr = this.Module._spinorflash_get_buffer(this.spiFlash);
const lfs = this.Module._lfs_init(pointerAdd(bufferPtr, 0x0B4000), 0x400000 - 0x0B4000);
const lfs = this.Module._lfs_init(pointerAdd(bufferPtr, fsStart), fsEnd - fsStart);

let ret: T;
try {
Expand Down Expand Up @@ -293,6 +296,20 @@ class Emulator {
this.Module._free(pointerToNumber(pathBytes));
});
}

backupFS() {
const bufferPtr = this.Module._spinorflash_get_buffer(this.spiFlash);
const data = new Uint8Array(this.Module.HEAPU8.buffer, pointerToNumber(bufferPtr) + fsStart, fsEnd - fsStart);

return new Uint8Array(data);
}

restoreFS(backup: ArrayBuffer) {
const bufferPtr = this.Module._spinorflash_get_buffer(this.spiFlash);
const data = new Uint8Array(this.Module.HEAPU8.buffer, pointerToNumber(bufferPtr) + fsStart, fsEnd - fsStart);

data.set(new Uint8Array(backup));
}
};

let emulator: Emulator | null = null;
Expand Down Expand Up @@ -369,18 +386,30 @@ function handleMessage(msg: MessageToWorkerType) {

case "readDir":
if (emulator)
sendMessage("dirFiles", emulator.readDir(data));
sendMessage("dirFiles", emulator.readDir(data), msg.messageId);
break;

case "readFile":
if (emulator)
sendMessage("fileData", { path: data, data: emulator.readFile(data) });
sendMessage("fileData", { path: data, data: emulator.readFile(data) }, msg.messageId);
break;

case "createDir":
if (emulator) {
emulator.createDir(data);
sendMessage("createdDir", data);
sendMessage("done", undefined, msg.messageId);
}
break;

case "backupFS":
if (emulator)
sendMessage("backupData", emulator.backupFS().buffer, msg.messageId);
break;

case "restoreFS":
if (emulator) {
emulator.restoreFS(data);
sendMessage("done", undefined, msg.messageId);
}
break;
}
Expand Down

0 comments on commit 4414aa6

Please sign in to comment.