feat(m4): persist the download queue across restarts
The queue/scheduler lived only in renderer memory, so saved/scheduled and other pending downloads were silently lost on quit (audit M4). Persist them: - main: queue.ts store (proven cached-atomic createJsonStore, queue.json) + queue:list / queue:save IPC; QUEUE_MAX cap. - shared: PersistedQueueItem = the durable subset of DownloadItem (runtime-only progress/speed/eta/sizeLabel/finishing dropped). - renderer: mirror the persistable items on every queue change (a signature over the subset means progress ticks don't re-save; main's jsonStore coalesces the writes) and rehydrate on launch via an App useEffect. Pure mappers extracted to store/queuePersist.ts and unit-tested. Restore semantics: downloading -> queued (yt-dlp continues the .part), saved + scheduledFor survives so the promoter fires when due, paused/error return actionable; completed/canceled are never persisted. A queueHydrated gate stops a launch-time store change from wiping queue.json before it's read. Reconciles ROADMAP.md. typecheck + 268 tests + eslint + prettier green. (OS-level quit/relaunch cycle is worth a manual smoke test.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { createJsonStore } from './jsonStore'
|
||||
import { QUEUE_MAX } from './constants'
|
||||
import type { PersistedQueueItem, PersistedQueueStatus } from '@shared/ipc'
|
||||
|
||||
/**
|
||||
* Persistence for the download queue (M4). The queue lived only in renderer memory,
|
||||
* so `saved` / scheduled and other pending downloads were silently lost on quit.
|
||||
* The renderer now mirrors its durable items here (via `queueSave`) and rehydrates
|
||||
* them on launch (via `queueList`), so a parked/scheduled download returns.
|
||||
*
|
||||
* Reuses the shared cached-atomic `createJsonStore` (R1/R2/R3), so writes are atomic
|
||||
* and coalesced — the renderer can call `queueSave` freely on queue changes.
|
||||
*/
|
||||
|
||||
const PERSISTED_STATUSES: readonly PersistedQueueStatus[] = [
|
||||
'queued',
|
||||
'downloading',
|
||||
'paused',
|
||||
'saved',
|
||||
'error'
|
||||
]
|
||||
|
||||
/** Defensive row validator (a hand-edited or partially-written file can't crash a read). */
|
||||
function isValidQueueItem(o: unknown): o is PersistedQueueItem {
|
||||
if (typeof o !== 'object' || o === null) return false
|
||||
const it = o as Record<string, unknown>
|
||||
return (
|
||||
typeof it.id === 'string' &&
|
||||
typeof it.url === 'string' &&
|
||||
typeof it.title === 'string' &&
|
||||
(it.kind === 'video' || it.kind === 'audio') &&
|
||||
typeof it.quality === 'string' &&
|
||||
typeof it.status === 'string' &&
|
||||
(PERSISTED_STATUSES as readonly string[]).includes(it.status)
|
||||
)
|
||||
}
|
||||
|
||||
const store = createJsonStore('queue.json', isValidQueueItem, QUEUE_MAX)
|
||||
|
||||
export function listQueue(): PersistedQueueItem[] {
|
||||
return store.read()
|
||||
}
|
||||
|
||||
export function saveQueue(items: PersistedQueueItem[]): void {
|
||||
store.write(items)
|
||||
}
|
||||
Reference in New Issue
Block a user