mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-09-19 17:50:13 +00:00
implement remaining features of the server page (#9)
* implement remaining features of the server page * fix fm init * ?
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
};
|
||||
@@ -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, '/');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user