H4 + H2 formatter half: carry raw numbers across the progress boundary
Closes H4 and the remaining formatter half of H2. - New @shared/format.ts is the single home for fmtBytes/fmtSpeed/fmtEta, imported by both main and renderer. main/lib/formatters.ts re-exports them. - DownloadProgress now carries raw speedBytesPerSec/etaSeconds instead of pre-formatted speed/eta strings. main's parseProgress emits the raw numbers. - The renderer stores the raw numbers on DownloadItem; QueueItem formats them for per-item display, and summarizeQueue sums/maxes them directly. The lossy string->number->string round-trip in queueStats (parseSpeed / parseEtaSeconds / a local formatSpeed / formatEta) is deleted, along with a duplicate fmtEta in store/downloads.ts. - Per-item and aggregate speed now use the same 1024-based scale; the aggregate previously used a 1000-based formatter (a latent inconsistency). Tests updated for the raw-number contract (parseProgress, summarizeQueue). typecheck + 248 tests + eslint + prettier green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-31
@@ -1,7 +1,11 @@
|
||||
import type { DownloadProgress } from '@shared/ipc'
|
||||
import { fmtBytes } from '@shared/format'
|
||||
|
||||
// Pure formatting helpers for yt-dlp progress output. Extracted from download.ts
|
||||
// so they can be unit-tested without pulling in the electron import chain (L37).
|
||||
// yt-dlp progress-line parsing. Extracted from download.ts so it can be
|
||||
// unit-tested without pulling in the electron import chain (L37). The byte/
|
||||
// speed/ETA formatters now live in @shared/format (one home for both sides of
|
||||
// the IPC boundary); re-exported here so existing importers keep working.
|
||||
export { fmtBytes, fmtSpeed, fmtEta } from '@shared/format'
|
||||
|
||||
function num(s?: string): number | undefined {
|
||||
if (!s || s === 'NA') return undefined
|
||||
@@ -9,33 +13,6 @@ function num(s?: string): number | undefined {
|
||||
return Number.isFinite(n) ? n : undefined
|
||||
}
|
||||
|
||||
export function fmtBytes(bytes: number): string {
|
||||
if (bytes < 1024) return `${bytes} B`
|
||||
const units = ['KB', 'MB', 'GB', 'TB']
|
||||
let v = bytes / 1024
|
||||
let i = 0
|
||||
while (v >= 1024 && i < units.length - 1) {
|
||||
v /= 1024
|
||||
i++
|
||||
}
|
||||
return `${v.toFixed(v >= 100 ? 0 : 1)} ${units[i]}`
|
||||
}
|
||||
|
||||
function fmtSpeed(bytesPerSec?: number): string | undefined {
|
||||
if (bytesPerSec == null) return undefined
|
||||
return `${fmtBytes(bytesPerSec)}/s`
|
||||
}
|
||||
|
||||
export function fmtEta(seconds?: number): string | undefined {
|
||||
if (seconds == null) return undefined
|
||||
const s = Math.max(0, Math.round(seconds))
|
||||
const h = Math.floor(s / 3600)
|
||||
const m = Math.floor((s % 3600) / 60)
|
||||
const r = s % 60
|
||||
if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(r).padStart(2, '0')}`
|
||||
return `${m}:${String(r).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
export function parseProgress(rest: string): DownloadProgress | null {
|
||||
const parts = rest.split('|')
|
||||
if (parts.length < 6) return null
|
||||
@@ -47,8 +24,11 @@ export function parseProgress(rest: string): DownloadProgress | null {
|
||||
return {
|
||||
status: status || 'downloading',
|
||||
progress,
|
||||
speed: fmtSpeed(num(speed)),
|
||||
eta: fmtEta(num(eta)),
|
||||
// Carry raw numbers across the IPC boundary; the renderer formats them for
|
||||
// display AND aggregates them (combined speed / longest ETA) without having
|
||||
// to re-parse a formatted string (H4).
|
||||
speedBytesPerSec: num(speed),
|
||||
etaSeconds: num(eta),
|
||||
sizeLabel: totalBytes ? fmtBytes(totalBytes) : undefined,
|
||||
// No (estimated) total → the % can never advance; flag it so the renderer
|
||||
// shows an indeterminate bar rather than a stuck 0% (L137).
|
||||
|
||||
Reference in New Issue
Block a user