mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
On 1.0 a vCard export sent every blob photo through the avatar download
queue, a burst of 40 and then 6 a second, two at a time: about 160 s for
1000 photos. Avatars and print (which gives up after 5 s) waited behind
it, the Export button showed nothing, and a second click started the
whole export again.
- Export fetches the blob media of all cards with Blob/get
(`data:asBase64`), asking for the sizes first and packing the requests
to about 4 MiB. `urn:ietf:params:jmap:blob` goes into `using` only
when the session advertises it; without it, or when a request is
refused, the rest is downloaded one by one behind avatars and print,
which keep half of the download burst for themselves.
- The export buttons in the settings, the bulk bar, the context menu and
the detail menu show that an export is running and ignore clicks until
it is done.
- When the photo changes, the form rebuilds `media` on the card as the
server has it now. A logo or sound blobId from the list's older copy
made 1.0 refuse the whole update ("The following blobIds do not exist
or are not accessible").
- A new photo takes the key, place, `pref`, `label` and `contexts` of the
one it replaces, so it stays the photo shown on cards with several.
Removing the photo removes every photo the card had instead of only the
shown one, which brought up the next; logos and sounds stay.
- A card whose login has no client downloads nothing, instead of asking
the active login's server and remembering the refusal for two minutes.
- Photo downloads run at half the server's `maxConcurrentRequests`
instead of a fixed two.
- `invalidProperties` naming `media/...` also counts as a refused blobId
in media, not only `media` itself.
51 lines
1.9 KiB
TypeScript
51 lines
1.9 KiB
TypeScript
/**
|
|
* A JMAP session advertises hard ceilings on what one request may carry: how
|
|
* many method calls it holds (`maxCallsInRequest`) and how many objects a
|
|
* single /get or /set may touch (`maxObjectsInGet`, `maxObjectsInSet`). Going
|
|
* over any of them fails the *whole* request, not the surplus, so a batch built
|
|
* from a list the user controls - tags, category tabs, a multi-select, an
|
|
* import - is split against the advertised limit before it is sent.
|
|
*
|
|
* Stalwart defaults to 16 method calls and 500 objects, so the ceilings are low
|
|
* enough to reach with ordinary use: nine tags is already 18 calls.
|
|
*/
|
|
|
|
/** Split `items` into consecutive batches of at most `size` entries. */
|
|
export function batched<T>(items: T[], size: number): T[][] {
|
|
const step = Math.max(1, Math.floor(size));
|
|
const result: T[][] = [];
|
|
for (let i = 0; i < items.length; i += step) {
|
|
result.push(items.slice(i, i + step));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Split `items` into consecutive batches whose sizes add up to at most
|
|
* `budget`, and of at most `maxCount` entries. An item larger than the budget
|
|
* gets a batch of its own; an item without a size counts as the whole budget.
|
|
*/
|
|
export function batchedBySize<T>(items: T[], sizeOf: (item: T) => number | undefined, budget: number, maxCount: number): T[][] {
|
|
const limit = Math.max(1, Math.floor(maxCount));
|
|
const result: T[][] = [];
|
|
let batch: T[] = [];
|
|
let total = 0;
|
|
for (const item of items) {
|
|
const size = sizeOf(item) ?? budget;
|
|
if (batch.length > 0 && (total + size > budget || batch.length >= limit)) {
|
|
result.push(batch);
|
|
batch = [];
|
|
total = 0;
|
|
}
|
|
batch.push(item);
|
|
total += size;
|
|
}
|
|
if (batch.length > 0) result.push(batch);
|
|
return result;
|
|
}
|
|
|
|
/** How many items fit in one request when each item costs `callsPerItem` method calls. */
|
|
export function itemsPerRequest(maxCalls: number, callsPerItem: number): number {
|
|
return Math.max(1, Math.floor(maxCalls / callsPerItem));
|
|
}
|