zwischenstand
This commit is contained in:
@@ -8,13 +8,8 @@
|
||||
import { refreshLocal } from "../lib/sync";
|
||||
|
||||
import {
|
||||
GearIcon,
|
||||
CloudArrowUpIcon,
|
||||
ArrowClockwiseIcon,
|
||||
DotsThreeVerticalIcon,
|
||||
CheckSquareOffsetIcon,
|
||||
SquareIcon,
|
||||
DownloadIcon,
|
||||
TrashIcon,
|
||||
FingerprintIcon,
|
||||
} from "phosphor-svelte";
|
||||
|
||||
@@ -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
|
||||
},
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user