fix(backend): fix hasAudio() race condition for parallel downloads

Snapshot dir before each yt-dlp call so hasAudio() only detects
files added by the current attempt, not pre-existing files from
parallel jobs downloading to the same album directory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-02 11:29:00 +02:00
1 parent 016a231174
commit 4b6095f213
1 file changed
+22 -15
+22 -15
View File
@@ -245,8 +245,15 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
);
});
const hasAudio = () => fs.readdirSync(dir).some(f => AUDIO_EXTS.some(e => f.endsWith(`.${e}`)));
const ytmUrl = `https://music.youtube.com/search?q=${encodeURIComponent(query)}`;
// Snapshot files already in dir before we start — so hasAudio() only detects NEW files
// from the current download attempt, not pre-existing files from parallel jobs.
let snapBefore = new Set(fs.readdirSync(dir));
const resetSnap = () => { snapBefore = new Set(fs.readdirSync(dir)); };
const hasAudio = () => fs.readdirSync(dir).some(
f => !snapBefore.has(f) && AUDIO_EXTS.some(e => f.endsWith(`.${e}`))
);
const tryYtdlp = async (args) => { resetSnap(); await spawnYtdlp(args); };
const uploaderWord = artistFilterWord(mainArtist);
const variantFilter = buildVariantFilter(title);
@@ -268,60 +275,60 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
log(jobId, `🔍 Suche YT Music (~${durationSec}s ±${margin}s): "${query}"`);
// 1. YTM — Dauer + Artist + Varianten-Filter (streng)
await spawnYtdlp([ytmUrl, '--playlist-items', '1', ...mf(dur, true, true), ...baseArgs]);
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...mf(dur, true, true), ...baseArgs]);
if (!hasAudio()) {
// 2. YT — Dauer + Artist + Varianten-Filter
log(jobId, '⚠️ Kein YTM-Treffer — Fallback zu YouTube…');
await spawnYtdlp([`ytsearch1:${query}`, ...mf(dur, true, true), ...baseArgs]);
await tryYtdlp([`ytsearch1:${query}`, ...mf(dur, true, true), ...baseArgs]);
}
if (!hasAudio()) {
// 3. YTM — Dauer + Varianten-Filter (kein Artist)
log(jobId, '⚠️ Kein Treffer mit Artist-Filter — ohne Artist…');
await spawnYtdlp([ytmUrl, '--playlist-items', '1', ...mf(dur, false, true), ...baseArgs]);
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...mf(dur, false, true), ...baseArgs]);
}
if (!hasAudio()) {
// 4. YT — Dauer + Varianten-Filter
await spawnYtdlp([`ytsearch1:${query}`, ...mf(dur, false, true), ...baseArgs]);
await tryYtdlp([`ytsearch1:${query}`, ...mf(dur, false, true), ...baseArgs]);
}
if (!hasAudio()) {
// 5. YTM — nur Varianten-Filter (kein Dauer-Check)
log(jobId, '⚠️ Kein Treffer im Dauerbereich — suche ohne Dauerbeschränkung…');
await spawnYtdlp([ytmUrl, '--playlist-items', '1', ...mf(null, false, true), ...baseArgs]);
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...mf(null, false, true), ...baseArgs]);
}
if (!hasAudio()) {
// 6. YT — nur Varianten-Filter
await spawnYtdlp([`ytsearch1:${query}`, ...mf(null, false, true), ...baseArgs]);
await tryYtdlp([`ytsearch1:${query}`, ...mf(null, false, true), ...baseArgs]);
}
if (!hasAudio()) {
// 7+8. Letzter Ausweg: komplett ungefiltert
log(jobId, '⚠️ Letzter Versuch: keine Filter…');
await spawnYtdlp([ytmUrl, '--playlist-items', '1', ...baseArgs]);
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...baseArgs]);
}
if (!hasAudio()) {
await spawnYtdlp([`ytsearch1:${query}`, ...baseArgs]);
await tryYtdlp([`ytsearch1:${query}`, ...baseArgs]);
}
} else {
log(jobId, `🔍 Suche YT Music: "${query}"`);
// Ohne Dauer: Artist + Varianten → ohne Artist → ohne alles
await spawnYtdlp([ytmUrl, '--playlist-items', '1', ...mf(null, true, true), ...baseArgs]);
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...mf(null, true, true), ...baseArgs]);
if (!hasAudio()) {
await spawnYtdlp([`ytsearch1:${query}`, ...mf(null, true, true), ...baseArgs]);
await tryYtdlp([`ytsearch1:${query}`, ...mf(null, true, true), ...baseArgs]);
}
if (!hasAudio()) {
log(jobId, '⚠️ Kein Treffer mit Artist — ohne Artist-Filter…');
await spawnYtdlp([ytmUrl, '--playlist-items', '1', ...mf(null, false, true), ...baseArgs]);
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...mf(null, false, true), ...baseArgs]);
}
if (!hasAudio()) {
await spawnYtdlp([`ytsearch1:${query}`, ...mf(null, false, true), ...baseArgs]);
await tryYtdlp([`ytsearch1:${query}`, ...mf(null, false, true), ...baseArgs]);
}
if (!hasAudio()) {
log(jobId, '⚠️ Letzter Versuch: keine Filter…');
await spawnYtdlp([ytmUrl, '--playlist-items', '1', ...baseArgs]);
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...baseArgs]);
}
if (!hasAudio()) {
await spawnYtdlp([`ytsearch1:${query}`, ...baseArgs]);
await tryYtdlp([`ytsearch1:${query}`, ...baseArgs]);
}
}