Skip to content

feat: harden peer download handling #13

@roziscoding

Description

@roziscoding

Summary

Build on #12's SQLite download progress tracking to make peer downloads resumable and robust across retries and Jack restarts.

Once Jack persists download state (expectedBytes, downloadedBytes, partPath, status, timestamps, and errors), failed or interrupted downloads should not have to restart from byte 0. Jack should use the persisted state plus the existing .part file to resume from the last durable byte whenever the peer still serves the same file.

Proposed Scope

  • Use the SQLite download registry from feat: track download progress in SQLite #12 as the source of truth for active, failed, and resumable downloads.
  • Add a configurable download concurrency limit under downloads, for example maxConcurrentDownloads.
  • Keep the existing per-filename dedupe, but add an async semaphore around the expensive file download step.
  • Add bounded retries with exponential backoff and jitter.
  • Retry transient failures: network errors, timeouts, 5xx, and 429.
  • Do not retry non-429 4xx failures.
  • Respect Retry-After for 429 where possible.
  • Add startup reconciliation for stale downloading rows and existing *.part files.
  • Keep the 100 GB guard and validate size consistently.

Required Resumability

Implement HTTP Range support between Jack peers.

Serving side:

  • Advertise Accept-Ranges: bytes on full-file responses.
  • Parse Range: bytes=<start>-<end> request headers.
  • Return 206 Partial Content with Content-Range: bytes start-end/total for valid ranges.
  • Return 416 Range Not Satisfiable for invalid or unsatisfiable ranges.
  • Stream only the requested slice from disk.

Client side:

  • On retry or restart reconciliation, inspect the persisted download row and .part file size.
  • If a .part file exists, send Range: bytes=<existingSize>-.
  • Append only after validating that the peer returned 206 with a matching Content-Range.
  • If the peer returns 200, or the range metadata does not match the persisted expected size, discard/truncate the stale .part and restart from byte 0.
  • Update the SQLite progress row as bytes are appended.
  • Atomically rename .part into the completed path only after the expected total bytes are present.

Acceptance Criteria

  • feat: track download progress in SQLite #12 exists or is implemented first, and feat: harden peer download handling #13 uses that SQLite registry instead of inventing separate state.
  • Successful downloads are only exposed to Radarr/Sonarr after the final atomic rename.
  • Multiple queued .torrent files cannot start unlimited simultaneous peer downloads.
  • Transient peer/network failures retry with bounded backoff.
  • Interrupted downloads resume from the existing .part file when the peer supports valid range requests.
  • If resume validation fails, Jack safely restarts from byte 0 and records that transition in progress state.
  • Failed downloads do not leave importable partial files in the completed folder.
  • Stale downloading rows and .part files from prior crashes are reconciled deliberately on startup.
  • Tests cover range serving, client resume, restart reconciliation, concurrency limiting, retry behavior, failed-download cleanup, and stale partial handling.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions