Skip to content

feat(api): batch torrent uploads in single request#885

Open
s0up4200 wants to merge 1 commit intodevelopfrom
feat/batch-torrent-upload
Open

feat(api): batch torrent uploads in single request#885
s0up4200 wants to merge 1 commit intodevelopfrom
feat/batch-torrent-upload

Conversation

@s0up4200
Copy link
Copy Markdown
Collaborator

@s0up4200 s0up4200 commented Dec 29, 2025

Summary

  • Send all torrent files in a single HTTP request instead of sequential uploads
  • Fixes timeout issues when adding many torrents (e.g., 272 files)

Dependencies

Requires autobrr/go-qbittorrent#101

Closes #883

Summary by CodeRabbit

Release Notes

  • New Features

    • Added batch torrent upload capability, allowing multiple torrent files to be added simultaneously in a single operation.
    • Optimized torrent addition workflow for improved efficiency when handling multiple files.
  • Chores

    • Updated Go module dependencies.

✏️ Tip: You can customize this high-level summary in your review settings.

Use go-qbittorrent's new AddTorrentsFromMemoryCtx to send all torrent
files in one HTTP request instead of sequential uploads. Fixes timeout
issues when adding many torrents at once.

Closes #883
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 29, 2025

Walkthrough

This PR introduces batch torrent addition support by adding an AddTorrents method to the torrentAdder interface and SyncManager, consolidating multiple torrent file uploads into single batch API operations instead of sequential individual additions, addressing context deadline issues with large torrent batches.

Changes

Cohort / File(s) Summary
Dependency Update
go.mod
Updated github.com/autobrr/go-qbittorrent from v1.15.0-rc1.0.20251209201933-62cc902b8602 to v1.15.0-rc1.0.20251229150025-9477b4e75669.
Handler & Interface
internal/api/handlers/torrents.go
Added AddTorrents method to torrentAdder interface. Implemented addTorrents helper on TorrentsHandler to batch-add multiple torrent files; replaced per-file sequential additions with single batch operation; updated error handling for batch context.
Test Scaffolding
internal/api/handlers/torrents_add_test.go
Extended fullMockSyncManager with AddTorrents method and supporting test infrastructure (addTorrentsCall struct, addTorrentsCalls, addTorrentsErr) to enable testing of batched torrent additions.
Sync Manager Implementation
internal/qbittorrent/sync_manager.go
Added public AddTorrents method to SyncManager for batch-adding torrents from memory via client.AddTorrentsFromMemoryCtx; triggers post-modification sync with "add_torrents_batch" event. Note: method implementation appears duplicated in diff.

Sequence Diagram

sequenceDiagram
    actor Client
    participant TorrentsHandler
    participant SyncManager
    participant qBittorrent API
    
    Client->>TorrentsHandler: POST /torrents/add (multiple files)
    activate TorrentsHandler
    
    TorrentsHandler->>TorrentsHandler: addTorrents(files, options)
    
    alt torrentAdder (test/mock) available
        TorrentsHandler->>TorrentsHandler: torrentAdder.AddTorrents()
    else production flow
        TorrentsHandler->>SyncManager: AddTorrents(files, options)
        activate SyncManager
        
        SyncManager->>qBittorrent API: client.AddTorrentsFromMemoryCtx(files)
        activate qBittorrent API
        qBittorrent API-->>SyncManager: success/error
        deactivate qBittorrent API
        
        rect rgb(200, 220, 255)
        note over SyncManager: Post-modification sync
        SyncManager->>SyncManager: sync("add_torrents_batch")
        end
        
        SyncManager-->>TorrentsHandler: error or nil
        deactivate SyncManager
    end
    
    TorrentsHandler-->>Client: response
    deactivate TorrentsHandler
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

enhancement, backend, torrent

Poem

🐰 Hoppy batches, files combine,
No more timeouts, one request fine!
Many torrents? Single call—
The swiftest hop of them all!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR contains a duplicate AddTorrents implementation in sync_manager.go and dependency updates that extend slightly beyond the core batch upload feature. Remove the duplicate AddTorrents method in sync_manager.go and clarify the necessity of the go-qbittorrent dependency update within the PR description.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: implementing batch torrent uploads in a single request to address timeout issues.
Linked Issues check ✅ Passed The PR implements batch torrent upload functionality to resolve context deadline exceeded errors when adding large torrent batches, directly addressing issue #883's root cause.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/batch-torrent-upload

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@s0up4200 s0up4200 added api torrent enhancement New feature or request labels Dec 29, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
internal/api/handlers/torrents_add_test.go (2)

876-908: fullMockSyncManager’s AddTorrents support matches the new interface

The added addTorrentsCalls tracking plus AddTorrents method correctly satisfies the extended torrentAdder interface and mirrors the existing single-file mock behavior. You may want a dedicated test that posts multiple torrent files and asserts addTorrentsCalls is hit, to lock in the new batch path end‑to‑end.


455-457: Comment about negative indexer IDs is now stale

The note here still claims negative indexer_id values are treated like “no indexer”, but the handler now explicitly rejects indexer_id <= 0 with a 400. Consider updating or removing this note to avoid confusion.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2fadd01 and 6ae55b2.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • go.mod
  • internal/api/handlers/torrents.go
  • internal/api/handlers/torrents_add_test.go
  • internal/qbittorrent/sync_manager.go
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: Audionut
Repo: autobrr/qui PR: 553
File: internal/services/crossseed/service.go:1045-1082
Timestamp: 2025-11-06T12:11:04.963Z
Learning: The autobrr/qui project uses a custom go-qbittorrent client library (github.com/autobrr/go-qbittorrent) that supports both "paused" and "stopped" parameters when adding torrents via the options map. Both parameters should be set together when controlling torrent start state, as seen in internal/services/crossseed/service.go and throughout the codebase.
Learnt from: s0up4200
Repo: autobrr/qui PR: 632
File: internal/backups/service.go:1401-1404
Timestamp: 2025-11-25T22:46:03.762Z
Learning: In qui's backup service (internal/backups/service.go), background torrent downloads initiated during manifest import intentionally use a fire-and-forget pattern with the shared service context (s.ctx). Per-run cancellation is not needed, as orphaned downloads completing after run deletion are considered harmless and acceptable. This design prioritizes simplicity over per-run lifecycle management for background downloads.
📚 Learning: 2025-11-06T12:11:04.963Z
Learnt from: Audionut
Repo: autobrr/qui PR: 553
File: internal/services/crossseed/service.go:1045-1082
Timestamp: 2025-11-06T12:11:04.963Z
Learning: The autobrr/qui project uses a custom go-qbittorrent client library (github.com/autobrr/go-qbittorrent) that supports both "paused" and "stopped" parameters when adding torrents via the options map. Both parameters should be set together when controlling torrent start state, as seen in internal/services/crossseed/service.go and throughout the codebase.

Applied to files:

  • go.mod
  • internal/api/handlers/torrents.go
📚 Learning: 2025-12-11T08:40:01.329Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 746
File: internal/services/reannounce/service.go:480-481
Timestamp: 2025-12-11T08:40:01.329Z
Learning: In autobrr/qui's internal/services/reannounce/service.go, the hasHealthyTracker, getProblematicTrackers, and getHealthyTrackers functions intentionally match qbrr's lenient tracker health logic (skip unregistered trackers and check if any other tracker is healthy) rather than go-qbittorrent's strict isTrackerStatusOK logic (which treats unregistered as an immediate failure). For multi-tracker torrents, if one tracker is working, reannouncing won't help. The duplication of the health check logic across these three functions is acceptable as it's a simple one-liner, and extracting it would add unnecessary complexity.

Applied to files:

  • go.mod
📚 Learning: 2025-11-25T22:46:03.762Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 632
File: internal/backups/service.go:1401-1404
Timestamp: 2025-11-25T22:46:03.762Z
Learning: In qui's backup service (internal/backups/service.go), background torrent downloads initiated during manifest import intentionally use a fire-and-forget pattern with the shared service context (s.ctx). Per-run cancellation is not needed, as orphaned downloads completing after run deletion are considered harmless and acceptable. This design prioritizes simplicity over per-run lifecycle management for background downloads.

Applied to files:

  • go.mod
  • internal/api/handlers/torrents.go
  • internal/qbittorrent/sync_manager.go
📚 Learning: 2025-12-28T18:44:10.496Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 876
File: internal/logstream/hub_test.go:188-192
Timestamp: 2025-12-28T18:44:10.496Z
Learning: In Go 1.25 (Aug 2025), use wg.Go(func()) to spawn a goroutine and automate the Add/Done lifecycle. Replace manual patterns like wg.Add(1); go func(){ defer wg.Done(); ... }() with wg.Go(func(){ ... }). Ensure the codebase builds with Go 1.25+ and apply this in relevant Go files (e.g., internal/logstream/hub_test.go). If targeting older Go versions, maintain the existing pattern.

Applied to files:

  • internal/api/handlers/torrents.go
  • internal/api/handlers/torrents_add_test.go
  • internal/qbittorrent/sync_manager.go
🔇 Additional comments (6)
go.mod (1)

13-13: go-qbittorrent version bump looks appropriate for batch support

The pseudo-version update is reasonable for picking up the new batch-adding API, as long as CI/builds are green and go mod tidy has been run to sync the module graph.

internal/qbittorrent/sync_manager.go (2)

218-225: NewSyncManager field initialization remains consistent

The adjusted struct literal keeps the same defaults (TTL cache, debounce, concurrency limits, tracker caches) and doesn’t alter behavior; no issues here.


1501-1521: AddTorrents implementation cleanly mirrors existing AddTorrent behavior

The batch method correctly:

  • No-ops on an empty files slice.
  • Reuses getClientAndSyncManager for instance resolution.
  • Delegates to client.AddTorrentsFromMemoryCtx with the shared options map.
  • Triggers syncAfterModification only on success.

This matches the existing AddTorrent pattern while reducing HTTP chatter for large batches.

internal/api/handlers/torrents.go (3)

30-36: torrentAdder interface extension keeps tests and production in sync

Adding AddTorrents to the interface and plumbing it through getAppPreferences/addTorrents maintains a clean seam between real SyncManager and mocks; the handler stays agnostic to the concrete implementation.


96-103: addTorrents wrapper is consistent with other adapter helpers

The new helper mirrors addTorrent/addTorrentFromURLs, preferring the injected torrentAdder in tests and falling back to syncManager.AddTorrents in production. This keeps handler logic simple and centralizes the decision.


515-529: Batch file path looks good; verify clients are OK with new failedFiles shape

Switching to a single addTorrents call for all torrentFiles is appropriate for large batches and keeps context deadlines applied once across the whole upload. On batch failure you now emit a single entry with Filename: "batch" instead of per-filename failures, while still preserving per-file read errors separately. That’s reasonable given the API limitation, but it slightly changes the response shape—worth confirming any UI/API consumers handle the "batch" sentinel gracefully.

@s0up4200 s0up4200 changed the base branch from main to develop January 2, 2026 07:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api enhancement New feature or request torrent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG Adding many torrents causes a post file request error "context deadline exceeded"

1 participant