zwischenstand

This commit is contained in:
2026-03-19 07:39:25 +01:00
parent ff63dda086
commit b863b04505
15 changed files with 647 additions and 337 deletions

View File

@@ -8,13 +8,8 @@
import { refreshLocal } from "../lib/sync";
import {
GearIcon,
CloudArrowUpIcon,
ArrowClockwiseIcon,
DotsThreeVerticalIcon,
CheckSquareOffsetIcon,
SquareIcon,
DownloadIcon,
TrashIcon,
FingerprintIcon,
} from "phosphor-svelte";

View File

@@ -8,8 +8,9 @@ export const SETTINGS = {
},
ui: {
toastDurationMs: 5000,
transferUpdateIntervalMs: 1000,
kbpsCalculationWindowMs: 1000,
transferUpdateIntervalMs: 200,
speedSmoothingSamples: 50, // Anzahl der Messwerte für den gleitenden ETA-Durchschnitt
transferOverlayPersistMs: 4000,
estimatedInterFileGapMs: 700, // Initialer Schätzwert für die Pause zwischen zwei Dateien
},
};

View File

@@ -123,6 +123,7 @@ export const storageUsage = derived(
// Für die Anzeige der Transferdetails (Dateiname, Fortschritt, Geschwindigkeit, ETA)
export const transferStats = writable({
isActive: false,
currentFileName: '',
pendingFileName: '',
bytesDone: 0,
@@ -130,11 +131,13 @@ export const transferStats = writable({
overallDone: 0,
overallTotal: 0,
bulkStartTime: 0,
fileStartTime: 0
fileStartTime: 0,
filesRemaining: 0
});
export const resetTransferStats = () => {
transferStats.set({
isActive: false,
currentFileName: '',
pendingFileName: '',
bytesDone: 0,
@@ -142,41 +145,111 @@ export const resetTransferStats = () => {
overallDone: 0,
overallTotal: 0,
bulkStartTime: 0,
fileStartTime: 0
fileStartTime: 0,
filesRemaining: 0
});
};
let speedHistory: { bytes: number, time: number }[] = [];
let speedSamples: number[] = [];
let lastSampleTime = 0;
let lastSampleBytes = 0;
let lastCalculatedSpeedKbs = 0;
let gapStartTime = 0;
let gapTimes: number[] = [];
let currentAverageGapMs = SETTINGS.ui.estimatedInterFileGapMs;
let wasActivelyTransferring = false;
export const transferDetails = derived(transferStats, ($s) => {
const now = performance.now();
// Nur nullen, wenn wirklich kein Transfer mehr im Store steht.
// Erlaubt das saubere Ausfaden des UI mit 100% Werten, auch wenn isActive schon false ist.
if ($s.overallTotal === 0) {
speedHistory = [];
speedSamples = [];
lastSampleTime = 0;
lastSampleBytes = 0;
lastCalculatedSpeedKbs = 0;
gapStartTime = 0;
gapTimes = [];
currentAverageGapMs = SETTINGS.ui.estimatedInterFileGapMs;
wasActivelyTransferring = false;
return { filePercent: 0, totalPercent: 0, speedKbs: 0, fileEta: Infinity, totalEta: Infinity };
}
speedHistory.push({ bytes: $s.overallDone, time: now });
speedHistory = speedHistory.filter(p => now - p.time < SETTINGS.ui.kbpsCalculationWindowMs);
const isActivelyTransferring = $s.bytesDone > 0 && $s.bytesDone < $s.bytesTotal;
let speedKbs = 0;
if (speedHistory.length > 1) {
const first = speedHistory[0];
const last = speedHistory[speedHistory.length - 1];
const timeDiff = (last.time - first.time) / 1000;
const bytesDiff = last.bytes - first.bytes;
if (timeDiff > 0) speedKbs = (bytesDiff / 1024) / timeDiff;
// --- 1. Gap Calculation (Realer Overhead zwischen den Dateien) ---
if (!isActivelyTransferring) {
if (wasActivelyTransferring || gapStartTime === 0) {
gapStartTime = now;
}
} else {
if (!wasActivelyTransferring && gapStartTime > 0) {
const gapMs = now - gapStartTime;
gapStartTime = 0;
if (gapMs > 0 && gapMs < 10000) { // Plausibilitäts-Check
gapTimes.push(gapMs);
currentAverageGapMs = gapTimes.reduce((a, b) => a + b, 0) / gapTimes.length;
console.debug(`[Transfer] Inter-file overhead gap: ${gapMs.toFixed(1)}ms. Neues Bulk-Average: ${currentAverageGapMs.toFixed(1)}ms`);
}
}
}
wasActivelyTransferring = isActivelyTransferring;
// --- 2. Speed Calculation (Gleitender Durchschnitt) ---
if (isActivelyTransferring) {
if (lastSampleTime > 0) {
const timeDiff = (now - lastSampleTime) / 1000;
const updateIntervalSecs = SETTINGS.ui.transferUpdateIntervalMs / 1000;
if (timeDiff >= updateIntervalSecs) { // Dynamisches Fenster gemäß Config
const bytesDiff = $s.overallDone - lastSampleBytes;
if (bytesDiff >= 0) {
const currentSpeedKbs = (bytesDiff / 1024) / timeDiff;
speedSamples.push(currentSpeedKbs);
if (speedSamples.length > SETTINGS.ui.speedSmoothingSamples) {
speedSamples.shift();
}
lastCalculatedSpeedKbs = speedSamples.reduce((a, b) => a + b, 0) / speedSamples.length;
}
lastSampleTime = now;
lastSampleBytes = $s.overallDone;
}
} else {
lastSampleTime = now;
lastSampleBytes = $s.overallDone;
}
} else {
lastSampleTime = 0; // Friert den Speed ein
}
const speedBytesPerSec = speedKbs * 1024;
const speedBytesPerSec = lastCalculatedSpeedKbs * 1024;
const estimatedGapSecs = ($s.filesRemaining * currentAverageGapMs) / 1000;
// ETA Aufrunden (Math.ceil), damit die letzte Sekunde immer als "1s" und nicht "0s" angezeigt wird.
// Harter Fallback auf 0, sobald die Datei/der Bulk physisch 100% erreicht hat.
let fileEta = Infinity;
if ($s.bytesTotal > 0 && $s.bytesDone >= $s.bytesTotal) {
fileEta = 0;
} else if (speedBytesPerSec > 100) {
fileEta = Math.ceil(($s.bytesTotal - $s.bytesDone) / speedBytesPerSec);
}
let totalEta = Infinity;
if ($s.overallTotal > 0 && $s.overallDone >= $s.overallTotal) {
totalEta = 0;
} else if (speedBytesPerSec > 100) {
totalEta = Math.ceil((($s.overallTotal - $s.overallDone) / speedBytesPerSec) + estimatedGapSecs);
}
return {
filePercent: Math.round(($s.bytesTotal > 0 ? $s.bytesDone / $s.bytesTotal : 0) * 100),
totalPercent: Math.round(($s.overallTotal > 0 ? $s.overallDone / $s.overallTotal : 0) * 100),
speedKbs: parseFloat(speedKbs.toFixed(2)),
// Wenn Speed zu gering, direkt Infinity für das ∞ Symbol
fileEta: speedBytesPerSec > 100 ? ($s.bytesTotal - $s.bytesDone) / speedBytesPerSec : Infinity,
totalEta: speedBytesPerSec > 100 ? ($s.overallTotal - $s.overallDone) / speedBytesPerSec : Infinity
speedKbs: parseFloat(lastCalculatedSpeedKbs.toFixed(2)),
fileEta,
totalEta
};
});

View File

@@ -111,9 +111,11 @@ export async function downloadSelectedFiles() {
transferStats.update(s => ({
...s,
isActive: true,
overallTotal: totalBytes,
overallDone: 0,
bulkStartTime: bulkStart
bulkStartTime: bulkStart,
filesRemaining: files.length
}));
isTransferingRemote.set(true);
@@ -122,10 +124,17 @@ export async function downloadSelectedFiles() {
for (const file of files) {
console.debug(`Starte Download von: ${file.name}`);
transferStats.update(s => ({ ...s, pendingFileName: file.name }));
// Setzt die Einzel-Balken hart auf 0 und bereitet UI perfekt auf neue Datei vor
transferStats.update(s => ({
...s,
pendingFileName: file.name,
currentFileName: file.name,
bytesTotal: file.size,
bytesDone: 0,
filesRemaining: s.filesRemaining > 0 ? s.filesRemaining - 1 : 0
}));
const fullPath = `${pathPrefix}/${file.name}`;
await new Promise(r => setTimeout(r, SETTINGS.ui.transferUpdateIntervalMs)); // Kurze Verzögerung für UI-Update
await getFile(fullPath);
}
@@ -140,6 +149,7 @@ export async function downloadSelectedFiles() {
} finally {
transferStats.update(s => ({
...s,
isActive: false, // UI Overlay verstecken
overallDone: s.overallTotal,
}));
isTransferingRemote.set(false);
@@ -208,18 +218,28 @@ export async function uploadSelectedFiles() {
transferStats.update(s => ({
...s,
isActive: true,
overallTotal: totalBytes,
overallDone: 0,
bulkStartTime: bulkStart
bulkStartTime: bulkStart,
filesRemaining: files.length
}));
// Wir nutzen isFetchingRemote als generischen "Transfer aktiv"-Trigger für das UI TODO: Namensänderung in isTransferring?
isTransferingRemote.set(true);
try {
for (const file of files) {
console.debug(`Starte Upload von: ${file.name} (${(file.size / 1024).toFixed(1)} kB)`);
transferStats.update(s => ({ ...s, pendingFileName: file.name }));
// Resetted die Store-Stats VOR der DB-Abfrage, UI glättet sich sofort
transferStats.update(s => ({
...s,
pendingFileName: file.name,
currentFileName: file.name,
bytesTotal: file.size,
bytesDone: 0,
filesRemaining: s.filesRemaining > 0 ? s.filesRemaining - 1 : 0
}));
const dbRecord = await getLocalFile(file.name);
if (!dbRecord || !dbRecord.blob) {
@@ -227,7 +247,6 @@ export async function uploadSelectedFiles() {
}
const fullPath = `${pathPrefix}/${file.name}`;
await new Promise(r => setTimeout(r, SETTINGS.ui.transferUpdateIntervalMs));
await putFile(dbRecord.blob, fullPath, file.name);
}
@@ -245,6 +264,7 @@ export async function uploadSelectedFiles() {
} finally {
transferStats.update(s => ({
...s,
isActive: false,
overallDone: s.overallTotal, // Schließt den Ladebalken visuell sauber ab
}));
isTransferingRemote.set(false);

View File

@@ -210,9 +210,9 @@ export async function putFile(fileBlob: Blob, remotePath: string, fileNameForUI:
uploadState.credits--;
offset += chunkLen;
// UI gedrosselt updaten (z.B. alle 100ms)
// UI gedrosselt updaten (gemäß Settings)
const now = performance.now();
if (now - lastUiUpdate > 100) {
if (now - lastUiUpdate > SETTINGS.ui.transferUpdateIntervalMs) {
transferStats.update(s => ({
...s,
bytesDone: offset,
@@ -223,7 +223,11 @@ export async function putFile(fileBlob: Blob, remotePath: string, fileNameForUI:
}
// Abschließendes UI Update
transferStats.update(s => ({ ...s, bytesDone: fileData.length }));
transferStats.update(s => ({
...s,
bytesDone: fileData.length,
overallDone: s.overallDone + (fileData.length - s.bytesDone)
}));
// END Frame senden
const endBuffer = new ArrayBuffer(3 + 4);