From b07aad512ad92ba5fa46bdc2f49b91dbb2a975ff Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:45:15 +0100 Subject: [PATCH] fix: move the import progress bar message by message for mbox files --- lib/mail-import/__tests__/engine.test.ts | 8 ++++++++ lib/mail-import/sources.ts | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/mail-import/__tests__/engine.test.ts b/lib/mail-import/__tests__/engine.test.ts index 4fb5d6a74..6a481208c 100644 --- a/lib/mail-import/__tests__/engine.test.ts +++ b/lib/mail-import/__tests__/engine.test.ts @@ -129,6 +129,14 @@ describe('runImport', () => { expect(fake.imported[0].receivedAt).toBe('2020-05-05T10:00:00Z'); }); + it('moves the progress message by message through a small mbox', async () => { + const fake = fakeClient(); + const fractions: number[] = []; + await runImport(options(fake.client, [file(mbox(10))], { onProgress: (p) => { if (p.phase === 'running') fractions.push(p.fraction); } })); + const between = new Set(fractions.filter((f) => f > 0 && f < 1).map((f) => f.toFixed(2))); + expect(between.size).toBeGreaterThanOrEqual(5); + }); + it('puts several messages in one Email/import and keeps uploads within the server limit', async () => { const fake = fakeClient({ uploadConcurrency: 4 }); await runImport(options(fake.client, [file(mbox(45))])); diff --git a/lib/mail-import/sources.ts b/lib/mail-import/sources.ts index 0d44bae7f..06c66121a 100644 --- a/lib/mail-import/sources.ts +++ b/lib/mail-import/sources.ts @@ -180,11 +180,14 @@ async function* mboxFromChunks( options: SourceOptions, startIndex: number, onIndex: (next: number) => void, + /** Called with each message's offset in the stream, for a progress that moves message by message. */ + onOffset?: (offset: number) => void, ): ItemSource { const splitter = new MboxSplitter({ maxMessageBytes: options.maxMessageBytes }); let index = startIndex; const emit = function* (messages: MboxMessage[]): Generator { for (const m of messages) { + onOffset?.(m.offset); const i = index++; yield i < options.skipBefore ? { kind: "skipped", index: i, reason: "resumed" } : mboxItem(m, i, folder); } @@ -206,7 +209,8 @@ async function* fileChunks(file: Blob, onProgress?: (fraction: number) => void): } export async function* mboxSource(file: File, options: SourceOptions): ItemSource { - yield* mboxFromChunks(fileChunks(file, options.onProgress), [], options, 0, () => {}); + // Reads run ahead in 1 MB pieces; the messages handed over say how far the import really is. + yield* mboxFromChunks(fileChunks(file), [], options, 0, () => {}, (offset) => options.onProgress?.(offset / Math.max(1, file.size))); options.onProgress?.(1); }