mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
A persisted account id with no subject yet read as decided: Everyone's noOffline wiped mail a group scope allowed, and a wider scope went out at every reload. A public, partial policy counted as the organisation's word, and turned on the desktop default in a forbidding organisation. - an organisation's cap on one file now binds a pin - Clear and the off switch read what would be lost when asked, messages, changes and drafts, and Clear waits for a connection - turning one account off survives the last one going off, and off then on no longer warns of an eviction - a tab that takes over the store sends no scope until it has heard the policy - a dropped account is judged by what applies to anyone - a choice reaches the store once it settles; progress announces phases only - restored Pro tabs ask for their titles
63 lines
2.3 KiB
TypeScript
63 lines
2.3 KiB
TypeScript
/**
|
|
* How far a first sync got, and how long it may still take. The store reports
|
|
* batches of a phase: messages listed, then threads, then bodies, then files.
|
|
*/
|
|
|
|
export interface SyncProgress {
|
|
area: string;
|
|
phase: string;
|
|
done: number;
|
|
total: number | null;
|
|
/** When this phase started here, and how far it was then. */
|
|
startedAt: number;
|
|
startedDone: number;
|
|
at: number;
|
|
}
|
|
|
|
/**
|
|
* Listing a message is about ten times as fast as fetching its body and
|
|
* indexing it (bench rig, 50,000 messages: 35 s to list, 400 s in all). So
|
|
* while listing, the time left includes the bodies that come after it.
|
|
*/
|
|
const BODY_COST_PER_LIST_ITEM = 10;
|
|
/** An estimate from less than this jumps about too much to show. */
|
|
const MIN_SAMPLE_MS = 5_000;
|
|
/** A pause this long (asleep, offline) says nothing about the rate: the sample starts again. */
|
|
const SAMPLE_GAP_MS = 60_000;
|
|
/** Too small to be worth a progress line: new mail arriving in a normal pass. */
|
|
const MIN_ITEMS_SHOWN = 50;
|
|
|
|
export function nextProgress(
|
|
previous: SyncProgress | undefined,
|
|
event: { area: string; phase: string; done: number; total: number | null },
|
|
now: number,
|
|
): SyncProgress {
|
|
const samePhase = previous?.area === event.area && previous.phase === event.phase && event.done >= previous.done && now - previous.at < SAMPLE_GAP_MS;
|
|
return {
|
|
area: event.area,
|
|
phase: event.phase,
|
|
done: event.done,
|
|
total: event.total,
|
|
startedAt: samePhase ? previous.startedAt : now,
|
|
startedDone: samePhase ? previous.startedDone : event.done,
|
|
at: now,
|
|
};
|
|
}
|
|
|
|
export function progressWorthShowing(progress: SyncProgress | undefined): progress is SyncProgress {
|
|
return !!progress && (progress.total === null || progress.total >= MIN_ITEMS_SHOWN) && progress.done >= 0;
|
|
}
|
|
|
|
/** Minutes left, rounded up; null while there's nothing sensible to say. */
|
|
export function minutesLeft(progress: SyncProgress): number | null {
|
|
const { total, done, startedAt, startedDone, at } = progress;
|
|
if (total === null || total <= done) return null;
|
|
const elapsed = at - startedAt;
|
|
const moved = done - startedDone;
|
|
if (elapsed < MIN_SAMPLE_MS || moved <= 0) return null;
|
|
const perItem = elapsed / moved;
|
|
let left = (total - done) * perItem;
|
|
if (progress.phase === 'list') left += total * perItem * BODY_COST_PER_LIST_ITEM;
|
|
return Math.max(1, Math.ceil(left / 60_000));
|
|
}
|