Skip to content

feat: report two-stage download progress to the *arr and logs - #37

Open
bugrax wants to merge 2 commits into
wouterdebie:mainfrom
bugrax:progress-reporting
Open

feat: report two-stage download progress to the *arr and logs#37
bugrax wants to merge 2 commits into
wouterdebie:mainfrom
bugrax:progress-reporting

Conversation

@bugrax

@bugrax bugrax commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #5.

put.io reports a transfer as 100% as soon as its own (cloud) download
finishes, but putioarr still has to pull the files down to local disk. During
that second stage the *arr showed a progress bar frozen at 0% — it was pinned
there so the *arr wouldn't try to import files that weren't on disk yet (#16) —
so users couldn't tell a stuck download from one still making progress.

What this does

Maps the two stages onto the single progress bar the *arr shows:

stage progress
put.io cloud download 0–50%
local pull to disk 50–100%

Both stages move the same number of bytes, so the 50/50 split is byte-accurate.
The transfer only reaches 100% once the files are actually on local disk, so the
import-safety behaviour from #16 is preserved. Orphaned watch-folder files (#34)
have no cloud stage, so their local pull is reported directly as 0–100%.

The periodic status log prints the same combined percentage per transfer:

Active transfers: 2
  [abcd: Some.Movie.2024.1080p] (73%)
  [ef01: Some.Show.S01E04] (100%)

Implementation

  • Local download progress is tracked per transfer with a lock-free AtomicU64
    counter, incremented per streamed chunk in the download worker. It's read only
    when the *arr polls (torrent-get) or the status line is logged, so the
    download hot path is untouched — this avoids the slowdown that an earlier
    byte-counting attempt hit (noted in Download progress updates in the console / log / sonarr / radarr  #5).
  • The counter is seeded from bytes already on disk when a download resumes, and
    pruned once a transfer completes or leaves the account, so the map stays
    bounded.

Testing

Built and deployed to a live instance; verified the *arr progress bar now
advances continuously through both the cloud and local stages instead of
sitting at 0% during the local pull, and the status log shows the combined
percentage.

put.io reports a transfer as 100% as soon as *its own* cloud download
finishes, but putioarr still has to pull the files down to local disk.
During that second stage the *arr saw a progress bar frozen at 0% (it
was pinned there so it wouldn't import files that weren't on disk yet),
so users couldn't tell a stuck download from one still making progress
(issue wouterdebie#5).

Track the bytes pulled to local disk per transfer with a lock-free
AtomicU64 counter (incremented per streamed chunk, read only when the
*arr polls or the status line is logged, so the download hot path is
unaffected) and map the two stages onto a single 0-100% bar:

  - put.io cloud download -> 0-50%
  - local pull to disk    -> 50-100%

Both stages move the same number of bytes, so the split is byte-accurate.
The transfer only reaches 100% once the files are actually on disk, so
the existing import-safety behaviour (issue wouterdebie#16) is preserved. Orphaned
watch-folder files (issue wouterdebie#34) have no cloud stage, so their local pull
is reported directly as 0-100%.

The periodic status log now prints the same combined percentage per
transfer, e.g. `[abcd: Some.Movie] (73%)`.
Copilot AI review requested due to automatic review settings July 18, 2026 10:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves user-visible download progress reporting by combining put.io’s “cloud download” stage with putioarr’s “local pull to disk” stage into a single progress signal for *arr clients and periodic logs, while preserving the “don’t report complete until local files exist” safety behavior.

Changes:

  • Add per-transfer local byte counters (AtomicU64) in shared state, updated by download workers.
  • Map two-stage progress into a single 0–100% scale for torrent-get responses (including orphan watch-folder files).
  • Include the combined progress percentage in the periodic “Active transfers” log output.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/state.rs Adds a per-transfer local byte counter map and accessors for tracking local pull progress.
src/http/handlers.rs Computes and reports combined two-stage progress to *arr clients; prunes counters for absent transfers.
src/download_system/transfer.rs Updates periodic status logging to include combined progress percent.
src/download_system/orchestration.rs Clears per-transfer progress counters once a transfer is locally complete.
src/download_system/download.rs Increments the per-transfer counter as streamed chunks are written; seeds from bytes already on disk.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/http/handlers.rs
Comment thread src/download_system/download.rs Outdated
Comment thread src/state.rs
Comment thread src/download_system/transfer.rs Outdated
Comment thread src/http/handlers.rs Outdated
…ixes

- handlers: keep a transfer in a downloading state (non-zero
  left_until_done) while the local pull is incomplete even when put.io
  reports no size, so a 0/0 transfer isn't read as finished and imported
  early (preserves wouterdebie#16).
- handlers: only prune local-progress counters when the transfers listing
  succeeded; a transient list failure returns an empty set that would
  otherwise wipe progress for in-flight downloads.
- download: track how many of a file's bytes are in the shared counter so
  a from-scratch restart (server ignoring the Range request, HTTP 200)
  removes its previous contribution instead of double-counting the
  re-downloaded bytes.
- state: give local_byte_counter a read-lock fast path for the common
  case where the counter already exists, avoiding write-lock contention
  between download workers.
- transfer: cap the logged percentage at 99 until local_complete flips,
  so the status line doesn't claim 100% while the pull is still active.
@bugrax

bugrax commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — all five addressed in 508147e:

  1. Unknown size reintroducing Transmission client should only report download as "complete" when we've finished downloading it from Putio #16 (handlers.rs) — the downloading-state override no longer requires total_size > 0. When the size is unknown we can't show a percentage, but we still force a non-zero left_until_done and a downloading status while the local pull is incomplete, so a 0/0 transfer isn't read as finished.
  2. Double-counting on a 200 restart (download.rs) — each file now tracks how many of its bytes are currently in the shared counter. A resume (206) counts the on-disk bytes exactly once; a from-scratch restart (200, truncating) subtracts this file's previous contribution before re-counting, so nothing is counted twice.
  3. Write-lock contention (state.rs) — local_byte_counter now takes a read lock first and only falls back to the write lock when the counter doesn't exist yet.
  4. Log claiming 100% early (transfer.rs) — the computed percentage is capped at 99 until local_complete flips (the early return still reports a true 100%).
  5. Pruning on a failed listing (handlers.rs) — counters are only pruned when list_transfers succeeded, so a transient failure (empty set) can't wipe progress for in-flight downloads.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Download progress updates in the console / log / sonarr / radarr

2 participants