diff --git a/src/main.ts b/src/main.ts index 70d8b1d..ef9ea70 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2204,13 +2204,15 @@ function downloadVODPart( if (speed > 0 && downloadedBytes > 0) { const itemStartTime = itemTracking.startTime; const elapsedSec = (Date.now() - itemStartTime) / 1000; - if (elapsedSec > 3) { + if (elapsedSec > 5) { // Wait at least 5 seconds before showing ETA const avgSpeed = downloadedBytes / elapsedSec; if (expectedDurationSeconds && expectedDurationSeconds > 0) { const estimatedTotalBytes = avgSpeed * expectedDurationSeconds; if (estimatedTotalBytes > downloadedBytes) { const remainingSec = (estimatedTotalBytes - downloadedBytes) / avgSpeed; - etaStr = formatETA(remainingSec); + if (remainingSec > 0 && remainingSec < 86400) { // Between 0 and 24 hours + etaStr = formatETA(remainingSec); + } } } } @@ -2798,96 +2800,96 @@ async function processOneQueueItem(item: QueueItem): Promise { item.last_error = ''; - let finalResult: DownloadResult = { success: false, error: 'Unbekannter Fehler beim Download' }; - const maxAttempts = getRetryAttemptLimit(); + try { + let finalResult: DownloadResult = { success: false, error: 'Unbekannter Fehler beim Download' }; + const maxAttempts = getRetryAttemptLimit(); - for (let attempt = 1; attempt <= maxAttempts; attempt++) { - appendDebugLog('queue-item-attempt', { itemId: item.id, attempt, max: maxAttempts }); + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + appendDebugLog('queue-item-attempt', { itemId: item.id, attempt, max: maxAttempts }); - const result = item.mergeGroup - ? await processDownloadMergeGroup(item, (progress) => { - mainWindow?.webContents.send('download-progress', progress); - }) - : await downloadVOD(item, (progress) => { - mainWindow?.webContents.send('download-progress', progress); - }); + const result = item.mergeGroup + ? await processDownloadMergeGroup(item, (progress) => { + mainWindow?.webContents.send('download-progress', progress); + }) + : await downloadVOD(item, (progress) => { + mainWindow?.webContents.send('download-progress', progress); + }); + + if (result.success) { + finalResult = result; + break; + } - if (result.success) { finalResult = result; - break; + + if (!isDownloading || currentDownloadCancelled || cancelledItemIds.has(item.id) || pauseRequested) { + finalResult = { success: false, error: pauseRequested ? 'Download wurde pausiert.' : 'Download wurde abgebrochen.' }; + break; + } + + const errorClass = classifyDownloadError(result.error || ''); + runtimeMetrics.lastErrorClass = errorClass; + + if (errorClass === 'tooling' || errorClass === 'validation') { + appendDebugLog('queue-item-no-retry', { + itemId: item.id, + errorClass, + error: result.error || 'unknown' + }); + break; + } + + if (attempt < maxAttempts) { + const retryDelaySeconds = getRetryDelaySeconds(errorClass, attempt); + runtimeMetrics.retriesScheduled += 1; + runtimeMetrics.lastRetryDelaySeconds = retryDelaySeconds; + + item.last_error = `Versuch ${attempt}/${maxAttempts} fehlgeschlagen (${errorClass}): ${result.error || 'Unbekannter Fehler'}`; + mainWindow?.webContents.send('download-progress', { + id: item.id, + progress: -1, + speed: '', + eta: '', + status: `Neuer Versuch in ${retryDelaySeconds}s (${errorClass})...`, + currentPart: item.currentPart, + totalParts: item.totalParts + } as DownloadProgress); + saveQueue(downloadQueue); + emitQueueUpdated(); + await sleep(retryDelaySeconds * 1000); + } else { + runtimeMetrics.retriesExhausted += 1; + } } - finalResult = result; - - if (!isDownloading || currentDownloadCancelled || cancelledItemIds.has(item.id) || pauseRequested) { - finalResult = { success: false, error: pauseRequested ? 'Download wurde pausiert.' : 'Download wurde abgebrochen.' }; - break; + if (!hasQueueItemId(item.id)) { + appendDebugLog('queue-item-finished-removed', { itemId: item.id }); + return; } - const errorClass = classifyDownloadError(result.error || ''); - runtimeMetrics.lastErrorClass = errorClass; + const wasPaused = pauseRequested || (finalResult.error || '').includes('pausiert'); + item.status = finalResult.success ? 'completed' : (wasPaused ? 'paused' : 'error'); + item.progress = finalResult.success ? 100 : item.progress; + item.last_error = finalResult.success || wasPaused ? '' : (finalResult.error || 'Unbekannter Fehler beim Download'); - if (errorClass === 'tooling' || errorClass === 'validation') { - appendDebugLog('queue-item-no-retry', { - itemId: item.id, - errorClass, - error: result.error || 'unknown' - }); - break; + if (finalResult.success) { + runtimeMetrics.downloadsCompleted += 1; + } else if (!wasPaused) { + runtimeMetrics.downloadsFailed += 1; } - if (attempt < maxAttempts) { - const retryDelaySeconds = getRetryDelaySeconds(errorClass, attempt); - runtimeMetrics.retriesScheduled += 1; - runtimeMetrics.lastRetryDelaySeconds = retryDelaySeconds; + appendDebugLog('queue-item-finished', { + itemId: item.id, + status: item.status, + error: item.last_error + }); - item.last_error = `Versuch ${attempt}/${maxAttempts} fehlgeschlagen (${errorClass}): ${result.error || 'Unbekannter Fehler'}`; - mainWindow?.webContents.send('download-progress', { - id: item.id, - progress: -1, - speed: '', - eta: '', - status: `Neuer Versuch in ${retryDelaySeconds}s (${errorClass})...`, - currentPart: item.currentPart, - totalParts: item.totalParts - } as DownloadProgress); - saveQueue(downloadQueue); - emitQueueUpdated(); - await sleep(retryDelaySeconds * 1000); - } else { - runtimeMetrics.retriesExhausted += 1; - } - } - - if (!hasQueueItemId(item.id)) { - appendDebugLog('queue-item-finished-removed', { itemId: item.id }); + saveQueue(downloadQueue); + emitQueueUpdated(); + } finally { activeDownloads.delete(item.id); cancelledItemIds.delete(item.id); - return; } - - const wasPaused = pauseRequested || (finalResult.error || '').includes('pausiert'); - item.status = finalResult.success ? 'completed' : (wasPaused ? 'paused' : 'error'); - item.progress = finalResult.success ? 100 : item.progress; - item.last_error = finalResult.success || wasPaused ? '' : (finalResult.error || 'Unbekannter Fehler beim Download'); - - if (finalResult.success) { - runtimeMetrics.downloadsCompleted += 1; - } else if (!wasPaused) { - runtimeMetrics.downloadsFailed += 1; - } - - activeDownloads.delete(item.id); - cancelledItemIds.delete(item.id); - - appendDebugLog('queue-item-finished', { - itemId: item.id, - status: item.status, - error: item.last_error - }); - - saveQueue(downloadQueue); - emitQueueUpdated(); } async function processQueue(): Promise { diff --git a/src/renderer.ts b/src/renderer.ts index 8abdf8c..eb3df48 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -99,7 +99,7 @@ async function init(): Promise { // Update stats bar updateStatsBar(); - setInterval(updateStatsBar, 5000); + const _statsInterval = setInterval(updateStatsBar, 5000); if (config.client_id && config.client_secret) { await connect(); @@ -275,9 +275,14 @@ function mergeQueueState(nextQueue: QueueItem[]): QueueItem[] { return item; } + // Keep the higher progress value to prevent backward jumps from stale data + const bestProgress = (prev.status === 'downloading' && prev.progress > item.progress) + ? prev.progress + : (item.progress > 0 ? item.progress : prev.progress); + return { ...item, - progress: item.progress > 0 ? item.progress : prev.progress, + progress: bestProgress, speed: item.speed || prev.speed, eta: item.eta || prev.eta, currentPart: item.currentPart || prev.currentPart,