Make the UI's Download button download the episode you clicked
POST /api/enclosures/{id}/download requeued the row and asked for a
normal scan, but a scan takes the lowest-id pending rows up to
max_new_per_check. With a large backlog and a small cap the requested
row was never a candidate, so other episodes downloaded while it stayed
pending.
A queue expresses what is outstanding, not what was asked for. Download
is now its own command that fetches one specific enclosure immediately,
ignoring queue order and the per-scan cap, still via the single worker.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
This commit is contained in:
58
src/main.rs
58
src/main.rs
@@ -175,6 +175,7 @@ async fn run(ctx: &Ctx, cmd: Cmd) -> Result<()> {
|
||||
fetch(ctx, feed.as_deref(), force).await
|
||||
}
|
||||
Cmd::Reap { dry_run } => reap(ctx, dry_run, true),
|
||||
Cmd::Download { enclosure } => download_one(ctx, enclosure).await,
|
||||
Cmd::Status => {
|
||||
let (pending, downloaded) = ctx.db.counts()?;
|
||||
ctx.out.emit(Event::Status { feeds: ctx.cfg().feeds.len(), pending, downloaded });
|
||||
@@ -716,6 +717,63 @@ async fn fetch_one(
|
||||
Ok((path, got.bytes))
|
||||
}
|
||||
|
||||
/// Downloads one specific enclosure immediately, whatever the per-scan cap says and
|
||||
/// wherever it sits in the queue.
|
||||
async fn download_one(ctx: &Ctx, id: i64) -> Result<()> {
|
||||
let cfg = ctx.cfg();
|
||||
let enc = ctx
|
||||
.db
|
||||
.enclosure(id)?
|
||||
.ok_or_else(|| anyhow::anyhow!("no enclosure {id}"))?;
|
||||
if enc.path.is_some() {
|
||||
return Ok(()); // Already here.
|
||||
}
|
||||
let feed_cfg = cfg
|
||||
.feeds
|
||||
.get(&enc.feed_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("enclosure {id} belongs to unsubscribed feed {:?}", enc.feed_id))?;
|
||||
|
||||
let title = ctx.db.feed_summary(&enc.feed_id)?.title;
|
||||
let folder = download::folder_for(&cfg, &enc.feed_id, feed_cfg, title.as_deref());
|
||||
let dest_dir = cfg.general.download_dir.join(&folder);
|
||||
|
||||
ctx.out.emit(Event::FeedStart { feed: enc.feed_id.clone() });
|
||||
let is_torrent = download::looks_like_torrent(&enc.url, enc.mime.as_deref());
|
||||
let result = if is_torrent {
|
||||
if !cfg.torrent.enabled {
|
||||
Err(anyhow::anyhow!("torrents are disabled"))
|
||||
} else {
|
||||
torrent_one(ctx, &enc.feed_id, &enc.url, &dest_dir).await
|
||||
}
|
||||
} else {
|
||||
fetch_one(ctx, &enc.feed_id, feed_cfg, &enc.url, &dest_dir).await
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok((path, bytes)) => {
|
||||
ctx.db.mark_downloaded(&enc.url, &path, bytes)?;
|
||||
ctx.out.emit(Event::DownloadDone {
|
||||
feed: enc.feed_id.clone(),
|
||||
url: enc.url.clone(),
|
||||
path: path.display().to_string(),
|
||||
bytes,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = format!("{e:#}");
|
||||
ctx.db.mark_enclosure(&enc.url, "error", Some(&msg))?;
|
||||
ctx.out.emit(Event::DownloadError {
|
||||
feed: enc.feed_id.clone(),
|
||||
url: enc.url.clone(),
|
||||
msg,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Terminal, so a UI waiting on this request stops here.
|
||||
ctx.out.emit(Event::ScanDone { feeds: 1 });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Torrent progress is reported the same way an HTTP download's is, throttled to whole
|
||||
/// percents so a UI is not flooded.
|
||||
async fn torrent_one(
|
||||
|
||||
Reference in New Issue
Block a user