Classify yt-dlp failures into actionable buckets (2.3)
When a job fails, today the user sees a raw stderr line — often something like "ERROR: [youtube] dQw4w9WgXcQ: Sign in to confirm you're not a bot" that doesn't tell a non-expert what to do. New src/error_class.rs scans the failed job's log buffer for the nine well-known yt-dlp fingerprints and returns: - an ErrorClass enum (rate-limited, members-only, geo-blocked, not-found, codec-missing, disk-full, network-error, bad-cookies, other) - a one-line human-readable suggested action paired with each class Auto-populated in Job::drain when state transitions to Failed. Exposed on JobSnapshot as error_class + error_hint. Rendered in the web UI Downloads modal (red badge + hint box) and the desktop downloads panel (colored label + hint text). Other (no fingerprint matched) is filtered out of the badge so the existing raw last_line keeps doing the talking for unknown failures. Includes 10 unit tests covering each pattern + the walks-from-end priority case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
a8b14f250a
commit
d9a7007f34
6 changed files with 365 additions and 8 deletions
|
|
@ -128,6 +128,11 @@ pub struct Job {
|
|||
pub progress: f32,
|
||||
/// Rolling log buffer — capped at [`JOB_LOG_CAP`] lines via O(1) front-pop.
|
||||
pub log: VecDeque<String>,
|
||||
/// Best-effort classification of the failure, populated when `state`
|
||||
/// transitions to `Failed`. `None` while running or on success. The UI
|
||||
/// surfaces the class + a one-line suggested fix from
|
||||
/// [`crate::error_class`].
|
||||
pub failure_class: Option<crate::error_class::ErrorClass>,
|
||||
rx: Receiver<Msg>,
|
||||
}
|
||||
|
||||
|
|
@ -144,6 +149,15 @@ impl Job {
|
|||
Msg::Progress(p) => self.progress = p,
|
||||
Msg::Finished(ok) => {
|
||||
self.state = if ok { JobState::Done } else { JobState::Failed };
|
||||
// Classify only on the failure transition so the
|
||||
// classifier doesn't re-run for every poll() call on a
|
||||
// long-finished job. The log is already in `self.log`
|
||||
// by this point since we drained Line messages above.
|
||||
if !ok && self.failure_class.is_none() {
|
||||
self.failure_class = Some(crate::error_class::classify(
|
||||
self.log.iter().map(|s| s.as_str()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -594,7 +608,15 @@ impl Downloader {
|
|||
let _ = tx.send(Msg::Finished(ok));
|
||||
});
|
||||
|
||||
self.jobs.push(Job { url, label, state: JobState::Running, progress: 0.0, log: VecDeque::new(), rx });
|
||||
self.jobs.push(Job {
|
||||
url,
|
||||
label,
|
||||
state: JobState::Running,
|
||||
progress: 0.0,
|
||||
log: VecDeque::new(),
|
||||
failure_class: None,
|
||||
rx,
|
||||
});
|
||||
}
|
||||
|
||||
/// Drain pending messages from all job threads and promote queued jobs.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue