implement remaining features of the server page (#9)

* implement remaining features of the server page

* fix fm init

* ?
This commit is contained in:
UUBulb
2024-11-21 22:20:14 +08:00
committed by GitHub
parent be2f5a519f
commit 8248d6c910
18 changed files with 994 additions and 32 deletions
+71
View File
@@ -0,0 +1,71 @@
let receivedLength = 0;
let expectedLength = 0;
let root: FileSystemDirectoryHandle;
let draftHandle: FileSystemFileHandle;
let accessHandle: FileSystemSyncAccessHandle;
enum Operation {
WriteHeader = 1,
WriteChunks,
DeleteFiles,
};
onmessage = async function (event) {
try {
const { operation, arrayBuffer, fileName } = event.data;
switch (operation) {
case Operation.WriteHeader: {
const dataView = new DataView(arrayBuffer);
expectedLength = Number(dataView.getBigUint64(4, false));
receivedLength = 0;
// Create a new temporary file
root = await navigator.storage.getDirectory();
draftHandle = await root.getFileHandle(fileName, { create: true });
accessHandle = await draftHandle.createSyncAccessHandle();
// Inform that file handle is created
const dataChunk = arrayBuffer.slice(12);
receivedLength += dataChunk.byteLength;
accessHandle.write(dataChunk, { at: 0 });
const progress = 'got handle';
postMessage({ type: 1, progress: progress });
break;
}
case Operation.WriteChunks: {
if (!accessHandle) {
throw new Error('accessHandle is undefined');
}
const dataChunk = arrayBuffer;
accessHandle.write(dataChunk, { at: receivedLength });
receivedLength += dataChunk.byteLength;
if (receivedLength === expectedLength) {
accessHandle.flush();
accessHandle.close();
const fileBlob = await draftHandle.getFile();
const blob = new Blob([fileBlob], { type: 'application/octet-stream' });
postMessage({ type: 2, blob: blob, fileName: fileName });
}
break;
}
case Operation.DeleteFiles: {
for await (const [name, handle] of root.entries()) {
if (handle.kind === 'file') {
await root.removeEntry(name);
} else if (handle.kind === 'directory') {
await root.removeEntry(name, { recursive: true });
}
}
break;
}
}
} catch (error) {
if (error instanceof Error)
postMessage({ type: 0, error: error.message });
}
};
+67
View File
@@ -1,6 +1,8 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
import { z } from "zod"
import { FMEntry, FMOpcode } from "@/types"
import FMWorker from "./fm?worker"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
@@ -46,3 +48,68 @@ export const conv = {
export const sleep = (ms: number) => {
return new Promise(resolve => setTimeout(resolve, ms));
};
export const fm = {
parseFMList: async (buf: ArrayBufferLike) => {
const dataView = new DataView(buf);
let offset = 4; // Identifier: 4 bytes (NZFN), not needed here
const pathLength = dataView.getUint32(offset);
offset += 4; // File Path Length: 4 bytes
const pathBuf = new Uint8Array(buf, offset, pathLength);
const path = new TextDecoder('utf-8').decode(pathBuf);
offset += pathLength; // Path: N bytes
const fmList: FMEntry[] = [];
while (offset < dataView.byteLength) {
const fileType = dataView.getUint8(offset);
offset += 1; // File Type: 1 byte
const nameLength = dataView.getUint8(offset);
offset += 1; // File Name Length: 1 byte
const nameBuf = new Uint8Array(buf, offset, nameLength);
const name = new TextDecoder('utf-8').decode(nameBuf);
offset += nameLength; // File Name: N bytes
fmList.push({
type: fileType,
name: name,
})
}
return { path, fmList };
},
buildUploadHeader: ({ path, file }: { path: string, file: File }) => {
const filePath = `${path}/${file.name}`;
// Build header (opcode + file size + path)
const filePathBytes = new TextEncoder().encode(filePath);
const header = new ArrayBuffer(1 + 8 + filePathBytes.length);
const headerView = new DataView(header);
headerView.setUint8(0, FMOpcode.Upload);
headerView.setBigUint64(1, BigInt(file.size), false);
new Uint8Array(header, 9).set(filePathBytes);
return header;
},
readFileAsArrayBuffer: async (blob: Blob): Promise<string | ArrayBuffer | null> => {
const reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsArrayBuffer(blob);
});
},
}
export const fmWorker = new FMWorker();
export function formatPath(path: string) {
return path.replace(/\/{2,}/g, '/');
}