File upload. Yeah
This commit is contained in:
@@ -6,6 +6,7 @@ import { crc32 } from './crc32';
|
||||
import { get } from 'svelte/store';
|
||||
import { saveLocalFile } from '../db';
|
||||
import { refreshLocal } from '../sync';
|
||||
import { file } from 'astro:schema';
|
||||
|
||||
let lastUiUpdate = 0;
|
||||
let currentFileCrc32 = 0;
|
||||
@@ -17,7 +18,7 @@ let fileChunks: Uint8Array[] = [];
|
||||
let lsTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
let lsResolve: ((data: any[]) => void) | null = null;
|
||||
let lsReject: ((error: Error) => void) | null = null;
|
||||
let fileGetResolve: ((success: boolean) => void) | null = null;
|
||||
let fileGetResolve: ((result: { success: boolean, blob?: Blob }) => void) | null = null;
|
||||
let fileGetReject: ((error: Error) => void) | null = null;
|
||||
|
||||
export function showErrorToast(errorCode: number) {
|
||||
@@ -87,7 +88,6 @@ export function parseIncomingFrame(view: DataView, sender: FrameSender) {
|
||||
case FRAME.LS_END:
|
||||
if (lsTimeout) clearTimeout(lsTimeout);
|
||||
const total = view.getUint32(3, true);
|
||||
console.debug(`LS Stream beendet. Erwartete Einträge: ${total}, empfangen: ${lsBuffer.length}`, lsBuffer);
|
||||
if (total !== lsBuffer.length) {
|
||||
console.warn(`LS Stream: Erwartete Anzahl Einträge laut Header: ${total}, tatsächlich empfangen: ${lsBuffer.length}`);
|
||||
addToast(`Warnung: LS Stream erwartete ${total} Einträge, aber nur ${lsBuffer.length} empfangen.`, 'warning');
|
||||
@@ -98,20 +98,22 @@ export function parseIncomingFrame(view: DataView, sender: FrameSender) {
|
||||
}
|
||||
break;
|
||||
|
||||
case FRAME.FILE_START:
|
||||
case FRAME.FILE_START:
|
||||
currentFileCrc32 = 0;
|
||||
const totalBytes = view.getUint32(3, true);
|
||||
const nowStart = performance.now();
|
||||
fileChunks = [];
|
||||
|
||||
transferStats.update(s => ({
|
||||
...s,
|
||||
bytesTotal: totalBytes,
|
||||
bytesDone: 0,
|
||||
currentFileName: s.pendingFileName || s.currentFileName,
|
||||
fileStartTime: nowStart,
|
||||
bulkStartTime: s.bulkStartTime === 0 ? nowStart : s.bulkStartTime
|
||||
}));
|
||||
if (fileTransfer.mode === 'file') {
|
||||
transferStats.update(s => ({
|
||||
...s,
|
||||
bytesTotal: totalBytes,
|
||||
bytesDone: 0,
|
||||
currentFileName: s.pendingFileName || s.currentFileName,
|
||||
fileStartTime: nowStart,
|
||||
bulkStartTime: s.bulkStartTime === 0 ? nowStart : s.bulkStartTime
|
||||
}));
|
||||
}
|
||||
|
||||
// Parser-interne Metriken (Watchdog etc.)
|
||||
fileTransfer.totalBytes = totalBytes;
|
||||
@@ -120,8 +122,6 @@ case FRAME.FILE_START:
|
||||
fileTransfer.startTime = nowStart;
|
||||
lastUiUpdate = 0;
|
||||
|
||||
console.log(`[FILE_GET] Stream gestartet. Erwartete Größe: ${fileTransfer.totalBytes} Bytes.`);
|
||||
|
||||
fileTransfer.metricsTimer = setInterval(() => {
|
||||
if (!fileTransfer.active) return;
|
||||
|
||||
@@ -166,17 +166,18 @@ case FRAME.FILE_START:
|
||||
fileTransfer.receivedBytes += payloadLength;
|
||||
fileTransfer.credits--;
|
||||
|
||||
const nowChunk = performance.now();
|
||||
if (nowChunk - lastUiUpdate > SETTINGS.ui.transferUpdateIntervalMs) {
|
||||
const delta = fileTransfer.receivedBytes - previousReceived; // Das Delta seit dem letzten Paket
|
||||
if (fileTransfer.mode === 'file') {
|
||||
const nowChunk = performance.now();
|
||||
if (nowChunk - lastUiUpdate > SETTINGS.ui.transferUpdateIntervalMs) {
|
||||
const delta = fileTransfer.receivedBytes - previousReceived;
|
||||
|
||||
transferStats.update(s => ({
|
||||
...s,
|
||||
bytesDone: fileTransfer.receivedBytes,
|
||||
overallDone: s.overallDone + (fileTransfer.receivedBytes - s.bytesDone)
|
||||
}));
|
||||
console.log("[FILE_GET] Fortschritt: " + ((fileTransfer.receivedBytes / fileTransfer.totalBytes) * 100).toFixed(2) + "%");
|
||||
lastUiUpdate = nowChunk;
|
||||
transferStats.update(s => ({
|
||||
...s,
|
||||
bytesDone: fileTransfer.receivedBytes,
|
||||
overallDone: s.overallDone + (fileTransfer.receivedBytes - s.bytesDone)
|
||||
}));
|
||||
lastUiUpdate = nowChunk;
|
||||
}
|
||||
}
|
||||
|
||||
if (fileTransfer.credits <= 64) {
|
||||
@@ -186,53 +187,74 @@ case FRAME.FILE_START:
|
||||
break;
|
||||
|
||||
case FRAME.FILE_END:
|
||||
transferStats.update(s => {
|
||||
return {
|
||||
if (fileTransfer.mode === 'file') {
|
||||
transferStats.update(s => ({
|
||||
...s,
|
||||
bytesDone: s.bytesTotal,
|
||||
};
|
||||
});
|
||||
// Watchdog stoppen
|
||||
}));
|
||||
}
|
||||
if (fileTransfer.metricsTimer) clearInterval(fileTransfer.metricsTimer);
|
||||
fileTransfer.active = false;
|
||||
|
||||
const buzzerCrc32 = view.getUint32(3, true);
|
||||
|
||||
console.log(`[CRC] Lokal: 0x${currentFileCrc32.toString(16).toUpperCase()}`);
|
||||
console.log(`[CRC] Buzzer: 0x${buzzerCrc32.toString(16).toUpperCase()}`);
|
||||
const totalElapsed = (performance.now() - fileTransfer.startTime) / 1000;
|
||||
const avgSpeed = (fileTransfer.receivedBytes / 1024) / totalElapsed;
|
||||
|
||||
console.log(`[FILE_GET] Stream beendet.`);
|
||||
console.log(`[FILE_GET] Empfangen: ${fileTransfer.receivedBytes} Bytes in ${totalElapsed.toFixed(2)}s.`);
|
||||
console.log(`[FILE_GET] Durchschnitt: ${avgSpeed.toFixed(2)} KB/s`);
|
||||
|
||||
if (currentFileCrc32 === buzzerCrc32) {
|
||||
console.log("%c[CRC] Match! Datei ist integer.", "color: green; font-weight: bold;");
|
||||
const fileBlob = new Blob(fileChunks, { type: 'audio/wav' });
|
||||
const fileName = get(transferStats).currentFileName;
|
||||
saveLocalFile(fileName, fileBlob, fileTransfer.totalBytes)
|
||||
.then(() => {
|
||||
console.log(`Datei ${fileName} erfolgreich lokal gespeichert.`);
|
||||
refreshLocal();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Datenbankfehler:", err);
|
||||
addToast(`Speichern von ${fileName} fehlgeschlagen.`, 'error');
|
||||
});
|
||||
const fileBlob = new Blob(fileChunks, { type: 'application/octet-stream' });
|
||||
|
||||
if (fileTransfer.mode === 'file') {
|
||||
const fileName = get(transferStats).currentFileName;
|
||||
saveLocalFile(fileName, fileBlob, fileTransfer.totalBytes)
|
||||
.then(() => {
|
||||
refreshLocal();
|
||||
if (fileGetResolve) {
|
||||
fileGetResolve({ success: true });
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Datenbankfehler:", err);
|
||||
addToast(`Speichern von ${fileName} fehlgeschlagen.`, 'error');
|
||||
if (fileGetReject) fileGetReject(err);
|
||||
})
|
||||
.finally(() => {
|
||||
fileGetResolve = null;
|
||||
fileGetReject = null;
|
||||
});
|
||||
} else {
|
||||
// TAGS Modus: Blob direkt zurückgeben, nichts speichern
|
||||
if (fileGetResolve) fileGetResolve({ success: true, blob: fileBlob });
|
||||
fileGetResolve = null;
|
||||
fileGetReject = null;
|
||||
}
|
||||
} else {
|
||||
console.error("[CRC] Mismatch! Datei beschädigt.");
|
||||
addToast("CRC Fehler: Datei wurde fehlerhaft übertragen.", "error");
|
||||
if (fileGetReject) fileGetReject(new Error("CRC Mismatch"));
|
||||
break;
|
||||
}
|
||||
|
||||
if (fileGetResolve) {
|
||||
fileGetResolve(true);
|
||||
fileGetResolve = null;
|
||||
fileGetReject = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case FRAME.ACK:
|
||||
if (uploadState.active && payloadLength >= 2) {
|
||||
const creditsAdded = view.getUint16(3, true);
|
||||
uploadState.credits += creditsAdded;
|
||||
if (uploadState.onCreditsAdded) {
|
||||
uploadState.onCreditsAdded();
|
||||
}
|
||||
}
|
||||
|
||||
case FRAME.SUCCESS:
|
||||
if (payloadLength >= 1) {
|
||||
const successDataType = view.getUint8(3);
|
||||
if (uploadState.active && successDataType === DATA.FILE_PUT || successDataType === DATA.TAGS_PUT) {
|
||||
if (uploadState.onSuccess) uploadState.onSuccess();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FRAME.ERROR:
|
||||
const errorCode = view.getUint16(3, true);
|
||||
console.error(`Received error frame with code: 0x${errorCode.toString(16)}`);
|
||||
@@ -249,6 +271,9 @@ case FRAME.FILE_START:
|
||||
fileGetResolve = null;
|
||||
fileGetReject = null;
|
||||
}
|
||||
if (uploadState.active && uploadState.onError) {
|
||||
uploadState.onError(new Error(`Buzzer Error 0x${errorCode.toString(16)}`));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -304,7 +329,6 @@ async function sendCredits(count: number, send: FrameSender) {
|
||||
const buffer = new ArrayBuffer(5);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
console.debug(`Sende ${count} Credits für Stream...`);
|
||||
view.setUint8(0, FRAME.ACK);
|
||||
view.setUint16(1, 2, true);
|
||||
view.setUint16(3, count, true);
|
||||
@@ -332,6 +356,7 @@ export function setLsResolver(resolve: (data: any[]) => void, reject: (error: Er
|
||||
|
||||
const fileTransfer = {
|
||||
active: false,
|
||||
mode: 'file' as 'file' | 'tags',
|
||||
startTime: 0,
|
||||
totalBytes: 0,
|
||||
receivedBytes: 0,
|
||||
@@ -341,9 +366,22 @@ const fileTransfer = {
|
||||
metricsTimer: null as ReturnType<typeof setInterval> | null
|
||||
};
|
||||
|
||||
export function setFileGetResolver(resolve: (success: boolean) => void, reject: (error: Error) => void) {
|
||||
export const uploadState = {
|
||||
active: false,
|
||||
credits: 0,
|
||||
onCreditsAdded: null as (() => void) | null,
|
||||
onSuccess: null as (() => void) | null,
|
||||
onError: null as ((err: Error) => void) | null,
|
||||
};
|
||||
|
||||
export function setFileGetResolver(
|
||||
resolve: (result: { success: boolean, blob?: Blob }) => void,
|
||||
reject: (error: Error) => void,
|
||||
mode: 'file' | 'tags' = 'file' // Standard ist 'file'
|
||||
) {
|
||||
fileGetResolve = resolve;
|
||||
fileGetReject = reject;
|
||||
fileTransfer.mode = mode;
|
||||
}
|
||||
|
||||
export function buildFileGetRequest(path: string): ArrayBuffer {
|
||||
@@ -359,5 +397,21 @@ export function buildFileGetRequest(path: string): ArrayBuffer {
|
||||
const uint8Buffer = new Uint8Array(buffer);
|
||||
uint8Buffer.set(pathBytes, 4);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export function buildTagsGetRequest(path: string): ArrayBuffer {
|
||||
const encoder = new TextEncoder();
|
||||
const pathBytes = encoder.encode(path);
|
||||
const buffer = new ArrayBuffer(4 + pathBytes.length);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
view.setUint8(0, FRAME.REQUEST);
|
||||
view.setUint16(1, 1 + pathBytes.length, true);
|
||||
view.setUint8(3, DATA.TAGS_GET);
|
||||
|
||||
const uint8Buffer = new Uint8Array(buffer);
|
||||
uint8Buffer.set(pathBytes, 4);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
Reference in New Issue
Block a user