refactor(main): execFileAsync helper — unify the 5 spawn-and-read sites
Replace the hand-rolled `new Promise((resolve) => execFile(…, cb))` wrappers in
probe / ytdlp (×2) / ffmpeg / indexer.probeFlat / download.probeMeta with one
lib/exec.ts execFileAsync returning a normalized
{ok,stdout,stderr,timedOut,error} that never rejects. Each caller keeps its own
error mapping (timedOut -> "Timed out …" vs stderr) and JSON parsing; behaviour
is unchanged. windowsHide defaults on; per-call timeout/maxBuffer preserved.
Foundation for CC4/CC5 — the stdout line-buffer half lands with SIMP5, the
net.request half with SIMP16, at which point those checkboxes close.
Unit-tested (exec.test.ts) against the node binary: ok / stderr+nonzero-exit /
missing-binary / timeout. typecheck + 262 tests + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+23
-26
@@ -14,6 +14,7 @@ import {
|
||||
YTDLP_MISSING_MSG
|
||||
} from './binaries'
|
||||
import { getSettings } from './settings'
|
||||
import { execFileAsync } from './lib/exec'
|
||||
import { getDownloadArchivePath, getDefaultMediaDir } from './paths'
|
||||
import { ensureManagedYtdlp } from './ytdlp'
|
||||
import { materializeCookies, hasStoredCookies } from './cookies'
|
||||
@@ -123,32 +124,28 @@ function logFailure(opts: StartDownloadOptions, title: string | undefined, error
|
||||
// itself contains a newline, since each --print field is emitted on its own line.
|
||||
const META_SEP = '\u001f'
|
||||
|
||||
function probeMeta(ytdlp: string, url: string): Promise<DownloadMeta | null> {
|
||||
return new Promise((resolve) => {
|
||||
execFile(
|
||||
ytdlp,
|
||||
[
|
||||
'--no-playlist',
|
||||
'--no-warnings',
|
||||
'--skip-download',
|
||||
'--print',
|
||||
`%(title)s${META_SEP}%(uploader)s${META_SEP}%(duration_string)s`,
|
||||
'--',
|
||||
url
|
||||
],
|
||||
{ windowsHide: true, maxBuffer: META_MAX_BUFFER, timeout: META_PROBE_TIMEOUT_MS },
|
||||
(err, stdout) => {
|
||||
if (err) return resolve(null)
|
||||
const [title, uploader, duration] = stdout.split(META_SEP).map((l) => l.trim())
|
||||
const clean = (v?: string): string | undefined => (v && v !== 'NA' ? v : undefined)
|
||||
resolve({
|
||||
title: clean(title),
|
||||
channel: clean(uploader),
|
||||
durationLabel: clean(duration)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
async function probeMeta(ytdlp: string, url: string): Promise<DownloadMeta | null> {
|
||||
const r = await execFileAsync(
|
||||
ytdlp,
|
||||
[
|
||||
'--no-playlist',
|
||||
'--no-warnings',
|
||||
'--skip-download',
|
||||
'--print',
|
||||
`%(title)s${META_SEP}%(uploader)s${META_SEP}%(duration_string)s`,
|
||||
'--',
|
||||
url
|
||||
],
|
||||
{ maxBuffer: META_MAX_BUFFER, timeout: META_PROBE_TIMEOUT_MS }
|
||||
)
|
||||
if (!r.ok) return null
|
||||
const [title, uploader, duration] = r.stdout.split(META_SEP).map((l) => l.trim())
|
||||
const clean = (v?: string): string | undefined => (v && v !== 'NA' ? v : undefined)
|
||||
return {
|
||||
title: clean(title),
|
||||
channel: clean(uploader),
|
||||
durationLabel: clean(duration)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Argv construction (shared by startDownload and the command preview) ---
|
||||
|
||||
Reference in New Issue
Block a user