feat(audit): Batch 15 live-verify — download-engine lifecycle (R4, L65, L139, B2, B6, L143)
The watched live session for the six deferred lifecycle items, each verified against real yt-dlp/Electron behavior on Windows: - R4: cancel now deletes the download's orphaned partials. The engine captures the final output path via a new `--print before_dl:dest|%(filename)s` (empirically %(filepath)s is still 'NA' that early) and the close handler sweeps only unambiguous intermediates (.part / .part-Frag* / .ytdl / <stem>.fNNN.<ext>) via the pure, unit-tested lib/partials.ts — never a bare <stem>.<ext> or another download's files. Live-verified with decoys. - L65: verified-acceptable, no code change — a taskkill /F pause leaves the .part byte-intact and yt-dlp --continue resumes at the exact byte offset. - L139: spawn↔self-update interlock (new ytdlpLock.ts). The updater refuses while any yt-dlp spawn is live; download/terminal IPC handlers hold (await) behind an in-progress exe rewrite instead of failing. - B2: source indexing is cancellable — AbortSignal through the probe walk (kills the in-flight child, live-verified via tasklist), sources:index-cancel IPC, and a Cancel button in the Library add-source row. - B6: app-update download is cancellable — app:update-cancel routes through the existing finish() teardown (abort + destroy + unlink, live-verified on Electron net.request); Cancel button under the update progress bar. The checksum phase is cancelable too. - L143: closing the window mid-download (non-tray mode) now offers a native tray-independent "Quit anyway / Keep downloading" dialog, replacing the passive "use the tray to quit" notification. Peer-review pass caught and fixed: a non-reentrant update lock (concurrent begin clobbered the settle promise), a dead Cancel window during the B6 checksum fetch + a canceller slot-ownership bug (L140 shape), and a persist-after-cancel hole in the index walk. typecheck + 304 tests + eslint + production build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+43
-23
@@ -1,4 +1,4 @@
|
||||
import { app, shell, BrowserWindow, nativeTheme, Notification, Menu } from 'electron'
|
||||
import { app, shell, BrowserWindow, nativeTheme, dialog, Menu } from 'electron'
|
||||
import { join, resolve } from 'path'
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import { IpcChannels } from '@shared/ipc'
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
getSystemThemeInfo
|
||||
} from './ipc'
|
||||
import { runStartupYtdlpAutoUpdate } from './ytdlp'
|
||||
import { getAppIconImage } from './binaries'
|
||||
import { hasActiveDownloads } from './download'
|
||||
import { getSettings, applyLaunchAtStartup, migrateSecretsAtRest } from './settings'
|
||||
import { ensureMediaDirs } from './paths'
|
||||
@@ -64,15 +63,39 @@ let mainWindow: BrowserWindow | null = null
|
||||
// Tell the user (once per run) that closing the window left AeroFetch running so
|
||||
// an in-progress download could finish — shown only when they haven't already
|
||||
// opted into tray mode, so a window that "won't close" doesn't read as a bug.
|
||||
let notifiedBackground = false
|
||||
function notifyBackgroundOnce(): void {
|
||||
if (notifiedBackground || !Notification.isSupported()) return
|
||||
notifiedBackground = true
|
||||
new Notification({
|
||||
title: 'AeroFetch is still running',
|
||||
body: 'Your download is finishing in the background. Use the tray icon to reopen or quit.',
|
||||
icon: getAppIconImage()
|
||||
}).show()
|
||||
// L143: an in-window, tray-INDEPENDENT way to quit at the moment the window "won't
|
||||
// close" because a download is running — without it the only exit is the tray menu
|
||||
// (and Task Manager if the tray ever failed to create). Guarded so hammering the X
|
||||
// can't stack dialogs. Only used when the user did NOT opt into tray mode; a
|
||||
// deliberate minimize-to-tray close stays a silent hide.
|
||||
let quitPromptOpen = false
|
||||
function promptQuitWhileDownloading(win: BrowserWindow): void {
|
||||
if (quitPromptOpen) return
|
||||
quitPromptOpen = true
|
||||
dialog
|
||||
.showMessageBox(win, {
|
||||
type: 'question',
|
||||
buttons: ['Keep downloading', 'Quit anyway'],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
title: 'Download in progress',
|
||||
message: 'A download is still in progress.',
|
||||
detail:
|
||||
'AeroFetch will keep downloading in the background — reopen it from the tray icon. Quit anyway to stop the download and exit now.'
|
||||
})
|
||||
.then(({ response }) => {
|
||||
quitPromptOpen = false
|
||||
if (response === 1) {
|
||||
markQuitting() // lets the close handler through + runs the before-quit teardown
|
||||
app.quit()
|
||||
} else {
|
||||
win.hide() // keep downloading in the background
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
quitPromptOpen = false
|
||||
win.hide()
|
||||
})
|
||||
}
|
||||
|
||||
// Web permissions a download manager never needs. They're denied for the app
|
||||
@@ -155,11 +178,15 @@ function createWindow(): void {
|
||||
const downloadsRunning = hasActiveDownloads()
|
||||
if (getSettings().minimizeToTray || downloadsRunning) {
|
||||
e.preventDefault()
|
||||
win.hide()
|
||||
// If we're only staying alive because a download is running (the user
|
||||
// didn't opt into tray mode), tell them once — otherwise a window that
|
||||
// won't close looks like a bug.
|
||||
if (downloadsRunning && !getSettings().minimizeToTray) notifyBackgroundOnce()
|
||||
// A download (not tray mode) is the only thing holding the app open: offer a
|
||||
// tray-independent "Quit anyway" instead of silently hiding — otherwise a
|
||||
// window that won't close looks like a bug, with no in-window way out (L143).
|
||||
// A tray-mode close is the user's explicit choice, so that stays a silent hide.
|
||||
if (downloadsRunning && !getSettings().minimizeToTray) {
|
||||
promptQuitWhileDownloading(win)
|
||||
} else {
|
||||
win.hide()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -167,13 +194,6 @@ function createWindow(): void {
|
||||
if (mainWindow === win) mainWindow = null
|
||||
})
|
||||
|
||||
// When the user re-opens the window (via tray, second-instance, or deeplink)
|
||||
// reset the background-notify latch so they're informed again if they close
|
||||
// while a download is still running (L68).
|
||||
win.on('show', () => {
|
||||
notifiedBackground = false
|
||||
})
|
||||
|
||||
// The OS may have launched us with an aerofetch:// link or a "Send to" .url
|
||||
// file on the command line — hand it to DownloadBar's link-suggestion banner
|
||||
// once the page (and its IPC listener) is actually ready to receive it.
|
||||
|
||||
Reference in New Issue
Block a user