Skip to content

Paginate mylist sync to handle TorBox's 10,000-item per-response cap#2

Merged
mainlink0435 merged 1 commit into
mainlink0435:mainfrom
Fredddi43:paginate-mylist-sync
Jun 26, 2026
Merged

Paginate mylist sync to handle TorBox's 10,000-item per-response cap#2
mainlink0435 merged 1 commit into
mainlink0435:mainfrom
Fredddi43:paginate-mylist-sync

Conversation

@Fredddi43

Copy link
Copy Markdown
Contributor

What does this fix?

warpbox's metadata sync silently drops the oldest torrents on accounts with
more than 10k torrents. TorBox's mylist endpoints cap each response at ~10,000
items regardless of the limit param, and listGeneric made a single,
un-paginated call — so only the newest 10,000 ever synced. The missing torrents
never enter the SQLite tree, so they don't appear over WebDAV and their files
are unbrowsable/unplayable.

This paginates listGeneric with offset in fixed listPageSize (1000)
windows until a short page ends the list, accumulating all items.
ListFilesParams.Limit, when > 0, becomes a ceiling on the total fetched rather
than a per-request limit. Both torrents and usenet go through listGeneric, so
both are fixed.

refs #1

How to test

  1. On a TorBox account with >10,000 torrents, confirm the cap:
    GET /v1/api/torrents/mylist?bypass_cache=true&limit=50000&offset=0 returns
    exactly 10,000 items.
  2. Build and run warpbox, let it sync, and compare the WebDAV root
    (/webdav/__all__/) entry count to your real torrent count:
    • Before: ~10,000 (oldest torrents missing from the mount).
    • After: all items (e.g. 10,899); previously-missing older torrents now
      list and play.
  3. CGO_ENABLED=1 go test ./internal/torbox/TestListPaginatesPastCap
    covers full-page → short-page accumulation across multiple requests.

Checklist

  • Tests pass (CGO_ENABLED=1 go test ./...)
  • go vet ./... is clean
  • I've opened an issue and referenced it here (refs #1)
  • This is a bug fix, not a new feature

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings June 24, 2026 13:13
TorBox's /torrents/mylist and /usenet/mylist cap each response at ~10,000
items regardless of the requested `limit`. listGeneric made a single,
un-paginated call, so on accounts with more than 10k torrents the OLDEST
items were silently dropped from the sync — they never entered the WebDAV
tree and their files became unbrowsable/unplayable, while the newest 10k
masked the loss.

Page through with `offset` in fixed windows (listPageSize = 1000) until a
short page signals the end, accumulating every item. `params.Limit`, when
> 0, now acts as a ceiling on the TOTAL number fetched rather than a
per-request limit.

Observed on a ~10.9k-torrent account: a single `limit=50000` call returned
exactly 10,000; the remaining ~899 (oldest) torrents were missing from the
mount and only reappeared after pagination.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Fredddi43 Fredddi43 force-pushed the paginate-mylist-sync branch from a1ea1ba to 7f87b71 Compare June 24, 2026 13:17

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 fixes TorBox “mylist” sync truncation for accounts with more than ~10,000 items by paginating listGeneric requests using offset and accumulating results until the final (short) page is reached, ensuring older torrents/usenet entries are included in the local SQLite tree and thus exposed via WebDAV.

Changes:

  • Add pagination to Client.listGeneric using a fixed listPageSize window and offset iteration.
  • Redefine ListFilesParams.Limit semantics to act as a total fetch ceiling (rather than a per-request limit).
  • Add a regression test to ensure pagination continues past the per-response cap behavior.

Reviewed changes

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

File Description
internal/torbox/client.go Implements paginated fetching for TorBox mylist endpoints and returns accumulated results.
internal/torbox/client_test.go Updates param expectations and adds a pagination regression test.

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

Comment thread internal/torbox/client.go
Comment on lines +169 to 184
for {
u := base.JoinPath(endpoint)
q := u.Query()
q.Set("bypass_cache", strconv.FormatBool(params.BypassCache))
q.Set("offset", strconv.Itoa(offset))
q.Set("limit", strconv.Itoa(listPageSize))
u.RawQuery = q.Encode()

req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody)
if err != nil {
return nil, fmt.Errorf("torbox: creating %s request: %w", label, err)
}
req.Header.Set("Authorization", "Bearer "+c.apiKey)

slog.Debug("torbox "+label, "offset", params.Offset, "limit", params.Limit)
slog.Debug("torbox "+label, "offset", offset, "limit", listPageSize)

Comment on lines +416 to +428
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
var data []Torrent
switch r.URL.Query().Get("offset") {
case "0":
data = make([]Torrent, listPageSize) // full page → more follow
case strconv.Itoa(listPageSize):
data = make([]Torrent, 7) // short page → end
default:
t.Errorf("unexpected offset %q", r.URL.Query().Get("offset"))
}
json.NewEncoder(w).Encode(apiResponse[[]Torrent]{Data: data, Success: boolPtr(true)})
}))
@mainlink0435 mainlink0435 merged commit fd3742d into mainlink0435:main Jun 26, 2026
mainlink0435 added a commit that referenced this pull request Jun 26, 2026
sync.limit was a holdover from before PR #2's pagination fix. It capped
the number of items fetched per sync cycle, but the pagination loop already
handles termination via the short-page signal. Removing the ceiling means
all torrents and Usenet items are always synced — no silent data loss on
larger accounts.

Deleted:
- Limit field from ListFilesParams, SyncConfig, SyncWorker, server Config
- maxTotal ceiling check in listGeneric loop
- SyncLimit display from landing page
- sync.limit config key, defaults, validation, tests
- All docs references to sync.limit
Fredddi43 pushed a commit to Fredddi43/warpbox that referenced this pull request Jun 26, 2026
…ize)

The const listPageSize (1000) from PR mainlink0435#2 is now an adjustable config
parameter with default 5000. A new sync.list_page_size config option
controls the per-request window, with a safe fallback defaultListPageSize
(1000) if the param is not set.

Changes:
- internal/config/config.go — ListPageSize field, default 5000, validate 1–10000
- internal/torbox/client.go — PageSize on ListFilesParams, renamed const
- internal/metadata/sync.go — listPageSize field threaded through to API calls
- cmd/warpbox/main.go — pass cfg.Sync.ListPageSize to NewSyncWorker
- config.yml.example, config.yml — document the new option
- docs/config-tuning.md, docs/tech-spec.md — reference and profile updates
- config_test.go, sync_test.go — test callers updated
@Fredddi43 Fredddi43 deleted the paginate-mylist-sync branch June 26, 2026 07:36
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.

3 participants