Skip to content

perf: speed up existing webui content downloads#1482

Open
s0up4200 wants to merge 6 commits intodevelopfrom
codex/content-download-speedup-safe
Open

perf: speed up existing webui content downloads#1482
s0up4200 wants to merge 6 commits intodevelopfrom
codex/content-download-speedup-safe

Conversation

@s0up4200
Copy link
Collaborator

@s0up4200 s0up4200 commented Feb 19, 2026

Improves performance of the existing torrent content-file download flow in the WebUI by bypassing HTTP compression on that download endpoint. Keeps existing local-filesystem access checks and path-safety validation while reducing CPU overhead and improving large-file throughput.

Summary by CodeRabbit

  • New Features

    • Direct streaming for full-file torrent downloads for faster, more efficient transfers.
  • Improvements

    • Smarter file-location resolution prioritizing save/download paths before content-derived paths.
    • Compression bypass for direct-download routes to improve transfer semantics and performance.
    • Enforced local-filesystem access checks to block downloads when not permitted.
    • UI: disable download/rename actions in incognito mode.
  • Tests

    • Added coverage for path precedence, conditional GET behavior, and compression-skip logic.

@s0up4200 s0up4200 changed the title feat: add content file downloads and speed up web streams perf: speed up existing webui content downloads Feb 19, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d432f080a9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

No actionable comments were generated in the recent review. 🎉


Walkthrough

Adds disk-based torrent file download handling with traversal-safe path resolution, local-filesystem access gating, a fast-path direct stream for full-file downloads (with write-deadline), and a compression bypass for the download endpoint. Also updates TorrentsHandler constructor to accept an instanceStore and adjusts related tests and UI guards for incognito/local access.

Changes

Cohort / File(s) Summary
Torrent handlers & tests
internal/api/handlers/torrents.go, internal/api/handlers/torrents_download_test.go
Added DownloadTorrentContentFile with secure candidate path resolution (save/download/content ordering), traversal protection, require-local-access gating, fast-path full-file streaming with write-deadline, and updated constructor to accept instanceStore. Tests added/modified for save-path precedence, Conditional GET (304), and candidate ordering.
Server compression
internal/api/server.go, internal/api/server_test.go
Introduced skipCompressionForPath(requestPath string) bool and wrapped compression middleware to bypass compression for the torrent file download endpoint. Unit test verifies path matching behavior.
Frontend guards
web/src/components/torrents/TorrentDetailsPanel.tsx, web/src/components/torrents/details/TorrentFileTable.tsx
Disabled download/rename actions when local filesystem access is absent or incognitoMode is enabled by gating onDownloadFile and adding disabled={incognitoMode} to rename menu items. Minor prop/handler wiring updates; no public API changes.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Handler as DownloadTorrentContentFile
    participant AccessCheck as requireLocalAccess
    participant Resolver as syncManager / contentResolver
    participant PathUtil as resolveTorrentFilePath
    participant FS as Filesystem

    Client->>Handler: GET /api/instances/{id}/torrents/{hash}/files/{index}/download
    Handler->>AccessCheck: verify local filesystem access
    alt access denied
        AccessCheck-->>Client: 403 Forbidden
    else access granted
        Handler->>Resolver: fetch torrent metadata
        Resolver-->>Handler: file list & metadata
        Handler->>PathUtil: build ordered path candidates (save, download, content)
        PathUtil-->>Handler: validated absolute paths
        Handler->>FS: open selected file
        FS-->>Handler: file handle & stats
        alt full-file fast-path
            Handler-->>Client: set headers (Accept-Ranges, Content-Length), stream file (write-deadline)
        else fallback
            Handler-->>Client: ServeContent fallback (supports 304/412)
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

enhancement, web, backend, torrent

Poem

🐰 I found the paths where torrents hide,
Save first, then download, content beside.
No compressing hops on this fast-track,
With deadlines set, the stream goes zack-zack! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'perf: speed up existing webui content downloads' directly aligns with the primary objective of improving performance of torrent content-file downloads by bypassing HTTP compression.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 codex/content-download-speedup-safe

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

…load-speedup-safe

# Conflicts:
#	internal/api/handlers/torrents.go
#	internal/api/handlers/torrents_download_test.go
Copy link
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: 1

🧹 Nitpick comments (3)
web/src/components/torrents/details/TorrentFileTable.tsx (1)

469-480: Consider adding disabled={incognitoMode} to Rename menu items for consistency.

The new Download item is correctly disabled in incognito mode, matching the pattern in TorrentFileTree.tsx. However, the existing Rename File/Folder items in this component don't have the disabled prop, unlike their counterparts in TorrentFileTree.tsx (Line 367, 464). This is a pre-existing inconsistency but worth addressing for UI consistency.

♻️ Suggested fix for consistency
                       {isFile && onRenameFile && (
-                        <ContextMenuItem onClick={() => onRenameFile(node.id)}>
+                        <ContextMenuItem onClick={() => onRenameFile(node.id)} disabled={incognitoMode}>
                           <Pencil className="h-3.5 w-3.5 mr-2" />
                           Rename File
                         </ContextMenuItem>
                       )}
                       {!isFile && onRenameFolder && (
-                        <ContextMenuItem onClick={() => onRenameFolder(node.id)}>
+                        <ContextMenuItem onClick={() => onRenameFolder(node.id)} disabled={incognitoMode}>
                           <Pencil className="h-3.5 w-3.5 mr-2" />
                           Rename Folder
                         </ContextMenuItem>
                       )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/torrents/details/TorrentFileTable.tsx` around lines 469 -
480, Add the same incognito-mode disabling to the Rename menu items: when
rendering the ContextMenuItem for rename, pass disabled={incognitoMode} so both
the Rename File and Rename Folder options respect incognitoMode; update the two
places that render ContextMenuItem with onRenameFile(node.id) and
onRenameFolder(node.id) (symbols: isFile, onRenameFile, onRenameFolder,
ContextMenuItem, incognitoMode in TorrentFileTable.tsx) to include the disabled
prop.
internal/api/server.go (1)

678-691: Path matching logic is correct but could be more explicit.

The function correctly identifies the download endpoint pattern by checking the last 8 path segments, which handles the baseURL prefix gracefully. The check at indices 0, 1, 3, 5, 7 matches the static parts (api, instances, torrents, files, download) while skipping the dynamic parameters.

Consider adding a brief comment explaining why the last 8 segments are used (to handle baseURL prefixes).

📝 Suggested documentation
 func skipCompressionForPath(requestPath string) bool {
 	cleanPath := path.Clean("/" + requestPath)
 	segments := strings.Split(strings.Trim(cleanPath, "/"), "/")
 	if len(segments) < 8 {
 		return false
 	}
 
+	// Check last 8 segments to handle baseURL prefixes (e.g., /qui/api/...)
 	tail := segments[len(segments)-8:]
 	return tail[0] == "api" &&
 		tail[1] == "instances" &&
 		tail[3] == "torrents" &&
 		tail[5] == "files" &&
 		tail[7] == "download"
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/server.go` around lines 678 - 691, The function
skipCompressionForPath uses the last 8 path segments to detect the download
endpoint but lacks context; add a concise comment above skipCompressionForPath
explaining that trimming to the last 8 segments (tail) is intentional to allow
arbitrary baseURL prefixes while still matching the fixed pattern
"api/instances/:instanceID/torrents/:torrentID/files/:fileID/download"; mention
variables segments and tail in the comment so future readers understand why the
length check and indexed comparisons (tail[0], tail[1], tail[3], tail[5],
tail[7]) are used.
internal/api/handlers/torrents_download_test.go (1)

138-156: The fixture reuse suggestion cannot be implemented — no integration fixtures exist in internal/qbittorrent/.

While table-driven tests are preferred per guidelines, consolidating these tests would reduce clarity: they test distinct error scenarios (missing instance, forbidden access, missing file, traversal attacks, nil properties) with different mock setups and assertions. The existing helpers (createInstanceStoreWithInstance, newDownloadRequest) already reduce duplication. Consider table-driven consolidation only for tests sharing identical setup and assertion patterns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/handlers/torrents_download_test.go` around lines 138 - 156,
These tests cover distinct error scenarios and cannot be safely merged into a
table-driven test because each uses different mock setups and assertions; keep
the separate test functions (e.g.,
TestDownloadTorrentContentFile_ReturnsNotFoundForMissingInstance) and existing
helpers (createInstanceStoreWithInstance, newDownloadRequest,
mockContentResolver) as-is, and add a brief comment above the block of tests
explaining why a table-driven refactor or shared integration fixture under
internal/qbittorrent/ was not used to prevent future maintainers from attempting
the consolidation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/torrents/TorrentDetailsPanel.tsx`:
- Around line 658-662: The download action should not be exposed when
incognitoMode is true: update the UI rendering in TorrentDetailsPanel to hide or
disable any download buttons/menu items rather than relying on the early return
in handleDownloadFile; locate the download action render points (the elements
that call handleDownloadFile and related menu items in this component) and
conditionally render nothing or a disabled control when incognitoMode is true,
and keep handleDownloadFile as a defensive guard. Ensure the UX is consistent
across all places the download action appears (including the other download
action occurrences referenced in this file).

---

Nitpick comments:
In `@internal/api/handlers/torrents_download_test.go`:
- Around line 138-156: These tests cover distinct error scenarios and cannot be
safely merged into a table-driven test because each uses different mock setups
and assertions; keep the separate test functions (e.g.,
TestDownloadTorrentContentFile_ReturnsNotFoundForMissingInstance) and existing
helpers (createInstanceStoreWithInstance, newDownloadRequest,
mockContentResolver) as-is, and add a brief comment above the block of tests
explaining why a table-driven refactor or shared integration fixture under
internal/qbittorrent/ was not used to prevent future maintainers from attempting
the consolidation.

In `@internal/api/server.go`:
- Around line 678-691: The function skipCompressionForPath uses the last 8 path
segments to detect the download endpoint but lacks context; add a concise
comment above skipCompressionForPath explaining that trimming to the last 8
segments (tail) is intentional to allow arbitrary baseURL prefixes while still
matching the fixed pattern
"api/instances/:instanceID/torrents/:torrentID/files/:fileID/download"; mention
variables segments and tail in the comment so future readers understand why the
length check and indexed comparisons (tail[0], tail[1], tail[3], tail[5],
tail[7]) are used.

In `@web/src/components/torrents/details/TorrentFileTable.tsx`:
- Around line 469-480: Add the same incognito-mode disabling to the Rename menu
items: when rendering the ContextMenuItem for rename, pass
disabled={incognitoMode} so both the Rename File and Rename Folder options
respect incognitoMode; update the two places that render ContextMenuItem with
onRenameFile(node.id) and onRenameFolder(node.id) (symbols: isFile,
onRenameFile, onRenameFolder, ContextMenuItem, incognitoMode in
TorrentFileTable.tsx) to include the disabled prop.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5610c786d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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.

1 participant