Paginate load_mail_ids_for_folder above the server's 1000-per-page cap

Replace the silent `min(limit, 1000)` cap with proper pagination: the
server rejects a single `load_range` count > 1000, so for any user-facing
`sync_limit` above that we now loop 1000-entry pages, advancing the
cursor with the last (oldest in DESC) entry's element id, until we have
the requested count or the list is exhausted. `limit == 0` still
delegates to `load_all`, which already paginates the whole list.

Honors what the user typed (1050 means 1050, not 1000).
This commit is contained in:
Anthony
2026-05-28 12:35:32 +02:00
parent 14bd40b87b
commit 8a5683ef04
+35 -8
View File
@@ -168,25 +168,52 @@ impl TutaSession {
entries_list_id: &tutasdk::GeneratedId,
limit: usize,
) -> Result<Vec<Mail>, ApiCallError> {
// limit == 0 means "all": paginate the whole entries list. Otherwise
// load a single page (newest first), capped at the server-side max of
// 1000 per request. A higher user-facing `sync_limit` (e.g. 2000)
// would need multi-page stitching here; for now we honor the cap and
// any excess is picked up by the realtime event bus.
const SERVER_LOAD_RANGE_MAX: usize = 1000;
// The server rejects a single `load_range` with `count` > 1000 (400
// Bad request). For higher user-facing `sync_limit` we page through
// by 1000 each call, advancing the cursor with the last entry id of
// the previous page (DESC = newest first, oldest at the end). The
// `limit == 0` path delegates to `load_all` which already paginates.
const SERVER_PAGE: usize = 1000;
let entries: Vec<MailSetEntry> = if limit == 0 {
self.crypto_client()
.load_all(entries_list_id, ListLoadDirection::DESC)
.await?
} else {
} else if limit <= SERVER_PAGE {
self.crypto_client()
.load_range(
entries_list_id,
&CustomId::default(),
limit.min(SERVER_LOAD_RANGE_MAX),
limit,
ListLoadDirection::DESC,
)
.await?
} else {
let mut out: Vec<MailSetEntry> = Vec::with_capacity(limit);
let mut start = CustomId::default();
while out.len() < limit {
let want = (limit - out.len()).min(SERVER_PAGE);
let page: Vec<MailSetEntry> = self
.crypto_client()
.load_range(entries_list_id, &start, want, ListLoadDirection::DESC)
.await?;
let got = page.len();
if got == 0 {
break;
}
// Use the last (oldest in DESC) entry's element id as the
// exclusive cursor for the next page.
let next_start = page
.last()
.and_then(|e| e._id.as_ref())
.map(|id| id.element_id.clone());
out.extend(page);
if got < want {
break; // server has no more
}
let Some(ns) = next_start else { break };
start = ns;
}
out
};
// Group entries by list_id for batch loading