From d714957f98bfa77a604904553074616f0c2e0304 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 04:58:55 +0100 Subject: [PATCH 01/22] feat: import mbox, EML and ZIP mail with progress, pause and resume --- app/(main)/[locale]/layout.tsx | 2 + components/email/email-viewer.tsx | 52 +- components/layout/mailbox-context-menu.tsx | 3 +- .../__tests__/mail-import-dialog.test.tsx | 133 +++++ .../mail-transfer/mail-backup-settings.tsx | 18 + .../mail-transfer/mail-import-dialog.tsx | 300 +++++++++++ .../mail-transfer/mail-transfer-host.tsx | 58 +++ components/mail-transfer/start-import.ts | 51 ++ .../mail-transfer/transfer-progress.tsx | 34 ++ components/mail/mail-app.tsx | 45 +- components/settings/about-data-settings.tsx | 3 + lib/eml-import.ts | 5 + lib/jmap/client-interface.ts | 15 + lib/jmap/client.ts | 29 +- lib/mail-export/__tests__/zip.test.ts | 76 +++ lib/mail-export/crc32.ts | 20 + lib/mail-export/zip-writer.ts | 251 +++++++++ lib/mail-import/__tests__/engine.test.ts | 223 ++++++++ lib/mail-import/__tests__/mbox-status.test.ts | 42 ++ lib/mail-import/__tests__/mbox.test.ts | 155 ++++++ lib/mail-import/checkpoints.ts | 151 ++++++ lib/mail-import/engine.ts | 492 ++++++++++++++++++ lib/mail-import/mbox-status.ts | 88 ++++ lib/mail-import/mbox.ts | 371 +++++++++++++ lib/mail-import/sources.ts | 273 ++++++++++ lib/mail-import/zip-reader.ts | 145 ++++++ lib/sign-out-cleanup.ts | 3 + locales/ar/common.json | 50 ++ locales/ca/common.json | 50 ++ locales/cs/common.json | 50 ++ locales/da/common.json | 50 ++ locales/de/common.json | 50 ++ locales/en/common.json | 50 ++ locales/es/common.json | 50 ++ locales/et/common.json | 50 ++ locales/fa/common.json | 50 ++ locales/fr/common.json | 50 ++ locales/he/common.json | 50 ++ locales/hu/common.json | 50 ++ locales/it/common.json | 50 ++ locales/ja/common.json | 50 ++ locales/ko/common.json | 50 ++ locales/lv/common.json | 50 ++ locales/mn/common.json | 50 ++ locales/nb/common.json | 50 ++ locales/nl/common.json | 50 ++ locales/pl/common.json | 50 ++ locales/pt/common.json | 50 ++ locales/ro/common.json | 50 ++ locales/ru/common.json | 50 ++ locales/sk/common.json | 50 ++ locales/th/common.json | 50 ++ locales/tr/common.json | 50 ++ locales/uk/common.json | 50 ++ locales/vi/common.json | 50 ++ locales/zh-TW/common.json | 50 ++ locales/zh/common.json | 50 ++ package-lock.json | 2 +- package.json | 1 + stores/mail-transfer-store.ts | 77 +++ 60 files changed, 4526 insertions(+), 92 deletions(-) create mode 100644 components/mail-transfer/__tests__/mail-import-dialog.test.tsx create mode 100644 components/mail-transfer/mail-backup-settings.tsx create mode 100644 components/mail-transfer/mail-import-dialog.tsx create mode 100644 components/mail-transfer/mail-transfer-host.tsx create mode 100644 components/mail-transfer/start-import.ts create mode 100644 components/mail-transfer/transfer-progress.tsx create mode 100644 lib/mail-export/__tests__/zip.test.ts create mode 100644 lib/mail-export/crc32.ts create mode 100644 lib/mail-export/zip-writer.ts create mode 100644 lib/mail-import/__tests__/engine.test.ts create mode 100644 lib/mail-import/__tests__/mbox-status.test.ts create mode 100644 lib/mail-import/__tests__/mbox.test.ts create mode 100644 lib/mail-import/checkpoints.ts create mode 100644 lib/mail-import/engine.ts create mode 100644 lib/mail-import/mbox-status.ts create mode 100644 lib/mail-import/mbox.ts create mode 100644 lib/mail-import/sources.ts create mode 100644 lib/mail-import/zip-reader.ts create mode 100644 stores/mail-transfer-store.ts diff --git a/app/(main)/[locale]/layout.tsx b/app/(main)/[locale]/layout.tsx index 5014d7ea8..52333c522 100644 --- a/app/(main)/[locale]/layout.tsx +++ b/app/(main)/[locale]/layout.tsx @@ -19,6 +19,7 @@ import { AccessibilityQuickPanelHost } from "@/components/accessibility/accessib import { TotpReauthDialog } from "@/components/totp-reauth-dialog"; import { PluginConnectDialog } from "@/components/plugins/plugin-connect-dialog"; import { QuickRuleHost } from "@/components/filters/quick-rule-host"; +import { MailTransferHost } from "@/components/mail-transfer/mail-transfer-host"; import { PluginConsentDialog } from "@/components/plugins/plugin-consent-dialog"; import { PluginAppEvents } from "@/components/providers/plugin-app-events"; import { PluginOAuthCallbackListener } from "@/components/providers/plugin-oauth-callback-listener"; @@ -86,6 +87,7 @@ export default async function LocaleLayout({ + diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index 9f384b391..b344f5040 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -3,7 +3,7 @@ import { Fragment, useState, useEffect, useLayoutEffect, useMemo, useRef, useCallback, useId } from "react"; import { Email, ContactCard, Mailbox } from "@/lib/jmap/types"; import { attachmentDownloadFilename, attachmentsBundleFilename, DEFAULT_ATTACHMENT_TEMPLATE } from "@/lib/download-filename"; -import { EML_IMPORT_ACCEPT, expandImportableEmails } from "@/lib/eml-import"; +import { openMailImport } from "@/stores/mail-transfer-store"; import { TRANSPARENT_BLOCKED_PIXEL, applyNewTabToAnchor, emailIframeCsp, escapeHtml, isExternalResourceUrl, isOpenableLinkHref, plainTextToSafeHtml, sanitizeEmailBodyForIframe, sanitizeEmailHtml, sanitizePlainTextRenderedHtml } from "@/lib/email-sanitization"; import { getRenderableHtmlBody } from "@/lib/email-body-selection"; import { mailLanguage } from "@/lib/mail-language"; @@ -1304,6 +1304,7 @@ export function EmailViewer({ const archiveActionEnabled = useArchiveActionEnabled(); const tComposer = useTranslations('email_composer'); const tNotifications = useTranslations('notifications'); + const tImport = useTranslations('mail_import'); const tCommon = useTranslations('common'); const tFiles = useTranslations('files'); const tDemoWelcome = useTranslations('demo_welcome'); @@ -3606,52 +3607,9 @@ export function EmailViewer({ await exportEmail(email, { onArchive, onDelete, archiveEnabled: archiveActionEnabled }, blobSource ?? { client, accountId: undefined }); }; - // Import email from .eml file or .zip archive containing .eml files + // Import mail into the open folder: the import dialog (lib/mail-import) does the rest. const handleImportEmail = () => { - if (!client) return; - const input = document.createElement('input'); - input.type = 'file'; - input.accept = EML_IMPORT_ACCEPT; - input.multiple = true; - input.onchange = async (e) => { - const files = Array.from((e.target as HTMLInputElement).files ?? []); - if (files.length === 0) return; - const { selectedMailbox, mailboxes, fetchEmails } = useEmailStore.getState(); - const mailbox = mailboxes.find(mb => mb.id === selectedMailbox); - const mailboxId = mailbox?.originalId || selectedMailbox; - if (!mailboxId) { - toast.error(tNotifications('import_email_error')); - return; - } - - let emails; - try { - emails = await expandImportableEmails(files); - } catch { - toast.error(tNotifications('import_email_error')); - return; - } - - let imported = 0; - let failed = 0; - for (const { blob, receivedAt } of emails) { - try { - await client.importRawEmail(blob, { [mailboxId]: true }, { '$seen': true }, undefined, receivedAt); - imported++; - } catch { - failed++; - } - } - - if (imported > 0) { - toast.success(tNotifications('import_email_success')); - await fetchEmails(client); - } - if (failed > 0 || emails.length === 0) { - toast.error(tNotifications('import_email_error')); - } - }; - input.click(); + openMailImport({ mailboxId: useEmailStore.getState().selectedMailbox || undefined }); }; // Print only the email content in a new window. A print asked for from @@ -4131,7 +4089,7 @@ export function EmailViewer({ ...(effectiveEmailContent.isHtml ? [{ kind: 'item' as const, key: 'extra:view_mode' as const, icon: isDark ? Sun : Moon, label: isDark ? t('view_in_light_mode') : t('view_in_dark_mode'), run: toggleViewMode }] : []), - { kind: 'item', key: 'extra:import', icon: Upload, label: t('import_email'), run: handleImportEmail }, + { kind: 'item', key: 'extra:import', icon: Upload, label: tImport('menu_import'), run: handleImportEmail }, ...(onShowShortcuts && !isMobile ? [{ kind: 'item' as const, key: 'extra:shortcuts' as const, icon: Keyboard, label: t('keyboard_shortcuts'), run: onShowShortcuts }] : []), diff --git a/components/layout/mailbox-context-menu.tsx b/components/layout/mailbox-context-menu.tsx index 0b739879a..d723bbf64 100644 --- a/components/layout/mailbox-context-menu.tsx +++ b/components/layout/mailbox-context-menu.tsx @@ -140,6 +140,7 @@ export function MailboxContextMenu({ notifications, }: MailboxContextMenuProps) { const t = useTranslations("mailbox_context_menu"); + const tImport = useTranslations("mail_import"); const handleAction = (action: () => void) => { action(); @@ -317,7 +318,7 @@ export function MailboxContextMenu({ handleAction(() => onImportEmail?.(mailbox.id))} disabled={!onImportEmail || !canAddItems} /> diff --git a/components/mail-transfer/__tests__/mail-import-dialog.test.tsx b/components/mail-transfer/__tests__/mail-import-dialog.test.tsx new file mode 100644 index 000000000..c15a88713 --- /dev/null +++ b/components/mail-transfer/__tests__/mail-import-dialog.test.tsx @@ -0,0 +1,133 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; +import { MailTransferHost } from '@/components/mail-transfer/mail-transfer-host'; +import { useMailTransferStore, openMailImport } from '@/stores/mail-transfer-store'; +import { useEmailStore } from '@/stores/email-store'; +import { useAuthStore } from '@/stores/auth-store'; +import type { Mailbox } from '@/lib/jmap/types'; +import type { ImportProgress } from '@/lib/mail-import/engine'; + +const startImport = vi.fn(); +vi.mock('@/components/mail-transfer/start-import', () => ({ startImport: (...args: unknown[]) => startImport(...args) })); + +const box = (id: string, name: string, extra: Partial = {}): Mailbox => ({ + id, name, sortOrder: 0, totalEmails: 0, unreadEmails: 0, totalThreads: 0, unreadThreads: 0, isSubscribed: true, + myRights: { mayAddItems: true } as Mailbox['myRights'], ...extra, +}); + +const progress = (extra: Partial = {}): ImportProgress => ({ + phase: 'running', imported: 3, failed: 0, skipped: {}, failures: [], fraction: 0.4, foldersCreated: 0, ...extra, +}); + +beforeEach(() => { + startImport.mockReset(); + useMailTransferStore.setState({ request: null, visible: false, job: null }); + useEmailStore.setState({ + mailboxes: [ + box('inbox', 'Inbox', { role: 'inbox' }), + box('acc2:shared', 'Team', { originalId: 'shared', accountId: 'acc2', isShared: true, accountName: 'team@example.org' }), + box('ro', 'Read only', { myRights: { mayAddItems: false } as Mailbox['myRights'] }), + ], + } as never); + useAuthStore.setState({ client: { getAccountId: () => 'acc1' } as never, activeAccountId: 'bulwark-1' } as never); +}); + +function pick(files: File[]) { + const input = screen.getByTestId('mail-import-file') as HTMLInputElement; + Object.defineProperty(input, 'files', { value: files, configurable: true }); + fireEvent.change(input); +} + +describe('MailImportDialog', () => { + it('imports into a shared folder in that folder\'s own account', async () => { + render(); + act(() => openMailImport({ mailboxId: 'acc2:shared' })); + expect(screen.getByRole('dialog')).toBeTruthy(); + // Folders the user can't add to aren't offered. + expect(screen.queryByRole('option', { name: 'Read only' })).toBeNull(); + pick([new File(['From a Tue May 5 10:11:12 2020\nSubject: x\n\nb\n'], 'old.mbox')]); + await screen.findByText('old.mbox'); + expect(screen.getByText(/format_mbox/)).toBeTruthy(); + fireEvent.click(screen.getByTestId('mail-import-start')); + expect(startImport).toHaveBeenCalledWith(expect.objectContaining({ + accountId: 'acc2', targetMailboxId: 'shared', keepFolders: false, resume: false, + files: [expect.objectContaining({ format: 'mbox' })], + })); + }); + + it('offers to keep a ZIP\'s folders and the top level as target', async () => { + const { ZipWriter } = await import('@/lib/mail-export/zip-writer'); + const parts: Uint8Array[] = []; + const z = new ZipWriter(async (c) => { parts.push(c.slice()); }); + await z.addFile('Archive/a.eml', new TextEncoder().encode('Subject: a\r\n\r\nA')); + await z.finish(); + render(); + act(() => openMailImport()); + pick([new File(parts as BlobPart[], 'export.zip')]); + const keep = await screen.findByRole('checkbox', { name: /keep_folders/ }); + expect((keep as HTMLInputElement).checked).toBe(true); + fireEvent.change(screen.getByTestId('mail-import-target'), { target: { value: '\u0000top' } }); + fireEvent.click(screen.getByTestId('mail-import-start')); + expect(startImport).toHaveBeenCalledWith(expect.objectContaining({ accountId: 'acc1', targetMailboxId: null, keepFolders: true })); + }); + + it('cannot start without files', () => { + render(); + act(() => openMailImport()); + expect((screen.getByTestId('mail-import-start') as HTMLButtonElement).disabled).toBe(true); + }); + + it('shows a pause in a live region, cancels on Cancel import, and hides on Escape without cancelling', async () => { + const controller = new AbortController(); + render(); + act(() => { + openMailImport(); + useMailTransferStore.getState().startJob({ kind: 'import', controller, progress: progress({ phase: 'paused', pausedUntil: Date.now() + 3600_000, quota: { files: 1000, bytes: 50_000_000 } }) }); + }); + const status = screen.getByTestId('mail-import-status'); + expect(status.getAttribute('aria-live')).toBe('polite'); + expect(status.textContent).toBe('paused'); + expect(screen.getByRole('progressbar').getAttribute('aria-valuenow')).toBe('40'); + + fireEvent.keyDown(screen.getByRole('dialog'), { key: 'Escape' }); + await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull()); + expect(controller.signal.aborted).toBe(false); + // The chip brings it back. + fireEvent.click(screen.getByTestId('mail-transfer-chip')); + expect(screen.getByRole('dialog')).toBeTruthy(); + + fireEvent.click(screen.getByRole('button', { name: 'cancel_import' })); + expect(controller.signal.aborted).toBe(true); + }); + + it('opening another import while one runs shows the running one', () => { + const controller = new AbortController(); + render(); + act(() => { + openMailImport(); + useMailTransferStore.getState().startJob({ kind: 'import', controller, progress: progress() }); + useMailTransferStore.getState().hide(); + }); + act(() => openMailImport({ mailboxId: 'inbox' })); + expect(screen.getByRole('heading', { name: 'running_title' })).toBeTruthy(); + }); + + it('lists what was skipped and what failed when it ends', () => { + render(); + act(() => { + openMailImport(); + useMailTransferStore.getState().startJob({ + kind: 'import', controller: new AbortController(), + progress: progress({ phase: 'done', imported: 10, failed: 1, failures: [{ label: 'Hello', reason: 'invalid' }], skipped: { deleted: 2, too_large: 1 } }), + }); + }); + expect(screen.getByText('done_title')).toBeTruthy(); + expect(screen.getByText('skip_too_large')).toBeTruthy(); + expect(screen.getByText('skip_deleted')).toBeTruthy(); + expect(screen.getByText('Hello: failure_invalid')).toBeTruthy(); + // The footer's Close (the X in the corner is named the same). + const closes = screen.getAllByRole('button', { name: 'close' }); + fireEvent.click(closes[closes.length - 1]); + expect(useMailTransferStore.getState().job).toBeNull(); + }); +}); diff --git a/components/mail-transfer/mail-backup-settings.tsx b/components/mail-transfer/mail-backup-settings.tsx new file mode 100644 index 000000000..0758d52c5 --- /dev/null +++ b/components/mail-transfer/mail-backup-settings.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import { SettingItem } from "@/components/settings/settings-section"; +import { Button } from "@/components/ui/button"; +import { openMailImport } from "@/stores/mail-transfer-store"; + +/** Settings > Backup: bring mail in from files. */ +export function MailBackupSettings() { + const tImport = useTranslations("mail_import"); + return ( + + + + ); +} diff --git a/components/mail-transfer/mail-import-dialog.tsx b/components/mail-transfer/mail-import-dialog.tsx new file mode 100644 index 000000000..a30507432 --- /dev/null +++ b/components/mail-transfer/mail-import-dialog.tsx @@ -0,0 +1,300 @@ +"use client"; + +import { useEffect, useId, useMemo, useRef, useState } from "react"; +import { useLocale, useTranslations } from "next-intl"; +import { Dialog } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { IconButton } from "@/components/ui/icon-button"; +import { FileArchive, FileText, Upload, X } from "@/components/icons"; +import { useEmailStore } from "@/stores/email-store"; +import { useAuthStore } from "@/stores/auth-store"; +import { isJobActive, useMailTransferStore, type ImportRequest } from "@/stores/mail-transfer-store"; +import { checkpointKey, readCheckpoint } from "@/lib/mail-import/checkpoints"; +import { hasFolders, sniffFormat, type ImportFormat, type SkipReason } from "@/lib/mail-import/sources"; +import type { ImportProgress } from "@/lib/mail-import/engine"; +import type { Mailbox } from "@/lib/jmap/types"; +import { formatFileSize, getMailboxPath } from "@/lib/utils"; +import { formatClock, TransferBar } from "./transfer-progress"; +import { startImport } from "./start-import"; + +const ACCEPT = ".eml,.zip,.mbox,.mbx,.pst,.ost,message/rfc822,application/zip,application/mbox"; +const TOP = "\u0000top"; +const SKIP_ORDER: SkipReason[] = ["too_large", "deleted", "embedded", "contact", "calendar", "other"]; + +interface Picked { + file: File; + format: ImportFormat; + folders: boolean; +} + +function mailboxValue(m: Mailbox): string { + return m.id; +} + +export function MailImportDialog({ request }: { request: ImportRequest }) { + const t = useTranslations("mail_import"); + const locale = useLocale(); + const id = useId(); + const visible = useMailTransferStore((s) => s.visible); + const job = useMailTransferStore((s) => s.job); + const close = useMailTransferStore((s) => s.close); + const hide = useMailTransferStore((s) => s.hide); + const cancelJob = useMailTransferStore((s) => s.cancelJob); + const mailboxes = useEmailStore((s) => s.mailboxes); + const client = useAuthStore((s) => s.client); + const activeAccountId = useAuthStore((s) => s.activeAccountId); + const fileInput = useRef(null); + const chooseButton = useRef(null); + + const targets = useMemo( + () => mailboxes + .filter((m) => m.myRights?.mayAddItems !== false) + .map((m) => ({ + mailbox: m, + label: (m.isShared && m.accountName ? `${m.accountName} › ` : "") + getMailboxPath(m, mailboxes), + })) + .sort((a, b) => Number(!!a.mailbox.isShared) - Number(!!b.mailbox.isShared) || a.label.localeCompare(b.label)), + [mailboxes], + ); + const initialTarget = request.mailboxId && targets.some((x) => x.mailbox.id === request.mailboxId) + ? request.mailboxId + : targets.find((x) => x.mailbox.role === "inbox" && !x.mailbox.isShared)?.mailbox.id ?? targets[0]?.mailbox.id ?? TOP; + + const [picked, setPicked] = useState([]); + const [target, setTarget] = useState(initialTarget); + const [keepFolders, setKeepFolders] = useState(true); + const [resume, setResume] = useState(true); + const [resumable, setResumable] = useState(0); + + const anyFolders = picked.some((p) => p.folders); + const targetMailbox = targets.find((x) => x.mailbox.id === target)?.mailbox; + const accountId = targetMailbox?.accountId || client?.getAccountId() || ""; + const targetId = target === TOP ? null : targetMailbox ? targetMailbox.originalId || targetMailbox.id : null; + const effectiveKeep = anyFolders && keepFolders; + + // An earlier import of the same file into the same place that stopped part way. + useEffect(() => { + let current = true; + if (picked.length === 0 || !accountId) { setResumable(0); return; } + void Promise.all(picked.map((p) => readCheckpoint(checkpointKey({ + scope: activeAccountId ?? "default", accountId, target: targetId, keepFolders: effectiveKeep, file: p.file, + })))).then((found) => { + if (current) setResumable(found.reduce((n, c) => n + (c?.done ?? 0), 0)); + }); + return () => { current = false; }; + }, [picked, accountId, targetId, effectiveKeep, activeAccountId]); + + const addFiles = async (list: FileList | null) => { + const files = Array.from(list ?? []); + const next = await Promise.all(files.map(async (file) => { + const format = await sniffFormat(file); + return { file, format, folders: await hasFolders(file, format) }; + })); + setPicked((prev) => [...prev, ...next.filter((n) => !prev.some((p) => p.file.name === n.file.name && p.file.size === n.file.size))]); + }; + + const formatName = (format: ImportFormat) => t(`format_${format}`); + const progress = job?.kind === "import" ? job.progress : null; + const active = isJobActive(job); + + const onStart = () => { + if (picked.length === 0 || !accountId) return; + if (target === TOP && !effectiveKeep) return; + startImport({ + files: picked.map((p) => ({ file: p.file, format: p.format })), + accountId, + targetMailboxId: targetId, + keepFolders: effectiveKeep, + resume: resume && resumable > 0, + }); + }; + + const onClose = () => (active ? hide() : close()); + + if (!visible) return null; + + if (progress) { + return ( + + + + + ) : ( + + )} + > + + + ); + } + + const canStart = picked.length > 0 && !!accountId && !(target === TOP && !effectiveKeep); + + return ( + + + + + )} + > +
+
+ { void addFiles(e.target.files); e.target.value = ""; }} + /> + + {picked.length > 0 && ( +
    + {picked.map((p, i) => ( +
  • + {p.format === "eml" ?
  • + ))} +
+ )} +
+ +
+ + +
+ + {anyFolders && ( + + )} + + {resumable > 0 && ( + + )} + +

{t("quota_note")}

+
+
+ ); +} + +type T = ReturnType; + +function progressTitle(t: T, p: ImportProgress): string { + if (p.phase === "done") return t("done_title"); + if (p.phase === "cancelled") return t("cancelled_title"); + if (p.phase === "failed") return t("failed_title"); + return t("running_title"); +} + +function ProgressBody({ progress: p, t, locale, statusId }: { progress: ImportProgress; t: T; locale: string; statusId: string }) { + const running = p.phase === "running" || p.phase === "paused"; + const skipped = SKIP_ORDER.filter((r) => (p.skipped[r] ?? 0) > 0); + let status: string; + if (p.phase === "paused" && p.pausedUntil) { + const time = formatClock(p.pausedUntil, locale); + status = p.quota?.files && p.quota.bytes + ? t("paused", { time, files: p.quota.files, size: formatFileSize(p.quota.bytes) }) + : t("paused_generic", { time }); + } else if (running) { + status = p.fileName ? t("reading_file", { name: p.fileName }) : t("running_title"); + } else { + status = t("summary_imported", { count: p.imported }); + } + return ( +
+ {running && } +

+ {running ? t("status_imported", { count: p.imported }) : null} +

+

+ {status} +

+ {!running && ( +
+ {p.foldersCreated > 0 &&

{t("summary_folders", { count: p.foldersCreated })}

} + {skipped.length > 0 && ( +
+

{t("summary_skipped")}

+
    + {skipped.map((r) =>
  • {t(`skip_${r}`, { count: p.skipped[r] ?? 0 })}
  • )} +
+
+ )} + {p.failed > 0 && ( +
+

{t("summary_failed", { count: p.failed })}

+
    + {p.failures.map((f, i) => ( +
  • {f.label || t("untitled")}: {f.reason === "invalid" ? t("failure_invalid") : f.reason}
  • + ))} +
+
+ )} + {p.phase === "cancelled" &&

{t("cancelled_hint")}

} + {p.phase === "failed" && p.error &&

{p.error}

} +
+ )} +
+ ); +} diff --git a/components/mail-transfer/mail-transfer-host.tsx b/components/mail-transfer/mail-transfer-host.tsx new file mode 100644 index 000000000..cd4bb008d --- /dev/null +++ b/components/mail-transfer/mail-transfer-host.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { useEffect } from "react"; +import { useLocale, useTranslations } from "next-intl"; +import { Loader2, Pause } from "@/components/icons"; +import { isJobActive, useMailTransferStore } from "@/stores/mail-transfer-store"; +import { onBeforeSignOut } from "@/lib/sign-out-cleanup"; +import { clearCheckpointsFor } from "@/lib/mail-import/checkpoints"; +import { MailImportDialog } from "./mail-import-dialog"; +import { formatClock } from "./transfer-progress"; + +/** + * Draws the mail import dialog, and while it is hidden with a job running, + * a chip in the corner that shows the progress and brings it back. Mounted + * with the app, so a job outlives the menu or settings page that started it. + */ +export function MailTransferHost() { + const request = useMailTransferStore((s) => s.request); + const visible = useMailTransferStore((s) => s.visible); + const job = useMailTransferStore((s) => s.job); + + // An account that signs out takes its running job and its checkpoints with it. + useEffect(() => onBeforeSignOut(async (accountId) => { + useMailTransferStore.getState().cancelJob(); + if (accountId !== "all") await clearCheckpointsFor(accountId); + }), []); + + return ( + <> + {request?.kind === "import" && } + {!visible && isJobActive(job) && } + + ); +} + +function TransferChip() { + const t = useTranslations("mail_import"); + const locale = useLocale(); + const job = useMailTransferStore((s) => s.job); + const show = useMailTransferStore((s) => s.show); + if (!job) return null; + const p = job.progress; + const paused = p.phase === "paused" && p.pausedUntil; + const label = paused + ? t("chip_paused", { time: formatClock(p.pausedUntil!, locale) }) + : t("chip", { percent: Math.round(p.fraction * 100) }); + return ( + + ); +} diff --git a/components/mail-transfer/start-import.ts b/components/mail-transfer/start-import.ts new file mode 100644 index 000000000..9544bcd54 --- /dev/null +++ b/components/mail-transfer/start-import.ts @@ -0,0 +1,51 @@ +import { runImport, type ImportFile } from "@/lib/mail-import/engine"; +import { useAuthStore } from "@/stores/auth-store"; +import { useEmailStore } from "@/stores/email-store"; +import { useMailTransferStore } from "@/stores/mail-transfer-store"; + +export interface StartImportInput { + files: ImportFile[]; + /** JMAP account of the target. */ + accountId: string; + /** null = the top level (with keepFolders). */ + targetMailboxId: string | null; + keepFolders: boolean; + resume: boolean; +} + +/** Runs an import as the dialog's job and refreshes the mail lists when it ends. */ +export function startImport(input: StartImportInput): void { + const auth = useAuthStore.getState(); + const client = auth.client; + if (!client) return; + const store = useMailTransferStore.getState(); + const controller = new AbortController(); + store.startJob({ + kind: "import", + controller, + progress: { phase: "running", imported: 0, failed: 0, skipped: {}, failures: [], fraction: 0, foldersCreated: 0 }, + }); + void (async () => { + const result = await runImport({ + client, + accountId: input.accountId, + targetMailboxId: input.targetMailboxId, + keepFolders: input.keepFolders, + files: input.files, + scope: auth.activeAccountId ?? "default", + resume: input.resume, + signal: controller.signal, + onProgress: (progress) => useMailTransferStore.getState().updateProgress(progress), + }); + useMailTransferStore.getState().updateProgress(result); + // Show what arrived, if the same account is still the one on screen. + if (useAuthStore.getState().client !== client || result.imported === 0) return; + const email = useEmailStore.getState(); + try { + await email.fetchMailboxes(client); + await email.fetchEmails(client, email.selectedMailbox || undefined, { background: true }); + } catch { + /* the next refresh shows them */ + } + })(); +} diff --git a/components/mail-transfer/transfer-progress.tsx b/components/mail-transfer/transfer-progress.tsx new file mode 100644 index 000000000..a0c6aba3e --- /dev/null +++ b/components/mail-transfer/transfer-progress.tsx @@ -0,0 +1,34 @@ +"use client"; + +import { cn } from "@/lib/utils"; + +/** The bar of an import or export, 0 to 1; indeterminate while nothing is known. */ +export function TransferBar({ fraction, label, paused }: { fraction: number | null; label: string; paused?: boolean }) { + const percent = fraction === null ? null : Math.round(Math.max(0, Math.min(1, fraction)) * 100); + return ( +
+
+
+ ); +} + +/** "14:00", or a date and time when the moment isn't today. */ +export function formatClock(ms: number, locale: string, now = Date.now()): string { + const date = new Date(ms); + const sameDay = new Date(now).toDateString() === date.toDateString(); + return new Intl.DateTimeFormat(locale, sameDay ? { hour: "2-digit", minute: "2-digit" } : { dateStyle: "medium", timeStyle: "short" }).format(date); +} diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx index 3718674e6..2b2838c19 100644 --- a/components/mail/mail-app.tsx +++ b/components/mail/mail-app.tsx @@ -123,7 +123,7 @@ import { isFilePreviewable } from "@/lib/file-preview"; import { prepareQuickReply, type QuickReplyQuestion } from "@/lib/outgoing/quick-reply"; import enMessages from "@/locales/en/common.json"; import { computeReplyThreadingHeaders } from "@/lib/email-threading"; -import { EML_IMPORT_ACCEPT, expandImportableEmails } from "@/lib/eml-import"; +import { openMailImport } from "@/stores/mail-transfer-store"; import { deliveryAddresses, resolveComposeAccountEmail } from "@/lib/reply-identity"; import { useProMultiAccountIdentities } from "@/hooks/use-pro-multi-account-identities"; import { Filter, X, Mail, MailOpen, PenSquare, CheckSquare, Square, AlertTriangle, ArrowLeft, RefreshCw, Reply, ReplyAll, Forward, Archive, Trash2, FolderInput, ShieldAlert, ShieldCheck, Folder } from "@/components/icons"; @@ -3763,48 +3763,9 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { } }; + // The import dialog (lib/mail-import) picks the files and runs the import. const handleImportEmailFromContextMenu = (mailboxId: string) => { - if (!client) return; - const mailbox = mailboxes.find(mb => mb.id === mailboxId); - if (!mailbox) return; - const targetMailboxId = mailbox.originalId || mailbox.id; - - const input = document.createElement('input'); - input.type = 'file'; - input.accept = EML_IMPORT_ACCEPT; - input.multiple = true; - input.onchange = async (e) => { - const files = Array.from((e.target as HTMLInputElement).files ?? []); - if (files.length === 0) return; - - let emails; - try { - emails = await expandImportableEmails(files); - } catch { - toast.error(t('notifications.import_email_error')); - return; - } - - let imported = 0; - let failed = 0; - for (const { blob, receivedAt } of emails) { - try { - await client.importRawEmail(blob, { [targetMailboxId]: true }, { '$seen': true }, undefined, receivedAt); - imported++; - } catch { - failed++; - } - } - - if (imported > 0) { - toast.success(t('notifications.import_email_success')); - if (selectedMailbox) await fetchEmails(client, selectedMailbox); - } - if (failed > 0 || (imported === 0 && emails.length === 0)) { - toast.error(t('notifications.import_email_error')); - } - }; - input.click(); + openMailImport({ mailboxId }); }; const handleRefreshMailboxes = async () => { diff --git a/components/settings/about-data-settings.tsx b/components/settings/about-data-settings.tsx index 33fc31285..692d0acad 100644 --- a/components/settings/about-data-settings.tsx +++ b/components/settings/about-data-settings.tsx @@ -16,6 +16,7 @@ import { SpamSiegeGame } from './spam-siege-game'; import { ConfirmDialog } from '@/components/ui/confirm-dialog'; import { toast } from '@/stores/toast-store'; import { DebugSettings } from './debug-settings'; +import { MailBackupSettings } from '@/components/mail-transfer/mail-backup-settings'; const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0"; const GIT_COMMIT = process.env.NEXT_PUBLIC_GIT_COMMIT || "unknown"; @@ -212,6 +213,8 @@ export function AboutDataSettings() { )} + + diff --git a/lib/eml-import.ts b/lib/eml-import.ts index dae322846..ddca1a577 100644 --- a/lib/eml-import.ts +++ b/lib/eml-import.ts @@ -31,6 +31,11 @@ const HEADER_SCAN_BYTES = 64 * 1024; */ export async function readMessageDate(blob: Blob, now: number = Date.now()): Promise { const head = await blob.slice(0, HEADER_SCAN_BYTES).text(); + return dateFromHeaderBlock(head, now); +} + +/** `readMessageDate` for header text already read (anything after the header block is ignored). */ +export function dateFromHeaderBlock(head: string, now: number = Date.now()): string | undefined { const end = head.search(/\r?\n\r?\n/); const headers = (end === -1 ? head : head.slice(0, end)).replace(/\r?\n[ \t]+/g, " "); const match = /^Date:[ \t]*(.+)$/im.exec(headers); diff --git a/lib/jmap/client-interface.ts b/lib/jmap/client-interface.ts index 8e3ae5177..2716bf5f2 100644 --- a/lib/jmap/client-interface.ts +++ b/lib/jmap/client-interface.ts @@ -39,6 +39,16 @@ export interface KeywordDiscoveryResult { complete: boolean; } +/** One message for `importUploadedEmails`. */ +export interface UploadedEmailImport { + blobId: string; + mailboxIds: Record; + keywords: Record; + receivedAt?: string; +} + +export type UploadedEmailImportResult = { id: string } | { error: string; type?: string }; + /** What `getBlobsAsBase64` brought back. */ export interface BlobBatch { /** Contents as base64 by blobId, for every blob the server returned. */ @@ -683,6 +693,11 @@ export interface IJMAPClient { * without it the server stamps the import time. */ importRawEmail(blob: Blob, mailboxIds: Record, keywords?: Record, accountId?: string, receivedAt?: string): Promise; + /** + * Email/import of several uploaded messages in one request (the mail + * import). One result per entry, in order: the new id or the server's error. + */ + importUploadedEmails?(entries: UploadedEmailImport[], accountId?: string): Promise; submitEmail(emailId: string, identityId: string): Promise; /** * Server-side move of one email across accounts reachable through THIS client diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index 285bd9569..be9496c20 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -4,7 +4,7 @@ import { ArchiveEmailsError } from "./archive-error"; import { generateUUID } from '@/lib/utils'; import type { Attachment, Email, Mailbox, MailboxRights, StateChange, AccountStates, CollectionChanges, ShareNotification, BusyPeriod, CalendarParticipantIdentity, CalendarEventNotification, Thread, Identity, EmailAddress, ContactCard, AddressBook, AddressBookRights, VacationResponse, Calendar, CalendarComponentType, CalendarRights, CalendarEvent, CalendarEventFilter, CalendarTask, CreateCalendarOptions, FileNode, FileNodeFilter, FileNodeRights, Principal, PrincipalAvailability, PrincipalListing, PushSubscription, EmailPushConfig, DeliveryStatus, EmailSubmission, RejectedRecipient, ScheduledEmail, SendEmailResult, SharedAccount } from "./types"; import type { SieveScript, SieveCapabilities } from "./sieve-types"; -import type { BlobBatch, CalendarEventUpdateOptions, CalendarQueryOptions, FileNodeListings, IJMAPClient, KeywordDiscoveryResult, KeywordInfo, KeywordMigration, SearchEmailsResult, SearchRequestOptions } from "./client-interface"; +import type { BlobBatch, CalendarEventUpdateOptions, CalendarQueryOptions, FileNodeListings, IJMAPClient, KeywordDiscoveryResult, KeywordInfo, KeywordMigration, SearchEmailsResult, SearchRequestOptions, UploadedEmailImport, UploadedEmailImportResult } from "./client-interface"; import { attachSearchSnippets, filterHasSnippetTerms, snippetFilterFor, type SearchSnippetResult } from "@/lib/search-snippet"; import { textConditions, textFilter } from "./search-utils"; import { batched, batchedBySize, itemsPerRequest } from "./request-limits"; @@ -11224,6 +11224,33 @@ export class JMAPClient implements IJMAPClient { return emailId; } + async importUploadedEmails(entries: UploadedEmailImport[], accountId?: string): Promise { + if (entries.length === 0) return []; + const targetAccountId = accountId || this.accountId; + const emails: Record> = {}; + entries.forEach((entry, i) => { + emails[`imp${i}`] = { + blobId: entry.blobId, + mailboxIds: entry.mailboxIds, + keywords: entry.keywords, + ...(entry.receivedAt ? { receivedAt: entry.receivedAt } : {}), + }; + }); + const response = await this.request([ + ['Email/import', { accountId: targetAccountId, emails }, '0'], + ]); + const [name, result] = response.methodResponses?.[0] ?? []; + if (name !== 'Email/import') { + throw jmapError(`Email/import failed: ${result?.description || result?.type || 'unknown'}`, result); + } + return entries.map((_, i) => { + const created = result?.created?.[`imp${i}`]; + if (created?.id) return { id: created.id as string }; + const err = result?.notCreated?.[`imp${i}`]; + return { error: (err?.description || err?.type || 'Email/import returned nothing') as string, type: err?.type as string | undefined }; + }); + } + /** Submit an already-imported email for delivery. */ async submitEmail(emailId: string, identityId: string): Promise { const submissionAccountId = this.getSubmissionAccountId(); diff --git a/lib/mail-export/__tests__/zip.test.ts b/lib/mail-export/__tests__/zip.test.ts new file mode 100644 index 000000000..321aa7112 --- /dev/null +++ b/lib/mail-export/__tests__/zip.test.ts @@ -0,0 +1,76 @@ +import { describe, it, expect } from 'vitest'; +import { unzipSync, strFromU8 } from 'fflate'; +import { ZipWriter } from '@/lib/mail-export/zip-writer'; +import { crc32 } from '@/lib/mail-export/crc32'; +import { listZipEntries, readZipEntry, streamZipEntry } from '@/lib/mail-import/zip-reader'; + +const enc = new TextEncoder(); +const dec = new TextDecoder(); +const join = (parts: Uint8Array[]) => { + const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0)); + let at = 0; + for (const p of parts) { out.set(p, at); at += p.length; } + return out; +}; + +describe('crc32', () => { + it('matches the reference value and streams', () => { + expect(crc32(enc.encode('123456789'))).toBe(0xcbf43926); + expect(crc32(enc.encode('6789'), crc32(enc.encode('12345')))).toBe(0xcbf43926); + }); +}); + +describe('ZipWriter', () => { + async function build(fill: (z: ZipWriter) => Promise) { + const chunks: Uint8Array[] = []; + const z = new ZipWriter(async (c) => { chunks.push(c.slice()); }); + await fill(z); + await z.finish(); + return join(chunks); + } + + it('writes files, folders and a streamed entry that other readers open', async () => { + const big = 'From MAILER-DAEMON x\n'.repeat(5000); + const zip = await build(async (z) => { + await z.addFile('Inbox/0001 Hello.eml', enc.encode('Subject: Hello\r\n\r\nbody'), new Date(2020, 4, 5, 10, 11, 12)); + await z.addDirectory('Empty'); + await z.addFile('Prüfung/ä.eml', enc.encode('x'.repeat(1000))); + const s = await z.openStream('Archive.mbox'); + await s.write(enc.encode(big.slice(0, 1000))); + await s.write(enc.encode(big.slice(1000))); + await s.close(); + }); + const files = unzipSync(zip); + expect(Object.keys(files).sort()).toEqual(['Archive.mbox', 'Empty/', 'Inbox/0001 Hello.eml', 'Prüfung/ä.eml']); + expect(strFromU8(files['Inbox/0001 Hello.eml'])).toBe('Subject: Hello\r\n\r\nbody'); + expect(strFromU8(files['Archive.mbox'])).toBe(big); + }); + + it('is read back by the importer, whole and in chunks', async () => { + const big = enc.encode('line of mail\n'.repeat(20000)); + const zip = await build(async (z) => { + await z.addFile('a.eml', enc.encode('A')); + const s = await z.openStream('b.mbox'); + for (let i = 0; i < big.length; i += 7000) await s.write(big.subarray(i, i + 7000)); + await s.close(); + }); + const file = new Blob([zip]); + const entries = await listZipEntries(file); + expect(entries.map((e) => [e.name, e.size])).toEqual([['a.eml', 1], ['b.mbox', big.length]]); + expect(dec.decode(await readZipEntry(file, entries[0]))).toBe('A'); + const parts: Uint8Array[] = []; + for await (const c of streamZipEntry(file, entries[1], 4096)) parts.push(c); + // Compared as text: jsdom and Node each have their own Uint8Array. + expect(dec.decode(join(parts))).toBe(dec.decode(big)); + }); + + it('writes Zip64 records past 65,535 entries', async () => { + const zip = await build(async (z) => { + for (let i = 0; i < 65_540; i++) await z.addFile(`${i}`, new Uint8Array(0)); + }); + // The classic end record says "see Zip64", and the Zip64 one has the count. + const entries = await listZipEntries(new Blob([zip])); + expect(entries).toHaveLength(65_540); + expect(entries[65_539].name).toBe('65539'); + }, 60_000); +}); diff --git a/lib/mail-export/crc32.ts b/lib/mail-export/crc32.ts new file mode 100644 index 000000000..0dc7d23b6 --- /dev/null +++ b/lib/mail-export/crc32.ts @@ -0,0 +1,20 @@ +let table: Uint32Array | null = null; + +function crcTable(): Uint32Array { + if (table) return table; + table = new Uint32Array(256); + for (let n = 0; n < 256; n++) { + let c = n; + for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; + table[n] = c >>> 0; + } + return table; +} + +/** CRC-32 (ZIP, PNG) of `data`, continuing from `crc` for streamed input. */ +export function crc32(data: Uint8Array, crc = 0): number { + const t = crcTable(); + let c = (crc ^ 0xffffffff) >>> 0; + for (let i = 0; i < data.length; i++) c = t[(c ^ data[i]) & 0xff] ^ (c >>> 8); + return (c ^ 0xffffffff) >>> 0; +} diff --git a/lib/mail-export/zip-writer.ts b/lib/mail-export/zip-writer.ts new file mode 100644 index 000000000..3548c1b0b --- /dev/null +++ b/lib/mail-export/zip-writer.ts @@ -0,0 +1,251 @@ +import { Deflate, deflateSync } from "fflate"; +import { crc32 } from "./crc32"; + +/** + * A streaming ZIP writer: each entry goes to the sink as soon as it is added, + * so an export never holds the archive in memory. Zip64 records are written + * when the archive passes 4 GB or 65,535 entries (fflate's Zip writes none). + * + * - addFile: a whole file in memory (one message); deflated when that saves space. + * - openStream: a file written piece by piece (a folder's mbox), deflated, + * with its CRC and sizes in a data descriptor after the data. + */ + +export type ZipSink = (chunk: Uint8Array) => Promise; + +interface CentralEntry { + name: Uint8Array; + flags: number; + method: number; + time: number; + date: number; + crc: number; + compressedSize: number; + size: number; + offset: number; + directory: boolean; +} + +const MAX32 = 0xffffffff; +const MAX16 = 0xffff; +/** General purpose flag 11: the name is UTF-8. */ +const FLAG_UTF8 = 0x0800; +/** General purpose flag 3: CRC and sizes follow the data. */ +const FLAG_DESCRIPTOR = 0x0008; + +function dosDateTime(date: Date): { time: number; date: number } { + const d = Number.isNaN(date.getTime()) || date.getFullYear() < 1980 ? new Date(1980, 0, 1) : date; + return { + time: (d.getHours() << 11) | (d.getMinutes() << 5) | Math.floor(d.getSeconds() / 2), + date: ((Math.min(d.getFullYear(), 2107) - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate(), + }; +} + +function u64(view: DataView, at: number, value: number): void { + view.setUint32(at, value % 0x1_0000_0000, true); + view.setUint32(at + 4, Math.floor(value / 0x1_0000_0000), true); +} + +const encoder = new TextEncoder(); + +export interface ZipEntryStream { + write(chunk: Uint8Array): Promise; + close(): Promise; +} + +export class ZipWriter { + private offset = 0; + private readonly entries: CentralEntry[] = []; + private streaming = false; + + constructor(private readonly sink: ZipSink) {} + + /** Bytes written so far. */ + get bytesWritten(): number { + return this.offset; + } + + private async emit(bytes: Uint8Array): Promise { + if (bytes.length === 0) return; + this.offset += bytes.length; + await this.sink(bytes); + } + + private localHeader(entry: Omit, zip64: boolean): Uint8Array { + const header = new Uint8Array(30 + entry.name.length); + const v = new DataView(header.buffer); + v.setUint32(0, 0x04034b50, true); + v.setUint16(4, zip64 ? 45 : 20, true); + v.setUint16(6, entry.flags, true); + v.setUint16(8, entry.method, true); + v.setUint16(10, entry.time, true); + v.setUint16(12, entry.date, true); + v.setUint32(14, entry.crc, true); + v.setUint32(18, entry.compressedSize, true); + v.setUint32(22, entry.size, true); + v.setUint16(26, entry.name.length, true); + v.setUint16(28, 0, true); + header.set(entry.name, 30); + return header; + } + + private assertIdle(): void { + if (this.streaming) throw new Error("ZipWriter: close the open entry first"); + } + + /** An empty folder, so the tree survives without files in it. */ + async addDirectory(path: string, mtime = new Date()): Promise { + this.assertIdle(); + const name = encoder.encode(path.endsWith("/") ? path : `${path}/`); + const { time, date } = dosDateTime(mtime); + const entry = { name, flags: FLAG_UTF8, method: 0, time, date, crc: 0, compressedSize: 0, size: 0 }; + const offset = this.offset; + await this.emit(this.localHeader(entry, false)); + this.entries.push({ ...entry, offset, directory: true }); + } + + async addFile(path: string, data: Uint8Array, mtime = new Date()): Promise { + this.assertIdle(); + const name = encoder.encode(path); + const { time, date } = dosDateTime(mtime); + const crc = crc32(data); + const deflated = data.length > 64 ? deflateSync(data, { level: 1 }) : null; + const useDeflate = !!deflated && deflated.length < data.length; + const body = useDeflate ? deflated! : data; + const entry = { name, flags: FLAG_UTF8, method: useDeflate ? 8 : 0, time, date, crc, compressedSize: body.length, size: data.length }; + const offset = this.offset; + await this.emit(this.localHeader(entry, false)); + await this.emit(body); + this.entries.push({ ...entry, offset, directory: false }); + } + + async openStream(path: string, mtime = new Date()): Promise { + this.assertIdle(); + this.streaming = true; + const name = encoder.encode(path); + const { time, date } = dosDateTime(mtime); + const flags = FLAG_UTF8 | FLAG_DESCRIPTOR; + const offset = this.offset; + await this.emit(this.localHeader({ name, flags, method: 8, time, date, crc: 0, compressedSize: 0, size: 0 }, false)); + + let crc = 0; + let size = 0; + let compressedSize = 0; + let pending: Uint8Array[] = []; + const deflate = new Deflate({ level: 1 }); + deflate.ondata = (chunk) => { + pending.push(chunk); + compressedSize += chunk.length; + }; + const drain = async () => { + const out = pending; + pending = []; + for (const chunk of out) await this.emit(chunk); + }; + + return { + write: async (chunk: Uint8Array) => { + if (chunk.length === 0) return; + crc = crc32(chunk, crc); + size += chunk.length; + deflate.push(chunk, false); + await drain(); + }, + close: async () => { + deflate.push(new Uint8Array(0), true); + await drain(); + const zip64 = size >= MAX32 || compressedSize >= MAX32; + const descriptor = new Uint8Array(zip64 ? 24 : 16); + const v = new DataView(descriptor.buffer); + v.setUint32(0, 0x08074b50, true); + v.setUint32(4, crc, true); + if (zip64) { + u64(v, 8, compressedSize); + u64(v, 16, size); + } else { + v.setUint32(8, compressedSize, true); + v.setUint32(12, size, true); + } + await this.emit(descriptor); + this.entries.push({ name, flags, method: 8, time, date, crc, compressedSize, size, offset, directory: false }); + this.streaming = false; + }, + }; + } + + /** Writes the central directory. Nothing may be added afterwards. */ + async finish(): Promise { + this.assertIdle(); + const cdStart = this.offset; + for (const e of this.entries) { + const needSize = e.size >= MAX32; + const needCompressed = e.compressedSize >= MAX32; + const needOffset = e.offset >= MAX32; + const extraLength = needSize || needCompressed || needOffset + ? 4 + 8 * (Number(needSize) + Number(needCompressed) + Number(needOffset)) + : 0; + const record = new Uint8Array(46 + e.name.length + extraLength); + const v = new DataView(record.buffer); + const zip64 = extraLength > 0; + v.setUint32(0, 0x02014b50, true); + v.setUint16(4, zip64 ? 45 : 20, true); + v.setUint16(6, zip64 ? 45 : 20, true); + v.setUint16(8, e.flags, true); + v.setUint16(10, e.method, true); + v.setUint16(12, e.time, true); + v.setUint16(14, e.date, true); + v.setUint32(16, e.crc, true); + v.setUint32(20, needCompressed ? MAX32 : e.compressedSize, true); + v.setUint32(24, needSize ? MAX32 : e.size, true); + v.setUint16(28, e.name.length, true); + v.setUint16(30, extraLength, true); + v.setUint16(32, 0, true); + v.setUint16(34, 0, true); + v.setUint16(36, 0, true); + v.setUint32(38, e.directory ? 0x10 : 0, true); + v.setUint32(42, needOffset ? MAX32 : e.offset, true); + record.set(e.name, 46); + if (zip64) { + let at = 46 + e.name.length; + v.setUint16(at, 0x0001, true); + v.setUint16(at + 2, extraLength - 4, true); + at += 4; + if (needSize) { u64(v, at, e.size); at += 8; } + if (needCompressed) { u64(v, at, e.compressedSize); at += 8; } + if (needOffset) { u64(v, at, e.offset); at += 8; } + } + await this.emit(record); + } + const cdSize = this.offset - cdStart; + const count = this.entries.length; + const zip64 = count >= MAX16 || cdStart >= MAX32 || cdSize >= MAX32; + if (zip64) { + const zip64End = this.offset; + const record = new Uint8Array(56 + 20); + const v = new DataView(record.buffer); + v.setUint32(0, 0x06064b50, true); + u64(v, 4, 44); + v.setUint16(12, 45, true); + v.setUint16(14, 45, true); + v.setUint32(16, 0, true); + v.setUint32(20, 0, true); + u64(v, 24, count); + u64(v, 32, count); + u64(v, 40, cdSize); + u64(v, 48, cdStart); + v.setUint32(56, 0x07064b50, true); + v.setUint32(60, 0, true); + u64(v, 64, zip64End); + v.setUint32(72, 1, true); + await this.emit(record); + } + const end = new Uint8Array(22); + const v = new DataView(end.buffer); + v.setUint32(0, 0x06054b50, true); + v.setUint16(8, Math.min(count, MAX16), true); + v.setUint16(10, Math.min(count, MAX16), true); + v.setUint32(12, Math.min(cdSize, MAX32), true); + v.setUint32(16, Math.min(cdStart, MAX32), true); + await this.emit(end); + } +} diff --git a/lib/mail-import/__tests__/engine.test.ts b/lib/mail-import/__tests__/engine.test.ts new file mode 100644 index 000000000..d21b4a4cf --- /dev/null +++ b/lib/mail-import/__tests__/engine.test.ts @@ -0,0 +1,223 @@ +import { describe, it, expect, vi } from 'vitest'; +import { runImport, uploadWindowEnd, quotaFrom, type ImportProgress, type ImportOptions } from '@/lib/mail-import/engine'; +import { checkpointKey, readCheckpoint } from '@/lib/mail-import/checkpoints'; +import type { IJMAPClient, UploadedEmailImport } from '@/lib/jmap/client-interface'; +import type { Mailbox } from '@/lib/jmap/types'; + +const mbox = (n: number, opts: { status?: (i: number) => string } = {}) => + Array.from({ length: n }, (_, i) => + `From a@example.org Tue May 5 10:${String(i % 60).padStart(2, '0')}:00 2020\nSubject: m${i}\n${opts.status?.(i) ?? ''}Date: Tue, 5 May 2020 10:00:00 +0000\n\nbody ${i}\n\n`, + ).join(''); + +// Checkpoints older than 30 days are dropped, so the fake clock starts now. +const T0 = Date.now(); + +const file = (content: string, name = 'mail.mbox') => new File([content], name, { lastModified: 1000 }); + +const mailbox = (id: string, name: string, extra: Partial = {}): Mailbox => ({ + id, name, sortOrder: 0, totalEmails: 0, unreadEmails: 0, totalThreads: 0, unreadThreads: 0, isSubscribed: true, + myRights: {} as Mailbox['myRights'], ...extra, +}); + +function fakeClient(opts: { failWhen?: (text: string) => boolean; quotaAfter?: number; uploadConcurrency?: number } = {}) { + let uploads = 0; + let inFlight = 0; + let maxInFlight = 0; + let quotaHit = false; + const imported: Array = []; + const blobs = new Map(); + const created: Array<{ name: string; parentId?: string; accountId?: string }> = []; + const mailboxes: Mailbox[] = [mailbox('inbox', 'Inbox', { role: 'inbox' }), mailbox('target', 'Target')]; + const importCalls: number[] = []; + const client = { + getCapabilities: () => ({ 'urn:ietf:params:jmap:core': { maxConcurrentUpload: opts.uploadConcurrency ?? 4 } }), + getMaxConcurrentRequests: () => 4, + getMaxSizeUpload: () => 1000, + getMailboxes: vi.fn(async () => mailboxes.map((m) => ({ ...m }))), + createMailbox: vi.fn(async (name: string, parentId?: string, accountId?: string) => { + created.push({ name, parentId, accountId }); + const box = mailbox(`new-${created.length}`, name, { parentId }); + return box; + }), + uploadBlob: vi.fn(async (f: File, o: { accountId?: string; signal?: AbortSignal }) => { + const n = uploads++; + inFlight++; + maxInFlight = Math.max(maxInFlight, inFlight); + try { + await new Promise((r) => setTimeout(r, 1)); + if (o.signal?.aborted) throw new DOMException('Upload aborted', 'AbortError'); + if (opts.quotaAfter !== undefined && n >= opts.quotaAfter && !quotaHit) { + quotaHit = true; + throw new Error('Failed to upload file: 429 - {"title":"Quota exceeded","detail":"You have exceeded the blob upload quota of 1000 files or 50000000 bytes."}'); + } + const text = await f.text(); + if (opts.failWhen?.(text)) throw new Error('Failed to upload file: 500 - oops'); + const id = `blob-${n}`; + blobs.set(id, text); + return { blobId: id, size: f.size, type: f.type }; + } finally { + inFlight--; + } + }), + importUploadedEmails: vi.fn(async (entries: UploadedEmailImport[], accountId?: string) => { + importCalls.push(entries.length); + return entries.map((e) => { + const text = blobs.get(e.blobId)!; + imported.push({ ...e, accountId, text }); + return { id: `email-${imported.length}` }; + }); + }), + } as unknown as IJMAPClient; + return { client, imported, created, importCalls, get maxInFlight() { return maxInFlight; }, get uploads() { return uploads; } }; +} + +function options(client: IJMAPClient, files: File[], extra: Partial = {}): ImportOptions { + let clock = T0; + return { + client, + accountId: 'acc-shared', + targetMailboxId: 'target', + keepFolders: false, + files: files.map((f) => ({ file: f, format: f.name.endsWith('.zip') ? 'zip' : f.name.endsWith('.eml') ? 'eml' : 'mbox' })), + scope: 'bulwark-1', + resume: false, + signal: new AbortController().signal, + onProgress: () => {}, + now: () => clock, + sleep: async (ms) => { clock += ms; }, + reportEveryMs: 0, + ...extra, + }; +} + +describe('runImport', () => { + it('imports every message of an mbox into the target folder of its own account, with state and date', async () => { + const fake = fakeClient(); + const status = (i: number) => (i === 1 ? 'Status: O\nX-Status: F\n' : i === 2 ? 'X-Mozilla-Status: 0009\n' : ''); + const result = await runImport(options(fake.client, [file(mbox(4, { status }))])); + expect(result.phase).toBe('done'); + expect(result.imported).toBe(3); + expect(result.skipped).toEqual({ deleted: 1 }); + expect(fake.imported.map((e) => e.accountId)).toEqual(['acc-shared', 'acc-shared', 'acc-shared']); + expect(fake.imported.every((e) => e.mailboxIds.target)).toBe(true); + expect(fake.imported.map((e) => e.keywords)).toEqual([{ $seen: true }, { $flagged: true }, { $seen: true }]); + expect(fake.imported[0].receivedAt).toBe('2020-05-05T10:00:00Z'); + // The envelope line is gone; Email/import would keep it as a broken header. + expect(fake.imported[0].text.startsWith('Subject: m0\n')).toBe(true); + }); + + 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))])); + expect(fake.imported).toHaveLength(45); + expect(Math.max(...fake.importCalls)).toBeGreaterThan(1); + expect(fake.importCalls.length).toBeLessThan(45); + // maxConcurrentRequests 4 leaves two requests for the rest of the app. + expect(fake.maxInFlight).toBeLessThanOrEqual(2); + }); + + it('pauses on the hourly upload limit until the window ends, then goes on', async () => { + const fake = fakeClient({ quotaAfter: 3 }); + const seen: ImportProgress[] = []; + const result = await runImport(options(fake.client, [file(mbox(6))], { onProgress: (p) => seen.push(p) })); + const paused = seen.find((p) => p.phase === 'paused'); + expect(paused?.pausedUntil).toBe(uploadWindowEnd(T0) + 5000); + expect(paused?.quota).toEqual({ files: 1000, bytes: 50_000_000 }); + expect(result.phase).toBe('done'); + expect(result.imported).toBe(6); + expect(fake.imported.map((e) => e.text.split('\n')[0]).sort()).toEqual(['Subject: m0', 'Subject: m1', 'Subject: m2', 'Subject: m3', 'Subject: m4', 'Subject: m5']); + }); + + it('counts a message that keeps failing and carries on with the rest', async () => { + const fake = fakeClient({ failWhen: (text) => text.startsWith('Subject: m1\n') }); + const result = await runImport(options(fake.client, [file(mbox(3))])); + expect(result.imported).toBe(2); + expect(result.failed).toBe(1); + expect(result.failures).toEqual([{ label: 'm1', reason: 'HTTP 500' }]); + // Tried three times before giving up. + expect(fake.uploads).toBe(5); + }); + + it('skips messages over the upload size', async () => { + const fake = fakeClient(); + const big = `From a Tue May 5 10:11:12 2020\nSubject: big\n\n${'x'.repeat(2000)}\n\n`; + const result = await runImport(options(fake.client, [file(big + mbox(1))])); + expect(result.skipped).toEqual({ too_large: 1 }); + expect(result.imported).toBe(1); + }); + + it('keeps a checkpoint when cancelled and continues there without importing twice', async () => { + const fake = fakeClient(); + const controller = new AbortController(); + const f = file(mbox(30)); + let cancelledAt = 0; + const first = await runImport(options(fake.client, [f], { + signal: controller.signal, + onProgress: (p) => { if (p.imported >= 5 && !controller.signal.aborted) { cancelledAt = p.imported; controller.abort(); } }, + })); + expect(first.phase).toBe('cancelled'); + expect(cancelledAt).toBeGreaterThan(0); + const key = checkpointKey({ scope: 'bulwark-1', accountId: 'acc-shared', target: 'target', keepFolders: false, file: f }); + const saved = await readCheckpoint(key); + expect(saved?.done).toBe(first.imported); + + const second = await runImport(options(fake.client, [f], { resume: true })); + expect(second.phase).toBe('done'); + expect(second.imported).toBe(30); + const subjects = fake.imported.map((e) => e.text.split('\n')[0]); + expect(new Set(subjects).size).toBe(30); + expect(subjects).toHaveLength(30); + await expect(readCheckpoint(key)).resolves.toBeNull(); + }); + + it('starts again from the top when not resuming', async () => { + const fake = fakeClient(); + const f = file(mbox(3)); + await runImport(options(fake.client, [f])); + await runImport(options(fake.client, [f])); + expect(fake.imported).toHaveLength(6); + }); + + it('recreates a file\'s folders under the target, once, reusing ones that exist', async () => { + const { ZipWriter } = await import('@/lib/mail-export/zip-writer'); + const parts: Uint8Array[] = []; + const z = new ZipWriter(async (c) => { parts.push(c.slice()); }); + const enc = new TextEncoder(); + await z.addFile('Projects/2020/a.eml', enc.encode('Subject: a\r\n\r\nA')); + await z.addFile('Projects/2020/b.eml', enc.encode('Subject: b\r\n\r\nB')); + await z.addFile('Projects/c.eml', enc.encode('Subject: c\r\n\r\nC')); + await z.addFile('Target/d.eml', enc.encode('Subject: d\r\n\r\nD')); + await z.addFile('top.eml', enc.encode('Subject: t\r\n\r\nT')); + await z.finish(); + const zip = new File(parts as BlobPart[], 'export.zip', { lastModified: 1 }); + + const fake = fakeClient(); + const result = await runImport(options(fake.client, [zip], { keepFolders: true, targetMailboxId: null })); + expect(result.imported).toBe(5); + expect(fake.created).toEqual([ + { name: 'Projects', parentId: undefined, accountId: 'acc-shared' }, + { name: '2020', parentId: 'new-1', accountId: 'acc-shared' }, + ]); + const byText = Object.fromEntries(fake.imported.map((e) => [e.text.split('\r\n')[0], Object.keys(e.mailboxIds)[0]])); + // "Target" exists at the top; a message at the top of the file goes to the Inbox. + expect(byText).toEqual({ 'Subject: a': 'new-2', 'Subject: b': 'new-2', 'Subject: c': 'new-1', 'Subject: d': 'target', 'Subject: t': 'inbox' }); + expect(result.foldersCreated).toBe(2); + }); + + it('puts everything into the target when folders are not kept', async () => { + const { ZipWriter } = await import('@/lib/mail-export/zip-writer'); + const parts: Uint8Array[] = []; + const z = new ZipWriter(async (c) => { parts.push(c.slice()); }); + await z.addFile('Deep/x.eml', new TextEncoder().encode('Subject: x\r\n\r\nX')); + await z.finish(); + const fake = fakeClient(); + await runImport(options(fake.client, [new File(parts as BlobPart[], 'e.zip')])); + expect(fake.created).toEqual([]); + expect(Object.keys(fake.imported[0].mailboxIds)).toEqual(['target']); + }); + + it('reads the hour window and the quota from the server', () => { + expect(uploadWindowEnd(Date.UTC(2026, 10, 1, 13, 20))).toBe(Date.UTC(2026, 10, 1, 14, 0)); + expect(quotaFrom('You have exceeded the blob upload quota of 1000 files or 50000000 bytes.')).toEqual({ files: 1000, bytes: 50000000 }); + }); +}); diff --git a/lib/mail-import/__tests__/mbox-status.test.ts b/lib/mail-import/__tests__/mbox-status.test.ts new file mode 100644 index 000000000..249eb8ff4 --- /dev/null +++ b/lib/mail-import/__tests__/mbox-status.test.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest'; +import { mboxState } from '@/lib/mail-import/mbox-status'; + +describe('mboxState', () => { + it('imports a message without any state header as read, as before', () => { + expect(mboxState('Subject: x\nDate: Tue, 5 May 2020 10:00:00 +0000')).toEqual({ keywords: { $seen: true }, expunged: false }); + }); + + it('reads Status R as read and Status O alone as unread', () => { + expect(mboxState('Status: RO').keywords).toEqual({ $seen: true }); + expect(mboxState('Status: O').keywords).toEqual({}); + }); + + it('reads X-Status answered, flagged and draft', () => { + expect(mboxState('Status: O\nX-Status: AFT').keywords).toEqual({ $answered: true, $flagged: true, $draft: true }); + }); + + it('reads Thunderbird X-Mozilla-Status bits', () => { + expect(mboxState('X-Mozilla-Status: 0001').keywords).toEqual({ $seen: true }); + expect(mboxState('X-Mozilla-Status: 0000').keywords).toEqual({}); + expect(mboxState('X-Mozilla-Status: 1006').keywords).toEqual({ $answered: true, $flagged: true, $forwarded: true }); + }); + + it('marks a Thunderbird-expunged message (0x8) to be skipped', () => { + expect(mboxState('X-Mozilla-Status: 0009').expunged).toBe(true); + expect(mboxState('X-Mozilla-Status: 0001').expunged).toBe(false); + }); + + it('adds X-Keywords and X-Mozilla-Keys, skipping invalid and server-set ones', () => { + const { keywords } = mboxState('Status: R\nX-Keywords: Work, $label1 bad(word) $HasAttachment\nX-Mozilla-Keys: todo'); + expect(keywords).toEqual({ $seen: true, work: true, $label1: true, todo: true }); + }); + + it('reads Google Takeout labels', () => { + expect(mboxState('X-Gmail-Labels: Inbox,Unread').keywords).toEqual({}); + expect(mboxState('X-Gmail-Labels: Inbox,Starred').keywords).toEqual({ $seen: true, $flagged: true }); + }); + + it('reads folded and differently cased headers', () => { + expect(mboxState('subject: a\r\nx-status:\r\n F\r\nSTATUS: O').keywords).toEqual({ $flagged: true }); + }); +}); diff --git a/lib/mail-import/__tests__/mbox.test.ts b/lib/mail-import/__tests__/mbox.test.ts new file mode 100644 index 000000000..db335b015 --- /dev/null +++ b/lib/mail-import/__tests__/mbox.test.ts @@ -0,0 +1,155 @@ +import { describe, it, expect } from 'vitest'; +import { MboxSplitter, envelopeDate, readHeaderBlock, type MboxMessage } from '@/lib/mail-import/mbox'; + +const enc = new TextEncoder(); +const latin1 = (b: Uint8Array) => Array.from(b, (c) => String.fromCharCode(c)).join(''); + +function join(parts: Uint8Array[]): Uint8Array { + const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0)); + let at = 0; + for (const p of parts) { out.set(p, at); at += p.length; } + return out; +} + +/** Splits `data` fed in chunks of `chunk` bytes (every size hits a different edge). */ +function split(data: Uint8Array | string, chunk = data.length, options = {}): MboxMessage[] { + const bytes = typeof data === 'string' ? enc.encode(data) : data; + const s = new MboxSplitter(options); + const out: MboxMessage[] = []; + for (let i = 0; i < bytes.length; i += Math.max(1, chunk)) out.push(...s.push(bytes.subarray(i, i + Math.max(1, chunk)))); + out.push(...s.end()); + return out; +} +const texts = (msgs: MboxMessage[]) => msgs.map((m) => latin1(join(m.parts))); + +const TWO = [ + 'From alice@example.org Tue May 5 10:11:12 2020', + 'Subject: one', + '', + 'Body one', + '', + 'From bob@example.org Wed May 6 11:00:00 2020', + 'Subject: two', + '', + 'Body two', + '', +].join('\n'); + +describe('MboxSplitter', () => { + it('splits on From lines after an empty line and drops the envelope line and the separator', () => { + const msgs = split(TWO); + expect(texts(msgs)).toEqual(['Subject: one\n\nBody one\n', 'Subject: two\n\nBody two\n']); + expect(msgs[0].fromLine).toBe('From alice@example.org Tue May 5 10:11:12 2020'); + expect(msgs[1].offset).toBe(TWO.indexOf('From bob')); + }); + + it('gives the same messages whatever the chunk size', () => { + const whole = texts(split(TWO)); + for (const size of [1, 2, 3, 5, 7, 13, 64]) expect(texts(split(TWO, size))).toEqual(whole); + }); + + it('keeps a From line that follows a non-empty line in the body', () => { + const mbox = 'From a Tue May 5 10:11:12 2020\nSubject: x\n\nline\nFrom here on\n'; + expect(texts(split(mbox))).toEqual(['Subject: x\n\nline\nFrom here on\n']); + }); + + it('does not split on a body line "From now on" after an empty line that has no date', () => { + const mbox = 'From a Tue May 5 10:11:12 2020\nSubject: x\n\nhello\n\nFrom now on we meet\n'; + expect(texts(split(mbox))).toHaveLength(1); + }); + + it('unquotes mboxrd ">From " lines by one ">"', () => { + const mbox = 'From a Tue May 5 10:11:12 2020\nSubject: q\n\n>From the start\n>>From deeper\n> not a from\n>Fromage\n'; + expect(texts(split(mbox, 4))).toEqual(['Subject: q\n\nFrom the start\n>From deeper\n> not a from\n>Fromage\n']); + }); + + it('handles CRLF files and keeps CRLF in the message', () => { + const msgs = split(TWO.replace(/\n/g, '\r\n'), 3); + expect(texts(msgs)).toEqual(['Subject: one\r\n\r\nBody one\r\n', 'Subject: two\r\n\r\nBody two\r\n']); + }); + + it('passes 8-bit bytes through untouched', () => { + const head = enc.encode('From a Tue May 5 10:11:12 2020\nSubject: x\nContent-Type: text/plain; charset=iso-8859-1\n\n'); + const body = new Uint8Array([0x47, 0x72, 0xfc, 0xdf, 0x65, 0x0a, 0xff, 0xfe, 0x80, 0x0a]); + const msgs = split(join([head, body]), 5); + const out = join(msgs[0].parts); + expect(Array.from(out.subarray(out.length - body.length))).toEqual(Array.from(body)); + }); + + it('keeps an empty line the message ends with when the separator adds another', () => { + const mbox = 'From a Tue May 5 10:11:12 2020\nSubject: x\n\nbody\n\n\nFrom b Tue May 5 10:11:12 2020\nSubject: y\n\nz\n'; + expect(texts(split(mbox))).toEqual(['Subject: x\n\nbody\n\n', 'Subject: y\n\nz\n']); + }); + + it('ignores bytes before the first envelope line', () => { + expect(texts(split('\n\nFrom a Tue May 5 10:11:12 2020\nSubject: x\n\nb\n'))).toEqual(['Subject: x\n\nb\n']); + }); + + describe('Content-Length (mboxcl2)', () => { + const body = 'first\n\nFrom inside Tue May 5 10:11:12 2020\nstill body\n'; + const mboxcl2 = `From a Tue May 5 10:11:12 2020\nSubject: one\nContent-Length: ${body.length}\n\n${body}\nFrom b Wed May 6 11:00:00 2020\nSubject: two\n\nz\n`; + + it('keeps an unquoted From line inside a body whose Content-Length lands on the next message', () => { + for (const size of [1, 4, 9, mboxcl2.length]) { + const out = texts(split(mboxcl2, size)); + expect(out).toHaveLength(2); + expect(out[0]).toBe(`Subject: one\nContent-Length: ${body.length}\n\n${body}`); + expect(out[1]).toBe('Subject: two\n\nz\n'); + } + }); + + it('trusts a length that lands right on the next From line too', () => { + const withBlank = `${body}\n`; + const mbox = `From a Tue May 5 10:11:12 2020\nContent-Length: ${withBlank.length}\n\n${withBlank}From b Wed May 6 11:00:00 2020\n\nz\n`; + expect(texts(split(mbox, 3))).toHaveLength(2); + }); + + it('splits at the From lines after all when the length is wrong', () => { + const wrong = mboxcl2.replace(`Content-Length: ${body.length}`, 'Content-Length: 5'); + const out = texts(split(wrong, 7)); + expect(out).toHaveLength(3); + expect(out[0]).toBe('Subject: one\nContent-Length: 5\n\nfirst\n'); + expect(out[1]).toBe('still body\n'); + expect(out[2]).toBe('Subject: two\n\nz\n'); + }); + + it('splits when the length runs past the end of the file', () => { + const past = mboxcl2.replace(`Content-Length: ${body.length}`, 'Content-Length: 99999'); + expect(texts(split(past))).toHaveLength(3); + }); + + it('trusts a length that ends the file', () => { + const last = `From a Tue May 5 10:11:12 2020\nContent-Length: ${body.length}\n\n${body}`; + expect(texts(split(last, 2))).toEqual([`Content-Length: ${body.length}\n\n${body}`]); + }); + }); + + it('reports a message over the size limit without keeping its bytes', () => { + const big = `From a Tue May 5 10:11:12 2020\nSubject: big\n\n${'x'.repeat(500)}\n\nFrom b Wed May 6 11:00:00 2020\nSubject: small\n\nok\n`; + const msgs = split(big, 50, { maxMessageBytes: 100 }); + expect(msgs[0].tooLarge).toBe(true); + expect(msgs[0].parts).toEqual([]); + expect(msgs[0].size).toBeGreaterThan(100); + expect(msgs[1].tooLarge).toBe(false); + expect(latin1(join(msgs[1].parts))).toBe('Subject: small\n\nok\n'); + }); +}); + +describe('envelopeDate', () => { + it('reads the asctime date of an envelope line as UTC', () => { + expect(envelopeDate('From alice@example.org Tue May 5 10:11:12 2020')).toBe('2020-05-05T10:11:12Z'); + expect(envelopeDate('From - Fri Jan 31 23:59:00 2014')).toBe('2014-01-31T23:59:00Z'); + }); + it('applies a zone written before the year (Gmail Takeout)', () => { + expect(envelopeDate('From 1234@xxx Sat Apr 18 08:29:01 +0200 2020')).toBe('2020-04-18T06:29:01Z'); + }); + it('returns undefined without a date', () => { + expect(envelopeDate('From someone')).toBeUndefined(); + }); +}); + +describe('readHeaderBlock', () => { + it('stops at the first empty line', () => { + expect(readHeaderBlock([enc.encode('A: 1\r\nB: 2\r\n\r\nbody')])).toBe('A: 1\r\nB: 2'); + }); +}); diff --git a/lib/mail-import/checkpoints.ts b/lib/mail-import/checkpoints.ts new file mode 100644 index 000000000..ec0c3c203 --- /dev/null +++ b/lib/mail-import/checkpoints.ts @@ -0,0 +1,151 @@ +/** + * Where an import stopped, per file, so a cancelled, crashed or paused import + * of a large file continues instead of importing every message again + * (Email/import doesn't skip duplicates). Kept in IndexedDB, keyed by the + * Bulwark account, the target and the file's name, size and date; removed + * when the import finishes, when the account signs out and on a full + * sign-out. Falls back to memory where IndexedDB isn't available. + */ + +export interface ImportCheckpoint { + key: string; + /** The Bulwark account that ran the import. */ + scope: string; + fileName: string; + /** Items handled (imported, skipped or failed) from the start of the file. */ + done: number; + imported: number; + updatedAt: number; +} + +const DB_NAME = "bulwark-mail-import"; +const STORE = "checkpoints"; +/** Older checkpoints are dropped: the file has likely changed or been forgotten. */ +const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000; + +export function checkpointKey(parts: { + scope: string; + accountId: string; + target: string | null; + keepFolders: boolean; + file: { name: string; size: number; lastModified: number }; +}): string { + return [parts.scope, parts.accountId, parts.target ?? "", parts.keepFolders ? "1" : "0", parts.file.name, parts.file.size, parts.file.lastModified].join("\u0000"); +} + +const memory = new Map(); + +function hasIdb(): boolean { + try { + return typeof indexedDB !== "undefined" && indexedDB !== null; + } catch { + return false; + } +} + +let dbPromise: Promise | null = null; + +function openDb(): Promise { + if (dbPromise) return dbPromise; + dbPromise = new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, 1); + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains(STORE)) { + db.createObjectStore(STORE, { keyPath: "key" }).createIndex("scope", "scope"); + } + }; + request.onsuccess = () => { + const db = request.result; + // Another tab deleting the database (sign-out) must not wait on us. + db.onversionchange = () => { db.close(); dbPromise = null; }; + resolve(db); + }; + request.onerror = () => { dbPromise = null; reject(request.error); }; + request.onblocked = () => { dbPromise = null; reject(new Error("IndexedDB blocked")); }; + }); + return dbPromise; +} + +function done(request: IDBRequest): Promise { + return new Promise((resolve, reject) => { + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); +} + +async function withStore(mode: IDBTransactionMode, run: (store: IDBObjectStore) => IDBRequest): Promise { + const db = await openDb(); + return done(run(db.transaction(STORE, mode).objectStore(STORE))); +} + +export async function readCheckpoint(key: string, now = Date.now()): Promise { + let found: ImportCheckpoint | null; + if (!hasIdb()) { + found = memory.get(key) ?? null; + } else { + try { + found = ((await withStore("readonly", (s) => s.get(key))) as ImportCheckpoint | undefined) ?? null; + } catch { + found = memory.get(key) ?? null; + } + } + if (found && now - found.updatedAt > MAX_AGE_MS) { + await deleteCheckpoint(key); + return null; + } + return found; +} + +export async function writeCheckpoint(checkpoint: ImportCheckpoint): Promise { + memory.set(checkpoint.key, checkpoint); + if (!hasIdb()) return; + try { + await withStore("readwrite", (s) => s.put(checkpoint)); + memory.delete(checkpoint.key); + } catch { + /* kept in memory for this tab */ + } +} + +export async function deleteCheckpoint(key: string): Promise { + memory.delete(key); + if (!hasIdb()) return; + try { + await withStore("readwrite", (s) => s.delete(key)); + } catch { + /* nothing stored */ + } +} + +/** Removes the checkpoints of one Bulwark account (it signs out). */ +export async function clearCheckpointsFor(scope: string): Promise { + for (const [key, value] of memory) if (value.scope === scope) memory.delete(key); + if (!hasIdb()) return; + try { + const db = await openDb(); + const tx = db.transaction(STORE, "readwrite"); + const keys = await done(tx.objectStore(STORE).index("scope").getAllKeys(scope)); + const store = db.transaction(STORE, "readwrite").objectStore(STORE); + await Promise.all(keys.map((k) => done(store.delete(k)))); + } catch { + /* nothing stored */ + } +} + +/** Removes every checkpoint (everyone signed out). */ +export async function clearAllCheckpoints(): Promise { + memory.clear(); + if (!hasIdb()) return; + const db = dbPromise ? await dbPromise.catch(() => null) : null; + db?.close(); + dbPromise = null; + await new Promise((resolve) => { + try { + const request = indexedDB.deleteDatabase(DB_NAME); + request.onsuccess = request.onerror = request.onblocked = () => resolve(); + } catch { + resolve(); + } + }); +} diff --git a/lib/mail-import/engine.ts b/lib/mail-import/engine.ts new file mode 100644 index 000000000..afd6c2292 --- /dev/null +++ b/lib/mail-import/engine.ts @@ -0,0 +1,492 @@ +import type { IJMAPClient, UploadedEmailImport, UploadedEmailImportResult } from "@/lib/jmap/client-interface"; +import type { Mailbox } from "@/lib/jmap/types"; +import { checkpointKey, deleteCheckpoint, readCheckpoint, writeCheckpoint } from "./checkpoints"; +import { emlSource, mboxSource, zipSource, type ImportFormat, type ImportItem, type ItemSource, type SkipReason, type SourceOptions } from "./sources"; + +/** + * One engine for every mail import (EML, ZIP, mbox, PST): it reads messages + * from the files, uploads them a few at a time, imports them several per + * Email/import, and creates the file's folders where asked. + * + * Paced to the server: at most maxConcurrentUpload uploads (and two request + * slots left for the rest of the app), through the XHR upload path so a + * refusal doesn't pause the whole client. Stalwart takes 1000 uploads or + * 50 MB per user per hour (counted in fixed hour windows, whether the blobs + * get imported or not) and answers HTTP 429 past that: the import then + * pauses until the window ends and goes on by itself. A checkpoint per file + * lets a cancelled or interrupted import continue where it stopped. + */ + +export type ImportPhase = "running" | "paused" | "done" | "cancelled" | "failed"; + +export interface ImportFailure { + label: string; + reason: string; +} + +export interface ImportProgress { + phase: ImportPhase; + imported: number; + failed: number; + skipped: Partial>; + /** The first failures, for the summary. */ + failures: ImportFailure[]; + /** Part of all files read, 0 to 1. */ + fraction: number; + /** Messages in the files, once known for all of them. */ + total?: number; + /** The file being read. */ + fileName?: string; + /** While paused by the upload limit: when the import goes on (ms since epoch). */ + pausedUntil?: number; + /** The limit the server named, when it did. */ + quota?: { files?: number; bytes?: number }; + foldersCreated: number; + /** Why the whole import stopped (phase failed). */ + error?: string; +} + +export interface ImportFile { + file: File; + format: ImportFormat; +} + +/** Opens a PST source (loaded only when a PST is imported). */ +export type PstOpener = (file: File, options: SourceOptions) => ItemSource; + +export interface ImportOptions { + client: IJMAPClient; + /** The JMAP account the messages go into (a shared folder's own account). */ + accountId: string; + /** Folder the messages go into; null is the top level (only with keepFolders). */ + targetMailboxId: string | null; + /** Recreate the files' folders under the target; otherwise everything goes into it. */ + keepFolders: boolean; + files: ImportFile[]; + /** The Bulwark account, for the checkpoints. */ + scope: string; + /** Continue after the items an earlier run of the same import handled. */ + resume: boolean; + signal: AbortSignal; + onProgress: (progress: ImportProgress) => void; + openPst?: PstOpener; + /** Test seams. */ + now?: () => number; + sleep?: (ms: number, signal: AbortSignal) => Promise; + /** Least time between two progress reports (ms). */ + reportEveryMs?: number; +} + +const MAX_FAILURES_LISTED = 20; +/** Messages per Email/import call. */ +const IMPORT_BATCH = 20; +/** How long a few uploaded messages wait for more before they're imported. */ +const IMPORT_FLUSH_MS = 400; +/** Retries of an upload that failed for another reason than the limit. */ +const UPLOAD_RETRY_DELAYS_MS = [2000, 8000]; +/** Stalwart's upload window (uploadTtl, 1 h by default). */ +const UPLOAD_WINDOW_MS = 60 * 60 * 1000; +const CHECKPOINT_EVERY_MS = 2000; + +export class ImportCancelled extends Error { + constructor() { + super("Import cancelled"); + this.name = "AbortError"; + } +} + +function isAbort(error: unknown): boolean { + return (error as { name?: string } | null)?.name === "AbortError"; +} + +function defaultSleep(ms: number, signal: AbortSignal): Promise { + return new Promise((resolve, reject) => { + if (signal.aborted) { reject(new ImportCancelled()); return; } + const timer = setTimeout(() => { signal.removeEventListener("abort", onAbort); resolve(); }, ms); + const onAbort = () => { clearTimeout(timer); reject(new ImportCancelled()); }; + signal.addEventListener("abort", onAbort, { once: true }); + }); +} + +/** What an upload error says: the client's XHR path reports "Failed to upload file: - ". */ +export function uploadErrorInfo(error: unknown): { status?: number; body: string } { + const message = error instanceof Error ? error.message : String(error ?? ""); + const m = /Failed to upload file: (\d{3}) - ([\s\S]*)$/.exec(message); + return m ? { status: Number(m[1]), body: m[2] } : { body: message }; +} + +/** "You have exceeded the blob upload quota of 1000 files or 50000000 bytes." */ +export function quotaFrom(body: string): { files?: number; bytes?: number } { + const m = /quota of (\d+) files? or (\d+) bytes/i.exec(body); + return m ? { files: Number(m[1]), bytes: Number(m[2]) } : {}; +} + +/** + * When the upload limit lets uploads through again. The server doesn't send + * Retry-After in a way the browser may read (no CORS exposure), so this is + * the end of the current window: Stalwart counts in fixed windows of + * uploadTtl since the epoch, full hours by default. + */ +export function uploadWindowEnd(now: number, windowMs = UPLOAD_WINDOW_MS): number { + return (Math.floor(now / windowMs) + 1) * windowMs; +} + +function maxConcurrentUploads(client: IJMAPClient): number { + const core = client.getCapabilities()["urn:ietf:params:jmap:core"] as { maxConcurrentUpload?: unknown } | undefined; + const uploads = typeof core?.maxConcurrentUpload === "number" && core.maxConcurrentUpload > 0 ? core.maxConcurrentUpload : 4; + // Uploads and imports share the request slots with the rest of the app; leave it two. + return Math.max(1, Math.min(uploads, (client.getMaxConcurrentRequests?.() ?? 4) - 2)); +} + +/** Top-level folder names that mean a special folder, as Outlook and others name them. */ +const ROLE_NAMES: Record = { + inbox: ["inbox", "posteingang"], + sent: ["sent", "sent items", "sent mail", "sent messages", "gesendet", "gesendete elemente", "gesendete objekte"], + drafts: ["drafts", "entwürfe"], + trash: ["trash", "deleted items", "deleted messages", "papierkorb", "gelöschte elemente", "gelöschte objekte"], + junk: ["junk", "junk e-mail", "junk email", "junk mail", "spam", "junk-e-mail"], + archive: ["archive", "archiv"], +}; + +function roleFor(name: string): string | undefined { + const key = name.trim().toLowerCase(); + return Object.keys(ROLE_NAMES).find((role) => ROLE_NAMES[role].includes(key)); +} + +/** + * Maps a file's folder paths to mailboxes in the account, creating what's + * missing once. A folder with the same name under the same parent is used + * again, so a resumed import doesn't make a second copy of the tree; at the + * top level an "Inbox" or "Sent Items" means the account's own. + */ +export class FolderResolver { + private mailboxes: Mailbox[] | null = null; + private readonly cache = new Map>(); + created = 0; + + constructor( + private readonly client: IJMAPClient, + private readonly accountId: string, + private readonly root: string | null, + ) {} + + private async list(): Promise { + if (!this.mailboxes) this.mailboxes = await this.client.getMailboxes(this.accountId); + return this.mailboxes; + } + + private static idOf(m: Mailbox): string { + return m.originalId || m.id; + } + + private async inbox(): Promise { + const box = (await this.list()).find((m) => m.role === "inbox"); + if (!box) throw new Error("No Inbox in this account"); + return FolderResolver.idOf(box); + } + + resolve(path: string[]): Promise { + const key = path.join("\u0000"); + let pending = this.cache.get(key); + if (!pending) { + pending = this.resolveUncached(path); + // A failed creation may be tried again by the next message. + pending.catch(() => this.cache.delete(key)); + this.cache.set(key, pending); + } + return pending; + } + + private async resolveUncached(path: string[]): Promise { + if (path.length === 0) return this.root ?? this.inbox(); + const parentId = path.length === 1 ? this.root : await this.resolve(path.slice(0, -1)); + const name = path[path.length - 1].trim().slice(0, 255) || "Imported"; + const mailboxes = await this.list(); + const same = (m: Mailbox) => m.name.trim().toLowerCase() === name.toLowerCase(); + const parentMatches = (m: Mailbox) => (parentId === null ? !m.parentId : m.parentId === parentId || m.parentId === mailboxes.find((p) => FolderResolver.idOf(p) === parentId)?.id); + const existing = mailboxes.find((m) => parentMatches(m) && same(m)); + if (existing) return FolderResolver.idOf(existing); + if (parentId === null) { + const role = roleFor(name); + const special = role && mailboxes.find((m) => m.role === role); + if (special) return FolderResolver.idOf(special); + } + const created = await this.client.createMailbox(name, parentId ?? undefined, this.accountId); + this.created++; + mailboxes.push({ ...created, parentId: parentId ?? undefined }); + return FolderResolver.idOf(created); + } +} + +interface Uploaded { + index: number; + fileIndex: number; + label: string; + entry: UploadedEmailImport; +} + +/** Counts handled items per file, so the checkpoint is the first index not handled yet. */ +class Watermark { + private next = 0; + private readonly ahead = new Set(); + constructor(start: number) { this.next = start; } + mark(index: number): void { + if (index < this.next) return; + this.ahead.add(index); + while (this.ahead.has(this.next)) { this.ahead.delete(this.next); this.next++; } + } + get value(): number { return this.next; } +} + +export async function runImport(options: ImportOptions): Promise { + const { client, accountId, signal } = options; + const now = options.now ?? Date.now; + const sleep = options.sleep ?? defaultSleep; + const progress: ImportProgress = { phase: "running", imported: 0, failed: 0, skipped: {}, failures: [], fraction: 0, foldersCreated: 0 }; + const folders = new FolderResolver(client, accountId, options.targetMailboxId); + const maxSize = client.getMaxSizeUpload() || Number.POSITIVE_INFINITY; + const lanes = maxConcurrentUploads(client); + + let lastReport = 0; + const report = (force = false) => { + const t = now(); + if (!force && t - lastReport < (options.reportEveryMs ?? 150)) return; + lastReport = t; + progress.foldersCreated = folders.created; + options.onProgress({ ...progress, skipped: { ...progress.skipped }, failures: [...progress.failures] }); + }; + const skip = (reason: SkipReason, n = 1) => { progress.skipped[reason] = (progress.skipped[reason] ?? 0) + n; }; + const fail = (label: string, reason: string) => { + progress.failed++; + if (progress.failures.length < MAX_FAILURES_LISTED) progress.failures.push({ label, reason }); + }; + + // Shared pause: when the server refuses uploads, every lane waits. + let pausedUntil = 0; + const waitWhilePaused = async () => { + while (pausedUntil > now()) { + progress.phase = "paused"; + progress.pausedUntil = pausedUntil; + report(true); + await sleep(Math.max(1000, pausedUntil - now()), signal); + } + if (progress.phase === "paused") { + progress.phase = "running"; + progress.pausedUntil = undefined; + report(true); + } + }; + + const sizes = options.files.map((f) => f.file.size); + const totalBytes = sizes.reduce((a, b) => a + b, 0) || 1; + const fileFraction = options.files.map(() => 0); + const totals: (number | undefined)[] = options.files.map(() => undefined); + const setFraction = () => { + progress.fraction = Math.min(1, fileFraction.reduce((n, f, i) => n + f * sizes[i], 0) / totalBytes); + }; + + const keys = options.files.map(({ file }) => checkpointKey({ + scope: options.scope, accountId, target: options.targetMailboxId, keepFolders: options.keepFolders, file, + })); + const marks: Watermark[] = []; + const importedPerFile: number[] = options.files.map(() => 0); + let lastSaved = 0; + const saveCheckpoints = async (force = false) => { + if (!force && now() - lastSaved < CHECKPOINT_EVERY_MS) return; + lastSaved = now(); + await Promise.all(marks.map((m, i) => m.value > 0 ? writeCheckpoint({ + key: keys[i], scope: options.scope, fileName: options.files[i].file.name, done: m.value, imported: importedPerFile[i], updatedAt: now(), + }) : Promise.resolve())); + }; + + // ── Email/import in batches ── + let queue: Uploaded[] = []; + let importing: Promise = Promise.resolve(); + let flushTimer: ReturnType | null = null; + const handled = (u: { fileIndex: number; index: number }) => marks[u.fileIndex].mark(u.index); + + const importBatch = async (batch: Uploaded[]) => { + let results: UploadedEmailImportResult[] | null = null; + for (let attempt = 0; results === null; attempt++) { + try { + if (client.importUploadedEmails) { + results = await client.importUploadedEmails(batch.map((b) => b.entry), accountId); + } else { + // A client without batch import (the demo) can't take uploaded blobs either. + results = batch.map(() => ({ error: "Import isn't available here" })); + } + } catch (error) { + const name = (error as { name?: string } | null)?.name; + if (name === "RateLimitError" && attempt < 3) { + await sleep(Math.max(1000, (error as { retryAfterMs?: number }).retryAfterMs ?? 60_000), signal); + continue; + } + if (attempt < 1 && !isAbort(error)) { await sleep(2000, signal); continue; } + results = batch.map(() => ({ error: error instanceof Error ? error.message : String(error) })); + } + } + results.forEach((result, i) => { + const item = batch[i]; + if ("id" in result) { + progress.imported++; + importedPerFile[item.fileIndex]++; + } else { + fail(item.label, result.type === "invalidEmail" ? "invalid" : result.error); + } + handled(item); + }); + report(); + await saveCheckpoints(); + }; + + const flush = () => { + if (flushTimer) { clearTimeout(flushTimer); flushTimer = null; } + if (queue.length === 0) return importing; + const batch = queue; + queue = []; + importing = importing.then(() => importBatch(batch)); + return importing; + }; + const enqueue = (u: Uploaded) => { + queue.push(u); + if (queue.length >= IMPORT_BATCH) void flush(); + else if (!flushTimer) flushTimer = setTimeout(() => { flushTimer = null; void flush(); }, IMPORT_FLUSH_MS); + }; + + // ── Uploads, a few at a time ── + const running = new Set>(); + const upload = async (item: Extract, fileIndex: number) => { + let mailboxId: string; + try { + mailboxId = await folders.resolve(options.keepFolders ? item.folder : []); + } catch (error) { + if (isAbort(error)) throw error; + fail(item.label, error instanceof Error ? error.message : String(error)); + handled({ fileIndex, index: item.index }); + return; + } + const file = new File([item.data], "message.eml", { type: "message/rfc822" }); + for (let attempt = 0; ; attempt++) { + await waitWhilePaused(); + try { + const { blobId } = await client.uploadBlob(file, { accountId, signal }); + enqueue({ + index: item.index, + fileIndex, + label: item.label, + entry: { blobId, mailboxIds: { [mailboxId]: true }, keywords: item.keywords, receivedAt: item.receivedAt }, + }); + return; + } catch (error) { + if (isAbort(error) || signal.aborted) throw new ImportCancelled(); + const { status, body } = uploadErrorInfo(error); + if (status === 429) { + pausedUntil = Math.max(pausedUntil, uploadWindowEnd(now()) + 5000); + progress.quota = quotaFrom(body); + continue; + } + if (status === 413 || (status === 400 && /maxSizeUpload/.test(body))) { + skip("too_large"); + handled({ fileIndex, index: item.index }); + return; + } + if (attempt < UPLOAD_RETRY_DELAYS_MS.length) { + await sleep(UPLOAD_RETRY_DELAYS_MS[attempt], signal); + continue; + } + fail(item.label, status ? `HTTP ${status}` : body); + handled({ fileIndex, index: item.index }); + return; + } + } + }; + + const startUpload = async (item: Extract, fileIndex: number) => { + while (running.size >= lanes) await Promise.race(running); + if (signal.aborted) throw new ImportCancelled(); + // A lane never rejects: a cancel shows in `signal`, anything else is a failure of this message. + const task: Promise = upload(item, fileIndex) + .catch((error) => { + if (!isAbort(error) && !signal.aborted) { + fail(item.label, error instanceof Error ? error.message : String(error)); + handled({ fileIndex, index: item.index }); + } + }) + .finally(() => { running.delete(task); }); + running.add(task); + }; + + try { + for (let f = 0; f < options.files.length; f++) { + const { file, format } = options.files[f]; + const checkpoint = options.resume ? await readCheckpoint(keys[f]) : null; + if (!options.resume) await deleteCheckpoint(keys[f]); + const skipBefore = checkpoint?.done ?? 0; + if (checkpoint) { progress.imported += checkpoint.imported; importedPerFile[f] = checkpoint.imported; } + marks.push(new Watermark(skipBefore)); + progress.fileName = file.name; + const sourceOptions: SourceOptions = { + maxMessageBytes: maxSize, + skipBefore, + signal, + onProgress: (fraction) => { fileFraction[f] = fraction; setFraction(); report(); }, + onTotal: (count) => { + totals[f] = count; + if (totals.every((t) => t !== undefined)) progress.total = totals.reduce((a: number, b) => a + (b ?? 0), 0); + report(); + }, + }; + let source: ItemSource; + if (format === "pst") { + if (!options.openPst) throw new Error("PST import isn't available"); + source = options.openPst(file, sourceOptions); + } else if (format === "mbox") source = mboxSource(file, sourceOptions); + else if (format === "zip") source = zipSource(file, sourceOptions); + else source = emlSource(file, sourceOptions); + + try { + for await (const item of source) { + if (signal.aborted) throw new ImportCancelled(); + if (item.kind === "skipped") { + // Items an earlier run handled are already counted in its imported total. + if (item.reason !== "resumed") skip(item.reason); + handled({ fileIndex: f, index: item.index }); + continue; + } + if (item.embeddedSkipped) skip("embedded", item.embeddedSkipped); + await startUpload(item, f); + report(); + } + } finally { + await source.return?.(undefined); + } + fileFraction[f] = 1; + setFraction(); + } + while (running.size > 0) await Promise.race(running); + if (signal.aborted) throw new ImportCancelled(); + await flush(); + await importing; + progress.phase = "done"; + progress.fraction = 1; + progress.pausedUntil = undefined; + await Promise.all(keys.map((k) => deleteCheckpoint(k))); + } catch (error) { + // Let the uploads in flight end (they were aborted) and keep what got imported. + await Promise.allSettled([...running]); + await flush().catch(() => {}); + await importing.catch(() => {}); + await saveCheckpoints(true).catch(() => {}); + progress.pausedUntil = undefined; + if (isAbort(error) || signal.aborted) { + progress.phase = "cancelled"; + } else { + progress.phase = "failed"; + progress.error = error instanceof Error ? error.message : String(error); + } + } finally { + if (flushTimer) clearTimeout(flushTimer); + } + report(true); + return progress; +} diff --git a/lib/mail-import/mbox-status.ts b/lib/mail-import/mbox-status.ts new file mode 100644 index 000000000..16cd892f1 --- /dev/null +++ b/lib/mail-import/mbox-status.ts @@ -0,0 +1,88 @@ +/** + * Message state from the headers mail programs write into mbox files. + * + * - `Status:` R = read (O = not recent, ignored). + * - `X-Status:` A = answered, F = flagged, T = draft. + * - `X-Mozilla-Status:` Thunderbird's flags in four hex digits: 0x1 read, + * 0x2 replied, 0x4 flagged, 0x8 expunged (deleted, the folder wasn't + * compacted yet: such a message is skipped, or the import brings back + * deleted mail), 0x1000 forwarded. + * - `X-Keywords:` and `X-Mozilla-Keys:` keywords, separated by spaces or commas. + * - `X-Gmail-Labels:` (Google Takeout) "Unread" and "Starred". + * + * A message with none of these is imported as read, as Bulwark always did. + */ + +export interface MboxState { + keywords: Record; + /** Thunderbird marked it deleted (expunged): don't import it. */ + expunged: boolean; +} + +/** Unfolded header fields by lowercased name (first occurrence wins). */ +export function headerFields(headerBlock: string): Map { + const fields = new Map(); + for (const line of headerBlock.replace(/\r?\n[ \t]+/g, " ").split(/\r?\n/)) { + const colon = line.indexOf(":"); + if (colon <= 0) continue; + const name = line.slice(0, colon).trim().toLowerCase(); + if (!fields.has(name)) fields.set(name, line.slice(colon + 1).trim()); + } + return fields; +} + +/** RFC 8621 keyword characters: printable ASCII except ( ) { ] % * " \ and space. */ +const KEYWORD = /^[\x21-\x7e]{1,255}$/; +const BAD_KEYWORD_CHARS = /[(){\]%*"\\]/; +/** Keywords the server sets itself or that mean something else in an mbox header. */ +const IGNORED_KEYWORDS = new Set(["$recent", "\\recent", "$hasattachment", "$hasnoattachment"]); + +function addKeywords(into: Record, value: string | undefined): void { + if (!value) return; + for (const raw of value.split(/[\s,]+/)) { + let keyword = raw.trim(); + if (!keyword) continue; + // IMAP system flags written as keywords. + const system: Record = { "\\seen": "$seen", "\\flagged": "$flagged", "\\answered": "$answered", "\\draft": "$draft" }; + keyword = system[keyword.toLowerCase()] ?? keyword; + if (!KEYWORD.test(keyword) || BAD_KEYWORD_CHARS.test(keyword)) continue; + if (IGNORED_KEYWORDS.has(keyword.toLowerCase())) continue; + into[keyword.toLowerCase()] = true; + } +} + +export function mboxState(headerBlock: string): MboxState { + const fields = headerFields(headerBlock); + const status = fields.get("status"); + const xStatus = fields.get("x-status"); + const mozilla = fields.get("x-mozilla-status"); + const gmail = fields.get("x-gmail-labels"); + const keywords: Record = {}; + let expunged = false; + + const known = status !== undefined || xStatus !== undefined || mozilla !== undefined || gmail !== undefined; + if (!known) keywords.$seen = true; + + if (status && /R/i.test(status)) keywords.$seen = true; + if (xStatus) { + if (/A/i.test(xStatus)) keywords.$answered = true; + if (/F/i.test(xStatus)) keywords.$flagged = true; + if (/T/i.test(xStatus)) keywords.$draft = true; + } + if (mozilla && /^[0-9a-f]{1,8}$/i.test(mozilla)) { + const flags = parseInt(mozilla, 16); + if (flags & 0x1) keywords.$seen = true; + if (flags & 0x2) keywords.$answered = true; + if (flags & 0x4) keywords.$flagged = true; + if (flags & 0x8) expunged = true; + if (flags & 0x1000) keywords.$forwarded = true; + } + if (gmail !== undefined) { + const labels = gmail.split(",").map((l) => l.trim().toLowerCase()); + if (!labels.includes("unread")) keywords.$seen = true; + if (labels.includes("starred")) keywords.$flagged = true; + } + addKeywords(keywords, fields.get("x-keywords")); + addKeywords(keywords, fields.get("x-mozilla-keys")); + return { keywords, expunged }; +} diff --git a/lib/mail-import/mbox.ts b/lib/mail-import/mbox.ts new file mode 100644 index 000000000..31ff91e76 --- /dev/null +++ b/lib/mail-import/mbox.ts @@ -0,0 +1,371 @@ +/** + * A streaming mbox splitter that works on bytes, never on decoded text. + * + * Messages are separated by a line starting with `From ` that follows an + * empty line (or opens the file). That one rule reads mboxo, mboxrd, mboxcl + * and mboxcl2 files: + * + * - `>From ` lines lose one `>` (the mboxrd rule; on an mboxo file this also + * unquotes a line that was `>From ` originally, which is rare and harmless). + * - A `Content-Length` header (mboxcl, mboxcl2) is used only when the message + * it measures ends exactly where another message starts. Until then, `From ` + * lines inside the body are held as possible separators; if the length turns + * out wrong, they split the message after all (Dovecot does the same). + * - CRLF files work: an empty line is LF or CRLF, line endings are kept. + * + * Bytes are passed through untouched (8-bit bodies, signatures): only the + * envelope line and one `>` of quoted lines are removed, and the empty line + * in front of each separator, which belongs to the mbox format. + */ + +const LF = 0x0a; +const CR = 0x0d; +const GT = 0x3e; +const FROM = [0x46, 0x72, 0x6f, 0x6d, 0x20]; // "From " + +export interface MboxMessage { + /** The message bytes, in order. Pass them to `new Blob(parts)`. */ + parts: Uint8Array[]; + /** Total bytes in `parts`. */ + size: number; + /** The envelope line without its line ending, decoded as Latin-1. */ + fromLine: string; + /** Byte offset of the envelope line in the file. */ + offset: number; + /** Larger than `maxMessageBytes`: `parts` is empty, the message was skipped. */ + tooLarge: boolean; +} + +export interface MboxSplitterOptions { + /** Stop keeping the bytes of a message past this size (it is reported `tooLarge`). */ + maxMessageBytes?: number; +} + +interface Segment { + bytes: Uint8Array; + /** This segment is the empty line in front of a held separator. */ + heldEmpty?: boolean; + /** This segment is a held separator line (its text in `fromLine`). */ + heldFrom?: { line: string; offset: number }; +} + +function startsWithFrom(line: Uint8Array, at: number): boolean { + if (line.length - at < FROM.length) return false; + for (let i = 0; i < FROM.length; i++) if (line[at + i] !== FROM[i]) return false; + return true; +} + +function isEmptyLine(line: Uint8Array): boolean { + return line.length === 1 ? line[0] === LF : line.length === 2 && line[0] === CR && line[1] === LF; +} + +function latin1(bytes: Uint8Array): string { + let out = ""; + for (let i = 0; i < bytes.length; i += 8192) { + out += String.fromCharCode(...bytes.subarray(i, Math.min(bytes.length, i + 8192))); + } + return out; +} + +function stripEol(line: Uint8Array): Uint8Array { + let end = line.length; + if (end > 0 && line[end - 1] === LF) end--; + if (end > 0 && line[end - 1] === CR) end--; + return line.subarray(0, end); +} + +/** + * Whether a `From ` line looks like an envelope line ("From sender date"), + * which carries a time. Guards against a body line "From now on..." after an + * empty line in a file whose writer didn't quote it. + */ +export function isEnvelopeLine(text: string): boolean { + return /^From \S*.*\d{1,2}:\d{2}/.test(text); +} + +const CONTENT_LENGTH = /^content-length:[ \t]*(\d+)[ \t]*\r?$/im; + +export class MboxSplitter { + private readonly maxBytes: number; + /** Bytes of the line not finished yet (the chunk ended inside it). */ + private carry: Uint8Array[] = []; + private carryOffset = 0; + /** Offset of the next byte pushed. */ + private offset = 0; + + private current: { + fromLine: string; + offset: number; + segments: Segment[]; + size: number; + tooLarge: boolean; + /** Header block finished (first empty line seen). */ + inBody: boolean; + headerBytes: Uint8Array[]; + /** End offset Content-Length says the body has, while it is unconfirmed. */ + expectedEnd?: number; + holding: boolean; + } | null = null; + /** An empty line held back: it is dropped if a separator follows it. */ + private pendingEmpty: { bytes: Uint8Array; offset: number } | null = null; + private out: MboxMessage[] = []; + + constructor(options: MboxSplitterOptions = {}) { + this.maxBytes = options.maxMessageBytes ?? Number.POSITIVE_INFINITY; + } + + /** Feed the next chunk of the file. Returns the messages it completed. */ + push(chunk: Uint8Array): MboxMessage[] { + let start = 0; + for (let i = 0; i < chunk.length; i++) { + if (chunk[i] !== LF) continue; + const piece = chunk.subarray(start, i + 1); + if (this.carry.length > 0) { + this.carry.push(piece); + const line = concat(this.carry); + this.carry = []; + this.line(line, this.carryOffset); + } else { + this.line(piece, this.offset + start); + } + start = i + 1; + } + if (start < chunk.length) { + if (this.carry.length === 0) this.carryOffset = this.offset + start; + this.carry.push(chunk.subarray(start)); + } + this.offset += chunk.length; + return this.take(); + } + + /** The file ended. Returns the last messages. */ + end(): MboxMessage[] { + if (this.carry.length > 0) { + const line = concat(this.carry); + this.carry = []; + this.line(line, this.carryOffset); + } + if (this.current?.expectedEnd !== undefined) { + this.settleLength(this.offset === this.current.expectedEnd || this.pendingEmptyEndsAt(this.current.expectedEnd)); + } + // The empty line closing the last message belongs to the format. + this.pendingEmpty = null; + this.finish(); + return this.take(); + } + + private take(): MboxMessage[] { + const out = this.out; + this.out = []; + return out; + } + + private pendingEmptyEndsAt(offset: number): boolean { + return !!this.pendingEmpty && this.pendingEmpty.offset === offset; + } + + private line(line: Uint8Array, offset: number): void { + const cur = this.current; + // Content-Length said the body ends here: is there a separator? + if (cur?.expectedEnd !== undefined && offset >= cur.expectedEnd) { + const atEnd = offset === cur.expectedEnd; + const afterEmpty = this.pendingEmptyEndsAt(cur.expectedEnd); + if (atEnd && isEmptyLine(line)) { + // An empty line right at the end: decide on the line after it. + } else { + const confirmed = (atEnd || afterEmpty) && startsWithFrom(line, 0) && isEnvelopeLine(latin1(stripEol(line))); + this.settleLength(confirmed); + } + } + + const empty = isEmptyLine(line); + if (startsWithFrom(line, 0) && (this.current === null || this.pendingEmpty !== null)) { + const text = latin1(stripEol(line)); + if (this.current === null || isEnvelopeLine(text)) { + if (this.current?.holding) { + // Inside the length Content-Length claims: keep it as a possible separator. + this.flushPendingEmpty(true); + this.addSegment({ bytes: line, heldFrom: { line: text, offset } }); + return; + } + this.pendingEmpty = null; + this.finish(); + this.start(text, offset); + return; + } + } + + if (this.current === null) return; // Bytes before the first envelope line. + + if (empty) { + this.flushPendingEmpty(false); + this.pendingEmpty = { bytes: line, offset }; + if (!this.current.inBody) this.endHeaders(offset + line.length); + return; + } + this.flushPendingEmpty(false); + // mboxrd: one ">" less on ">From ", ">>From ", ... + let at = 0; + while (at < line.length && line[at] === GT) at++; + const bytes = at > 0 && startsWithFrom(line, at) ? line.subarray(1) : line; + if (!this.current.inBody) this.current.headerBytes.push(bytes); + this.addSegment({ bytes }); + } + + private flushPendingEmpty(held: boolean): void { + if (!this.pendingEmpty) return; + this.addSegment({ bytes: this.pendingEmpty.bytes, heldEmpty: held || undefined }); + this.pendingEmpty = null; + } + + private addSegment(segment: Segment): void { + const cur = this.current!; + cur.size += segment.bytes.length; + if (cur.size > this.maxBytes) { + cur.tooLarge = true; + cur.segments = []; + return; + } + if (cur.tooLarge) return; + // Plain lines next to each other in one chunk become one segment. + const last = cur.segments[cur.segments.length - 1]; + const b = segment.bytes; + if (last && !last.heldEmpty && !last.heldFrom && !segment.heldEmpty && !segment.heldFrom + && last.bytes.buffer === b.buffer && last.bytes.byteOffset + last.bytes.length === b.byteOffset) { + last.bytes = new Uint8Array(b.buffer, last.bytes.byteOffset, last.bytes.length + b.length); + return; + } + cur.segments.push({ ...segment }); + } + + private start(fromLine: string, offset: number): void { + this.current = { fromLine, offset, segments: [], size: 0, tooLarge: false, inBody: false, headerBytes: [], holding: false }; + } + + private endHeaders(bodyStart: number): void { + const cur = this.current!; + cur.inBody = true; + const match = CONTENT_LENGTH.exec(latin1(concat(cur.headerBytes))); + cur.headerBytes = []; + if (match) { + cur.expectedEnd = bodyStart + Number(match[1]); + cur.holding = true; + } + } + + /** + * Content-Length was checked: when it was right, the held separators were + * body lines; when it was wrong, they split the message. + */ + private settleLength(confirmed: boolean): void { + const cur = this.current!; + cur.expectedEnd = undefined; + cur.holding = false; + if (confirmed || cur.tooLarge || !cur.segments.some((s) => s.heldFrom)) { + for (const s of cur.segments) { s.heldEmpty = undefined; s.heldFrom = undefined; } + return; + } + // Split at each held separator. The last part becomes the current message. + const parts: { fromLine: string; offset: number; segments: Segment[] }[] = [ + { fromLine: cur.fromLine, offset: cur.offset, segments: [] }, + ]; + for (const s of cur.segments) { + if (s.heldEmpty) continue; + if (s.heldFrom) { + parts.push({ fromLine: s.heldFrom.line, offset: s.heldFrom.offset, segments: [] }); + continue; + } + parts[parts.length - 1].segments.push(s); + } + for (const part of parts.slice(0, -1)) { + this.out.push(toMessage(part.fromLine, part.offset, part.segments, false)); + } + const last = parts[parts.length - 1]; + this.current = { + fromLine: last.fromLine, + offset: last.offset, + segments: last.segments, + size: last.segments.reduce((n, s) => n + s.bytes.length, 0), + tooLarge: false, + inBody: true, + headerBytes: [], + holding: false, + }; + } + + private finish(): void { + const cur = this.current; + if (!cur) return; + this.current = null; + this.out.push(toMessage(cur.fromLine, cur.offset, cur.segments, cur.tooLarge, cur.size)); + } +} + +function toMessage(fromLine: string, offset: number, segments: Segment[], tooLarge: boolean, size?: number): MboxMessage { + const parts = tooLarge ? [] : mergeAdjacent(segments.map((s) => s.bytes)); + return { + parts, + size: size ?? parts.reduce((n, p) => n + p.length, 0), + fromLine, + offset, + tooLarge, + }; +} + +/** Joins subarrays that sit next to each other in the same buffer, so a Blob gets few parts. */ +function mergeAdjacent(parts: Uint8Array[]): Uint8Array[] { + const out: Uint8Array[] = []; + for (const p of parts) { + const last = out[out.length - 1]; + if (last && last.buffer === p.buffer && last.byteOffset + last.length === p.byteOffset) { + out[out.length - 1] = new Uint8Array(p.buffer, last.byteOffset, last.length + p.length); + } else { + out.push(p); + } + } + return out; +} + +function concat(parts: Uint8Array[]): Uint8Array { + if (parts.length === 1) return parts[0]; + let n = 0; + for (const p of parts) n += p.length; + const out = new Uint8Array(n); + let at = 0; + for (const p of parts) { out.set(p, at); at += p.length; } + return out; +} + +/** The header block of a message (up to the first empty line, at most 64 KB), as Latin-1 text. */ +export function readHeaderBlock(parts: Uint8Array[], limit = 64 * 1024): string { + const bytes: Uint8Array[] = []; + let n = 0; + for (const p of parts) { + bytes.push(p.subarray(0, Math.max(0, limit - n))); + n += p.length; + if (n >= limit) break; + } + const text = latin1(concat(bytes)); + const end = text.search(/\r?\n\r?\n/); + return end === -1 ? text : text.slice(0, end); +} + +const MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]; + +/** + * The date of an envelope line, "From sender Tue May 5 10:11:12 2020" (asctime, + * read as UTC, optionally with a zone before the year), as a JMAP UTCDate. + */ +export function envelopeDate(fromLine: string): string | undefined { + const m = /(?:^|\s)(?:mon|tue|wed|thu|fri|sat|sun)\w*\s+([a-z]{3})\w*\s+(\d{1,2})\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\s+(?:([+-]\d{4})\s+)?(\d{4})/i.exec(fromLine); + if (!m) return undefined; + const month = MONTHS.indexOf(m[1].toLowerCase()); + if (month < 0) return undefined; + let time = Date.UTC(Number(m[7]), month, Number(m[2]), Number(m[3]), Number(m[4]), Number(m[5] ?? 0)); + if (m[6]) { + const sign = m[6][0] === "-" ? -1 : 1; + time -= sign * (Number(m[6].slice(1, 3)) * 60 + Number(m[6].slice(3))) * 60_000; + } + if (Number.isNaN(time)) return undefined; + return new Date(time).toISOString().replace(/\.\d{3}Z$/, "Z"); +} diff --git a/lib/mail-import/sources.ts b/lib/mail-import/sources.ts new file mode 100644 index 000000000..80c787c06 --- /dev/null +++ b/lib/mail-import/sources.ts @@ -0,0 +1,273 @@ +import { dateFromHeaderBlock } from "@/lib/eml-import"; +import { MboxSplitter, envelopeDate, readHeaderBlock, type MboxMessage } from "./mbox"; +import { mboxState } from "./mbox-status"; +import { listZipEntries, readZipEntry, streamZipEntry } from "./zip-reader"; + +/** + * What the import reads from each file: messages with their folder, state and + * date, in a stable order (the index the checkpoint counts), read piece by + * piece so a large file never has to fit in memory. + */ + +export type ImportFormat = "eml" | "zip" | "mbox" | "pst"; + +export type SkipReason = + /** Larger than the server takes (maxSizeUpload). */ + | "too_large" + /** Deleted in Thunderbird, still in the file until the folder is compacted. */ + | "deleted" + /** PST contacts and distribution lists. */ + | "contact" + /** PST appointments, meeting requests and tasks. */ + | "calendar" + /** Other PST items (notes, journal entries) and files in a ZIP that aren't mail. */ + | "other" + /** Messages attached to a PST message, which pst-extractor can't read. */ + | "embedded" + /** Imported by an earlier run of the same import. */ + | "resumed"; + +export interface ImportMessage { + kind: "message"; + index: number; + /** Folder path inside the file; [] is the top. */ + folder: string[]; + data: Blob; + size: number; + keywords: Record; + receivedAt?: string; + /** Names the message in the summary when it fails: its subject or entry name. */ + label: string; + /** Messages attached to this one that couldn't be read. */ + embeddedSkipped?: number; +} + +export interface ImportSkip { + kind: "skipped"; + index: number; + reason: SkipReason; + label?: string; +} + +export type ImportItem = ImportMessage | ImportSkip; + +export interface SourceOptions { + /** Messages larger than this are skipped as too_large without reading them in. */ + maxMessageBytes: number; + /** Items before this index were handled by an earlier run: report them as `resumed`. */ + skipBefore: number; + /** Called as the file is read: the part done, 0 to 1. */ + onProgress?: (fraction: number) => void; + /** Called once the number of messages in the file is known. */ + onTotal?: (count: number) => void; + signal?: AbortSignal; +} + +export type ItemSource = AsyncGenerator; + +const READ_CHUNK = 1 << 20; +const MIME = "message/rfc822"; + +const PST_MAGIC = [0x21, 0x42, 0x44, 0x4e]; // "!BDN" +const ZIP_MAGIC = [0x50, 0x4b, 0x03, 0x04]; +const ZIP_EMPTY = [0x50, 0x4b, 0x05, 0x06]; + +function startsWith(head: Uint8Array, magic: number[]): boolean { + return magic.every((b, i) => head[i] === b); +} + +/** The format of a file, from its first bytes; the name only breaks ties. */ +export async function sniffFormat(file: Blob & { name?: string }): Promise { + const head = new Uint8Array(await file.slice(0, 512).arrayBuffer()); + if (startsWith(head, PST_MAGIC)) return "pst"; + if (startsWith(head, ZIP_MAGIC) || startsWith(head, ZIP_EMPTY)) return "zip"; + // mbox opens with an envelope line, maybe after a byte order mark or empty lines. + let at = head[0] === 0xef && head[1] === 0xbb && head[2] === 0xbf ? 3 : 0; + while (head[at] === 0x0d || head[at] === 0x0a) at++; + const first = String.fromCharCode(...head.subarray(at, at + 5)); + if (first === "From ") return "mbox"; + if (/\.(mbox|mbx)$/i.test(file.name ?? "")) return "mbox"; + return "eml"; +} + +function throwIfAborted(signal?: AbortSignal): void { + if (signal?.aborted) throw new DOMException("Import cancelled", "AbortError"); +} + +function emlLabel(name: string): string { + return name.split(/[\\/]/).pop() || name; +} + +const encodedWord = /=\?([^?]+)\?([bq])\?([^?]*)\?=/gi; + +/** The Subject of a header block, for the summary; encoded words decoded when possible. */ +export function subjectOf(headerBlock: string): string { + const m = /^subject:[ \t]*(.*(?:\r?\n[ \t].*)*)/im.exec(headerBlock); + if (!m) return ""; + const raw = m[1].replace(/\r?\n[ \t]+/g, " ").trim(); + return raw.replace(/\?=\s+=\?/g, "?==?").replace(encodedWord, (all, charset: string, enc: string, text: string) => { + try { + const bytes = enc.toLowerCase() === "b" + ? Uint8Array.from(atob(text), (c) => c.charCodeAt(0)) + : Uint8Array.from(text.replace(/_/g, " ").replace(/=([0-9a-f]{2})/gi, (_, h: string) => String.fromCharCode(parseInt(h, 16))), (c) => c.charCodeAt(0)); + return new TextDecoder(charset).decode(bytes); + } catch { + return all; + } + }).slice(0, 200); +} + +export async function* emlSource(file: File, options: SourceOptions): ItemSource { + options.onTotal?.(1); + if (options.skipBefore > 0) { + yield { kind: "skipped", index: 0, reason: "resumed" }; + options.onProgress?.(1); + return; + } + if (file.size > options.maxMessageBytes) { + yield { kind: "skipped", index: 0, reason: "too_large", label: file.name }; + options.onProgress?.(1); + return; + } + const head = await file.slice(0, 64 * 1024).text(); + yield { + kind: "message", + index: 0, + folder: [], + data: file.slice(0, file.size, MIME), + size: file.size, + keywords: { $seen: true }, + receivedAt: dateFromHeaderBlock(head), + label: emlLabel(file.name), + }; + options.onProgress?.(1); +} + +function mboxItem(message: MboxMessage, index: number, folder: string[]): ImportItem { + const label = message.tooLarge ? message.fromLine : ""; + if (message.tooLarge) return { kind: "skipped", index, reason: "too_large", label }; + const headers = readHeaderBlock(message.parts); + const state = mboxState(headers); + const subject = subjectOf(headers); + if (state.expunged) return { kind: "skipped", index, reason: "deleted", label: subject }; + return { + kind: "message", + index, + folder, + data: new Blob(message.parts as BlobPart[], { type: MIME }), + size: message.size, + keywords: state.keywords, + receivedAt: dateFromHeaderBlock(headers) ?? envelopeDate(message.fromLine), + label: subject, + }; +} + +/** Splits chunks into messages; `startIndex` continues the numbering inside a ZIP. */ +async function* mboxFromChunks( + chunks: AsyncIterable, + folder: string[], + options: SourceOptions, + startIndex: number, + onIndex: (next: number) => void, +): ItemSource { + const splitter = new MboxSplitter({ maxMessageBytes: options.maxMessageBytes }); + let index = startIndex; + const emit = function* (messages: MboxMessage[]): Generator { + for (const m of messages) { + const i = index++; + yield i < options.skipBefore ? { kind: "skipped", index: i, reason: "resumed" } : mboxItem(m, i, folder); + } + }; + for await (const chunk of chunks) { + throwIfAborted(options.signal); + yield* emit(splitter.push(chunk)); + } + yield* emit(splitter.end()); + onIndex(index); +} + +async function* fileChunks(file: Blob, onProgress?: (fraction: number) => void): AsyncGenerator { + for (let at = 0; at < file.size; at += READ_CHUNK) { + const end = Math.min(file.size, at + READ_CHUNK); + yield new Uint8Array(await file.slice(at, end).arrayBuffer()); + onProgress?.(end / Math.max(1, file.size)); + } +} + +export async function* mboxSource(file: File, options: SourceOptions): ItemSource { + yield* mboxFromChunks(fileChunks(file, options.onProgress), [], options, 0, () => {}); + options.onProgress?.(1); +} + +/** `Archive/2020.mbox` → ["Archive", "2020"]; `Inbox/a.eml` → ["Inbox"]. */ +function zipFolder(name: string, isMbox: boolean): string[] { + const parts = name.split("/").filter((p) => p && p !== "." && p !== ".."); + if (isMbox) { + const last = parts.pop() ?? ""; + const folder = last.replace(/\.(mbox|mbx)$/i, ""); + return folder ? [...parts, folder] : parts; + } + parts.pop(); + return parts; +} + +export async function* zipSource(file: File, options: SourceOptions): ItemSource { + const entries = (await listZipEntries(file)).filter((e) => !e.directory && !/(^|\/)(__MACOSX|\.DS_Store)/.test(e.name)); + const isMbox = (name: string) => /\.(mbox|mbx)$/i.test(name); + const isEml = (name: string) => /\.eml$/i.test(name); + if (!entries.some((e) => isMbox(e.name))) options.onTotal?.(entries.filter((e) => isEml(e.name)).length); + const totalBytes = entries.reduce((n, e) => n + e.compressedSize, 0) || 1; + let doneBytes = 0; + let index = 0; + for (const entry of entries) { + throwIfAborted(options.signal); + if (isMbox(entry.name)) { + const before = doneBytes; + const chunks = (async function* () { + let seen = 0; + for await (const chunk of streamZipEntry(file, entry)) { + seen += chunk.length; + options.onProgress?.((before + entry.compressedSize * Math.min(1, seen / Math.max(1, entry.size))) / totalBytes); + yield chunk; + } + })(); + yield* mboxFromChunks(chunks, zipFolder(entry.name, true), options, index, (next) => { index = next; }); + } else if (isEml(entry.name)) { + const i = index++; + if (i < options.skipBefore) { + yield { kind: "skipped", index: i, reason: "resumed" }; + } else if (entry.size > options.maxMessageBytes) { + yield { kind: "skipped", index: i, reason: "too_large", label: emlLabel(entry.name) }; + } else { + const bytes = await readZipEntry(file, entry); + const head = new TextDecoder("latin1").decode(bytes.subarray(0, 64 * 1024)); + yield { + kind: "message", + index: i, + folder: zipFolder(entry.name, false), + data: new Blob([bytes as BlobPart], { type: MIME }), + size: bytes.length, + keywords: { $seen: true }, + receivedAt: dateFromHeaderBlock(head), + label: emlLabel(entry.name), + }; + } + } else { + const i = index++; + yield i < options.skipBefore ? { kind: "skipped", index: i, reason: "resumed" } : { kind: "skipped", index: i, reason: "other", label: emlLabel(entry.name) }; + } + doneBytes += entry.compressedSize; + options.onProgress?.(doneBytes / totalBytes); + } +} + +/** Whether the file has folders of its own, so the dialog offers to recreate them. */ +export async function hasFolders(file: File, format: ImportFormat): Promise { + if (format === "pst") return true; + if (format !== "zip") return false; + try { + return (await listZipEntries(file)).some((e) => e.name.includes("/") || /\.(mbox|mbx)$/i.test(e.name)); + } catch { + return false; + } +} diff --git a/lib/mail-import/zip-reader.ts b/lib/mail-import/zip-reader.ts new file mode 100644 index 000000000..dec53aff1 --- /dev/null +++ b/lib/mail-import/zip-reader.ts @@ -0,0 +1,145 @@ +import { Inflate, inflateSync } from "fflate"; + +/** + * Reads a ZIP through its central directory with `Blob.slice`, so a large + * archive is never loaded whole: entries are read one at a time, and a large + * entry (an mbox inside the archive) is inflated in chunks. Handles Zip64, + * entries with data descriptors, stored and deflated entries. + */ + +export interface ZipEntry { + /** Path inside the archive, `/` separated. */ + name: string; + directory: boolean; + method: number; + compressedSize: number; + size: number; + localOffset: number; +} + +const EOCD = 0x06054b50; +const ZIP64_LOCATOR = 0x07064b50; +const ZIP64_EOCD = 0x06064b50; +const CENTRAL = 0x02014b50; +const LOCAL = 0x04034b50; +const MAX32 = 0xffffffff; + +export class ZipFormatError extends Error { + constructor(message: string) { + super(message); + this.name = "ZipFormatError"; + } +} + +async function bytes(file: Blob, start: number, end: number): Promise { + return new Uint8Array(await file.slice(start, end).arrayBuffer()); +} + +function u64(view: DataView, at: number): number { + return view.getUint32(at, true) + view.getUint32(at + 4, true) * 0x1_0000_0000; +} + +const utf8 = new TextDecoder("utf-8"); +// IBM 437 isn't a WHATWG label; old archivers' non-UTF-8 names are rare in mail exports. +const legacy = new TextDecoder("windows-1252"); + +export async function listZipEntries(file: Blob): Promise { + const tailStart = Math.max(0, file.size - (0xffff + 22)); + const tail = await bytes(file, tailStart, file.size); + const tv = new DataView(tail.buffer, tail.byteOffset, tail.byteLength); + let eocd = -1; + for (let i = tail.length - 22; i >= 0; i--) { + if (tv.getUint32(i, true) === EOCD) { eocd = i; break; } + } + if (eocd < 0) throw new ZipFormatError("No end of central directory"); + let count = tv.getUint16(eocd + 10, true); + let cdSize = tv.getUint32(eocd + 12, true); + let cdOffset = tv.getUint32(eocd + 16, true); + if (count === 0xffff || cdSize === MAX32 || cdOffset === MAX32) { + const locatorAt = eocd - 20; + if (locatorAt >= 0 && tv.getUint32(locatorAt, true) === ZIP64_LOCATOR) { + const zip64At = u64(tv, locatorAt + 8); + const rec = await bytes(file, zip64At, zip64At + 56); + const rv = new DataView(rec.buffer, rec.byteOffset, rec.byteLength); + if (rv.getUint32(0, true) !== ZIP64_EOCD) throw new ZipFormatError("Bad Zip64 end record"); + count = u64(rv, 32); + cdSize = u64(rv, 40); + cdOffset = u64(rv, 48); + } + } + const cd = await bytes(file, cdOffset, cdOffset + cdSize); + const v = new DataView(cd.buffer, cd.byteOffset, cd.byteLength); + const entries: ZipEntry[] = []; + let at = 0; + for (let n = 0; n < count && at + 46 <= cd.length; n++) { + if (v.getUint32(at, true) !== CENTRAL) throw new ZipFormatError("Bad central directory entry"); + const flags = v.getUint16(at + 8, true); + const method = v.getUint16(at + 10, true); + let compressedSize = v.getUint32(at + 20, true); + let size = v.getUint32(at + 24, true); + const nameLength = v.getUint16(at + 28, true); + const extraLength = v.getUint16(at + 30, true); + const commentLength = v.getUint16(at + 32, true); + let localOffset = v.getUint32(at + 42, true); + const nameBytes = cd.subarray(at + 46, at + 46 + nameLength); + const name = (flags & 0x0800 ? utf8 : legacy).decode(nameBytes).replace(/\\/g, "/"); + // Zip64 extra field: the values that were 0xFFFFFFFF, in this order. + let extra = at + 46 + nameLength; + const extraEnd = extra + extraLength; + while (extra + 4 <= extraEnd) { + const id = v.getUint16(extra, true); + const len = v.getUint16(extra + 2, true); + if (id === 0x0001) { + let p = extra + 4; + if (size === MAX32) { size = u64(v, p); p += 8; } + if (compressedSize === MAX32) { compressedSize = u64(v, p); p += 8; } + if (localOffset === MAX32) { localOffset = u64(v, p); p += 8; } + } + extra += 4 + len; + } + entries.push({ name, directory: name.endsWith("/"), method, compressedSize, size, localOffset }); + at += 46 + nameLength + extraLength + commentLength; + } + return entries; +} + +async function dataStart(file: Blob, entry: ZipEntry): Promise { + const header = await bytes(file, entry.localOffset, entry.localOffset + 30); + const v = new DataView(header.buffer, header.byteOffset, header.byteLength); + if (header.length < 30 || v.getUint32(0, true) !== LOCAL) throw new ZipFormatError("Bad local header"); + return entry.localOffset + 30 + v.getUint16(26, true) + v.getUint16(28, true); +} + +function assertMethod(entry: ZipEntry): void { + if (entry.method !== 0 && entry.method !== 8) throw new ZipFormatError(`Unsupported compression ${entry.method}`); +} + +/** The whole entry, for small entries (a message). */ +export async function readZipEntry(file: Blob, entry: ZipEntry): Promise { + assertMethod(entry); + const start = await dataStart(file, entry); + const raw = await bytes(file, start, start + entry.compressedSize); + return entry.method === 0 ? raw : inflateSync(raw, { out: new Uint8Array(entry.size) }); +} + +/** The entry in chunks of about `chunkBytes`, inflated as they come (an mbox inside the archive). */ +export async function* streamZipEntry(file: Blob, entry: ZipEntry, chunkBytes = 1 << 20): AsyncGenerator { + assertMethod(entry); + const start = await dataStart(file, entry); + const end = start + entry.compressedSize; + if (entry.method === 0) { + for (let at = start; at < end; at += chunkBytes) yield await bytes(file, at, Math.min(end, at + chunkBytes)); + return; + } + let out: Uint8Array[] = []; + const inflate = new Inflate((chunk) => { out.push(chunk); }); + for (let at = start; at < end; at += chunkBytes) { + const next = Math.min(end, at + chunkBytes); + inflate.push(await bytes(file, at, next), next >= end); + const ready = out; + out = []; + for (const chunk of ready) yield chunk; + } + if (start === end) inflate.push(new Uint8Array(0), true); + for (const chunk of out) yield chunk; +} diff --git a/lib/sign-out-cleanup.ts b/lib/sign-out-cleanup.ts index d89b38355..65eb15da0 100644 --- a/lib/sign-out-cleanup.ts +++ b/lib/sign-out-cleanup.ts @@ -1,4 +1,5 @@ import { fileStorage } from '@/lib/plugin-storage'; +import { clearAllCheckpoints } from '@/lib/mail-import/checkpoints'; /** * What a full sign-out removes from this browser beyond the stores that @@ -68,6 +69,8 @@ export function purgeSignedOutData(): void { } // Attachment bytes a plugin staged for an upload that never started. void fileStorage.clearFiles().catch(() => {}); + // Where mail imports stopped: file names and folders of the accounts. + void clearAllCheckpoints().catch(() => {}); for (const cleanup of signedOutCleanups) { try { cleanup(); diff --git a/locales/ar/common.json b/locales/ar/common.json index 2116d773b..07c78763d 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "البحث غير متاح في العرض الموحّد" }, + "mail_import": { + "menu_import": "استيراد البريد…", + "settings_label": "استيراد البريد", + "settings_description": "استورد البريد من ملفات ‎.eml أو ZIP أو mbox أو ملفات Outlook ‏‎.pst.", + "settings_button": "استيراد…", + "title": "استيراد البريد", + "description": "اختر ملفات ‎.eml أو ‎.zip أو ‎.mbox أو ملفات Outlook ‏‎.pst.", + "choose_files": "اختيار ملفات…", + "chosen_files": "الملفات المختارة", + "remove_file": "إزالة {name}", + "format_eml": "رسالة", + "format_zip": "أرشيف ZIP", + "format_mbox": "ملف mbox", + "format_pst": "ملف بيانات Outlook", + "target_label": "الاستيراد إلى", + "target_top": "المستوى الأعلى", + "keep_folders": "إعادة إنشاء مجلدات الملف", + "keep_folders_hint": "يُعاد استخدام المجلدات التي تحمل الاسم نفسه. تذهب علبة الوارد والعناصر المرسلة والعناصر المحذوفة في المستوى الأعلى إلى مجلداتك.", + "resume": "المتابعة من حيث توقف آخر استيراد لهذه الملفات (الرسائل المنجزة: {count})", + "quota_note": "كثير من الخوادم لا تقبل من المستخدم الواحد إلا نحو 1000 رسالة أو 50 ميغابايت في الساعة. يتوقف الاستيراد الأكبر مؤقتًا ثم يتابع وحده.", + "start": "استيراد", + "cancel": "إلغاء", + "running_title": "جارٍ استيراد البريد", + "progress_label": "تقدّم الاستيراد", + "status_imported": "الرسائل المستوردة: {count}", + "reading_file": "جارٍ قراءة {name}", + "paused": "متوقف مؤقتًا حتى {time}. يقبل الخادم {files} عملية رفع أو {size} في الساعة على الأكثر. سيتابع الاستيراد وحده.", + "paused_generic": "متوقف مؤقتًا حتى {time}. بلغ الخادم حد الرفع. سيتابع الاستيراد وحده.", + "background": "المتابعة في الخلفية", + "cancel_import": "إلغاء الاستيراد", + "chip": "جارٍ استيراد البريد: {percent}٪", + "chip_paused": "الاستيراد متوقف مؤقتًا حتى {time}", + "done_title": "انتهى الاستيراد", + "cancelled_title": "أُلغي الاستيراد", + "failed_title": "توقف الاستيراد", + "summary_imported": "الرسائل المستوردة: {count}", + "summary_folders": "المجلدات المنشأة: {count}", + "summary_skipped": "لم يُستورد:", + "skip_too_large": "أكبر مما يقبله الخادم: {count}", + "skip_deleted": "محذوفة في Thunderbird: {count}", + "skip_embedded": "رسائل مرفقة برسائل أخرى: {count}", + "skip_contact": "جهات الاتصال: {count}", + "skip_calendar": "عناصر التقويم: {count}", + "skip_other": "عناصر أخرى: {count}", + "summary_failed": "تعذّر الاستيراد: {count}", + "failure_invalid": "ليست رسالة صالحة", + "untitled": "(بلا موضوع)", + "cancelled_hint": "للمتابعة لاحقًا، استورد الملفات نفسها إلى المجلد نفسه مرة أخرى.", + "close": "إغلاق" + }, "recover_deleted": { "tab": "استعادة العناصر المحذوفة", "title": "استعادة العناصر المحذوفة", diff --git a/locales/ca/common.json b/locales/ca/common.json index e023d59f5..3593795aa 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "La cerca no està disponible a la vista unificada" }, + "mail_import": { + "menu_import": "Importa correu…", + "settings_label": "Importa correu", + "settings_description": "Porta correu des de fitxers .eml, ZIP, mbox o Outlook .pst.", + "settings_button": "Importa…", + "title": "Importa correu", + "description": "Trieu fitxers .eml, .zip, .mbox o Outlook .pst.", + "choose_files": "Tria fitxers…", + "chosen_files": "Fitxers triats", + "remove_file": "Treu {name}", + "format_eml": "Missatge", + "format_zip": "Arxiu ZIP", + "format_mbox": "Fitxer mbox", + "format_pst": "Fitxer de dades d'Outlook", + "target_label": "Importa a", + "target_top": "Nivell superior", + "keep_folders": "Recrea les carpetes del fitxer", + "keep_folders_hint": "Les carpetes amb el mateix nom es tornen a fer servir. Safata d'entrada, Elements enviats i Elements suprimits al nivell superior van a les vostres.", + "resume": "Continua on es va aturar l'última importació d'aquests fitxers (missatges ja fets: {count})", + "quota_note": "Molts servidors només accepten uns 1000 missatges o 50 MB per hora de cada usuari. Una importació més gran fa una pausa i després continua sola.", + "start": "Importa", + "cancel": "Cancel·la", + "running_title": "S'està important el correu", + "progress_label": "Progrés de la importació", + "status_imported": "Missatges importats: {count}", + "reading_file": "S'està llegint {name}", + "paused": "En pausa fins a les {time}. El servidor accepta com a màxim {files} pujades o {size} per hora. La importació continua sola.", + "paused_generic": "En pausa fins a les {time}. S'ha arribat al límit de pujada del servidor. La importació continua sola.", + "background": "Continua en segon pla", + "cancel_import": "Cancel·la la importació", + "chip": "S'està important el correu: {percent} %", + "chip_paused": "Importació en pausa fins a les {time}", + "done_title": "Importació acabada", + "cancelled_title": "Importació cancel·lada", + "failed_title": "Importació aturada", + "summary_imported": "Missatges importats: {count}", + "summary_folders": "Carpetes creades: {count}", + "summary_skipped": "No importats:", + "skip_too_large": "Més grans del que accepta el servidor: {count}", + "skip_deleted": "Suprimits a Thunderbird: {count}", + "skip_embedded": "Missatges adjunts a altres missatges: {count}", + "skip_contact": "Contactes: {count}", + "skip_calendar": "Elements del calendari: {count}", + "skip_other": "Altres elements: {count}", + "summary_failed": "No s'han pogut importar: {count}", + "failure_invalid": "no és un missatge vàlid", + "untitled": "(sense assumpte)", + "cancelled_hint": "Per continuar més tard, torneu a importar els mateixos fitxers a la mateixa carpeta.", + "close": "Tanca" + }, "recover_deleted": { "tab": "Recupera elements suprimits", "title": "Recupera elements suprimits", diff --git a/locales/cs/common.json b/locales/cs/common.json index a93c1ce6a..52e923127 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "Vyhledávání není ve sjednoceném zobrazení k dispozici" }, + "mail_import": { + "menu_import": "Importovat poštu…", + "settings_label": "Importovat poštu", + "settings_description": "Přeneste poštu ze souborů .eml, ZIP, mbox nebo Outlook .pst.", + "settings_button": "Importovat…", + "title": "Importovat poštu", + "description": "Vyberte soubory .eml, .zip, .mbox nebo Outlook .pst.", + "choose_files": "Vybrat soubory…", + "chosen_files": "Vybrané soubory", + "remove_file": "Odebrat {name}", + "format_eml": "Zpráva", + "format_zip": "Archiv ZIP", + "format_mbox": "Soubor mbox", + "format_pst": "Datový soubor Outlooku", + "target_label": "Importovat do", + "target_top": "Nejvyšší úroveň", + "keep_folders": "Znovu vytvořit složky ze souboru", + "keep_folders_hint": "Složky se stejným názvem se použijí znovu. Doručená pošta, Odeslaná pošta a Odstraněná pošta na nejvyšší úrovni skončí ve vašich vlastních.", + "resume": "Pokračovat tam, kde se poslední import těchto souborů zastavil (již hotové zprávy: {count})", + "quota_note": "Mnoho serverů přijímá od jednoho uživatele jen asi 1000 zpráv nebo 50 MB za hodinu. Větší import se pozastaví a pak pokračuje sám.", + "start": "Importovat", + "cancel": "Zrušit", + "running_title": "Importuje se pošta", + "progress_label": "Průběh importu", + "status_imported": "Importované zprávy: {count}", + "reading_file": "Čte se {name}", + "paused": "Pozastaveno do {time}. Server přijímá nejvýše {files} nahrání nebo {size} za hodinu. Import pokračuje sám.", + "paused_generic": "Pozastaveno do {time}. Byl dosažen limit nahrávání serveru. Import pokračuje sám.", + "background": "Běžet na pozadí", + "cancel_import": "Zrušit import", + "chip": "Importuje se pošta: {percent} %", + "chip_paused": "Import pozastaven do {time}", + "done_title": "Import dokončen", + "cancelled_title": "Import zrušen", + "failed_title": "Import zastaven", + "summary_imported": "Importované zprávy: {count}", + "summary_folders": "Vytvořené složky: {count}", + "summary_skipped": "Neimportováno:", + "skip_too_large": "Větší, než server přijme: {count}", + "skip_deleted": "Smazané v Thunderbirdu: {count}", + "skip_embedded": "Zprávy připojené k jiným zprávám: {count}", + "skip_contact": "Kontakty: {count}", + "skip_calendar": "Položky kalendáře: {count}", + "skip_other": "Jiné položky: {count}", + "summary_failed": "Nelze importovat: {count}", + "failure_invalid": "není platná zpráva", + "untitled": "(bez předmětu)", + "cancelled_hint": "Chcete-li pokračovat později, importujte stejné soubory znovu do stejné složky.", + "close": "Zavřít" + }, "recover_deleted": { "tab": "Obnovit smazané položky", "title": "Obnovit smazané položky", diff --git a/locales/da/common.json b/locales/da/common.json index 9710e03a2..b2ccff94f 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "Søgning er ikke tilgængelig i den samlede visning" }, + "mail_import": { + "menu_import": "Importér mail…", + "settings_label": "Importér mail", + "settings_description": "Hent mail ind fra .eml-, ZIP-, mbox- eller Outlook-.pst-filer.", + "settings_button": "Importér…", + "title": "Importér mail", + "description": "Vælg .eml-, .zip-, .mbox- eller Outlook-.pst-filer.", + "choose_files": "Vælg filer…", + "chosen_files": "Valgte filer", + "remove_file": "Fjern {name}", + "format_eml": "Besked", + "format_zip": "ZIP-arkiv", + "format_mbox": "mbox-fil", + "format_pst": "Outlook-datafil", + "target_label": "Importér til", + "target_top": "Øverste niveau", + "keep_folders": "Genskab mapperne fra filen", + "keep_folders_hint": "Mapper med samme navn bruges igen. Indbakke, Sendt post og Slettet post på øverste niveau havner i dine egne.", + "resume": "Fortsæt, hvor den sidste import af disse filer stoppede (beskeder allerede klaret: {count})", + "quota_note": "Mange servere tager kun imod omkring 1000 beskeder eller 50 MB i timen fra én bruger. En større import holder pause og fortsætter derefter af sig selv.", + "start": "Importér", + "cancel": "Annullér", + "running_title": "Importerer mail", + "progress_label": "Importens forløb", + "status_imported": "Importerede beskeder: {count}", + "reading_file": "Læser {name}", + "paused": "Sat på pause til {time}. Serveren tager højst imod {files} uploads eller {size} i timen. Importen fortsætter af sig selv.", + "paused_generic": "Sat på pause til {time}. Serverens uploadgrænse er nået. Importen fortsætter af sig selv.", + "background": "Kør i baggrunden", + "cancel_import": "Annullér import", + "chip": "Importerer mail: {percent} %", + "chip_paused": "Import sat på pause til {time}", + "done_title": "Import færdig", + "cancelled_title": "Import annulleret", + "failed_title": "Import stoppet", + "summary_imported": "Importerede beskeder: {count}", + "summary_folders": "Oprettede mapper: {count}", + "summary_skipped": "Ikke importeret:", + "skip_too_large": "Større end serveren tillader: {count}", + "skip_deleted": "Slettet i Thunderbird: {count}", + "skip_embedded": "Beskeder vedhæftet andre beskeder: {count}", + "skip_contact": "Kontakter: {count}", + "skip_calendar": "Kalenderelementer: {count}", + "skip_other": "Andre elementer: {count}", + "summary_failed": "Kunne ikke importeres: {count}", + "failure_invalid": "ikke en gyldig besked", + "untitled": "(intet emne)", + "cancelled_hint": "Importér de samme filer til samme mappe igen for at fortsætte senere.", + "close": "Luk" + }, "recover_deleted": { "tab": "Gendan slettede elementer", "title": "Gendan slettede elementer", diff --git a/locales/de/common.json b/locales/de/common.json index 04c1424a3..ba2cff1bb 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "Die Suche ist in der vereinheitlichten Ansicht nicht verfügbar" }, + "mail_import": { + "menu_import": "E-Mails importieren…", + "settings_label": "E-Mails importieren", + "settings_description": "E-Mails aus .eml-, ZIP-, mbox- oder Outlook-.pst-Dateien übernehmen.", + "settings_button": "Importieren…", + "title": "E-Mails importieren", + "description": "Wählen Sie .eml-, .zip-, .mbox- oder Outlook-.pst-Dateien.", + "choose_files": "Dateien auswählen…", + "chosen_files": "Ausgewählte Dateien", + "remove_file": "{name} entfernen", + "format_eml": "Nachricht", + "format_zip": "ZIP-Archiv", + "format_mbox": "mbox-Datei", + "format_pst": "Outlook-Datendatei", + "target_label": "Importieren in", + "target_top": "Oberste Ebene", + "keep_folders": "Ordner aus der Datei übernehmen", + "keep_folders_hint": "Ordner mit gleichem Namen werden wiederverwendet. Posteingang, Gesendete Elemente und Gelöschte Elemente auf oberster Ebene landen in Ihren eigenen.", + "resume": "Dort weitermachen, wo der letzte Import dieser Dateien aufgehört hat (bereits erledigte Nachrichten: {count})", + "quota_note": "Viele Server nehmen pro Benutzer nur etwa 1000 Nachrichten oder 50 MB pro Stunde an. Ein größerer Import pausiert und läuft dann von selbst weiter.", + "start": "Importieren", + "cancel": "Abbrechen", + "running_title": "E-Mails werden importiert", + "progress_label": "Fortschritt des Imports", + "status_imported": "Importierte Nachrichten: {count}", + "reading_file": "{name} wird gelesen", + "paused": "Pausiert bis {time}. Der Server nimmt höchstens {files} Uploads oder {size} pro Stunde an. Der Import läuft von selbst weiter.", + "paused_generic": "Pausiert bis {time}. Das Upload-Limit des Servers ist erreicht. Der Import läuft von selbst weiter.", + "background": "Im Hintergrund weiterlaufen lassen", + "cancel_import": "Import abbrechen", + "chip": "E-Mails werden importiert: {percent} %", + "chip_paused": "Import pausiert bis {time}", + "done_title": "Import abgeschlossen", + "cancelled_title": "Import abgebrochen", + "failed_title": "Import angehalten", + "summary_imported": "Importierte Nachrichten: {count}", + "summary_folders": "Angelegte Ordner: {count}", + "summary_skipped": "Nicht importiert:", + "skip_too_large": "Größer als vom Server erlaubt: {count}", + "skip_deleted": "In Thunderbird gelöscht: {count}", + "skip_embedded": "An andere Nachrichten angehängte Nachrichten: {count}", + "skip_contact": "Kontakte: {count}", + "skip_calendar": "Kalendereinträge: {count}", + "skip_other": "Andere Elemente: {count}", + "summary_failed": "Nicht importierbar: {count}", + "failure_invalid": "keine gültige Nachricht", + "untitled": "(kein Betreff)", + "cancelled_hint": "Um später weiterzumachen, importieren Sie dieselben Dateien erneut in denselben Ordner.", + "close": "Schließen" + }, "recover_deleted": { "tab": "Gelöschte E-Mails", "title": "Gelöschte Elemente wiederherstellen", diff --git a/locales/en/common.json b/locales/en/common.json index 89aad41a5..6c04c2c30 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "Search is not available in the unified view" }, + "mail_import": { + "menu_import": "Import mail…", + "settings_label": "Import mail", + "settings_description": "Bring in mail from .eml, ZIP, mbox or Outlook .pst files.", + "settings_button": "Import…", + "title": "Import mail", + "description": "Choose .eml, .zip, .mbox or Outlook .pst files.", + "choose_files": "Choose files…", + "chosen_files": "Chosen files", + "remove_file": "Remove {name}", + "format_eml": "Message", + "format_zip": "ZIP archive", + "format_mbox": "mbox file", + "format_pst": "Outlook data file", + "target_label": "Import into", + "target_top": "Top level", + "keep_folders": "Recreate the folders from the file", + "keep_folders_hint": "Folders with the same name are used again. Inbox, Sent Items and Deleted Items at the top go into your own.", + "resume": "Continue where the last import of these files stopped (messages already done: {count})", + "quota_note": "Many servers take only about 1000 messages or 50 MB an hour from one user. A larger import pauses and then goes on by itself.", + "start": "Import", + "cancel": "Cancel", + "running_title": "Importing mail", + "progress_label": "Import progress", + "status_imported": "Messages imported: {count}", + "reading_file": "Reading {name}", + "paused": "Paused until {time}. The server takes at most {files} uploads or {size} an hour. The import goes on by itself.", + "paused_generic": "Paused until {time}. The server's upload limit was reached. The import goes on by itself.", + "background": "Run in background", + "cancel_import": "Cancel import", + "chip": "Importing mail: {percent}%", + "chip_paused": "Import paused until {time}", + "done_title": "Import finished", + "cancelled_title": "Import cancelled", + "failed_title": "Import stopped", + "summary_imported": "Messages imported: {count}", + "summary_folders": "Folders created: {count}", + "summary_skipped": "Not imported:", + "skip_too_large": "Larger than the server accepts: {count}", + "skip_deleted": "Deleted in Thunderbird: {count}", + "skip_embedded": "Messages attached to other messages: {count}", + "skip_contact": "Contacts: {count}", + "skip_calendar": "Calendar items: {count}", + "skip_other": "Other items: {count}", + "summary_failed": "Couldn't be imported: {count}", + "failure_invalid": "not a valid message", + "untitled": "(no subject)", + "cancelled_hint": "To go on later, import the same files into the same folder again.", + "close": "Close" + }, "recover_deleted": { "tab": "Recover deleted items", "title": "Recover deleted items", diff --git a/locales/es/common.json b/locales/es/common.json index 4b53c9222..d2a079456 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "La búsqueda no está disponible en la vista unificada" }, + "mail_import": { + "menu_import": "Importar correo…", + "settings_label": "Importar correo", + "settings_description": "Traer correo desde archivos .eml, ZIP, mbox u Outlook .pst.", + "settings_button": "Importar…", + "title": "Importar correo", + "description": "Elija archivos .eml, .zip, .mbox u Outlook .pst.", + "choose_files": "Elegir archivos…", + "chosen_files": "Archivos elegidos", + "remove_file": "Quitar {name}", + "format_eml": "Mensaje", + "format_zip": "Archivo ZIP", + "format_mbox": "Archivo mbox", + "format_pst": "Archivo de datos de Outlook", + "target_label": "Importar en", + "target_top": "Nivel superior", + "keep_folders": "Recrear las carpetas del archivo", + "keep_folders_hint": "Las carpetas con el mismo nombre se reutilizan. Bandeja de entrada, Elementos enviados y Elementos eliminados en el nivel superior van a las suyas.", + "resume": "Seguir donde se detuvo la última importación de estos archivos (mensajes ya procesados: {count})", + "quota_note": "Muchos servidores solo aceptan unos 1000 mensajes o 50 MB por hora de cada usuario. Una importación mayor se pausa y luego sigue sola.", + "start": "Importar", + "cancel": "Cancelar", + "running_title": "Importando correo", + "progress_label": "Progreso de la importación", + "status_imported": "Mensajes importados: {count}", + "reading_file": "Leyendo {name}", + "paused": "En pausa hasta las {time}. El servidor acepta como máximo {files} subidas o {size} por hora. La importación sigue sola.", + "paused_generic": "En pausa hasta las {time}. Se alcanzó el límite de subida del servidor. La importación sigue sola.", + "background": "Seguir en segundo plano", + "cancel_import": "Cancelar importación", + "chip": "Importando correo: {percent} %", + "chip_paused": "Importación en pausa hasta las {time}", + "done_title": "Importación terminada", + "cancelled_title": "Importación cancelada", + "failed_title": "Importación detenida", + "summary_imported": "Mensajes importados: {count}", + "summary_folders": "Carpetas creadas: {count}", + "summary_skipped": "No importados:", + "skip_too_large": "Más grandes de lo que acepta el servidor: {count}", + "skip_deleted": "Eliminados en Thunderbird: {count}", + "skip_embedded": "Mensajes adjuntos a otros mensajes: {count}", + "skip_contact": "Contactos: {count}", + "skip_calendar": "Elementos de calendario: {count}", + "skip_other": "Otros elementos: {count}", + "summary_failed": "No se pudieron importar: {count}", + "failure_invalid": "no es un mensaje válido", + "untitled": "(sin asunto)", + "cancelled_hint": "Para seguir más tarde, importe los mismos archivos en la misma carpeta otra vez.", + "close": "Cerrar" + }, "recover_deleted": { "tab": "Recuperar elementos eliminados", "title": "Recuperar elementos eliminados", diff --git a/locales/et/common.json b/locales/et/common.json index e12342f13..64749372d 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "Otsing pole ühendatud vaates saadaval" }, + "mail_import": { + "menu_import": "Impordi kirju…", + "settings_label": "Impordi kirju", + "settings_description": "Too kirjad sisse .eml-, ZIP-, mbox- või Outlooki .pst-failidest.", + "settings_button": "Impordi…", + "title": "Impordi kirju", + "description": "Valige .eml-, .zip-, .mbox- või Outlooki .pst-failid.", + "choose_files": "Vali failid…", + "chosen_files": "Valitud failid", + "remove_file": "Eemalda {name}", + "format_eml": "Kiri", + "format_zip": "ZIP-arhiiv", + "format_mbox": "mbox-fail", + "format_pst": "Outlooki andmefail", + "target_label": "Impordi kausta", + "target_top": "Ülemine tase", + "keep_folders": "Loo failist kaustad uuesti", + "keep_folders_hint": "Sama nimega kaustu kasutatakse uuesti. Ülemise taseme Sisendkaust, Saadetud ja Kustutatud lähevad teie omadesse.", + "resume": "Jätka sealt, kus nende failide viimane import peatus (juba tehtud kirju: {count})", + "quota_note": "Paljud serverid võtavad ühelt kasutajalt vastu ainult umbes 1000 kirja või 50 MB tunnis. Suurem import teeb pausi ja jätkab siis ise.", + "start": "Impordi", + "cancel": "Loobu", + "running_title": "Kirjade importimine", + "progress_label": "Impordi edenemine", + "status_imported": "Imporditud kirju: {count}", + "reading_file": "Loetakse faili {name}", + "paused": "Peatatud kuni {time}. Server võtab tunnis vastu kuni {files} üleslaadimist või {size}. Import jätkab ise.", + "paused_generic": "Peatatud kuni {time}. Serveri üleslaadimise piir on täis. Import jätkab ise.", + "background": "Jätka taustal", + "cancel_import": "Katkesta import", + "chip": "Kirjade importimine: {percent}%", + "chip_paused": "Import peatatud kuni {time}", + "done_title": "Import lõpetatud", + "cancelled_title": "Import katkestatud", + "failed_title": "Import peatus", + "summary_imported": "Imporditud kirju: {count}", + "summary_folders": "Loodud kaustu: {count}", + "summary_skipped": "Importimata:", + "skip_too_large": "Suuremad, kui server lubab: {count}", + "skip_deleted": "Thunderbirdis kustutatud: {count}", + "skip_embedded": "Teiste kirjade külge lisatud kirjad: {count}", + "skip_contact": "Kontaktid: {count}", + "skip_calendar": "Kalendrikirjed: {count}", + "skip_other": "Muud üksused: {count}", + "summary_failed": "Ei saanud importida: {count}", + "failure_invalid": "pole kehtiv kiri", + "untitled": "(teemata)", + "cancelled_hint": "Hiljem jätkamiseks importige samad failid uuesti samasse kausta.", + "close": "Sulge" + }, "recover_deleted": { "tab": "Taasta kustutatud üksused", "title": "Taasta kustutatud üksused", diff --git a/locales/fa/common.json b/locales/fa/common.json index f3604c96c..4e273d67b 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "جستجو در نمای یکپارچه در دسترس نیست" }, + "mail_import": { + "menu_import": "درون‌ریزی نامه…", + "settings_label": "درون‌ریزی نامه", + "settings_description": "نامه‌ها را از فایل‌های ‎.eml، ZIP، mbox یا ‎.pst اوت‌لوک بیاورید.", + "settings_button": "درون‌ریزی…", + "title": "درون‌ریزی نامه", + "description": "فایل‌های ‎.eml، ‎.zip، ‎.mbox یا ‎.pst اوت‌لوک را انتخاب کنید.", + "choose_files": "انتخاب فایل‌ها…", + "chosen_files": "فایل‌های انتخاب‌شده", + "remove_file": "برداشتن {name}", + "format_eml": "پیام", + "format_zip": "بایگانی ZIP", + "format_mbox": "فایل mbox", + "format_pst": "فایل داده اوت‌لوک", + "target_label": "درون‌ریزی به", + "target_top": "بالاترین سطح", + "keep_folders": "ساختن دوباره پوشه‌های فایل", + "keep_folders_hint": "پوشه‌های هم‌نام دوباره به کار می‌روند. صندوق ورودی، موارد ارسال‌شده و موارد حذف‌شده در بالاترین سطح به پوشه‌های خودتان می‌روند.", + "resume": "ادامه از جایی که آخرین درون‌ریزی این فایل‌ها متوقف شد (پیام‌های انجام‌شده: {count})", + "quota_note": "بسیاری از سرورها از هر کاربر فقط حدود ۱۰۰۰ پیام یا ۵۰ مگابایت در ساعت می‌پذیرند. درون‌ریزی بزرگ‌تر مکث می‌کند و سپس خودش ادامه می‌دهد.", + "start": "درون‌ریزی", + "cancel": "لغو", + "running_title": "در حال درون‌ریزی نامه", + "progress_label": "پیشرفت درون‌ریزی", + "status_imported": "پیام‌های درون‌ریزی‌شده: {count}", + "reading_file": "در حال خواندن {name}", + "paused": "تا {time} متوقف شد. سرور حداکثر {files} بارگذاری یا {size} در ساعت می‌پذیرد. درون‌ریزی خودش ادامه می‌دهد.", + "paused_generic": "تا {time} متوقف شد. محدودیت بارگذاری سرور پر شده است. درون‌ریزی خودش ادامه می‌دهد.", + "background": "اجرا در پس‌زمینه", + "cancel_import": "لغو درون‌ریزی", + "chip": "در حال درون‌ریزی نامه: {percent}٪", + "chip_paused": "درون‌ریزی تا {time} متوقف است", + "done_title": "درون‌ریزی تمام شد", + "cancelled_title": "درون‌ریزی لغو شد", + "failed_title": "درون‌ریزی متوقف شد", + "summary_imported": "پیام‌های درون‌ریزی‌شده: {count}", + "summary_folders": "پوشه‌های ساخته‌شده: {count}", + "summary_skipped": "درون‌ریزی نشد:", + "skip_too_large": "بزرگ‌تر از حد مجاز سرور: {count}", + "skip_deleted": "حذف‌شده در Thunderbird: {count}", + "skip_embedded": "پیام‌های پیوست‌شده به پیام‌های دیگر: {count}", + "skip_contact": "مخاطبان: {count}", + "skip_calendar": "موارد تقویم: {count}", + "skip_other": "موارد دیگر: {count}", + "summary_failed": "درون‌ریزی نشد: {count}", + "failure_invalid": "پیام معتبری نیست", + "untitled": "(بدون موضوع)", + "cancelled_hint": "برای ادامه در زمانی دیگر، همان فایل‌ها را دوباره در همان پوشه درون‌ریزی کنید.", + "close": "بستن" + }, "recover_deleted": { "tab": "بازیابی موارد حذف‌شده", "title": "بازیابی موارد حذف‌شده", diff --git a/locales/fr/common.json b/locales/fr/common.json index f0c00df6c..75c1f268d 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "La recherche n'est pas disponible dans la vue unifiée" }, + "mail_import": { + "menu_import": "Importer des e-mails…", + "settings_label": "Importer des e-mails", + "settings_description": "Importer des e-mails depuis des fichiers .eml, ZIP, mbox ou Outlook .pst.", + "settings_button": "Importer…", + "title": "Importer des e-mails", + "description": "Choisissez des fichiers .eml, .zip, .mbox ou Outlook .pst.", + "choose_files": "Choisir des fichiers…", + "chosen_files": "Fichiers choisis", + "remove_file": "Retirer {name}", + "format_eml": "Message", + "format_zip": "Archive ZIP", + "format_mbox": "Fichier mbox", + "format_pst": "Fichier de données Outlook", + "target_label": "Importer dans", + "target_top": "Niveau supérieur", + "keep_folders": "Recréer les dossiers du fichier", + "keep_folders_hint": "Les dossiers du même nom sont réutilisés. Boîte de réception, Éléments envoyés et Éléments supprimés au niveau supérieur vont dans les vôtres.", + "resume": "Reprendre là où le dernier import de ces fichiers s'est arrêté (messages déjà traités : {count})", + "quota_note": "Beaucoup de serveurs n'acceptent qu'environ 1000 messages ou 50 Mo par heure et par utilisateur. Un import plus grand se met en pause puis reprend tout seul.", + "start": "Importer", + "cancel": "Annuler", + "running_title": "Import des e-mails", + "progress_label": "Progression de l'import", + "status_imported": "Messages importés : {count}", + "reading_file": "Lecture de {name}", + "paused": "En pause jusqu'à {time}. Le serveur accepte au plus {files} envois ou {size} par heure. L'import reprend tout seul.", + "paused_generic": "En pause jusqu'à {time}. La limite d'envoi du serveur est atteinte. L'import reprend tout seul.", + "background": "Continuer en arrière-plan", + "cancel_import": "Annuler l'import", + "chip": "Import des e-mails : {percent} %", + "chip_paused": "Import en pause jusqu'à {time}", + "done_title": "Import terminé", + "cancelled_title": "Import annulé", + "failed_title": "Import interrompu", + "summary_imported": "Messages importés : {count}", + "summary_folders": "Dossiers créés : {count}", + "summary_skipped": "Non importés :", + "skip_too_large": "Plus grands que ce qu'accepte le serveur : {count}", + "skip_deleted": "Supprimés dans Thunderbird : {count}", + "skip_embedded": "Messages joints à d'autres messages : {count}", + "skip_contact": "Contacts : {count}", + "skip_calendar": "Éléments d'agenda : {count}", + "skip_other": "Autres éléments : {count}", + "summary_failed": "Import impossible : {count}", + "failure_invalid": "message non valide", + "untitled": "(sans objet)", + "cancelled_hint": "Pour reprendre plus tard, importez à nouveau les mêmes fichiers dans le même dossier.", + "close": "Fermer" + }, "recover_deleted": { "tab": "Récupérer des éléments supprimés", "title": "Récupérer des éléments supprimés", diff --git a/locales/he/common.json b/locales/he/common.json index 8f1f998a3..2b6e185c5 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -5381,6 +5381,56 @@ "unified_mailbox": { "search_unavailable": "חיפוש אינו זמין בתצוגה המאוחדת" }, + "mail_import": { + "menu_import": "ייבוא דואר…", + "settings_label": "ייבוא דואר", + "settings_description": "הבאת דואר מקובצי ‎.eml‏, ZIP‏, mbox או ‎.pst של Outlook.", + "settings_button": "ייבוא…", + "title": "ייבוא דואר", + "description": "בחרו קובצי ‎.eml‏, ‎.zip‏, ‎.mbox או ‎.pst של Outlook.", + "choose_files": "בחירת קבצים…", + "chosen_files": "קבצים שנבחרו", + "remove_file": "הסרת {name}", + "format_eml": "הודעה", + "format_zip": "ארכיון ZIP", + "format_mbox": "קובץ mbox", + "format_pst": "קובץ נתונים של Outlook", + "target_label": "ייבוא אל", + "target_top": "רמה עליונה", + "keep_folders": "יצירה מחדש של התיקיות מהקובץ", + "keep_folders_hint": "תיקיות באותו שם ישמשו שוב. דואר נכנס, פריטים שנשלחו ופריטים שנמחקו ברמה העליונה ייכנסו לתיקיות שלכם.", + "resume": "להמשיך מהמקום שבו נעצר הייבוא האחרון של הקבצים האלה (הודעות שכבר טופלו: {count})", + "quota_note": "שרתים רבים מקבלים ממשתמש אחד רק כ-1000 הודעות או 50 MB בשעה. ייבוא גדול יותר נעצר לזמן מה ואז ממשיך מעצמו.", + "start": "ייבוא", + "cancel": "ביטול", + "running_title": "מייבא דואר", + "progress_label": "התקדמות הייבוא", + "status_imported": "הודעות שיובאו: {count}", + "reading_file": "קורא את {name}", + "paused": "מושהה עד {time}. השרת מקבל לכל היותר {files} העלאות או {size} בשעה. הייבוא ימשיך מעצמו.", + "paused_generic": "מושהה עד {time}. הגעתם למגבלת ההעלאה של השרת. הייבוא ימשיך מעצמו.", + "background": "המשך ברקע", + "cancel_import": "ביטול הייבוא", + "chip": "מייבא דואר: {percent}%", + "chip_paused": "הייבוא מושהה עד {time}", + "done_title": "הייבוא הסתיים", + "cancelled_title": "הייבוא בוטל", + "failed_title": "הייבוא נעצר", + "summary_imported": "הודעות שיובאו: {count}", + "summary_folders": "תיקיות שנוצרו: {count}", + "summary_skipped": "לא יובאו:", + "skip_too_large": "גדולות ממה שהשרת מקבל: {count}", + "skip_deleted": "נמחקו ב-Thunderbird: {count}", + "skip_embedded": "הודעות שצורפו להודעות אחרות: {count}", + "skip_contact": "אנשי קשר: {count}", + "skip_calendar": "פריטי לוח שנה: {count}", + "skip_other": "פריטים אחרים: {count}", + "summary_failed": "לא ניתן לייבא: {count}", + "failure_invalid": "זו אינה הודעה תקינה", + "untitled": "(ללא נושא)", + "cancelled_hint": "כדי להמשיך מאוחר יותר, ייבאו שוב את אותם קבצים לאותה תיקייה.", + "close": "סגירה" + }, "recover_deleted": { "tab": "שחזור פריטים שנמחקו", "title": "שחזור פריטים שנמחקו", diff --git a/locales/hu/common.json b/locales/hu/common.json index 6c3668404..3114c2f2b 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "A keresés nem érhető el az egységes nézetben" }, + "mail_import": { + "menu_import": "Levelek importálása…", + "settings_label": "Levelek importálása", + "settings_description": "Levelek behozása .eml, ZIP, mbox vagy Outlook .pst fájlokból.", + "settings_button": "Importálás…", + "title": "Levelek importálása", + "description": "Válasszon .eml, .zip, .mbox vagy Outlook .pst fájlokat.", + "choose_files": "Fájlok kiválasztása…", + "chosen_files": "Kiválasztott fájlok", + "remove_file": "{name} eltávolítása", + "format_eml": "Üzenet", + "format_zip": "ZIP-archívum", + "format_mbox": "mbox-fájl", + "format_pst": "Outlook-adatfájl", + "target_label": "Importálás ide", + "target_top": "Legfelső szint", + "keep_folders": "A fájl mappáinak újbóli létrehozása", + "keep_folders_hint": "Az azonos nevű mappák újra felhasználódnak. A legfelső szinten lévő Beérkezett üzenetek, Elküldött elemek és Törölt elemek a saját mappáiba kerülnek.", + "resume": "Folytatás ott, ahol e fájlok legutóbbi importálása megállt (már kész üzenetek: {count})", + "quota_note": "Sok szerver felhasználónként óránként csak körülbelül 1000 üzenetet vagy 50 MB-ot fogad. A nagyobb importálás szünetel, majd magától folytatódik.", + "start": "Importálás", + "cancel": "Mégse", + "running_title": "Levelek importálása folyamatban", + "progress_label": "Az importálás állapota", + "status_imported": "Importált üzenetek: {count}", + "reading_file": "{name} olvasása", + "paused": "Szüneteltetve eddig: {time}. A szerver óránként legfeljebb {files} feltöltést vagy {size} adatot fogad. Az importálás magától folytatódik.", + "paused_generic": "Szüneteltetve eddig: {time}. Elérte a szerver feltöltési korlátját. Az importálás magától folytatódik.", + "background": "Futtatás a háttérben", + "cancel_import": "Importálás megszakítása", + "chip": "Levelek importálása: {percent}%", + "chip_paused": "Az importálás szünetel eddig: {time}", + "done_title": "Az importálás befejeződött", + "cancelled_title": "Az importálás megszakítva", + "failed_title": "Az importálás leállt", + "summary_imported": "Importált üzenetek: {count}", + "summary_folders": "Létrehozott mappák: {count}", + "summary_skipped": "Nem importált:", + "skip_too_large": "Nagyobb, mint amit a szerver elfogad: {count}", + "skip_deleted": "Thunderbirdben törölve: {count}", + "skip_embedded": "Más üzenetekhez csatolt üzenetek: {count}", + "skip_contact": "Névjegyek: {count}", + "skip_calendar": "Naptárelemek: {count}", + "skip_other": "Egyéb elemek: {count}", + "summary_failed": "Nem importálható: {count}", + "failure_invalid": "nem érvényes üzenet", + "untitled": "(nincs tárgy)", + "cancelled_hint": "A későbbi folytatáshoz importálja újra ugyanazokat a fájlokat ugyanabba a mappába.", + "close": "Bezárás" + }, "recover_deleted": { "tab": "Törölt elemek visszaállítása", "title": "Törölt elemek visszaállítása", diff --git a/locales/it/common.json b/locales/it/common.json index 99afff3f1..2a5d26089 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "La ricerca non è disponibile nella vista unificata" }, + "mail_import": { + "menu_import": "Importa posta…", + "settings_label": "Importa posta", + "settings_description": "Importa la posta da file .eml, ZIP, mbox o Outlook .pst.", + "settings_button": "Importa…", + "title": "Importa posta", + "description": "Scegli file .eml, .zip, .mbox o Outlook .pst.", + "choose_files": "Scegli file…", + "chosen_files": "File scelti", + "remove_file": "Rimuovi {name}", + "format_eml": "Messaggio", + "format_zip": "Archivio ZIP", + "format_mbox": "File mbox", + "format_pst": "File di dati di Outlook", + "target_label": "Importa in", + "target_top": "Livello principale", + "keep_folders": "Ricrea le cartelle del file", + "keep_folders_hint": "Le cartelle con lo stesso nome vengono riutilizzate. Posta in arrivo, Posta inviata ed Elementi eliminati al livello principale finiscono nelle tue.", + "resume": "Riprendi da dove si è fermata l'ultima importazione di questi file (messaggi già elaborati: {count})", + "quota_note": "Molti server accettano solo circa 1000 messaggi o 50 MB all'ora per utente. Un'importazione più grande va in pausa e poi riprende da sola.", + "start": "Importa", + "cancel": "Annulla", + "running_title": "Importazione della posta", + "progress_label": "Avanzamento dell'importazione", + "status_imported": "Messaggi importati: {count}", + "reading_file": "Lettura di {name}", + "paused": "In pausa fino alle {time}. Il server accetta al massimo {files} caricamenti o {size} all'ora. L'importazione riprende da sola.", + "paused_generic": "In pausa fino alle {time}. È stato raggiunto il limite di caricamento del server. L'importazione riprende da sola.", + "background": "Continua in background", + "cancel_import": "Annulla importazione", + "chip": "Importazione della posta: {percent}%", + "chip_paused": "Importazione in pausa fino alle {time}", + "done_title": "Importazione completata", + "cancelled_title": "Importazione annullata", + "failed_title": "Importazione interrotta", + "summary_imported": "Messaggi importati: {count}", + "summary_folders": "Cartelle create: {count}", + "summary_skipped": "Non importati:", + "skip_too_large": "Più grandi di quanto accetta il server: {count}", + "skip_deleted": "Eliminati in Thunderbird: {count}", + "skip_embedded": "Messaggi allegati ad altri messaggi: {count}", + "skip_contact": "Contatti: {count}", + "skip_calendar": "Elementi del calendario: {count}", + "skip_other": "Altri elementi: {count}", + "summary_failed": "Impossibile importare: {count}", + "failure_invalid": "non è un messaggio valido", + "untitled": "(nessun oggetto)", + "cancelled_hint": "Per riprendere più tardi, importa di nuovo gli stessi file nella stessa cartella.", + "close": "Chiudi" + }, "recover_deleted": { "tab": "Recupera elementi eliminati", "title": "Recupera elementi eliminati", diff --git a/locales/ja/common.json b/locales/ja/common.json index 3a8dcb529..23bd8190c 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "統合ビューでは検索を利用できません" }, + "mail_import": { + "menu_import": "メールをインポート…", + "settings_label": "メールをインポート", + "settings_description": ".eml、ZIP、mbox、Outlook の .pst ファイルからメールを取り込みます。", + "settings_button": "インポート…", + "title": "メールをインポート", + "description": ".eml、.zip、.mbox、Outlook の .pst ファイルを選んでください。", + "choose_files": "ファイルを選択…", + "chosen_files": "選択したファイル", + "remove_file": "{name} を外す", + "format_eml": "メッセージ", + "format_zip": "ZIP アーカイブ", + "format_mbox": "mbox ファイル", + "format_pst": "Outlook データファイル", + "target_label": "インポート先", + "target_top": "最上位", + "keep_folders": "ファイルのフォルダーを作り直す", + "keep_folders_hint": "同じ名前のフォルダーはそのまま使います。最上位の受信トレイ、送信済みアイテム、削除済みアイテムはご自身のフォルダーに入ります。", + "resume": "これらのファイルの前回のインポートが止まったところから続ける(処理済みのメッセージ: {count})", + "quota_note": "多くのサーバーは 1 人のユーザーから 1 時間に約 1000 通または 50 MB までしか受け付けません。それより大きいインポートは一時停止し、その後自動で再開します。", + "start": "インポート", + "cancel": "キャンセル", + "running_title": "メールをインポートしています", + "progress_label": "インポートの進み具合", + "status_imported": "インポートしたメッセージ: {count}", + "reading_file": "{name} を読み込み中", + "paused": "{time} まで一時停止中。サーバーが受け付けるのは 1 時間に最大 {files} 件のアップロードまたは {size} です。インポートは自動で再開します。", + "paused_generic": "{time} まで一時停止中。サーバーのアップロード上限に達しました。インポートは自動で再開します。", + "background": "バックグラウンドで続ける", + "cancel_import": "インポートをキャンセル", + "chip": "メールをインポート中: {percent}%", + "chip_paused": "インポートは {time} まで一時停止中", + "done_title": "インポートが完了しました", + "cancelled_title": "インポートをキャンセルしました", + "failed_title": "インポートが止まりました", + "summary_imported": "インポートしたメッセージ: {count}", + "summary_folders": "作成したフォルダー: {count}", + "summary_skipped": "インポートしなかったもの:", + "skip_too_large": "サーバーの上限より大きい: {count}", + "skip_deleted": "Thunderbird で削除済み: {count}", + "skip_embedded": "他のメッセージに添付されたメッセージ: {count}", + "skip_contact": "連絡先: {count}", + "skip_calendar": "カレンダーの項目: {count}", + "skip_other": "その他の項目: {count}", + "summary_failed": "インポートできなかったもの: {count}", + "failure_invalid": "有効なメッセージではありません", + "untitled": "(件名なし)", + "cancelled_hint": "後で続けるには、同じファイルを同じフォルダーにもう一度インポートしてください。", + "close": "閉じる" + }, "recover_deleted": { "tab": "削除済みアイテムの復元", "title": "削除済みアイテムの復元", diff --git a/locales/ko/common.json b/locales/ko/common.json index 8dadb7040..95bda3dbb 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "통합 보기에서는 검색을 사용할 수 없습니다" }, + "mail_import": { + "menu_import": "메일 가져오기…", + "settings_label": "메일 가져오기", + "settings_description": ".eml, ZIP, mbox 또는 Outlook .pst 파일에서 메일을 가져옵니다.", + "settings_button": "가져오기…", + "title": "메일 가져오기", + "description": ".eml, .zip, .mbox 또는 Outlook .pst 파일을 선택하세요.", + "choose_files": "파일 선택…", + "chosen_files": "선택한 파일", + "remove_file": "{name} 제거", + "format_eml": "메시지", + "format_zip": "ZIP 압축 파일", + "format_mbox": "mbox 파일", + "format_pst": "Outlook 데이터 파일", + "target_label": "가져올 위치", + "target_top": "최상위", + "keep_folders": "파일의 폴더를 다시 만들기", + "keep_folders_hint": "이름이 같은 폴더는 다시 사용합니다. 최상위의 받은 편지함, 보낸 편지함, 지운 편지함은 사용자의 폴더로 들어갑니다.", + "resume": "이 파일들의 마지막 가져오기가 멈춘 곳부터 계속 (이미 처리한 메시지: {count})", + "quota_note": "많은 서버는 사용자 한 명에게서 시간당 약 1000개의 메시지 또는 50MB만 받습니다. 더 큰 가져오기는 잠시 멈췄다가 저절로 계속됩니다.", + "start": "가져오기", + "cancel": "취소", + "running_title": "메일을 가져오는 중", + "progress_label": "가져오기 진행 상황", + "status_imported": "가져온 메시지: {count}", + "reading_file": "{name} 읽는 중", + "paused": "{time}까지 일시 중지됨. 서버는 시간당 최대 {files}건의 업로드 또는 {size}를 받습니다. 가져오기는 저절로 계속됩니다.", + "paused_generic": "{time}까지 일시 중지됨. 서버의 업로드 한도에 도달했습니다. 가져오기는 저절로 계속됩니다.", + "background": "백그라운드에서 실행", + "cancel_import": "가져오기 취소", + "chip": "메일을 가져오는 중: {percent}%", + "chip_paused": "가져오기가 {time}까지 일시 중지됨", + "done_title": "가져오기 완료", + "cancelled_title": "가져오기 취소됨", + "failed_title": "가져오기 중단됨", + "summary_imported": "가져온 메시지: {count}", + "summary_folders": "만든 폴더: {count}", + "summary_skipped": "가져오지 않음:", + "skip_too_large": "서버가 받는 크기보다 큼: {count}", + "skip_deleted": "Thunderbird에서 삭제됨: {count}", + "skip_embedded": "다른 메시지에 첨부된 메시지: {count}", + "skip_contact": "연락처: {count}", + "skip_calendar": "캘린더 항목: {count}", + "skip_other": "기타 항목: {count}", + "summary_failed": "가져올 수 없음: {count}", + "failure_invalid": "올바른 메시지가 아님", + "untitled": "(제목 없음)", + "cancelled_hint": "나중에 계속하려면 같은 파일을 같은 폴더로 다시 가져오세요.", + "close": "닫기" + }, "recover_deleted": { "tab": "삭제된 항목 복구", "title": "삭제된 항목 복구", diff --git a/locales/lv/common.json b/locales/lv/common.json index 8160cd0b0..3751f5cbb 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "Meklēšana nav pieejama apvienotajā skatā" }, + "mail_import": { + "menu_import": "Importēt pastu…", + "settings_label": "Importēt pastu", + "settings_description": "Ienesiet pastu no .eml, ZIP, mbox vai Outlook .pst failiem.", + "settings_button": "Importēt…", + "title": "Importēt pastu", + "description": "Izvēlieties .eml, .zip, .mbox vai Outlook .pst failus.", + "choose_files": "Izvēlēties failus…", + "chosen_files": "Izvēlētie faili", + "remove_file": "Noņemt {name}", + "format_eml": "Ziņojums", + "format_zip": "ZIP arhīvs", + "format_mbox": "mbox fails", + "format_pst": "Outlook datu fails", + "target_label": "Importēt mapē", + "target_top": "Augšējais līmenis", + "keep_folders": "Atjaunot mapes no faila", + "keep_folders_hint": "Mapes ar tādu pašu nosaukumu tiek izmantotas atkārtoti. Augšējā līmeņa Iesūtne, Nosūtītie un Dzēstie nonāk jūsu pašu mapēs.", + "resume": "Turpināt no vietas, kur šo failu pēdējā importēšana apstājās (jau apstrādāti ziņojumi: {count})", + "quota_note": "Daudzi serveri no viena lietotāja pieņem tikai aptuveni 1000 ziņojumu vai 50 MB stundā. Lielāka importēšana apstājas un pēc tam turpinās pati.", + "start": "Importēt", + "cancel": "Atcelt", + "running_title": "Notiek pasta importēšana", + "progress_label": "Importēšanas gaita", + "status_imported": "Importētie ziņojumi: {count}", + "reading_file": "Lasa {name}", + "paused": "Apturēts līdz {time}. Serveris stundā pieņem ne vairāk kā {files} augšupielādes vai {size}. Importēšana turpināsies pati.", + "paused_generic": "Apturēts līdz {time}. Sasniegts servera augšupielādes ierobežojums. Importēšana turpināsies pati.", + "background": "Turpināt fonā", + "cancel_import": "Atcelt importēšanu", + "chip": "Notiek pasta importēšana: {percent}%", + "chip_paused": "Importēšana apturēta līdz {time}", + "done_title": "Importēšana pabeigta", + "cancelled_title": "Importēšana atcelta", + "failed_title": "Importēšana apturēta", + "summary_imported": "Importētie ziņojumi: {count}", + "summary_folders": "Izveidotās mapes: {count}", + "summary_skipped": "Nav importēti:", + "skip_too_large": "Lielāki, nekā serveris pieņem: {count}", + "skip_deleted": "Dzēsti programmā Thunderbird: {count}", + "skip_embedded": "Ziņojumi, kas pievienoti citiem ziņojumiem: {count}", + "skip_contact": "Kontakti: {count}", + "skip_calendar": "Kalendāra vienumi: {count}", + "skip_other": "Citi vienumi: {count}", + "summary_failed": "Neizdevās importēt: {count}", + "failure_invalid": "nav derīgs ziņojums", + "untitled": "(bez temata)", + "cancelled_hint": "Lai turpinātu vēlāk, vēlreiz importējiet tos pašus failus tajā pašā mapē.", + "close": "Aizvērt" + }, "recover_deleted": { "tab": "Atjaunot dzēstos vienumus", "title": "Atjaunot dzēstos vienumus", diff --git a/locales/mn/common.json b/locales/mn/common.json index 015e1f720..bccb42c7d 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "Нэгдсэн харагдац дээр хайлт ашиглах боломжгүй" }, + "mail_import": { + "menu_import": "Шуудан импортлох…", + "settings_label": "Шуудан импортлох", + "settings_description": ".eml, ZIP, mbox эсвэл Outlook-ийн .pst файлаас шуудан оруулна.", + "settings_button": "Импортлох…", + "title": "Шуудан импортлох", + "description": ".eml, .zip, .mbox эсвэл Outlook-ийн .pst файл сонгоно уу.", + "choose_files": "Файл сонгох…", + "chosen_files": "Сонгосон файлууд", + "remove_file": "{name}-г хасах", + "format_eml": "Захидал", + "format_zip": "ZIP архив", + "format_mbox": "mbox файл", + "format_pst": "Outlook-ийн өгөгдлийн файл", + "target_label": "Импортлох газар", + "target_top": "Дээд түвшин", + "keep_folders": "Файлын хавтаснуудыг дахин үүсгэх", + "keep_folders_hint": "Ижил нэртэй хавтсыг дахин ашиглана. Дээд түвшний Ирсэн, Илгээсэн, Устгасан хавтаснууд таны өөрийнх рүү орно.", + "resume": "Эдгээр файлын сүүлийн импорт зогссон газраас үргэлжлүүлэх (аль хэдийн хийсэн захидал: {count})", + "quota_note": "Олон сервер нэг хэрэглэгчээс цагт ойролцоогоор 1000 захидал эсвэл 50 MB л хүлээн авдаг. Үүнээс том импорт түр зогсоод дараа нь өөрөө үргэлжилнэ.", + "start": "Импортлох", + "cancel": "Цуцлах", + "running_title": "Шуудан импортлож байна", + "progress_label": "Импортын явц", + "status_imported": "Импортолсон захидал: {count}", + "reading_file": "{name}-г уншиж байна", + "paused": "{time} хүртэл түр зогссон. Сервер цагт хамгийн ихдээ {files} байршуулалт эсвэл {size} хүлээн авна. Импорт өөрөө үргэлжилнэ.", + "paused_generic": "{time} хүртэл түр зогссон. Серверийн байршуулах хязгаарт хүрсэн. Импорт өөрөө үргэлжилнэ.", + "background": "Ард ажиллуулах", + "cancel_import": "Импортыг цуцлах", + "chip": "Шуудан импортлож байна: {percent}%", + "chip_paused": "Импорт {time} хүртэл түр зогссон", + "done_title": "Импорт дууслаа", + "cancelled_title": "Импорт цуцлагдлаа", + "failed_title": "Импорт зогслоо", + "summary_imported": "Импортолсон захидал: {count}", + "summary_folders": "Үүсгэсэн хавтас: {count}", + "summary_skipped": "Импортлоогүй:", + "skip_too_large": "Серверийн хүлээн авахаас том: {count}", + "skip_deleted": "Thunderbird-д устгасан: {count}", + "skip_embedded": "Өөр захидалд хавсаргасан захидал: {count}", + "skip_contact": "Харилцагчид: {count}", + "skip_calendar": "Хуанлийн зүйлс: {count}", + "skip_other": "Бусад зүйлс: {count}", + "summary_failed": "Импортлож чадсангүй: {count}", + "failure_invalid": "хүчинтэй захидал биш", + "untitled": "(гарчиггүй)", + "cancelled_hint": "Дараа үргэлжлүүлэхийн тулд ижил файлуудыг ижил хавтас руу дахин импортлоно уу.", + "close": "Хаах" + }, "recover_deleted": { "tab": "Устгасан зүйлсийг сэргээх", "title": "Устгасан зүйлсийг сэргээх", diff --git a/locales/nb/common.json b/locales/nb/common.json index bea1f8ef8..87a01f677 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "Søk er ikke tilgjengelig i den samlede visningen" }, + "mail_import": { + "menu_import": "Importer e-post…", + "settings_label": "Importer e-post", + "settings_description": "Hent inn e-post fra .eml-, ZIP-, mbox- eller Outlook-.pst-filer.", + "settings_button": "Importer…", + "title": "Importer e-post", + "description": "Velg .eml-, .zip-, .mbox- eller Outlook-.pst-filer.", + "choose_files": "Velg filer…", + "chosen_files": "Valgte filer", + "remove_file": "Fjern {name}", + "format_eml": "Melding", + "format_zip": "ZIP-arkiv", + "format_mbox": "mbox-fil", + "format_pst": "Outlook-datafil", + "target_label": "Importer til", + "target_top": "Øverste nivå", + "keep_folders": "Gjenskap mappene fra filen", + "keep_folders_hint": "Mapper med samme navn brukes på nytt. Innboks, Sendte elementer og Slettede elementer på øverste nivå havner i dine egne.", + "resume": "Fortsett der forrige import av disse filene stoppet (meldinger allerede ferdige: {count})", + "quota_note": "Mange servere tar bare imot rundt 1000 meldinger eller 50 MB i timen fra én bruker. En større import tar en pause og fortsetter så av seg selv.", + "start": "Importer", + "cancel": "Avbryt", + "running_title": "Importerer e-post", + "progress_label": "Fremdrift for importen", + "status_imported": "Importerte meldinger: {count}", + "reading_file": "Leser {name}", + "paused": "Satt på pause til {time}. Serveren tar imot høyst {files} opplastinger eller {size} i timen. Importen fortsetter av seg selv.", + "paused_generic": "Satt på pause til {time}. Serverens opplastingsgrense er nådd. Importen fortsetter av seg selv.", + "background": "Kjør i bakgrunnen", + "cancel_import": "Avbryt import", + "chip": "Importerer e-post: {percent} %", + "chip_paused": "Import satt på pause til {time}", + "done_title": "Import fullført", + "cancelled_title": "Import avbrutt", + "failed_title": "Import stoppet", + "summary_imported": "Importerte meldinger: {count}", + "summary_folders": "Opprettede mapper: {count}", + "summary_skipped": "Ikke importert:", + "skip_too_large": "Større enn serveren tillater: {count}", + "skip_deleted": "Slettet i Thunderbird: {count}", + "skip_embedded": "Meldinger lagt ved andre meldinger: {count}", + "skip_contact": "Kontakter: {count}", + "skip_calendar": "Kalenderelementer: {count}", + "skip_other": "Andre elementer: {count}", + "summary_failed": "Kunne ikke importeres: {count}", + "failure_invalid": "ikke en gyldig melding", + "untitled": "(uten emne)", + "cancelled_hint": "Importer de samme filene til samme mappe igjen for å fortsette senere.", + "close": "Lukk" + }, "recover_deleted": { "tab": "Gjenopprett slettede elementer", "title": "Gjenopprett slettede elementer", diff --git a/locales/nl/common.json b/locales/nl/common.json index cf24997fc..494aa0e9d 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "Zoeken is niet beschikbaar in de gecombineerde weergave" }, + "mail_import": { + "menu_import": "E-mail importeren…", + "settings_label": "E-mail importeren", + "settings_description": "Haal e-mail binnen uit .eml-, ZIP-, mbox- of Outlook-.pst-bestanden.", + "settings_button": "Importeren…", + "title": "E-mail importeren", + "description": "Kies .eml-, .zip-, .mbox- of Outlook-.pst-bestanden.", + "choose_files": "Bestanden kiezen…", + "chosen_files": "Gekozen bestanden", + "remove_file": "{name} verwijderen", + "format_eml": "Bericht", + "format_zip": "ZIP-archief", + "format_mbox": "mbox-bestand", + "format_pst": "Outlook-gegevensbestand", + "target_label": "Importeren in", + "target_top": "Hoogste niveau", + "keep_folders": "De mappen uit het bestand overnemen", + "keep_folders_hint": "Mappen met dezelfde naam worden opnieuw gebruikt. Postvak IN, Verzonden items en Verwijderde items op het hoogste niveau gaan naar die van u.", + "resume": "Doorgaan waar de laatste import van deze bestanden stopte (al verwerkte berichten: {count})", + "quota_note": "Veel servers nemen per gebruiker maar ongeveer 1000 berichten of 50 MB per uur aan. Een grotere import pauzeert en gaat daarna vanzelf verder.", + "start": "Importeren", + "cancel": "Annuleren", + "running_title": "E-mail wordt geïmporteerd", + "progress_label": "Voortgang van de import", + "status_imported": "Geïmporteerde berichten: {count}", + "reading_file": "{name} wordt gelezen", + "paused": "Gepauzeerd tot {time}. De server neemt hoogstens {files} uploads of {size} per uur aan. De import gaat vanzelf verder.", + "paused_generic": "Gepauzeerd tot {time}. De uploadlimiet van de server is bereikt. De import gaat vanzelf verder.", + "background": "Op de achtergrond verder", + "cancel_import": "Import annuleren", + "chip": "E-mail wordt geïmporteerd: {percent}%", + "chip_paused": "Import gepauzeerd tot {time}", + "done_title": "Import voltooid", + "cancelled_title": "Import geannuleerd", + "failed_title": "Import gestopt", + "summary_imported": "Geïmporteerde berichten: {count}", + "summary_folders": "Aangemaakte mappen: {count}", + "summary_skipped": "Niet geïmporteerd:", + "skip_too_large": "Groter dan de server toestaat: {count}", + "skip_deleted": "Verwijderd in Thunderbird: {count}", + "skip_embedded": "Berichten die aan andere berichten hangen: {count}", + "skip_contact": "Contacten: {count}", + "skip_calendar": "Agenda-items: {count}", + "skip_other": "Andere items: {count}", + "summary_failed": "Kon niet worden geïmporteerd: {count}", + "failure_invalid": "geen geldig bericht", + "untitled": "(geen onderwerp)", + "cancelled_hint": "Importeer later dezelfde bestanden opnieuw in dezelfde map om verder te gaan.", + "close": "Sluiten" + }, "recover_deleted": { "tab": "Verwijderde items herstellen", "title": "Verwijderde items herstellen", diff --git a/locales/pl/common.json b/locales/pl/common.json index eb67c8751..d44b6768b 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "Wyszukiwanie jest niedostępne w widoku ujednoliconym" }, + "mail_import": { + "menu_import": "Importuj pocztę…", + "settings_label": "Importuj pocztę", + "settings_description": "Przenieś pocztę z plików .eml, ZIP, mbox lub Outlook .pst.", + "settings_button": "Importuj…", + "title": "Importuj pocztę", + "description": "Wybierz pliki .eml, .zip, .mbox lub Outlook .pst.", + "choose_files": "Wybierz pliki…", + "chosen_files": "Wybrane pliki", + "remove_file": "Usuń {name}", + "format_eml": "Wiadomość", + "format_zip": "Archiwum ZIP", + "format_mbox": "Plik mbox", + "format_pst": "Plik danych programu Outlook", + "target_label": "Importuj do", + "target_top": "Najwyższy poziom", + "keep_folders": "Odtwórz foldery z pliku", + "keep_folders_hint": "Foldery o tej samej nazwie są używane ponownie. Skrzynka odbiorcza, Elementy wysłane i Elementy usunięte na najwyższym poziomie trafiają do Twoich własnych.", + "resume": "Kontynuuj tam, gdzie zatrzymał się ostatni import tych plików (wiadomości już zrobione: {count})", + "quota_note": "Wiele serwerów przyjmuje od jednego użytkownika tylko około 1000 wiadomości lub 50 MB na godzinę. Większy import robi przerwę, a potem sam działa dalej.", + "start": "Importuj", + "cancel": "Anuluj", + "running_title": "Importowanie poczty", + "progress_label": "Postęp importu", + "status_imported": "Zaimportowane wiadomości: {count}", + "reading_file": "Odczytywanie {name}", + "paused": "Wstrzymano do {time}. Serwer przyjmuje najwyżej {files} przesłań lub {size} na godzinę. Import sam działa dalej.", + "paused_generic": "Wstrzymano do {time}. Osiągnięto limit przesyłania serwera. Import sam działa dalej.", + "background": "Działaj w tle", + "cancel_import": "Anuluj import", + "chip": "Importowanie poczty: {percent}%", + "chip_paused": "Import wstrzymany do {time}", + "done_title": "Import zakończony", + "cancelled_title": "Import anulowany", + "failed_title": "Import zatrzymany", + "summary_imported": "Zaimportowane wiadomości: {count}", + "summary_folders": "Utworzone foldery: {count}", + "summary_skipped": "Nie zaimportowano:", + "skip_too_large": "Większe, niż przyjmuje serwer: {count}", + "skip_deleted": "Usunięte w Thunderbirdzie: {count}", + "skip_embedded": "Wiadomości dołączone do innych wiadomości: {count}", + "skip_contact": "Kontakty: {count}", + "skip_calendar": "Elementy kalendarza: {count}", + "skip_other": "Inne elementy: {count}", + "summary_failed": "Nie udało się zaimportować: {count}", + "failure_invalid": "to nie jest poprawna wiadomość", + "untitled": "(bez tematu)", + "cancelled_hint": "Aby kontynuować później, zaimportuj te same pliki ponownie do tego samego folderu.", + "close": "Zamknij" + }, "recover_deleted": { "tab": "Odzyskaj usunięte elementy", "title": "Odzyskaj usunięte elementy", diff --git a/locales/pt/common.json b/locales/pt/common.json index a4b70cef5..c9f6357dc 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "A pesquisa não está disponível na vista unificada" }, + "mail_import": { + "menu_import": "Importar e-mails…", + "settings_label": "Importar e-mails", + "settings_description": "Trazer e-mails de arquivos .eml, ZIP, mbox ou Outlook .pst.", + "settings_button": "Importar…", + "title": "Importar e-mails", + "description": "Escolha arquivos .eml, .zip, .mbox ou Outlook .pst.", + "choose_files": "Escolher arquivos…", + "chosen_files": "Arquivos escolhidos", + "remove_file": "Remover {name}", + "format_eml": "Mensagem", + "format_zip": "Arquivo ZIP", + "format_mbox": "Arquivo mbox", + "format_pst": "Arquivo de dados do Outlook", + "target_label": "Importar para", + "target_top": "Nível superior", + "keep_folders": "Recriar as pastas do arquivo", + "keep_folders_hint": "Pastas com o mesmo nome são reutilizadas. Caixa de entrada, Itens enviados e Itens excluídos no nível superior vão para as suas.", + "resume": "Continuar de onde a última importação destes arquivos parou (mensagens já processadas: {count})", + "quota_note": "Muitos servidores aceitam só cerca de 1000 mensagens ou 50 MB por hora de cada usuário. Uma importação maior faz uma pausa e depois continua sozinha.", + "start": "Importar", + "cancel": "Cancelar", + "running_title": "Importando e-mails", + "progress_label": "Progresso da importação", + "status_imported": "Mensagens importadas: {count}", + "reading_file": "Lendo {name}", + "paused": "Pausado até {time}. O servidor aceita no máximo {files} envios ou {size} por hora. A importação continua sozinha.", + "paused_generic": "Pausado até {time}. O limite de envio do servidor foi atingido. A importação continua sozinha.", + "background": "Continuar em segundo plano", + "cancel_import": "Cancelar importação", + "chip": "Importando e-mails: {percent}%", + "chip_paused": "Importação pausada até {time}", + "done_title": "Importação concluída", + "cancelled_title": "Importação cancelada", + "failed_title": "Importação interrompida", + "summary_imported": "Mensagens importadas: {count}", + "summary_folders": "Pastas criadas: {count}", + "summary_skipped": "Não importados:", + "skip_too_large": "Maiores do que o servidor aceita: {count}", + "skip_deleted": "Excluídos no Thunderbird: {count}", + "skip_embedded": "Mensagens anexadas a outras mensagens: {count}", + "skip_contact": "Contatos: {count}", + "skip_calendar": "Itens de calendário: {count}", + "skip_other": "Outros itens: {count}", + "summary_failed": "Não foi possível importar: {count}", + "failure_invalid": "não é uma mensagem válida", + "untitled": "(sem assunto)", + "cancelled_hint": "Para continuar mais tarde, importe os mesmos arquivos na mesma pasta outra vez.", + "close": "Fechar" + }, "recover_deleted": { "tab": "Recuperar itens excluídos", "title": "Recuperar itens excluídos", diff --git a/locales/ro/common.json b/locales/ro/common.json index ffdd16db2..93e0a1ccc 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "Funcția de căutare nu este disponibilă în vizualizarea unificată" }, + "mail_import": { + "menu_import": "Importă e-mailuri…", + "settings_label": "Importă e-mailuri", + "settings_description": "Aduceți e-mailuri din fișiere .eml, ZIP, mbox sau Outlook .pst.", + "settings_button": "Importă…", + "title": "Importă e-mailuri", + "description": "Alegeți fișiere .eml, .zip, .mbox sau Outlook .pst.", + "choose_files": "Alege fișiere…", + "chosen_files": "Fișiere alese", + "remove_file": "Elimină {name}", + "format_eml": "Mesaj", + "format_zip": "Arhivă ZIP", + "format_mbox": "Fișier mbox", + "format_pst": "Fișier de date Outlook", + "target_label": "Importă în", + "target_top": "Nivelul de sus", + "keep_folders": "Recreează dosarele din fișier", + "keep_folders_hint": "Dosarele cu același nume sunt refolosite. Inbox, Elemente trimise și Elemente șterse de la nivelul de sus ajung în ale dumneavoastră.", + "resume": "Continuă de unde s-a oprit ultimul import al acestor fișiere (mesaje deja procesate: {count})", + "quota_note": "Multe servere acceptă de la un utilizator doar circa 1000 de mesaje sau 50 MB pe oră. Un import mai mare face o pauză și apoi continuă singur.", + "start": "Importă", + "cancel": "Anulează", + "running_title": "Se importă e-mailurile", + "progress_label": "Progresul importului", + "status_imported": "Mesaje importate: {count}", + "reading_file": "Se citește {name}", + "paused": "În pauză până la {time}. Serverul acceptă cel mult {files} încărcări sau {size} pe oră. Importul continuă singur.", + "paused_generic": "În pauză până la {time}. S-a atins limita de încărcare a serverului. Importul continuă singur.", + "background": "Rulează în fundal", + "cancel_import": "Anulează importul", + "chip": "Se importă e-mailurile: {percent}%", + "chip_paused": "Import în pauză până la {time}", + "done_title": "Import încheiat", + "cancelled_title": "Import anulat", + "failed_title": "Import oprit", + "summary_imported": "Mesaje importate: {count}", + "summary_folders": "Dosare create: {count}", + "summary_skipped": "Neimportate:", + "skip_too_large": "Mai mari decât acceptă serverul: {count}", + "skip_deleted": "Șterse în Thunderbird: {count}", + "skip_embedded": "Mesaje atașate la alte mesaje: {count}", + "skip_contact": "Contacte: {count}", + "skip_calendar": "Elemente de calendar: {count}", + "skip_other": "Alte elemente: {count}", + "summary_failed": "Nu s-au putut importa: {count}", + "failure_invalid": "nu este un mesaj valid", + "untitled": "(fără subiect)", + "cancelled_hint": "Pentru a continua mai târziu, importați din nou aceleași fișiere în același dosar.", + "close": "Închide" + }, "recover_deleted": { "tab": "Recuperați elementele șterse", "title": "Recuperați elementele șterse", diff --git a/locales/ru/common.json b/locales/ru/common.json index 9061aba90..747c4f1dd 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "Поиск недоступен в объединённом представлении" }, + "mail_import": { + "menu_import": "Импортировать почту…", + "settings_label": "Импортировать почту", + "settings_description": "Перенесите почту из файлов .eml, ZIP, mbox или Outlook .pst.", + "settings_button": "Импортировать…", + "title": "Импорт почты", + "description": "Выберите файлы .eml, .zip, .mbox или Outlook .pst.", + "choose_files": "Выбрать файлы…", + "chosen_files": "Выбранные файлы", + "remove_file": "Убрать {name}", + "format_eml": "Сообщение", + "format_zip": "Архив ZIP", + "format_mbox": "Файл mbox", + "format_pst": "Файл данных Outlook", + "target_label": "Импортировать в", + "target_top": "Верхний уровень", + "keep_folders": "Воссоздать папки из файла", + "keep_folders_hint": "Папки с тем же именем используются повторно. Входящие, Отправленные и Удалённые на верхнем уровне попадают в ваши собственные.", + "resume": "Продолжить с места, где остановился прошлый импорт этих файлов (уже обработано сообщений: {count})", + "quota_note": "Многие серверы принимают от одного пользователя лишь около 1000 сообщений или 50 МБ в час. Большой импорт приостанавливается и затем продолжается сам.", + "start": "Импортировать", + "cancel": "Отмена", + "running_title": "Импорт почты", + "progress_label": "Ход импорта", + "status_imported": "Импортировано сообщений: {count}", + "reading_file": "Чтение {name}", + "paused": "Пауза до {time}. Сервер принимает не более {files} загрузок или {size} в час. Импорт продолжится сам.", + "paused_generic": "Пауза до {time}. Достигнут лимит загрузок сервера. Импорт продолжится сам.", + "background": "Продолжить в фоне", + "cancel_import": "Отменить импорт", + "chip": "Импорт почты: {percent} %", + "chip_paused": "Импорт приостановлен до {time}", + "done_title": "Импорт завершён", + "cancelled_title": "Импорт отменён", + "failed_title": "Импорт остановлен", + "summary_imported": "Импортировано сообщений: {count}", + "summary_folders": "Создано папок: {count}", + "summary_skipped": "Не импортировано:", + "skip_too_large": "Больше, чем принимает сервер: {count}", + "skip_deleted": "Удалено в Thunderbird: {count}", + "skip_embedded": "Сообщения, вложенные в другие сообщения: {count}", + "skip_contact": "Контакты: {count}", + "skip_calendar": "Элементы календаря: {count}", + "skip_other": "Другие элементы: {count}", + "summary_failed": "Не удалось импортировать: {count}", + "failure_invalid": "недопустимое сообщение", + "untitled": "(без темы)", + "cancelled_hint": "Чтобы продолжить позже, снова импортируйте те же файлы в ту же папку.", + "close": "Закрыть" + }, "recover_deleted": { "tab": "Восстановить удалённое", "title": "Восстановить удалённое", diff --git a/locales/sk/common.json b/locales/sk/common.json index 28f6b8cd0..6e0fc7ec4 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "Vyhľadávanie nie je v zjednotenom zobrazení k dispozícii" }, + "mail_import": { + "menu_import": "Importovať poštu…", + "settings_label": "Importovať poštu", + "settings_description": "Preneste poštu zo súborov .eml, ZIP, mbox alebo Outlook .pst.", + "settings_button": "Importovať…", + "title": "Importovať poštu", + "description": "Vyberte súbory .eml, .zip, .mbox alebo Outlook .pst.", + "choose_files": "Vybrať súbory…", + "chosen_files": "Vybrané súbory", + "remove_file": "Odstrániť {name}", + "format_eml": "Správa", + "format_zip": "Archív ZIP", + "format_mbox": "Súbor mbox", + "format_pst": "Dátový súbor Outlooku", + "target_label": "Importovať do", + "target_top": "Najvyššia úroveň", + "keep_folders": "Znovu vytvoriť priečinky zo súboru", + "keep_folders_hint": "Priečinky s rovnakým názvom sa použijú znova. Doručená pošta, Odoslaná pošta a Odstránená pošta na najvyššej úrovni skončia vo vašich vlastných.", + "resume": "Pokračovať tam, kde sa posledný import týchto súborov zastavil (už hotové správy: {count})", + "quota_note": "Mnohé servery prijímajú od jedného používateľa len asi 1000 správ alebo 50 MB za hodinu. Väčší import sa pozastaví a potom pokračuje sám.", + "start": "Importovať", + "cancel": "Zrušiť", + "running_title": "Importuje sa pošta", + "progress_label": "Priebeh importu", + "status_imported": "Importované správy: {count}", + "reading_file": "Číta sa {name}", + "paused": "Pozastavené do {time}. Server prijíma najviac {files} nahraní alebo {size} za hodinu. Import pokračuje sám.", + "paused_generic": "Pozastavené do {time}. Dosiahol sa limit nahrávania servera. Import pokračuje sám.", + "background": "Spustiť na pozadí", + "cancel_import": "Zrušiť import", + "chip": "Importuje sa pošta: {percent} %", + "chip_paused": "Import pozastavený do {time}", + "done_title": "Import dokončený", + "cancelled_title": "Import zrušený", + "failed_title": "Import zastavený", + "summary_imported": "Importované správy: {count}", + "summary_folders": "Vytvorené priečinky: {count}", + "summary_skipped": "Neimportované:", + "skip_too_large": "Väčšie, než server prijme: {count}", + "skip_deleted": "Odstránené v Thunderbirde: {count}", + "skip_embedded": "Správy priložené k iným správam: {count}", + "skip_contact": "Kontakty: {count}", + "skip_calendar": "Položky kalendára: {count}", + "skip_other": "Iné položky: {count}", + "summary_failed": "Nedá sa importovať: {count}", + "failure_invalid": "nie je platná správa", + "untitled": "(bez predmetu)", + "cancelled_hint": "Ak chcete pokračovať neskôr, importujte rovnaké súbory znova do rovnakého priečinka.", + "close": "Zavrieť" + }, "recover_deleted": { "tab": "Obnoviť odstránené položky", "title": "Obnoviť odstránené položky", diff --git a/locales/th/common.json b/locales/th/common.json index 60ccca46c..4b789fbdf 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -397,6 +397,56 @@ "unified_mailbox": { "search_unavailable": "ไม่สามารถค้นหาในมุมมองรวมศูนย์ได้" }, + "mail_import": { + "menu_import": "นำเข้าอีเมล…", + "settings_label": "นำเข้าอีเมล", + "settings_description": "นำอีเมลเข้าจากไฟล์ .eml, ZIP, mbox หรือ .pst ของ Outlook", + "settings_button": "นำเข้า…", + "title": "นำเข้าอีเมล", + "description": "เลือกไฟล์ .eml, .zip, .mbox หรือ .pst ของ Outlook", + "choose_files": "เลือกไฟล์…", + "chosen_files": "ไฟล์ที่เลือก", + "remove_file": "เอา {name} ออก", + "format_eml": "ข้อความ", + "format_zip": "ไฟล์บีบอัด ZIP", + "format_mbox": "ไฟล์ mbox", + "format_pst": "ไฟล์ข้อมูล Outlook", + "target_label": "นำเข้าไปที่", + "target_top": "ระดับบนสุด", + "keep_folders": "สร้างโฟลเดอร์จากไฟล์ขึ้นใหม่", + "keep_folders_hint": "โฟลเดอร์ที่ชื่อเดียวกันจะถูกใช้ซ้ำ กล่องจดหมายเข้า รายการที่ส่ง และรายการที่ลบในระดับบนสุดจะเข้าไปในโฟลเดอร์ของคุณเอง", + "resume": "ทำต่อจากจุดที่การนำเข้าไฟล์เหล่านี้ครั้งล่าสุดหยุดไว้ (ข้อความที่ทำแล้ว: {count})", + "quota_note": "เซิร์ฟเวอร์หลายแห่งรับจากผู้ใช้หนึ่งคนได้เพียงประมาณ 1000 ข้อความหรือ 50 MB ต่อชั่วโมง การนำเข้าที่ใหญ่กว่านี้จะหยุดพักแล้วทำต่อเอง", + "start": "นำเข้า", + "cancel": "ยกเลิก", + "running_title": "กำลังนำเข้าอีเมล", + "progress_label": "ความคืบหน้าการนำเข้า", + "status_imported": "ข้อความที่นำเข้าแล้ว: {count}", + "reading_file": "กำลังอ่าน {name}", + "paused": "หยุดพักถึง {time} เซิร์ฟเวอร์รับการอัปโหลดได้สูงสุด {files} ครั้งหรือ {size} ต่อชั่วโมง การนำเข้าจะทำต่อเอง", + "paused_generic": "หยุดพักถึง {time} ถึงขีดจำกัดการอัปโหลดของเซิร์ฟเวอร์แล้ว การนำเข้าจะทำต่อเอง", + "background": "ทำงานเบื้องหลัง", + "cancel_import": "ยกเลิกการนำเข้า", + "chip": "กำลังนำเข้าอีเมล: {percent}%", + "chip_paused": "การนำเข้าหยุดพักถึง {time}", + "done_title": "นำเข้าเสร็จแล้ว", + "cancelled_title": "ยกเลิกการนำเข้าแล้ว", + "failed_title": "การนำเข้าหยุดลง", + "summary_imported": "ข้อความที่นำเข้าแล้ว: {count}", + "summary_folders": "โฟลเดอร์ที่สร้าง: {count}", + "summary_skipped": "ไม่ได้นำเข้า:", + "skip_too_large": "ใหญ่กว่าที่เซิร์ฟเวอร์รับได้: {count}", + "skip_deleted": "ถูกลบใน Thunderbird: {count}", + "skip_embedded": "ข้อความที่แนบอยู่ในข้อความอื่น: {count}", + "skip_contact": "รายชื่อติดต่อ: {count}", + "skip_calendar": "รายการปฏิทิน: {count}", + "skip_other": "รายการอื่น: {count}", + "summary_failed": "นำเข้าไม่ได้: {count}", + "failure_invalid": "ไม่ใช่ข้อความที่ถูกต้อง", + "untitled": "(ไม่มีหัวเรื่อง)", + "cancelled_hint": "หากต้องการทำต่อภายหลัง ให้นำเข้าไฟล์เดิมไปยังโฟลเดอร์เดิมอีกครั้ง", + "close": "ปิด" + }, "recover_deleted": { "tab": "กู้คืนรายการที่ลบ", "title": "กู้คืนรายการที่ลบ", diff --git a/locales/tr/common.json b/locales/tr/common.json index 46613e997..4dbf796a0 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "Birleşik görünümde arama kullanılamaz" }, + "mail_import": { + "menu_import": "E-posta içe aktar…", + "settings_label": "E-posta içe aktar", + "settings_description": ".eml, ZIP, mbox veya Outlook .pst dosyalarından e-posta alın.", + "settings_button": "İçe aktar…", + "title": "E-posta içe aktar", + "description": ".eml, .zip, .mbox veya Outlook .pst dosyalarını seçin.", + "choose_files": "Dosya seç…", + "chosen_files": "Seçilen dosyalar", + "remove_file": "{name} öğesini kaldır", + "format_eml": "İleti", + "format_zip": "ZIP arşivi", + "format_mbox": "mbox dosyası", + "format_pst": "Outlook veri dosyası", + "target_label": "Şuraya aktar", + "target_top": "En üst düzey", + "keep_folders": "Dosyadaki klasörleri yeniden oluştur", + "keep_folders_hint": "Aynı adlı klasörler yeniden kullanılır. En üst düzeydeki Gelen Kutusu, Gönderilmiş Öğeler ve Silinmiş Öğeler sizinkilere gider.", + "resume": "Bu dosyaların son içe aktarımının durduğu yerden devam et (zaten işlenen iletiler: {count})", + "quota_note": "Birçok sunucu bir kullanıcıdan saatte yalnızca yaklaşık 1000 ileti veya 50 MB kabul eder. Daha büyük bir içe aktarım duraklar ve sonra kendiliğinden devam eder.", + "start": "İçe aktar", + "cancel": "İptal", + "running_title": "E-posta içe aktarılıyor", + "progress_label": "İçe aktarma ilerlemesi", + "status_imported": "İçe aktarılan iletiler: {count}", + "reading_file": "{name} okunuyor", + "paused": "{time} saatine kadar duraklatıldı. Sunucu saatte en fazla {files} yükleme veya {size} kabul ediyor. İçe aktarım kendiliğinden devam eder.", + "paused_generic": "{time} saatine kadar duraklatıldı. Sunucunun yükleme sınırına ulaşıldı. İçe aktarım kendiliğinden devam eder.", + "background": "Arka planda çalıştır", + "cancel_import": "İçe aktarımı iptal et", + "chip": "E-posta içe aktarılıyor: %{percent}", + "chip_paused": "İçe aktarım {time} saatine kadar duraklatıldı", + "done_title": "İçe aktarım tamamlandı", + "cancelled_title": "İçe aktarım iptal edildi", + "failed_title": "İçe aktarım durdu", + "summary_imported": "İçe aktarılan iletiler: {count}", + "summary_folders": "Oluşturulan klasörler: {count}", + "summary_skipped": "İçe aktarılmayanlar:", + "skip_too_large": "Sunucunun kabul ettiğinden büyük: {count}", + "skip_deleted": "Thunderbird'de silinmiş: {count}", + "skip_embedded": "Başka iletilere ekli iletiler: {count}", + "skip_contact": "Kişiler: {count}", + "skip_calendar": "Takvim öğeleri: {count}", + "skip_other": "Diğer öğeler: {count}", + "summary_failed": "İçe aktarılamadı: {count}", + "failure_invalid": "geçerli bir ileti değil", + "untitled": "(konu yok)", + "cancelled_hint": "Daha sonra devam etmek için aynı dosyaları aynı klasöre yeniden içe aktarın.", + "close": "Kapat" + }, "recover_deleted": { "tab": "Silinen öğeleri kurtar", "title": "Silinen öğeleri kurtar", diff --git a/locales/uk/common.json b/locales/uk/common.json index 6c1397a83..dfa3fd6a4 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "Пошук недоступний в об'єднаному перегляді" }, + "mail_import": { + "menu_import": "Імпортувати пошту…", + "settings_label": "Імпортувати пошту", + "settings_description": "Перенесіть пошту з файлів .eml, ZIP, mbox або Outlook .pst.", + "settings_button": "Імпортувати…", + "title": "Імпорт пошти", + "description": "Виберіть файли .eml, .zip, .mbox або Outlook .pst.", + "choose_files": "Вибрати файли…", + "chosen_files": "Вибрані файли", + "remove_file": "Прибрати {name}", + "format_eml": "Повідомлення", + "format_zip": "Архів ZIP", + "format_mbox": "Файл mbox", + "format_pst": "Файл даних Outlook", + "target_label": "Імпортувати до", + "target_top": "Верхній рівень", + "keep_folders": "Відтворити теки з файлу", + "keep_folders_hint": "Теки з тією самою назвою використовуються повторно. Вхідні, Надіслані та Видалені на верхньому рівні потрапляють до ваших власних.", + "resume": "Продовжити з місця, де зупинився останній імпорт цих файлів (уже оброблено повідомлень: {count})", + "quota_note": "Багато серверів приймають від одного користувача лише близько 1000 повідомлень або 50 МБ на годину. Більший імпорт призупиняється, а потім продовжується сам.", + "start": "Імпортувати", + "cancel": "Скасувати", + "running_title": "Імпорт пошти", + "progress_label": "Перебіг імпорту", + "status_imported": "Імпортовано повідомлень: {count}", + "reading_file": "Читання {name}", + "paused": "Пауза до {time}. Сервер приймає щонайбільше {files} завантажень або {size} на годину. Імпорт продовжиться сам.", + "paused_generic": "Пауза до {time}. Досягнуто ліміту завантажень сервера. Імпорт продовжиться сам.", + "background": "Продовжити у фоні", + "cancel_import": "Скасувати імпорт", + "chip": "Імпорт пошти: {percent} %", + "chip_paused": "Імпорт призупинено до {time}", + "done_title": "Імпорт завершено", + "cancelled_title": "Імпорт скасовано", + "failed_title": "Імпорт зупинено", + "summary_imported": "Імпортовано повідомлень: {count}", + "summary_folders": "Створено тек: {count}", + "summary_skipped": "Не імпортовано:", + "skip_too_large": "Більші, ніж приймає сервер: {count}", + "skip_deleted": "Видалені в Thunderbird: {count}", + "skip_embedded": "Повідомлення, вкладені в інші повідомлення: {count}", + "skip_contact": "Контакти: {count}", + "skip_calendar": "Елементи календаря: {count}", + "skip_other": "Інші елементи: {count}", + "summary_failed": "Не вдалося імпортувати: {count}", + "failure_invalid": "недійсне повідомлення", + "untitled": "(без теми)", + "cancelled_hint": "Щоб продовжити пізніше, знову імпортуйте ті самі файли до тієї самої теки.", + "close": "Закрити" + }, "recover_deleted": { "tab": "Відновити видалене", "title": "Відновити видалене", diff --git a/locales/vi/common.json b/locales/vi/common.json index 30fac8a94..f6610f6a5 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -5392,6 +5392,56 @@ "unified_mailbox": { "search_unavailable": "Tìm kiếm không có sẵn trong chế độ xem hợp nhất" }, + "mail_import": { + "menu_import": "Nhập thư…", + "settings_label": "Nhập thư", + "settings_description": "Đưa thư vào từ tệp .eml, ZIP, mbox hoặc .pst của Outlook.", + "settings_button": "Nhập…", + "title": "Nhập thư", + "description": "Chọn tệp .eml, .zip, .mbox hoặc .pst của Outlook.", + "choose_files": "Chọn tệp…", + "chosen_files": "Tệp đã chọn", + "remove_file": "Bỏ {name}", + "format_eml": "Thư", + "format_zip": "Tệp nén ZIP", + "format_mbox": "Tệp mbox", + "format_pst": "Tệp dữ liệu Outlook", + "target_label": "Nhập vào", + "target_top": "Cấp cao nhất", + "keep_folders": "Tạo lại các thư mục từ tệp", + "keep_folders_hint": "Thư mục cùng tên sẽ được dùng lại. Hộp thư đến, Thư đã gửi và Thư đã xóa ở cấp cao nhất sẽ vào thư mục của bạn.", + "resume": "Tiếp tục từ chỗ lần nhập trước của các tệp này dừng lại (thư đã xử lý: {count})", + "quota_note": "Nhiều máy chủ chỉ nhận khoảng 1000 thư hoặc 50 MB mỗi giờ từ một người dùng. Lần nhập lớn hơn sẽ tạm dừng rồi tự tiếp tục.", + "start": "Nhập", + "cancel": "Hủy", + "running_title": "Đang nhập thư", + "progress_label": "Tiến độ nhập", + "status_imported": "Thư đã nhập: {count}", + "reading_file": "Đang đọc {name}", + "paused": "Tạm dừng đến {time}. Máy chủ nhận tối đa {files} lượt tải lên hoặc {size} mỗi giờ. Việc nhập sẽ tự tiếp tục.", + "paused_generic": "Tạm dừng đến {time}. Đã chạm giới hạn tải lên của máy chủ. Việc nhập sẽ tự tiếp tục.", + "background": "Chạy trong nền", + "cancel_import": "Hủy nhập", + "chip": "Đang nhập thư: {percent}%", + "chip_paused": "Việc nhập tạm dừng đến {time}", + "done_title": "Đã nhập xong", + "cancelled_title": "Đã hủy nhập", + "failed_title": "Việc nhập đã dừng", + "summary_imported": "Thư đã nhập: {count}", + "summary_folders": "Thư mục đã tạo: {count}", + "summary_skipped": "Không nhập:", + "skip_too_large": "Lớn hơn mức máy chủ nhận: {count}", + "skip_deleted": "Đã xóa trong Thunderbird: {count}", + "skip_embedded": "Thư đính kèm trong thư khác: {count}", + "skip_contact": "Danh bạ: {count}", + "skip_calendar": "Mục lịch: {count}", + "skip_other": "Mục khác: {count}", + "summary_failed": "Không nhập được: {count}", + "failure_invalid": "không phải thư hợp lệ", + "untitled": "(không có tiêu đề)", + "cancelled_hint": "Để tiếp tục sau, hãy nhập lại cùng các tệp vào cùng thư mục.", + "close": "Đóng" + }, "recover_deleted": { "tab": "Khôi phục các mục đã xóa", "title": "Khôi phục các mục đã xóa", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 1c8ada23d..6c61c7c9c 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -5380,6 +5380,56 @@ "unified_mailbox": { "search_unavailable": "整合檢視中無法使用搜尋" }, + "mail_import": { + "menu_import": "匯入郵件…", + "settings_label": "匯入郵件", + "settings_description": "從 .eml、ZIP、mbox 或 Outlook .pst 檔案匯入郵件。", + "settings_button": "匯入…", + "title": "匯入郵件", + "description": "請選擇 .eml、.zip、.mbox 或 Outlook .pst 檔案。", + "choose_files": "選擇檔案…", + "chosen_files": "已選檔案", + "remove_file": "移除 {name}", + "format_eml": "郵件", + "format_zip": "ZIP 壓縮檔", + "format_mbox": "mbox 檔案", + "format_pst": "Outlook 資料檔", + "target_label": "匯入到", + "target_top": "最上層", + "keep_folders": "依檔案重建資料夾", + "keep_folders_hint": "同名資料夾會重複使用。最上層的收件匣、寄件備份和刪除的郵件會進入您自己的資料夾。", + "resume": "從這些檔案上次匯入停止的地方繼續(已處理的郵件:{count})", + "quota_note": "許多伺服器每小時只接受單一使用者約 1000 封郵件或 50 MB。較大的匯入會暫停,然後自動繼續。", + "start": "匯入", + "cancel": "取消", + "running_title": "正在匯入郵件", + "progress_label": "匯入進度", + "status_imported": "已匯入郵件:{count}", + "reading_file": "正在讀取 {name}", + "paused": "已暫停至 {time}。伺服器每小時最多接受 {files} 次上傳或 {size}。匯入會自動繼續。", + "paused_generic": "已暫停至 {time}。已達伺服器的上傳上限。匯入會自動繼續。", + "background": "在背景執行", + "cancel_import": "取消匯入", + "chip": "正在匯入郵件:{percent}%", + "chip_paused": "匯入已暫停至 {time}", + "done_title": "匯入完成", + "cancelled_title": "匯入已取消", + "failed_title": "匯入已停止", + "summary_imported": "已匯入郵件:{count}", + "summary_folders": "已建立資料夾:{count}", + "summary_skipped": "未匯入:", + "skip_too_large": "超過伺服器允許的大小:{count}", + "skip_deleted": "已在 Thunderbird 中刪除:{count}", + "skip_embedded": "附在其他郵件中的郵件:{count}", + "skip_contact": "聯絡人:{count}", + "skip_calendar": "行事曆項目:{count}", + "skip_other": "其他項目:{count}", + "summary_failed": "無法匯入:{count}", + "failure_invalid": "不是有效的郵件", + "untitled": "(無主旨)", + "cancelled_hint": "如需稍後繼續,請再次把相同的檔案匯入到同一個資料夾。", + "close": "關閉" + }, "recover_deleted": { "tab": "復原已刪除的項目", "title": "復原已刪除的項目", diff --git a/locales/zh/common.json b/locales/zh/common.json index 0c5b088d5..47bb2946d 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -5250,6 +5250,56 @@ "unified_mailbox": { "search_unavailable": "统一视图中无法使用搜索" }, + "mail_import": { + "menu_import": "导入邮件…", + "settings_label": "导入邮件", + "settings_description": "从 .eml、ZIP、mbox 或 Outlook .pst 文件导入邮件。", + "settings_button": "导入…", + "title": "导入邮件", + "description": "请选择 .eml、.zip、.mbox 或 Outlook .pst 文件。", + "choose_files": "选择文件…", + "chosen_files": "已选文件", + "remove_file": "移除 {name}", + "format_eml": "邮件", + "format_zip": "ZIP 压缩包", + "format_mbox": "mbox 文件", + "format_pst": "Outlook 数据文件", + "target_label": "导入到", + "target_top": "顶层", + "keep_folders": "按文件重建文件夹", + "keep_folders_hint": "同名文件夹会被重复使用。顶层的收件箱、已发送邮件和已删除邮件会进入您自己的文件夹。", + "resume": "从这些文件上次导入停止的地方继续(已处理的邮件:{count})", + "quota_note": "很多服务器每小时只接受单个用户约 1000 封邮件或 50 MB。更大的导入会暂停,然后自动继续。", + "start": "导入", + "cancel": "取消", + "running_title": "正在导入邮件", + "progress_label": "导入进度", + "status_imported": "已导入邮件:{count}", + "reading_file": "正在读取 {name}", + "paused": "已暂停至 {time}。服务器每小时最多接受 {files} 次上传或 {size}。导入会自动继续。", + "paused_generic": "已暂停至 {time}。已达到服务器的上传限制。导入会自动继续。", + "background": "在后台运行", + "cancel_import": "取消导入", + "chip": "正在导入邮件:{percent}%", + "chip_paused": "导入已暂停至 {time}", + "done_title": "导入完成", + "cancelled_title": "导入已取消", + "failed_title": "导入已停止", + "summary_imported": "已导入邮件:{count}", + "summary_folders": "已创建文件夹:{count}", + "summary_skipped": "未导入:", + "skip_too_large": "超过服务器允许的大小:{count}", + "skip_deleted": "已在 Thunderbird 中删除:{count}", + "skip_embedded": "附在其他邮件中的邮件:{count}", + "skip_contact": "联系人:{count}", + "skip_calendar": "日历项目:{count}", + "skip_other": "其他项目:{count}", + "summary_failed": "无法导入:{count}", + "failure_invalid": "不是有效的邮件", + "untitled": "(无主题)", + "cancelled_hint": "如需稍后继续,请再次把相同的文件导入到同一个文件夹。", + "close": "关闭" + }, "recover_deleted": { "tab": "恢复已删除项目", "title": "恢复已删除项目", diff --git a/package-lock.json b/package-lock.json index 878d8cb72..390986c08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "clsx": "^2.1.1", "date-fns": "^4.4.0", "dompurify": "^3.4.12", + "fflate": "0.8.3", "ioredis": "^5.11.1", "jalaali-js": "^2.0.1", "jszip": "^3.10.2", @@ -6142,7 +6143,6 @@ "version": "0.8.3", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", - "dev": true, "license": "MIT" }, "node_modules/file-entry-cache": { diff --git a/package.json b/package.json index 6f78a0579..0e355eb0e 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "clsx": "^2.1.1", "date-fns": "^4.4.0", "dompurify": "^3.4.12", + "fflate": "0.8.3", "ioredis": "^5.11.1", "jalaali-js": "^2.0.1", "jszip": "^3.10.2", diff --git a/stores/mail-transfer-store.ts b/stores/mail-transfer-store.ts new file mode 100644 index 000000000..dc840da50 --- /dev/null +++ b/stores/mail-transfer-store.ts @@ -0,0 +1,77 @@ +import { create } from "zustand"; +import type { ImportProgress } from "@/lib/mail-import/engine"; + +/** + * The mail import (and export) dialog: what it was opened for, whether it is + * on screen, and the job it runs. A job keeps running while the dialog is + * hidden ("Run in background"); a small chip then shows its progress and + * brings the dialog back. One job at a time. + */ + +export interface ImportRequest { + kind: "import"; + /** The folder it was opened from (its id in the mail store), if any. */ + mailboxId?: string; +} + +export type TransferRequest = ImportRequest; + +export interface TransferJob { + kind: "import"; + progress: ImportProgress; + controller: AbortController; +} + +interface MailTransferState { + request: TransferRequest | null; + visible: boolean; + job: TransferJob | null; + /** Opens the dialog; false when a job is running already (it is shown instead). */ + open: (request: TransferRequest) => boolean; + /** Hides the dialog; a running job goes on. */ + hide: () => void; + show: () => void; + /** Closes the dialog and forgets a finished job. */ + close: () => void; + startJob: (job: TransferJob) => void; + updateProgress: (progress: ImportProgress) => void; + cancelJob: () => void; +} + +export function isJobActive(job: TransferJob | null): boolean { + return !!job && (job.progress.phase === "running" || job.progress.phase === "paused"); +} + +export const useMailTransferStore = create()((set, get) => ({ + request: null, + visible: false, + job: null, + open: (request) => { + if (isJobActive(get().job)) { + set({ visible: true }); + return false; + } + set({ request, visible: true, job: null }); + return true; + }, + hide: () => set({ visible: false }), + show: () => set({ visible: true }), + close: () => { + if (isJobActive(get().job)) { + set({ visible: false }); + return; + } + set({ request: null, visible: false, job: null }); + }, + startJob: (job) => set({ job }), + updateProgress: (progress) => { + const job = get().job; + if (job) set({ job: { ...job, progress } }); + }, + cancelJob: () => get().job?.controller.abort(), +})); + +/** Opens the import dialog, for a folder or (from Settings) for none. */ +export function openMailImport(request: Omit = {}): void { + useMailTransferStore.getState().open({ kind: "import", ...request }); +} From a06281f23fa5ed3a81012ab289647f40b0329926 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:13:02 +0100 Subject: [PATCH 02/22] feat: import Outlook PST files into their folders --- .github/workflows/perf-guard.yml | 2 +- .gitignore | 2 + Dockerfile | 1 + .../mail-transfer/mail-import-dialog.tsx | 11 +- components/mail-transfer/start-import.ts | 4 + .../__tests__/fixtures/Outlook.pst | Bin 0 -> 1287168 bytes .../__tests__/fixtures/Outlook.pst.LICENSE | 27 ++ lib/mail-import/__tests__/pst-source.test.ts | 118 +++++++++ lib/mail-import/__tests__/pst.test.ts | 181 +++++++++++++ lib/mail-import/pst-source.ts | 114 +++++++++ lib/mail-import/pst/extract.ts | 241 ++++++++++++++++++ lib/mail-import/pst/items.ts | 87 +++++++ lib/mail-import/pst/mime.ts | 213 ++++++++++++++++ lib/mail-import/pst/shims/buffer.ts | 4 + lib/mail-import/pst/shims/fs.ts | 67 +++++ lib/mail-import/pst/shims/iconv.ts | 16 ++ lib/mail-import/pst/shims/zlib.ts | 9 + lib/mail-import/pst/worker.ts | 80 ++++++ locales/ar/common.json | 7 +- locales/ca/common.json | 7 +- locales/cs/common.json | 7 +- locales/da/common.json | 7 +- locales/de/common.json | 7 +- locales/en/common.json | 7 +- locales/es/common.json | 7 +- locales/et/common.json | 7 +- locales/fa/common.json | 7 +- locales/fr/common.json | 7 +- locales/he/common.json | 7 +- locales/hu/common.json | 7 +- locales/it/common.json | 7 +- locales/ja/common.json | 7 +- locales/ko/common.json | 7 +- locales/lv/common.json | 7 +- locales/mn/common.json | 7 +- locales/nb/common.json | 7 +- locales/nl/common.json | 7 +- locales/pl/common.json | 7 +- locales/pt/common.json | 7 +- locales/ro/common.json | 7 +- locales/ru/common.json | 7 +- locales/sk/common.json | 7 +- locales/th/common.json | 7 +- locales/tr/common.json | 7 +- locales/uk/common.json | 7 +- locales/vi/common.json | 7 +- locales/zh-TW/common.json | 7 +- locales/zh/common.json | 7 +- package-lock.json | 114 +++++++++ package.json | 6 +- scripts/build-pst-worker.mjs | 49 ++++ scripts/lite/build.mjs | 5 + 52 files changed, 1527 insertions(+), 34 deletions(-) create mode 100644 lib/mail-import/__tests__/fixtures/Outlook.pst create mode 100644 lib/mail-import/__tests__/fixtures/Outlook.pst.LICENSE create mode 100644 lib/mail-import/__tests__/pst-source.test.ts create mode 100644 lib/mail-import/__tests__/pst.test.ts create mode 100644 lib/mail-import/pst-source.ts create mode 100644 lib/mail-import/pst/extract.ts create mode 100644 lib/mail-import/pst/items.ts create mode 100644 lib/mail-import/pst/mime.ts create mode 100644 lib/mail-import/pst/shims/buffer.ts create mode 100644 lib/mail-import/pst/shims/fs.ts create mode 100644 lib/mail-import/pst/shims/iconv.ts create mode 100644 lib/mail-import/pst/shims/zlib.ts create mode 100644 lib/mail-import/pst/worker.ts create mode 100644 scripts/build-pst-worker.mjs diff --git a/.github/workflows/perf-guard.yml b/.github/workflows/perf-guard.yml index d3c299c52..1ff1264e4 100644 --- a/.github/workflows/perf-guard.yml +++ b/.github/workflows/perf-guard.yml @@ -41,7 +41,7 @@ jobs: DEV_MOCK_JMAP: "true" NEXT_TELEMETRY_DISABLED: "1" GIT_COMMIT: ${{ github.sha }} - run: node scripts/build-plugin-sandbox.mjs && npx next build --webpack + run: node scripts/build-pst-worker.mjs && node scripts/build-plugin-sandbox.mjs && npx next build --webpack - name: Run perf guard env: diff --git a/.gitignore b/.gitignore index a67d67b1c..24d233b38 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ /lite-image/ # scripts/build-plugin-sandbox.mjs /public/plugin-sandbox-assets/ +# scripts/build-pst-worker.mjs +/public/pst-worker.js # production /build diff --git a/Dockerfile b/Dockerfile index 515c30937..7485ced40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,7 @@ RUN rm -rf "app/api/dev-jmap" && \ find . -path ./node_modules -prune -o \ \( -name __tests__ -o -name "*.test.ts" -o -name "*.test.tsx" \) \ -prune -exec rm -rf {} \; && \ + node scripts/build-pst-worker.mjs && \ node scripts/build-plugin-sandbox.mjs && \ npx next build --webpack diff --git a/components/mail-transfer/mail-import-dialog.tsx b/components/mail-transfer/mail-import-dialog.tsx index a30507432..026fe1d00 100644 --- a/components/mail-transfer/mail-import-dialog.tsx +++ b/components/mail-transfer/mail-import-dialog.tsx @@ -232,6 +232,7 @@ export function MailImportDialog({ request }: { request: ImportRequest }) { )} + {picked.some((p) => p.format === "pst") &&

{t("pst_note")}

}

{t("quota_note")}

@@ -240,6 +241,14 @@ export function MailImportDialog({ request }: { request: ImportRequest }) { type T = ReturnType; +const PST_ERRORS = ["encrypted", "not_pst", "unreadable", "worker"]; + +/** The PST reader's errors in words; anything else as the server or browser said it. */ +function errorText(t: T, error: string): string { + const code = /^pst:(\w+):/.exec(error)?.[1]; + return code && PST_ERRORS.includes(code) ? t(`pst_error_${code}`) : error; +} + function progressTitle(t: T, p: ImportProgress): string { if (p.phase === "done") return t("done_title"); if (p.phase === "cancelled") return t("cancelled_title"); @@ -292,7 +301,7 @@ function ProgressBody({ progress: p, t, locale, statusId }: { progress: ImportPr )} {p.phase === "cancelled" &&

{t("cancelled_hint")}

} - {p.phase === "failed" && p.error &&

{p.error}

} + {p.phase === "failed" && p.error &&

{errorText(t, p.error)}

} )} diff --git a/components/mail-transfer/start-import.ts b/components/mail-transfer/start-import.ts index 9544bcd54..649f7cdcd 100644 --- a/components/mail-transfer/start-import.ts +++ b/components/mail-transfer/start-import.ts @@ -25,7 +25,10 @@ export function startImport(input: StartImportInput): void { controller, progress: { phase: "running", imported: 0, failed: 0, skipped: {}, failures: [], fraction: 0, foldersCreated: 0 }, }); + const hasPst = input.files.some((f) => f.format === "pst"); void (async () => { + // The PST reader (and its worker) load only for an Outlook file. + const openPst = hasPst ? (await import("@/lib/mail-import/pst-source")).openPstSource : undefined; const result = await runImport({ client, accountId: input.accountId, @@ -36,6 +39,7 @@ export function startImport(input: StartImportInput): void { resume: input.resume, signal: controller.signal, onProgress: (progress) => useMailTransferStore.getState().updateProgress(progress), + openPst, }); useMailTransferStore.getState().updateProgress(result); // Show what arrived, if the same account is still the one on screen. diff --git a/lib/mail-import/__tests__/fixtures/Outlook.pst b/lib/mail-import/__tests__/fixtures/Outlook.pst new file mode 100644 index 0000000000000000000000000000000000000000..6d0dae3b8f74d05366294736dfec3e24cc4242df GIT binary patch literal 1287168 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 z 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%0D1AZ_=~M9{=iRX;-4_k;K&2` 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�TJG1OIl){(TtDKN53Z!bKv zcD@4Gr<4pfRy*-ZM;1&nOY87hln}YH72%_@_rIt^$LXrPZ~cP9N0&y@#*mAffJH4I zXW@N*Ax6LY@P{SqXc8xnB^F>L7bVsVTKw~nqCDOxX>kOt79ILQdwBXq;ci6(R!rq+ z*)^GkV+EW6zK$-EZ@^W`LXU(ne+ZZ*Y0ka&Ai1QXLSWuA1h6F9#3U$1?LALSIRL5~ zDoWU&^fgG0QRN?Z`Qw?9y{(iD#HMUWsTG(WKXWCA0W`C$wPUo+ZKXYDDM!h?A*;$%cS)KHu#o zGf3W=ZBbyI<&oRJPd2{?{?TzCR%%X;LB&xf!hpatu$g%_F|uk1HhiDZVJkH^+b8v` z*jFE8Nn$DvV28H(pxSCb9>>~#d%&C0^|UyPd%fWt3+hYb?g`sbmf-yvb!@iRfA6ME z{ypiMK!mX&J8tXQ&0q07#~aVt{vzj_C8LTB)|L3}_4P9)c?HTGGbg#(Caw@G#N6upczbkf%)GXg?uER>Vx?wo zla9iO&})p~YC)Xh;>d_pi3OJ}L*1Uei^6l9>r5HJOc^mRgrNFN4=| z&DzB)mcluPDWsaS(Mbu7&U57IZ(_+-L{|D2tz#FOoc=;I2S~J)y<`&?I0;XmKF(IU zw>YHl61{B1Dj4`*|6eP0c!r@zU8rgwSc#_7P?C({^nm*lh_zug zn6Y!BXilk8zH%+n#3X+-OA63$Ty|LC7XPlXQ)c2Z%y^Nrz{ z)2{_|6bAyS@~rApCg7G`ozG4=C?LABCnpsT%|=^w z)3LB!gwISn8IY42GW4IxN^?h}>0RE&l}U(-*`}N=@cwSJBRBxWaVyFwsD%646jYY$#y<&6N%GEIuU>BL~^N>tq}#B59E*wFn%;LT#;(?GTi zsR`AUqByqgH9)_p;R-LzlWc8Cuqd~)P@IXw+VNx$>kIFAa6Vkjz7KuZTv%?CFBo5e zL@w_Ti0_dCK%LLl>8A zJm(TvuE^CPxAsRu@B6uls@j88#c4RD61TdC*?5kI*S#B#^LY%ErPX-wV(FyKh~w$T z3YrhP?d1m0e0-l7I7Q?X&y7P~B+I08?ENc$DnnDg%f85+y%5BfM>*9W}Z{$UTT2h^kb^%I!+iHbR%?4N+4um-s>-}Be%zzBPS z6F3{SM{4h95|+)Kl+J_Q+6pVi&3EH#Ue^(G+Q~P|L#>)DzTN?N&EuU6J-j~ferMb{ zXQy(5rCf~rYD{4hLYP!s5>I;cpmPZnB+m!j4bW$n_AY0GqdsIvG5CUrSpL!-fM>RS z?0{te(^j;7b=d5;N;jvC>x+P zYUsjMAL%k z0`KAjxH5If7J}ZX^?>xn4`FOPE0R{&(>;^SxesiLiIyn~PR?07mnb^&jM8DSd#Wi_ zIoRh$?;IvZu;tW^nTYPhn*%1!!8IGL8t$tFJ-|paGA}mqXbUkf->5>HmeX-cg{o6t&%?$ta1f3d)lN#Ps~o{VR!<#&WpA5 z67LHJCQ%t$QBi8JEUFa+Ew*N?v?=m^WbIUCWal0+)oY$lNGc(<-<)T2MGU3*KL@@a zM391Hi9d|Vtom>iPMMD2$&HwM$~KZoQwVJnCf`SVmSbHUo_SE{WCbgWzOySDtaSJo z#CJ@(*_7%vm|0^%3-JyWW#>8D`(CJMyprf_6g$6rIzDi+)Mu7D*lZw@(V@{2PW)k} zDNR+DnK~U5(ZF9a%(+djmmkD&?BP1s%q1F^UON$~+Mjy!E}@4fBii!uu>uJ` zZd1g@)PU8+b$pd^k^sy*|_i~6#8`<7=P^N5hd>fT4i~hg5@y??C<}ary zd?XaV9xQXQaC~&!ZZQ#@Q4M6uC6)#&1p_PNPj%n!okb!dmoYdzaQgLl37C$i_O;-B zv?iCcWc@M(uAhUm$L(A?6{++O-IZgIq7HH&iFII))5ymQZfdznp7T6g6|DHRRanK_ ztoqnn`5lSwynnVf?|SaWe|&Xj1e{ivzFGncPK%2}VmP8*4F$ZcjEgmim`VsjT^54W zI)K{zhliUMF!?DK^Hd;p`GB(3d=)(OPF14VDK2n=$jJnCvmfeWVwV0Un)j*k_<`@$ zKh2z{cnTO?Ube3CM@di>qUxARvD&Jcig4>vS$|yx;8ATad-x099C*g>7z-H8D(Dka zbwjfvT62Jw&$mQ;qjqWxm;~*fHp?`^KXGV@28z_LF9G6;bzWX35k5Cp}M1lkId0VAlUSVfI|n^toL`jfYEaEu<_(1RR&g#XINUQl?*b*CEDp6qJ-Fn5jReh^L4}6+pd<=VQ@f z2uDZN=ioon0TX=6nBR#D9hp7~jay+Rq$zfeRetSnP`IoXmH7tj&{@t{j=zvBG6iW{ zs1%b~tP7CMa1G`jd0e25Z8?EQPO|BSBvM+UxV*+r!8bTrx3aAD2`%$5*^jzpc||h5 zuK5MukSj-SD+(t_(n1&A-j)z3J);{pmiLyR1>d4SUYG45`~ujAPshbj_B^&Oz3egC zGb1r@Y^fuRk)YBe-&pJPrz~FlIsoVvk z|47?k7kxVyn);WKQQKzb0kLF4FtWd(15u9Pxlmf15+JS~eDs9^aGPI3U|o!I)SL8$ z_3AkY>nC>gz6hjWP+}qr%_Q`BPQ4>}*U-cV^(yz>RR=J}hxu*@GByh%JZNnJ= z<$Zs`qF=jfwn)bflYjt!yYudt5zIhRe}1@ww~rxJ*%P-!5De)PE(rT4>C>s%3KXWq zSd;UII#Vy|eJbIa_>%g-66wc+{WDz9G3|D}eQmItIs|t2>I7asJS)h&{HH_SHpz0R zT)TZZn=3CfNQ;e+MEHFHzvoIW@`HZ#LINWruo#xeYJW9)o^?I(Pb4>un%iNYk}OR1 z>A&@9@T?dZAZvm0u_^QH&5S`Z?o0{rM)71ypv<0Btu7w}yS}g@z2vF%)WQ{B$!Nuq zehwUBjH7#>7Nw>7^~H$~yC74$cif(j5x9@DSXml#k8>Zyjb$soZnRns0EX+}>3y}& zUW7d#a|#E|wU8F~C0hD=clKpm!fo$>lky7WS=T-Q$?srv6CA-MU}qxMZ0q@zAlWIq zoQ7C^q+Jo^H1N*H=@44I$Vtu~cz7Shv2Oo#}dQ(xaqr}c-d*ASV5ZQ@a;`VI^2`*R*l z><)j3md0b+cV(fFU?*C(T+0s#nmABJ=mh4f8T94MYxsbfuI48FDb4ztm>8^eOs9fA zxwX}0eJ#J_`D(9Sxtv2@wFd^5vmewWN#yliuSN|>B(^-_1MtDC(kucOwQE9%;rCNQ zpwsBbt{2wKt?3!b3UYuSc$C;F~wmI1;rA$!@trWcG-80##Dny9zd0BzY%7Zxh#vttmlHU|fk=7A9VRM&g9z}iQh=w9^H#K*iRNZ@{QZ)K|wq?m-jRaD%h zk~pT%Y2sj!^xDsbRCeTAfkuW?kNOP$f@I?T(l4vil7dbQ#VG{9~c=>C@`q5{Els13D&y^VeL41VH9^_hTB__YYCD=<+><=rf zxKH?9BvD~%+h6~zd$^UT+xHIy@Z!Ez&6)4BV!chQbFhwID_Eg$7bd3qOqRVM%3#SF zIyYt=WIvRMca2i4mICRjF&(* z-o%k7edc?w{ztjyDG#dO`B?>Y!BL+sZuELZKFU2FN)pMnnTUa>2t@eAmiI)`i}qTJ z8USHe{p9lQmNGrN0F4wRSFY*sxO5^mnl~qR%O>|v9!Zxd??;m{xP|I}6z)&yMTwYu zXMn~|o{p{yXW>pF65x2gI(e)?^=b4uVu3>*Ng5*wXd({lGTb{Q9=Ys^eCyY_d_HV2m z0zym4ZJ+5g5t`rx)izL=0PJBDcL3o^vgd|WgovuoIYYUc8Tb?dc%lLLEy+9DQ>J3i zVG1gaFvErZ1qRQf!=%1DAj$rSxe}c*czMY#sci$GSP-_!B2@_o=&@Hazx9i>-_0Fu zIrW$^I%!+ZtS`%v;xCxI070_Zg;Fn;kc(lV^jnCj8-7|R1S!xDo(n2T-vVG}h(+UO zFU0pn-F3AMV?+EAqd!HKtw$>{=akMSpNe8LP-tE2-4a|sY3MBFmzFu&^2w68IWk)H zoOqJWHvX<5Lf;OxzxeUY2IE=)O=O|V5f~&!i8KUx#e-~X6(H_0~OMTrh}WHVi3 zZOGwtdEbf667Ovi5aZG65g&=r0p4gpTf?_kkm)5XByiUgEE)w&C7Yew9?IGHPHT3(T>M=dE_8b90Aux47C9+D(oUa zRy!cpU0p?ZhJ$r)FYbrBNZHGDweFWz%4Sl4-M({%TIP>LG0@eqbaOafxB zeR^jn6T)s@J*8jyEgvwwY+Ejspio^!Ta_+bOTsfikeIz_ZKQ5O^~CUR9*Nv`zmi|&jI`qb9lJ0q=xZDDk!2yLXGJOrhGo=Po{5=(qgU+A4fRETxpShD4o_1 zOsEDfWoLr;>M23x9MmyWZI@i-o7a)H$$S!lkO+)M{Y-If`2%HxJLb!}#idob6v@^R ziB#KY9tYrC2+|$c7B3xHS3^%hN9rfW2B0zXv+!0Cloy(g_j^_W%_NV8<&!{&GSJ^z z1^x^`r(w{5<6eW5-^EQuE5y#h%hTO~1C2B{^*(8j+{_oAYqWyD-P2p{1+IL+V2q&D z1ax438GX{^Q>G~037#)4Q=f#yEK#yu68T1}sI-m^yi{qu6jF54=O}+)@9(+d;YOz) z06geC{LhQNTKtn$b>zMuEJ&@Q3JyFg?@N;+2hWyx*o`kekcs2bxq1z;T!Y`>ym z7hyb9okhhqLypFb1{a}1N|W~y?+tzutqU~2e}WKxu^eV5-)x^+EjldMrmSbu9;J%r1hwnw51eEezT4XQZyv zue0JhhN%og|E#Os{W7(c057lpR9<%ztKYIsn*59cGxb{Fv`apo-eruqLJ{rmSLv)~ zJ=5B1QJDF@elZSCk-#&+k6oz1EjBS(O15b~Bnm;%%?gjZEsleqr^z2AsJ)o92y3e$ zmn>~e(Y#I%UTG!C#>=irb+&vo${!L!`*u26q9mJ%WPH*Ycw3|X(OEGQ@<;+#tN1%$ zCkp<=v}eI3a8`A+>X^#qYOl{3L8Q!0l)&+p z@Fc!A5B+d$K%YGFg#-$yrww@2O2WM{xF^RC*0wawA~yrKmLvp-H*Q>dx0uTwLtG)* zTSm$5BUME9XFeg?>qt^H0+>RoturDsgo$Nr6;hgNCp)DBq5L!|A-tH|;z9&Gim7I< z1a^MS3SeAR91OtfpidFmzGip;bUEJp+obzFqyt=aD&5bYu6YI`9X^rp?@Hn3B@W z9dTyU2aAV5heqLQ9|y!MnSCmJIsF^|8Ohj3loEK%OFi>g89l4%7ox3fRXfJxtYM1= z*zSj0>(2#!_w873E3(ag zd=t=AR;IB@R*)677A(N|5XZ2QZNuv&qzMh^)zDj?ImqFj*K?U#mwo@>)jPmosZSGqa6$=`xzEf;em z*39{a#cd5f7=!n=rio_a`BO$8=9D`e3oC_ubsyo!Z==C9R&v!!IHLA(CPUKOWt)eb z7M~>h$ll#>S(fexEV3z!vvoz~$`t6TN@1M{rw{*n*3)2~TyeFa75v?=(y2q?Cq{W6 z3h+?}aFTZ`g4YW3T8XZ2otQ1I)>5S8og@tKvS)$G`?vNYek6mmi_OnoCmN1=&^tJq zA03=p(He#8=cBUE`fW{&c;VRnybEIx1)c%Zh@{`EBbfGD)B9Hf3(>6!?@c2{h?cuy z1K35aTbs3rwu1}+*KWUTms|Ub6xO=uLOHU~B?1_EH*VnMXxy2F5~xb660~-Dd7fzB zx)jzm_zJ!|M(GibneL%NZrC2){p)!M9}TQFTLBvX=;mP1Q?h@?6A`o&)x6T06&l2r zaa8+J_`R%TmX58VbZ&gurWj0cjgM+Z(pbIetF5U*A{npt!}@SSdvN_)DQnQ`n8WSz zhrJW4^WvtbI7ZC%c;prVZSm3z#QR^aMhUF~=j!zn`2A9{1$f=?<%fm_ZKAs87*y0e zxuZ!M{7i1GHFI-sJd0lbTnKYQG(WLqtI!CjdZ#Ks+-C`>agL;E97fi{8a^)ZV&&0| z#9@B9T~MT>TfTk5bOfoWr@=9g_&G`1HO>Y5ujO&I=qv5Q$}WNfy&h% zVw(EUO#bg|Mr;rzW}e&)=A|L>wYq>RG_}$CxQ(0XVhN?P7UZ!ViN%&!R0E=s;83+T zRd%y+UZdH?a`Go{0LD_N00x)lbkv$UCy1kju7Ta3EyNENE%4@WE=~9y zPfK^lQFQEPAznl_!{7tQ&9%{$omb*2;aXZ*K(%fo4%GNBdTw%3UtnY%|2Qnz`S8f; zGEB~`)okvAoc|g_)NsP6B};tq-^#eA$yDkE;6Iw+KaaLgC*|Wv&jacOB=8IPc}N8~ zAE|Wdoj-)r15bh12x-?_0~kGo`M1DzBF-TcQV!|FS4>_9#SrY+*KhxGEmsP?3Y>D* zH$KyKYWX0xPIAJ36#3jZWH16_rf8L!M}Zczo;}(HOYsp$=lTWwuK;B^J=|3}qZ2So8j{lj!F zEwHpS2uR9OvMkaKf^>H`B8{|&gi=dNNq2*E=SmAmgMf5N2)u)SpXd3#f1tB7XYQQy zIp^M)d+!OWyDv*VmrbI$KU>KgOPu$5pTU?}blNnkOvu{W!ep|uqX`bTC@UWg@7h<; zD|PV*awTmR`Ttyx<-2o;LXkOhq9I~0EuhIOO_fVMCbCd28LqEpg%zp zJc7Dht8737wL|S$q{S>^CZ`uCVj{Dmi!VUKdY4EMJ{;#x!Pok1Kw>+uZtURui(`1+ zoCGt{goOQveOIqurjDJgl8IRcU>Q*N2SUZoqqAVlk1_(=sI<@8L$Bl^nTeGBx%X(p zoK+(pC@+%64lPq#&bt^F+`FB}SlY)OM_vLDk{>33#{7spNcyIby%q+5@|w~>Vy#qJ zKguRto=#gu554C{6nLvMH8VO54Hrcq;Gg@FK6$IZ>L-uui39$~fkJaNz$JMr*OI{)S6x zNCb=k!TcLQVd{DLeqYW{pUn{Ce~$eGE^iUQ)(56ffeg+3B~(Lady~4Vjjh!3#V6!w zgmLXWqRz>1Tjgtnn0Ms&Tl7Zas}&3CV!Qowe@n}qy7qg;XrBrrEPQU|o;A>UmCg5n z3yPml^$?veB4#4*17bvX>k5LKBYn-Y2sA|AGv3AOzv07ze}-EnZ{64}d?0#UTKQUK z(3+RN>dA(HK@7vOeczb6_g~?)jR9-7&ue@zb^?)qrvPLChpGo;fJ+VX+W;O>bbpa) zkn=1L|6XBCq7AF}h>^V*1G};Jj+rJ@oCmHbmkhX{@m+uB*u$cHCREwB!5u z4m+$jMW23YEREu)?DII_plS?d55ar$4-}_%d8m{8BR7RB!4^WusgmzgFp*Q)E-xL8 zzmk3kzIEYgBW3zDPJ{V4wdofHD?Vy8Qx-%aB*q}zU8GgV?B@i^>suGNDCzDmYYWlG zXs(qV=kU^5NP<)QcP0mLY}_M|AfR1)FmeGi67?xU!n$dWPiKPtGm9|6=5drt-^ zrsSiXb|KM&iU7sNCpdWwInjUGv<0vzc>^>(1St8hdPj&zxDHA8^FVHKJu!bT{fETR1Rx`g z8P@WT3qbUegE93TdEI|-{Y9$auiPDdhd?*uBmr>i)drOH!1#mT^dV*ZNW}9~ikZLq z|9_$VtA-fzI6xJ^69L=-W%9A8`(#u;0CtQeOc(_y=|9!&Sp9>aZVWhO&-cIW6#efJ zz}^4LwJac)>$b7B+=C{H0DEX?0Jyr0Bd7eU2}+Hsznb>w0L;w>ScUlBBeKWo8YMM5 zeF#=PaR%0OyL6Wc=d1E;KY>TUu6E9cUOLyam3AHDERKx=!VdYlaOoD3Ja zvj^ZUdExNGf>aOYhtaPD0?IQ27`XWHkWgD= zqse!dnOil}J~E}*A8(9s<8E7iwjccV+arfMN6)0&pIGU>G7?!2G{}W`1oOB5E~F^H zfmu;jeFgC04xO^gL_5yDGwApF`8$#bIL@HNZuOei;`WKt@Zb*V;Ar0qe>j~@nbutM zzw`jjExbjB5Wcoo~i}?BAfW{{TdPxL{%oICQo9TKQ%;@BELLzN!D{f&ej{ zr|%4<#^N-Rhyb3m_rjMb6w*Doz2?P!bp6-BW6!~dV|@xotekSX`U;HaF#m^^XaKPVnj$L@#2rJLWu|6s@d`!L`v0ib(kpvk0L=zj3i>kjPf@|y?5{_p&Q zzDK?OKKDWS(SLs|{twp?nneGe^%tbZ2k!ol>VZ`S>c{;Vh@O9sZr`@j{3n3|9Ke(! z^Fhx4^!*&h(E$|k17reB&q@>c*|`23#;<>W3kA@L(6s&Y-^IUe|Lc{o^c-GzYN0f0vgZG?{Bymz?`YA*NKy$~WQxkiT33 zN(o*%1|WY>$^+vrfAtv-&;oEgbdkq5_z1iIhnRvBc>aGtEc}Fx~-+j12KNYgBAV{G0;zRt7*C%ny(P2+RNIiumu)q2fV2p8`U@ zUw={gN7?lt8=wFH*3`N@z)1f@fBr{^5&-{PfiViGx@W)kn=d;cxO`0ZujiUO=T+o= zrp}B@=QHz?+s$uExU}uY4eCBVL&N--CfK1mZZHv=Y2*{-7NKwA2Mu%;KH%bfy68N# zOmHk6ZRBH7gygJ&W;}6V-DhvTxCRI$zew&U)#yvJ3s16Qnk5=ee@Q)AEdOjO3eI#_ zm!tP{TNpt)LL%r%LupdAr1y%slRHO5BT26rrSm4ez)o8dxU0H4zr)tGU-E3tdKE^p zcFK(h5;hU-?;~s=ylkZI$7Hi&ipjobden0`Zm0I6+asALtUo)@%6Ia(^@*df-y(;s zCO#JywP;9%YYH0|Q3|GbHRdOdB$wq(P|&_AyjPsK(sDB=gtoEQ#T4Mb!g5L*lO)syh`P$C1%U;8 zeQZJvGkC9Xv&%8hNOixAGr&soa!?|V^)h)&P%p5;OemzOCD z&AHKzPg4eMD^9|ttvfD z=xr+ooevQ+r+U>?O&mPio6@f=H?Yw4Pb9@z#?!VwUS#`u4scU@qzj^l z7;8a}mdSL#X(lsK%n?qH15ys5-C%<>`&}X zv1&}Zs>VIC{n$()fl2HF9USao*aapG2X#p|lbPU@mNSfuIJczUj)}sB7zB~M-R^5Z zOLMxT&F^2dJ{-aZttsX)>RoK-rD38vp>D26d>?B`QIFtYT2yd5O%d^W_J`bu%crz@ znO{!kDqOWvpe>awRvY(sRlmaf-QpR;^>4^1o3_%c4cxb!$Bye=9w*oZ9UO)XW}yfE z;j)Ly+EmUoKTx)il`MRvjtwA?{H&U+w>OJJnMh~3)fT?l7Jq0s_aDm~10__z&4dNS zAU|xHq2+akZ|`JUJ%JKWoDtbTgsY|T*KGYIVa)1CTr9nGFc1{FLKDsJ9C2k}FfVsK_X zku=Ju7jVnyev^_-h;d7URiUl(iU_?{fgVk-VNiJ%67?=gbsAUr;gCB>ime&h! z6%!Xer$SA~e&og5tS_iDp+rnMKny^6S==44+GRc`0;KE`BpMF+akOYV6gDt?P!UX> zHK<2Q9aq2y?B>LjP+n#qEG5cRw64pc$Jf=3sh3zY?QPQ9E@Cf{rj|rv&k0l7G{^;n ziUHvT$^D9>;kbvwKJF3Y@~*Y4qV_Y^%}F&Kjps%?P&|7g%7OtP8$&@EmTq6CPQxWB z0Z?}u*4)!b@|ehi*h!<-tg$zl8gSr%c;pn!k800x0hzR26{Y$(5i%gEE|cLKml2Na+p%|z zY#>l4IfjW$eujUQTK zu0My$hV%!2c`PxqRhz@{&gO5uuW+Jmrux#bs=W0JbiWB*=Ylawr5BxkEQZ2a$!n1A z*$&;!EvduSWE(<30Wqh$5!p^dg|Ha&FkvP`Eo=0Ivt&A+5o!L$3ZGTqqpr}oT@|Ta z@NvVB7DKZq3~Iut986yMs&1Qj3`th#%Xe+@Eoji?JDiZWnZQ`teo`$=>Ym&nn*!-e zn*l9aJ%t{A^KJ(@}}p zIiZPSQM@QQV;m*QtEQAl??}qj$$l89%EEe~y*@%`!H}yzYIoA>)%~qmfFdC;Vf{0Z zauyp~P|iN>qJ^e;=_cuZFeXEC*Ey#xN_7S2RjKDPNm$p#E)NC!zt-glc}Qde zZXU_9qbIIs!sdb$*-3MldK}!^>;ruL;AA%=BMGIcULA&_S|;TEp&|s`SO}VTnzm(A zT&Yj?J>ZT=LQSnD=iLupmn$Vsb7{1bEf&}4TTJ!HJ>?E+^BBGyLc|QC0qcQ)qq5s4 zH#s8VsLhb8=*y?_^gjmV)%EbalZTe`WG^-3RNN`r`N+f0%}{S+*$%_k|lw!SPRa?$bhqnyt6m>TqGMCX&$;0Oq<#J z=45=qf>JwE|GT)q!kf)$${cI5g}0B=zsTMWi?6q`<59*mRw&RF+L3RaU`1sSW3j0#nTE4@tp-IeXBFX)|iQz-S|T1oe}vyA_YuO5UP9dRqpIy2RnX!ASAd* z{tqio0ld(8z4JC^3s|}>SnydT?)BGJ2_RtXA5%dSg(aLh1?N&CI}r{0@bn!1Wuy5zVWLVISgiy?xvC+_+qQ1jM;H~oM1nQiZb*= z>Fqi#Bx@|t5J+RMWZelm$kJ@WmL5X0FjobQ;*02a$q8^W(An7RtDh|6cBs#QhK~C< zPrc;}8>1XJCkraNYAgzzha66sI_0M>cUpOlS;0NwpO^E!W$-{mPDlPwyM`IEYbYf# zD0@tDlUWz`({BK868UaNzyus>aZ8@mMT)ZLL^FDq43(SNeWE2H5vFO;h&s6tqo3#~ z=d3(ZT2d`D?RiOopHQRNrlcACESzCGRK3@0t7RY4X5Kl-t*;;jB6}i9%v8r1;1yy* zc@#5z_QzijtA||)NH956TnG~%D5^bW6YKZ?jya+@Di+}PYC~Ud%)SC|WDoL%V6*P# z#G1HX)?l?kZ-hxjj#0wc8|jmsE$*NFycVQ|9#!o4Qt7wF{iT*~j3t|VjyY7n6C3!f zmJw*^B~6tjl{uwgK}@$yqJIAF8WfqfvsH_80-CMCKSEr06x>p)kLl)Y>f^-VkpU zGMX?lG{eYkzax5*U?D};+QNiR)M|Z;! zg(u;pjBsgo*Z|FVKgysP`n73yB`M}Qw>^aY-8z^F!fFRW8=7frLJmQ0d1Bo`rTp4b znNz)v(fLH>&Ng*u07uxeP|*v>g6hGa>TPPX&*13S4>o&dhCSo%OCkKLH-{!{d$O;o znJE(ZEc32DO&WkOsA?xm;+++5db`AJ!8@s@N8QKjxdVuaK$oqa^0%6rI>UzTG_nfD zOL1_TjtNZ3=!S)pMv}0dZ3{~x(5pg*-ztXLNlKyyC_a~Qo-}%l6v9WrI2ULo4I?$- zuXiq+7!SZ;Dx4~k!EFy@uxwceu#&mcm*6-A_FkAztjt!p7Eu(m`o7lz)Nx4M;anNm zt0gEG&^39JM-VYH(Bd_??`}B#(yN%5ON-lCf94f~8tY)@lcmh#_0Xx*%Bawg_s(9% zVduM$=ddp{=YZc8lf1S{l73&B%hA=5A{v}%sh79l9k+9DA)ILWONFCtkxQ5huX$sY zugy;9&6%}@5qzPi%L;ioe;6q|633}HYu@PmC=SEiHi(0*Q%Yy=(Ny%if{~_vU+3J3 z+?6o6p8+maNP=InRFX$9tzzeaUx0d>);D?#b9F({=oH7e{+%P zQn)KThe!YgU-dhx*HRn)R-#a-=621CoQBvz5X5myZ`DFNh>l%_4;(s#>9(4Fy|PH}lIAM~)M?P0bQFm)ovD*PBzWQ=DkB@sRpI_E$0uGh zV@)A09*!^tQ)_>jou8i4B8+O<9(hUC(~RA+rPADmIr|ORkc5B3mQ{cd(z`TL2X?5v zFPki&p1f@P`o`UXm1v}6hM-}f?Sm`WM0?dbuR;-UQYqI(dIe@>&Z|Ixk01G@FM^_8 zlf>g7sBhY$Ld%oH`YI~V_S!0@eLP@wzj?Eeo-vL}kt0E&@FX?w!d?p7wql5p{q^W$ z_^gmIhPh`lZXz2L=_Rj?%Yt^m+IzX8!Au1AF=Ov-xb_b}tv&Ot8I1ar=P;77aTKwg zN_^xSiwly|Z*ix2jkT_tLX;&Os;5^Y^N-=QuLgD5h}{TszG={sMW!RocT(+$r@fn7 zO(}fOmfYF?|UYf*y$X=Py(eIbst$h>eS+ zl^LZ8jI-EK+7N+spDyTOfFPH6yM$B`AxF|3%@;)_iowHBYkVMsFvgSjw#du$j=~1H zU)R4);HVJU(A3C%FvD2)M1u&SybebDna`tZ@TxSZ63$|W5OSWIRCTLyh_K95ET2A^ z+n(L_0Y0chG|6dURzakflAsOIVKXx9g)P1z0X$$_CDxsWxovzO25a^~3rW_nW~e_h zKIP=4WLB#Q6%n>k@!+qU(m&x= z!C+aUT1d5bejKfJbf4{79aq~;I8Rb{JDVBZOut#84nE?n9N{$@z`{-?=yp=OZkXKHs=rsI zP)Ce~1iIZVqbcf|9uau3TD6Lro8i{U=i8Oo0O^gSpu?FFSHkj#Y(pCmUz z{7^Peuuu`X!J|FlhxCYQcHmsqxQpwwO$C!u3g#0PhtuGb%bjn-&Z->4emB*!3Pn_} zXvXg4WaE(=NH%r*z5{jHJvIi3A+49kL$WFI3SdX>eq1aP%jD4XpPTX~&6LSLc`_$E z)djKXF$cKu=eNsMuNF8o_2R5^*JVV-C<`(hN2X8TpaN+LO~%%fSeQ!`eD1EC@$3(Z z3u3b^xhty<>cB}n7)X`!%Ncr$X$T^FR!qQ=`g2hOT{SgX;cL-kQxc?RAWeXxU*rV3 zrwoe{P8zE{G=P+uR`^UNrX~xdlwr0-#Z2(58iJ+Xp4#R)2z9qoU&RdRY3O=@hxJ~@tZg7cb%3$pDbynz>)+`0$ z{A;WnsxHg2Y5N;Sgu9B{Y7X~{v`b9yv;A8UUBcIG9iD(|LWL&gUhX@Ow@%4;3d9iX z{v{s6JBNb7^p*y&zS-HNf$NA5-Z-fRxhopP@a9-|%VSJQG&h->k;L+aE{#0GDFLZv zrzYE~$Z0`uVo!NMeYbL9;9AktaufFY^kNv?%IfVaVXv9u-Lch zGu53O1Ts9U0m`iDFscHQCQ*Oom0E`P9B0C$&N(!b4FsDqZ=#ugtY@jJcf%mxuYux6 zn}Y>kT-?Q~SdG;EQ5E(LvI*M08iOo26AB0R*l=Bt5a6J^$*((7-Vn_lhAoCqThF$V z+Q5qRZWt6NQal9hy)8m#T-x(e>3fP#*{pKfRPosQk&=nM2M1d8!EYg=rQR`@?dL-y zL&rkP5d^iTI6*mo!q^;zLHQWa@&p%?8%=XC3ui92-^UZ{`EQi`h#ZF9H|2Xw~H zO|YBA?XL?KPk-ej5_%-pEO4Ltx?O!Q9dN2ShQ#%;081%{H`-YXp95$`*er@})p za&c0+ynSSgKHHjfE}IUqu#9ia_Jo-vj!3gS`%_qcTF+srVIiu~B57+|q^eWCYJx+^ zD*@n8bFZa$Zn0_DR46UTEpu#g1snGgD6c!eNJ6=x41L_l$VhLmEA2H5T)S9mUWQk? zXzT^!F_>ZOt-wzxHyM^ibajO-4D+>{8F$o+&bMW5!ukj)*bzeP1WPR1ce_H&4k$mb zhKp{fwV37%vl(7}XgKU2(ce+>7xP+tb+)M4pQ^yGbL#0&}Zxq*$ zeAjlZ6T+55puV_8S5ZN_tZO&o%h!QNNiHsva)Wd0JD-Sq*SKBB=QjdY&gf_u7-E?X zo4@R9*i!44`fWw>V?(jtdx|ZcFU38=C5WRJ?7d?%x>9*?JTNGm3sFY$=ME*-|G24a zktoVxrH=oOhs{SHEP&@s)-%!gA^PW2DajkB*N-L}tldTkfOK2f*en%u*V9egboy-2 zAC=o{#sP%YilfPyLncWoMhX4(3V6o~P+bp;>_{%(1Q6#$p48?ovpgI-j~c0|XQ#ej z+bcUPR&YEtt}xkP}0bYQW#we z(N@?@8VySpu{)bo-kS++B7@L_#$NUw9073;AE5CpjlWviJaBrd6LjvERr>esFl0d$ zhlV1XGm=jnbZjvSkmV=&TJ<_ZyLQ$KpBD9z&Cmw;EEX*#ERU#QO~uvgn@hY_0p1_L zypWVIUEPjaERN7c%x304o_{J7T<#T9Sw2`>!fHWAkxvs*D|X92!E9 zGh-S@eyZ`qa5W0;>`}1tVuC}u<}7}#pUYWS0$C^4Q`DZHu3gaT5BDM4PjbIE`Voh; z^r?=?;X7-vQj10?gT2=JWrl8iaj(_{c1> zFD#6dB4Nk`*sxPce$nHn4rwh6R2EFEmmyC2BuDyF$;2qw879b<%((pIIn16?)W49L zrN5UeefH{Qo6yZCml&wI>PjQXE1;yCYE}3TXuiU>OkU&XsEt8V*ZZaTBE2H>v775# zInIRj)%4)~xLkb+P_{S}?TgV1pn94FlN@cQ$Gu!n`@!Ztj}>hDJBZ`TStMvxUTJoo zbEkr7L`I--6vtwt4f-`l4c}Sd^#T(Fa}Jrm0W}T@Dl2>S7G7TuF>7P|3b?vnNuq13 zrxJTWl;fVX_%c;BGc7L3dmpaY`H9Q63hoGHmU$Ec+sR#*i{aAm5?2WKIfcTMqRGvRtBS5YS zTofS>e6<{Yc;jp(dy!C6R8@&cco7+Hxvo{y*@`?TEdrr1v||zoKa4W76W=7$M6(B-HwD5ZC>3Zmx(;MU-JJ~`OzB#Ibcv_^?^WDM@YGHx# zNXWR*l1HmJpYD;Oap)a`CdG{s$uGz6Izp|`sT&tJD}b9BYbpiI zPZWR2-2Q0m=7kwaT2kIwGItVwD?xlv?cD3v^Bj1~aFH%-Dp0HM9C?@YTrnLGsTwnMaLtYu;tG zk06ugLTzh#6CLwq>}XsQ_okwQ=lK1U0dDDjBf2aG!>q6%di)&!d>JtD%xV24p` z2sk6AU%ga-m^x{6((Jo`0Jg-#48<+lG4hY)n7Dicbhoq+o-0GFk9!?!s^1{B9qr48 z%-y>JS*XrsljEw8<#b+1NyR!=@)B8)cN?h{Gl4MNBGC*5fJst(BE~vT449C z1SKYtWPQ2nYb3z-{5S{J-kKttQ4iQ1PQElIvsSnl6%X>@=`1g*k}zFceo||GB~oly zfK8VZ$BZaFc$qg+kgiU4K7ne|g#E$H)`$x?%%w#xciO&~N~{xkm1={H@BJ2$ic&`?*y^?`DfN~zK*KZ**S{f z=P}2uhE8Bi$j+L)h0sok(SyLs^7w*fU~6_eE3s9Cq*ZRz8csT-bFetXBh z7-#*1;2rh>Rzu*m{$6BD@pzY1n>Y9kAsxzufNkLD_uG6!6nUTo3TW)0t;(#(r2X@# z9mB_WmpP{3Wb9yU+A~gkis)DE_j<2fVyjKE5#E&}rBm8ojuCmkSkto_ve=47~!W=U`mVS2DK7S+; z_>FI;k5XKw?4>jPPg+!IQkRXP8aZaJ2)9rrCDi)r%{Q+rA`cJnLy7h@ETmKKq|z2; zN!YUQI(4#Zucfm!t>nqID(W1!m-9s#H)K*PlNCu0X)NDhTAvKElL?|kh!4KwpwD!i zGAMRaLnK`Cga3&pFBiM80Nv2v>JJqkTO{D? z@`YMAR9R=4t{O;vRjDf=hM_CppK^}KhiW3l!m)v{g_mOaNsEV+k2PqaF9N7Cq>X$G zS!XS={~Y4sg{!t9ZBDz6fz~W%@gly8`~W%GzZWvibSgHENIho99yYsH_(X!Pbw5~8hsL#*iks|n9n!0m(Ph_80ae;SFAs8CTz!Ub7H=v+r$H02$a9uPhi7LP4v&*w03g{ zi4%;i8NLi(Gf%?ip2<~5OlS}6y%Iz)=A<@XEEv&Ym*}W3Q-F-Kb*_D=s6ZAkUbcUK z4ol#SCsjw>H{{vipV~?kU>Go$pS>Sn{a#c2ag01cn2VI?aSdk&auR>#&G)g=30BTT zka=YiYa+*@J;6M)>#nfP%eDo&w#cVZ1qBqgTbbjTBn5VmHSzAaYhK9Gvw@R~U`Yy$ z9vhyV7369Dx$lfE4b<(QjemFNLE9RbNq@uU6Iq-r!w$4)`sOjRka}q8p$q0aWzS*5 z?3GSx3IaeVo8BLfqo?(TC-wo5(e%cRg%s>kYGVNB*o|l=Fyt zfp05lf`8!+AtxzNw%%?*)1Pu0^BvWjRm$E@_B9C;ftIeSE0#U|PmwI64WX)0#o4n% zhd}AJic3;Z6pZMY3+V)W8;FM#rRx7Eb-q?%ZgL#=nNcjW(zdGXU~sLgyBk{%p*EHB zOWNgrr6bEAV5wEG<%;glyWj&=qxuvF1G(CrADb@E(hv*!AB|S*9P&I(!R6=hVe1G| zCP=Tp7rHm{K1A2yz&yA>4cQt-5D~Q>adD+kaMJVTS~#}VW?jigRG_Faxt=>m6(kz> zad`e0E2ZOJIK%S6FxPcoX}JwK5a;T~`$0wVLZB%C>~H4K2Ww;*ZKJ2WMb>Q&tf|f; z?3d$wT4h~pW_D_-Y-jVRb|>360o}C!jU29;SWuj`QBvVAd_E_VwfCZTDTBji8wnJl zj<(!B>-tLDii_%HYgkXhl~sHxc9i+D$O|~zX-1}Pmv`*@J?h5n$(q~^!b!W9smjo^ zJd-tg{2+opExGcM^l!dLTnEmPsQ_i}K7XCqUjH^}CaPPsXGo3zBl!(mGF3dU%J+dk zCgoK!65T)R_x3uhzAuMg$5iQn_~6;C-!Tbc1#-2&TAD#&42%$Fcrczl-L z-1TBLQRTe(;3AneCPA0^sLU@ZKitp6d1On`Yjr)3g*^HuhO5U%y+}jZ8u7=R?J}QE zpR}o@RK72Kb(litYT}1IVX+kR4C^poxqG_qo0Hi~@+zv6a`;Wr)ONIMcp|aQM4$Id z(3s)sDzqWhLa=BOHb0Sb7ngXwOk4e4wPwv0J-Bg*i)e-nGxM0~qGCE$Rrb=oyAkiY z3aM5AbX`u3$00|wLRLBGOty>2^?rlvPIO(I(u(50Qm^9m4x_&X;=-e8dFZq zBl}n{m3Jx&bTADsrmUTv99Ta%>HISG>XoCxcoArpW&9-*sSmN6PZ?LQpt&AY|DH!) zF3Y4#bDdBqx8HhQuovfGp@aq@RK8(aynGWX%J|a4Pd81}G>6MhLn-uypvx$KrwVL> z18S9luG7fZo|NXpwpuG@&1FFbl>Pi3U7oB=40zZDyKyOpsnv9}iRCWCb{aDSm+HTs z$;Uwq$HUX+V^r8rI*BHCB58pvyT1<>odCDiCv&%{{w^{4blGe%@ zShl2k^EGogJo0S{#B`!S$^^f~_TIV0BS?Bv;hoSix(p}MW;X)VhO6d#1Pka&SYPsu zo3=GwG@a?8jl%X`9J4*e-Zlxk+p25m;({#J?Y#~6aKSVW$RuIn4R%Xe!OSwDW2-9O zLiD&tV@vW@9*H1}Z6O!w&#@WkB2`MyS^6tUPU45Iq04EcKh8dUQ4u1(-%<*NA6(dr zaWviwq-ms(RW(m;`G{;2@={e10S>l=WX;V9M>gd>AV?jPg5v~zmd z&k8c2B?-Y~PQfPHe*8nTy8Dnt{XLQ9T#yDQeeAUI-B0yRMm<{)&mQ&7|@xdYPa-->{jN9(}N zSBwGIorL=2W^byf1-VEIAR+uf@UV?V^E+eyc%7s|qm%(HyHkl@O5&Bkx9OfvYMV?i z))yCo?Cp&n3*LpFCi`t^?9_%yle_*(ayU1wj`0P{EG?w5qk+Q~o2a#x0Tp$6OfdVi zFO?REQPcsh3fbVzbyj<1A)2CI1Up_s~RODXo{^}rIxZT=fcxI9ESb+H1 z`)%L(l|8-6%-Mb&9jw@%#s>J7ht?n6lL?H-fU5qvlnF@3JZc6%Ye5=2GOjWuswKjUj%Tu^k{mz}-ZWJ}M@evKgK%RPOs{MP-H9_%Yz1>jQ@bM?wB#8Tz zWN4b=Iz04aR~PrDAwdJ|ug?uDIK%bz#7#F%?}a5Z*kreRDJxnD%=#n}$)aja*v z=!yy{8%LhFM;V;SvDLCt9nL?}ZTlL7X@c4j`x|6dYs*2Arus9xrkNVbBFH^oXMtbK zN21l&GWAJgWEY~h=;suqW(H-aR1$FTN#kx5EbS^Tz{E2^*5$!2W)Tt=fEbqG%Ep&m zFSG?4p=ngo9^WW(^Ga0}R?d=KOqKxkoV9bqR)rA+&tVz#PdHOI>32tk}gShfGDTBZqZh2I)NuJQU}aqnPKghH=j7{k|_`Trmlmx#ePP z40~NCNlP&TW0ha@jf>}6AjFCt{PpVb357&Qj$J%T0=Y@=TFvHltGb9sPB^Oumv>7C zm5mECdfIkURMNd4Ph@50*fn1!@U+)_YD*4!*F~fj7?C5c!jtY%<_kIUat}3#V8+b> zGI2NtKuENu9W72@7rj2vN$toKz0tahp>isRFnr-6PLW5Xmrgo`kjMJNm@ErcVwUbI zHt=3h#t6T&Hvb??kbG~Ni@op%KZU{e^ZcLQ!Xxh8SseaJsOX;H-X6r0VS}`WUtR9z zt-z5EDj^_-N&s^3;fwgp+IZqc%Uh?Tm&?q_X zMGf$OBa7XJm?fMDfB1A#SI$PLN)^RYaSzlppxJYtTXXbUW>s~6)pM%b%`I?JfILSI zmF0J_Bzo{7?eE;6)nD}bs%b=@2E*+bCr)mA{ipvVm10p6tN_ZD_90fb0jZ8{x*|DTc1mtEJ zRzY)=8wAoEHFlCO0CwgEAEu!?oFD8P+P|IJ+SXohDSkUCbvy3 zJeMQD;TV_003XO_E`sYsCJzq_^;Zp+VJ`&J&$?={fAYOh{bf>UWcN zUt-G@HoNJm!UnueO*^^zU-SGHw(*T^h{;RUw<|iWU-*+TTI!y)(u7fG%!ec9qM}gG zYS3>Kos~noKK>f^aDMM|p`-XYyRX~nQW`#R>OD1apSR_JUHY89$xm>5|>>i9)vvl_RC7EKV#IL4o z3Bb)c%ETD?6~F5IeLS4|c#$`$xI?~Z%-$l+#hP}4|C%E=JQU*KdB43Q*O(h3Izrb~EShRvlw`!sr23oo4!A16aNo=Ny*F4s|d zw+OzjQblJ=Z z>-F;jq?zE|LfSNK$zmXvX*cq4xq^EN&mizco)}-_8$)l4-N{|z5t(!GA#z`-`8=4b zSh)Ik>pEFKH|8(VA=9W&--TcjcnMR=&0Y^uZ{{1^q9|r@!!SV>6)ufl$xb!f4f>oEee+8@`FbqdeO8&Js=kbYT^!f!WtOnP{z;q35u+_jWiACp7aI%#HSnswbpJ@D@DODyj>>RWX*p2~OfzCd@@{7RxBbNChNek-{PzL^!v|2&rFjOQlKSQmgpT$D^;1tA2&w%lG zQExNRxTj4z_BZLMYqt+3*9IFh)Z4LNM6d+CP1STgWB=;r15eXr2-`?r`j=`p%7WB| z{JEEV+k^xGE)mDm)x6E77&lYZ>@3LcUx?Ai#lyIDg$fhyU5D1-hz)aMwO>^bIN|1a9DoM8zy@Z z$7UODSB!oz|1e*)`W#fr#u8`zK28ogWsrGdXi~P>m`?zv+OjEeg8SiOo-7=_>;^IvLkv zjYuOvB3_OZmqlKLZ#DzkYb7GSrT`%iqbp~ckz9m{A%=plu7~M+-&#=7|6tlkq)o zp7(ubMKZIWol10^^i4^yGK_34(OE&qrq~a5lUd-Ha((CZF2$?jaMX3sm<_6(LNkvJ z!gq2DvJHd1XAnJo)NdAz9Yu!K|Ji-{?y;j`)3KLz{*w|c*Qp#xLga_Z2CNc`Q4b z2ny!oGIK`X8l+Y#Wg)6ZNauCsI)hEGcJSsWW?s_B%%5Skx$5^(!#pPZI{iY$ClAuws#eGKBP zBFFehTkVl#Bo(@#5}Kx5%amgS8La#R?dVrBH{4!YUn^(w^G1)?nPOV86;Xfn&LwUU zd4Cq|w!@|x+gPA}8Xff`=H2o7(lkHcFVaibz#a5A+n=)0Q{Mk9+p}LsCW`FH*QSBU zdL^%nR<34xhmTJ^j#i@4QI={Jvp<$(Go%oh)~5ZLu$Z3A^J{g=}9j`EMqXN`*;vP z>t2+Q-up0p-8GanGd78&k=M<6g1f(^>S+FxChhkEgJtX{G2Runhs?;K{$uKeY%SAM z)R-FI`u_T9Q)0^yM-9~tAIBDgX;DfLPxEeJ!iV}3nR>^;#kKRk5Efiuj9Ie|?W34>CA>y0OF*VVadR+QKacTw3TgqF> zA|G`_XDr8ZR%0^Q`IvV{HuH$?TMEJ21@hc~`~QZXmngq#4j*|iSuRznLeKp@O5u@3 zTg?Q#p!Ohf3<*Y8GG7DrG2w`JODccfu_lgIi@^w$VaqyT_F!dZJ*n6isE$O)Q%_}t{Jj&KzVYdq@d=-RWDiZWx zz;zGomd|B9?{BERpQX@6!A8`Fwxh-|_s8-}A@w9LMv= zb05b&ZuhmF_jR4udA`o`+PaorKfHER@%jbfmJZ>?uQkwg-wV0nHT_T{g{a=3g(Bu6 zy5B0eRMFPRfrgaFy!Q4#hc8Q3e#vzUHtRk8zV9>74&RovtiAooCpjxzH;pH=OrPBB zQk~Kj(i!+@GdNG`TD+n90hg%ZL4y!*K5{L-KRnEkuIFkhaUyM;+ z!`JFdhKlXGtP~r(UqV`7AK;t3&p!K&oltGL@>yOZ`jVvJydpRo?5UihO}z$KCY=F~ z=zq3a6^*aB{=ndY8k)kOA=HZ|)MpshrlsbG8oWH8W)_L9!iRp=y<7I`Z6cSvb`p$G zKry(Z+u(5M&po;ulb?Q!(w|?gG%SBPYkHT=o#7$K;+3)-lLVN~=c|6-=MFZ#H^{5? zU5)M?xmo;)aj(23;X$K;jOaH4oxB*Cn9efkVbS|iZIT}j=kw2-ePk;B{x+xjmV3{J zl#^L(Rcb>neSxGJTD|ah-0&x*w%$yfVP1B?DMP2I`^51@cFGf*1`he|o%-hy*M(F+ z6egWn`m-el9cek-wCvhrz*x-5BdaYRJ>g`%zI6Gp%wq8Y_u3@eni{Zthah42Q$|wo z$m+xRUa-Q(OwJ|(%EJaYmAWSN%;=0kO{T93LoswgMed|lU zVcwntmEJ#`P5BS;?idF}6<*t}bVBme>aDEql827ubQTy1t3;3V51GRq|!zrgTW@X7-xsn+kLdj|qf?Pyf_>XqSnGY`J!9;KU9_P_D@ddxju;fb=P z;rmUbM<@PtIahaB*om|p2^gWi@3guoY+bh+gMDK3%pVts?Q~yNG)iNi3)NkRKc{hZ z2}m&?-B>znHw}*OUVbgK@)aTd&XqS>4Xx+%l_xw-?y{hcu8n!?;Zu$sW>-#E`}DK+ z8T=~W@?4Z#hO)jp7U7geb~oLRwFcF-4lE@fa~zE9gmsN&Erbh5z3w}av6DJ2u+7a+ zOf43>h&g-N)noX^?%cB(|IUhYss0v6XQ2C*+cbq`h)#%sl12Rrmwn7m7SGjI37=C4^)K}I^5Fv?GLDt z{v1BU=fLP+S_*u3arE^8&U_CQN%3AqU1JW+?MHfsi~I0Cv_mfxs(&Ei&qwRtnGuv^ z2(O1RgJ)S9@_aQ6KgIL;S*ouXsh=6C=}fyfyihlC`EuXg-OGoiJ}O-!sK>4@;kuTQ zZAxZe1t(;xE_04N79H>6%p!Pqm7-WZy4SwQzHh`|CqH43gyUE&PeU{0S+V1CdV~5rwihF@nGw+u1r9>eb8cm z4w#dCc2Yk_>(|T2SAArcr{^#}I|mis_lx3f`JLea-pzZ&H~{Id_6umd;0bK8ZpxE&Y#fTDFOW-eg$Ur>(G-C!r#ee}?ZCA;Y? z8eNfF>Lyj>y1$MK(^8F{>Cw)j{#NuzzWq2!&|UyYoCELAoKIbOw`9uWoA^92EWD!? zbN!rMcvp|`#^Y2lM3c=no!)Te9$9xQ>`%|-iyt!>(24z`^SUIyTkeWbk(k!eo5rK6 z;jD+wR;}M6TtCb%cO*zleim(0kRKP*HHse!QGm_`pFCQ~qB=04`|Rk_(!jZmNCK-r zZBH$w z=^|r82C>9R+nq6gCn0@M_b663@)Yju(f_%GA)w zn-w1>S%selfoTMj6D}*i&dYV@E%d#vC-*soNuD2}HV2QV55%qBQF(hx;UyheXZuO# zaXH=tH+3SV6aLPZG$C58Q0=6qO<%KfDjvHp_v1D0Xy7$T`{!_-O9Lawv!(>>(IZ=P zxXxG~=Rff79V1cZUrTCe*Ui}&{dk|aN+TFxT|}52vzT?5KWnO zSf5F8buiXDGWl3~^!Ki_v)>{e5=5S57TZoSw>pXNoFviT4ah~_97xCtW@c(i>VH(- z^9^-M9wXEwVMc1g6fnJ3{3hMMXrg-dZJk!wq27lxHe{p4KfbE(9GJ3d2AA;cT~F@} z9*b(OJYCAnBmRZf*zU+||Z1&zI zJTugV?Hmei?VlAn%sp*SV%S5xW`06vFCx=cWLtghQF8zyFG z#JhH_ZAWrgTDorXsZeTOC$M2k`@EiY44mgQeyGu1X~*WXLeY5jl34xOr{QDc3!~FT z!p&mDPn+v~Z+PaVK6VDjIE$Mob>YssSKr!>`%J&fgN(#_4*n1ujWF%tMeGRkx&q;5P+vhmI= zeH5Ctr)}7EK54@%OcU&RGw>%W?3K!gnp7}-wB?*n=S4pkh2Re_@)P`x^g@^b^hzr& zes=T;zsgOzDz)DSmde@%{jHC{_?^to5~h|G2eIOH39r7nxnEjxCyTW%#2R%dT1^oDB%j?`{}mhaWXxNdj_uOw!umtw z`LpM3lT z==sFaaSxh#IGXV%?VWt-o6VVvmHkKz=Vc>qvnOY6j<6mSs{F9(1^-YyDxxH3Fnv?e z)nn4IMwE?br+O>lnpe{F*Wi39iAvn%j9?7o30z#SK^^@`SG{jmnKHt+9cHRK-IwpD zdUSv067gp{eHa@m3n`on@P*-CH^?xZXW1Bu6x| zu?Ty5_y?!^&TlWj>8Rnpxw*RXJ-T{QQ)2dN6+c}$D@5ga|B?Q4!qCarOGmlBJ)h40 z`kYqBE+uj=(ez`H^z?S(p}a#MMPd&Khu)`Wb2sTre33~kfPS-&2p}F0Kv4U`TP$}6 zL$~hh8NX%r{iH~FPU;Y(-OjksR1wzC_YU2eR@Ra@=k?hMLf<%JSuEVIQx6r>99=PcteP8{JwG5{OVQYCn$bPrxalqX4(av`=b2+cS ze|Zgjr-7qSqW_%vbyA$EXgTYV8JSfLefxVte^+0Sj}ARLX;$vn^veiI9Fz~HCSQ#)|XdjCCF?YLNE=Ykvq2)(i z5VC?n+&Dw|cFE^qZa03)b4#c3j-0e8ZAO1%Oq7w_3n6{tojAT0G@7nHLptl8wlj@8 zi4)3|yeLR*24_jVx3pLPK5#KgI^8Sv#ElLZ_tJ&HD{dyw;aVor3m9h0(q9jEH-992 zeHt57$sDi${9AjnK4-;7c4f^Cv(0-(*QrJ^tXlOWPvq7{johRRiyTK;dKHy!*+(+W zaDjQU2RkI9KX_lg!^T@Ha`D3xul2y-X&%8EDdlsYtn5nyQdiHN%HeY@I$>SI_rfmq zrsa{Ma?z}_aTRk*?(otdcISuG8&ta(Lb;1Go>C_k5AgU1;==UW3@{j7WBo3{itejd zE-_|3e}68waraqn$+2Xav^y)lv^xTdVW%;<7S-UwAcEtGdqP5SMOd}=Q%uM z=pJl*`o;+#(`4PL%;Sq-TYyhL#@=gXW@PuUz4R zp6^~Y3gUSp)bJONi6<_&98i5``Bm9?eXU_RfPH5;MosfTdg-rNT&;fl8-A+zmilV@ zqF@{0$LBe;z=!AP=_IBylflyFJ8joo*4}Y<3UR*8JTa?<1ACudp6osl_nQ6|2i(Jq z_@iLJ{E1K^<-26PgJ5^-l@AvBj_?3@M$0J=tGM%DUl4tcrK()3xLq?oIxJt?4=(@? zb??y`H@Pd`TLo`^otYag_bP9_OgqPwSKgC)JsrQQvC)r=CKHzf=C1U6onkGDd;c|z z-7(;qqNT^k#K%WLpsPeVD!kz&C;*^E@5T6p;oxNxf~M%@gj4miW;YqQHmcGvGTjK5 zvVlW{NXsCPp)ifilZMYcIB&g|WX`;B{o$XybK_ypDK^?2Xj^S*J6)45$nJ8%=>Q}r z&&1Uqp-@uYkC!yR(z8Fl&!Q(h z8CgytAFirBHhR@a#phMKv2C?Oa9v?lZ)}U9pmF(<*^S{V(^i==##hb|%vueoifM~7 zjm~+Je0I)y)xMXTR6iy!GHSR9!ZrH;u)&_o+0Xli^CRZbU7tKy-(S}Q6Xxf> zXSbzr$7b$xt&#B!4ULP)k7eK3gi^Ymy)XXc=r>X;q;kpY`+Eg?u&ereSfNDk8883(LTSyi)c_fGLheE!$ z`fZg1&bMACa6de@Qlv;Hue253tX(dYp15Z8>^d=VxrfnorTa5OI@j%UGbMD}1~x%j zM;m(>3#0j%oo7qG3to$BmLW2xbyQyf%eayYhSwD9>BH{949axE@7;}wgiW{oba1Wx${R4i%UobH z$C#3r)W7atgiQR=d2izci}}yW29eVD=fo?Kp*S|BtqNIYxJ805zySHas6+LKUrD3i zOtM-_+Qr$wJG=7QIpZJ?yeLMXk8M`2DH`TW30li`(B`+`Qq;uB{YiALO-P+G3T1rp z=oGUr)A7gpT<`TdWqO!P@{7{;mQ|BrF^|5SbeuBa*xX(_gycM}2sdK+bx4F(`?c$y z6m#kwwlHQxHu*o#sj;3r8E;>83Jn_>k$#=>tW&3DfPFAFjAEm3VS*DzcjJ9_>xrza zdCZ#21NRS^2L)GJCou+$IF43ZvGjC3{umJEaF8cTcOw0sGnd<;*IsL>?=h8Oe6V{e zm)6|{{L)Je?4N>N`kGPp^Xm=wY{35AeoTkGYL7P+nk=8Fs-4j)pdaOqIOAJVE(iC* z^?4cuQxsI>oHibjH9ol6qW;KNUWht$RA2T)dS8!NOD5K?E6?+KG+{SN^?|PG#viGZjvtI;64u#vcEWvGE#S$&gDKGz3{wo@-wvLEOdI1>r=Zol2!v_h%=t?U}rqg-{dG=@YPGAHk zS{V&4{{6UUc{E7%&)@x6h1Wk?eleumiN1Mp$1nZZipY7>=$&XrL#B&6w&^G^jsLt+1R?ThF7W&BIPdO9}9n%eG`1_kNvc2+~{WXy_J;}#L7Jc z;@6YY`LsxP0&L^{+qw{PVy5%3nWt@cQ(1{^@Cc|LN&z|NWn7 z|9_G!|KG_#?FxE%1$;oLlYAtab6MVLSzb*5R6w1Yg8UYsQc(Wy1qj~1oPaPW6!flJ zL3A7h9sIus%A+}*{vF7VmM@fd;wU4aixSq^wfoH{eFAwT zENfu6!wjklzvkXx3k+JzMB;8+`w5MGIC1O1jnj0O%-h0b2<{%WPXm_GoScB#!oM`t zc?-V#OG1bWwGdFy6)7gDI3wr3Pr)`>Gxq`Q<`SbQ-W1;Q_-8vk$$OuGI_Y=w7J!K4 z^P9mXBN4N#{~_UjPeILpk)Y;+zUTC}%w_Zz`b`4>Yj{Z}dgp*$Y|4Av-;A$<+M+)Z z-jZ%e2|Gq3qKPhu0NjvdF+IY}!odocG4aa&MU7j3)p|prnFXJ!cu+K*OGiMNz^fa{ zbJ^?6xz6r1Eg6QE#&nLVb>_7w#CI%>JYK!~yBzq=_bZ9MT{l1ja zp!WMnFu`CeJ_nDz!OM{|FLrjL+CxF9^TiGAp{;AjOppdcsEYJ6?EM2~FO<_6dOk*< zgtKAy`Zcx60wZ2I55yxy@_Hl!98W7ASTi-#kd(V&FY0#*-b!qJ?)h!2NnOHwYgIoT zS26TtZoLDeKo@+HB%)mISU!_v{ptFWft!4_Yey`@_{&?B;oZ%asTqg0j^zcFf6o#* zn|J@}J%?Lvkv-(CFXdl;e-0XO)To}bZQK&5Nq0V*%yIYCRO#b-4!_0&-*`TsM=#_Z z*A6@V_-X(+&Z$e6(Pv%j(5cI6njA$?IibQ=;+d4CxxptX-S`(~M z49wtuD>)nV_3gsoQ=7;3m%GCyO3vjU&}hGXBxg<6ULw5UCpQP%8d*oGpk;{r_k+R5 z!zVw#u6|qDsTrVg-;wppMfZ^$bb4n|t%^z3n^(ifXO0-h6;bql4z64iNjvnWH!oK? z=8f6nQ~BI2e@OwIo}hadqn(aN$Gvj%EGUrL>QY0G#(yYtQx^L2)E?oJ*YRCDN(&X* z{X}pcdR6Q|6{*|41Y2uj@h{_1@OO8gJDx`jrT9*PB`t`|_r~MbgpJZ3doG(OJIohZ z7PB@fUiUQm+sFa26=h% zY|tOBs`)W-2gj&BdR+?U#FL)^HTZaC=GX%=)S)^xpCwuka$8FDEZZTWKMU?z<9VxA zC*7Q1DM3ZkDA6Djyy}vC=P;Z>hc2FYhfHowwEwv zlZHL>hH$A~9q^St***c26&FbD7kdHG&Vv%6RA=sv{}i{%^qrf&np(|kr9kn&Ao`eD zEVYsSE|ucyy6vK!*A%7wl_s~65b((XUi6U4Y--kodTK$jS5yD12SW7XW*L71aANp4 z+9vd31m-4Y2NR)fh*7>By)V>Czzwtl8f}m61BxgbeG@1oLv#}Q1$d4C_s_pi`+{EV z{}96vJ%Ua^3-4$6k1q7She`^jGb|0fxL+iAkFNWxH9&d)AJzw)Uz`m87k!}k|LcSQ zXZ3+P%DJtO+e}31UFEne`Fn^17R5RXq0V9<^6)r{oD8A-UrKXfR~2E&{|eA2KZEW6 z|4&2=ikxW&!&;JIj%iU?QYwm!y$Ye6K^FZxSf~`lECIpoIJ`{+w*8+>F_|F3OHvul z0){*83Po*@_oM9R;2VX9S%8cS%0S9BDz-}z1_L>%{*zN7H*G(b>JDBN6!SL?9Cuuc z{Gb2!!?21NO3OY4WJ;Xue}dY_U@ah%sJ{i4{wwj{tUx*VXWA$@ij032fwhFoq6#cj zQ*$8c+JC8PZ)zaUQ5A3Sd}(oO&p*Mi@4=*{fQ&H;ohqg#AIY8oe377ewFho zBP?DtyYF>pJL+UuLXD@!^3ZR^{uHJh2=zlwK*?pyVnCV^wofzmsuEJU6lS6NzJg^A zYxVd0%b+AsWtX0uLOGK_6`lYyr+>fEMxiQ5hR$z(e}Ko6leZd~$vCbx^EDELg{rAo zuC?&C*?-r&Hp+I+F)rQOQ8@_}0h%Cy#G!kNNt}%3%5-D=HR*$l@z|@zNaZBh40uDsnoI|xlCWJg5NSF{3?e6v%U*>^gF^=3 ze-jMr(`HvMiR~u{$8!OG_zH^4H6_s;eJP*>d#`tt)l1716Q$9g1OxCgSbEMqq}rn9 zfa?2h+%#}pU`eZ&sF~X!oLy*cxJ03!@zW?tClNb*gLv4N;$G-(m`7yvUFFF%8F>Th zs!=_|zYD8F)!}#d=l-7HPd_$99TH6wVwLF__EAPeuUt5KY@)KKnlxYEE@`1t**9d= z7BLJzkzIcEcbj&#MZ(^G#AqVp^qf26KWUWvhEO(Y=pynGo!%#;a-R-)h`f}eXMJ+)rCm9v}|5jl(TG}LP|k3V;#Pu>>hOy8Jh`U>%aZZ;Gl{COp4a!NAko{Vs(dKU33u1J&xzRP*;(Vv7XN9e&Fr zZh@x?#I|phIx5GOJHHGOps`^c^rI1V?mZRc;V|o;Zg%jO@q+9bMiouB?a&PP)eatCAEbxt@IOAAPtftF zb>4*l=^L0*ntdMg3Og3{flO6JFjX?{OBZ5rYAcknMFZ(FF91p80FNS}J}|;2l@S5w z7TS+*qJm|qat{`qk8d(pP!~Ed;v%5d<}d^#)FDQStnvbxoPrs&kB$G8EPESp)IN9aXTBvhlRO}Rv z^cifNT5&FyLd0fKQpgabO;2gZ*4Q~?%b;e9jX+9NMJD%}Gz)n*nQ4~PUX;zuqst&q z8|2xAi%wvX18@9gZRgyrOHnzwDA+ghv?OeHYI_w9p|ndUeC&#MLnxbDIzo#N*o_#S z$A0!8#Rkk!7x19{9OGocFoUY4?HhM%(9=0UO$Z$WUsP?e=f5c(4*I;}0QBv!Wv7c! z9(ac7_+P;eGpds0q#b-Xv)jDMI;s$iP}qWK#&(p%s9EGOA>?tP*g2z1&rV>Ef_{%M zTj6qywx%_~$6{_>>3)eL5i~V_n(| zc7dS{*8JGeMws#8@Z%Iojxico0IXXi6tmYCcWK(#0-sHfu=e3a*`>E3aBoqY1U$85 zkRt_aB8+-Ri39VY$vVl9t*&F-^SW64RhT(%gk#(#9V2TyXYCxfH7v@`tg#TMnz~mB zy+NtYetOChRQEAcR+*j~WSKI$M(k?FgPuakKdw#e$`sHi>K-zib5GR_&Ufy;G!&GX z!bZ!^*;utR3n0B3sbE8?n)6Dgklj|Z6vN%=tyE`)=+~IGO6*5rvd%9fA2D7{aef!; zKZGF<9HKGoU*x{r_UTGpH}kHY+?A-#PCnqLIFQL1wh+M@!-&d6V5F=q|qXr2oI z4T{&)&8P0OD2@?!M7Ei&nSl+k`A@SNVqG!~TbLAM8)v$$AncziY_G9q){259T?Mp^Q#C0`&auoWc5!vZ-|-WffL3b8rGVveLShgZ$VLQGNio+Pd{_~n{y zx*lW{5R0JDqU1+V4lDScDB4D#R7Mad-lnr1+s=kljjZhz%WzRco1FG=CN%e-lZC6F zR={QnlE4JyB5Ed70a{}QY6GZ7FWAlqzZjuraCwzryNxvTRJy^ZvyCtl)BX}rGvkdz)}hGnZu(r zDYniyk}#e@i>le)TtJCCze^$@M;cAqaoGoPb&_JJV%+hAQrlCaH{k&kE@(=W46DCD zd)6|Uqe583pv>8D$9-Xhn84JWEGF_Z?bt;)Fl=xn3V;s9lcT84&#J4nc)3iDIoK{1 zr8P@)GbWQrt7KG!X2LN?irAQA1w_M2l8k*w3n*W#BAj-H4^Nixn4yeR(6C_hC31Fd zE)hxEQu~!B1;yN=LNk||0uN+iq)~iSt2J1-D275xAZKPF#CECVAZEuD3m7b=nV_qH zB>;J&E!7V4TtLk}puz$iCf{OfnjMKj0$ELyMMjh;zZ4SN{8?PihVvL1j|qWLuoO02 z_7XiY9PE_jzz7?IC=?VBq7sB@A+1nX;Va(xOSILEA_HmNMm+RP-< z6vVvvAOk}|kjKwk!3Vy#vp9%}Q{j>DHv9%zx)eLLl}8zQg|D?kHT06JD`r|x%Pdk{ zx3VxZ^e8ewIC7D62&~e{#=e8$XGv@@3$g?SMVS(5W(X$k!iH;kF?mKBfCJ&i1Bj<~ zn7xfzlHvdY-wV%a!i{mU;Wj!ygI80=77{$X7*SZY1zZU(4a1TYVHQSZ5Czq0$R|ca zp`45GuGK!;VCX%RoEcAnVD%_d)r@u&84f{XWKb%34Zk@|AtJ#S!h3=^XrKcTT;cv$ z5?2s23kv!T&>tCww5E*27(lHms4A<~+58tkp)u{ZGTWHolmTgKP?8EmG6fTnnTj$I z!%L(oVHW4VDo>hPmj|$Thr3$qIm6_9 zHt%s{ez*@+Z7IlDo#c=yTK`Ue?5ooVBqeT`czKJ_Mne}%<9aQf=}=Q$0r`>+Wal!4 z7sAqYPkQj4i2%G+VkZVf!(1{zyeKvF5ky-iRA?0Qx;aL4Q^10l;85*@p>6;WFS92T z;ih_eyG0Q?^e$7Hd@D;Iia>&TWBY_m!4v01Wr#U=z@t;UVl<3bmaF@WI{@LY7;?MK)}W%%)!91;mq*^@y+D~`Hj zWm8fSWh6*LHhOAWEP#Sy3F4!|VhUk{qBi7kgM8F;ed5FBAgG~TpnrF4tr_+#Yp(ys z?id9{l%h#(Sdz&MyExbYB5@ao&Dn5`iBvX#f{6hXMJ9-`^A~Qz+!?>$k77joTJ&Wj zwrQ}K?MOVAAr__E6I3XmhF)C-8%?hxcBKW2hu6gF7&|No2fIm``IC)xNC!3&rJ$OO z&tAiBEuysesJ(EMcB>#)g=Rhm6%i{3dP%=FiGG)Me0H}Adte*_(z720N02^QQBo|7 z@Xuh!WVS75w;X848`xza4s1NCU>-j3#zIDawHB2qk+~%b7a(1w0V7S)0?Q?_0FRG$ zQm3h&liI?QljE5jQRo+E$hlW9WmU%n&&niw%qZf3&Q~6QOv@?5aLuy4W_F_~HAH2ioDo{JtD-o9h`?jHE9<~|)jwZC(2N2O2v)2=TgFNmJ&2dXT+SaWRTn)ZI6p_oEf$-5ig#u{C zA8ae8VfV?y#HwFGh@3P;9^%K*dw;5?u3iYs9~%PwduQ$(J8UvfFl*^^5zrxJ(C+S7 z8Pv#*SKVUt9n+ys0l*Fm$b{TSBFS0;)z8c%>mm?M^S{Rws*AK(LXLzn^zP@1qYqYGnl^Q*J* zZ~@_b(ryQ;;3+-v(c#!_8a{s#zkv=LsMUZjZ#!81nF{9>}Q;E{{brUX@j7=CrphMUxzsLGi~|J0y|UtjK+9 zQCay|0g|HJz^^f9R0JOQHE=Z0XDVQBMf*kYtX{fL`kawNs}3yWFZ)K5UMup_7t_Wv?zeXPd`!O)6(WF^kT}%@BC1|aqJX<;pD5~G|_N1&7ylXFpqG5H^G+@cg$jGSUTq* z$aV-qVd`IFfgF5<_|*q=E`HnzHwN=z?-iU1fohqZBYxL}+i&bERnOv#paYcv5&=Do z*~Kw|E(9?&&imq2%V#-xlD=DwRuEd zR0CPaXv3;u8z{7BFnyOoe53=^a~{)po~R6r{aEsRL(wu;(OY|$md(pVis;Yr1e(Kj zq(Zw8;BJ|Gdr=HKIO$>zl&uG}folRBh*qj9B=fpir_+Coybf|4&_zaJ74b4G7n-rB zH-JiB%jAKihFAhA?`2)zlrrO02OfqeEr5>kXpu`AO4((<{SJSN65;8km*{^E7`s)G#0iN?GHo z{WsUNnQv5=o^Y`ae#{I{`>r0(An~$7nfbJD@qpM+F&#QDVX zb`(Ub;>EUxVB_?8+3RcssqNp@j%}J`n_KNK8ej3x^1s`G`J5%39Wl2HO?obO)sHu} zy_ntdfDs)YQ>c9Y%dr&Pgwcg&cZOWu>LlzGbYRP6Bd}E|gp>da+7-L5i!B2nJiL9* zV_T6z(ynD2qeNB#MGP3>$bpSOO`xZNpFta+Jjq6)2fmsNJ|Ak9JTN0r&dr28CO)8X zWl@j?b~OUuDAK5MaqU;u=<+^d1Q@;W$xo zj?&DXx2V9LXt%I^g9l=8u6lAnD+st*+jRhI{dxJgFlB}wD$Tq5TJ&zFMU;m% zVx?}9fmQ9UaH2a5)F53vEWA7kBCfUcVA|w>vHs0D-E1WvFtN)rc5z?u#yCdBS(eH+o zuQ!HN^2YdA=FEPfAC8@${s-pl}(GE26+Q zS%ZCts;d^T)x$*3)8Wty&W=kFejEDPUoK2uFhpzUbXOQJUp#H_ph@rD7Rj~N%Ho^tsD)|7Sq4Ocp8U}(;oJb`op ze7vj~se5`{?kbxK&+5)KwsB zHZ17rnIR$kGEQ3FSkn77|6^05jw!>5#irIak1a*v&9rij?lv0=s>HNO;~4+3UHvrf z_TN?-{u~ z9@1LjxgKEbmBGq+c@1Np(EnMm>8Y|JYmPy?)U=r2@~@+F#!ozqML*#tGmf6|yAS3A zd=kLx^sdv{Ri(3}IFEW9-cHy?-!2hOq))O9ylKtJCt0&Xw5trWzEz<; z(9zF3(|zt;@4d+j`9T(S$mPXPavtZ4#mUc;^K&=lG7z84*rOBs^tw&1E+(wmc zkA_QtvB%YEGQF%ma#h(x?mO&(k-6@)HW60HIlO3kuKOuPmCCAJQ$o$DYb5FxtrV1z zs7vZf9&8$@hL{cQQh_@s4v(w{qG0JU8x$M~M>{M+WWx@6CIpe-kUw6Kri5Tago!qQ zvSS$gpezIp+lCV3TRK%UhmT{<)-|CVJmx^KQ$EE;+Jz4|;}>9TMH4{Q>A#SX8iGAS z0_(B*)SeNiSWXxOEaZgga5*R!1;ifNFSW)1tyc`WVPk*UXEQB0`=YlmmT&PPpR?N6q#SlY=yc1u}#evVzT-*33H z^&eQ+`y-w|YK*^uV-Y?U9o;6cgZp}?H|<)(oe~4eB&)Ab%^nLLiXjDB@#bZ$o z+AFsPG|mt zddzmmUF5vU9hZyTXR9j@nVWOGImuyGQN4Ua*74UASeRaCpu6epTbzG6R`~SSneolm zEz9BE`BYvc2`5Tvb6cl#DapU})aPp2RY8FtR^}xPDhd}~2>o#D4)|)4l@C*xc&htp z_y&W|hw=%5s|TfqiD!?sn?}POA6ZMF#qH~Pzj{?w))~cJpDtPiKl}QF*I+n^in+>U*f|3G0i8r;NZP4xoKBEy?l*| z8ohh?sMxw9r=p%96*UEK4 zZ{6ox7S}!(r4vez){TbDTdih`vDKK0JRe3p^^|tKdLm3q<84TA6c{v?cED;NAUJE& z`>uk$au5nEC0A>;*jmpFd0-Mrb`^Kvt?n!&G_Yo~ zlf(TTqZl+;!3gXwDWAl2TRaV_%h7)fj2C(d1BV`{hg?CyWGTe9N1m2s{v@p8^kr6|C15p>u@8F5107-+l)6ML>UO7p#oh$J zZUE{cX(|h0A8}f}x<|Ho)p*R(D39|n0u=a!PD0I};8o4P^iu!nKr~VqpQy9lc}WJVnD;*KYz)J~Pc~1g*5n8{yPx*4Y364V*39)9y;qbVV(eCYB#Wt= z8Xx_-U~ed@y^ZZ-)R7~Y>%C{fIaJR|>hxUT%o!RFFs85hQM@~zbE6v0ELZgP>vT8G zi0+4cX>x*WgCTyUTfguaEP#1gzFjCK?C#6Q%=L=jQ-*be;=|HN3X!TOyKmj3vJ^pY z(Atq$gk--|tUn>MogQ^ja4HRDg%00Vfoz>&AMkKwv!~d1hX7{7b{w|yMV+V8O|gu= zYO3@-8dh7H1^y_)r3e6ZTj*RYr(AvxyUcdW>cvwUjhT1Y2up&i<~)eHj}>k$~hjEcXjNzbe5Cfnvt=~D?bhAD;#KJp4vjEBgTcDY=gfHPKr*`dy@#B=AsA9 z@!3|KX@(1zyLJat<*#@>y8+A+8KJ9~$BD~X<*SO)Rc+0^!^8cLq)U&++oW+H{IbQE zwYi#C#j-iR`h=rKOib2!1GxOmcm@sHVG2c6&12_@IbTz-`M``Oud#IMB~u-a!h`~b z!-fPWRAGVF1d7t1(4_gx1~O&HD&m1#p1hnzDWFH~n{wKvlvUG6xe&{X4#%jtmz8TM zItbeylm#-ad3qSpOmN?H1pU?61?TEQ#?n~I+UHXtv88xt9G)u8-;d;RkHAEw`%n{& zVSOk*VA&!gs{+7)D=0VKSKNQr0S87H%g^)QaXell z+{29ZsN1=C-6rK)wU|gL2eY7eI{YMJokCQ8RwNjUVGLf5PbDoXoN_Y)c>s z-f#bUf~T&;oY=PwVBC14;5^$zX1<8b$T~b~_RfH==5m=p$S$1H3`^0i7Hg+x73&{} z@pn1L%14EV5H@Z`^t-tmw9k0(%jG-eA|Pe%Je4r?lt3!WosRS@4XoT(kSvOkbOgDBJq~O%xB01ZR0^u73 z{Y_9{O~;(fcEay*l43OlG%SH&9K-^-f09g zKeU|iFiStKtdzp((WesoZn0d5f_c%VNMSqryeiN7h&?C3k5ar#(|IDjrY&AIiKMZS zfc&E@gO*DS)D=a(4ujv-a3=29^3Z1Z6>-yBzB@+h7x!!ty|=#F8@XM4;p2H*0pkL{ zZkRJc=Vz=sFJAsS{a)ivqSb?PmQ&-x>VDxZew{8x0?X`Sg(dSDUM3|D!%;gSylgD~ z*^gs6Fuk}iZGMw00TsV048!qYFCXV=k7W*+vDv6_d93K{pN7&=v|`{MD&-rN*mO&r zc8IS7Gn4T?ib=UpbtEvq;r10nCi&4@v(EEZq8h`u6U5a0;k4wUhu11z^K}k-W-W2- z91+W4BFhLWQS)iGZ(xp+KGpuSkmmlJ0_R!7zj9!5bQc&xFkBj#sP+3h>UNa0jz8bx zO)rRrJ>0sx>W5+CG5($Oc(Xu5IVT0uywF=VK9~~2rK{nFSMu(wp-keq8FzU&RWNQe{b!zi^!4uQ$?2a>w=SR5wU=Gs^HC`Fb*iZ} zQDC9e9+;Te5@mkMKC!IWgvDzZ5HlU?D@f6WJt@CXwn5NOh#)1~Z}#TOIB*q@=Ceun zwm)^|z*B((rT^`r@5W=~ng#${L(SPp-(m_Ct_U~j(y-LPdL+Oa9ClXbtP`il^YAhP zh?&-`a7J<+7}n|6V}!{)@CXrZ+jM6F%5K|%mR6Qp4M*RuDulr|7QZ6kI=sC|ba!>9$^WiRcV1N{*ZcATI+TLuBo z^u}LrlrlWDGZMI^^kEW%z<($kNVQ$dwyq$LSNn*?&*bB%*ateCHuNse2r%M&<^an| zmj3@>@6E%STB3YW1|>2jKrjj-gD6NMa||dRV9Sh&fHEm!AbH{B{@(rLh7YRtO6}UkuBx?a zt@W!_uJmN$ao#%Hk9~jMu5*{kzj^T3fcgDl(aaE$HA$>s?{6u8wH35LH&48q?#)-T z%);kO=W@SP^2-g7rO3H+137=L4{g#_nYqz^$fDVU99u zcQAZ*`+uRoXgtzWxKme$*u`D~&;o#A%Z_$9-D0Vi1h z80k>*TdTM8>N|yJ&yX|u?Z*wq**r>mV*ld@jH-fjjpKR}t83Pl*!XzL-u8m|9Fxpd zRMjey8S3bAvQ(5hk4`hS3%(YBJ}bL!XL-%{XLhFqTa;u9N6!n5YzXQYo4{1z?DajH zEpk2%F)kt$r$5f1x0P`gPWca(Car)Wm~ zviw?-MeDeNbf^q!S?wI(!L^9BNp?jCJT=>tZM;1t=CLK#9?H*gew%+&4*)~hJiv2~LQAtExXsd1_p zk>`~=fh$0V6_fRd_X9;b3w#J+``T^$+_&nFOLUpi_hco^pz1|F6xsv>FGMN4vK_A| z^r&ScQnVOyiuI-n#WuKbU((TYMB*t$F|kz;~)swyGto_|Sp8oOQP< zc8I2Up#sM-e)^G0@R7h@hoDT{a>McmY6N@#Vq(Y5IWw%kx3 zS>K(a>7_;s@CI``34)cK{kHe>Sh=6B4zb6?ARls&QxTo1sO{U*7?VFqOHnO$wj>&i zW3vCSG$X+BSQhon!jqAo-|SODai#pY{ygfSdg<5!ty;xOnuH&vFk7aoz{+Lwws8NA z=Jzkje`--?*0;JQO+_G1a50!{efNac{9vT5!upBLNl&7(7w^+49Y!JvZEtI?K*C+! z`_p?iVdnyx_3S_QUK>$=%Ws^TgbjIgVhawkJdvy$PyMdmPDL^;vmMT;n>6%Wiogoi z#0tvQp0ckkn=kDhJ!@Tf5MNg3^26^%TzO%CbS#uokohDvExa#l^qB(DpFg&1$cUvR zPJp;9BAvd8Br2H-mRiIzIQ%L;l^+Ka%`-PN##9hLBvPAin!({cQfv#ZzU!&x(n1{# z`!9RD8{=N;_#+9LEY_PWDAzWVuZC<9{6pkT?UM?r{e}MpOOO_0BQqU zkI;k3_NmR~E$$*8;(s~1v?IWz8o9N6K2l1Dh)q;2lvHt5#he&s*>|J1LvR?i3%L1} zY~m$^nQEhJDG|ACH>;Y$4aG%Jq)clzJHlcLWguE)Cqdr@FoScbrCMGV26H{2YdPUb z4mK}=C6>)nt3}9yu;#0`N{`SJ{&4m0U$%CURIp8$x_`rM^Od-! zaBlGN`GMjBu&%T{ByI0=bmsd(qhW>BVCG|+44xOTR_S+@8rhM9&+^%rS&z}RCG)98d-W-6mN*7bUy_J} zMIi8c`FQIPRYkmXQafOc7)*s6%QWXu@*-n{J7ytOwogwqPp*??>N<>%Y~&B*IxYsO zxUxlg^*VSeKd!14ae0zus>jt7HmbP}0cKP}q^exYOF}Es;oQnnxNM)9Z!5({+kJ_Cr=*}2q73@RJTvhkHiCn?PV-2`DB{;^I9n>umO?cw1u#kE;%Tk+SH6Oq0dB#na|`3H}*!9)q>ufEzlDz&qta9j~a&WZ~OF? z%?}kciSMLjyqinhgcWbj)8vcF2&+}PDG}SynW)RM?plfYd+A9S%xCla{@ipaIARp2$i0`SRfYRu*j=-Li9DIzJin&mYM999PR zIPyt-Hc{Cx5t69TaA7V^ZkkU5~XP$0ho;sLJuyrCs_vgPXa~}3f%Ul z3r{zrfQ>K;#oA4jlnUuCLJ_g0?9N3-A<8?6M+$=6=os;n4tn-&_NUt<=rvEWrlhM935in zX%+RN03Y5mAuX3jhpV7lInUpr#JC0U5q+<3rjbP>Z1(e7G|~$5(sf>P-!1-yg{r*u z4HRLc#S+U@6@#!d)MzWG`(kfe+*b+4cqM}Vbl*8iq!ao}IPi1f5u=V}lp1U}Kcayp zH?Jj50<0l(Y#@S~lh2|yd)7j7W?d^_I$ZH@|FtAW}uV8bVF7}=8&rrlKjLh+8 zy7$vNn*ri9G)hq(o7O$yF;CZ320$D?_XMruJbEgT0(rD9>99+YCcv2m|x^ULehtkI` z97X8bW^0Npu|5}@Yrm}9e=E($PkI~qqO}n zY|0G@O!WIFhP?r>rX^CjcXU3>Cy4KD0z;2Zh{h_f(GElojTrtM*=}`2rf;M!8Ym;kKD%~Nbf!QRY0$gg$rS^Kj*f1 zq%1}&l{z>d0njoqmd~dq)v=w`i}sk&fo$MeuXp!8Yu*^>Cw|O({P3gE8beLD1fH;5 zaXIYG_b-_!^KCPCDmhol*Y%!98Q6|98v~!adnA09QVb9_OsKtWty`}8D)-C(pTIX* zY}41Gc@A{W08<1ua6vCdWVcFi)NNm)3m0YVjuuIq3GG3WB`5env5|w!@k!Jh1hIy5bfn`U#E|@8~U;V7}z|YTEu{21^c-xzM)UF-Oa** zWuMxY)dx<~r7J9sGy8#z-p387(-dqgXBkcM&+pBkwF_&5r`* zg?J*XE5<}Nyx>nv8_-y}9Yy9TCVh0e(VRH^*a$pqUSd-R@yI3DPTpc7rRZifW`t$U zGO_CGHq|z&cSwmMi%qh2SZ9k;z4;MJ;@~#f2+;z>=vp^rel`f+eT0V?WbfTvTA0@^ zo%`}>+gsO|d@KMVu~_!c@$-?e{#+F0C;XuorpVeNTC|sr0k-?bF!Uf=N(-?!Zp-?>Q-#6aj=(`o3&!(t$0ZC3MRZ9w5~l?FubDCxNrdZgOtRqt zI2_RqytA@x*0VzNHO*Lyrt~fT78`Be@R4jPPHyabvIFWg4_^{?#3$qv^Y25GgHdX& zc<*`IKxEpxPwpq`KL|Us|5iS{XC}@M1wuy! zS@#G#_wFq5sw(MG_X29Y+vIngPoFm9QoXQd)$AY>C^GZvy*QE5;;o3{C3;* z*_AvBzJK=SqYE-Vt|WJr3CR}Lxd0V6U!rj09qXf$%Z1B5)hbpMkreE!n2;%A#sh

{Kc%}0Fo>6=k{@Sbi@w$T$S>i6#$T;e;wA?)I<^YTM~z0vr1EYqGO)TMW}pT7;Y zylV5Ys^i1bK23a+lfGbOiadXtA@`2BIRHaC!XPjIRg#LCK7l}DD_u4xd4K2#&En1k z%tYw&I>`kQ7nMdg{l#;SP$zb{x^s!q#L1NMy_1hj^UAWRK0qN0=$oTSFcteXQfm6{ z5n1M}H;Exd<{z;a00cunSh8{(&q%x$7@i5jI}Vy=c?7I4apz7c({+XXz!PJuvi+)zVhuMN zv=55yB7LIq=Be8P^6$TSKNUBG@A}3kcn8|7SHQ^#`>X4Z+o#|Xirw>|UAfJr2(Ev^ zI3_~BmgkXON^EqJX@-em^B-WrIvH5%TKu(2swzALlsnuzoL!b7wN1@NVw^3CFPUAOsS zI3*%LX4NI~e5tB9Q=hKeG09?|@9E8_Ve_8!FiV(fPZ7vtDr5S9lGrm-~;z?`95R2 zu9am^jcPHc`Ej)bcEGk6n8D7@&3U~Rl|VNMT!5R9^10$M_$0L3q#hWPQ#`K`8Ljd$ z9F5@&1?tjW@Gw`e_T8TX!UpFDqwWXzxK`jEem}MbFPGRgC|5Uao1lIE_!|3yyxy8# z&Fo9$1B^#Mihdwq;*J)@#vj+jA_#@oC6&fteuhN!=n+?p0Sbw|<33hJBoefllb^}* zfa3)yk}1+35Uq~^E8A5@u7s5oW{v<`cEXc=sJ>x5jsy1vSvw*rXQM0y58E58%1>bUIqL?Z z#Q0W<6q_z3efSofz5VK*kZw6}roxkeADl@6v+@_m)_0DdEPh!rC=1 zEk3hT6n2XlIE%c-8e{rw=!@h&SV@v!*YvhAt8gn1Zd*3VqL&kPTedG{+SrCCJ_PXf zoomsyih)CHyrCRFqOCz2F02!k%*R?RZAo2L=Tdi{K`+gXbaTpW>j;**`jp$_^>DpO zb0Xu>lrI1(^rLr}0m)cD|8eW?U1@36H z9is=&fGaQ|tBBjv8P?f#TyC4QIFAl-f(=#BPCx~28_MU7EX`IV*@+}(uOGwo9i-46 zS)2&#E58z-=|pEFO}I)kYh1oi>5>~DC*{SHL1X3 zRE4MeG`9~{VqiSF?^@6N)so1rpytP#ctr$h{Su3xwAMn#FV#r~F3jbx<|Un|iKTDX zu=co#pH){nbf&6*mO5FQ_U;gU-6u@0F0LT9W45b0fHB(wvnxM!by62S=n%tGAyAHs z^9HrG^lGQXV*7#(7$=KIGR}!|Z&zAAj6UqriSXWMYxuc)FWnn0ig_IU#e4@MnYU2V zaxS3x2P-5(mxU+bJf=$BmPAYqunw%w@HH5>x-%OwidhKqRjto5toJhk>kTNCB8TJ19jN>RVs~GQ zC0bl4RH&_N+pv!)vbS6E6DtMy6=b{(v~#XJh32)KC}gqG;on*l2!s;SOYWKq6iSVBdY(xa58w0$UD)i*sXSg<|poOwVH8Ou~UGUCJ0yvW+j@q%D8eJgVE1L)X4R_;+ zl}DHQlZ}{V-iev&M}a3JiDys!pjObZuHI^L!!<_v`T4dfD?Y z`Q(!@7y*E-17+Y18iV&@TVRX2>>DXM)fNs_ra8w^@)(;XR7@*+RHDYdJ8<4z3%;wM zLD1rzXs_d+dniRu^< zy*t7S64}Us_e}RK7RVSS#!sp7-+w~I69;pawlK)>o+5LbD3m@Jf9a;}JA-B5AtUVA zX?{fJf-8@PwFkJ{kyhBtB&=OER&#=o(U4tQsC?q34(p2v%QzKVQelVUV(OI>=Y@)3 zjVF-^YcL=iEOestaln>JpSENS2}S8DefG;K@O4smMibH!yMMVXu1$!cH<ZPn4^rjAjdG3k!mmi2W z{!4ft_W7?)&vkf($zD3SJQwweRE-yRWaeSP%DYkDjAomRzbv*Uk8ch9#?1UcHxkCe zumkBwTb?C&A+aCqDKs8%8D6lw&nuv2O>=|npwJ=e0&H#X=Z zs`#~!;aoAWbxcv7M*$mmXMJJ|NyH9F3Rf9j9od1m6j}beA_tKQy(27Vg%s~gwY*N| zEcHy$M)*CfndeRZp@i0(Fsp%W%3#mPDMvmcMu8*>cA?nx3RHr%fgPV%W|b}a4Huxo zV=UEiu+%^~MmA&HNJd$p0Q1zDjK~Ml9SU10Bd@os<%y|YYV<~l_1E%37%bdT{()7U zFskIf=oIu4aIn^Uz&iH_+NcH6-BHmP(}P4=Q=zWKQk8`)KB7{;+qP0Nks6syC;Igk zjinbJ(IJYQvgLosCwdTV0E)bvSLYXKFut~zNMx2{xLYpup@^*>l7p3E{aod003iP! z4!iur(9{NOuvn)1e)(jXQY_aZJsJWmGng*}Yj_qm_uMgi4Cwo563^6ZoE91M%T8m_ zTSN=!^zF$+|KYTJEKFnpJP4i3rdn~Smw2(R>Qvw_KY5Rp9}Lw7r**J3ScmuSBL?T9 z!B~4mVOfmk1Om4~echo@xHThw9usO#+TxHuE8pN0-);F6#hpIW*0qLGyhNr-gu6Pjp11>mL>&iHgOKb?2`&1OyPFlXLZAeP!Fk5xvtQD5ixd7t;@A2YCl_rXdTt1WB(hzN!DRdQ6>_ zggP~t(&%U#S)U0f^mPZZJKMhDxpl{f@zB&!<&R_XMakMWgken{RfTSdPjG(p`ERgs ztlz)^|J1th5T@_TrwV)t!Q-zMDc*VG*l=%?h$-Jm+YVHp4eivy9*V02|JbfeFz9X%jJzh6S+~vUG>Va{rQVe z>6~1ZDoHLfiD$=OW71e7kzi#&-n+rQr91o>_ot;Wcyf1FLQ+P(JG!$#`^dPW^-b$G z%d{dfjwle3bhO>)Xir&q|%Q)KFaMRILrqfQe1&gz8LB$bKl3TU;ei77Rf zYC~D`2r!%vkmd{=3_GX9R2yazkKN4A*lmm^?sQd|d>Cyy($|>dfPWb*CuLKV=%>J6 zDusKL6itopJgQ3Hj->VxyKpJf<2Ph@bHI1EV3?)={H+3*!60vhrKu9Ei)>HN>Km!; zep^|n_Q*cF;+f^5Jxd}IbxDO@%FDO+^>Q^zNbDAop_8fb;zRpq68ZF+&t-ih<=C4( ztJ&@bxELOGatj!9s)UWyq`D}z%95oqin!o78p~tPBf3i;rwv=&r|-GZ z%H2||@%iMG44ZAu2xKC?j`EK^-wQlofplSru?QQ{&wbc8U6*B8$00a3mE_3@JKGUp zb9i^DYJ|@KjY5_uuq#%n1WWl17tq}?p-8WgcvlqIZ3SF9^@qUbYVBkipU`_bK|8(m zg%W(|vAx9uK0L#R$c-KPfY|PTu1eDKY$j=zvZnsUdZjTDcV-?b)?gglkplKWZi+qD zv1}nu06RmD97=_crY`b;>BJ`x_4g-Zd_hrcJ2uyK(wv`?el-}Ps5!TvnZ*Ui$|3)ikB2OT_l~0%q?p1^~Zzhcl7?#Z!R6P1^Zh3*}|DmR$0Kt zu9H{hE)mHsX#QX>nP{LLg6rc+)pv0H{{Ab0VIo-kwv&t6uy2B(K&?d>Z7WHyKLSkZ z&U{yQrL(QD#V}dLc*A;!nEOL|7|T&~6PD!`IqM<}E&99 zEqSrZ7!#*fi>9h#AGekJ&~KQU`4~2hV9T<(t8zsg77ZLMe7V*b?(2kJLaw=_W1Bv} zDIH&P*^;}DM<17eM{KJW8pmoSHGXZbx|yOU)6e$>UO;tl8e%IM-m6FST74q1=#>>^ zjl4!(xl9elAUxZ!i%9I4$o4UWY9|WorK0$ME++_Q^Hd33%raY!^NuU8P2csyHKNwf z-Rd;Yy`z*T_gn`PDorFlyN}RimPn?y%E4=xVA2kv+h+YLmu>CE6zTm0hN`gVTAL{G z?d1|LE235C1hkhp6NT}oe>|2udVe%om7=)I`^6EoRL^4= z4lQdhrjHP#j9uNr06_vp_O;XjyMuq$$jA!~5xq+GqKz;*dVg%UWM!iQV@^hOPqgH@ zHoYJ0(xUzXy|}S8I>(gJ9=Rc{X-g=BWHNj#vdDBe#Y8G9%$Kz(UMi}pQ7vrTaKVa_ z$|X1G);VZ;_{LlQF!af!VFeO$0lV zfo;c`on_w`$?fXyfoC%d3G8G%P`2L@b7%6FKtM=$Pep{-Z2u+xhxDSz%Ew9UMNIv8 zMGDx|R|Th=)`Nfs3Iv`~iKJ!reBQnN4N14l(a&|fzK0WWSuE36CEWIelgo%k5&8+d zfkT}xkKHNL-Tc^MfEy3rWeGM~ihJE3NtI|p1=&#gY`N$Fb|*&IXbfcFT6xukLuaJJ zBYJ^*NYFAvtgNk+OaRlZKS3RaVt7*$QStHf-3C2m!e8HK@^vryEgk(NYv0qskl;5y1P{6hWy3)ah z*;J~{3*g^AEtkpfGetKRRl;ObZ&zlX$TuJc@H0QObYMrubg-zpj5=IzZu-1m=S`Wy zodhc`E`5uNdn+vjCC|Bc;0He<9vsMe6F(U5V3r@nrDm2Hl3=pgA=D|h0TEq_qS5%S z`{lIz0emYc41q-Syt7Hqc=dzQltCF&3G-cQlI#Do z?qq%;yW+Koj8r5|L)C8#sfbg6V0HKaaqpK)56-^%_u? z;PI|3kq$l7`upRUh2XIR)BV{G7#k!j*ZRVx^MAT20}Zq#_FBvGFNBt5bq45Ev`Tb? zI6h;_7t0Kn;nH&5k%S6%yk%Z3qNAgfU?4MYWT%~>>V?Ts9*4coWuFNfwy$hNH>%Jv zdcNtW-Hj4QQ@LHOxx7{y*%>v}=S#HWs&%7pSDKX5$sf>LWQm%pKuT-U-j>p@Rsv5i zr!juX3_v(((S}oeN-AUz!2d}MA>!UM75TrSzU^+bo7a$}%_hB!5QTZSaN!j3Np&Lvh$ z-i3FK$g+B-CP(N*BQZs<=(PDWBJCEQ+hqD&S9zfx4jZJ+rnfFWB4TxenQ9~B?j{>5 z0}>7H)^Ro#$LW-TB z;!VC6MI`QTE%N=rI-WR0VF697O3_C?!Vq263!epmiI8bb<>|U5+Zsa@i%@3OH)+@z z$G52h(THkC4T@DGOwryEV7t(J2ldG`XUjU;YRvL``ZJvh$vQu{yw&5RRM>2x3()vK z_?_D#KGv34CzZo-Uo~}QqPKS3RGodgejf8JC?8c_m&wTv7%S@G_c7;ajC<((^m#td z7DZho!=GH#pbEK1VZ8j+DRWtjU3cFo0cTd4%vfZstZz5b{_If-V$I=Ri3D-VSRgRf z%vQYnW*FD1|V*tvz^|eU3{mVO(T?M6CWQCn;NSWVq`e$K}rl?z~g8>6WXR|xKh;AJL zkNp{aa*yyilNujek|Jms#HC{t%d^KyVBsiYY*TZR+cc5})lF2>efP(r9$Tz$;({1) zUq8V1_Oy(rI`gWW=IgEZVxe5(V#!n-#`riRS>hx48p~cpxOOD zL$Z?~z5NV`f%^ms{`~w*Gk)el=w3Yd^#obDl}ktgr2q5hr~r}YmtKbGs^Ge(u)Ef- z?W$nDPEVm0xpYzgDLwR%M%kC4dbx8aq=xhMLxQ>mUm!>58HiJV0tEJz0Qpcq}|Jtwq|Ct{D@9%%a|L93P#0L4_{r~^A zxBsL1{y*s3>$qn)D_q9^)VKe(um8R4`=aK5AmX$3&1kHAdmFbL@p}gS$4&k{$p7`L z`R^{D2c}~Vn2bLob02e6;-;8=+kXdhGx1>tgWA90enGdM5YCfx1+$8qseRBva^+E=&wH?Q>n zg`j_j{~v_o;DCR8!u?DBPlN2hvw!mQGH5G~ah(R(Vh4`#ngrDk0=~~6LE8rbzXuZU zAO5ewgl06Wpc(If%hzcTR1Nt4s#OrF8jb^eUsa=86UYG?^trkXVhoa?Bh>^PV=W0H z#R7Q~gv2pof!wG^;xuA`yqTdvL9syI0J&2i3*<;U2?_!djG+pHYy~zDV~Pa%2w)I{ zM}qbWs1O59g7O9F5Q9vD76pC~;}HpB3SL8u!z8Fn5DGE2lOT727Q}b~gIoj)5aTio zvJq563_BVWE%**GuFxPiK`g|`qCp~pM2MkFgJ6PGh>=T!xB?Z3L8Cz!fdFFQX;6WH z12JM~5LM6tF_LIdo?t7)u%JPb0s_R~lb|929AdC&P`W?}F$|edg`fqRsUtyb0R>{% zlOTj(0Adhnke}cz#P~*oZV3K@7!Q%S^8zu5@dk<8Bj|w`JDAXG!6j%Wg$Z33m_Re1 zY0z3h1H{mzK?eoX5W|cHg$gDhhA9m?B6tNc(rD0bfi=WvV?qxEa?ngO4bl=QLX3|z z$WgEiF}Bd46M_{(A|T^t7Maj$K^AU?NrJWs_&7!v68BwTk7Kwqp#y?9xEU8Fv{TTAo3SB5 z$pRl7Bbo%o3Ya*C8wq+Qu)#4zkT?ke499?xpf3U{j=`ltiGp+-14Dx%1%5b20S)pH zyv8x8G^kY&ieu!_pj3etjv+~dtON@p#Qw&|0(gm{03zI#cSn)rEwluGP?j7z4ISgydOyIpNs0B2SNR$rGO>Y7x2Rd z^qCS&n326n?pNCeNLe1be>7Um9--<%z z>Xmw$I4kLM5iyZnqFUnC62IuYmGsIly8SPDeESML=NEnNFST2|=K ze$nZ_>-qCvmmh?!T#G#U*X754)1&`&dCN7w$|tO(ZNKWb{!Ncuq5p2D<1c!=@4v2R z?ibxMY=y4)s~%6umFxJzl@$B`GrjHi|K+pN9=TubY5(2+U;o$d_OFx@5s^_`|4%kP zUd#NL{1@^z)XF!jeigX0_E*Lq{!ixWuS-p-Um5pbspv{t`s=-jBu%XWPncBKLle zznd%N2IW`M^55;bxN?0&VI_tC9#1|i|IlH@75eD!^5~U{6@I-J*zfW^E9D}8(Pw@? z_s~i`fxp`K^*8I{i=Wc zZ~DHK`hWkGS6u&3!T5OXT{@~o8oIyQ`MdvrUyGnuUc8v{%AJP4-}m8P@B0_u!TzR8 z{OW+;5Bfjp|CVn+TygzBGc-Pa%TC=pe@^KC>c5}*7#SFiMxk8fN2Db34AKj^33(g2 z0r>%W3^_E?6_d{OlQ~!!aexPwdwLp4OqTg zuW;nXt}XlSKj+V+u46yQA-aj#h4eW#4~!ST&x}MI0kfEmgQ+ zFIcc0Mx{SCkh+TUn)Mp8G}0ceN;|v?@#-a7$7tyi zNny+QEop__uO2`@h^EKajdnsi*Uf6&spp1IJ&$uWx^*XN2ZJdY_agH}@v%akrmk0N zYciG&t>2D0rjcHC$}oB0YWwMdi*8P+uBNBQ56om+7<|mUZs;wHFM96Cob>5R4t-!+ z6XW^v292b=PU`legdRb9ufz|REoGtT+4cWkh3?`Br8`PqCe??b8PBP>DO zY={B8Kvc8IeYG~;yqNz_9XihyDPa@a-VLl*J9YS^iEp` z&p77o7)w6Xl4|n!;uUo4Y{AFd`me?wpNd}0D9AzO(~sLTjrAW~{n+nh_U8MGv72y* z>OCh?2pUrZIGpcQB;(di?Z*Q;kqut!-!O26){z7O_NKk1(HDu`3YJso8b-tGq3dB* zri^1_uezQ7dWF2$bm1x{wKx6qnip>b3!G&P_fF{kh|Sl1uDh3xd7M7)XeED0YvRSOt6@3b^qhRt3PMOzb}+2 zEb_l(_hi(^hBtHkoz{2sZn4WcPv{8ve&|f0?q_hS%3nj@?uY9+eC%=Oir@}T`E99)XzQt- zze*o}Nk3{{BD-FBV7PQ(pZ7BT6l~9dt<3B!q(yOGgi>MIp&gBH+*GRPeAL7DbiF@z zYi?-j1#*Fm9mSmp?tmPU`JYI!&(^S|nL94{U;B=lg5oNbq%+rS3*3VY%DnowhOwDT zP%Z}ja4AgJXS^zPX(H(_3%S$WyKl55-cO~93N5v7w-QvFKiU+sUwJOijyq^U&Y`F8 z#Ppu(z@lbg-)++t6qa`dt$VDHFF0d)yspF1U2w$MOex)$c`&{$RI^(ch^_e?id##b zL+Q5(%5PV_`qWh(9Q=d*R~-5wqd`mJKwQ-I_*^NCRW;Zzrm|9_YZgzo$hFTn`zJZC zbI`RPQ_tT*kwEb9HzV2{>uR>gfN(`f^)2X;SYh%3d~%MoCR3<5upz-e?ysTz^H&eu zI$6JEtNIy{Xr(6ssWn#;6CDFp8yXlv!>h@~&bP234b9!K7fPPG4DqJao8zT}6DcMS za-aS2U?}U|ntgi5wd`yE*m93LTDjvkP0Q{|V~eBR-w$OsXxKPxc21~@YyWgm;dA=A z4;k;fsxFq&dykYW)L4tcoDwguk_>`GL+JxO;X60WBv~H6ZMi+>UOqwii}H9mS*x#l}^4t%4v2 zmgiQdBOXi6*U!9h-V$tmY}#>fkJPyK2i)27pLPd(QVbZ3{sSrEZzey!hrQpfH~ukn z0!mWdXnc0`-m4uQNlRMNcP<2PBzSa*A6T{J!#&w)#(^Em>tFe9@IG_#%8W7R5NXYbL`#~<5v2cmVba;W zIo)aX=kxkx=#KQ6kS-8(@3ACSLAR zeV)d5=k+&w*BH(VCqV#q@>2`e5Lql9=!O%0CfTk%O*z06;QpUdOSo`|X zo}EcO9>Q1XTNV;Jrycj--Jq}V`Qh4vAVgF%#T>J9Q)0(5xtQ~*vSV!amJ`nL%cgW; z%tzc%b?bnxuDy+Tj6x&^kFRKNCEjgj#!iN4`mqub$|OU<1uJOg|T-#3_Uwh{@B%mW58#X#Cdk|v0Pw(z|i4R+RUa8q)$`=c|;??(T zyQiwE$FWmIvaSfyR;xzQh!M{d7uOz$e(idhyIOH*+r+2S2E1GAZhVTvJ7z7ojpgFP z^{BV*j^{i+?Qrv2Y*@ysXY#OX-uGYKPebfEe5qL@#3U~{=|TKOdEB!tKJPx9Ku+Jc zu)nlnM8xDrBG(bxbk8S0o}W108+70fR@T8(F6S)sencs{Z3tG}@j5)EF)h##8TRGP zMM&l*{!pFvxhwmvES+etgcl>q3L}eN6i?xh=iEdS+8&z+aSnXnMA>ce@znJ@_!p4% zAvMh&hZN{jW{14xo%?4_c3qr`igm<~=dTAt3v>9e$}Ml@L`}t;TZ)S+Yj)PXH9wSU zqQZQAyZ6;V*VSQ`A6$(!h4|-{HeQ1|P@VaX z_w?o3!z)8Z-fE-N59B3ndVeTx*4hm=>d}*vH2>UvVjmv@~_y1RRs zaJneIaqogQ=P7oC-g9itsSI8AAGgljvuV3^8=1;vscX4Q*RDTH4j?pHm&i&x%whlJ zz%+=$XwA9jbyFV$+OGdSx9GbbC5Lu@egEj!eWrOYiSEfNocn@;3@BYZ9C>|S3BJR` zW6Os4n8aOJn+MHEMGLs$KxNA40n9MG7{1?d`?e_GC)zVdtQ(!_+tG*Go;k=dm1|Q@ ze22g8NVDonD7~qAc~8a1(LkJL`cVrP&$ZBy{_rz1{%5@D=*apkd%svQM{dIG#9&JA z<8!+aj>RDIzQzWRz#s=_CBt>S&fgxgam5~Is`9netB1DlY56WZ{8zLyQfza0>$xEY}(~iSS=p5Cltj{>DbajhIB63mIdV2(Y z(cd?H_;Aix`_DZ^g(L~jD>u$xJ#j65v-dzESI+!Xecs%~zmJ=q_k~$UM<l6 zg?oNhRAZ!knWN?uq)lu^o*~he_8||CiS|mYmC`>x7qydl)Zd^rttM#L(-C(3gc)+J z?8n=GpGiznjc2F2oyUxa_4TYfWp%3UdJDhtdNfPpO}a!{7wmPIoQIzZUDL?g&Jp=` z!;mJy{`ITDR%clH^g3(9w?}#>N(!@1KK`->=^uAMJJ@RQoqKh|eSk~A58T>fJQ>!BJ+udxe%9~U2!snO`#S3RQ0qu^VSS?Vo|#kL+MzL- z<-0bt53FS$30QZb!2jJRg{W8;#RD>9>F3naE>w3c1xCl^1c<(J*LiO?lelTmo&(O` z1-9JMpxsW#uV$HsoS%E8z2w;HY~hs(_UlHkgIsIgvfk=$A1WIj5-pW}YI$_0f1mNB zUv%>4v`2TZg*DG}iNcsK4|AOwgP8~Gq!mxhxk{frVkq_6xbK##F;<5%^TtoFs_%qV zdD+7^%hbPbn7<0DUSA?>`;_T?_pl;c8ud8Ie}A;&i`Vhdu-D438g`_dg1HpDH;ST% zy|H+_apKxnrHPac8i$obbtG&kABgHzLASasY!^5(XjxqOWAh4HUFw?Au-K8dTvhiM#nxEAvFRuzl*)4%utaC+?^62e|LN`r)xi3|7S}k#L94#r$pfyUSD{R8%b=E0 zQ@5kqVjLG}i`CYH9b%e)PjkM9JY4k+e}2TpEciJLJ9}>;AE!ON?1}%?k!y z(u$1PtZyziqLUBG6QHxhj-mGalvJ1V=ND+J&8LlgtiA+pxOJ&HWJCYoYT{3u6!rgT z)4Ut=?cElqeba5IuhQp39)=yriAsQJ8+UPvy!F#h3|;xElN`+4xk_26KREn(f7$Y~ zH|a#0#rJO~4#+D-Z__rqV2TqvG<=qXkGo&~%_4E)#^|??{qDE@Ya9Yj-l$xVyHI7t zFP$LMV?@|((UK10!Dsg@yB-&ZJKrdYRi0uFd@Mx@uGl+1JT)G6Fuh4t?nb`0A5ZV{ zUf!2C*djBDg7eduQ#W2RS$5l+cPG!(@7NIa@y&r6t(JA6IW_XP&WLX%$N#+t@AUpo zbd6d8SF|D8F=rF~CTWPaTT=W&9(U}9$ZQK%cms9#Y$nvP4nyHW}E=W&-!Gc zM9_U$(xyKA8ns~J&-@~+J=6#1#k zS389$tN4TChEpgi;+MATmv!}vhD#>X{meIczyiW`i|?ljTntKidfuN%OGCc z9nYSCCqN99KYX*Ek5EE(tY4~d59wSLV4l0!@L$ifHk4O$bK|a9IK=rB4O+K(Nu@mC z^gBMgzp!-MAhx{4U~};C*bO&ZZ`$HGITGVB*Q3ijtoOhCTt6BrMR%+&B^GkArT|I{ zzERKkfD}^e#~(?V{Q^G5v+}_-NX)M62hrE8&vN#m_*$8|FU4mw5mXSAExgwhG^pdn z_V9bI{>-kdLVM2!R9W(hn{`$aWiY1s_Q=Z{7)jQ1R8u*WR<{kstGM+%mN>W1`+BC+e{_QCiTZaTaQQoA5A&g7Z@2e4g*&2h1Qk*P!722 zg&f}H`2Ec>e@_pYEaoCX-)*}m+bYzYOT^4bL~I5SiOMOZLN{qK@4US`q|b}a{tO=M zO(;*&yj9{f@JI4&yp|WDyO+cl5fj;%o16c_@9TOF9W zBeC#Kw=4+)a_m+Gk2U-rWMNjV1d>cDt8EZXizU)Bg*P^&+juSQa2vs`*~~ClpT+v- zdgeQHfhb?sD>_Vy&Hv>%@;_lhOHjx;dw)l8R{bi4x47e+tqiyBrBmUT6PSH3M@1z~ z-c#a2HsvK5y!i`3Ro(}zF*)39(S0FiiWq$(1=LuU?KDkm|K!vBL2KbFYBrj7ofswT zzJj+haFUMTcE3!maoe4Pt)r4+t8ij_c&4jFdkuqOm|jC)%RD{;uz#NasPSm%>7i=T zhX~tj`9g+VLhDA{{g_BqZw?+{nEOE*ax?JKJBoL$UQqlVt}&zTGR4HjHJyTHX4FUO zmysmIEr-c1vQejC##`oSCWePNDtgm8!_P>Uz4e8Fx<(NakzyF7@PP@Ij$Du^eQQsk z-R-#fLWJcn!U?;m!-8-n6rRWVgoy+eKg5);^ke{M^ISUn9HqTJ4Q$i z2E6xX2?2qzXOG|5$;1FpKyeFvQxu&j;H51li*U75D(<|sA@UySv9jRM2YsO9Hgx%* zv7E>S@e8Tq?;yCT=5gI_a@gW`qq?K-aWd1ym7!Oz>FQEzGq|Z9{2rt3Gl}K(j93u^ zG|H8_zw%Q~^d?m&m9m7X&3#^j8`4SGFr(9OjQi=(i$SFnV%?tXy}$R zr4{3kH_IVc?==t$FvDDK1$fdFYEq_oWtUdyCp(vz>hoF>j#b`?Ex;5LJg%!yS09#E zNhb()8mt!r$b_EvQ_s)xr%(Cn?c;zzchzBtg#+kQ$>=T2J`>`T>IWt1{M$%?>QdNk z;XJ|3ofokZ$!Hf44l=tzefV=EX%VqTq#p4})}=hZZ{h@VIRYqG8#%=T;+nd8R2yaWg$d0FtS zC1a3vJXrHx>D9S@9qYHUK!d#(Ue!DF%vIsaH?~ZeF2m2i7~NUDNR2@RkMU;7C?Rdh zY&>bpO}Bhw*Po5Ucgbo^HB8@+y`PRCEV$u8r@1kK61r~a5*+EAPcU1;=X-~Q>|K`5evh_J2m0@aSg2=MWxk)2mko-N6V?@!-khdgWn{D z@!wl=oIO#JbK%Y2+6J8T$2KcE=8{!+^I#0S2$Pk!nDQv7Ch3cStQhCYP6)^RU$k{_ zDKWVz-$e>%xtX8ncja1R&LYcB`PWln>;~@b6dk$JGt1R-F1)tF0CsJUS4OB-=5;1( zEoPljt~W9WR`BQ@Tnmy{^a4huc)njH5s%b2Ltx^CP|l`D-$!z5m=`AVa#%oV@?#gD zVF9GqUv9(^@@E!73Iv{jC&^O{s%IzOn2dXC`anQAjM-r))^jz|LFMbqQo{`fepTy{ z^uymlt*DCw;|Q>JH;0b|$@X%j_u>eUW2Q)V?;4vYycnyQLM?teON7?rX*NNvXGIcr zPV-`#gmxuRqZ42!c6?#wRux2+==N!HVM$-OpCV48xmu5S{u7{<# z3vn?@QLc-e#AZUS5yaCM3I5!YkBJ~lzlmaVbvOopC&A7+?&@iiHf!|hjZ2(^(80Nx zBMq@-qm2l(fEd$>%zZN?QJKVVOA}!U-Z^N+-^soFl~yL|my-<6bn`Y#*&Q@olw^2u zAQu&wMNwffpn6D^=c$i8nBDccPAh6NqBh1s1I`Xs5Ap0eT;HpmPJ;A;e@QEVGbq*^ zc}#N(l<`ma3d`>qQ1m5k$0}CwjOz@f!7DeojIq$EAJn|}3OC&h5ab`@icVVRL5sG9 z+%*nTn1FOMkHUP8D%Ufr76DG!i5HVkm{{I729V!T&aI2ZQP!f@w$cDeQjf22YaXl= zyL@)1{jcP*A()I$0h$HF+>h?JtyGE6&j2R~md4mf4K6#uIN$>zxL;Lfp>rWg6^C@y zrcE3P<)I^+#iI++&n($~h|7SV<4BmKaNT*4PQQ^!<3tPEIL2i%41>z&6 zhwV@qiKz!!$sxi<5|N!zhC1uHWA^z1;zdghsA2XN+x(SOb09j1l@qn$;sAWV#mKRN z7}EVs^UiT16VaJ0jZq3xH)2EOv72DM`Z}!2;`Y;2F^5+%%lLoK?F-1|1sVK8fM^eD?bKC&dpgA>f3kcRI4-U4NmLb-9uG?D4n2A89g!xvE7@o(sTQ{fP3$85=5rH z4;$P8=YO`IR&P&->^nd;pPNA@bmnf zMgb?h5KP~WD<`{(a2KlW%*q6j&aWsL^#=kfL~wpf?Zm%+c9P)8?y=3~Y9{ewc8d|@ z>?BN@bWEKQo3v5+nsd>X^GG|bUN9a?p9)G^?I7VbPh!P00FwH4??MN&9Tl@;tfPsm zFR1gnzbnL@-r$prScPRme4H5e%hY;QDJV_b$MQ8Ogy_BB47z z^Cp^BTyglN00)0$@@dO@Z>muDMeM|lP|gO@PG~QZ!G0@`eWnvaMqtsV( zwsZyUSVwXp=&~UfQk+8gt58>~31oV6p99_&xgB6jN^;qKid+Yb7#`xo<|n)sCUHrF zE`tU;8}LOPq}Tag z78Z2oLJ&oubGDYh=o$wC$+$I7JpoFz9{kz{X2{#7Po$X`8O&xEcVo&+D14lfRE2?v zo3$UyJOF`bZ2dQ;2X5oq%&(@**mUwVR$Af{@kt@iy3%iWvt^Yc!A^F-dRL4OA_v;+b-Wwld0Z| zwg81wFl|FY~00000Tn@1iq$Q|Kf_=r)DLa1jgqrVj z9M1+A;uDdL%cXqwKQ+^GA9$_z0tNIW+(c?-;IgG8j(44Mu~lRn?Fo4GvYs3|$P#5; z(A0TpyBDh-czs6-l?RC?j2M4-!7_qF2t{Wr5i4mCpEn`x{PioZGakxQm--nUMd{sDOMa;A<*}Y7D^q(OD zS`F>+fK?k6dTM^uG_hfJv}%vKF!UX;f;h_bh9)lcHf*>3rxJ8gB8qmo00*S!s^|9Es+W-Q5fsV5a7 z&Bwz{11$%T(i6exKXlkQs2O+&)#Z%$lf|Pab3iDVFTU|12~WwcR|xVT$}{t>zRXO0 z#!A8g4@(J5V$VM5CY1>ygGmQ+?W#3iUi;Mj zh7UMLW}ZrRPkVB$%JCA22jPvg6`UQL2ZPZiA?teUkgf+;g(>b&$`_6|<4ruqXfA57 zF!|Rsn18ReX+4=JbLFO!^WchF+F}DVms*7m6LfdSN&)SSPHQ0>sQz#-;2T$f+?v5k z5;;@&!pT7*^AQjVef6Put&#=Sd}xkm=!LYd-NtG$KHpASosth zRy*{BYo`$%B1=9Y7_vD{yOpja`6TFn)VQQ4=>k<8nx=V6_7HrmHiR)Xr-=CcyFxr? zW9$v?U0Lh7qRwK{mehx06;sn`uqX7=sDaPHHXlq4Jj};NOV*<{&xLYx9y;)2&h<3T zI?@5>;WTGjGvi37&N71$Vecz;DsyuPJiT5Xv;kH!q3h_78HT$S;O_TFk?^cHC$=V@ z7UIQck0ycXu-)sb?l;L)nOC^xt{VLU9cf(8@$*T>Q8U$EmNv#F8OjcB>Go630QjcR zm3d)5*g0kCiUggwW1I45b>>y+%i^ALTZjlcL}Yea`ub25|0dB19r6U`C9ts_b;)#WBB&JoL2W|jC0OzFdi;N2v*^5UIv3@2;)#gT& zDF#o~@-D6SU?-IKc8aj;vlYJZc{58rAg{Pm_?%D`8j~$hugX*Xdz0C=%^E0;5PvHzwG)F({jUXHa%DsD8LY<4do=+@QMbJ zBCZZ?cskV>Td>l6isXh$bU3(=iZ(&0XBMES(tutE}k)nKg#SH~iXY_8)um%eNC zk3<7o%85~i%`ojB4q;Iu&LERK%+-n6P2qVg^cRBWU;6D*i)-wJi_g=s2x|X)hL|IS zzgpOqL4>Yu(yvn+wI!c-UVUzCPT%C+i7Ta5B^Zzr_*%uZ;*x<%bAEVOSov7jr(a!- z_hR)$dOMN__3@|BzgFC+CVTf+1F2s&jooA&LL35bfqu9Yt*5k~{;pW5!N#M(tQ4R2 z1=IX2fXjIB@Z@p^V(zP|o&;!oLuJwgoaaT`4zn0;nFudhk-+;5k=JILXVyuvWP|W^ z^AD`5RqF%dUHU;;#1DRHLlre+D072I^a9o!*sN%kFG<^d1Cy*+<=9}c^JG*r0c?4g z+3zc<-ksqje+4vazY^`MofUj%rfm2ir~q@-1stsMb)UPqjoA^$2RJ^T%D7iPf^2s! zBtorl^Hj4`5M8i1<3QrzB+Wt<82+!#5p^?GHm6iq?MU|kCFD-mh;m&A__%4WVue_h z7vjOa@&2#Tg$iS#rcUz;w)#Y=>ptpNXy z)sJ~H`udXBG45 zBqa7-*P{|Y6q`_rSD+#6_R42IDT9X?JHr9r)N?GBtKu~?WRPG8^mcPbk5`q%^03>r z!G>AZ5Q0E&jKeEG!?&gzc_`#rN^1_t2zJ#zRokh83qvZ#ZB}BY40W=HzQb#~JC8OP z0}`go)ONtc;c1;>qQHjII`hXjXc^z+-nq3UAi!04CTvtoOSzEjq#@0B$eSTIU2;_3 zi+LD&@9lly5`11YGt1!fs9rl`p*$q(zo3#pue4yzxOx;5>ERw$$#guQ2p2TLB_C@9 za`s~;)e3T)|9|Q@O+l`e=$br2vZt`v@XB&pZm_CFkB>-Ue<2b&E5fffUe~3_knc0O zkYlrl7S#j-7bhY1JlSJ2)4j_>mg4CwFKf#%(?C{DBT62r7SL#uyo2_D>B&F3`-~s= z@jSZuuT`Ez5wU)bI6r8{jM{GJ9;9n9eKT}QmhTd}E`!r;r>a>X+|P5RXD>{=vL-PN z{a*_XJbNrOKa7*fmaWAYR1bX>2M^<+GOgzN*s%laAmTO{!RcDb98(8D4DiMo-p z^;$?S8f-!G#AguF8drQ(i>*6x4r}hk?^c~QTnz2`X=^vMkM%3|oCK7@88h4CBcjsUBLAol^(Dc z9+BwFeW)mBmslprDbD(D5Y8jmka3*+$5%SkXE8!>dRi+~GG#mFvtpC;V`OXPu8MH#wjB{^i`T#jaJG<2E;mC~OTwfSC4pLotgCuGYB!}H$z%R%34D-nP z@<{#`TShm{rc^~ix9((7rnbd@ z23arSso0Kc!xjx~(@nEddAMytdO&lKDc9+9VZneirio9f9`gh2!48+2^Cf@Y|jFiEe;|0=s`3`Sq|qfIBZJ8+Koc&6XA4t0TY?y)7V1{6^u2w6OC^?)m-OZ zh9C}o(i7|b!_!n(y&)?ggW}Qc=(+rT_*9*Tlws#ePA1ni=Wru>Z}+Tz5umq6xt+h+ zKJ(0;W#c0aKnqpaTLw3PoIS8?YK8-=^e9K<9LT7*|D=b;jw6pO<+nc-kpND1#FG@= zfQisU$*1Co?r-mHsxI1@4aX}ZOO0^2A@wP|GriASx1 zq-H3tOtKl*tD{ORi!Jr`>m+Pd2=gq(L4Pb4%?1~^3DJ=HRL{XT>ccqX7|2?ns^-#H zR#dW;QAtrD?Aq40;&k1j(1o7p+2O6e7aLq`}7co_wzXw0=hg8GS`n6`b#?gYs z$-scy?SFc+o>W)ldMcJzn@cf&c^`uQC@=3e$}6L)?mOLinl_Qo$aa+?g`!j!Fnep1 z)mTv|u7uwQk^|Lzxob%M+rc`J!#xji;KMLCs;&8yrpmn88ueTJ^PqH)HMpi$R++%V zxymrI{|4g~H%9Bqt)K7vp?F&#VW0S0)|@Jr_=;Rn6-aRyA4kJ|ykpJlS@@>L#`%i7 zO`XYR#ct+kH4F$wTTvs8(c7D1LtvcaJ zP%C+*0WN1ejlxC9Q`98ppnc|cf$UWk~{cN8PSazQRthJ`AOx?a37~A-V2JYCpa`@k^uT$ zf0C8#lR6nbp;_}HKlNbKzo;Gj&J0Y#W?iPQu_5<`qL~+b;N4eNPPf34A^`0I)d$A)&p|+n$z_3qx zhtHeS$T^F!G!b8eCQ$sF_^>8e(>vi>=%IE|(_+A?5kDKzSK;s;;mIBb=Ky3Jk-qgY zEm)ObqV*LIJS8Itv7;&>^&Aj0=Kzs0XrCshe3eA~MWIzrlrSwC0g$jS_#5}klm`9| zFuq`j6)7c(>>c(#kSm;Vx!`G)KzbPiQ6&Y27i0FPx8;EE9N>&ulmG zLE84wgtNQ2j@_gx{JCQU2crgj#7eZQ@^N!9-rIOYKL&cIC43OAmPh{VGQQlt9m`tpJ~EBFd|D#y&HwM-74% zs6?y3;wpZ;iq)eACg6j~nt&ZO7({V2oa3mEAjn7x7xz)RR-S4t3&+IK4Ourc?J8#pob_J=6>^ zl=}gfxFVDGf%i+FZ5V5edY^_kO8vOrS*biL&0@o;i8iM-D1lOS>$qph1-r=!*(DvY znMg4EuN49AnI*v9FM7*8-FrbOXRo~f7l&iD;exXODAKbE_5>hDf) z!-F5Q%xLpqaxxC2w4Q{GSkilgULQp>2@H8R@2qGRJLt^mtz{)3kGw?c%fs`-a9)8>Jni~<{pC$q^<~B3H$Gz)m^FKqY*2=5vCM`qiy^uNV22^Jq-#)Lys1Pe6 zcmtD88Ti77Q2(Z%QtKItB}Sw(fOC$&H8Z$)Lxz)|>SYTDki=$SH!lC(N{VQ(Gi{y3j_W z3df@%Y5pF1!#jJ|lwb?=?^BM4uQ#xPFFKC#=&<_R?SC=;_V_`8Gr^zH{>DF`V6M!8 z9M)9;3e``CnAwvHI<@#>U5iavaFg8`Fh0Pj3r`3yMMa4Y4Q9u;GJ41ku%l9!mA_0%D}obr z@Mk%lX|bkDyre=D73Ko+f;_Dz*M4ru9DA^m(xgy>tRv1Of@WWt{R9nuJ#axT(L%%X zWbvLeU1$q-s>BY@MNsa9ZA4cBaB#rw1}skN_PU0AJdohWVmh9vHr54WKM~<9d{%q! z#z8Sc!XaPqqp>9A)#y-EJ%X~gJlD?e?l@l~5HruAkd}LQ% zV3`!5n(GJfGPmQryx#F+QA*C|$;1;@`hes|p8T`DV$1$*sr9nQ$C8H<4W2Ds$@y+2 z=!Csh1^G9#>?pEe+xQUGELf2Vs?Hd9nLX=#W)#DEpTCdTKg%+Mc&CHbU3Tm^R`fxI z{6=uX2;o&o-x=?t{ipWPNy$38+*pv4v4+wOkUiW@`^mroR7Z|Xg(}Dq)WRJAbZ9=v@e^#S3m>2 z&Z8mw(Rln>tSKDO*HHhZUAJ9e0)A!bp*O<>_1AT0E+n$|{k+c2{8Q8<2e9+nk#KK4 zF6q00IYNv+OMz8-5AGk}J8BJPL_L`(j6kYk4I;?A_t^JQUk6^*^Zk_+^&zQ9^?q1#=>B@__U_8J9SWmm_HFCG{1lRTVF`M&V zQgYzTZ}m4=r$;J<(O4#CsT_Xcgy1#1v{UF7JSg3ZN}nFPdg%q705xJ?p3g}fJNX0K za_Duugzc0CWh8R&=R8eJJ2_=LKp}zn8`tH7!^yC$g-8|u1nOo*k^Ro;MVl78c^1Qt zlJhyi^6vay1?OJ`rM*~wIu?Nn@90H2tGROkjb_^{IA{S~IdxGocokX~j8xsmb8u8& zS~>(5LPLy>yRcF%LoteC`-i$yONcmrF7d_zKL`nY;T3GX1X%I-jX(fNIe?gUQ%S(w zlUJ^elmXb)=z`tG!3<6r;xqz|0hs1;*58;Vb`-QetB2Qy{Y}Ac? zG^r#|$xZYQCgnx^2YSk21V?S3yeMJyUv&%A^EXpGFde?$5_HsU`}XloIx~;spM7ic z)MQwlG9@W~hna)tIUAjlQr3ck*B}^7a?kB7&$>tC%ZU<0W4NALN)}y1aS_&iq~yCL zds&p56F7&rKWZB!Tmng4Z`3G6n`gwFdlP#}Fl(^D+jRdAq?6m#yVdC6Qcwq+#;qV<;I+b2 zT<6`YBgU~z&;auUXs zH9r$)FTXB~G6SL9DU@0r`&S`)SWTeOG=XV}Qh{vOK`LFC0$Q*{R5u;>j6yM?W2<&`3%colbGf8)Fz>i$_4%lEi-kiltYKF zyl+N=h>Q=LA1}rF_oFN_pyAp)+{CY^L0}ZrjV{`TnNGEPXtYZ`z@2eGPWNnAcdQVq|Xs%IR-MJw!6>CsKUMeXI z4f?O%RyYwkoIfJ5?b?#$Yi1Y3v`xL(~$`wfk8;{ z5A^hoWM&bCG4UWhXbU5*9xgop-iQM-ll-MsO}vmL$C8qL^vkSXbYZ%8|Mi%0N#fWu zY+`970S9FD-teEdDc{==^+O{NrA>aNO~~$bBsBXm`ns>q_bb${A4>q${(Va-{Q z8~;%aI=1&+fh?;?XxdBKG~6Y(PJ^85KXIZi|IeN#y$RvO8-pDU@Xc+EB(f=2wA#dH zu5YfeR}}!ibJih~5fCZ$s=}T4^1eFC(MemsK>LqD>Stpql*)9Gq0gSYmPdDscUR}8&WTrqTyo>&F@i5H6DCsZy3 z_qu2aJb*H%o5T)_h5)+)rqkm_Ft4~3YA+m7Ap!}I`1~ds%7hZfO3-B7{!~y|IG}T= zRgp@vL+fR(#fo8p9#X7`MYG$;^W)IPoh~P(_eQ@Twd}UGzqPf>hJOM{*#6#S!%6=S zF1R<)&hh6yGwLIEv%Mt7tE<{lQGhbip=j>4mT-vK%!@UHH)E(&Ku>qA(6w564!~Cq z#?8;DF5yg5@n*B{d3tmN)^4xE6+9?~dRk-4`>Z}IiR^!v|LbZT$Vkr#;yvJz*hN~z zVVWBz<%l-}pGkO;9~oPV!5OUR{dsT^xH`@E_h&B~ov# zlpO#7000000S*097?)Y_Cb*y(?HI0~?^gi}=@!3?CELfb@GUW-ZMKU0BB&$M6_wE14g;rK3e8EEGiY%71~At zVpPw-%?wg!OE}#kQ|ISG+W!kasfy$u|Coq-xl@(@&Ts%}U@8_Jx?jIYf0|_^2+b8c z?zyeN+>8FkVpVa+6mwb*VaWKkGjSrC2Tx_*g|OIMQbZbWp_lf;>a&ZXHcW# zyY2`7)|*q!J@v#gX2&mVQwjkHPk0W}E4+|Q0fL{GVP^2^H=^Om!b1U6CB0*j6iDIF z+`;Do`GAr96k7nBkH3yYspMB4pwbi>^A? zR-i)cowOR+S`FQlV9IY%hA_+8-ev`4pJTNh%82^W9oF{<4Gh30?YmBL$!Xx> zJ#5*~D*M`MsYV8vDwmnZCaK9jm4_+W)Cz(3>)!?}5k53pE*acBhKhBd-oAL~X)I^A z0Q!zwpVZJH{_Kd_2IU+Npefn3JNv+PwP%{*DQ`b#4!zDCXqw*z*!^isuc8JkzLql7 zXalwFcK!&NT#?+fXTX*UlojCn&NL*HSNo-h4eHvk1Z^Lbm()9ljUfFXmCE_T(Htz= zq^|y~nT*wjZ-7VZg6tvH*`uu7DN|Q-V4VIYAzhAJY+Yl6ZU5FilD>x8_$4UB!hv-h zSG`@vuw(iwYQYWo!cVl0W+>8Dkk zYx1Avj<^C#)u3!BBaUvF6_+|dBm`vWe_bRr7V;^WM?l!AwW=`aIu{LWY2|){v}FQ2VF)dP)qV$5VfP7>Dp*vhMtR)uX|Q6UTi@zbStn z6Vz8h!#dvE9I`dckQQ zq#_jc+=~uO%qnOj3XA&;vrs7;y>-EhL}be4H(jNaZ_f?40LZug-WyjLz}568zWrg1tpsqW%m4T1TP%wmMF$CyW2o@1;;-wk}=Gd{S=>Wy%fhKef-q zO1-{zn|zRmp1&NZgDS$vZ8VtBKAx`43q3o^&Oi!~AMpPa7pK1^QeeMUc`bINS7XgO z`^MQmq%jxglQ@Bhv~9ZahcID1p)YjO&G%aJ7pH*vtaINwv0>W4pBaIO4$lNypm1^8 ztpW8e#R=whB^S*@jvQ4Xy{Vy-mjTt3ioemkjwGQn02lHYTfqO;h#`L;(6P>xxSmV3 z^(P|RgRP0I#s0P)>!g@n!{EPagmG+RaxDxlb%9I+LW%pdgAE;~wULfW5!A+gKotH3~_UEZrLkFYO4R{$dQwS$wrMza7($137q$ME}>eWWII7|HK{(T{q(gI zt+DDb+K9~yx}#}{zMqiAuu~9@=VK#JpHhyHg2jia(@^7n3#e(Q(wu(6#p}%^;Y0Vk zW}iBR3EkD8IcH|84IK72fxxv%m=}q4<=kif_50hcvIS2KwP}{U(CDRUbWcuNE2Z=M zQo8lauv20AcIAwZk_X+#b4xJuls{~TX=k;F^Py))lwL?v1-V6{eoUS1SZ2DTV`piOYS}9mV$I=z{)|`H2&tzw z+|Inl(tcW9o47}sU`vo$2t|))c&_afos(A7O`8R7&T37O8Sr%}P>rS~FWDzMOHOG% zT&BL{2=m)$<{6aBudYCo4dzLh{vSEE$s@|Zyrld~H3vu}RGtj5TC8`B-NxRcLeh>F z${%9N9}iLy4hE> z%L%_=G=b0v_u_4x_>^~QVkYp{J9YLXp^EAa?H#E`_d!e8JiuB`)vX!uFEe}aT0{k0 z=~E=yfrw)rbB9O2TXM-*G4am9}dhuQUU5{il#x@&uz;OM$J; z=VNx2R9VJ*Xi;{sOk2SZu92D%2}y0Xg_vMhTi&6S;teE)0oMI@3~?&sDVO)JxjkhO z_Smu7n_jkG2QIRo8Ax7V{&;|Z8=#i?gQFFb7S6q&Qu{sr@gx<{Sk!0TSq3n*E~Ws0 zEBoGyM`ZkTK`hd@C8FhyewR+aPz@I{i)Osc&KD+Xb#tz-X+TJ5JP)2r+7+eX(UU(9 z6k0S>tLnwySB-4gn8vFtbJ%7I$5-O#J@;tJ^|#S}y;8iAA48Ownw6}}Z=&}NdY@bW z5_Kx^KZhXKJ|B+w66=(?qL(?25gLtkc^!73d9LH16TKDyz_y3qE_pw-F7r=R=G=no z=P%tKb{N-OykzTb@++f1(#X7Qu`U2_Xgw7yI5m;)70s3NW@mBd!xc4NY4hkQnA>>r zMQL(?y|DOwlzm)CeLO#ctZB-?T@A}eKca(nto(>PF!bac;&Mfu{$!QskerZvP?>RY z*?xddWQDPHKn}~nsUssyfqC72Hx@O#xI*VgORE}}S;ceR{H0|(X?@N^z!LR3Dy^mT zachR9!{*g7*Pl91r2~1fabw(dH!Sb3#T3kco1SL8zvVUe&|A6DUCn%akgk0~koDz} z?%vFDuE)JAQU>I&Q}A>0PM?Gfh;&vRFZ;zb`Bh=xc?~m+UYCj3JoKazOHx0-DCBu= zRfMA)>N_2Y5AcD}KcWbL4to-Pi6A#vNBgnv&hfjE*LEEJU1rswYbW2Q;C^NU8oP@y&jWmbizgoj zws__`$KD2d0qQ@GMU)nG3!H#CSF?1oAgiOFa8Yl9d-lrI?8qIy@$N3=lI1`;DnLH2 zMN2;gb{5a94zCY`@qDzFDQv&hXOw5W-ccgPZmBZSx%#?OXK8m?%Ipg97#tehl6Z-z zb4OH%#lh5`F9M^UA1Q0xY_JKz6PK_A<9RDJPW3dCZqE9SSzfB(z66?CY!kB@N z;iNX^SnQiG&|6ErJ#Xf)7<%TxJF_)t0uSgCK5q+#`2^Ea1fv?E&Z&rj+WvdT-7dRG zVLv7xAGx7IqKxb34SN;S#OB z?Z59YQZ1^^#yA(1Va^p<02vnsNSiwqhopO0YC1_R;~m3JjEoA8i@=>Q3{kj{ZE1

+ + + + ) : ( + <> + {progress.phase === "done" && progress.file?.download && ( + + )} + + + )} + > + + + ); + } + + const formats: { value: ExportFormat; label: string; hint: string }[] = [ + { value: "mbox", label: folder ? t("format_mbox_folder") : t("format_mbox_account"), hint: t("hint_mbox") }, + { value: "eml", label: folder ? t("format_eml_folder") : t("format_eml_account"), hint: t("hint_eml") }, + ]; + + return ( + + + + + )} + > +
+
+ {t("format_label")} +
+ {formats.map((f) => ( + + ))} +
+
+

{t(`save_${mode}`)}

+
+
+ ); +} + +type T = ReturnType; + +function ExportProgressBody({ progress: p, t, locale, statusId }: { progress: ExportProgress; t: T; locale: string; statusId: string }) { + const running = p.phase === "running" || p.phase === "paused"; + let status: string; + if (p.phase === "paused" && p.pausedUntil) status = t("paused", { time: formatClock(p.pausedUntil, locale) }); + else if (running) status = t("status_exported", { count: p.exported, total: p.total }); + else if (p.phase === "done") status = t("summary_exported", { count: p.exported }); + else status = t("summary_exported", { count: p.exported }); + const tooLarge = p.phase === "failed" && /larger than this browser can keep in memory/.test(p.error ?? ""); + return ( +
+ {running && 0 ? p.fraction : null} label={t("progress_label")} paused={p.phase === "paused"} />} +

{status}

+ {p.phase === "done" && p.file && ( +

{t("summary_file", { name: p.file.name, size: formatFileSize(p.file.size) })}

+ )} + {p.failed > 0 && ( +
+

{t("summary_failed", { count: p.failed })}

+
    + {p.failures.map((f, i) =>
  • {f.label || t("untitled")}: {f.reason}
  • )} +
+
+ )} + {p.phase === "failed" &&

{tooLarge ? t("too_large") : p.error}

} +
+ ); +} diff --git a/components/mail-transfer/mail-transfer-host.tsx b/components/mail-transfer/mail-transfer-host.tsx index cd4bb008d..ee8e85174 100644 --- a/components/mail-transfer/mail-transfer-host.tsx +++ b/components/mail-transfer/mail-transfer-host.tsx @@ -7,10 +7,11 @@ import { isJobActive, useMailTransferStore } from "@/stores/mail-transfer-store" import { onBeforeSignOut } from "@/lib/sign-out-cleanup"; import { clearCheckpointsFor } from "@/lib/mail-import/checkpoints"; import { MailImportDialog } from "./mail-import-dialog"; +import { MailExportDialog } from "./mail-export-dialog"; import { formatClock } from "./transfer-progress"; /** - * Draws the mail import dialog, and while it is hidden with a job running, + * Draws the mail import and export dialogs, and while it is hidden with a job running, * a chip in the corner that shows the progress and brings it back. Mounted * with the app, so a job outlives the menu or settings page that started it. */ @@ -28,17 +29,20 @@ export function MailTransferHost() { return ( <> {request?.kind === "import" && } + {request?.kind === "export" && } {!visible && isJobActive(job) && } ); } function TransferChip() { - const t = useTranslations("mail_import"); + const tImport = useTranslations("mail_import"); + const tExport = useTranslations("mail_export"); const locale = useLocale(); const job = useMailTransferStore((s) => s.job); const show = useMailTransferStore((s) => s.show); if (!job) return null; + const t = job.kind === "export" ? tExport : tImport; const p = job.progress; const paused = p.phase === "paused" && p.pausedUntil; const label = paused diff --git a/components/mail-transfer/start-export.ts b/components/mail-transfer/start-export.ts new file mode 100644 index 000000000..9e87206c2 --- /dev/null +++ b/components/mail-transfer/start-export.ts @@ -0,0 +1,50 @@ +import { runExport, folderTree, type ExportFolder, type ExportFormat } from "@/lib/mail-export/engine"; +import type { ExportSink } from "@/lib/mail-export/sinks"; +import { useAuthStore } from "@/stores/auth-store"; +import { useMailTransferStore } from "@/stores/mail-transfer-store"; + +export interface StartExportInput { + format: ExportFormat; + /** JMAP account of the folder (or of the whole export). */ + accountId: string; + /** One folder (its id in its account); the whole account when absent. */ + mailboxId?: string; + sink: ExportSink; +} + +/** Runs an export as the dialog's job. */ +export function startExport(input: StartExportInput): void { + const client = useAuthStore.getState().client; + if (!client) return; + const controller = new AbortController(); + useMailTransferStore.getState().startJob({ + kind: "export", + controller, + progress: { phase: "running", exported: 0, failed: 0, failures: [], total: 0, fraction: 0 }, + }); + void (async () => { + let folders: ExportFolder[]; + try { + folders = input.mailboxId + ? [{ mailboxId: input.mailboxId, path: [] }] + : folderTree(await client.getMailboxes(input.accountId)); + } catch (error) { + await input.sink.abort().catch(() => {}); + useMailTransferStore.getState().updateProgress({ + phase: "failed", exported: 0, failed: 0, failures: [], total: 0, fraction: 0, + error: error instanceof Error ? error.message : String(error), + }); + return; + } + const result = await runExport({ + client, + accountId: input.accountId, + folders, + format: input.format, + sink: input.sink, + signal: controller.signal, + onProgress: (progress) => useMailTransferStore.getState().updateProgress(progress), + }); + useMailTransferStore.getState().updateProgress(result); + })(); +} diff --git a/lib/__tests__/jmap-mail-transfer.test.ts b/lib/__tests__/jmap-mail-transfer.test.ts new file mode 100644 index 000000000..53d94c966 --- /dev/null +++ b/lib/__tests__/jmap-mail-transfer.test.ts @@ -0,0 +1,54 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { JMAPClient } from '../jmap/client'; + +type Call = [string, Record, string]; + +function client() { + const c = new JMAPClient('https://jmap.example.com', 'user@example.com', 'pass'); + Object.assign(c, { accountId: 'me', downloadUrl: 'https://jmap.example.com/download/{accountId}/{blobId}/{name}?accept={type}' }); + return c; +} + +afterEach(() => vi.unstubAllGlobals()); + +describe('importUploadedEmails', () => { + it('imports several uploaded messages in one Email/import into the given account, one result each', async () => { + const c = client(); + const calls: Call[][] = []; + vi.spyOn(c as unknown as { request: (c: Call[]) => Promise }, 'request').mockImplementation(async (req: Call[]) => { + calls.push(req); + return { methodResponses: [['Email/import', { created: { imp0: { id: 'e1' } }, notCreated: { imp1: { type: 'invalidEmail', description: 'Blob does not contain a valid RFC 5322 message.' } } }, '0']] }; + }); + const results = await c.importUploadedEmails([ + { blobId: 'b1', mailboxIds: { inbox: true }, keywords: { $seen: true }, receivedAt: '2020-01-02T03:04:05Z' }, + { blobId: 'b2', mailboxIds: { inbox: true }, keywords: {} }, + ], 'shared'); + expect(calls).toHaveLength(1); + expect(calls[0][0][0]).toBe('Email/import'); + expect(calls[0][0][1]).toEqual({ + accountId: 'shared', + emails: { + imp0: { blobId: 'b1', mailboxIds: { inbox: true }, keywords: { $seen: true }, receivedAt: '2020-01-02T03:04:05Z' }, + imp1: { blobId: 'b2', mailboxIds: { inbox: true }, keywords: {} }, + }, + }); + expect(results).toEqual([{ id: 'e1' }, { error: 'Blob does not contain a valid RFC 5322 message.', type: 'invalidEmail' }]); + }); +}); + +describe('fetchBlobForTransfer', () => { + it('downloads with the account in the URL', async () => { + const fetch = vi.fn(async () => new Response(new Uint8Array([1, 2, 3]))); + vi.stubGlobal('fetch', fetch); + const bytes = await client().fetchBlobForTransfer('blob1', 'shared'); + expect(Array.from(bytes)).toEqual([1, 2, 3]); + expect(String((fetch.mock.calls[0] as unknown[])[0])).toContain('/download/shared/blob1/'); + }); + + it('reports a 429 to the export without pausing the rest of the client', async () => { + vi.stubGlobal('fetch', vi.fn(async () => new Response('slow down', { status: 429, headers: { 'Retry-After': '30' } }))); + const c = client(); + await expect(c.fetchBlobForTransfer('blob1', undefined)).rejects.toMatchObject({ name: 'RateLimitError', retryAfterMs: 30_000 }); + expect((c as unknown as { isRateLimited(): boolean }).isRateLimited()).toBe(false); + }); +}); diff --git a/lib/jmap/client-interface.ts b/lib/jmap/client-interface.ts index 2716bf5f2..28e03746d 100644 --- a/lib/jmap/client-interface.ts +++ b/lib/jmap/client-interface.ts @@ -549,7 +549,13 @@ export interface IJMAPClient { * Many blobs at once through Blob/get (RFC 9404), for exports; null when * the session doesn't advertise the blob capability. */ - getBlobsAsBase64?(blobIds: string[], accountId?: string): Promise; + getBlobsAsBase64?(blobIds: string[], accountId?: string, options?: { sized?: boolean }): Promise; + /** + * One blob for a mail export, outside the client-wide rate limit: a 429 + * throws RateLimitError for the export to wait on, instead of pausing + * every other request. + */ + fetchBlobForTransfer?(blobId: string, accountId: string | undefined, signal?: AbortSignal): Promise; downloadBlob(blobId: string, name?: string, type?: string, accountId?: string): Promise; // ── Identities ──────────────────────────────────────────────── diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index be9496c20..d729fb384 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -6105,6 +6105,26 @@ export class JMAPClient implements IJMAPClient { * runs as a document (toInertBlob); image/svg+xml and text/html come back * as application/octet-stream. */ + /** + * One blob for the mail export, outside the client-wide rate limit: a 429 + * throws RateLimitError for the export to wait on instead of pausing every + * other request (authenticatedFetch would). A 401 goes through fetchBlob, + * which renews the session or token. + */ + async fetchBlobForTransfer(blobId: string, accountId: string | undefined, signal?: AbortSignal): Promise { + const url = this.getBlobDownloadUrl(blobId, 'message.eml', 'message/rfc822', accountId); + const response = await this.timedFetch(url, { signal }, { Authorization: this.authHeader }, JMAPClient.TRANSFER_TIMEOUT_MS); + if (response.status === 401) { + return new Uint8Array(await (await this.fetchBlob(blobId, 'message.eml', 'message/rfc822', accountId)).arrayBuffer()); + } + if (response.status === 429) throw new RateLimitError(JMAPClient.parseRetryAfter(response)); + if (!response.ok) { + const body = await response.text().catch(() => ''); + throw new Error(`Failed to fetch blob: ${response.status} - ${body}`); + } + return new Uint8Array(await response.arrayBuffer()); + } + async fetchBlobAsObjectUrl(blobId: string, name?: string, type?: string, accountId?: string): Promise { const blob = await this.fetchBlob(blobId, name, type, accountId); return URL.createObjectURL(toInertBlob(blob)); @@ -6130,7 +6150,7 @@ export class JMAPClient implements IJMAPClient { * It checks the access to each blob by itself, though, whichever account the * request names, so the rest are asked for in the user's own account. */ - async getBlobsAsBase64(blobIds: string[], accountId?: string): Promise { + async getBlobsAsBase64(blobIds: string[], accountId?: string, options?: { sized?: boolean }): Promise { if (!this.hasCapability(BLOB_CAPABILITY)) return null; let account = accountId || this.accountId; const ids = [...new Set(blobIds)]; @@ -6151,7 +6171,8 @@ export class JMAPClient implements IJMAPClient { }; let chunks: string[][] = ids.length > 0 ? [ids] : []; - if (ids.length * BLOB_LARGE_BYTES > BLOB_BATCH_BYTES) { + // `sized`: the caller already kept the batch within BLOB_BATCH_BYTES (the mail export knows each message's size). + if (!options?.sized && ids.length * BLOB_LARGE_BYTES > BLOB_BATCH_BYTES) { const sizes = new Map(); try { for (const chunk of batched(ids, this.getMaxObjectsInGet())) { diff --git a/lib/mail-export/__tests__/engine.test.ts b/lib/mail-export/__tests__/engine.test.ts new file mode 100644 index 000000000..2d0df7938 --- /dev/null +++ b/lib/mail-export/__tests__/engine.test.ts @@ -0,0 +1,201 @@ +import { describe, it, expect, vi } from 'vitest'; +import { runExport, folderTree, safeName, type ExportProgress } from '@/lib/mail-export/engine'; +import { memorySink, ExportTooLargeError, type ExportSink } from '@/lib/mail-export/sinks'; +import { runImport } from '@/lib/mail-import/engine'; +import type { IJMAPClient, UploadedEmailImport } from '@/lib/jmap/client-interface'; +import type { Mailbox } from '@/lib/jmap/types'; + +const enc = new TextEncoder(); +const T0 = Date.now(); + +interface Msg { id: string; mailbox: string; raw: string; keywords: Record; receivedAt: string; subject: string } + +const msg = (i: number, mailbox: string, extra: Partial = {}): Msg => ({ + id: `m${mailbox}${i}`, mailbox, subject: `Subject ${i}`, + raw: `From: a@example.org\r\nSubject: Subject ${i}\r\nDate: Tue, 5 May 2020 10:0${i % 10}:00 +0000\r\n\r\nFrom the body ${i}\r\n`, + keywords: i % 2 ? { $seen: true } : { $flagged: true }, receivedAt: `2020-05-0${(i % 9) + 1}T10:00:00Z`, ...extra, +}); + +function fakeClient(messages: Msg[], opts: { blobGet?: boolean; rateLimitOnce?: boolean } = {}) { + let inFlight = 0; + let maxInFlight = 0; + const calls = { blobGet: 0, download: 0 }; + let limited = false; + const track = async (run: () => T): Promise => { + inFlight++; + maxInFlight = Math.max(maxInFlight, inFlight); + await new Promise((r) => setTimeout(r, 1)); + try { return run(); } finally { inFlight--; } + }; + const byBlob = new Map(messages.map((m) => [`blob-${m.id}`, m])); + const client = { + getMaxConcurrentRequests: () => 4, + queryEmailFields: vi.fn(async (filter: { inMailbox: string }) => messages + .filter((m) => m.mailbox === filter.inMailbox) + .map((m) => ({ id: m.id, blobId: `blob-${m.id}`, size: m.raw.length, keywords: m.keywords, receivedAt: m.receivedAt, subject: m.subject })) + .reverse()), + getBlobsAsBase64: opts.blobGet === false ? undefined : vi.fn(async (ids: string[]) => track(() => { + calls.blobGet++; + return { data: new Map(ids.map((id) => [id, btoa(byBlob.get(id)!.raw)])), failed: [] }; + })), + fetchBlobForTransfer: vi.fn(async (blobId: string) => track(() => { + calls.download++; + if (opts.rateLimitOnce && !limited) { + limited = true; + throw Object.assign(new Error('Rate limited by server'), { name: 'RateLimitError', retryAfterMs: 30_000 }); + } + return enc.encode(byBlob.get(blobId)!.raw); + })), + } as unknown as IJMAPClient; + return { client, calls, get maxInFlight() { return maxInFlight; } }; +} + +function collectingSink() { + const parts: Uint8Array[] = []; + const sink: ExportSink = { + kind: 'memory', + write: async (c) => { parts.push(c.slice()); }, + close: async () => ({ name: 'x', size: parts.reduce((n, p) => n + p.length, 0), discard: async () => {} }), + abort: async () => {}, + }; + return { sink, file: (name: string) => new File(parts as BlobPart[], name, { lastModified: 1 }) }; +} + +function importClient() { + const imported: Array = []; + const blobs = new Map(); + const created: Array<{ name: string; parentId?: string }> = []; + let n = 0; + const client = { + getCapabilities: () => ({}), + getMaxConcurrentRequests: () => 4, + getMaxSizeUpload: () => 50_000_000, + getMailboxes: async () => [{ id: 'in', name: 'Inbox', role: 'inbox' } as Mailbox, { id: 'target', name: 'Restore' } as Mailbox], + createMailbox: async (name: string, parentId?: string) => { created.push({ name, parentId }); return { id: `c${created.length}`, name } as Mailbox; }, + uploadBlob: async (f: File) => { const id = `b${n++}`; blobs.set(id, await f.text()); return { blobId: id, size: f.size, type: f.type }; }, + importUploadedEmails: async (entries: UploadedEmailImport[]) => entries.map((e) => { imported.push({ ...e, text: blobs.get(e.blobId)! }); return { id: `e${imported.length}` }; }), + } as unknown as IJMAPClient; + return { client, imported, created }; +} + +const exportOpts = { signal: new AbortController().signal, onProgress: () => {}, sleep: async () => {}, now: () => T0, reportEveryMs: 0 }; +const importOpts = { scope: 's', resume: false, signal: new AbortController().signal, onProgress: () => {}, sleep: async () => {}, reportEveryMs: 0 }; + +describe('runExport', () => { + it('exports a folder as mbox that imports back to the same messages, dates and state', async () => { + const messages = Array.from({ length: 12 }, (_, i) => msg(i, 'inbox')); + const fake = fakeClient(messages); + const out = collectingSink(); + const result = await runExport({ ...exportOpts, client: fake.client, accountId: 'acc', folders: [{ mailboxId: 'inbox', path: [] }], format: 'mbox', sink: out.sink }); + expect(result.phase).toBe('done'); + expect(result.exported).toBe(12); + // Small messages travel in one Blob/get, not one download each. + expect(fake.calls.blobGet).toBe(1); + expect(fake.calls.download).toBe(0); + + const back = importClient(); + const imported = await runImport({ ...importOpts, client: back.client, accountId: 'acc', targetMailboxId: 'target', keepFolders: false, files: [{ file: out.file('Inbox.mbox'), format: 'mbox' }] }); + expect(imported.imported).toBe(12); + // Same bytes (the state headers the mbox carried are gone again), same order, state and arrival time. + expect(back.imported.map((e) => e.text)).toEqual(messages.map((m) => m.raw)); + expect(back.imported.map((e) => e.keywords)).toEqual(messages.map((m) => Object.fromEntries(Object.keys(m.keywords).map((k) => [k, true])))); + expect(back.imported.map((e) => e.receivedAt)).toEqual(messages.map((m) => m.receivedAt)); + }); + + it('exports the account as a ZIP of .eml files whose folders come back on import', async () => { + const messages = [msg(1, 'inbox'), msg(2, 'arch'), msg(3, 'y2020'), msg(4, 'y2020')]; + const fake = fakeClient(messages); + const out = collectingSink(); + const folders = [ + { mailboxId: 'inbox', path: ['Inbox'] }, { mailboxId: 'arch', path: ['Archive'] }, + { mailboxId: 'y2020', path: ['Archive', '2020'] }, { mailboxId: 'empty', path: ['Empty'] }, + ]; + await runExport({ ...exportOpts, client: fake.client, accountId: 'acc', folders, format: 'eml', sink: out.sink }); + const { listZipEntries } = await import('@/lib/mail-import/zip-reader'); + const names = (await listZipEntries(out.file('a.zip'))).map((e) => e.name); + expect(names).toEqual(['Inbox/00001 Subject 1.eml', 'Archive/00001 Subject 2.eml', 'Archive/2020/00001 Subject 3.eml', 'Archive/2020/00002 Subject 4.eml', 'Empty/']); + + const back = importClient(); + await runImport({ ...importOpts, client: back.client, accountId: 'acc', targetMailboxId: 'target', keepFolders: true, files: [{ file: out.file('a.zip'), format: 'zip' }] }); + expect(back.created).toEqual([{ name: 'Inbox', parentId: 'target' }, { name: 'Archive', parentId: 'target' }, { name: '2020', parentId: 'c2' }]); + expect(back.imported).toHaveLength(4); + }); + + it('exports the account as a ZIP with one mbox per folder', async () => { + const fake = fakeClient([msg(1, 'inbox'), msg(2, 'y2020')]); + const out = collectingSink(); + await runExport({ ...exportOpts, client: fake.client, accountId: 'acc', folders: [{ mailboxId: 'inbox', path: ['Inbox'] }, { mailboxId: 'y2020', path: ['Archive', '2020'] }], format: 'mbox', sink: out.sink }); + const back = importClient(); + await runImport({ ...importOpts, client: back.client, accountId: 'acc', targetMailboxId: null, keepFolders: true, files: [{ file: out.file('a.zip'), format: 'zip' }] }); + expect(back.created).toEqual([{ name: 'Archive', parentId: undefined }, { name: '2020', parentId: 'c1' }]); + expect(Object.keys(back.imported[0].mailboxIds)).toEqual(['in']); + }); + + it('keeps at most maxConcurrentRequests - 2 requests in flight and downloads large messages one by one', async () => { + const big = 'x'.repeat(1_100_000); + const messages = Array.from({ length: 8 }, (_, i) => msg(i, 'inbox', { raw: `Subject: ${i}\r\n\r\n${big}` })); + const fake = fakeClient(messages); + await runExport({ ...exportOpts, client: fake.client, accountId: 'acc', folders: [{ mailboxId: 'inbox', path: [] }], format: 'eml', sink: collectingSink().sink }); + expect(fake.calls.download).toBe(8); + expect(fake.calls.blobGet).toBe(0); + expect(fake.maxInFlight).toBeLessThanOrEqual(2); + expect(fake.maxInFlight).toBeGreaterThan(1); + }); + + it('downloads without Blob/get and pauses on a 429 by itself', async () => { + const fake = fakeClient([msg(1, 'inbox'), msg(2, 'inbox')], { blobGet: false, rateLimitOnce: true }); + const seen: ExportProgress[] = []; + const result = await runExport({ ...exportOpts, client: fake.client, accountId: 'acc', folders: [{ mailboxId: 'inbox', path: [] }], format: 'mbox', sink: collectingSink().sink, onProgress: (p) => seen.push(p) }); + expect(seen.some((p) => p.phase === 'paused' && p.pausedUntil === T0 + 30_000)).toBe(true); + expect(result.phase).toBe('done'); + expect(result.exported).toBe(2); + }); + + it('stays under the requests-per-minute it allows itself', async () => { + let clock = T0; + const fake = fakeClient(Array.from({ length: 6 }, (_, i) => msg(i, 'inbox')), { blobGet: false }); + const waits: number[] = []; + await runExport({ ...exportOpts, client: fake.client, accountId: 'acc', folders: [{ mailboxId: 'inbox', path: [] }], format: 'mbox', sink: collectingSink().sink, + requestsPerMinute: 3, now: () => clock, sleep: async (ms) => { waits.push(ms); clock += ms; } }); + // 1 query + 6 downloads at 3 a minute: two waits of about a minute. + expect(waits.filter((w) => w > 50_000)).toHaveLength(2); + }); + + it('stops cleanly when cancelled and aborts the sink', async () => { + const controller = new AbortController(); + const fake = fakeClient(Array.from({ length: 300 }, (_, i) => msg(i, 'inbox'))); + const out = collectingSink(); + const abort = vi.spyOn(out.sink, 'abort'); + const result = await runExport({ ...exportOpts, client: fake.client, accountId: 'acc', folders: [{ mailboxId: 'inbox', path: [] }], format: 'eml', sink: out.sink, signal: controller.signal, + onProgress: (p) => { if (p.exported >= 100) controller.abort(); } }); + expect(result.phase).toBe('cancelled'); + expect(result.exported).toBeLessThan(300); + expect(abort).toHaveBeenCalled(); + }); +}); + +describe('folderTree', () => { + it('orders special folders first, nests children and makes sibling names unique', () => { + const mb = (id: string, name: string, extra: Partial = {}) => ({ id, name, sortOrder: 0, ...extra }) as Mailbox; + const tree = folderTree([ + mb('t', 'Trash', { role: 'trash' }), mb('a', 'Projects'), mb('a2', 'projects'), mb('i', 'Inbox', { role: 'inbox' }), + mb('c', 'Q1/Q2', { parentId: 'a' }), mb('acc:x', 'Shared', { originalId: 'x' }), + ]); + expect(tree).toEqual([ + { mailboxId: 'i', path: ['Inbox'] }, { mailboxId: 't', path: ['Trash'] }, { mailboxId: 'a2', path: ['projects'] }, + { mailboxId: 'a', path: ['Projects (2)'] }, { mailboxId: 'c', path: ['Projects (2)', 'Q1 Q2'] }, { mailboxId: 'x', path: ['Shared'] }, + ]); + }); + it('makes names safe for every file system', () => { + expect(safeName('a:b*c?.', 'x')).toBe('a b c'); + expect(safeName('...', 'x')).toBe('x'); + }); +}); + +describe('memorySink', () => { + it('refuses more than its limit', async () => { + const sink = memorySink('a.mbox', 'application/mbox', 10); + await sink.write(new Uint8Array(8)); + await expect(sink.write(new Uint8Array(8))).rejects.toBeInstanceOf(ExportTooLargeError); + }); +}); diff --git a/lib/mail-export/__tests__/mbox-writer.test.ts b/lib/mail-export/__tests__/mbox-writer.test.ts new file mode 100644 index 000000000..018131ffe --- /dev/null +++ b/lib/mail-export/__tests__/mbox-writer.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect } from 'vitest'; +import { mboxEntry, envelopeLine } from '@/lib/mail-export/mbox-writer'; +import { MboxSplitter } from '@/lib/mail-import/mbox'; +import { mboxState } from '@/lib/mail-import/mbox-status'; +import { readHeaderBlock } from '@/lib/mail-import/mbox'; + +const enc = new TextEncoder(); +const dec = new TextDecoder(); +const join = (parts: Uint8Array[]) => { + const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0)); + let at = 0; + for (const p of parts) { out.set(p, at); at += p.length; } + return out; +}; + +function reread(mbox: Uint8Array) { + const s = new MboxSplitter(); + return [...s.push(mbox), ...s.end()].map((m) => ({ text: dec.decode(join(m.parts)), state: mboxState(readHeaderBlock(m.parts)), fromLine: m.fromLine })); +} + +describe('mboxEntry (mboxrd)', () => { + const raw = 'Subject: hi\r\nX-Mozilla-Status: 0001\r\nStatus: RO\r\nX-Keywords:\r\n old\r\nFrom: a@example.org\r\n\r\nFrom the top\r\n>From quoted\r\nend\r\n'; + + it('writes an envelope line in asctime UTC', () => { + expect(envelopeLine('2020-05-05T10:11:12Z', '\n')).toBe('From MAILER-DAEMON Tue May 5 10:11:12 2020\n'); + }); + + it('quotes From lines, replaces old state headers and keeps CRLF', () => { + const out = dec.decode(join(mboxEntry(enc.encode(raw), { $flagged: true, work: true }, '2020-05-05T10:11:12Z'))); + expect(out).toBe( + 'From MAILER-DAEMON Tue May 5 10:11:12 2020\r\nStatus: O\r\nX-Status: F\r\nX-Keywords: work\r\n' + + 'Subject: hi\r\nFrom: a@example.org\r\n\r\n>From the top\r\n>>From quoted\r\nend\r\n\r\n', + ); + }); + + it('round-trips through the importer: same bytes minus state headers, same keywords', () => { + const msgs: { raw: string; keywords: Record }[] = [ + { raw: 'Subject: one\n\n>>From x\nFrom y\n\nFrom MAILER-DAEMON Tue May 5 10:11:12 2020\n', keywords: { $seen: true, $answered: true } }, + { raw: 'Subject: two\r\n\r\nno newline at end', keywords: { $draft: true, $forwarded: true } }, + ]; + const mbox = join(msgs.flatMap((m) => mboxEntry(enc.encode(m.raw), m.keywords, '2021-01-02T03:04:05Z'))); + const back = reread(mbox); + expect(back).toHaveLength(2); + expect(back[0].state.keywords).toEqual({ $seen: true, $answered: true }); + expect(back[0].text.endsWith('Subject: one\n\n>>From x\nFrom y\n\nFrom MAILER-DAEMON Tue May 5 10:11:12 2020\n')).toBe(true); + expect(back[1].state.keywords).toEqual({ $draft: true, $forwarded: true }); + expect(back[1].text.endsWith('Subject: two\r\n\r\nno newline at end\r\n')).toBe(true); + expect(back[1].fromLine).toBe('From MAILER-DAEMON Sat Jan 2 03:04:05 2021'); + }); +}); diff --git a/lib/mail-export/engine.ts b/lib/mail-export/engine.ts new file mode 100644 index 000000000..1040ef8bb --- /dev/null +++ b/lib/mail-export/engine.ts @@ -0,0 +1,353 @@ +import type { IJMAPClient } from "@/lib/jmap/client-interface"; +import type { Mailbox } from "@/lib/jmap/types"; +import { mboxEntry } from "./mbox-writer"; +import { ZipWriter, type ZipEntryStream } from "./zip-writer"; +import type { ExportFile, ExportSink } from "./sinks"; + +/** + * Exports folders as mbox (mboxrd) or as a ZIP of .eml files, streamed into a + * sink so the mailbox never sits in memory. + * + * - One folder as mbox is a plain .mbox file; anything else is a ZIP: one + * `.mbox` per folder, or `/ .eml` per message, with + * empty folders kept as directories. A message in several folders is + * written once per folder, as IMAP programs do. + * - Messages come in batches through Blob/get (many per request) where the + * server offers it, larger ones and the rest as downloads. At most + * maxConcurrentRequests - 2 requests run at once (Stalwart 1.0 counts + * downloads against it), and the export keeps itself well under the + * server's 1000 requests a minute. A 429 pauses the export alone, never + * the rest of the app (downloads don't go through the client-wide limit). + */ + +export type ExportFormat = "mbox" | "eml"; +export type ExportPhase = "running" | "paused" | "done" | "cancelled" | "failed"; + +export interface ExportFolder { + /** The mailbox id in its account (not the store's namespaced id). */ + mailboxId: string; + /** Path of names from the top, e.g. ["Archive", "2020"]. */ + path: string[]; +} + +export interface ExportProgress { + phase: ExportPhase; + exported: number; + failed: number; + failures: { label: string; reason: string }[]; + total: number; + fraction: number; + pausedUntil?: number; + file?: ExportFile; + error?: string; +} + +export interface ExportOptions { + client: IJMAPClient; + accountId: string; + folders: ExportFolder[]; + format: ExportFormat; + sink: ExportSink; + signal: AbortSignal; + onProgress: (progress: ExportProgress) => void; + now?: () => number; + sleep?: (ms: number, signal: AbortSignal) => Promise; + reportEveryMs?: number; + /** Requests per minute the export allows itself (the server's limit is 1000 for everything). */ + requestsPerMinute?: number; +} + +interface MessageRef { + id: string; + blobId: string; + size: number; + keywords: Record; + receivedAt?: string; + subject: string; +} + +/** Messages up to this size travel inside Blob/get; bigger ones are downloaded. */ +const BATCH_MESSAGE_MAX = 1024 * 1024; +/** Bytes per Blob/get, like the client's BLOB_BATCH_BYTES. */ +const BATCH_BYTES = 4 * 1024 * 1024; +const BATCH_COUNT = 100; +const MAX_FAILURES_LISTED = 20; +const RETRY_DELAYS_MS = [500, 2000, 5000]; + +function isAbort(error: unknown): boolean { + return (error as { name?: string } | null)?.name === "AbortError"; +} + +function cancelled(): DOMException { + return new DOMException("Export cancelled", "AbortError"); +} + +function defaultSleep(ms: number, signal: AbortSignal): Promise { + return new Promise((resolve, reject) => { + if (signal.aborted) { reject(cancelled()); return; } + const timer = setTimeout(() => { signal.removeEventListener("abort", onAbort); resolve(); }, ms); + const onAbort = () => { clearTimeout(timer); reject(cancelled()); }; + signal.addEventListener("abort", onAbort, { once: true }); + }); +} + +function decodeBase64(base64: string): Uint8Array { + const fromBase64 = (Uint8Array as unknown as { fromBase64?: (s: string) => Uint8Array }).fromBase64; + if (fromBase64) return fromBase64(base64); + const binary = atob(base64); + const out = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i); + return out; +} + +/** A name that is safe as a file or folder name on every system. */ +export function safeName(name: string, fallback: string, max = 80): string { + // eslint-disable-next-line no-control-regex + const clean = name.replace(/[\u0000-\u001f\u007f<>:"/\\|?*]+/g, " ").replace(/\s+/g, " ").trim().replace(/^\.+|\.+$/g, ""); + const cut = Array.from(clean).slice(0, max).join("").trim(); + return cut || fallback; +} + +/** + * The account's folders as export paths, parents first in the order the app + * shows them; two folders with the same name next to each other get " (2)". + */ +export function folderTree(mailboxes: Mailbox[]): ExportFolder[] { + const idOf = (m: Mailbox) => m.originalId || m.id; + const byParent = new Map(); + const ids = new Set(mailboxes.map((m) => m.id)); + for (const m of mailboxes) { + const parent = m.parentId && ids.has(m.parentId) ? m.parentId : ""; + if (!byParent.has(parent)) byParent.set(parent, []); + byParent.get(parent)!.push(m); + } + const ROLE_ORDER = ["inbox", "drafts", "sent", "archive", "junk", "trash"]; + const rank = (m: Mailbox) => { const i = m.role ? ROLE_ORDER.indexOf(m.role) : -1; return i === -1 ? ROLE_ORDER.length : i; }; + const out: ExportFolder[] = []; + const visit = (parent: string, path: string[]) => { + const children = (byParent.get(parent) ?? []).slice().sort((a, b) => rank(a) - rank(b) || (a.sortOrder ?? 0) - (b.sortOrder ?? 0) || a.name.localeCompare(b.name)); + const used = new Set(); + for (const m of children) { + const base = safeName(m.name, "Folder"); + let name = base; + for (let n = 2; used.has(name.toLowerCase()); n++) name = `${base} (${n})`; + used.add(name.toLowerCase()); + out.push({ mailboxId: idOf(m), path: [...path, name] }); + visit(m.id, [...path, name]); + } + }; + visit("", []); + return out; +} + +/** Keeps the export under a number of requests per minute. */ +class RateGate { + private times: number[] = []; + constructor(private readonly perMinute: number, private readonly now: () => number, private readonly sleep: (ms: number) => Promise) {} + async take(): Promise { + for (;;) { + const t = this.now(); + this.times = this.times.filter((x) => t - x < 60_000); + if (this.times.length < this.perMinute) { this.times.push(t); return; } + await this.sleep(60_000 - (t - this.times[0]) + 10); + } + } +} + +export async function runExport(options: ExportOptions): Promise { + const { client, accountId, signal, sink } = options; + const now = options.now ?? Date.now; + const sleep = (ms: number) => (options.sleep ?? defaultSleep)(ms, signal); + const gate = new RateGate(options.requestsPerMinute ?? 600, now, sleep); + const lanes = Math.max(1, (client.getMaxConcurrentRequests?.() ?? 4) - 2); + const progress: ExportProgress = { phase: "running", exported: 0, failed: 0, failures: [], total: 0, fraction: 0 }; + let lastReport = 0; + const report = (force = false) => { + const t = now(); + if (!force && t - lastReport < (options.reportEveryMs ?? 150)) return; + lastReport = t; + progress.fraction = progress.total > 0 ? Math.min(1, (progress.exported + progress.failed) / progress.total) : 0; + options.onProgress({ ...progress, failures: [...progress.failures] }); + }; + const fail = (label: string, reason: string) => { + progress.failed++; + if (progress.failures.length < MAX_FAILURES_LISTED) progress.failures.push({ label, reason }); + }; + const pause = async (ms: number) => { + progress.phase = "paused"; + progress.pausedUntil = now() + ms; + report(true); + await sleep(ms); + progress.phase = "running"; + progress.pausedUntil = undefined; + report(true); + }; + + /** Runs a request, waiting out the server's refusals. */ + const withRetry = async (run: () => Promise): Promise => { + for (let attempt = 0; ; attempt++) { + if (signal.aborted) throw cancelled(); + await gate.take(); + try { + return await run(); + } catch (error) { + if (isAbort(error) || signal.aborted) throw cancelled(); + const name = (error as { name?: string } | null)?.name; + if (name === "RateLimitError") { + await pause(Math.max(1000, (error as { retryAfterMs?: number }).retryAfterMs ?? 60_000)); + continue; + } + const message = error instanceof Error ? error.message : String(error); + const busy = /maxConcurrentRequests/.test(message); + if ((busy || /network|Failed to fetch|timed out/i.test(message)) && attempt < RETRY_DELAYS_MS.length) { + await sleep(RETRY_DELAYS_MS[attempt]); + continue; + } + throw error; + } + } + }; + + const download = (ref: MessageRef): Promise => withRetry(async () => { + if (client.fetchBlobForTransfer) return client.fetchBlobForTransfer(ref.blobId, accountId, signal); + return new Uint8Array(await (await client.fetchBlob(ref.blobId, "message.eml", "message/rfc822", accountId)).arrayBuffer()); + }); + + /** The raw messages of one batch, in order; null where a message couldn't be fetched. */ + const fetchBatch = async (batch: MessageRef[]): Promise<(Uint8Array | Error)[]> => { + const out = new Map(); + if (batch.length > 1 || (batch.length === 1 && batch[0].size <= BATCH_MESSAGE_MAX)) { + try { + const result = client.getBlobsAsBase64 + ? await withRetry(() => client.getBlobsAsBase64!(batch.map((r) => r.blobId), accountId, { sized: true })) + : null; + if (result) for (const [blobId, base64] of result.data) out.set(blobId, decodeBase64(base64)); + } catch (error) { + if (isAbort(error) || signal.aborted) throw cancelled(); + // Fall back to downloads for this batch. + } + } + const results: (Uint8Array | Error)[] = []; + for (const ref of batch) { + const got = out.get(ref.blobId); + if (got) { results.push(got); continue; } + try { + results.push(await download(ref)); + } catch (error) { + if (isAbort(error) || signal.aborted) throw cancelled(); + results.push(error instanceof Error ? error : new Error(String(error))); + } + } + return results; + }; + + const batchesOf = (refs: MessageRef[]): MessageRef[][] => { + const batches: MessageRef[][] = []; + let current: MessageRef[] = []; + let bytes = 0; + for (const ref of refs) { + if (ref.size > BATCH_MESSAGE_MAX) { batches.push([ref]); continue; } + if (current.length > 0 && (bytes + ref.size > BATCH_BYTES || current.length >= BATCH_COUNT)) { + batches.push(current); + current = []; + bytes = 0; + } + current.push(ref); + bytes += ref.size; + } + if (current.length > 0) batches.push(current); + return batches; + }; + + try { + // Every folder's messages, oldest first, so the totals are known up front. + const lists: MessageRef[][] = []; + for (const folder of options.folders) { + const list = await withRetry(() => client.queryEmailFields( + { inMailbox: folder.mailboxId }, + ["blobId", "keywords", "receivedAt", "size", "subject"], + accountId, + Number.MAX_SAFE_INTEGER, + )); + const refs = list + .filter((e) => typeof e.blobId === "string") + .map((e) => ({ + id: String(e.id), + blobId: String(e.blobId), + size: typeof e.size === "number" ? e.size : 0, + keywords: (e.keywords as Record | undefined) ?? {}, + receivedAt: typeof e.receivedAt === "string" ? e.receivedAt : undefined, + subject: typeof e.subject === "string" ? e.subject : "", + })) + .reverse(); + lists.push(refs); + progress.total += refs.length; + report(); + } + + const single = options.folders.length === 1 && options.format === "mbox"; + const zip = single ? null : new ZipWriter((chunk) => sink.write(chunk)); + + for (let f = 0; f < options.folders.length; f++) { + const folder = options.folders[f]; + const refs = lists[f]; + const dir = folder.path.join("/"); + let stream: ZipEntryStream | null = null; + if (zip && options.format === "mbox" && refs.length > 0) stream = await zip.openStream(`${dir}.mbox`); + if (zip && options.format === "eml" && refs.length === 0 && dir) await zip.addDirectory(dir); + const write = async (bytes: Uint8Array) => (stream ? stream.write(bytes) : sink.write(bytes)); + + const batches = batchesOf(refs); + let seq = 0; + // Fetch a few batches ahead, write them in order. + const pending: Promise<(Uint8Array | Error)[]>[] = []; + let next = 0; + const fill = () => { + while (pending.length < lanes && next < batches.length) { + const p = fetchBatch(batches[next++]); + p.catch(() => {}); + pending.push(p); + } + }; + fill(); + for (let b = 0; b < batches.length; b++) { + const results = await pending.shift()!; + fill(); + for (let i = 0; i < results.length; i++) { + const ref = batches[b][i]; + const raw = results[i]; + seq++; + if (raw instanceof Error) { + fail(ref.subject, raw.message); + continue; + } + if (options.format === "mbox") { + for (const part of mboxEntry(raw, ref.keywords, ref.receivedAt)) await write(part); + } else { + const name = `${String(seq).padStart(5, "0")} ${safeName(ref.subject, "message", 60)}.eml`; + await zip!.addFile(dir ? `${dir}/${name}` : name, raw, ref.receivedAt ? new Date(ref.receivedAt) : new Date()); + } + progress.exported++; + report(); + } + if (signal.aborted) throw cancelled(); + } + if (stream) await stream.close(); + } + if (zip) await zip.finish(); + progress.file = await sink.close(); + progress.phase = "done"; + progress.fraction = 1; + } catch (error) { + await sink.abort().catch(() => {}); + progress.pausedUntil = undefined; + if (isAbort(error) || signal.aborted) { + progress.phase = "cancelled"; + } else { + progress.phase = "failed"; + progress.error = error instanceof Error ? error.message : String(error); + } + } + report(true); + return progress; +} diff --git a/lib/mail-export/mbox-writer.ts b/lib/mail-export/mbox-writer.ts new file mode 100644 index 000000000..deebc0118 --- /dev/null +++ b/lib/mail-export/mbox-writer.ts @@ -0,0 +1,102 @@ +/** + * Writes messages as mboxrd: an envelope line, the message with every + * `^>*From ` line quoted with one more `>`, and an empty line. The message's + * own line endings are kept; the lines added use the same ones. + * + * State goes into `Status:` (R read, O old), `X-Status:` (A answered, + * F flagged, T draft) and `X-Keywords:` (other keywords), the headers the + * import reads back. Those headers, and Thunderbird's X-Mozilla-* ones, are + * dropped from the message first so an old value can't contradict the new. + */ + +const LF = 0x0a; +const CR = 0x0d; +const GT = 0x3e; +const FROM = [0x46, 0x72, 0x6f, 0x6d, 0x20]; + +const STATE_HEADERS = /^(status|x-status|x-keywords|x-mozilla-status|x-mozilla-status2|x-mozilla-keys)$/i; +/** Keywords the server works out itself, or that have their own header. */ +const NOT_IN_X_KEYWORDS = new Set(["$seen", "$flagged", "$answered", "$draft", "$recent", "$hasattachment", "$hasnoattachment"]); + +const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + +/** `From MAILER-DAEMON Tue May 5 10:11:12 2020` for a JMAP UTCDate (UTC asctime). */ +export function envelopeLine(receivedAt: string | undefined, eol: string): string { + const parsed = receivedAt ? new Date(receivedAt) : new Date(0); + const d = Number.isNaN(parsed.getTime()) ? new Date(0) : parsed; + const pad = (n: number) => String(n).padStart(2, "0"); + const day = String(d.getUTCDate()).padStart(2, " "); + return `From MAILER-DAEMON ${DAYS[d.getUTCDay()]} ${MONTHS[d.getUTCMonth()]} ${day} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())} ${d.getUTCFullYear()}${eol}`; +} + +export function stateHeaders(keywords: Record | undefined, eol: string): string { + const k = keywords ?? {}; + const xStatus = `${k.$answered ? "A" : ""}${k.$flagged ? "F" : ""}${k.$draft ? "T" : ""}`; + const others = Object.keys(k).filter((key) => k[key] && !NOT_IN_X_KEYWORDS.has(key.toLowerCase())); + let out = `Status: ${k.$seen ? "RO" : "O"}${eol}`; + if (xStatus) out += `X-Status: ${xStatus}${eol}`; + if (others.length > 0) out += `X-Keywords: ${others.join(" ")}${eol}`; + return out; +} + +function startsWithFrom(data: Uint8Array, at: number, end: number): boolean { + if (end - at < FROM.length) return false; + for (let i = 0; i < FROM.length; i++) if (data[at + i] !== FROM[i]) return false; + return true; +} + +const encoder = new TextEncoder(); +const GT_BYTE = new Uint8Array([GT]); + +/** + * The bytes for one message, as a list of parts (mostly subarrays of `raw`). + * `raw` is the message as the server stores it. + */ +export function mboxEntry(raw: Uint8Array, keywords: Record | undefined, receivedAt: string | undefined): Uint8Array[] { + // The first line ending decides which ones added lines use. + const firstLf = raw.indexOf(LF); + const eol = firstLf > 0 && raw[firstLf - 1] === CR ? "\r\n" : "\n"; + const parts: Uint8Array[] = [encoder.encode(envelopeLine(receivedAt, eol) + stateHeaders(keywords, eol))]; + + let inHeaders = true; + let runStart = 0; + let lineStart = 0; + let skipping = false; + const flush = (end: number) => { + if (end > runStart) parts.push(raw.subarray(runStart, end)); + }; + while (lineStart < raw.length) { + let lineEnd = raw.indexOf(LF, lineStart); + lineEnd = lineEnd === -1 ? raw.length : lineEnd + 1; + if (inHeaders) { + const isEmpty = lineEnd - lineStart === 1 || (lineEnd - lineStart === 2 && raw[lineStart] === CR); + if (isEmpty) { + inHeaders = false; + skipping = false; + } else if (raw[lineStart] === 0x20 || raw[lineStart] === 0x09) { + // A folded line continues the field before it. + if (skipping) { flush(lineStart); runStart = lineEnd; } + } else { + const colon = raw.indexOf(0x3a, lineStart); + const name = colon > lineStart && colon < lineEnd + ? String.fromCharCode(...raw.subarray(lineStart, colon)).trim() + : ""; + skipping = STATE_HEADERS.test(name); + if (skipping) { flush(lineStart); runStart = lineEnd; } + } + } + let at = lineStart; + while (at < lineEnd && raw[at] === GT) at++; + if (startsWithFrom(raw, at, lineEnd)) { + flush(lineStart); + parts.push(GT_BYTE); + runStart = lineStart; + } + lineStart = lineEnd; + } + flush(raw.length); + const endsWithLf = raw.length > 0 && raw[raw.length - 1] === LF; + parts.push(encoder.encode(endsWithLf ? eol : eol + eol)); + return parts; +} diff --git a/lib/mail-export/sinks.ts b/lib/mail-export/sinks.ts new file mode 100644 index 000000000..7af8af3ea --- /dev/null +++ b/lib/mail-export/sinks.ts @@ -0,0 +1,195 @@ +/** + * Where an export's bytes go, so a large mailbox never has to fit in memory: + * + * 1. File System Access (Chrome, Edge): the user picks the file first and the + * export writes straight into it. + * 2. The origin private file system (Firefox, Safari): written to a private + * file on disk, then downloaded. The file is deleted when the dialog + * closes, on sign-out, and by a sweep when the next export starts. + * 3. Memory, for small exports only, where neither exists. + * + * Writes are gathered into 1 MB pieces: a message arrives in many small parts. + */ + +export interface ExportFile { + name: string; + size: number; + /** Saves the file (again) where the browser keeps downloads; absent when the user picked the place. */ + download?: () => void; + /** Frees what the export kept in this browser. */ + discard: () => Promise; +} + +export interface ExportSink { + kind: "picker" | "private" | "memory"; + write(chunk: Uint8Array): Promise; + close(): Promise; + abort(): Promise; +} + +export class ExportTooLargeError extends Error { + constructor(readonly limit: number) { + super(`The export is larger than this browser can keep in memory (${limit} bytes)`); + this.name = "ExportTooLargeError"; + } +} + +/** The most a memory-only export may hold. */ +export const MEMORY_EXPORT_LIMIT = 200 * 1024 * 1024; +const PRIVATE_DIR = "bulwark-exports"; +const FLUSH_BYTES = 1 << 20; + +interface WritableLike { + write(data: BufferSource | Blob): Promise; + close(): Promise; + abort(reason?: unknown): Promise; +} + +/** Gathers small writes and passes them on in pieces of about 1 MB. */ +function buffered(write: (chunk: Uint8Array) => Promise) { + let parts: Uint8Array[] = []; + let size = 0; + let total = 0; + const flush = async () => { + if (size === 0) return; + const out = new Uint8Array(size); + let at = 0; + for (const p of parts) { out.set(p, at); at += p.length; } + parts = []; + size = 0; + await write(out); + }; + return { + async write(chunk: Uint8Array) { + if (chunk.length === 0) return; + // The caller may reuse its buffer: keep a copy until it's written. + parts.push(chunk.slice()); + size += chunk.length; + total += chunk.length; + if (size >= FLUSH_BYTES) await flush(); + }, + flush, + get total() { return total; }, + }; +} + +function triggerDownload(url: string, name: string): void { + const a = document.createElement("a"); + a.href = url; + a.download = name; + a.rel = "noopener"; + a.style.display = "none"; + document.body.appendChild(a); + a.click(); + a.remove(); +} + +type SavePicker = (options: { suggestedName?: string; types?: { description?: string; accept: Record }[] }) => Promise<{ createWritable(): Promise }>; + +export function canPickSaveFile(): boolean { + return typeof window !== "undefined" && typeof (window as unknown as { showSaveFilePicker?: unknown }).showSaveFilePicker === "function"; +} + +/** + * Asks where to save (call it straight from the click, before any await: + * the browser only shows the picker for a user action). Rejects with an + * AbortError when the user closes the picker. + */ +export async function pickSaveFile(name: string, mime: string, extension: string): Promise { + const picker = (window as unknown as { showSaveFilePicker: SavePicker }).showSaveFilePicker; + const handle = await picker({ suggestedName: name, types: [{ accept: { [mime]: [extension] } }] }); + const writable = await handle.createWritable(); + const buf = buffered((chunk) => writable.write(chunk as BufferSource)); + return { + kind: "picker", + write: (chunk) => buf.write(chunk), + close: async () => { + await buf.flush(); + await writable.close(); + return { name, size: buf.total, discard: async () => {} }; + }, + abort: async () => { await writable.abort().catch(() => {}); }, + }; +} + +async function privateDir(): Promise { + try { + const root = await navigator.storage?.getDirectory?.(); + return root ? await root.getDirectoryHandle(PRIVATE_DIR, { create: true }) : null; + } catch { + return null; + } +} + +/** A private file on disk, downloaded when done; null where the browser can't write one from the page. */ +export async function privateFileSink(name: string, mime: string): Promise { + const dir = await privateDir(); + if (!dir) return null; + const entry = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + let handle: FileSystemFileHandle; + let writable: WritableLike; + try { + handle = await dir.getFileHandle(entry, { create: true }); + const create = (handle as unknown as { createWritable?: () => Promise }).createWritable; + if (typeof create !== "function") { + await dir.removeEntry(entry).catch(() => {}); + return null; + } + writable = await create.call(handle); + } catch { + await dir.removeEntry(entry).catch(() => {}); + return null; + } + const buf = buffered((chunk) => writable.write(chunk as BufferSource)); + const remove = () => dir.removeEntry(entry).catch(() => {}); + return { + kind: "private", + write: (chunk) => buf.write(chunk), + close: async () => { + await buf.flush(); + await writable.close(); + const file = await handle.getFile(); + const url = URL.createObjectURL(new Blob([file], { type: mime })); + triggerDownload(url, name); + return { + name, + size: file.size, + download: () => triggerDownload(url, name), + discard: async () => { URL.revokeObjectURL(url); await remove(); }, + }; + }, + abort: async () => { await writable.abort().catch(() => {}); await remove(); }, + }; +} + +/** Everything in memory, then downloaded: only for small exports. */ +export function memorySink(name: string, mime: string, limit = MEMORY_EXPORT_LIMIT): ExportSink { + let parts: Uint8Array[] = []; + let size = 0; + return { + kind: "memory", + write: async (chunk) => { + size += chunk.length; + if (size > limit) throw new ExportTooLargeError(limit); + parts.push(chunk.slice()); + }, + close: async () => { + const blob = new Blob(parts as BlobPart[], { type: mime }); + parts = []; + const url = URL.createObjectURL(blob); + triggerDownload(url, name); + return { name, size: blob.size, download: () => triggerDownload(url, name), discard: async () => URL.revokeObjectURL(url) }; + }, + abort: async () => { parts = []; }, + }; +} + +/** Deletes private export files left behind (a closed tab, a crash). */ +export async function sweepPrivateExports(): Promise { + try { + const root = await navigator.storage?.getDirectory?.(); + await root?.removeEntry(PRIVATE_DIR, { recursive: true }); + } catch { + /* nothing there */ + } +} diff --git a/lib/sign-out-cleanup.ts b/lib/sign-out-cleanup.ts index 65eb15da0..8507260d6 100644 --- a/lib/sign-out-cleanup.ts +++ b/lib/sign-out-cleanup.ts @@ -1,5 +1,6 @@ import { fileStorage } from '@/lib/plugin-storage'; import { clearAllCheckpoints } from '@/lib/mail-import/checkpoints'; +import { sweepPrivateExports } from '@/lib/mail-export/sinks'; /** * What a full sign-out removes from this browser beyond the stores that @@ -71,6 +72,8 @@ export function purgeSignedOutData(): void { void fileStorage.clearFiles().catch(() => {}); // Where mail imports stopped: file names and folders of the accounts. void clearAllCheckpoints().catch(() => {}); + // Mail exports written to the browser's private files and not deleted yet. + void sweepPrivateExports().catch(() => {}); for (const cleanup of signedOutCleanups) { try { cleanup(); diff --git a/locales/ar/common.json b/locales/ar/common.json index 9da6fe53b..137fdc947 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "تعذّر على Bulwark قراءة ملف Outlook هذا.", "pst_error_worker": "لم يُحمَّل قارئ ملفات Outlook. أعد تحميل الصفحة وحاول مرة أخرى." }, + "mail_export": { + "menu_export": "تصدير المجلد…", + "settings_label": "تصدير كل البريد", + "settings_description": "احفظ كل مجلدات هذا الحساب في ملف ZIP واحد، كملفات mbox أو ‎.eml.", + "settings_button": "تصدير…", + "title_folder": "تصدير «{name}»", + "title_account": "تصدير كل البريد", + "description_folder": "احفظ رسائل هذا المجلد في ملف.", + "description_account": "احفظ كل مجلدات هذا الحساب في ملف ZIP واحد.", + "format_label": "التنسيق", + "format_mbox_folder": "ملف mbox", + "format_eml_folder": "ملف ZIP من ملفات ‎.eml", + "format_mbox_account": "ملف ZIP بملف mbox لكل مجلد", + "format_eml_account": "ملف ZIP من ملفات ‎.eml، مجلد لكل مجلد بريد", + "hint_mbox": "تفتح برامج Thunderbird وApple Mail ومعظم برامج البريد ملفات mbox.", + "hint_eml": "ملف لكل رسالة، يفتحه أي برنامج بريد.", + "save_picker": "تختار أنت مكان حفظ الملف.", + "save_download": "يُنزَّل الملف عند انتهاء التصدير.", + "save_memory": "يحتفظ هذا المتصفح بالتصدير كله في الذاكرة، لذا لا يحفظ أكثر من 200 ميغابايت. للمزيد استخدم Chrome أو Edge، أو صدّر مجلدًا مجلدًا.", + "start": "تصدير", + "cancel": "إلغاء", + "running_title": "جارٍ تصدير البريد", + "progress_label": "تقدّم التصدير", + "status_exported": "الرسائل المصدّرة: {count} من {total}", + "paused": "متوقف مؤقتًا حتى {time}: طلب الخادم استراحة. سيتابع التصدير وحده.", + "background": "المتابعة في الخلفية", + "cancel_export": "إلغاء التصدير", + "chip": "جارٍ تصدير البريد: {percent}٪", + "chip_paused": "التصدير متوقف مؤقتًا حتى {time}", + "done_title": "انتهى التصدير", + "cancelled_title": "أُلغي التصدير", + "failed_title": "توقف التصدير", + "summary_exported": "الرسائل المصدّرة: {count}", + "summary_file": "حُفظ باسم {name} ({size}).", + "summary_failed": "تعذّر التصدير: {count}", + "download_again": "التنزيل مرة أخرى", + "too_large": "التصدير أكبر مما يستطيع هذا المتصفح حفظه في الذاكرة. استخدم Chrome أو Edge، أو صدّر مجلدًا مجلدًا.", + "close": "إغلاق", + "untitled": "(بلا موضوع)" + }, "recover_deleted": { "tab": "استعادة العناصر المحذوفة", "title": "استعادة العناصر المحذوفة", diff --git a/locales/ca/common.json b/locales/ca/common.json index ae1c1c8e8..a06eb0ad4 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "Bulwark no ha pogut llegir aquest fitxer d'Outlook.", "pst_error_worker": "El lector de fitxers d'Outlook no s'ha carregat. Torneu a carregar la pàgina i proveu-ho de nou." }, + "mail_export": { + "menu_export": "Exporta la carpeta…", + "settings_label": "Exporta tot el correu", + "settings_description": "Desa totes les carpetes d'aquest compte en un fitxer ZIP, com a fitxers mbox o .eml.", + "settings_button": "Exporta…", + "title_folder": "Exporta «{name}»", + "title_account": "Exporta tot el correu", + "description_folder": "Desa els missatges d'aquesta carpeta en un fitxer.", + "description_account": "Desa totes les carpetes d'aquest compte en un fitxer ZIP.", + "format_label": "Format", + "format_mbox_folder": "Fitxer mbox", + "format_eml_folder": "ZIP de fitxers .eml", + "format_mbox_account": "ZIP amb un fitxer mbox per carpeta", + "format_eml_account": "ZIP de fitxers .eml, una carpeta per cada carpeta de correu", + "hint_mbox": "Thunderbird, Apple Mail i la majoria de programes de correu obren fitxers mbox.", + "hint_eml": "Un fitxer per missatge, que qualsevol programa de correu pot obrir.", + "save_picker": "Trieu on es desa el fitxer.", + "save_download": "El fitxer es baixa quan acaba l'exportació.", + "save_memory": "Aquest navegador guarda tota l'exportació a la memòria i només pot desar fins a 200 MB. Per a més, feu servir Chrome o Edge, o exporteu carpeta per carpeta.", + "start": "Exporta", + "cancel": "Cancel·la", + "running_title": "S'està exportant el correu", + "progress_label": "Progrés de l'exportació", + "status_exported": "Missatges exportats: {count} de {total}", + "paused": "En pausa fins a les {time}: el servidor ha demanat una pausa. L'exportació continua sola.", + "background": "Continua en segon pla", + "cancel_export": "Cancel·la l'exportació", + "chip": "S'està exportant el correu: {percent} %", + "chip_paused": "Exportació en pausa fins a les {time}", + "done_title": "Exportació acabada", + "cancelled_title": "Exportació cancel·lada", + "failed_title": "Exportació aturada", + "summary_exported": "Missatges exportats: {count}", + "summary_file": "Desat com a {name} ({size}).", + "summary_failed": "No s'han pogut exportar: {count}", + "download_again": "Torna a baixar", + "too_large": "L'exportació és més gran del que aquest navegador pot guardar a la memòria. Feu servir Chrome o Edge, o exporteu carpeta per carpeta.", + "close": "Tanca", + "untitled": "(sense assumpte)" + }, "recover_deleted": { "tab": "Recupera elements suprimits", "title": "Recupera elements suprimits", diff --git a/locales/cs/common.json b/locales/cs/common.json index 04504cb34..a4eed6bb6 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark tento soubor Outlooku nedokázal přečíst.", "pst_error_worker": "Čtečka souborů Outlooku se nenačetla. Načtěte stránku znovu a zkuste to ještě jednou." }, + "mail_export": { + "menu_export": "Exportovat složku…", + "settings_label": "Exportovat všechnu poštu", + "settings_description": "Uložit všechny složky tohoto účtu do jednoho souboru ZIP jako soubory mbox nebo .eml.", + "settings_button": "Exportovat…", + "title_folder": "Exportovat „{name}“", + "title_account": "Exportovat všechnu poštu", + "description_folder": "Uložit zprávy v této složce jako soubor.", + "description_account": "Uložit všechny složky tohoto účtu do jednoho souboru ZIP.", + "format_label": "Formát", + "format_mbox_folder": "Soubor mbox", + "format_eml_folder": "ZIP se soubory .eml", + "format_mbox_account": "ZIP s jedním souborem mbox na složku", + "format_eml_account": "ZIP se soubory .eml, jedna složka na poštovní složku", + "hint_mbox": "Thunderbird, Apple Mail a většina poštovních programů otevře soubory mbox.", + "hint_eml": "Jeden soubor na zprávu, který otevře každý poštovní program.", + "save_picker": "Sami vyberete, kam se soubor uloží.", + "save_download": "Soubor se stáhne, až bude export hotový.", + "save_memory": "Tento prohlížeč drží celý export v paměti, takže uloží nejvýše 200 MB. Pro větší export použijte Chrome nebo Edge, nebo exportujte složku po složce.", + "start": "Exportovat", + "cancel": "Zrušit", + "running_title": "Exportuje se pošta", + "progress_label": "Průběh exportu", + "status_exported": "Exportované zprávy: {count} z {total}", + "paused": "Pozastaveno do {time}: server požádal o přestávku. Export pokračuje sám.", + "background": "Běžet na pozadí", + "cancel_export": "Zrušit export", + "chip": "Exportuje se pošta: {percent} %", + "chip_paused": "Export pozastaven do {time}", + "done_title": "Export dokončen", + "cancelled_title": "Export zrušen", + "failed_title": "Export zastaven", + "summary_exported": "Exportované zprávy: {count}", + "summary_file": "Uloženo jako {name} ({size}).", + "summary_failed": "Nelze exportovat: {count}", + "download_again": "Stáhnout znovu", + "too_large": "Export je větší, než tento prohlížeč udrží v paměti. Použijte Chrome nebo Edge, nebo exportujte složku po složce.", + "close": "Zavřít", + "untitled": "(bez předmětu)" + }, "recover_deleted": { "tab": "Obnovit smazané položky", "title": "Obnovit smazané položky", diff --git a/locales/da/common.json b/locales/da/common.json index 82cc9cfdd..8d0d768d0 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "Bulwark kunne ikke læse denne Outlook-fil.", "pst_error_worker": "Læseren til Outlook-filer blev ikke indlæst. Genindlæs siden, og prøv igen." }, + "mail_export": { + "menu_export": "Eksportér mappe…", + "settings_label": "Eksportér al mail", + "settings_description": "Gem alle mapper i denne konto i én ZIP-fil som mbox- eller .eml-filer.", + "settings_button": "Eksportér…", + "title_folder": "Eksportér “{name}”", + "title_account": "Eksportér al mail", + "description_folder": "Gem beskederne i denne mappe som en fil.", + "description_account": "Gem alle mapper i denne konto i én ZIP-fil.", + "format_label": "Format", + "format_mbox_folder": "mbox-fil", + "format_eml_folder": "ZIP med .eml-filer", + "format_mbox_account": "ZIP med én mbox-fil pr. mappe", + "format_eml_account": "ZIP med .eml-filer, én mappe pr. mailmappe", + "hint_mbox": "Thunderbird, Apple Mail og de fleste mailprogrammer kan åbne mbox-filer.", + "hint_eml": "Én fil pr. besked, som alle mailprogrammer kan åbne.", + "save_picker": "Du vælger, hvor filen gemmes.", + "save_download": "Filen downloades, når eksporten er færdig.", + "save_memory": "Denne browser holder hele eksporten i hukommelsen og kan derfor højst gemme 200 MB. Brug Chrome eller Edge til mere, eller eksportér mappe for mappe.", + "start": "Eksportér", + "cancel": "Annullér", + "running_title": "Eksporterer mail", + "progress_label": "Eksportens forløb", + "status_exported": "Eksporterede beskeder: {count} af {total}", + "paused": "Sat på pause til {time}: serveren bad om en pause. Eksporten fortsætter af sig selv.", + "background": "Kør i baggrunden", + "cancel_export": "Annullér eksport", + "chip": "Eksporterer mail: {percent} %", + "chip_paused": "Eksport sat på pause til {time}", + "done_title": "Eksport færdig", + "cancelled_title": "Eksport annulleret", + "failed_title": "Eksport stoppet", + "summary_exported": "Eksporterede beskeder: {count}", + "summary_file": "Gemt som {name} ({size}).", + "summary_failed": "Kunne ikke eksporteres: {count}", + "download_again": "Download igen", + "too_large": "Eksporten er større, end denne browser kan holde i hukommelsen. Brug Chrome eller Edge, eller eksportér mappe for mappe.", + "close": "Luk", + "untitled": "(intet emne)" + }, "recover_deleted": { "tab": "Gendan slettede elementer", "title": "Gendan slettede elementer", diff --git a/locales/de/common.json b/locales/de/common.json index 824c4b2ec..aaec06610 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark konnte diese Outlook-Datei nicht lesen.", "pst_error_worker": "Das Lesemodul für Outlook-Dateien wurde nicht geladen. Laden Sie die Seite neu und versuchen Sie es noch einmal." }, + "mail_export": { + "menu_export": "Ordner exportieren…", + "settings_label": "Alle E-Mails exportieren", + "settings_description": "Alle Ordner dieses Kontos in einer ZIP-Datei sichern, als mbox- oder .eml-Dateien.", + "settings_button": "Exportieren…", + "title_folder": "„{name}“ exportieren", + "title_account": "Alle E-Mails exportieren", + "description_folder": "Die Nachrichten in diesem Ordner als Datei sichern.", + "description_account": "Alle Ordner dieses Kontos in einer ZIP-Datei sichern.", + "format_label": "Format", + "format_mbox_folder": "mbox-Datei", + "format_eml_folder": "ZIP mit .eml-Dateien", + "format_mbox_account": "ZIP mit einer mbox-Datei pro Ordner", + "format_eml_account": "ZIP mit .eml-Dateien, ein Ordner pro E-Mail-Ordner", + "hint_mbox": "Thunderbird, Apple Mail und die meisten E-Mail-Programme öffnen mbox-Dateien.", + "hint_eml": "Eine Datei pro Nachricht, die jedes E-Mail-Programm öffnen kann.", + "save_picker": "Sie wählen, wo die Datei gespeichert wird.", + "save_download": "Die Datei wird heruntergeladen, wenn der Export fertig ist.", + "save_memory": "Dieser Browser hält den ganzen Export im Speicher und kann daher höchstens 200 MB sichern. Für mehr nutzen Sie Chrome oder Edge, oder exportieren Sie Ordner für Ordner.", + "start": "Exportieren", + "cancel": "Abbrechen", + "running_title": "E-Mails werden exportiert", + "progress_label": "Fortschritt des Exports", + "status_exported": "Exportierte Nachrichten: {count} von {total}", + "paused": "Pausiert bis {time}: Der Server hat um eine Pause gebeten. Der Export läuft von selbst weiter.", + "background": "Im Hintergrund weiterlaufen lassen", + "cancel_export": "Export abbrechen", + "chip": "E-Mails werden exportiert: {percent} %", + "chip_paused": "Export pausiert bis {time}", + "done_title": "Export abgeschlossen", + "cancelled_title": "Export abgebrochen", + "failed_title": "Export angehalten", + "summary_exported": "Exportierte Nachrichten: {count}", + "summary_file": "Gespeichert als {name} ({size}).", + "summary_failed": "Nicht exportierbar: {count}", + "download_again": "Erneut herunterladen", + "too_large": "Der Export ist größer, als dieser Browser im Speicher halten kann. Nutzen Sie Chrome oder Edge, oder exportieren Sie Ordner für Ordner.", + "close": "Schließen", + "untitled": "(kein Betreff)" + }, "recover_deleted": { "tab": "Gelöschte E-Mails", "title": "Gelöschte Elemente wiederherstellen", diff --git a/locales/en/common.json b/locales/en/common.json index 7861331f4..d3a9838e3 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "Bulwark couldn't read this Outlook file.", "pst_error_worker": "The Outlook file reader didn't load. Reload the page and try again." }, + "mail_export": { + "menu_export": "Export folder…", + "settings_label": "Export all mail", + "settings_description": "Save every folder of this account in one ZIP file, as mbox or .eml files.", + "settings_button": "Export…", + "title_folder": "Export “{name}”", + "title_account": "Export all mail", + "description_folder": "Save the messages in this folder as a file.", + "description_account": "Save every folder of this account in one ZIP file.", + "format_label": "Format", + "format_mbox_folder": "mbox file", + "format_eml_folder": "ZIP of .eml files", + "format_mbox_account": "ZIP with one mbox file per folder", + "format_eml_account": "ZIP of .eml files, one folder per mail folder", + "hint_mbox": "Thunderbird, Apple Mail and most mail programs open mbox files.", + "hint_eml": "One file per message, which any mail program can open.", + "save_picker": "You choose where to save the file.", + "save_download": "The file downloads when the export is done.", + "save_memory": "This browser keeps the whole export in memory, so it can save at most 200 MB. For more, use Chrome or Edge, or export folder by folder.", + "start": "Export", + "cancel": "Cancel", + "running_title": "Exporting mail", + "progress_label": "Export progress", + "status_exported": "Messages exported: {count} of {total}", + "paused": "Paused until {time}: the server asked for a break. The export goes on by itself.", + "background": "Run in background", + "cancel_export": "Cancel export", + "chip": "Exporting mail: {percent}%", + "chip_paused": "Export paused until {time}", + "done_title": "Export finished", + "cancelled_title": "Export cancelled", + "failed_title": "Export stopped", + "summary_exported": "Messages exported: {count}", + "summary_file": "Saved as {name} ({size}).", + "summary_failed": "Couldn't be exported: {count}", + "download_again": "Download again", + "too_large": "The export is larger than this browser can keep in memory. Use Chrome or Edge, or export folder by folder.", + "close": "Close", + "untitled": "(no subject)" + }, "recover_deleted": { "tab": "Recover deleted items", "title": "Recover deleted items", diff --git a/locales/es/common.json b/locales/es/common.json index 153a60cfd..01daab3af 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark no pudo leer este archivo de Outlook.", "pst_error_worker": "El lector de archivos de Outlook no se cargó. Vuelva a cargar la página e inténtelo de nuevo." }, + "mail_export": { + "menu_export": "Exportar carpeta…", + "settings_label": "Exportar todo el correo", + "settings_description": "Guardar todas las carpetas de esta cuenta en un archivo ZIP, como archivos mbox o .eml.", + "settings_button": "Exportar…", + "title_folder": "Exportar «{name}»", + "title_account": "Exportar todo el correo", + "description_folder": "Guardar los mensajes de esta carpeta en un archivo.", + "description_account": "Guardar todas las carpetas de esta cuenta en un archivo ZIP.", + "format_label": "Formato", + "format_mbox_folder": "Archivo mbox", + "format_eml_folder": "ZIP de archivos .eml", + "format_mbox_account": "ZIP con un archivo mbox por carpeta", + "format_eml_account": "ZIP de archivos .eml, una carpeta por carpeta de correo", + "hint_mbox": "Thunderbird, Apple Mail y la mayoría de los programas de correo abren archivos mbox.", + "hint_eml": "Un archivo por mensaje, que cualquier programa de correo puede abrir.", + "save_picker": "Usted elige dónde guardar el archivo.", + "save_download": "El archivo se descarga cuando termina la exportación.", + "save_memory": "Este navegador guarda toda la exportación en memoria, así que solo puede guardar hasta 200 MB. Para más, use Chrome o Edge, o exporte carpeta por carpeta.", + "start": "Exportar", + "cancel": "Cancelar", + "running_title": "Exportando correo", + "progress_label": "Progreso de la exportación", + "status_exported": "Mensajes exportados: {count} de {total}", + "paused": "En pausa hasta las {time}: el servidor pidió una pausa. La exportación sigue sola.", + "background": "Seguir en segundo plano", + "cancel_export": "Cancelar exportación", + "chip": "Exportando correo: {percent} %", + "chip_paused": "Exportación en pausa hasta las {time}", + "done_title": "Exportación terminada", + "cancelled_title": "Exportación cancelada", + "failed_title": "Exportación detenida", + "summary_exported": "Mensajes exportados: {count}", + "summary_file": "Guardado como {name} ({size}).", + "summary_failed": "No se pudieron exportar: {count}", + "download_again": "Descargar de nuevo", + "too_large": "La exportación es mayor de lo que este navegador puede guardar en memoria. Use Chrome o Edge, o exporte carpeta por carpeta.", + "close": "Cerrar", + "untitled": "(sin asunto)" + }, "recover_deleted": { "tab": "Recuperar elementos eliminados", "title": "Recuperar elementos eliminados", diff --git a/locales/et/common.json b/locales/et/common.json index 6eb510729..b7ee46de0 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "Bulwark ei saanud seda Outlooki faili lugeda.", "pst_error_worker": "Outlooki failide lugeja ei laadinud. Laadige leht uuesti ja proovige veel kord." }, + "mail_export": { + "menu_export": "Ekspordi kaust…", + "settings_label": "Ekspordi kõik kirjad", + "settings_description": "Salvestage selle konto kõik kaustad ühte ZIP-faili mbox- või .eml-failidena.", + "settings_button": "Ekspordi…", + "title_folder": "Ekspordi „{name}“", + "title_account": "Ekspordi kõik kirjad", + "description_folder": "Salvestage selle kausta kirjad faili.", + "description_account": "Salvestage selle konto kõik kaustad ühte ZIP-faili.", + "format_label": "Vorming", + "format_mbox_folder": "mbox-fail", + "format_eml_folder": "ZIP .eml-failidega", + "format_mbox_account": "ZIP, iga kausta kohta üks mbox-fail", + "format_eml_account": "ZIP .eml-failidega, iga kirjakausta kohta üks kaust", + "hint_mbox": "Thunderbird, Apple Mail ja enamik e-posti programme avavad mbox-faile.", + "hint_eml": "Iga kirja kohta üks fail, mida avab iga e-posti programm.", + "save_picker": "Teie valite, kuhu fail salvestatakse.", + "save_download": "Fail laaditakse alla, kui eksport on valmis.", + "save_memory": "See brauser hoiab kogu eksporti mälus, seega saab salvestada kuni 200 MB. Suurema jaoks kasutage Chrome'i või Edge'i või eksportige kaust kaupa.", + "start": "Ekspordi", + "cancel": "Loobu", + "running_title": "Kirjade eksportimine", + "progress_label": "Ekspordi edenemine", + "status_exported": "Eksporditud kirju: {count} / {total}", + "paused": "Peatatud kuni {time}: server palus pausi. Eksport jätkab ise.", + "background": "Jätka taustal", + "cancel_export": "Katkesta eksport", + "chip": "Kirjade eksportimine: {percent}%", + "chip_paused": "Eksport peatatud kuni {time}", + "done_title": "Eksport lõpetatud", + "cancelled_title": "Eksport katkestatud", + "failed_title": "Eksport peatus", + "summary_exported": "Eksporditud kirju: {count}", + "summary_file": "Salvestatud nimega {name} ({size}).", + "summary_failed": "Ei saanud eksportida: {count}", + "download_again": "Laadi uuesti alla", + "too_large": "Eksport on suurem, kui see brauser mälus hoida suudab. Kasutage Chrome'i või Edge'i või eksportige kaust kaupa.", + "close": "Sulge", + "untitled": "(teemata)" + }, "recover_deleted": { "tab": "Taasta kustutatud üksused", "title": "Taasta kustutatud üksused", diff --git a/locales/fa/common.json b/locales/fa/common.json index c45fa898d..d7b2fd4c0 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "Bulwark نتوانست این فایل اوت‌لوک را بخواند.", "pst_error_worker": "خواننده فایل‌های اوت‌لوک بارگیری نشد. صفحه را دوباره بارگیری کنید و دوباره امتحان کنید." }, + "mail_export": { + "menu_export": "برون‌بری پوشه…", + "settings_label": "برون‌بری همه نامه‌ها", + "settings_description": "همه پوشه‌های این حساب را در یک فایل ZIP، به شکل فایل‌های mbox یا ‎.eml ذخیره کنید.", + "settings_button": "برون‌بری…", + "title_folder": "برون‌بری «{name}»", + "title_account": "برون‌بری همه نامه‌ها", + "description_folder": "پیام‌های این پوشه را در یک فایل ذخیره کنید.", + "description_account": "همه پوشه‌های این حساب را در یک فایل ZIP ذخیره کنید.", + "format_label": "قالب", + "format_mbox_folder": "فایل mbox", + "format_eml_folder": "ZIP از فایل‌های ‎.eml", + "format_mbox_account": "ZIP با یک فایل mbox برای هر پوشه", + "format_eml_account": "ZIP از فایل‌های ‎.eml، یک پوشه برای هر پوشه نامه", + "hint_mbox": "Thunderbird، Apple Mail و بیشتر برنامه‌های نامه فایل‌های mbox را باز می‌کنند.", + "hint_eml": "یک فایل برای هر پیام که هر برنامه نامه‌ای باز می‌کند.", + "save_picker": "خودتان انتخاب می‌کنید فایل کجا ذخیره شود.", + "save_download": "فایل پس از پایان برون‌بری دانلود می‌شود.", + "save_memory": "این مرورگر کل برون‌بری را در حافظه نگه می‌دارد و بنابراین حداکثر ۲۰۰ مگابایت ذخیره می‌کند. برای بیشتر از Chrome یا Edge استفاده کنید یا پوشه به پوشه برون‌بری کنید.", + "start": "برون‌بری", + "cancel": "لغو", + "running_title": "در حال برون‌بری نامه", + "progress_label": "پیشرفت برون‌بری", + "status_exported": "پیام‌های برون‌بری‌شده: {count} از {total}", + "paused": "تا {time} متوقف شد: سرور درخواست مکث کرد. برون‌بری خودش ادامه می‌دهد.", + "background": "اجرا در پس‌زمینه", + "cancel_export": "لغو برون‌بری", + "chip": "در حال برون‌بری نامه: {percent}٪", + "chip_paused": "برون‌بری تا {time} متوقف است", + "done_title": "برون‌بری تمام شد", + "cancelled_title": "برون‌بری لغو شد", + "failed_title": "برون‌بری متوقف شد", + "summary_exported": "پیام‌های برون‌بری‌شده: {count}", + "summary_file": "با نام {name} ذخیره شد ({size}).", + "summary_failed": "برون‌بری نشد: {count}", + "download_again": "دانلود دوباره", + "too_large": "برون‌بری بزرگ‌تر از آن است که این مرورگر در حافظه نگه دارد. از Chrome یا Edge استفاده کنید یا پوشه به پوشه برون‌بری کنید.", + "close": "بستن", + "untitled": "(بدون موضوع)" + }, "recover_deleted": { "tab": "بازیابی موارد حذف‌شده", "title": "بازیابی موارد حذف‌شده", diff --git a/locales/fr/common.json b/locales/fr/common.json index 245078762..2e390f58d 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark n'a pas pu lire ce fichier Outlook.", "pst_error_worker": "Le lecteur de fichiers Outlook ne s'est pas chargé. Rechargez la page et réessayez." }, + "mail_export": { + "menu_export": "Exporter le dossier…", + "settings_label": "Exporter tous les e-mails", + "settings_description": "Enregistrer tous les dossiers de ce compte dans un fichier ZIP, en fichiers mbox ou .eml.", + "settings_button": "Exporter…", + "title_folder": "Exporter « {name} »", + "title_account": "Exporter tous les e-mails", + "description_folder": "Enregistrer les messages de ce dossier dans un fichier.", + "description_account": "Enregistrer tous les dossiers de ce compte dans un fichier ZIP.", + "format_label": "Format", + "format_mbox_folder": "Fichier mbox", + "format_eml_folder": "ZIP de fichiers .eml", + "format_mbox_account": "ZIP avec un fichier mbox par dossier", + "format_eml_account": "ZIP de fichiers .eml, un dossier par dossier de messagerie", + "hint_mbox": "Thunderbird, Apple Mail et la plupart des logiciels de messagerie ouvrent les fichiers mbox.", + "hint_eml": "Un fichier par message, que tout logiciel de messagerie peut ouvrir.", + "save_picker": "Vous choisissez où enregistrer le fichier.", + "save_download": "Le fichier est téléchargé à la fin de l'export.", + "save_memory": "Ce navigateur garde tout l'export en mémoire et ne peut donc enregistrer que 200 Mo au plus. Au-delà, utilisez Chrome ou Edge, ou exportez dossier par dossier.", + "start": "Exporter", + "cancel": "Annuler", + "running_title": "Export des e-mails", + "progress_label": "Progression de l'export", + "status_exported": "Messages exportés : {count} sur {total}", + "paused": "En pause jusqu'à {time} : le serveur a demandé une pause. L'export reprend tout seul.", + "background": "Continuer en arrière-plan", + "cancel_export": "Annuler l'export", + "chip": "Export des e-mails : {percent} %", + "chip_paused": "Export en pause jusqu'à {time}", + "done_title": "Export terminé", + "cancelled_title": "Export annulé", + "failed_title": "Export interrompu", + "summary_exported": "Messages exportés : {count}", + "summary_file": "Enregistré sous {name} ({size}).", + "summary_failed": "Export impossible : {count}", + "download_again": "Télécharger à nouveau", + "too_large": "L'export est plus grand que ce que ce navigateur peut garder en mémoire. Utilisez Chrome ou Edge, ou exportez dossier par dossier.", + "close": "Fermer", + "untitled": "(sans objet)" + }, "recover_deleted": { "tab": "Récupérer des éléments supprimés", "title": "Récupérer des éléments supprimés", diff --git a/locales/he/common.json b/locales/he/common.json index 91b1b5de5..10f957f4c 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -5436,6 +5436,46 @@ "pst_error_unreadable": "Bulwark לא הצליח לקרוא את קובץ ה-Outlook הזה.", "pst_error_worker": "קורא קובצי Outlook לא נטען. טענו את הדף מחדש ונסו שוב." }, + "mail_export": { + "menu_export": "ייצוא תיקייה…", + "settings_label": "ייצוא כל הדואר", + "settings_description": "שמירת כל התיקיות בחשבון זה בקובץ ZIP אחד, כקובצי mbox או ‎.eml.", + "settings_button": "ייצוא…", + "title_folder": "ייצוא „{name}”", + "title_account": "ייצוא כל הדואר", + "description_folder": "שמירת ההודעות בתיקייה זו כקובץ.", + "description_account": "שמירת כל התיקיות בחשבון זה בקובץ ZIP אחד.", + "format_label": "תבנית", + "format_mbox_folder": "קובץ mbox", + "format_eml_folder": "ZIP של קובצי ‎.eml", + "format_mbox_account": "ZIP עם קובץ mbox אחד לכל תיקייה", + "format_eml_account": "ZIP של קובצי ‎.eml, תיקייה לכל תיקיית דואר", + "hint_mbox": "Thunderbird‏, Apple Mail ורוב תוכנות הדואר פותחות קובצי mbox.", + "hint_eml": "קובץ לכל הודעה, שכל תוכנת דואר יכולה לפתוח.", + "save_picker": "אתם בוחרים היכן לשמור את הקובץ.", + "save_download": "הקובץ יורד בסיום הייצוא.", + "save_memory": "דפדפן זה שומר את כל הייצוא בזיכרון, ולכן יכול לשמור עד 200 MB. ליותר מזה השתמשו ב-Chrome או ב-Edge, או ייצאו תיקייה אחר תיקייה.", + "start": "ייצוא", + "cancel": "ביטול", + "running_title": "מייצא דואר", + "progress_label": "התקדמות הייצוא", + "status_exported": "הודעות שיוצאו: {count} מתוך {total}", + "paused": "מושהה עד {time}: השרת ביקש הפסקה. הייצוא ימשיך מעצמו.", + "background": "המשך ברקע", + "cancel_export": "ביטול הייצוא", + "chip": "מייצא דואר: {percent}%", + "chip_paused": "הייצוא מושהה עד {time}", + "done_title": "הייצוא הסתיים", + "cancelled_title": "הייצוא בוטל", + "failed_title": "הייצוא נעצר", + "summary_exported": "הודעות שיוצאו: {count}", + "summary_file": "נשמר בשם {name} ({size}).", + "summary_failed": "לא ניתן לייצא: {count}", + "download_again": "הורדה מחדש", + "too_large": "הייצוא גדול ממה שדפדפן זה יכול להחזיק בזיכרון. השתמשו ב-Chrome או ב-Edge, או ייצאו תיקייה אחר תיקייה.", + "close": "סגירה", + "untitled": "(ללא נושא)" + }, "recover_deleted": { "tab": "שחזור פריטים שנמחקו", "title": "שחזור פריטים שנמחקו", diff --git a/locales/hu/common.json b/locales/hu/common.json index 7c9066bf9..31751e0f6 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "A Bulwark nem tudta beolvasni ezt az Outlook-fájlt.", "pst_error_worker": "Az Outlook-fájlok olvasója nem töltődött be. Töltse újra az oldalt, és próbálja meg ismét." }, + "mail_export": { + "menu_export": "Mappa exportálása…", + "settings_label": "Az összes levél exportálása", + "settings_description": "A fiók összes mappájának mentése egy ZIP-fájlba, mbox vagy .eml fájlokként.", + "settings_button": "Exportálás…", + "title_folder": "„{name}” exportálása", + "title_account": "Az összes levél exportálása", + "description_folder": "A mappa üzeneteinek mentése fájlba.", + "description_account": "A fiók összes mappájának mentése egy ZIP-fájlba.", + "format_label": "Formátum", + "format_mbox_folder": "mbox-fájl", + "format_eml_folder": "ZIP .eml fájlokkal", + "format_mbox_account": "ZIP mappánként egy mbox-fájllal", + "format_eml_account": "ZIP .eml fájlokkal, levélmappánként egy mappával", + "hint_mbox": "A Thunderbird, az Apple Mail és a legtöbb levelezőprogram megnyitja az mbox-fájlokat.", + "hint_eml": "Üzenetenként egy fájl, amelyet bármely levelezőprogram megnyit.", + "save_picker": "Ön választja ki, hová kerüljön a fájl.", + "save_download": "A fájl az exportálás végén letöltődik.", + "save_memory": "Ez a böngésző a teljes exportot a memóriában tartja, ezért legfeljebb 200 MB-ot tud menteni. Nagyobbhoz használja a Chrome-ot vagy az Edge-et, vagy exportáljon mappánként.", + "start": "Exportálás", + "cancel": "Mégse", + "running_title": "Levelek exportálása folyamatban", + "progress_label": "Az exportálás állapota", + "status_exported": "Exportált üzenetek: {count} / {total}", + "paused": "Szüneteltetve eddig: {time}: a szerver szünetet kért. Az exportálás magától folytatódik.", + "background": "Futtatás a háttérben", + "cancel_export": "Exportálás megszakítása", + "chip": "Levelek exportálása: {percent}%", + "chip_paused": "Az exportálás szünetel eddig: {time}", + "done_title": "Az exportálás befejeződött", + "cancelled_title": "Az exportálás megszakítva", + "failed_title": "Az exportálás leállt", + "summary_exported": "Exportált üzenetek: {count}", + "summary_file": "Mentve: {name} ({size}).", + "summary_failed": "Nem exportálható: {count}", + "download_again": "Letöltés újra", + "too_large": "Az export nagyobb, mint amit ez a böngésző a memóriában tarthat. Használja a Chrome-ot vagy az Edge-et, vagy exportáljon mappánként.", + "close": "Bezárás", + "untitled": "(nincs tárgy)" + }, "recover_deleted": { "tab": "Törölt elemek visszaállítása", "title": "Törölt elemek visszaállítása", diff --git a/locales/it/common.json b/locales/it/common.json index c7f446ad1..9c3513f4e 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark non è riuscito a leggere questo file di Outlook.", "pst_error_worker": "Il lettore dei file di Outlook non si è caricato. Ricarica la pagina e riprova." }, + "mail_export": { + "menu_export": "Esporta cartella…", + "settings_label": "Esporta tutta la posta", + "settings_description": "Salva tutte le cartelle di questo account in un file ZIP, come file mbox o .eml.", + "settings_button": "Esporta…", + "title_folder": "Esporta «{name}»", + "title_account": "Esporta tutta la posta", + "description_folder": "Salva i messaggi di questa cartella in un file.", + "description_account": "Salva tutte le cartelle di questo account in un file ZIP.", + "format_label": "Formato", + "format_mbox_folder": "File mbox", + "format_eml_folder": "ZIP di file .eml", + "format_mbox_account": "ZIP con un file mbox per cartella", + "format_eml_account": "ZIP di file .eml, una cartella per ogni cartella di posta", + "hint_mbox": "Thunderbird, Apple Mail e la maggior parte dei programmi di posta aprono i file mbox.", + "hint_eml": "Un file per messaggio, che qualsiasi programma di posta può aprire.", + "save_picker": "Scegli tu dove salvare il file.", + "save_download": "Il file viene scaricato al termine dell'esportazione.", + "save_memory": "Questo browser tiene in memoria tutta l'esportazione, quindi può salvare al massimo 200 MB. Per di più usa Chrome o Edge, oppure esporta una cartella alla volta.", + "start": "Esporta", + "cancel": "Annulla", + "running_title": "Esportazione della posta", + "progress_label": "Avanzamento dell'esportazione", + "status_exported": "Messaggi esportati: {count} di {total}", + "paused": "In pausa fino alle {time}: il server ha chiesto una pausa. L'esportazione riprende da sola.", + "background": "Continua in background", + "cancel_export": "Annulla esportazione", + "chip": "Esportazione della posta: {percent}%", + "chip_paused": "Esportazione in pausa fino alle {time}", + "done_title": "Esportazione completata", + "cancelled_title": "Esportazione annullata", + "failed_title": "Esportazione interrotta", + "summary_exported": "Messaggi esportati: {count}", + "summary_file": "Salvato come {name} ({size}).", + "summary_failed": "Impossibile esportare: {count}", + "download_again": "Scarica di nuovo", + "too_large": "L'esportazione è più grande di quanto questo browser possa tenere in memoria. Usa Chrome o Edge, oppure esporta una cartella alla volta.", + "close": "Chiudi", + "untitled": "(nessun oggetto)" + }, "recover_deleted": { "tab": "Recupera elementi eliminati", "title": "Recupera elementi eliminati", diff --git a/locales/ja/common.json b/locales/ja/common.json index 8ac38e620..5197b6c54 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark はこの Outlook ファイルを読めませんでした。", "pst_error_worker": "Outlook ファイルの読み込み機能を読み込めませんでした。ページを再読み込みして、もう一度お試しください。" }, + "mail_export": { + "menu_export": "フォルダーをエクスポート…", + "settings_label": "すべてのメールをエクスポート", + "settings_description": "このアカウントのすべてのフォルダーを、mbox または .eml ファイルとして 1 つの ZIP ファイルに保存します。", + "settings_button": "エクスポート…", + "title_folder": "「{name}」をエクスポート", + "title_account": "すべてのメールをエクスポート", + "description_folder": "このフォルダーのメッセージをファイルに保存します。", + "description_account": "このアカウントのすべてのフォルダーを 1 つの ZIP ファイルに保存します。", + "format_label": "形式", + "format_mbox_folder": "mbox ファイル", + "format_eml_folder": ".eml ファイルの ZIP", + "format_mbox_account": "フォルダーごとに mbox ファイル 1 つの ZIP", + "format_eml_account": "メールフォルダーごとにフォルダーを分けた .eml ファイルの ZIP", + "hint_mbox": "mbox ファイルは Thunderbird、Apple Mail など多くのメールソフトで開けます。", + "hint_eml": "メッセージごとに 1 ファイル。どのメールソフトでも開けます。", + "save_picker": "保存先はご自身で選びます。", + "save_download": "エクスポートが終わるとファイルがダウンロードされます。", + "save_memory": "このブラウザーはエクスポート全体をメモリーに保持するため、保存できるのは最大 200 MB です。それ以上は Chrome か Edge を使うか、フォルダーごとにエクスポートしてください。", + "start": "エクスポート", + "cancel": "キャンセル", + "running_title": "メールをエクスポートしています", + "progress_label": "エクスポートの進み具合", + "status_exported": "エクスポートしたメッセージ: {count} / {total}", + "paused": "{time} まで一時停止中: サーバーが休止を求めました。エクスポートは自動で再開します。", + "background": "バックグラウンドで続ける", + "cancel_export": "エクスポートをキャンセル", + "chip": "メールをエクスポート中: {percent}%", + "chip_paused": "エクスポートは {time} まで一時停止中", + "done_title": "エクスポートが完了しました", + "cancelled_title": "エクスポートをキャンセルしました", + "failed_title": "エクスポートが止まりました", + "summary_exported": "エクスポートしたメッセージ: {count}", + "summary_file": "{name} として保存しました({size})。", + "summary_failed": "エクスポートできなかったもの: {count}", + "download_again": "もう一度ダウンロード", + "too_large": "エクスポートがこのブラウザーでメモリーに保持できる量を超えています。Chrome か Edge を使うか、フォルダーごとにエクスポートしてください。", + "close": "閉じる", + "untitled": "(件名なし)" + }, "recover_deleted": { "tab": "削除済みアイテムの復元", "title": "削除済みアイテムの復元", diff --git a/locales/ko/common.json b/locales/ko/common.json index f43c3107f..62c662d2e 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark가 이 Outlook 파일을 읽지 못했습니다.", "pst_error_worker": "Outlook 파일 읽기 도구를 불러오지 못했습니다. 페이지를 새로 고친 뒤 다시 시도하세요." }, + "mail_export": { + "menu_export": "폴더 내보내기…", + "settings_label": "모든 메일 내보내기", + "settings_description": "이 계정의 모든 폴더를 mbox 또는 .eml 파일로 하나의 ZIP 파일에 저장합니다.", + "settings_button": "내보내기…", + "title_folder": "‘{name}’ 내보내기", + "title_account": "모든 메일 내보내기", + "description_folder": "이 폴더의 메시지를 파일로 저장합니다.", + "description_account": "이 계정의 모든 폴더를 하나의 ZIP 파일에 저장합니다.", + "format_label": "형식", + "format_mbox_folder": "mbox 파일", + "format_eml_folder": ".eml 파일 ZIP", + "format_mbox_account": "폴더마다 mbox 파일 하나가 든 ZIP", + "format_eml_account": "메일 폴더마다 폴더 하나로 정리한 .eml 파일 ZIP", + "hint_mbox": "Thunderbird, Apple Mail 등 대부분의 메일 프로그램이 mbox 파일을 엽니다.", + "hint_eml": "메시지마다 파일 하나. 어떤 메일 프로그램으로도 열 수 있습니다.", + "save_picker": "파일을 저장할 위치를 직접 고릅니다.", + "save_download": "내보내기가 끝나면 파일이 다운로드됩니다.", + "save_memory": "이 브라우저는 내보내기 전체를 메모리에 두므로 최대 200MB까지만 저장할 수 있습니다. 더 크면 Chrome이나 Edge를 쓰거나 폴더별로 내보내세요.", + "start": "내보내기", + "cancel": "취소", + "running_title": "메일을 내보내는 중", + "progress_label": "내보내기 진행 상황", + "status_exported": "내보낸 메시지: {count}/{total}", + "paused": "{time}까지 일시 중지됨: 서버가 잠시 쉬기를 요청했습니다. 내보내기는 저절로 계속됩니다.", + "background": "백그라운드에서 실행", + "cancel_export": "내보내기 취소", + "chip": "메일을 내보내는 중: {percent}%", + "chip_paused": "내보내기가 {time}까지 일시 중지됨", + "done_title": "내보내기 완료", + "cancelled_title": "내보내기 취소됨", + "failed_title": "내보내기 중단됨", + "summary_exported": "내보낸 메시지: {count}", + "summary_file": "{name}(으)로 저장했습니다({size}).", + "summary_failed": "내보낼 수 없음: {count}", + "download_again": "다시 다운로드", + "too_large": "내보내기가 이 브라우저가 메모리에 담을 수 있는 크기보다 큽니다. Chrome이나 Edge를 쓰거나 폴더별로 내보내세요.", + "close": "닫기", + "untitled": "(제목 없음)" + }, "recover_deleted": { "tab": "삭제된 항목 복구", "title": "삭제된 항목 복구", diff --git a/locales/lv/common.json b/locales/lv/common.json index 4f62298de..036bc7876 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark nevarēja nolasīt šo Outlook failu.", "pst_error_worker": "Outlook failu lasītājs netika ielādēts. Pārlādējiet lapu un mēģiniet vēlreiz." }, + "mail_export": { + "menu_export": "Eksportēt mapi…", + "settings_label": "Eksportēt visu pastu", + "settings_description": "Saglabāt visas šī konta mapes vienā ZIP failā kā mbox vai .eml failus.", + "settings_button": "Eksportēt…", + "title_folder": "Eksportēt “{name}”", + "title_account": "Eksportēt visu pastu", + "description_folder": "Saglabāt šīs mapes ziņojumus failā.", + "description_account": "Saglabāt visas šī konta mapes vienā ZIP failā.", + "format_label": "Formāts", + "format_mbox_folder": "mbox fails", + "format_eml_folder": "ZIP ar .eml failiem", + "format_mbox_account": "ZIP ar vienu mbox failu katrai mapei", + "format_eml_account": "ZIP ar .eml failiem, viena mape katrai pasta mapei", + "hint_mbox": "Thunderbird, Apple Mail un lielākā daļa pasta programmu atver mbox failus.", + "hint_eml": "Viens fails katram ziņojumam, ko atver jebkura pasta programma.", + "save_picker": "Jūs izvēlaties, kur saglabāt failu.", + "save_download": "Fails tiks lejupielādēts, kad eksportēšana būs pabeigta.", + "save_memory": "Šī pārlūkprogramma tur visu eksportu atmiņā, tāpēc var saglabāt ne vairāk kā 200 MB. Lielākam eksportam izmantojiet Chrome vai Edge vai eksportējiet pa mapei.", + "start": "Eksportēt", + "cancel": "Atcelt", + "running_title": "Notiek pasta eksportēšana", + "progress_label": "Eksportēšanas gaita", + "status_exported": "Eksportētie ziņojumi: {count} no {total}", + "paused": "Apturēts līdz {time}: serveris lūdza pauzi. Eksportēšana turpināsies pati.", + "background": "Turpināt fonā", + "cancel_export": "Atcelt eksportēšanu", + "chip": "Notiek pasta eksportēšana: {percent}%", + "chip_paused": "Eksportēšana apturēta līdz {time}", + "done_title": "Eksportēšana pabeigta", + "cancelled_title": "Eksportēšana atcelta", + "failed_title": "Eksportēšana apturēta", + "summary_exported": "Eksportētie ziņojumi: {count}", + "summary_file": "Saglabāts kā {name} ({size}).", + "summary_failed": "Neizdevās eksportēt: {count}", + "download_again": "Lejupielādēt vēlreiz", + "too_large": "Eksports ir lielāks, nekā šī pārlūkprogramma var noturēt atmiņā. Izmantojiet Chrome vai Edge vai eksportējiet pa mapei.", + "close": "Aizvērt", + "untitled": "(bez temata)" + }, "recover_deleted": { "tab": "Atjaunot dzēstos vienumus", "title": "Atjaunot dzēstos vienumus", diff --git a/locales/mn/common.json b/locales/mn/common.json index 00a5218a9..c9966e04e 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "Bulwark энэ Outlook файлыг уншиж чадсангүй.", "pst_error_worker": "Outlook файл уншигч ачаалагдсангүй. Хуудсыг дахин ачаалаад дахин оролдоно уу." }, + "mail_export": { + "menu_export": "Хавтас экспортлох…", + "settings_label": "Бүх шуудан экспортлох", + "settings_description": "Энэ бүртгэлийн бүх хавтсыг mbox эсвэл .eml файл хэлбэрээр нэг ZIP файлд хадгална.", + "settings_button": "Экспортлох…", + "title_folder": "“{name}” экспортлох", + "title_account": "Бүх шуудан экспортлох", + "description_folder": "Энэ хавтасны захидлуудыг файлд хадгална.", + "description_account": "Энэ бүртгэлийн бүх хавтсыг нэг ZIP файлд хадгална.", + "format_label": "Формат", + "format_mbox_folder": "mbox файл", + "format_eml_folder": ".eml файлуудын ZIP", + "format_mbox_account": "Хавтас бүрт нэг mbox файлтай ZIP", + "format_eml_account": "Шуудангийн хавтас бүрт нэг хавтастай .eml файлуудын ZIP", + "hint_mbox": "Thunderbird, Apple Mail болон ихэнх шуудангийн программ mbox файл нээдэг.", + "hint_eml": "Захидал бүрт нэг файл, ямар ч шуудангийн программ нээнэ.", + "save_picker": "Файлыг хаана хадгалахыг та өөрөө сонгоно.", + "save_download": "Экспорт дуусахад файл татагдана.", + "save_memory": "Энэ хөтөч экспортыг бүхэлд нь санах ойд хадгалдаг тул хамгийн ихдээ 200 MB хадгална. Үүнээс их бол Chrome эсвэл Edge ашиглах, эсвэл хавтас тус бүрээр экспортлоно уу.", + "start": "Экспортлох", + "cancel": "Цуцлах", + "running_title": "Шуудан экспортлож байна", + "progress_label": "Экспортын явц", + "status_exported": "Экспортолсон захидал: {count} / {total}", + "paused": "{time} хүртэл түр зогссон: сервер завсарлага хүссэн. Экспорт өөрөө үргэлжилнэ.", + "background": "Ард ажиллуулах", + "cancel_export": "Экспортыг цуцлах", + "chip": "Шуудан экспортлож байна: {percent}%", + "chip_paused": "Экспорт {time} хүртэл түр зогссон", + "done_title": "Экспорт дууслаа", + "cancelled_title": "Экспорт цуцлагдлаа", + "failed_title": "Экспорт зогслоо", + "summary_exported": "Экспортолсон захидал: {count}", + "summary_file": "{name} нэрээр хадгалсан ({size}).", + "summary_failed": "Экспортлож чадсангүй: {count}", + "download_again": "Дахин татах", + "too_large": "Экспорт энэ хөтчийн санах ойд багтахаас том байна. Chrome эсвэл Edge ашиглах, эсвэл хавтас тус бүрээр экспортлоно уу.", + "close": "Хаах", + "untitled": "(гарчиггүй)" + }, "recover_deleted": { "tab": "Устгасан зүйлсийг сэргээх", "title": "Устгасан зүйлсийг сэргээх", diff --git a/locales/nb/common.json b/locales/nb/common.json index fa8501a8c..1972509bd 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "Bulwark kunne ikke lese denne Outlook-filen.", "pst_error_worker": "Leseren for Outlook-filer ble ikke lastet. Last inn siden på nytt og prøv igjen." }, + "mail_export": { + "menu_export": "Eksporter mappe…", + "settings_label": "Eksporter all e-post", + "settings_description": "Lagre alle mappene i denne kontoen i én ZIP-fil, som mbox- eller .eml-filer.", + "settings_button": "Eksporter…", + "title_folder": "Eksporter «{name}»", + "title_account": "Eksporter all e-post", + "description_folder": "Lagre meldingene i denne mappen som en fil.", + "description_account": "Lagre alle mappene i denne kontoen i én ZIP-fil.", + "format_label": "Format", + "format_mbox_folder": "mbox-fil", + "format_eml_folder": "ZIP med .eml-filer", + "format_mbox_account": "ZIP med én mbox-fil per mappe", + "format_eml_account": "ZIP med .eml-filer, én mappe per e-postmappe", + "hint_mbox": "Thunderbird, Apple Mail og de fleste e-postprogrammer åpner mbox-filer.", + "hint_eml": "Én fil per melding, som alle e-postprogrammer kan åpne.", + "save_picker": "Du velger hvor filen lagres.", + "save_download": "Filen lastes ned når eksporten er ferdig.", + "save_memory": "Denne nettleseren holder hele eksporten i minnet og kan derfor lagre høyst 200 MB. Bruk Chrome eller Edge for mer, eller eksporter mappe for mappe.", + "start": "Eksporter", + "cancel": "Avbryt", + "running_title": "Eksporterer e-post", + "progress_label": "Fremdrift for eksporten", + "status_exported": "Eksporterte meldinger: {count} av {total}", + "paused": "Satt på pause til {time}: serveren ba om en pause. Eksporten fortsetter av seg selv.", + "background": "Kjør i bakgrunnen", + "cancel_export": "Avbryt eksport", + "chip": "Eksporterer e-post: {percent} %", + "chip_paused": "Eksport satt på pause til {time}", + "done_title": "Eksport fullført", + "cancelled_title": "Eksport avbrutt", + "failed_title": "Eksport stoppet", + "summary_exported": "Eksporterte meldinger: {count}", + "summary_file": "Lagret som {name} ({size}).", + "summary_failed": "Kunne ikke eksporteres: {count}", + "download_again": "Last ned på nytt", + "too_large": "Eksporten er større enn denne nettleseren kan holde i minnet. Bruk Chrome eller Edge, eller eksporter mappe for mappe.", + "close": "Lukk", + "untitled": "(uten emne)" + }, "recover_deleted": { "tab": "Gjenopprett slettede elementer", "title": "Gjenopprett slettede elementer", diff --git a/locales/nl/common.json b/locales/nl/common.json index 0588ad1bb..f909eb143 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark kon dit Outlook-bestand niet lezen.", "pst_error_worker": "De lezer voor Outlook-bestanden is niet geladen. Laad de pagina opnieuw en probeer het nog eens." }, + "mail_export": { + "menu_export": "Map exporteren…", + "settings_label": "Alle e-mail exporteren", + "settings_description": "Bewaar alle mappen van dit account in één ZIP-bestand, als mbox- of .eml-bestanden.", + "settings_button": "Exporteren…", + "title_folder": "‘{name}’ exporteren", + "title_account": "Alle e-mail exporteren", + "description_folder": "Bewaar de berichten in deze map als bestand.", + "description_account": "Bewaar alle mappen van dit account in één ZIP-bestand.", + "format_label": "Indeling", + "format_mbox_folder": "mbox-bestand", + "format_eml_folder": "ZIP met .eml-bestanden", + "format_mbox_account": "ZIP met één mbox-bestand per map", + "format_eml_account": "ZIP met .eml-bestanden, één map per e-mailmap", + "hint_mbox": "Thunderbird, Apple Mail en de meeste e-mailprogramma's openen mbox-bestanden.", + "hint_eml": "Eén bestand per bericht, dat elk e-mailprogramma kan openen.", + "save_picker": "U kiest waar het bestand wordt opgeslagen.", + "save_download": "Het bestand wordt gedownload als de export klaar is.", + "save_memory": "Deze browser houdt de hele export in het geheugen en kan dus hoogstens 200 MB opslaan. Gebruik voor meer Chrome of Edge, of exporteer map voor map.", + "start": "Exporteren", + "cancel": "Annuleren", + "running_title": "E-mail wordt geëxporteerd", + "progress_label": "Voortgang van de export", + "status_exported": "Geëxporteerde berichten: {count} van {total}", + "paused": "Gepauzeerd tot {time}: de server vroeg om een pauze. De export gaat vanzelf verder.", + "background": "Op de achtergrond verder", + "cancel_export": "Export annuleren", + "chip": "E-mail wordt geëxporteerd: {percent}%", + "chip_paused": "Export gepauzeerd tot {time}", + "done_title": "Export voltooid", + "cancelled_title": "Export geannuleerd", + "failed_title": "Export gestopt", + "summary_exported": "Geëxporteerde berichten: {count}", + "summary_file": "Opgeslagen als {name} ({size}).", + "summary_failed": "Kon niet worden geëxporteerd: {count}", + "download_again": "Opnieuw downloaden", + "too_large": "De export is groter dan deze browser in het geheugen kan houden. Gebruik Chrome of Edge, of exporteer map voor map.", + "close": "Sluiten", + "untitled": "(geen onderwerp)" + }, "recover_deleted": { "tab": "Verwijderde items herstellen", "title": "Verwijderde items herstellen", diff --git a/locales/pl/common.json b/locales/pl/common.json index 5cb9b6aef..f81146884 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark nie mógł odczytać tego pliku programu Outlook.", "pst_error_worker": "Czytnik plików programu Outlook się nie wczytał. Odśwież stronę i spróbuj ponownie." }, + "mail_export": { + "menu_export": "Eksportuj folder…", + "settings_label": "Eksportuj całą pocztę", + "settings_description": "Zapisz wszystkie foldery tego konta w jednym pliku ZIP, jako pliki mbox lub .eml.", + "settings_button": "Eksportuj…", + "title_folder": "Eksportuj „{name}”", + "title_account": "Eksportuj całą pocztę", + "description_folder": "Zapisz wiadomości z tego folderu w pliku.", + "description_account": "Zapisz wszystkie foldery tego konta w jednym pliku ZIP.", + "format_label": "Format", + "format_mbox_folder": "Plik mbox", + "format_eml_folder": "ZIP z plikami .eml", + "format_mbox_account": "ZIP z jednym plikiem mbox na folder", + "format_eml_account": "ZIP z plikami .eml, jeden folder na folder poczty", + "hint_mbox": "Thunderbird, Apple Mail i większość programów pocztowych otwiera pliki mbox.", + "hint_eml": "Jeden plik na wiadomość, który otworzy każdy program pocztowy.", + "save_picker": "Wybierasz, gdzie zapisać plik.", + "save_download": "Plik zostanie pobrany po zakończeniu eksportu.", + "save_memory": "Ta przeglądarka trzyma cały eksport w pamięci, więc zapisze najwyżej 200 MB. Przy większym eksporcie użyj Chrome lub Edge albo eksportuj folder po folderze.", + "start": "Eksportuj", + "cancel": "Anuluj", + "running_title": "Eksportowanie poczty", + "progress_label": "Postęp eksportu", + "status_exported": "Wyeksportowane wiadomości: {count} z {total}", + "paused": "Wstrzymano do {time}: serwer poprosił o przerwę. Eksport sam działa dalej.", + "background": "Działaj w tle", + "cancel_export": "Anuluj eksport", + "chip": "Eksportowanie poczty: {percent}%", + "chip_paused": "Eksport wstrzymany do {time}", + "done_title": "Eksport zakończony", + "cancelled_title": "Eksport anulowany", + "failed_title": "Eksport zatrzymany", + "summary_exported": "Wyeksportowane wiadomości: {count}", + "summary_file": "Zapisano jako {name} ({size}).", + "summary_failed": "Nie udało się wyeksportować: {count}", + "download_again": "Pobierz ponownie", + "too_large": "Eksport jest większy, niż ta przeglądarka może trzymać w pamięci. Użyj Chrome lub Edge albo eksportuj folder po folderze.", + "close": "Zamknij", + "untitled": "(bez tematu)" + }, "recover_deleted": { "tab": "Odzyskaj usunięte elementy", "title": "Odzyskaj usunięte elementy", diff --git a/locales/pt/common.json b/locales/pt/common.json index f6dd85f8e..8c0dcba2f 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "O Bulwark não conseguiu ler este arquivo do Outlook.", "pst_error_worker": "O leitor de arquivos do Outlook não carregou. Recarregue a página e tente de novo." }, + "mail_export": { + "menu_export": "Exportar pasta…", + "settings_label": "Exportar todos os e-mails", + "settings_description": "Salvar todas as pastas desta conta em um arquivo ZIP, como arquivos mbox ou .eml.", + "settings_button": "Exportar…", + "title_folder": "Exportar “{name}”", + "title_account": "Exportar todos os e-mails", + "description_folder": "Salvar as mensagens desta pasta em um arquivo.", + "description_account": "Salvar todas as pastas desta conta em um arquivo ZIP.", + "format_label": "Formato", + "format_mbox_folder": "Arquivo mbox", + "format_eml_folder": "ZIP de arquivos .eml", + "format_mbox_account": "ZIP com um arquivo mbox por pasta", + "format_eml_account": "ZIP de arquivos .eml, uma pasta por pasta de e-mail", + "hint_mbox": "Thunderbird, Apple Mail e a maioria dos programas de e-mail abrem arquivos mbox.", + "hint_eml": "Um arquivo por mensagem, que qualquer programa de e-mail abre.", + "save_picker": "Você escolhe onde salvar o arquivo.", + "save_download": "O arquivo é baixado quando a exportação termina.", + "save_memory": "Este navegador guarda toda a exportação na memória e por isso só salva até 200 MB. Para mais, use o Chrome ou o Edge, ou exporte pasta por pasta.", + "start": "Exportar", + "cancel": "Cancelar", + "running_title": "Exportando e-mails", + "progress_label": "Progresso da exportação", + "status_exported": "Mensagens exportadas: {count} de {total}", + "paused": "Pausado até {time}: o servidor pediu uma pausa. A exportação continua sozinha.", + "background": "Continuar em segundo plano", + "cancel_export": "Cancelar exportação", + "chip": "Exportando e-mails: {percent}%", + "chip_paused": "Exportação pausada até {time}", + "done_title": "Exportação concluída", + "cancelled_title": "Exportação cancelada", + "failed_title": "Exportação interrompida", + "summary_exported": "Mensagens exportadas: {count}", + "summary_file": "Salvo como {name} ({size}).", + "summary_failed": "Não foi possível exportar: {count}", + "download_again": "Baixar de novo", + "too_large": "A exportação é maior do que este navegador consegue guardar na memória. Use o Chrome ou o Edge, ou exporte pasta por pasta.", + "close": "Fechar", + "untitled": "(sem assunto)" + }, "recover_deleted": { "tab": "Recuperar itens excluídos", "title": "Recuperar itens excluídos", diff --git a/locales/ro/common.json b/locales/ro/common.json index ce94bfda4..37d56aa42 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "Bulwark nu a putut citi acest fișier Outlook.", "pst_error_worker": "Cititorul de fișiere Outlook nu s-a încărcat. Reîncărcați pagina și încercați din nou." }, + "mail_export": { + "menu_export": "Exportă dosarul…", + "settings_label": "Exportă toate e-mailurile", + "settings_description": "Salvați toate dosarele acestui cont într-un fișier ZIP, ca fișiere mbox sau .eml.", + "settings_button": "Exportă…", + "title_folder": "Exportă „{name}”", + "title_account": "Exportă toate e-mailurile", + "description_folder": "Salvați mesajele din acest dosar într-un fișier.", + "description_account": "Salvați toate dosarele acestui cont într-un fișier ZIP.", + "format_label": "Format", + "format_mbox_folder": "Fișier mbox", + "format_eml_folder": "ZIP cu fișiere .eml", + "format_mbox_account": "ZIP cu un fișier mbox pentru fiecare dosar", + "format_eml_account": "ZIP cu fișiere .eml, câte un dosar pentru fiecare dosar de e-mail", + "hint_mbox": "Thunderbird, Apple Mail și majoritatea programelor de e-mail deschid fișiere mbox.", + "hint_eml": "Câte un fișier pentru fiecare mesaj, pe care îl deschide orice program de e-mail.", + "save_picker": "Alegeți unde se salvează fișierul.", + "save_download": "Fișierul se descarcă la sfârșitul exportului.", + "save_memory": "Acest browser ține tot exportul în memorie, deci poate salva cel mult 200 MB. Pentru mai mult, folosiți Chrome sau Edge sau exportați dosar cu dosar.", + "start": "Exportă", + "cancel": "Anulează", + "running_title": "Se exportă e-mailurile", + "progress_label": "Progresul exportului", + "status_exported": "Mesaje exportate: {count} din {total}", + "paused": "În pauză până la {time}: serverul a cerut o pauză. Exportul continuă singur.", + "background": "Rulează în fundal", + "cancel_export": "Anulează exportul", + "chip": "Se exportă e-mailurile: {percent}%", + "chip_paused": "Export în pauză până la {time}", + "done_title": "Export încheiat", + "cancelled_title": "Export anulat", + "failed_title": "Export oprit", + "summary_exported": "Mesaje exportate: {count}", + "summary_file": "Salvat ca {name} ({size}).", + "summary_failed": "Nu s-au putut exporta: {count}", + "download_again": "Descarcă din nou", + "too_large": "Exportul este mai mare decât poate ține acest browser în memorie. Folosiți Chrome sau Edge sau exportați dosar cu dosar.", + "close": "Închide", + "untitled": "(fără subiect)" + }, "recover_deleted": { "tab": "Recuperați elementele șterse", "title": "Recuperați elementele șterse", diff --git a/locales/ru/common.json b/locales/ru/common.json index 1ad24d970..7f60a3f8d 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark не смог прочитать этот файл Outlook.", "pst_error_worker": "Модуль чтения файлов Outlook не загрузился. Перезагрузите страницу и попробуйте снова." }, + "mail_export": { + "menu_export": "Экспортировать папку…", + "settings_label": "Экспортировать всю почту", + "settings_description": "Сохранить все папки этой учётной записи в один ZIP-файл в виде файлов mbox или .eml.", + "settings_button": "Экспортировать…", + "title_folder": "Экспорт «{name}»", + "title_account": "Экспорт всей почты", + "description_folder": "Сохранить сообщения этой папки в файл.", + "description_account": "Сохранить все папки этой учётной записи в один ZIP-файл.", + "format_label": "Формат", + "format_mbox_folder": "Файл mbox", + "format_eml_folder": "ZIP с файлами .eml", + "format_mbox_account": "ZIP с одним файлом mbox на папку", + "format_eml_account": "ZIP с файлами .eml, по папке на каждую почтовую папку", + "hint_mbox": "Thunderbird, Apple Mail и большинство почтовых программ открывают файлы mbox.", + "hint_eml": "По файлу на сообщение, их откроет любая почтовая программа.", + "save_picker": "Вы сами выбираете, куда сохранить файл.", + "save_download": "Файл скачается, когда экспорт завершится.", + "save_memory": "Этот браузер держит весь экспорт в памяти, поэтому сохранит не больше 200 МБ. Для большего используйте Chrome или Edge либо экспортируйте по одной папке.", + "start": "Экспортировать", + "cancel": "Отмена", + "running_title": "Экспорт почты", + "progress_label": "Ход экспорта", + "status_exported": "Экспортировано сообщений: {count} из {total}", + "paused": "Пауза до {time}: сервер попросил перерыв. Экспорт продолжится сам.", + "background": "Продолжить в фоне", + "cancel_export": "Отменить экспорт", + "chip": "Экспорт почты: {percent} %", + "chip_paused": "Экспорт приостановлен до {time}", + "done_title": "Экспорт завершён", + "cancelled_title": "Экспорт отменён", + "failed_title": "Экспорт остановлен", + "summary_exported": "Экспортировано сообщений: {count}", + "summary_file": "Сохранено как {name} ({size}).", + "summary_failed": "Не удалось экспортировать: {count}", + "download_again": "Скачать снова", + "too_large": "Экспорт больше, чем этот браузер может держать в памяти. Используйте Chrome или Edge либо экспортируйте по одной папке.", + "close": "Закрыть", + "untitled": "(без темы)" + }, "recover_deleted": { "tab": "Восстановить удалённое", "title": "Восстановить удалённое", diff --git a/locales/sk/common.json b/locales/sk/common.json index e5f1b7087..12be59cda 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "Bulwark nedokázal prečítať tento súbor Outlooku.", "pst_error_worker": "Čítačka súborov Outlooku sa nenačítala. Načítajte stránku znova a skúste to ešte raz." }, + "mail_export": { + "menu_export": "Exportovať priečinok…", + "settings_label": "Exportovať všetku poštu", + "settings_description": "Uložiť všetky priečinky tohto účtu do jedného súboru ZIP ako súbory mbox alebo .eml.", + "settings_button": "Exportovať…", + "title_folder": "Exportovať „{name}“", + "title_account": "Exportovať všetku poštu", + "description_folder": "Uložiť správy v tomto priečinku ako súbor.", + "description_account": "Uložiť všetky priečinky tohto účtu do jedného súboru ZIP.", + "format_label": "Formát", + "format_mbox_folder": "Súbor mbox", + "format_eml_folder": "ZIP so súbormi .eml", + "format_mbox_account": "ZIP s jedným súborom mbox na priečinok", + "format_eml_account": "ZIP so súbormi .eml, jeden priečinok na poštový priečinok", + "hint_mbox": "Thunderbird, Apple Mail a väčšina poštových programov otvorí súbory mbox.", + "hint_eml": "Jeden súbor na správu, ktorý otvorí každý poštový program.", + "save_picker": "Sami vyberiete, kam sa súbor uloží.", + "save_download": "Súbor sa stiahne, keď bude export hotový.", + "save_memory": "Tento prehliadač drží celý export v pamäti, takže uloží najviac 200 MB. Pri väčšom exporte použite Chrome alebo Edge, alebo exportujte priečinok po priečinku.", + "start": "Exportovať", + "cancel": "Zrušiť", + "running_title": "Exportuje sa pošta", + "progress_label": "Priebeh exportu", + "status_exported": "Exportované správy: {count} z {total}", + "paused": "Pozastavené do {time}: server požiadal o prestávku. Export pokračuje sám.", + "background": "Spustiť na pozadí", + "cancel_export": "Zrušiť export", + "chip": "Exportuje sa pošta: {percent} %", + "chip_paused": "Export pozastavený do {time}", + "done_title": "Export dokončený", + "cancelled_title": "Export zrušený", + "failed_title": "Export zastavený", + "summary_exported": "Exportované správy: {count}", + "summary_file": "Uložené ako {name} ({size}).", + "summary_failed": "Nedá sa exportovať: {count}", + "download_again": "Stiahnuť znova", + "too_large": "Export je väčší, než tento prehliadač udrží v pamäti. Použite Chrome alebo Edge, alebo exportujte priečinok po priečinku.", + "close": "Zavrieť", + "untitled": "(bez predmetu)" + }, "recover_deleted": { "tab": "Obnoviť odstránené položky", "title": "Obnoviť odstránené položky", diff --git a/locales/th/common.json b/locales/th/common.json index 415c0b198..78b393c56 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -452,6 +452,46 @@ "pst_error_unreadable": "Bulwark อ่านไฟล์ Outlook นี้ไม่ได้", "pst_error_worker": "ตัวอ่านไฟล์ Outlook โหลดไม่สำเร็จ โหลดหน้าใหม่แล้วลองอีกครั้ง" }, + "mail_export": { + "menu_export": "ส่งออกโฟลเดอร์…", + "settings_label": "ส่งออกอีเมลทั้งหมด", + "settings_description": "บันทึกทุกโฟลเดอร์ของบัญชีนี้ลงในไฟล์ ZIP เดียว เป็นไฟล์ mbox หรือ .eml", + "settings_button": "ส่งออก…", + "title_folder": "ส่งออก “{name}”", + "title_account": "ส่งออกอีเมลทั้งหมด", + "description_folder": "บันทึกข้อความในโฟลเดอร์นี้เป็นไฟล์", + "description_account": "บันทึกทุกโฟลเดอร์ของบัญชีนี้ลงในไฟล์ ZIP เดียว", + "format_label": "รูปแบบ", + "format_mbox_folder": "ไฟล์ mbox", + "format_eml_folder": "ZIP ของไฟล์ .eml", + "format_mbox_account": "ZIP ที่มีไฟล์ mbox หนึ่งไฟล์ต่อโฟลเดอร์", + "format_eml_account": "ZIP ของไฟล์ .eml หนึ่งโฟลเดอร์ต่อโฟลเดอร์อีเมล", + "hint_mbox": "Thunderbird, Apple Mail และโปรแกรมอีเมลส่วนใหญ่เปิดไฟล์ mbox ได้", + "hint_eml": "หนึ่งไฟล์ต่อหนึ่งข้อความ ซึ่งโปรแกรมอีเมลใดก็เปิดได้", + "save_picker": "คุณเลือกเองว่าจะบันทึกไฟล์ไว้ที่ใด", + "save_download": "ไฟล์จะถูกดาวน์โหลดเมื่อส่งออกเสร็จ", + "save_memory": "เบราว์เซอร์นี้เก็บการส่งออกทั้งหมดไว้ในหน่วยความจำ จึงบันทึกได้สูงสุด 200 MB หากมากกว่านั้นให้ใช้ Chrome หรือ Edge หรือส่งออกทีละโฟลเดอร์", + "start": "ส่งออก", + "cancel": "ยกเลิก", + "running_title": "กำลังส่งออกอีเมล", + "progress_label": "ความคืบหน้าการส่งออก", + "status_exported": "ข้อความที่ส่งออกแล้ว: {count} จาก {total}", + "paused": "หยุดพักถึง {time}: เซิร์ฟเวอร์ขอให้พัก การส่งออกจะทำต่อเอง", + "background": "ทำงานเบื้องหลัง", + "cancel_export": "ยกเลิกการส่งออก", + "chip": "กำลังส่งออกอีเมล: {percent}%", + "chip_paused": "การส่งออกหยุดพักถึง {time}", + "done_title": "ส่งออกเสร็จแล้ว", + "cancelled_title": "ยกเลิกการส่งออกแล้ว", + "failed_title": "การส่งออกหยุดลง", + "summary_exported": "ข้อความที่ส่งออกแล้ว: {count}", + "summary_file": "บันทึกเป็น {name} ({size}) แล้ว", + "summary_failed": "ส่งออกไม่ได้: {count}", + "download_again": "ดาวน์โหลดอีกครั้ง", + "too_large": "การส่งออกใหญ่เกินกว่าที่เบราว์เซอร์นี้เก็บไว้ในหน่วยความจำได้ ให้ใช้ Chrome หรือ Edge หรือส่งออกทีละโฟลเดอร์", + "close": "ปิด", + "untitled": "(ไม่มีหัวเรื่อง)" + }, "recover_deleted": { "tab": "กู้คืนรายการที่ลบ", "title": "กู้คืนรายการที่ลบ", diff --git a/locales/tr/common.json b/locales/tr/common.json index 246755415..4fccdc43a 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "Bulwark bu Outlook dosyasını okuyamadı.", "pst_error_worker": "Outlook dosya okuyucusu yüklenmedi. Sayfayı yeniden yükleyip tekrar deneyin." }, + "mail_export": { + "menu_export": "Klasörü dışa aktar…", + "settings_label": "Tüm e-postaları dışa aktar", + "settings_description": "Bu hesabın tüm klasörlerini mbox veya .eml dosyaları olarak tek bir ZIP dosyasına kaydedin.", + "settings_button": "Dışa aktar…", + "title_folder": "“{name}” klasörünü dışa aktar", + "title_account": "Tüm e-postaları dışa aktar", + "description_folder": "Bu klasördeki iletileri bir dosyaya kaydedin.", + "description_account": "Bu hesabın tüm klasörlerini tek bir ZIP dosyasına kaydedin.", + "format_label": "Biçim", + "format_mbox_folder": "mbox dosyası", + "format_eml_folder": ".eml dosyalarından ZIP", + "format_mbox_account": "Her klasör için bir mbox dosyalı ZIP", + "format_eml_account": "Her e-posta klasörü için bir klasörle .eml dosyalarından ZIP", + "hint_mbox": "Thunderbird, Apple Mail ve çoğu e-posta programı mbox dosyalarını açar.", + "hint_eml": "Her ileti için bir dosya; her e-posta programı açabilir.", + "save_picker": "Dosyanın nereye kaydedileceğini siz seçersiniz.", + "save_download": "Dışa aktarım bitince dosya indirilir.", + "save_memory": "Bu tarayıcı tüm dışa aktarımı bellekte tutar, bu yüzden en fazla 200 MB kaydedebilir. Daha fazlası için Chrome veya Edge kullanın ya da klasör klasör dışa aktarın.", + "start": "Dışa aktar", + "cancel": "İptal", + "running_title": "E-posta dışa aktarılıyor", + "progress_label": "Dışa aktarma ilerlemesi", + "status_exported": "Dışa aktarılan iletiler: {count} / {total}", + "paused": "{time} saatine kadar duraklatıldı: sunucu ara istedi. Dışa aktarım kendiliğinden devam eder.", + "background": "Arka planda çalıştır", + "cancel_export": "Dışa aktarımı iptal et", + "chip": "E-posta dışa aktarılıyor: %{percent}", + "chip_paused": "Dışa aktarım {time} saatine kadar duraklatıldı", + "done_title": "Dışa aktarım tamamlandı", + "cancelled_title": "Dışa aktarım iptal edildi", + "failed_title": "Dışa aktarım durdu", + "summary_exported": "Dışa aktarılan iletiler: {count}", + "summary_file": "{name} olarak kaydedildi ({size}).", + "summary_failed": "Dışa aktarılamadı: {count}", + "download_again": "Yeniden indir", + "too_large": "Dışa aktarım, bu tarayıcının bellekte tutabileceğinden büyük. Chrome veya Edge kullanın ya da klasör klasör dışa aktarın.", + "close": "Kapat", + "untitled": "(konu yok)" + }, "recover_deleted": { "tab": "Silinen öğeleri kurtar", "title": "Silinen öğeleri kurtar", diff --git a/locales/uk/common.json b/locales/uk/common.json index f3a34b421..001447b72 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark не зміг прочитати цей файл Outlook.", "pst_error_worker": "Модуль читання файлів Outlook не завантажився. Перезавантажте сторінку й спробуйте ще раз." }, + "mail_export": { + "menu_export": "Експортувати теку…", + "settings_label": "Експортувати всю пошту", + "settings_description": "Зберегти всі теки цього облікового запису в один ZIP-файл як файли mbox або .eml.", + "settings_button": "Експортувати…", + "title_folder": "Експорт «{name}»", + "title_account": "Експорт усієї пошти", + "description_folder": "Зберегти повідомлення цієї теки у файл.", + "description_account": "Зберегти всі теки цього облікового запису в один ZIP-файл.", + "format_label": "Формат", + "format_mbox_folder": "Файл mbox", + "format_eml_folder": "ZIP із файлами .eml", + "format_mbox_account": "ZIP з одним файлом mbox на теку", + "format_eml_account": "ZIP із файлами .eml, по теці на кожну поштову теку", + "hint_mbox": "Thunderbird, Apple Mail і більшість поштових програм відкривають файли mbox.", + "hint_eml": "По файлу на повідомлення, їх відкриє будь-яка поштова програма.", + "save_picker": "Ви самі вибираєте, куди зберегти файл.", + "save_download": "Файл завантажиться, коли експорт завершиться.", + "save_memory": "Цей браузер тримає весь експорт у пам'яті, тож збереже не більше 200 МБ. Для більшого використовуйте Chrome або Edge чи експортуйте по одній теці.", + "start": "Експортувати", + "cancel": "Скасувати", + "running_title": "Експорт пошти", + "progress_label": "Перебіг експорту", + "status_exported": "Експортовано повідомлень: {count} з {total}", + "paused": "Пауза до {time}: сервер попросив перерву. Експорт продовжиться сам.", + "background": "Продовжити у фоні", + "cancel_export": "Скасувати експорт", + "chip": "Експорт пошти: {percent} %", + "chip_paused": "Експорт призупинено до {time}", + "done_title": "Експорт завершено", + "cancelled_title": "Експорт скасовано", + "failed_title": "Експорт зупинено", + "summary_exported": "Експортовано повідомлень: {count}", + "summary_file": "Збережено як {name} ({size}).", + "summary_failed": "Не вдалося експортувати: {count}", + "download_again": "Завантажити знову", + "too_large": "Експорт більший, ніж цей браузер може тримати в пам'яті. Використовуйте Chrome або Edge чи експортуйте по одній теці.", + "close": "Закрити", + "untitled": "(без теми)" + }, "recover_deleted": { "tab": "Відновити видалене", "title": "Відновити видалене", diff --git a/locales/vi/common.json b/locales/vi/common.json index c4249585d..15849883d 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -5447,6 +5447,46 @@ "pst_error_unreadable": "Bulwark không đọc được tệp Outlook này.", "pst_error_worker": "Không tải được trình đọc tệp Outlook. Hãy tải lại trang rồi thử lại." }, + "mail_export": { + "menu_export": "Xuất thư mục…", + "settings_label": "Xuất toàn bộ thư", + "settings_description": "Lưu mọi thư mục của tài khoản này vào một tệp ZIP, dưới dạng tệp mbox hoặc .eml.", + "settings_button": "Xuất…", + "title_folder": "Xuất “{name}”", + "title_account": "Xuất toàn bộ thư", + "description_folder": "Lưu các thư trong thư mục này thành một tệp.", + "description_account": "Lưu mọi thư mục của tài khoản này vào một tệp ZIP.", + "format_label": "Định dạng", + "format_mbox_folder": "Tệp mbox", + "format_eml_folder": "ZIP gồm các tệp .eml", + "format_mbox_account": "ZIP với một tệp mbox cho mỗi thư mục", + "format_eml_account": "ZIP gồm các tệp .eml, mỗi thư mục thư một thư mục", + "hint_mbox": "Thunderbird, Apple Mail và hầu hết các chương trình thư đều mở được tệp mbox.", + "hint_eml": "Mỗi thư một tệp, chương trình thư nào cũng mở được.", + "save_picker": "Bạn chọn nơi lưu tệp.", + "save_download": "Tệp sẽ được tải xuống khi xuất xong.", + "save_memory": "Trình duyệt này giữ toàn bộ bản xuất trong bộ nhớ nên chỉ lưu được tối đa 200 MB. Với dung lượng lớn hơn, hãy dùng Chrome hoặc Edge, hoặc xuất từng thư mục.", + "start": "Xuất", + "cancel": "Hủy", + "running_title": "Đang xuất thư", + "progress_label": "Tiến độ xuất", + "status_exported": "Thư đã xuất: {count} / {total}", + "paused": "Tạm dừng đến {time}: máy chủ yêu cầu nghỉ. Việc xuất sẽ tự tiếp tục.", + "background": "Chạy trong nền", + "cancel_export": "Hủy xuất", + "chip": "Đang xuất thư: {percent}%", + "chip_paused": "Việc xuất tạm dừng đến {time}", + "done_title": "Đã xuất xong", + "cancelled_title": "Đã hủy xuất", + "failed_title": "Việc xuất đã dừng", + "summary_exported": "Thư đã xuất: {count}", + "summary_file": "Đã lưu thành {name} ({size}).", + "summary_failed": "Không xuất được: {count}", + "download_again": "Tải xuống lại", + "too_large": "Bản xuất lớn hơn mức trình duyệt này giữ được trong bộ nhớ. Hãy dùng Chrome hoặc Edge, hoặc xuất từng thư mục.", + "close": "Đóng", + "untitled": "(không có tiêu đề)" + }, "recover_deleted": { "tab": "Khôi phục các mục đã xóa", "title": "Khôi phục các mục đã xóa", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 113ad5ee4..00899c022 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -5435,6 +5435,46 @@ "pst_error_unreadable": "Bulwark 無法讀取此 Outlook 檔案。", "pst_error_worker": "Outlook 檔案讀取元件未能載入。請重新載入頁面後再試。" }, + "mail_export": { + "menu_export": "匯出資料夾…", + "settings_label": "匯出所有郵件", + "settings_description": "把此帳戶的所有資料夾以 mbox 或 .eml 檔案儲存到一個 ZIP 檔案中。", + "settings_button": "匯出…", + "title_folder": "匯出「{name}」", + "title_account": "匯出所有郵件", + "description_folder": "把此資料夾中的郵件儲存為檔案。", + "description_account": "把此帳戶的所有資料夾儲存到一個 ZIP 檔案中。", + "format_label": "格式", + "format_mbox_folder": "mbox 檔案", + "format_eml_folder": ".eml 檔案的 ZIP", + "format_mbox_account": "每個資料夾一個 mbox 檔案的 ZIP", + "format_eml_account": ".eml 檔案的 ZIP,每個郵件資料夾一個資料夾", + "hint_mbox": "Thunderbird、Apple Mail 和大多數郵件程式都能開啟 mbox 檔案。", + "hint_eml": "每封郵件一個檔案,任何郵件程式都能開啟。", + "save_picker": "由您選擇檔案儲存位置。", + "save_download": "匯出完成後會下載檔案。", + "save_memory": "此瀏覽器會把整個匯出保存在記憶體中,因此最多只能儲存 200 MB。更大的匯出請使用 Chrome 或 Edge,或逐一匯出資料夾。", + "start": "匯出", + "cancel": "取消", + "running_title": "正在匯出郵件", + "progress_label": "匯出進度", + "status_exported": "已匯出郵件:{count} / {total}", + "paused": "已暫停至 {time}:伺服器要求暫停。匯出會自動繼續。", + "background": "在背景執行", + "cancel_export": "取消匯出", + "chip": "正在匯出郵件:{percent}%", + "chip_paused": "匯出已暫停至 {time}", + "done_title": "匯出完成", + "cancelled_title": "匯出已取消", + "failed_title": "匯出已停止", + "summary_exported": "已匯出郵件:{count}", + "summary_file": "已儲存為 {name}({size})。", + "summary_failed": "無法匯出:{count}", + "download_again": "重新下載", + "too_large": "匯出內容超過此瀏覽器能放在記憶體中的大小。請使用 Chrome 或 Edge,或逐一匯出資料夾。", + "close": "關閉", + "untitled": "(無主旨)" + }, "recover_deleted": { "tab": "復原已刪除的項目", "title": "復原已刪除的項目", diff --git a/locales/zh/common.json b/locales/zh/common.json index 5f2d5cc1e..e2a334b5d 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -5305,6 +5305,46 @@ "pst_error_unreadable": "Bulwark 无法读取此 Outlook 文件。", "pst_error_worker": "Outlook 文件读取组件未能加载。请重新加载页面后再试。" }, + "mail_export": { + "menu_export": "导出文件夹…", + "settings_label": "导出所有邮件", + "settings_description": "把此账户的所有文件夹以 mbox 或 .eml 文件保存到一个 ZIP 文件中。", + "settings_button": "导出…", + "title_folder": "导出“{name}”", + "title_account": "导出所有邮件", + "description_folder": "把此文件夹中的邮件保存为文件。", + "description_account": "把此账户的所有文件夹保存到一个 ZIP 文件中。", + "format_label": "格式", + "format_mbox_folder": "mbox 文件", + "format_eml_folder": ".eml 文件的 ZIP", + "format_mbox_account": "每个文件夹一个 mbox 文件的 ZIP", + "format_eml_account": ".eml 文件的 ZIP,每个邮件文件夹一个文件夹", + "hint_mbox": "Thunderbird、Apple Mail 和大多数邮件程序都能打开 mbox 文件。", + "hint_eml": "每封邮件一个文件,任何邮件程序都能打开。", + "save_picker": "由您选择文件保存位置。", + "save_download": "导出完成后会下载文件。", + "save_memory": "此浏览器会把整个导出保存在内存中,因此最多只能保存 200 MB。更大的导出请使用 Chrome 或 Edge,或逐个文件夹导出。", + "start": "导出", + "cancel": "取消", + "running_title": "正在导出邮件", + "progress_label": "导出进度", + "status_exported": "已导出邮件:{count} / {total}", + "paused": "已暂停至 {time}:服务器要求暂停。导出会自动继续。", + "background": "在后台运行", + "cancel_export": "取消导出", + "chip": "正在导出邮件:{percent}%", + "chip_paused": "导出已暂停至 {time}", + "done_title": "导出完成", + "cancelled_title": "导出已取消", + "failed_title": "导出已停止", + "summary_exported": "已导出邮件:{count}", + "summary_file": "已保存为 {name}({size})。", + "summary_failed": "无法导出:{count}", + "download_again": "重新下载", + "too_large": "导出内容超过了此浏览器能放在内存中的大小。请使用 Chrome 或 Edge,或逐个文件夹导出。", + "close": "关闭", + "untitled": "(无主题)" + }, "recover_deleted": { "tab": "恢复已删除项目", "title": "恢复已删除项目", diff --git a/stores/mail-transfer-store.ts b/stores/mail-transfer-store.ts index dc840da50..8514b2485 100644 --- a/stores/mail-transfer-store.ts +++ b/stores/mail-transfer-store.ts @@ -1,8 +1,9 @@ import { create } from "zustand"; import type { ImportProgress } from "@/lib/mail-import/engine"; +import type { ExportProgress } from "@/lib/mail-export/engine"; /** - * The mail import (and export) dialog: what it was opened for, whether it is + * The mail import and export dialog: what it was opened for, whether it is * on screen, and the job it runs. A job keeps running while the dialog is * hidden ("Run in background"); a small chip then shows its progress and * brings the dialog back. One job at a time. @@ -14,14 +15,18 @@ export interface ImportRequest { mailboxId?: string; } -export type TransferRequest = ImportRequest; - -export interface TransferJob { - kind: "import"; - progress: ImportProgress; - controller: AbortController; +export interface ExportRequest { + kind: "export"; + /** A folder (its id in the mail store), or the whole account when absent. */ + mailboxId?: string; } +export type TransferRequest = ImportRequest | ExportRequest; + +export type TransferJob = + | { kind: "import"; progress: ImportProgress; controller: AbortController } + | { kind: "export"; progress: ExportProgress; controller: AbortController }; + interface MailTransferState { request: TransferRequest | null; visible: boolean; @@ -31,10 +36,10 @@ interface MailTransferState { /** Hides the dialog; a running job goes on. */ hide: () => void; show: () => void; - /** Closes the dialog and forgets a finished job. */ + /** Closes the dialog and forgets a finished job (an export's private file is deleted). */ close: () => void; startJob: (job: TransferJob) => void; - updateProgress: (progress: ImportProgress) => void; + updateProgress: (progress: ImportProgress | ExportProgress) => void; cancelJob: () => void; } @@ -42,6 +47,10 @@ export function isJobActive(job: TransferJob | null): boolean { return !!job && (job.progress.phase === "running" || job.progress.phase === "paused"); } +function discardExport(job: TransferJob | null): void { + if (job?.kind === "export") void job.progress.file?.discard().catch(() => {}); +} + export const useMailTransferStore = create()((set, get) => ({ request: null, visible: false, @@ -51,6 +60,7 @@ export const useMailTransferStore = create()((set, get) => ({ set({ visible: true }); return false; } + discardExport(get().job); set({ request, visible: true, job: null }); return true; }, @@ -61,12 +71,13 @@ export const useMailTransferStore = create()((set, get) => ({ set({ visible: false }); return; } + discardExport(get().job); set({ request: null, visible: false, job: null }); }, startJob: (job) => set({ job }), updateProgress: (progress) => { const job = get().job; - if (job) set({ job: { ...job, progress } }); + if (job) set({ job: { ...job, progress } as TransferJob }); }, cancelJob: () => get().job?.controller.abort(), })); @@ -75,3 +86,8 @@ export const useMailTransferStore = create()((set, get) => ({ export function openMailImport(request: Omit = {}): void { useMailTransferStore.getState().open({ kind: "import", ...request }); } + +/** Opens the export dialog for a folder, or for the whole account. */ +export function openMailExport(request: Omit = {}): void { + useMailTransferStore.getState().open({ kind: "export", ...request }); +} From dd1e0e0b1346a1bae5a0bf24309ce4827fe15168 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:34:26 +0100 Subject: [PATCH 05/22] feat: keep read and flagged state from Thunderbird .eml files on import --- lib/mail-import/__tests__/engine.test.ts | 8 ++++++++ lib/mail-import/sources.ts | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/mail-import/__tests__/engine.test.ts b/lib/mail-import/__tests__/engine.test.ts index 302ea6738..4fb5d6a74 100644 --- a/lib/mail-import/__tests__/engine.test.ts +++ b/lib/mail-import/__tests__/engine.test.ts @@ -115,6 +115,14 @@ describe('runImport', () => { expect(fake.imported[0].keywords).toEqual({ $seen: true }); }); + it('reads Thunderbird state headers in .eml files, and imports a plain .eml as read like before', async () => { + const fake = fakeClient(); + const unread = new File(['X-Mozilla-Status: 0004\r\nSubject: u\r\n\r\nStatus: R in the body\r\n'], 'u.eml'); + const plain = new File(['Subject: p\r\n\r\nbody\r\n'], 'p.eml'); + await runImport(options(fake.client, [unread, plain])); + expect(fake.imported.map((e) => e.keywords)).toEqual([{ $flagged: true }, { $seen: true }]); + }); + it('falls back to the Date header when the envelope line has no usable date', async () => { const fake = fakeClient(); await runImport(options(fake.client, [file('From MAILER-DAEMON Thu Jan 1 00:00:00 1970\nSubject: s\nDate: Tue, 5 May 2020 10:00:00 +0000\n\nb\n')])); diff --git a/lib/mail-import/sources.ts b/lib/mail-import/sources.ts index 48c9c6ac6..0d44bae7f 100644 --- a/lib/mail-import/sources.ts +++ b/lib/mail-import/sources.ts @@ -129,14 +129,15 @@ export async function* emlSource(file: File, options: SourceOptions): ItemSource options.onProgress?.(1); return; } - const head = await file.slice(0, 64 * 1024).text(); + const head = new TextDecoder("latin1").decode(await file.slice(0, 64 * 1024).arrayBuffer()); yield { kind: "message", index: 0, folder: [], data: file.slice(0, file.size, MIME), size: file.size, - keywords: { $seen: true }, + // Thunderbird writes X-Mozilla-Status into the .eml files it saves; none means read, as before. + keywords: mboxState(head.split(/\r?\n\r?\n/, 1)[0]).keywords, receivedAt: dateFromHeaderBlock(head), label: emlLabel(file.name), }; @@ -257,7 +258,7 @@ export async function* zipSource(file: File, options: SourceOptions): ItemSource folder: zipFolder(entry.name, false), data: new Blob([bytes as BlobPart], { type: MIME }), size: bytes.length, - keywords: { $seen: true }, + keywords: mboxState(head.split(/\r?\n\r?\n/, 1)[0]).keywords, receivedAt: dateFromHeaderBlock(head), label: emlLabel(entry.name), }; From 58f66c4fdcf5f950fb5c758befa8fe8da031c709 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:42:00 +0100 Subject: [PATCH 06/22] fix: list the folders in the import dialog when it opens from Settings --- .../__tests__/mail-import-dialog.test.tsx | 11 +++++++++++ components/mail-transfer/mail-import-dialog.tsx | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/components/mail-transfer/__tests__/mail-import-dialog.test.tsx b/components/mail-transfer/__tests__/mail-import-dialog.test.tsx index c15a88713..f191fd56e 100644 --- a/components/mail-transfer/__tests__/mail-import-dialog.test.tsx +++ b/components/mail-transfer/__tests__/mail-import-dialog.test.tsx @@ -71,6 +71,17 @@ describe('MailImportDialog', () => { expect(startImport).toHaveBeenCalledWith(expect.objectContaining({ accountId: 'acc1', targetMailboxId: null, keepFolders: true })); }); + it('loads the folders when opened from Settings before the mail view did, then picks the Inbox', async () => { + const fetchMailboxes = vi.fn(async () => {}); + const loaded = useEmailStore.getState().mailboxes; + useEmailStore.setState({ mailboxes: [], fetchMailboxes } as never); + render(); + act(() => openMailImport()); + expect(fetchMailboxes).toHaveBeenCalled(); + act(() => useEmailStore.setState({ mailboxes: loaded } as never)); + await waitFor(() => expect((screen.getByTestId('mail-import-target') as HTMLSelectElement).value).toBe('inbox')); + }); + it('cannot start without files', () => { render(); act(() => openMailImport()); diff --git a/components/mail-transfer/mail-import-dialog.tsx b/components/mail-transfer/mail-import-dialog.tsx index 026fe1d00..37da46feb 100644 --- a/components/mail-transfer/mail-import-dialog.tsx +++ b/components/mail-transfer/mail-import-dialog.tsx @@ -72,6 +72,17 @@ export function MailImportDialog({ request }: { request: ImportRequest }) { const targetId = target === TOP ? null : targetMailbox ? targetMailbox.originalId || targetMailbox.id : null; const effectiveKeep = anyFolders && keepFolders; + // Opened from Settings, the mail view may not have loaded the folders yet. + useEffect(() => { + if (client && mailboxes.length === 0) void useEmailStore.getState().fetchMailboxes(client).catch(() => {}); + }, [client, mailboxes.length]); + + // Keep the choice valid when the folders arrive or the files change. + useEffect(() => { + const valid = target === TOP ? anyFolders : targets.some((x) => x.mailbox.id === target); + if (!valid && target !== initialTarget) setTarget(initialTarget); + }, [targets, target, anyFolders, initialTarget]); + // An earlier import of the same file into the same place that stopped part way. useEffect(() => { let current = true; From d8f6be7b1e367644452da2e885b587180b9cd08d Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:42:00 +0100 Subject: [PATCH 07/22] fix: delete a finished export's file from the browser when its account signs out --- .../__tests__/mail-export-dialog.test.tsx | 21 +++++++++++++++++++ .../mail-transfer/mail-transfer-host.tsx | 7 +++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/components/mail-transfer/__tests__/mail-export-dialog.test.tsx b/components/mail-transfer/__tests__/mail-export-dialog.test.tsx index ec0175d28..2b0c24149 100644 --- a/components/mail-transfer/__tests__/mail-export-dialog.test.tsx +++ b/components/mail-transfer/__tests__/mail-export-dialog.test.tsx @@ -83,6 +83,27 @@ describe('MailExportDialog', () => { expect(useMailTransferStore.getState().request).toBeNull(); }); + it('signing out stops a running export, or frees a finished one\'s file', async () => { + const { runBeforeSignOut } = await import('@/lib/sign-out-cleanup'); + const controller = new AbortController(); + render(); + act(() => { + openMailExport({ mailboxId: 'inbox' }); + useMailTransferStore.getState().startJob({ kind: 'export', controller, progress: { phase: 'running', exported: 1, failed: 0, failures: [], total: 4, fraction: 0.25 } }); + }); + await act(() => runBeforeSignOut('acc1')); + expect(controller.signal.aborted).toBe(true); + + const discard = vi.fn(async () => {}); + act(() => { + useMailTransferStore.setState({ request: null, visible: false, job: null }); + openMailExport({ mailboxId: 'inbox' }); + useMailTransferStore.getState().startJob({ kind: 'export', controller: new AbortController(), progress: { phase: 'done', exported: 3, failed: 0, failures: [], total: 3, fraction: 1, file: { name: 'a.mbox', size: 1, discard } } }); + }); + await act(() => runBeforeSignOut('acc1')); + expect(discard).toHaveBeenCalled(); + }); + it('runs on in the background and the chip names the export', () => { const controller = new AbortController(); render(); diff --git a/components/mail-transfer/mail-transfer-host.tsx b/components/mail-transfer/mail-transfer-host.tsx index ee8e85174..a69c920e5 100644 --- a/components/mail-transfer/mail-transfer-host.tsx +++ b/components/mail-transfer/mail-transfer-host.tsx @@ -20,9 +20,12 @@ export function MailTransferHost() { const visible = useMailTransferStore((s) => s.visible); const job = useMailTransferStore((s) => s.job); - // An account that signs out takes its running job and its checkpoints with it. + // An account that signs out takes its running job, a finished export's + // file in the browser's private storage, and its checkpoints with it. useEffect(() => onBeforeSignOut(async (accountId) => { - useMailTransferStore.getState().cancelJob(); + const store = useMailTransferStore.getState(); + if (isJobActive(store.job)) store.cancelJob(); + else store.close(); if (accountId !== "all") await clearCheckpointsFor(accountId); }), []); 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 08/22] 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); } From c74426ffca7ea2d76c2713dbba9cdfba40b4f074 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:45:21 +0100 Subject: [PATCH 09/22] test: cover mbox and PST import and folder export against the test servers --- .../tests/22-ent-import-export.spec.ts | 351 ++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 integration/tests/22-ent-import-export.spec.ts diff --git a/integration/tests/22-ent-import-export.spec.ts b/integration/tests/22-ent-import-export.spec.ts new file mode 100644 index 000000000..59a4bd162 --- /dev/null +++ b/integration/tests/22-ent-import-export.spec.ts @@ -0,0 +1,351 @@ +/** + * Package C: mail import (mbox, PST) and export (folder as mbox or ZIP), + * through the real UI, as ent-carol, against a VM 106 Stalwart. + * + * Run with enterprise-rig/run-spec.sh. Each run works in its own folders + * (C--...) and deletes them with their mail at the end; the upload + * quota (1000 messages or 50 MB an hour per user) easily holds a run. + */ +import { test, expect, type Page, type Download } from '@playwright/test'; +import { mkdtempSync, readFileSync, utimesSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { ENT, ENT_PASSWORD, jmapAs, requireEntEnv, shot, userPage } from './helpers/ent'; +import { JMAP_URL } from './helpers/config'; +import { folderRow } from './helpers/app'; + +requireEntEnv(); + +const USER = ENT.carol; +const STAMP = `C-${Date.now().toString(36)}`; +const PST = readFileSync(path.join(__dirname, '..', '..', 'lib', 'mail-import', '__tests__', 'fixtures', 'Outlook.pst')); +const created: string[] = []; + +const MBOX = [ + 'From alice@example.org Tue May 5 10:11:12 2020', + 'Status: RO', + 'From: Alice ', + `Subject: ${STAMP} mbox read`, + 'Date: Tue, 5 May 2020 10:11:00 +0000', + '', + '>From the start of a line', + 'body one', + '', + 'From bob@example.org Wed May 6 11:00:00 2020', + 'Status: O', + 'X-Status: F', + 'From: Bob ', + `Subject: ${STAMP} mbox flagged`, + 'Date: Wed, 6 May 2020 10:59:00 +0000', + '', + 'body two', + '', + 'From carol@example.org Thu May 7 12:00:00 2020', + 'X-Mozilla-Status: 0009', + `Subject: ${STAMP} mbox deleted`, + '', + 'deleted in Thunderbird', + '', + 'From dave@example.org Fri May 8 13:30:00 2020', + `Subject: ${STAMP} mbox plain`, + '', + 'no state header', + '', +].join('\n'); + +const auth = () => 'Basic ' + Buffer.from(`${USER}:${ENT_PASSWORD}`).toString('base64'); + +async function createFolder(name: string, parentId: string | null = null): Promise { + const client = await jmapAs(USER); + const id = await client.createMailbox(name, parentId); + created.push(id); + return id; +} + +interface Stored { id: string; subject: string; receivedAt: string; keywords: Record; blobId: string; hasAttachment: boolean; mailboxIds: Record } + +async function messagesIn(mailboxId: string): Promise { + const client = await jmapAs(USER); + const res = await client.request([ + ['Email/query', { accountId: client.accountId, filter: { inMailbox: mailboxId }, sort: [{ property: 'receivedAt', isAscending: true }], limit: 500 }, '0'], + ['Email/get', { accountId: client.accountId, '#ids': { resultOf: '0', name: 'Email/query', path: '/ids' }, properties: ['subject', 'receivedAt', 'keywords', 'blobId', 'hasAttachment', 'mailboxIds'] }, '1'], + ]); + return res.methodResponses[1][1].list as Stored[]; +} + +async function children(parentId: string): Promise> { + const client = await jmapAs(USER); + const all = await client.mailboxes(); + return Object.fromEntries(all.filter((m) => m.parentId === parentId).map((m) => [m.name, m.id])); +} + +/** Seeds a message with Email/import, as another client would have stored it. */ +async function seed(mailboxId: string, subject: string, receivedAt: string, keywords: Record): Promise { + const client = await jmapAs(USER); + const raw = `From: Seed \r\nTo: ${USER}\r\nSubject: ${subject}\r\nDate: Mon, 1 Jan 2018 08:00:00 +0000\r\nMessage-ID: <${subject.replace(/\W/g, '')}@example.org>\r\n\r\nFrom the seed: ${subject}\r\n`; + const up = await fetch(`${JMAP_URL}/jmap/upload/${client.accountId}/`, { method: 'POST', headers: { Authorization: auth(), 'Content-Type': 'message/rfc822' }, body: raw }); + expect(up.ok).toBe(true); + const { blobId } = await up.json(); + const res = await client.request([['Email/import', { accountId: client.accountId, emails: { e: { blobId, mailboxIds: { [mailboxId]: true }, keywords, receivedAt } } }, '0']]); + expect(res.methodResponses[0][1].created?.e?.id, JSON.stringify(res.methodResponses[0][1])).toBeTruthy(); +} + +async function openFolderMenu(page: Page, name: string): Promise { + const row = folderRow(page, { name }).first(); + await expect(row).toBeVisible({ timeout: 30_000 }); + await row.click({ button: 'right' }); +} + +async function importFromFolderMenu(page: Page, folder: string, file: { name: string; buffer: Buffer; mimeType?: string } | string, opts: { keepFolders?: boolean } = {}) { + await openFolderMenu(page, folder); + await page.getByRole('menuitem', { name: 'Import mail…' }).click(); + const dialog = page.getByTestId('mail-import-dialog'); + await expect(dialog).toBeVisible(); + // A path keeps the file's date, which the resume checkpoint is keyed on, like a file on the user's disk. + await page.getByTestId('mail-import-file').setInputFiles(typeof file === 'string' ? file : { name: file.name, mimeType: file.mimeType ?? 'application/octet-stream', buffer: file.buffer }); + await expect(dialog.getByText(typeof file === 'string' ? path.basename(file) : file.name)).toBeVisible(); + if (opts.keepFolders === false) await dialog.getByRole('checkbox', { name: /Recreate the folders/ }).uncheck(); + return dialog; +} + +/** Hides the save picker so the export goes through the private file and a download Playwright can read. */ +async function withoutSavePicker(page: Page): Promise { + await page.evaluate(() => { delete (window as { showSaveFilePicker?: unknown }).showSaveFilePicker; }); +} + +async function exportFolder(page: Page, folder: string, format: 'mbox' | 'eml'): Promise { + await withoutSavePicker(page); + await openFolderMenu(page, folder); + await page.getByTestId('mailbox-export').click(); + const dialog = page.getByTestId('mail-export-dialog'); + await expect(dialog).toBeVisible(); + await page.getByTestId(`mail-export-format-${format}`).check(); + const download = page.waitForEvent('download', { timeout: 60_000 }); + await page.getByTestId('mail-export-start').click(); + const file = await download; + await expect(dialog.getByRole('heading', { name: 'Export finished' })).toBeVisible({ timeout: 60_000 }); + return file; +} + +async function readDownload(download: Download): Promise { + return readFileSync(await download.path()); +} + +test.describe.configure({ mode: 'serial' }); + +test.afterAll(async () => { + const client = await jmapAs(USER); + // Children first: a folder with subfolders can't be destroyed. + const all = await client.mailboxes(); + const ours = all.filter((m) => m.name.startsWith(STAMP) || created.includes(m.id)); + const withDescendants = new Set(ours.map((m) => m.id)); + let grew = true; + while (grew) { + grew = false; + for (const m of all) if (m.parentId && withDescendants.has(m.parentId) && !withDescendants.has(m.id)) { withDescendants.add(m.id); grew = true; } + } + const depth = (id: string): number => { const m = all.find((x) => x.id === id); return m?.parentId ? 1 + depth(m.parentId) : 0; }; + const ids = [...withDescendants].sort((a, b) => depth(b) - depth(a)); + for (const id of ids) { + await client.request([['Mailbox/set', { accountId: client.accountId, destroy: [id], onDestroyRemoveEmails: true }, '0']]).catch(() => {}); + } +}); + +test('an mbox lands in the folder with its dates, read and flagged state, and the deleted message left out', async ({ browser }) => { + const folderName = `${STAMP} mbox`; + const folderId = await createFolder(folderName); + const page = await userPage(browser, USER); + const dialog = await importFromFolderMenu(page, folderName, { name: 'thunderbird.mbox', buffer: Buffer.from(MBOX) }); + await expect(dialog.getByText('mbox file')).toBeVisible(); + await shot(page, 'c-import-dialog'); + await page.getByTestId('mail-import-start').click(); + await expect(dialog.getByRole('heading', { name: 'Import finished' })).toBeVisible({ timeout: 60_000 }); + await expect(dialog.getByText('Messages imported: 3')).toBeVisible(); + await expect(dialog.getByText('Deleted in Thunderbird: 1')).toBeVisible(); + await shot(page, 'c-import-summary'); + + const stored = await messagesIn(folderId); + expect(stored.map((m) => m.subject)).toEqual([`${STAMP} mbox read`, `${STAMP} mbox flagged`, `${STAMP} mbox plain`]); + // The envelope line is the arrival time. + expect(stored.map((m) => m.receivedAt)).toEqual(['2020-05-05T10:11:12Z', '2020-05-06T11:00:00Z', '2020-05-08T13:30:00Z']); + expect(stored.map((m) => !!m.keywords.$seen)).toEqual([true, false, true]); + expect(stored.map((m) => !!m.keywords.$flagged)).toEqual([false, true, false]); + // The stored message starts with its own headers (no leftover "From " envelope line) and is unquoted. + const res = await fetch(`${JMAP_URL}/jmap/download/${(await jmapAs(USER)).accountId}/${stored[0].blobId}/m.eml?accept=message/rfc822`, { headers: { Authorization: auth() } }); + const raw = await res.text(); + expect(raw.startsWith('From: Alice')).toBe(true); + expect(raw).toContain('\nFrom the start of a line'); + expect(raw).not.toMatch(/^Status:/m); + await page.context().close(); +}); + +test('a PST lands in its own folders under the target with dates, read state and attachments', async ({ browser }) => { + const parentName = `${STAMP} pst`; + const parentId = await createFolder(parentName); + const page = await userPage(browser, USER); + const dialog = await importFromFolderMenu(page, parentName, { name: 'Outlook.pst', buffer: PST }); + await expect(dialog.getByText('Outlook data file')).toBeVisible(); + await expect(dialog.getByRole('checkbox', { name: /Recreate the folders/ })).toBeChecked(); + await page.getByTestId('mail-import-start').click(); + await expect(dialog.getByRole('heading', { name: 'Import finished' })).toBeVisible({ timeout: 90_000 }); + await expect(dialog.getByText('Messages imported: 14')).toBeVisible(); + await expect(dialog.getByText('Folders created: 2')).toBeVisible(); + await expect(dialog.getByText('Contacts: 1')).toBeVisible(); + await expect(dialog.getByText('Calendar items: 1')).toBeVisible(); + await expect(dialog.getByText('Messages attached to other messages: 4')).toBeVisible(); + + const folders = await children(parentId); + expect(Object.keys(folders).sort()).toEqual(['Inbox', 'Sent Items']); + const inbox = await messagesIn(folders.Inbox); + const sent = await messagesIn(folders['Sent Items']); + expect(inbox).toHaveLength(8); + expect(sent).toHaveLength(6); + expect(inbox.filter((m) => !m.keywords.$seen).map((m) => m.subject).sort()).toEqual(['message 1', 'message 2']); + expect(inbox.find((m) => m.subject === 'Multiple attachments')?.receivedAt).toBe('2011-06-21T23:51:07Z'); + expect(inbox.find((m) => m.subject === 'Multiple attachments')?.hasAttachment).toBe(true); + // A message from the Sent folder, rebuilt without original headers. + const client = await jmapAs(USER); + const detail = await client.request([['Email/get', { accountId: client.accountId, ids: [sent.find((m) => m.subject === 'HTML body')!.id], properties: ['from', 'to', 'sentAt', 'attachments', 'htmlBody'] }, '0']]); + const html = detail.methodResponses[0][1].list[0]; + expect(html.from[0].email).toBe('saqib.razzaq@xp.local'); + expect(html.attachments.length).toBeGreaterThanOrEqual(12); + await page.context().close(); +}); + +test('a folder exported as mbox from the UI imports back to the same messages, dates and read state', async ({ browser }) => { + const sourceName = `${STAMP} export`; + const sourceId = await createFolder(sourceName); + await seed(sourceId, `${STAMP} exp read`, '2019-03-01T08:00:00Z', { $seen: true }); + await seed(sourceId, `${STAMP} exp flagged`, '2019-03-02T09:30:00Z', { $flagged: true }); + await seed(sourceId, `${STAMP} exp answered`, '2019-03-03T10:45:00Z', { $seen: true, $answered: true }); + const backName = `${STAMP} reimport`; + const backId = await createFolder(backName); + const page = await userPage(browser, USER); + + const download = await exportFolder(page, sourceName, 'mbox'); + expect(download.suggestedFilename()).toBe(`${sourceName}.mbox`); + const mbox = await readDownload(download); + expect(mbox.toString('latin1')).toMatch(/^From MAILER-DAEMON Fri Mar 1 08:00:00 2019\r?\n/); + expect(mbox.toString('latin1')).toContain('>From the seed'); + await shot(page, 'c-export-done'); + await page.getByRole('button', { name: 'Close' }).last().click(); + + const dialog = await importFromFolderMenu(page, backName, { name: download.suggestedFilename(), buffer: mbox }); + await page.getByTestId('mail-import-start').click(); + await expect(dialog.getByRole('heading', { name: 'Import finished' })).toBeVisible({ timeout: 60_000 }); + + const original = await messagesIn(sourceId); + const back = await messagesIn(backId); + const view = (list: Stored[]) => list.map((m) => ({ subject: m.subject, receivedAt: m.receivedAt, seen: !!m.keywords.$seen, flagged: !!m.keywords.$flagged, answered: !!m.keywords.$answered })); + expect(back).toHaveLength(3); + expect(view(back)).toEqual(view(original)); + await page.context().close(); +}); + +test('a folder exported as a ZIP of .eml files imports back to the same messages', async ({ browser }) => { + const sourceName = `${STAMP} export`; + const client = await jmapAs(USER); + const sourceId = (await client.mailboxByName(sourceName))!.id; + const backName = `${STAMP} reimport zip`; + const backId = await createFolder(backName); + const page = await userPage(browser, USER); + const download = await exportFolder(page, sourceName, 'eml'); + expect(download.suggestedFilename()).toBe(`${sourceName}.zip`); + const zip = await readDownload(download); + expect(zip.subarray(0, 4).toString('latin1')).toBe('PK\u0003\u0004'); + await page.getByRole('button', { name: 'Close' }).last().click(); + + const dialog = await importFromFolderMenu(page, backName, { name: download.suggestedFilename(), buffer: zip }); + await expect(dialog.getByText('ZIP archive')).toBeVisible(); + await page.getByTestId('mail-import-start').click(); + await expect(dialog.getByRole('heading', { name: 'Import finished' })).toBeVisible({ timeout: 60_000 }); + const original = (await messagesIn(sourceId)).map((m) => m.subject).sort(); + const back = (await messagesIn(backId)).map((m) => m.subject).sort(); + expect(back).toEqual(original); + await page.context().close(); +}); + +test('the progress dialog cancels an import, keeps what came in, and continues later without duplicates', async ({ browser }) => { + const folderName = `${STAMP} cancel`; + const folderId = await createFolder(folderName); + const count = 40; + const big = Array.from({ length: count }, (_, i) => + `From x@example.org Mon Jun 1 10:${String(i).padStart(2, '0')}:00 2020\nSubject: ${STAMP} n${i}\n\nbody ${i}\n\n`).join(''); + const onDisk = path.join(mkdtempSync(path.join(tmpdir(), 'bulwark-c-')), 'many.mbox'); + writeFileSync(onDisk, big); + utimesSync(onDisk, new Date('2024-01-01T00:00:00Z'), new Date('2024-01-01T00:00:00Z')); + const page = await userPage(browser, USER); + // Slow uploads down so Cancel lands in the middle. + await page.route(/\/jmap\/upload\//, async (route) => { + await new Promise((r) => setTimeout(r, 500)); + await route.continue(); + }); + const dialog = await importFromFolderMenu(page, folderName, onDisk); + await page.getByTestId('mail-import-start').click(); + await expect(dialog.getByRole('heading', { name: 'Importing mail' })).toBeVisible(); + await expect(dialog.getByRole('progressbar', { name: 'Import progress' })).toBeVisible(); + await expect(dialog.getByTestId('mail-import-count')).toHaveText(/Messages imported: [1-9]/, { timeout: 30_000 }); + await shot(page, 'c-import-progress'); + await dialog.getByRole('button', { name: 'Cancel import' }).click(); + await expect(dialog.getByRole('heading', { name: 'Import cancelled' })).toBeVisible({ timeout: 30_000 }); + await expect(dialog.getByText('To go on later, import the same files into the same folder again.')).toBeVisible(); + const partial = (await messagesIn(folderId)).length; + expect(partial).toBeGreaterThan(0); + expect(partial).toBeLessThan(count); + await page.getByRole('button', { name: 'Close' }).last().click(); + await page.unroute(/\/jmap\/upload\//); + + const again = await importFromFolderMenu(page, folderName, onDisk); + const resume = again.getByRole('checkbox', { name: /Continue where the last import/ }); + await expect(resume).toBeChecked(); + await page.getByTestId('mail-import-start').click(); + await expect(again.getByRole('heading', { name: 'Import finished' })).toBeVisible({ timeout: 60_000 }); + const all = await messagesIn(folderId); + expect(all).toHaveLength(count); + expect(new Set(all.map((m) => m.subject)).size).toBe(count); + await page.context().close(); +}); + +async function openBackupSettings(page: Page): Promise { + await page.goto('/settings/backup'); + // On a phone a settings link opens the list of sections first. + const button = page.getByTestId('settings-mail-import'); + if (!(await button.isVisible().catch(() => false))) { + await page.getByText('Backup & reset', { exact: true }).first().click(); + } + await expect(button).toBeVisible(); +} + +test('import and export dialogs fill the screen at 390 px and import from Settings', async ({ browser }) => { + const targetName = `${STAMP} phone`; + const target = await createFolder(targetName); + const page = await userPage(browser, USER, true); + await openBackupSettings(page); + await page.getByTestId('settings-mail-import').click(); + const dialog = page.getByTestId('mail-import-dialog'); + await expect(dialog).toBeVisible(); + // Full screen once the opening animation is over. + await expect.poll(async () => (await dialog.boundingBox())?.width ?? 0).toBeGreaterThanOrEqual(389); + await page.getByTestId('mail-import-file').setInputFiles({ name: 'phone.mbox', mimeType: 'application/mbox', buffer: Buffer.from(MBOX.replaceAll(`${STAMP} mbox`, `${STAMP} phone`)) }); + // From Settings there is no folder to start from: pick one. + await page.getByTestId('mail-import-target').selectOption({ label: targetName }); + await page.waitForTimeout(300); + await shot(page, 'c-import-phone'); + await page.getByTestId('mail-import-start').click(); + await expect(dialog.getByRole('heading', { name: 'Import finished' })).toBeVisible({ timeout: 60_000 }); + await expect.poll(async () => (await dialog.boundingBox())?.width ?? 0).toBeGreaterThanOrEqual(389); + await shot(page, 'c-import-phone-summary'); + expect(await messagesIn(target)).toHaveLength(3); + await page.getByRole('button', { name: 'Close' }).last().click(); + + await page.getByTestId('settings-mail-export').click(); + const exportDialog = page.getByTestId('mail-export-dialog'); + await expect(exportDialog.getByRole('heading', { name: 'Export all mail' })).toBeVisible(); + await expect.poll(async () => (await exportDialog.boundingBox())?.width ?? 0).toBeGreaterThanOrEqual(389); + await shot(page, 'c-export-phone'); + // Escape closes it and the focus goes back to the button that opened it. + await page.keyboard.press('Escape'); + await expect(exportDialog).toBeHidden(); + await expect(page.getByTestId('settings-mail-export')).toBeFocused(); + await page.context().close(); +}); From 4676be28e5f17ad354ea9ee19922c502b09a6c12 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:47:58 +0100 Subject: [PATCH 10/22] test: let held uploads go quietly when the import is cancelled --- integration/tests/22-ent-import-export.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration/tests/22-ent-import-export.spec.ts b/integration/tests/22-ent-import-export.spec.ts index 59a4bd162..2068975dd 100644 --- a/integration/tests/22-ent-import-export.spec.ts +++ b/integration/tests/22-ent-import-export.spec.ts @@ -278,7 +278,8 @@ test('the progress dialog cancels an import, keeps what came in, and continues l // Slow uploads down so Cancel lands in the middle. await page.route(/\/jmap\/upload\//, async (route) => { await new Promise((r) => setTimeout(r, 500)); - await route.continue(); + // Cancel aborts uploads that are still held here. + await route.continue().catch(() => {}); }); const dialog = await importFromFolderMenu(page, folderName, onDisk); await page.getByTestId('mail-import-start').click(); From 75fbd18bb9f5e35f1cade8df7fe259b90a774c84 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:53:08 +0100 Subject: [PATCH 11/22] fix: space the icon on the import dialog's Choose files button --- components/mail-transfer/mail-import-dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/mail-transfer/mail-import-dialog.tsx b/components/mail-transfer/mail-import-dialog.tsx index 37da46feb..f6cdfd8dd 100644 --- a/components/mail-transfer/mail-import-dialog.tsx +++ b/components/mail-transfer/mail-import-dialog.tsx @@ -179,7 +179,7 @@ export function MailImportDialog({ request }: { request: ImportRequest }) { onChange={(e) => { void addFiles(e.target.files); e.target.value = ""; }} /> {picked.length > 0 && ( From 8d48f982619e514b19b2cba8b8f27a4bb6adef8a Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 06:37:08 +0100 Subject: [PATCH 12/22] fix: keep failed messages for the next import run and stop an import that can't go on A message that failed for a passing reason (a dropped connection, a server error, an expired token) used to count as done, so continuing the import skipped it for good, and a slow upload followed by a cancel imported the messages after it twice. - The checkpoint keeps every message that is done, also past a gap, and the next run of the same import tries only the rest again. - Ten failures in a row, a full mailbox (overQuota) or a refusal (forbidden) stop the import with a message instead of going through the whole file. - An upload without an answer pauses the import instead of failing the message: 30 seconds, then until the upload window ends. - Email/import, the export's Blob/get and folder listing pass the client's own 429 pause by, and a 429 on them doesn't pause the rest of the app. - Signing out stops the job of that account only, writes no checkpoint and imports nothing more afterwards; another account's sign-out leaves the job alone. - One tab imports a file at a time (Web Locks), and Import waits until the dialog knows whether an earlier run can be continued. The import dialog tests now wait for the Import button to be enabled before clicking it, since it stays off during that lookup. --- .../__tests__/mail-export-dialog.test.tsx | 14 ++ .../mail-import-dialog-resume.test.tsx | 31 +++ .../__tests__/mail-import-dialog.test.tsx | 4 + .../mail-transfer/mail-export-dialog.tsx | 2 +- .../mail-transfer/mail-import-dialog.tsx | 26 +- .../mail-transfer/mail-transfer-host.tsx | 15 +- components/mail-transfer/start-export.ts | 19 +- components/mail-transfer/start-import.ts | 21 +- lib/__tests__/jmap-mail-transfer.test.ts | 22 ++ lib/jmap/client-interface.ts | 2 + lib/jmap/client.ts | 22 +- lib/mail-export/engine.ts | 1 + lib/mail-import/__tests__/engine.test.ts | 110 ++++++++- lib/mail-import/checkpoints.ts | 4 +- lib/mail-import/engine.ts | 227 +++++++++++++----- lib/mail-import/locks.ts | 36 +++ lib/mail-import/pst-source.ts | 2 +- lib/mail-import/pst/items.ts | 4 +- lib/mail-import/pst/worker.ts | 6 +- lib/mail-import/sources.ts | 17 +- locales/ar/common.json | 8 +- locales/ca/common.json | 8 +- locales/cs/common.json | 8 +- locales/da/common.json | 8 +- locales/de/common.json | 8 +- locales/en/common.json | 8 +- locales/es/common.json | 8 +- locales/et/common.json | 8 +- locales/fa/common.json | 8 +- locales/fr/common.json | 8 +- locales/he/common.json | 8 +- locales/hu/common.json | 8 +- locales/it/common.json | 8 +- locales/ja/common.json | 8 +- locales/ko/common.json | 8 +- locales/lv/common.json | 8 +- locales/mn/common.json | 8 +- locales/nb/common.json | 8 +- locales/nl/common.json | 8 +- locales/pl/common.json | 8 +- locales/pt/common.json | 8 +- locales/ro/common.json | 8 +- locales/ru/common.json | 8 +- locales/sk/common.json | 8 +- locales/th/common.json | 8 +- locales/tr/common.json | 8 +- locales/uk/common.json | 8 +- locales/vi/common.json | 8 +- locales/zh-TW/common.json | 8 +- locales/zh/common.json | 8 +- stores/mail-transfer-store.ts | 17 +- 51 files changed, 708 insertions(+), 134 deletions(-) create mode 100644 components/mail-transfer/__tests__/mail-import-dialog-resume.test.tsx create mode 100644 lib/mail-import/locks.ts diff --git a/components/mail-transfer/__tests__/mail-export-dialog.test.tsx b/components/mail-transfer/__tests__/mail-export-dialog.test.tsx index 2b0c24149..53656da6f 100644 --- a/components/mail-transfer/__tests__/mail-export-dialog.test.tsx +++ b/components/mail-transfer/__tests__/mail-export-dialog.test.tsx @@ -104,6 +104,20 @@ describe('MailExportDialog', () => { expect(discard).toHaveBeenCalled(); }); + it('another account signing out leaves the job alone; its own sign-out stops it with the sign-out reason', async () => { + const { runBeforeSignOut } = await import('@/lib/sign-out-cleanup'); + const controller = new AbortController(); + render(); + act(() => { + openMailExport({ mailboxId: 'inbox' }); + useMailTransferStore.getState().startJob({ kind: 'export', controller, scope: 'bulwark-a', finished: Promise.resolve(), progress: { phase: 'running', exported: 1, failed: 0, failures: [], total: 4, fraction: 0.25 } }); + }); + await act(() => runBeforeSignOut('bulwark-b')); + expect(controller.signal.aborted).toBe(false); + await act(() => runBeforeSignOut('bulwark-a')); + expect(controller.signal.reason).toBe('signout'); + }); + it('runs on in the background and the chip names the export', () => { const controller = new AbortController(); render(); diff --git a/components/mail-transfer/__tests__/mail-import-dialog-resume.test.tsx b/components/mail-transfer/__tests__/mail-import-dialog-resume.test.tsx new file mode 100644 index 000000000..631ed5dbd --- /dev/null +++ b/components/mail-transfer/__tests__/mail-import-dialog-resume.test.tsx @@ -0,0 +1,31 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen, fireEvent, act, waitFor } from '@testing-library/react'; +import { MailTransferHost } from '@/components/mail-transfer/mail-transfer-host'; +import { openMailImport } from '@/stores/mail-transfer-store'; +import { useEmailStore } from '@/stores/email-store'; +import { useAuthStore } from '@/stores/auth-store'; +import type { Mailbox } from '@/lib/jmap/types'; + +// The lookup of an earlier run never answers here. +vi.mock('@/lib/mail-import/checkpoints', () => ({ + checkpointKey: () => 'k', + readCheckpoint: () => new Promise(() => {}), + clearCheckpointsFor: async () => {}, +})); + +describe('MailImportDialog and an earlier run', () => { + it('keeps Import off until it knows whether the file can be continued', async () => { + useEmailStore.setState({ mailboxes: [{ id: 'inbox', name: 'Inbox', role: 'inbox', myRights: { mayAddItems: true } } as Mailbox] } as never); + useAuthStore.setState({ client: { getAccountId: () => 'acc1' } as never, activeAccountId: 'b1' } as never); + render(); + act(() => openMailImport()); + const input = screen.getByTestId('mail-import-file') as HTMLInputElement; + Object.defineProperty(input, 'files', { value: [new File(['From a Tue May 5 10:11:12 2020\n\nb\n'], 'a.mbox')], configurable: true }); + fireEvent.change(input); + await screen.findByText('a.mbox'); + const start = screen.getByTestId('mail-import-start') as HTMLButtonElement; + await waitFor(() => expect(start.disabled).toBe(true)); + await new Promise((r) => setTimeout(r, 100)); + expect(start.disabled).toBe(true); + }); +}); diff --git a/components/mail-transfer/__tests__/mail-import-dialog.test.tsx b/components/mail-transfer/__tests__/mail-import-dialog.test.tsx index f191fd56e..ceab01aec 100644 --- a/components/mail-transfer/__tests__/mail-import-dialog.test.tsx +++ b/components/mail-transfer/__tests__/mail-import-dialog.test.tsx @@ -48,6 +48,8 @@ describe('MailImportDialog', () => { pick([new File(['From a Tue May 5 10:11:12 2020\nSubject: x\n\nb\n'], 'old.mbox')]); await screen.findByText('old.mbox'); expect(screen.getByText(/format_mbox/)).toBeTruthy(); + // Import waits until it knows whether an earlier run can be continued. + await waitFor(() => expect((screen.getByTestId('mail-import-start') as HTMLButtonElement).disabled).toBe(false)); fireEvent.click(screen.getByTestId('mail-import-start')); expect(startImport).toHaveBeenCalledWith(expect.objectContaining({ accountId: 'acc2', targetMailboxId: 'shared', keepFolders: false, resume: false, @@ -67,6 +69,8 @@ describe('MailImportDialog', () => { const keep = await screen.findByRole('checkbox', { name: /keep_folders/ }); expect((keep as HTMLInputElement).checked).toBe(true); fireEvent.change(screen.getByTestId('mail-import-target'), { target: { value: '\u0000top' } }); + // Import waits until it knows whether an earlier run can be continued. + await waitFor(() => expect((screen.getByTestId('mail-import-start') as HTMLButtonElement).disabled).toBe(false)); fireEvent.click(screen.getByTestId('mail-import-start')); expect(startImport).toHaveBeenCalledWith(expect.objectContaining({ accountId: 'acc1', targetMailboxId: null, keepFolders: true })); }); diff --git a/components/mail-transfer/mail-export-dialog.tsx b/components/mail-transfer/mail-export-dialog.tsx index 20a46d437..af90c91a9 100644 --- a/components/mail-transfer/mail-export-dialog.tsx +++ b/components/mail-transfer/mail-export-dialog.tsx @@ -96,7 +96,7 @@ export function MailExportDialog({ request }: { request: ExportRequest }) { footer={active ? ( <> - + ) : ( <> diff --git a/components/mail-transfer/mail-import-dialog.tsx b/components/mail-transfer/mail-import-dialog.tsx index f6cdfd8dd..aa057ae52 100644 --- a/components/mail-transfer/mail-import-dialog.tsx +++ b/components/mail-transfer/mail-import-dialog.tsx @@ -65,6 +65,7 @@ export function MailImportDialog({ request }: { request: ImportRequest }) { const [keepFolders, setKeepFolders] = useState(true); const [resume, setResume] = useState(true); const [resumable, setResumable] = useState(0); + const [checking, setChecking] = useState(false); const anyFolders = picked.some((p) => p.folders); const targetMailbox = targets.find((x) => x.mailbox.id === target)?.mailbox; @@ -86,12 +87,16 @@ export function MailImportDialog({ request }: { request: ImportRequest }) { // An earlier import of the same file into the same place that stopped part way. useEffect(() => { let current = true; - if (picked.length === 0 || !accountId) { setResumable(0); return; } + if (picked.length === 0 || !accountId) { setResumable(0); setChecking(false); return; } + // Import waits for this: starting before it knows would throw the checkpoint away. + setChecking(true); void Promise.all(picked.map((p) => readCheckpoint(checkpointKey({ scope: activeAccountId ?? "default", accountId, target: targetId, keepFolders: effectiveKeep, file: p.file, })))).then((found) => { - if (current) setResumable(found.reduce((n, c) => n + (c?.done ?? 0), 0)); - }); + if (!current) return; + setResumable(found.reduce((n, c) => n + (c?.done ?? 0) + (c?.ahead?.length ?? 0), 0)); + setChecking(false); + }, () => { if (current) setChecking(false); }); return () => { current = false; }; }, [picked, accountId, targetId, effectiveKeep, activeAccountId]); @@ -136,7 +141,7 @@ export function MailImportDialog({ request }: { request: ImportRequest }) { footer={active ? ( <> - + ) : ( @@ -147,7 +152,7 @@ export function MailImportDialog({ request }: { request: ImportRequest }) { ); } - const canStart = picked.length > 0 && !!accountId && !(target === TOP && !effectiveKeep); + const canStart = picked.length > 0 && !!accountId && !checking && !(target === TOP && !effectiveKeep); return ( ; const PST_ERRORS = ["encrypted", "not_pst", "unreadable", "worker"]; +const IMPORT_ERRORS = ["over_quota", "forbidden", "failures", "network", "busy"]; -/** The PST reader's errors in words; anything else as the server or browser said it. */ +/** Why an import stopped, in words; anything else as the server or browser said it. */ function errorText(t: T, error: string): string { - const code = /^pst:(\w+):/.exec(error)?.[1]; - return code && PST_ERRORS.includes(code) ? t(`pst_error_${code}`) : error; + const pst = /^pst:(\w+):/.exec(error)?.[1]; + if (pst && PST_ERRORS.includes(pst)) return t(`pst_error_${pst}`); + const own = /^import:(\w+)$/.exec(error)?.[1]; + if (own && IMPORT_ERRORS.includes(own)) return t(`error_${own}`); + return error; } function progressTitle(t: T, p: ImportProgress): string { @@ -312,6 +321,7 @@ function ProgressBody({ progress: p, t, locale, statusId }: { progress: ImportPr )} {p.phase === "cancelled" &&

{t("cancelled_hint")}

} + {p.phase === "done" && (p.retryable ?? 0) > 0 &&

{t("retry_hint")}

} {p.phase === "failed" && p.error &&

{errorText(t, p.error)}

} )} diff --git a/components/mail-transfer/mail-transfer-host.tsx b/components/mail-transfer/mail-transfer-host.tsx index a69c920e5..1266b3f15 100644 --- a/components/mail-transfer/mail-transfer-host.tsx +++ b/components/mail-transfer/mail-transfer-host.tsx @@ -10,6 +10,9 @@ import { MailImportDialog } from "./mail-import-dialog"; import { MailExportDialog } from "./mail-export-dialog"; import { formatClock } from "./transfer-progress"; +/** How long sign-out waits for a cancelled job to stop (its budget is 2 s). */ +const SIGN_OUT_WAIT_MS = 1500; + /** * Draws the mail import and export dialogs, and while it is hidden with a job running, * a chip in the corner that shows the progress and brings it back. Mounted @@ -22,10 +25,18 @@ export function MailTransferHost() { // An account that signs out takes its running job, a finished export's // file in the browser's private storage, and its checkpoints with it. + // Another account signing out leaves the job alone. The job's checkpoint + // goes after the job has stopped, so a late write can't bring it back. useEffect(() => onBeforeSignOut(async (accountId) => { const store = useMailTransferStore.getState(); - if (isJobActive(store.job)) store.cancelJob(); - else store.close(); + const job = store.job; + if (job && (accountId === "all" || !job.scope || job.scope === accountId)) { + if (isJobActive(job)) { + store.cancelJob("signout"); + await Promise.race([job.finished, new Promise((resolve) => setTimeout(resolve, SIGN_OUT_WAIT_MS))]); + } + useMailTransferStore.getState().close(); + } if (accountId !== "all") await clearCheckpointsFor(accountId); }), []); diff --git a/components/mail-transfer/start-export.ts b/components/mail-transfer/start-export.ts index 9e87206c2..70960474a 100644 --- a/components/mail-transfer/start-export.ts +++ b/components/mail-transfer/start-export.ts @@ -1,5 +1,6 @@ import { runExport, folderTree, type ExportFolder, type ExportFormat } from "@/lib/mail-export/engine"; import type { ExportSink } from "@/lib/mail-export/sinks"; +import type { IJMAPClient } from "@/lib/jmap/client-interface"; import { useAuthStore } from "@/stores/auth-store"; import { useMailTransferStore } from "@/stores/mail-transfer-store"; @@ -14,15 +15,27 @@ export interface StartExportInput { /** Runs an export as the dialog's job. */ export function startExport(input: StartExportInput): void { - const client = useAuthStore.getState().client; - if (!client) return; + const auth = useAuthStore.getState(); + if (!auth.client) return; + const client: IJMAPClient = auth.client; const controller = new AbortController(); + let ended: () => void = () => {}; + const finished = new Promise((resolve) => { ended = resolve; }); useMailTransferStore.getState().startJob({ kind: "export", controller, + scope: auth.activeAccountId ?? "default", + finished, progress: { phase: "running", exported: 0, failed: 0, failures: [], total: 0, fraction: 0 }, }); void (async () => { + try { + await run(); + } finally { + ended(); + } + })(); + async function run() { let folders: ExportFolder[]; try { folders = input.mailboxId @@ -46,5 +59,5 @@ export function startExport(input: StartExportInput): void { onProgress: (progress) => useMailTransferStore.getState().updateProgress(progress), }); useMailTransferStore.getState().updateProgress(result); - })(); + } } diff --git a/components/mail-transfer/start-import.ts b/components/mail-transfer/start-import.ts index 649f7cdcd..dd28df0b9 100644 --- a/components/mail-transfer/start-import.ts +++ b/components/mail-transfer/start-import.ts @@ -1,4 +1,5 @@ import { runImport, type ImportFile } from "@/lib/mail-import/engine"; +import type { IJMAPClient } from "@/lib/jmap/client-interface"; import { useAuthStore } from "@/stores/auth-store"; import { useEmailStore } from "@/stores/email-store"; import { useMailTransferStore } from "@/stores/mail-transfer-store"; @@ -16,17 +17,29 @@ export interface StartImportInput { /** Runs an import as the dialog's job and refreshes the mail lists when it ends. */ export function startImport(input: StartImportInput): void { const auth = useAuthStore.getState(); - const client = auth.client; - if (!client) return; + if (!auth.client) return; + const client: IJMAPClient = auth.client; const store = useMailTransferStore.getState(); const controller = new AbortController(); + const scope = auth.activeAccountId ?? "default"; + let ended: () => void = () => {}; + const finished = new Promise((resolve) => { ended = resolve; }); store.startJob({ kind: "import", controller, + scope, + finished, progress: { phase: "running", imported: 0, failed: 0, skipped: {}, failures: [], fraction: 0, foldersCreated: 0 }, }); const hasPst = input.files.some((f) => f.format === "pst"); void (async () => { + try { + await run(); + } finally { + ended(); + } + })(); + async function run() { // The PST reader (and its worker) load only for an Outlook file. const openPst = hasPst ? (await import("@/lib/mail-import/pst-source")).openPstSource : undefined; const result = await runImport({ @@ -35,7 +48,7 @@ export function startImport(input: StartImportInput): void { targetMailboxId: input.targetMailboxId, keepFolders: input.keepFolders, files: input.files, - scope: auth.activeAccountId ?? "default", + scope, resume: input.resume, signal: controller.signal, onProgress: (progress) => useMailTransferStore.getState().updateProgress(progress), @@ -51,5 +64,5 @@ export function startImport(input: StartImportInput): void { } catch { /* the next refresh shows them */ } - })(); + } } diff --git a/lib/__tests__/jmap-mail-transfer.test.ts b/lib/__tests__/jmap-mail-transfer.test.ts index 53d94c966..547efc96f 100644 --- a/lib/__tests__/jmap-mail-transfer.test.ts +++ b/lib/__tests__/jmap-mail-transfer.test.ts @@ -36,6 +36,28 @@ describe('importUploadedEmails', () => { }); }); +describe('the transfer\'s own JMAP requests', () => { + const entry = { blobId: 'b1', mailboxIds: { inbox: true }, keywords: {} }; + + it('throw RateLimitError on a 429 without pausing the rest of the client', async () => { + vi.stubGlobal('fetch', vi.fn(async () => new Response('slow down', { status: 429, headers: { 'Retry-After': '20' } }))); + const c = client(); + Object.assign(c, { apiUrl: 'https://jmap.example.com/jmap/' }); + await expect(c.importUploadedEmails([entry])).rejects.toMatchObject({ name: 'RateLimitError', retryAfterMs: 20_000 }); + expect((c as unknown as { isRateLimited(): boolean }).isRateLimited()).toBe(false); + }); + + it('still go out while the client waits out a 429 of its own', async () => { + const fetch = vi.fn(async () => new Response(JSON.stringify({ methodResponses: [['Email/import', { created: { imp0: { id: 'e1' } } }, '0']] }))); + vi.stubGlobal('fetch', fetch); + const c = client(); + Object.assign(c, { apiUrl: 'https://jmap.example.com/jmap/' }); + (c as unknown as { setRateLimited(ms: number): void }).setRateLimited(60_000); + await expect(c.importUploadedEmails([entry])).resolves.toEqual([{ id: 'e1' }]); + expect(fetch).toHaveBeenCalledTimes(1); + }); +}); + describe('fetchBlobForTransfer', () => { it('downloads with the account in the URL', async () => { const fetch = vi.fn(async () => new Response(new Uint8Array([1, 2, 3]))); diff --git a/lib/jmap/client-interface.ts b/lib/jmap/client-interface.ts index 28e03746d..f808e0527 100644 --- a/lib/jmap/client-interface.ts +++ b/lib/jmap/client-interface.ts @@ -346,6 +346,8 @@ export interface IJMAPClient { properties: string[], accountId?: string, limit?: number, + /** `transfer`: a mail export's own request (a 429 doesn't pause the rest of the client). */ + opts?: { transfer?: boolean }, ): Promise>>; // ── Email mutations ─────────────────────────────────────────── diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index d729fb384..2add18172 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -1752,10 +1752,10 @@ export class JMAPClient implements IJMAPClient { private async authenticatedFetch( url: string, init?: Parameters[1], - opts?: { timeoutMs?: number }, + opts?: { timeoutMs?: number; transfer?: boolean }, ): Promise { // Short-circuit: if rate-limited, reject immediately without sending a request - if (this.isRateLimited()) { + if (!opts?.transfer && this.isRateLimited()) { const remaining = this.rateLimitedUntil - Date.now(); this.notifyRateLimitBlocked(remaining); throw new RateLimitError(remaining); @@ -1786,7 +1786,7 @@ export class JMAPClient implements IJMAPClient { // Handle 429 rate limiting - stop immediately, do not retry if (response.status === 429) { const retryAfterMs = JMAPClient.parseRetryAfter(response); - this.setRateLimited(retryAfterMs); + if (!opts?.transfer) this.setRateLimited(retryAfterMs); throw new RateLimitError(retryAfterMs); } @@ -2085,7 +2085,12 @@ export class JMAPClient implements IJMAPClient { } } - private async request(methodCalls: JMAPMethodCall[], using?: string[]): Promise { + /** + * `transfer`: a mail import or export's own request. A 429 throws + * RateLimitError for the transfer to wait on, without pausing the rest of + * the client, and a pause the client is in doesn't hold the transfer up. + */ + private async request(methodCalls: JMAPMethodCall[], using?: string[], opts?: { transfer?: boolean }): Promise { if (!this.apiUrl) { throw new Error('Not connected. Call connect() first.'); } @@ -2105,7 +2110,7 @@ export class JMAPClient implements IJMAPClient { 'Content-Type': 'application/json', }, body, - }), + }, opts?.transfer ? { transfer: true } : undefined), ); const responseText = await response.text(); @@ -4303,6 +4308,7 @@ export class JMAPClient implements IJMAPClient { properties: string[], accountId?: string, limit: number = 10000, + opts?: { transfer?: boolean }, ): Promise>> { const targetAccountId = accountId || this.accountId; const pageSize = Math.min(500, this.getMaxObjectsInGet()); @@ -4324,7 +4330,7 @@ export class JMAPClient implements IJMAPClient { "#ids": { resultOf: "0", name: "Email/query", path: "/ids" }, properties: ["id", ...properties], }, "1"], - ]); + ], undefined, opts?.transfer ? { transfer: true } : undefined); assertQuerySucceeded(response, 'Email query'); const [getName, getResult] = response.methodResponses?.[1] ?? []; if (getName !== "Email/get" || !getResult) { @@ -6158,7 +6164,7 @@ export class JMAPClient implements IJMAPClient { const blobGet = async (chunk: string[], property: "size" | "data:asBase64") => { const call = () => this.request([ ["Blob/get", { accountId: account, ids: chunk, properties: [property] }, "0"], - ], ["urn:ietf:params:jmap:core", BLOB_CAPABILITY]); + ], ["urn:ietf:params:jmap:core", BLOB_CAPABILITY], { transfer: options?.sized }); let [name, result] = (await call()).methodResponses?.[0] ?? []; if (name === "error" && result?.type === "forbidden" && account !== this.accountId) { account = this.accountId; @@ -11259,7 +11265,7 @@ export class JMAPClient implements IJMAPClient { }); const response = await this.request([ ['Email/import', { accountId: targetAccountId, emails }, '0'], - ]); + ], undefined, { transfer: true }); const [name, result] = response.methodResponses?.[0] ?? []; if (name !== 'Email/import') { throw jmapError(`Email/import failed: ${result?.description || result?.type || 'unknown'}`, result); diff --git a/lib/mail-export/engine.ts b/lib/mail-export/engine.ts index 1040ef8bb..a49f5a724 100644 --- a/lib/mail-export/engine.ts +++ b/lib/mail-export/engine.ts @@ -268,6 +268,7 @@ export async function runExport(options: ExportOptions): Promise ["blobId", "keywords", "receivedAt", "size", "subject"], accountId, Number.MAX_SAFE_INTEGER, + { transfer: true }, )); const refs = list .filter((e) => typeof e.blobId === "string") diff --git a/lib/mail-import/__tests__/engine.test.ts b/lib/mail-import/__tests__/engine.test.ts index 6a481208c..06c779236 100644 --- a/lib/mail-import/__tests__/engine.test.ts +++ b/lib/mail-import/__tests__/engine.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi } from 'vitest'; import { runImport, uploadWindowEnd, quotaFrom, type ImportProgress, type ImportOptions } from '@/lib/mail-import/engine'; -import { checkpointKey, readCheckpoint } from '@/lib/mail-import/checkpoints'; +import { checkpointKey, deleteCheckpoint, readCheckpoint } from '@/lib/mail-import/checkpoints'; import type { IJMAPClient, UploadedEmailImport } from '@/lib/jmap/client-interface'; import type { Mailbox } from '@/lib/jmap/types'; @@ -19,7 +19,17 @@ const mailbox = (id: string, name: string, extra: Partial = {}): Mailbo myRights: {} as Mailbox['myRights'], ...extra, }); -function fakeClient(opts: { failWhen?: (text: string) => boolean; quotaAfter?: number; uploadConcurrency?: number } = {}) { +function fakeClient(opts: { + failWhen?: (text: string) => boolean; + quotaAfter?: number; + uploadConcurrency?: number; + /** Upload errors with no HTTP status (a dropped connection), for the first n uploads. */ + networkErrors?: number; + /** Milliseconds an upload of a message takes. */ + delay?: (text: string) => number; + /** An Email/import error for a message. */ + importError?: (text: string) => { type: string; error: string } | undefined; +} = {}) { let uploads = 0; let inFlight = 0; let maxInFlight = 0; @@ -44,8 +54,10 @@ function fakeClient(opts: { failWhen?: (text: string) => boolean; quotaAfter?: n inFlight++; maxInFlight = Math.max(maxInFlight, inFlight); try { - await new Promise((r) => setTimeout(r, 1)); + const body = await f.text(); + await new Promise((r) => setTimeout(r, opts.delay?.(body) ?? 1)); if (o.signal?.aborted) throw new DOMException('Upload aborted', 'AbortError'); + if (opts.networkErrors && n < opts.networkErrors) throw new Error('Upload network error'); if (opts.quotaAfter !== undefined && n >= opts.quotaAfter && !quotaHit) { quotaHit = true; throw new Error('Failed to upload file: 429 - {"title":"Quota exceeded","detail":"You have exceeded the blob upload quota of 1000 files or 50000000 bytes."}'); @@ -63,6 +75,8 @@ function fakeClient(opts: { failWhen?: (text: string) => boolean; quotaAfter?: n importCalls.push(entries.length); return entries.map((e) => { const text = blobs.get(e.blobId)!; + const refused = opts.importError?.(text); + if (refused) return refused; imported.push({ ...e, accountId, text }); return { id: `email-${imported.length}` }; }); @@ -201,6 +215,96 @@ describe('runImport', () => { await expect(readCheckpoint(key)).resolves.toBeNull(); }); + it('tries a message that failed again on the next run, and only that one', async () => { + const fake = fakeClient({ failWhen: (text) => text.startsWith('Subject: m1\n') }); + const f = file(mbox(4)); + const first = await runImport(options(fake.client, [f])); + expect(first).toMatchObject({ phase: 'done', imported: 3, failed: 1, retryable: 1 }); + const again = fakeClient(); + const second = await runImport(options(again.client, [f], { resume: true })); + expect(again.imported.map((e) => e.text.split('\n')[0])).toEqual(['Subject: m1']); + expect(second).toMatchObject({ phase: 'done', imported: 4, failed: 0 }); + }); + + it('stops after ten failures in a row instead of burning through the file, and keeps the checkpoint', async () => { + const fake = fakeClient({ failWhen: () => true }); + const f = file(mbox(40)); + const result = await runImport(options(fake.client, [f])); + expect(result).toMatchObject({ phase: 'failed', error: 'import:failures' }); + expect(fake.uploads).toBeLessThan(40 * 3); + const key = checkpointKey({ scope: 'bulwark-1', accountId: 'acc-shared', target: 'target', keepFolders: false, file: f }); + // Nothing was done, so nothing is skipped next time: the failures are tried again. + expect((await readCheckpoint(key))?.done ?? 0).toBe(0); + }); + + it('stops at once when the mailbox is over quota', async () => { + const fake = fakeClient({ importError: (text) => (text.includes('Subject: m3\n') ? { type: 'overQuota', error: 'over quota' } : undefined) }); + const result = await runImport(options(fake.client, [file(mbox(60))])); + expect(result).toMatchObject({ phase: 'failed', error: 'import:over_quota' }); + expect(fake.uploads).toBeLessThan(60); + }); + + it('pauses on an upload with no answer and goes on, instead of failing the message', async () => { + const fake = fakeClient({ networkErrors: 1 }); + const seen: ImportProgress[] = []; + const result = await runImport(options(fake.client, [file(mbox(3))], { onProgress: (p) => seen.push(p) })); + expect(seen.some((p) => p.phase === 'paused' && p.pausedUntil === T0 + 30_000)).toBe(true); + expect(result).toMatchObject({ phase: 'done', imported: 3, failed: 0 }); + }); + + it('resumes without importing again what came in after a slow message', async () => { + // m2 is a slow upload; the messages after it are imported first. + const fake = fakeClient({ delay: (text) => (text.startsWith('Subject: m2\n') ? 300 : 1) }); + const controller = new AbortController(); + const f = file(mbox(12)); + await runImport(options(fake.client, [f], { + signal: controller.signal, + onProgress: () => { if (fake.uploads >= 8 && !controller.signal.aborted) controller.abort(); }, + })); + const firstRun = fake.imported.map((e) => e.text.split('\n')[0]); + expect(firstRun).not.toContain('Subject: m2'); + expect(firstRun.some((s) => Number(s.slice(10)) > 2)).toBe(true); + await runImport(options(fake.client, [f], { resume: true })); + const subjects = fake.imported.map((e) => e.text.split('\n')[0]); + expect(subjects).toHaveLength(12); + expect(new Set(subjects).size).toBe(12); + }); + + it('writes no checkpoint and imports nothing more once the account signs out', async () => { + const fake = fakeClient({ delay: () => 20 }); + const controller = new AbortController(); + const f = file(mbox(30)); + const key = checkpointKey({ scope: 'bulwark-1', accountId: 'acc-shared', target: 'target', keepFolders: false, file: f }); + let callsAtSignOut = -1; + const result = await runImport(options(fake.client, [f], { + signal: controller.signal, + onProgress: (p) => { + if (p.imported >= 2 && !controller.signal.aborted) { + callsAtSignOut = fake.importCalls.length; + controller.abort('signout'); + // What the sign-out does next: clear the account's checkpoints. + void deleteCheckpoint(key); + } + }, + })); + expect(result.phase).toBe('cancelled'); + expect(fake.importCalls.length).toBe(callsAtSignOut); + await expect(readCheckpoint(key)).resolves.toBeNull(); + }); + + it('refuses a file another tab is importing', async () => { + const locks = { request: vi.fn(async (_n: string, _o: unknown, cb: (lock: unknown) => unknown) => cb(null)) }; + vi.stubGlobal('navigator', { ...navigator, locks }); + try { + const fake = fakeClient(); + const result = await runImport(options(fake.client, [file(mbox(2))])); + expect(result).toMatchObject({ phase: 'failed', error: 'import:busy' }); + expect(fake.uploads).toBe(0); + } finally { + vi.unstubAllGlobals(); + } + }); + it('starts again from the top when not resuming', async () => { const fake = fakeClient(); const f = file(mbox(3)); diff --git a/lib/mail-import/checkpoints.ts b/lib/mail-import/checkpoints.ts index ec0c3c203..7a45ddbd5 100644 --- a/lib/mail-import/checkpoints.ts +++ b/lib/mail-import/checkpoints.ts @@ -12,8 +12,10 @@ export interface ImportCheckpoint { /** The Bulwark account that ran the import. */ scope: string; fileName: string; - /** Items handled (imported, skipped or failed) from the start of the file. */ + /** Items done for good (imported or skipped) from the start of the file, without a gap. */ done: number; + /** Items done after the first gap (a message that failed and is tried again). */ + ahead?: number[]; imported: number; updatedAt: number; } diff --git a/lib/mail-import/engine.ts b/lib/mail-import/engine.ts index afd6c2292..375ec932a 100644 --- a/lib/mail-import/engine.ts +++ b/lib/mail-import/engine.ts @@ -1,6 +1,7 @@ import type { IJMAPClient, UploadedEmailImport, UploadedEmailImportResult } from "@/lib/jmap/client-interface"; import type { Mailbox } from "@/lib/jmap/types"; import { checkpointKey, deleteCheckpoint, readCheckpoint, writeCheckpoint } from "./checkpoints"; +import { holdLock } from "./locks"; import { emlSource, mboxSource, zipSource, type ImportFormat, type ImportItem, type ItemSource, type SkipReason, type SourceOptions } from "./sources"; /** @@ -42,8 +43,10 @@ export interface ImportProgress { /** The limit the server named, when it did. */ quota?: { files?: number; bytes?: number }; foldersCreated: number; - /** Why the whole import stopped (phase failed). */ + /** Why the whole import stopped (phase failed): `import:`, `pst:: ...` or the error's text. */ error?: string; + /** Messages that failed for a passing reason; importing the same files again tries them again. */ + retryable?: number; } export interface ImportFile { @@ -138,17 +141,22 @@ function maxConcurrentUploads(client: IJMAPClient): number { return Math.max(1, Math.min(uploads, (client.getMaxConcurrentRequests?.() ?? 4) - 2)); } -/** Top-level folder names that mean a special folder, as Outlook and others name them. */ +/** + * Top-level folder names that mean a special folder, as Outlook, Thunderbird + * and others name them in the languages Outlook ships most often. Outlook + * files say which folder is which themselves (`folderRole`); for mbox and + * ZIP files the name is all there is. + */ const ROLE_NAMES: Record = { - inbox: ["inbox", "posteingang"], - sent: ["sent", "sent items", "sent mail", "sent messages", "gesendet", "gesendete elemente", "gesendete objekte"], - drafts: ["drafts", "entwürfe"], - trash: ["trash", "deleted items", "deleted messages", "papierkorb", "gelöschte elemente", "gelöschte objekte"], - junk: ["junk", "junk e-mail", "junk email", "junk mail", "spam", "junk-e-mail"], - archive: ["archive", "archiv"], + inbox: ["inbox", "posteingang", "boîte de réception", "bandeja de entrada", "posta in arrivo", "postvak in", "caixa de entrada", "skrzynka odbiorcza", "входящие", "doručená pošta", "inkorgen", "indbakke", "innboks", "saapuneet"], + sent: ["sent", "sent items", "sent mail", "sent messages", "gesendet", "gesendete elemente", "gesendete objekte", "éléments envoyés", "elementos enviados", "posta inviata", "verzonden items", "itens enviados", "elementy wysłane", "отправленные", "odeslaná pošta", "skickat", "sendt post", "sendte elementer", "lähetetyt"], + drafts: ["drafts", "entwürfe", "brouillons", "borradores", "bozze", "concepten", "rascunhos", "wersje robocze", "черновики", "koncepty", "utkast", "kladder", "luonnokset"], + trash: ["trash", "deleted items", "deleted messages", "papierkorb", "gelöschte elemente", "gelöschte objekte", "éléments supprimés", "elementos eliminados", "posta eliminata", "verwijderde items", "itens excluídos", "elementy usunięte", "удаленные", "удалённые", "odstraněná pošta", "borttaget", "slettet post", "slettede elementer", "poistetut"], + junk: ["junk", "junk e-mail", "junk email", "junk mail", "spam", "junk-e-mail", "courrier indésirable", "correo no deseado", "posta indesiderata", "ongewenste e-mail", "lixo eletrônico", "wiadomości-śmieci", "нежелательная почта", "nevyžádaná pošta", "skräppost", "uønsket e-mail", "søppelpost", "roskaposti"], + archive: ["archive", "archiv", "archives", "archivo", "archivio", "archief", "arquivo", "archiwum", "архив", "arkiv", "arkisto"], }; -function roleFor(name: string): string | undefined { +export function roleFor(name: string): string | undefined { const key = name.trim().toLowerCase(); return Object.keys(ROLE_NAMES).find((role) => ROLE_NAMES[role].includes(key)); } @@ -156,8 +164,9 @@ function roleFor(name: string): string | undefined { /** * Maps a file's folder paths to mailboxes in the account, creating what's * missing once. A folder with the same name under the same parent is used - * again, so a resumed import doesn't make a second copy of the tree; at the - * top level an "Inbox" or "Sent Items" means the account's own. + * again, so a resumed import doesn't make a second copy of the tree. At the + * top level a special folder (the file says so, or its name does) means the + * account's own Inbox, Sent, Drafts, Trash, Junk or Archive. */ export class FolderResolver { private mailboxes: Mailbox[] | null = null; @@ -185,11 +194,12 @@ export class FolderResolver { return FolderResolver.idOf(box); } - resolve(path: string[]): Promise { + /** `topRole`: what the file says the first folder of `path` is. */ + resolve(path: string[], topRole?: string): Promise { const key = path.join("\u0000"); let pending = this.cache.get(key); if (!pending) { - pending = this.resolveUncached(path); + pending = this.resolveUncached(path, topRole); // A failed creation may be tried again by the next message. pending.catch(() => this.cache.delete(key)); this.cache.set(key, pending); @@ -197,20 +207,20 @@ export class FolderResolver { return pending; } - private async resolveUncached(path: string[]): Promise { + private async resolveUncached(path: string[], topRole?: string): Promise { if (path.length === 0) return this.root ?? this.inbox(); - const parentId = path.length === 1 ? this.root : await this.resolve(path.slice(0, -1)); + const parentId = path.length === 1 ? this.root : await this.resolve(path.slice(0, -1), topRole); const name = path[path.length - 1].trim().slice(0, 255) || "Imported"; const mailboxes = await this.list(); + if (parentId === null) { + const role = (path.length === 1 && topRole) || roleFor(name); + const special = role && mailboxes.find((m) => m.role === role); + if (special) return FolderResolver.idOf(special); + } const same = (m: Mailbox) => m.name.trim().toLowerCase() === name.toLowerCase(); const parentMatches = (m: Mailbox) => (parentId === null ? !m.parentId : m.parentId === parentId || m.parentId === mailboxes.find((p) => FolderResolver.idOf(p) === parentId)?.id); const existing = mailboxes.find((m) => parentMatches(m) && same(m)); if (existing) return FolderResolver.idOf(existing); - if (parentId === null) { - const role = roleFor(name); - const special = role && mailboxes.find((m) => m.role === role); - if (special) return FolderResolver.idOf(special); - } const created = await this.client.createMailbox(name, parentId ?? undefined, this.accountId); this.created++; mailboxes.push({ ...created, parentId: parentId ?? undefined }); @@ -225,23 +235,66 @@ interface Uploaded { entry: UploadedEmailImport; } -/** Counts handled items per file, so the checkpoint is the first index not handled yet. */ -class Watermark { - private next = 0; - private readonly ahead = new Set(); - constructor(start: number) { this.next = start; } +/** + * Which items of a file are done for good (imported, or skipped for a reason + * that won't change): everything before `next`, and the ones in `ahead` + * past a gap. A message that failed for a passing reason leaves a gap, so + * the next run of the same import tries it again; nothing done is redone. + */ +export class Watermark { + private next: number; + private readonly ahead: Set; + constructor(start: number, ahead: Iterable = []) { + this.next = start; + this.ahead = new Set(ahead); + this.advance(); + } + private advance(): void { + while (this.ahead.has(this.next)) { this.ahead.delete(this.next); this.next++; } + } mark(index: number): void { if (index < this.next) return; this.ahead.add(index); - while (this.ahead.has(this.next)) { this.ahead.delete(this.next); this.next++; } + this.advance(); + } + has(index: number): boolean { + return index < this.next || this.ahead.has(index); } get value(): number { return this.next; } + get beyond(): number[] { return [...this.ahead].sort((a, b) => a - b); } } +/** The whole import stops (phase failed); the checkpoint keeps what was done. */ +export class ImportStopped extends Error { + constructor(readonly code: "over_quota" | "forbidden" | "failures" | "network" | "busy") { + super(`import:${code}`); + this.name = "ImportStopped"; + } +} + +/** This many failures in a row stop the import instead of burning through the file. */ +const MAX_FAILURES_IN_A_ROW = 10; +/** Pauses after uploads fail without an answer: a short one, then until the upload window ends. */ +const NETWORK_PAUSE_MS = 30_000; +/** Email/import errors that stop the whole import: nothing more would go in. */ +const STOPPING_IMPORT_ERRORS: Record = { overQuota: "over_quota", forbidden: "forbidden" }; + export async function runImport(options: ImportOptions): Promise { - const { client, accountId, signal } = options; + const { client, accountId } = options; const now = options.now ?? Date.now; const sleep = options.sleep ?? defaultSleep; + // Our own signal: a cancel from outside, or a stop from inside. + const inner = new AbortController(); + const outerAbort = () => inner.abort(options.signal.reason); + if (options.signal.aborted) outerAbort(); + else options.signal.addEventListener("abort", outerAbort, { once: true }); + const signal = inner.signal; + const state: { stopped: ImportStopped | null } = { stopped: null }; + const stop = (error: ImportStopped) => { + if (!state.stopped) state.stopped = error; + inner.abort(error); + }; + const progress: ImportProgress = { phase: "running", imported: 0, failed: 0, skipped: {}, failures: [], fraction: 0, foldersCreated: 0 }; const folders = new FolderResolver(client, accountId, options.targetMailboxId); const maxSize = client.getMaxSizeUpload() || Number.POSITIVE_INFINITY; @@ -256,9 +309,15 @@ export async function runImport(options: ImportOptions): Promise options.onProgress({ ...progress, skipped: { ...progress.skipped }, failures: [...progress.failures] }); }; const skip = (reason: SkipReason, n = 1) => { progress.skipped[reason] = (progress.skipped[reason] ?? 0) + n; }; - const fail = (label: string, reason: string) => { + let failuresInARow = 0; + let retryable = 0; + /** A failure; `permanent` ones (not a message at all) aren't tried again on the next run. */ + const fail = (u: { fileIndex: number; index: number; label: string }, reason: string, permanent = false) => { progress.failed++; - if (progress.failures.length < MAX_FAILURES_LISTED) progress.failures.push({ label, reason }); + if (progress.failures.length < MAX_FAILURES_LISTED) progress.failures.push({ label: u.label, reason }); + if (permanent) { handled(u); return; } + progress.retryable = ++retryable; + if (++failuresInARow >= MAX_FAILURES_IN_A_ROW) stop(new ImportStopped("failures")); }; // Shared pause: when the server refuses uploads, every lane waits. @@ -292,10 +351,13 @@ export async function runImport(options: ImportOptions): Promise const importedPerFile: number[] = options.files.map(() => 0); let lastSaved = 0; const saveCheckpoints = async (force = false) => { + // After sign-out nothing about the account may be written again. + if (options.signal.reason === "signout") return; if (!force && now() - lastSaved < CHECKPOINT_EVERY_MS) return; lastSaved = now(); - await Promise.all(marks.map((m, i) => m.value > 0 ? writeCheckpoint({ - key: keys[i], scope: options.scope, fileName: options.files[i].file.name, done: m.value, imported: importedPerFile[i], updatedAt: now(), + await Promise.all(marks.map((m, i) => m.value > 0 || m.beyond.length > 0 ? writeCheckpoint({ + key: keys[i], scope: options.scope, fileName: options.files[i].file.name, + done: m.value, ahead: m.beyond, imported: importedPerFile[i], updatedAt: now(), }) : Promise.resolve())); }; @@ -303,7 +365,9 @@ export async function runImport(options: ImportOptions): Promise let queue: Uploaded[] = []; let importing: Promise = Promise.resolve(); let flushTimer: ReturnType | null = null; - const handled = (u: { fileIndex: number; index: number }) => marks[u.fileIndex].mark(u.index); + function handled(u: { fileIndex: number; index: number }): void { + marks[u.fileIndex].mark(u.index); + } const importBatch = async (batch: Uploaded[]) => { let results: UploadedEmailImportResult[] | null = null; @@ -318,22 +382,36 @@ export async function runImport(options: ImportOptions): Promise } catch (error) { const name = (error as { name?: string } | null)?.name; if (name === "RateLimitError" && attempt < 3) { - await sleep(Math.max(1000, (error as { retryAfterMs?: number }).retryAfterMs ?? 60_000), signal); + await sleep(Math.max(1000, (error as { retryAfterMs?: number }).retryAfterMs ?? 60_000), options.signal).catch(() => {}); + if (options.signal.aborted) break; + continue; + } + if (attempt < 2 && !isAbort(error)) { + await sleep(2000 * (attempt + 1), options.signal).catch(() => {}); + if (options.signal.aborted) break; continue; } - if (attempt < 1 && !isAbort(error)) { await sleep(2000, signal); continue; } results = batch.map(() => ({ error: error instanceof Error ? error.message : String(error) })); } } + if (!results) return; results.forEach((result, i) => { const item = batch[i]; if ("id" in result) { progress.imported++; importedPerFile[item.fileIndex]++; - } else { - fail(item.label, result.type === "invalidEmail" ? "invalid" : result.error); + failuresInARow = 0; + handled(item); + return; } - handled(item); + const stopping = result.type ? STOPPING_IMPORT_ERRORS[result.type] : undefined; + if (stopping) { + progress.failed++; + stop(new ImportStopped(stopping)); + return; + } + // A blob that isn't a message won't become one: don't try it again. + fail(item, result.type === "invalidEmail" ? "invalid" : result.error, result.type === "invalidEmail"); }); report(); await saveCheckpoints(); @@ -355,14 +433,15 @@ export async function runImport(options: ImportOptions): Promise // ── Uploads, a few at a time ── const running = new Set>(); + let networkErrors = 0; const upload = async (item: Extract, fileIndex: number) => { + const u = { fileIndex, index: item.index, label: item.label }; let mailboxId: string; try { - mailboxId = await folders.resolve(options.keepFolders ? item.folder : []); + mailboxId = await folders.resolve(options.keepFolders ? item.folder : [], item.folderRole); } catch (error) { - if (isAbort(error)) throw error; - fail(item.label, error instanceof Error ? error.message : String(error)); - handled({ fileIndex, index: item.index }); + if (isAbort(error) || signal.aborted) throw new ImportCancelled(); + fail(u, error instanceof Error ? error.message : String(error)); return; } const file = new File([item.data], "message.eml", { type: "message/rfc822" }); @@ -370,6 +449,7 @@ export async function runImport(options: ImportOptions): Promise await waitWhilePaused(); try { const { blobId } = await client.uploadBlob(file, { accountId, signal }); + networkErrors = 0; enqueue({ index: item.index, fileIndex, @@ -385,17 +465,25 @@ export async function runImport(options: ImportOptions): Promise progress.quota = quotaFrom(body); continue; } + if (status === undefined) { + // No answer at all: the connection dropped, or a refusal the browser + // couldn't read. Pause and try again rather than losing messages. + networkErrors++; + if (networkErrors > 2) { stop(new ImportStopped("network")); throw new ImportCancelled(); } + pausedUntil = Math.max(pausedUntil, networkErrors === 1 ? now() + NETWORK_PAUSE_MS : uploadWindowEnd(now()) + 5000); + continue; + } if (status === 413 || (status === 400 && /maxSizeUpload/.test(body))) { skip("too_large"); - handled({ fileIndex, index: item.index }); + handled(u); return; } + if (status === 403) { stop(new ImportStopped("forbidden")); throw new ImportCancelled(); } if (attempt < UPLOAD_RETRY_DELAYS_MS.length) { await sleep(UPLOAD_RETRY_DELAYS_MS[attempt], signal); continue; } - fail(item.label, status ? `HTTP ${status}` : body); - handled({ fileIndex, index: item.index }); + fail(u, `HTTP ${status}`); return; } } @@ -404,30 +492,36 @@ export async function runImport(options: ImportOptions): Promise const startUpload = async (item: Extract, fileIndex: number) => { while (running.size >= lanes) await Promise.race(running); if (signal.aborted) throw new ImportCancelled(); - // A lane never rejects: a cancel shows in `signal`, anything else is a failure of this message. + // A lane never rejects: a cancel or a stop shows in `signal`, anything else is a failure of this message. const task: Promise = upload(item, fileIndex) .catch((error) => { - if (!isAbort(error) && !signal.aborted) { - fail(item.label, error instanceof Error ? error.message : String(error)); - handled({ fileIndex, index: item.index }); - } + if (!isAbort(error) && !signal.aborted) fail({ fileIndex, index: item.index, label: item.label }, error instanceof Error ? error.message : String(error)); }) .finally(() => { running.delete(task); }); running.add(task); }; + // One import of a file at a time, across the tabs of this browser. + const releases: (() => void)[] = []; + let keepCheckpoints = false; try { + for (const key of keys) { + const release = await holdLock(`bulwark-mail-import:${key}`); + if (!release) throw new ImportStopped("busy"); + releases.push(release); + } for (let f = 0; f < options.files.length; f++) { const { file, format } = options.files[f]; const checkpoint = options.resume ? await readCheckpoint(keys[f]) : null; if (!options.resume) await deleteCheckpoint(keys[f]); - const skipBefore = checkpoint?.done ?? 0; if (checkpoint) { progress.imported += checkpoint.imported; importedPerFile[f] = checkpoint.imported; } - marks.push(new Watermark(skipBefore)); + const mark = new Watermark(checkpoint?.done ?? 0, checkpoint?.ahead ?? []); + marks.push(mark); progress.fileName = file.name; const sourceOptions: SourceOptions = { maxMessageBytes: maxSize, - skipBefore, + skipBefore: mark.value, + skipAlso: new Set(mark.beyond), signal, onProgress: (fraction) => { fileFraction[f] = fraction; setFraction(); report(); }, onTotal: (count) => { @@ -446,7 +540,7 @@ export async function runImport(options: ImportOptions): Promise try { for await (const item of source) { - if (signal.aborted) throw new ImportCancelled(); + if (signal.aborted) throw state.stopped ?? new ImportCancelled(); if (item.kind === "skipped") { // Items an earlier run handled are already counted in its imported total. if (item.reason !== "resumed") skip(item.reason); @@ -464,21 +558,32 @@ export async function runImport(options: ImportOptions): Promise setFraction(); } while (running.size > 0) await Promise.race(running); - if (signal.aborted) throw new ImportCancelled(); + if (signal.aborted) throw state.stopped ?? new ImportCancelled(); await flush(); await importing; + if (state.stopped) throw state.stopped; progress.phase = "done"; progress.fraction = 1; progress.pausedUntil = undefined; - await Promise.all(keys.map((k) => deleteCheckpoint(k))); + // Messages that failed for a passing reason are tried again by the next run. + keepCheckpoints = retryable > 0; + if (keepCheckpoints) await saveCheckpoints(true); + else await Promise.all(keys.map((k) => deleteCheckpoint(k))); } catch (error) { - // Let the uploads in flight end (they were aborted) and keep what got imported. + const signedOut = options.signal.reason === "signout"; await Promise.allSettled([...running]); - await flush().catch(() => {}); - await importing.catch(() => {}); - await saveCheckpoints(true).catch(() => {}); + if (!signedOut) { + // Keep what was uploaded and where the import got to. + await flush().catch(() => {}); + await importing.catch(() => {}); + await saveCheckpoints(true).catch(() => {}); + } progress.pausedUntil = undefined; - if (isAbort(error) || signal.aborted) { + const reason = state.stopped ?? error; + if ((reason as { name?: string } | null)?.name === "ImportStopped") { + progress.phase = "failed"; + progress.error = (reason as ImportStopped).message; + } else if (isAbort(error) || options.signal.aborted) { progress.phase = "cancelled"; } else { progress.phase = "failed"; @@ -486,6 +591,8 @@ export async function runImport(options: ImportOptions): Promise } } finally { if (flushTimer) clearTimeout(flushTimer); + options.signal.removeEventListener("abort", outerAbort); + for (const release of releases) release(); } report(true); return progress; diff --git a/lib/mail-import/locks.ts b/lib/mail-import/locks.ts new file mode 100644 index 000000000..de2c05b1e --- /dev/null +++ b/lib/mail-import/locks.ts @@ -0,0 +1,36 @@ +/** + * Web Locks across the tabs of this browser, held until released: one tab + * imports a file at a time, and an export's private file isn't swept while + * the tab that wrote it still uses it. Where the browser has no Web Locks, + * every request succeeds (one tab can't know about another then). + */ + +interface LockManagerLike { + request(name: string, options: { ifAvailable: boolean }, callback: (lock: unknown) => Promise | void): Promise; +} + +function lockManager(): LockManagerLike | null { + const locks = typeof navigator !== "undefined" ? (navigator as { locks?: LockManagerLike }).locks : undefined; + return locks && typeof locks.request === "function" ? locks : null; +} + +export function hasWebLocks(): boolean { + return lockManager() !== null; +} + +/** Takes the lock if nobody holds it: a release function, or null when another tab has it. */ +export function holdLock(name: string): Promise<(() => void) | null> { + const locks = lockManager(); + if (!locks) return Promise.resolve(() => {}); + return new Promise((resolve) => { + locks + .request(name, { ifAvailable: true }, (lock) => { + if (!lock) { + resolve(null); + return; + } + return new Promise((release) => resolve(release)); + }) + .catch(() => resolve(() => {})); + }); +} diff --git a/lib/mail-import/pst-source.ts b/lib/mail-import/pst-source.ts index c63270feb..fcb049108 100644 --- a/lib/mail-import/pst-source.ts +++ b/lib/mail-import/pst-source.ts @@ -65,7 +65,7 @@ export async function* openPstSource(file: File, options: SourceOptions): ItemSo }; try { - worker.postMessage({ type: "open", file, skipBefore: options.skipBefore, maxMessageBytes: options.maxMessageBytes }); + worker.postMessage({ type: "open", file, skipBefore: options.skipBefore, skipAlso: [...(options.skipAlso ?? [])], maxMessageBytes: options.maxMessageBytes }); const opened = await next(); if (opened.type === "abort") throw new DOMException("Import cancelled", "AbortError"); if (opened.type === "error") throw new PstReadError(opened.code, opened.message); diff --git a/lib/mail-import/pst/items.ts b/lib/mail-import/pst/items.ts index 95327e133..38375b1e8 100644 --- a/lib/mail-import/pst/items.ts +++ b/lib/mail-import/pst/items.ts @@ -29,7 +29,7 @@ function utcDate(date: Date | undefined): string | undefined { return date.toISOString().replace(/\.\d{3}Z$/, "Z"); } -export function walkPst(pst: any, options: { skipBefore: number; maxMessageBytes: number; boundarySeed?: string }): PstWalk { +export function walkPst(pst: any, options: { skipBefore: number; skipAlso?: Set; maxMessageBytes: number; boundarySeed?: string }): PstWalk { const folders: PstFolderRef[] = [...pstFolders(pst)]; const total = folders.reduce((n, f) => n + (Number(f.folder.contentCount) || 0), 0); @@ -47,7 +47,7 @@ export function walkPst(pst: any, options: { skipBefore: number; maxMessageBytes } if (!child) break; const i = index++; - if (i < options.skipBefore) { + if (i < options.skipBefore || options.skipAlso?.has(i)) { yield { kind: "skipped", index: i, reason: "resumed" }; continue; } diff --git a/lib/mail-import/pst/worker.ts b/lib/mail-import/pst/worker.ts index 97679289a..f2c2d4aae 100644 --- a/lib/mail-import/pst/worker.ts +++ b/lib/mail-import/pst/worker.ts @@ -4,7 +4,7 @@ * folders and hands over one message at a time as MIME, only as many as the * page asked for, so a large file never piles up in memory. * - * page → worker { type: "open", file, skipBefore, maxMessageBytes } + * page → worker { type: "open", file, skipBefore, skipAlso, maxMessageBytes } * { type: "pull", count } * worker → page { type: "opened", total } * { type: "item", item, fraction } (item.mime is transferred) @@ -37,12 +37,12 @@ function errorCode(error: unknown): string { } scope.onmessage = (event: MessageEvent) => { - const data = event.data as { type: string; file?: File; skipBefore?: number; maxMessageBytes?: number; count?: number }; + const data = event.data as { type: string; file?: File; skipBefore?: number; skipAlso?: number[]; maxMessageBytes?: number; count?: number }; try { if (data.type === "open" && data.file) { registerPstFile("import.pst", data.file); pst = new PSTFile("import.pst") as unknown as { close(): void }; - const walk = walkPst(pst, { skipBefore: data.skipBefore ?? 0, maxMessageBytes: data.maxMessageBytes ?? Number.POSITIVE_INFINITY }); + const walk = walkPst(pst, { skipBefore: data.skipBefore ?? 0, skipAlso: new Set(data.skipAlso ?? []), maxMessageBytes: data.maxMessageBytes ?? Number.POSITIVE_INFINITY }); items = walk.items; total = walk.total; scope.postMessage({ type: "opened", total }); diff --git a/lib/mail-import/sources.ts b/lib/mail-import/sources.ts index 06c66121a..8eb0f14f9 100644 --- a/lib/mail-import/sources.ts +++ b/lib/mail-import/sources.ts @@ -40,6 +40,8 @@ export interface ImportMessage { label: string; /** Messages attached to this one that couldn't be read. */ embeddedSkipped?: number; + /** The special folder (inbox, sent, drafts, trash, junk) the top folder of `folder` is, when the file says so. */ + folderRole?: string; } export interface ImportSkip { @@ -56,6 +58,8 @@ export interface SourceOptions { maxMessageBytes: number; /** Items before this index were handled by an earlier run: report them as `resumed`. */ skipBefore: number; + /** Items after `skipBefore` an earlier run handled too (it stopped with gaps). */ + skipAlso?: Set; /** Called as the file is read: the part done, 0 to 1. */ onProgress?: (fraction: number) => void; /** Called once the number of messages in the file is known. */ @@ -65,6 +69,11 @@ export interface SourceOptions { export type ItemSource = AsyncGenerator; +/** Whether an earlier run of the same import handled item `index`. */ +export function isResumed(options: Pick, index: number): boolean { + return index < options.skipBefore || !!options.skipAlso?.has(index); +} + const READ_CHUNK = 1 << 20; const MIME = "message/rfc822"; @@ -119,7 +128,7 @@ export function subjectOf(headerBlock: string): string { export async function* emlSource(file: File, options: SourceOptions): ItemSource { options.onTotal?.(1); - if (options.skipBefore > 0) { + if (isResumed(options, 0)) { yield { kind: "skipped", index: 0, reason: "resumed" }; options.onProgress?.(1); return; @@ -189,7 +198,7 @@ async function* mboxFromChunks( 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); + yield isResumed(options, i) ? { kind: "skipped", index: i, reason: "resumed" } : mboxItem(m, i, folder); } }; for await (const chunk of chunks) { @@ -249,7 +258,7 @@ export async function* zipSource(file: File, options: SourceOptions): ItemSource yield* mboxFromChunks(chunks, zipFolder(entry.name, true), options, index, (next) => { index = next; }); } else if (isEml(entry.name)) { const i = index++; - if (i < options.skipBefore) { + if (isResumed(options, i)) { yield { kind: "skipped", index: i, reason: "resumed" }; } else if (entry.size > options.maxMessageBytes) { yield { kind: "skipped", index: i, reason: "too_large", label: emlLabel(entry.name) }; @@ -269,7 +278,7 @@ export async function* zipSource(file: File, options: SourceOptions): ItemSource } } else { const i = index++; - yield i < options.skipBefore ? { kind: "skipped", index: i, reason: "resumed" } : { kind: "skipped", index: i, reason: "other", label: emlLabel(entry.name) }; + yield isResumed(options, i) ? { kind: "skipped", index: i, reason: "resumed" } : { kind: "skipped", index: i, reason: "other", label: emlLabel(entry.name) }; } doneBytes += entry.compressedSize; options.onProgress?.(doneBytes / totalBytes); diff --git a/locales/ar/common.json b/locales/ar/common.json index 137fdc947..65fb75fdb 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "يستخدم ملف Outlook هذا تشفيرًا عاليًا لا يستطيع Bulwark قراءته. صدّر البريد في Outlook إلى ملف ‎.pst جديد بلا تشفير.", "pst_error_not_pst": "هذا الملف ليس ملف بيانات Outlook يستطيع Bulwark قراءته.", "pst_error_unreadable": "تعذّر على Bulwark قراءة ملف Outlook هذا.", - "pst_error_worker": "لم يُحمَّل قارئ ملفات Outlook. أعد تحميل الصفحة وحاول مرة أخرى." + "pst_error_worker": "لم يُحمَّل قارئ ملفات Outlook. أعد تحميل الصفحة وحاول مرة أخرى.", + "error_over_quota": "صندوق البريد ممتلئ، لذا توقف الاستيراد. أفرغ بعض المساحة واستورد الملفات نفسها إلى المجلد نفسه مرة أخرى للمتابعة.", + "error_forbidden": "لا يمكنك إضافة بريد إلى هذا المجلد، لذا توقف الاستيراد.", + "error_failures": "فشلت رسائل كثيرة متتالية، لذا توقف الاستيراد. استورد الملفات نفسها إلى المجلد نفسه لاحقًا للمتابعة.", + "error_network": "فشل الاتصال بالخادم مرارًا، لذا توقف الاستيراد. استورد الملفات نفسها إلى المجلد نفسه مرة أخرى للمتابعة.", + "error_busy": "يجري استيراد هذه الملفات في علامة تبويب أخرى بالفعل.", + "retry_hint": "استورد الملفات نفسها إلى المجلد نفسه مرة أخرى لإعادة محاولة الرسائل التي فشلت." }, "mail_export": { "menu_export": "تصدير المجلد…", diff --git a/locales/ca/common.json b/locales/ca/common.json index a06eb0ad4..5a0c2111c 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "Aquest fitxer d'Outlook fa servir xifratge alt, que Bulwark no pot llegir. A Outlook, exporteu el correu a un fitxer .pst nou sense xifratge.", "pst_error_not_pst": "Aquest fitxer no és un fitxer de dades d'Outlook que Bulwark pugui llegir.", "pst_error_unreadable": "Bulwark no ha pogut llegir aquest fitxer d'Outlook.", - "pst_error_worker": "El lector de fitxers d'Outlook no s'ha carregat. Torneu a carregar la pàgina i proveu-ho de nou." + "pst_error_worker": "El lector de fitxers d'Outlook no s'ha carregat. Torneu a carregar la pàgina i proveu-ho de nou.", + "error_over_quota": "La bústia és plena, així que la importació s'ha aturat. Feu espai i torneu a importar els mateixos fitxers a la mateixa carpeta per continuar.", + "error_forbidden": "No podeu afegir correu a aquesta carpeta, així que la importació s'ha aturat.", + "error_failures": "Han fallat massa missatges seguits, així que la importació s'ha aturat. Torneu a importar els mateixos fitxers a la mateixa carpeta més tard per continuar.", + "error_network": "La connexió amb el servidor ha fallat diverses vegades, així que la importació s'ha aturat. Torneu a importar els mateixos fitxers a la mateixa carpeta per continuar.", + "error_busy": "Aquests fitxers ja s'estan important en una altra pestanya.", + "retry_hint": "Torneu a importar els mateixos fitxers a la mateixa carpeta per tornar a provar els que han fallat." }, "mail_export": { "menu_export": "Exporta la carpeta…", diff --git a/locales/cs/common.json b/locales/cs/common.json index a4eed6bb6..ea4edc14f 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "Tento soubor Outlooku používá vysoké šifrování, které Bulwark neumí přečíst. V Outlooku exportujte poštu do nového souboru .pst bez šifrování.", "pst_error_not_pst": "Tento soubor není datový soubor Outlooku, který by Bulwark uměl přečíst.", "pst_error_unreadable": "Bulwark tento soubor Outlooku nedokázal přečíst.", - "pst_error_worker": "Čtečka souborů Outlooku se nenačetla. Načtěte stránku znovu a zkuste to ještě jednou." + "pst_error_worker": "Čtečka souborů Outlooku se nenačetla. Načtěte stránku znovu a zkuste to ještě jednou.", + "error_over_quota": "Schránka je plná, proto se import zastavil. Uvolněte místo a importujte stejné soubory znovu do stejné složky, abyste pokračovali.", + "error_forbidden": "Do této složky nesmíte přidávat poštu, proto se import zastavil.", + "error_failures": "Příliš mnoho zpráv za sebou selhalo, proto se import zastavil. Importujte stejné soubory později znovu do stejné složky, abyste pokračovali.", + "error_network": "Spojení se serverem opakovaně selhalo, proto se import zastavil. Importujte stejné soubory znovu do stejné složky, abyste pokračovali.", + "error_busy": "Tyto soubory se už importují v jiné kartě.", + "retry_hint": "Importujte stejné soubory znovu do stejné složky, abyste zkusili neúspěšné ještě jednou." }, "mail_export": { "menu_export": "Exportovat složku…", diff --git a/locales/da/common.json b/locales/da/common.json index 8d0d768d0..2db8c6f0f 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "Denne Outlook-fil bruger høj kryptering, som Bulwark ikke kan læse. Eksportér mailen i Outlook til en ny .pst-fil uden kryptering.", "pst_error_not_pst": "Denne fil er ikke en Outlook-datafil, som Bulwark kan læse.", "pst_error_unreadable": "Bulwark kunne ikke læse denne Outlook-fil.", - "pst_error_worker": "Læseren til Outlook-filer blev ikke indlæst. Genindlæs siden, og prøv igen." + "pst_error_worker": "Læseren til Outlook-filer blev ikke indlæst. Genindlæs siden, og prøv igen.", + "error_over_quota": "Postkassen er fuld, så importen stoppede. Skaf plads, og importér de samme filer til samme mappe igen for at fortsætte.", + "error_forbidden": "Du må ikke tilføje mail til denne mappe, så importen stoppede.", + "error_failures": "For mange beskeder i træk mislykkedes, så importen stoppede. Importér de samme filer til samme mappe igen senere for at fortsætte.", + "error_network": "Forbindelsen til serveren svigtede gentagne gange, så importen stoppede. Importér de samme filer til samme mappe igen for at fortsætte.", + "error_busy": "Disse filer importeres allerede i en anden fane.", + "retry_hint": "Importér de samme filer til samme mappe igen for at prøve dem, der mislykkedes, en gang til." }, "mail_export": { "menu_export": "Eksportér mappe…", diff --git a/locales/de/common.json b/locales/de/common.json index aaec06610..43ff4d62a 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "Diese Outlook-Datei nutzt eine hohe Verschlüsselung, die Bulwark nicht lesen kann. Exportieren Sie die E-Mails in Outlook in eine neue .pst-Datei ohne Verschlüsselung.", "pst_error_not_pst": "Diese Datei ist keine Outlook-Datendatei, die Bulwark lesen kann.", "pst_error_unreadable": "Bulwark konnte diese Outlook-Datei nicht lesen.", - "pst_error_worker": "Das Lesemodul für Outlook-Dateien wurde nicht geladen. Laden Sie die Seite neu und versuchen Sie es noch einmal." + "pst_error_worker": "Das Lesemodul für Outlook-Dateien wurde nicht geladen. Laden Sie die Seite neu und versuchen Sie es noch einmal.", + "error_over_quota": "Das Postfach ist voll, daher wurde der Import angehalten. Schaffen Sie Platz und importieren Sie dieselben Dateien erneut in denselben Ordner, um weiterzumachen.", + "error_forbidden": "Sie dürfen diesem Ordner keine E-Mails hinzufügen, daher wurde der Import angehalten.", + "error_failures": "Zu viele Nachrichten sind nacheinander fehlgeschlagen, daher wurde der Import angehalten. Importieren Sie dieselben Dateien später erneut in denselben Ordner, um weiterzumachen.", + "error_network": "Die Verbindung zum Server ist wiederholt fehlgeschlagen, daher wurde der Import angehalten. Importieren Sie dieselben Dateien erneut in denselben Ordner, um weiterzumachen.", + "error_busy": "Diese Dateien werden bereits in einem anderen Tab importiert.", + "retry_hint": "Importieren Sie dieselben Dateien erneut in denselben Ordner, um die fehlgeschlagenen noch einmal zu versuchen." }, "mail_export": { "menu_export": "Ordner exportieren…", diff --git a/locales/en/common.json b/locales/en/common.json index d3a9838e3..5ec102c54 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "This Outlook file uses high encryption, which Bulwark can't read. In Outlook, export the mail to a new .pst file without encryption.", "pst_error_not_pst": "This file isn't an Outlook data file Bulwark can read.", "pst_error_unreadable": "Bulwark couldn't read this Outlook file.", - "pst_error_worker": "The Outlook file reader didn't load. Reload the page and try again." + "pst_error_worker": "The Outlook file reader didn't load. Reload the page and try again.", + "error_over_quota": "The mailbox is full, so the import stopped. Make room and import the same files into the same folder again to go on.", + "error_forbidden": "You may not add mail to this folder, so the import stopped.", + "error_failures": "Too many messages failed in a row, so the import stopped. Import the same files into the same folder again later to go on.", + "error_network": "The connection to the server kept failing, so the import stopped. Import the same files into the same folder again to go on.", + "error_busy": "These files are being imported in another tab already.", + "retry_hint": "Import the same files into the same folder again to try the ones that failed once more." }, "mail_export": { "menu_export": "Export folder…", diff --git a/locales/es/common.json b/locales/es/common.json index 01daab3af..e18e98a31 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "Este archivo de Outlook usa cifrado alto, que Bulwark no puede leer. En Outlook, exporte el correo a un archivo .pst nuevo sin cifrado.", "pst_error_not_pst": "Este archivo no es un archivo de datos de Outlook que Bulwark pueda leer.", "pst_error_unreadable": "Bulwark no pudo leer este archivo de Outlook.", - "pst_error_worker": "El lector de archivos de Outlook no se cargó. Vuelva a cargar la página e inténtelo de nuevo." + "pst_error_worker": "El lector de archivos de Outlook no se cargó. Vuelva a cargar la página e inténtelo de nuevo.", + "error_over_quota": "El buzón está lleno, así que la importación se detuvo. Libere espacio e importe los mismos archivos en la misma carpeta otra vez para seguir.", + "error_forbidden": "No puede añadir correo a esta carpeta, así que la importación se detuvo.", + "error_failures": "Fallaron demasiados mensajes seguidos, así que la importación se detuvo. Importe los mismos archivos en la misma carpeta más tarde para seguir.", + "error_network": "La conexión con el servidor falló varias veces, así que la importación se detuvo. Importe los mismos archivos en la misma carpeta otra vez para seguir.", + "error_busy": "Estos archivos ya se están importando en otra pestaña.", + "retry_hint": "Importe los mismos archivos en la misma carpeta otra vez para reintentar los que fallaron." }, "mail_export": { "menu_export": "Exportar carpeta…", diff --git a/locales/et/common.json b/locales/et/common.json index b7ee46de0..08a6b11d9 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "See Outlooki fail kasutab tugevat krüpteerimist, mida Bulwark lugeda ei saa. Eksportige kirjad Outlookis uude krüpteerimata .pst-faili.", "pst_error_not_pst": "See fail pole Outlooki andmefail, mida Bulwark lugeda oskab.", "pst_error_unreadable": "Bulwark ei saanud seda Outlooki faili lugeda.", - "pst_error_worker": "Outlooki failide lugeja ei laadinud. Laadige leht uuesti ja proovige veel kord." + "pst_error_worker": "Outlooki failide lugeja ei laadinud. Laadige leht uuesti ja proovige veel kord.", + "error_over_quota": "Postkast on täis, seega import peatus. Tehke ruumi ja importige samad failid uuesti samasse kausta, et jätkata.", + "error_forbidden": "Sellesse kausta ei tohi te kirju lisada, seega import peatus.", + "error_failures": "Liiga palju kirju järjest ebaõnnestus, seega import peatus. Importige samad failid hiljem uuesti samasse kausta, et jätkata.", + "error_network": "Ühendus serveriga katkes korduvalt, seega import peatus. Importige samad failid uuesti samasse kausta, et jätkata.", + "error_busy": "Neid faile imporditakse juba teisel vahelehel.", + "retry_hint": "Importige samad failid uuesti samasse kausta, et ebaõnnestunud kirju veel kord proovida." }, "mail_export": { "menu_export": "Ekspordi kaust…", diff --git a/locales/fa/common.json b/locales/fa/common.json index d7b2fd4c0..66a6dbb30 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "این فایل اوت‌لوک از رمزگذاری قوی استفاده می‌کند که Bulwark نمی‌تواند بخواند. در اوت‌لوک، نامه‌ها را به یک فایل ‎.pst تازه بدون رمزگذاری صادر کنید.", "pst_error_not_pst": "این فایل، فایل داده اوت‌لوکی نیست که Bulwark بتواند بخواند.", "pst_error_unreadable": "Bulwark نتوانست این فایل اوت‌لوک را بخواند.", - "pst_error_worker": "خواننده فایل‌های اوت‌لوک بارگیری نشد. صفحه را دوباره بارگیری کنید و دوباره امتحان کنید." + "pst_error_worker": "خواننده فایل‌های اوت‌لوک بارگیری نشد. صفحه را دوباره بارگیری کنید و دوباره امتحان کنید.", + "error_over_quota": "صندوق پستی پر است، بنابراین درون‌ریزی متوقف شد. جا باز کنید و برای ادامه، همان فایل‌ها را دوباره در همان پوشه درون‌ریزی کنید.", + "error_forbidden": "اجازه ندارید به این پوشه نامه اضافه کنید، بنابراین درون‌ریزی متوقف شد.", + "error_failures": "پیام‌های زیادی پشت سر هم ناموفق بودند، بنابراین درون‌ریزی متوقف شد. برای ادامه، بعداً همان فایل‌ها را دوباره در همان پوشه درون‌ریزی کنید.", + "error_network": "اتصال به سرور چند بار قطع شد، بنابراین درون‌ریزی متوقف شد. برای ادامه، همان فایل‌ها را دوباره در همان پوشه درون‌ریزی کنید.", + "error_busy": "این فایل‌ها هم‌اکنون در زبانه دیگری درون‌ریزی می‌شوند.", + "retry_hint": "برای امتحان دوباره پیام‌های ناموفق، همان فایل‌ها را دوباره در همان پوشه درون‌ریزی کنید." }, "mail_export": { "menu_export": "برون‌بری پوشه…", diff --git a/locales/fr/common.json b/locales/fr/common.json index 2e390f58d..b1169e59d 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "Ce fichier Outlook utilise un chiffrement élevé que Bulwark ne sait pas lire. Dans Outlook, exportez les e-mails dans un nouveau fichier .pst sans chiffrement.", "pst_error_not_pst": "Ce fichier n'est pas un fichier de données Outlook que Bulwark sait lire.", "pst_error_unreadable": "Bulwark n'a pas pu lire ce fichier Outlook.", - "pst_error_worker": "Le lecteur de fichiers Outlook ne s'est pas chargé. Rechargez la page et réessayez." + "pst_error_worker": "Le lecteur de fichiers Outlook ne s'est pas chargé. Rechargez la page et réessayez.", + "error_over_quota": "La boîte aux lettres est pleine, l'import s'est donc arrêté. Libérez de la place et importez à nouveau les mêmes fichiers dans le même dossier pour reprendre.", + "error_forbidden": "Vous ne pouvez pas ajouter de messages à ce dossier, l'import s'est donc arrêté.", + "error_failures": "Trop de messages ont échoué à la suite, l'import s'est donc arrêté. Importez à nouveau les mêmes fichiers dans le même dossier plus tard pour reprendre.", + "error_network": "La connexion au serveur a échoué plusieurs fois, l'import s'est donc arrêté. Importez à nouveau les mêmes fichiers dans le même dossier pour reprendre.", + "error_busy": "Ces fichiers sont déjà en cours d'import dans un autre onglet.", + "retry_hint": "Importez à nouveau les mêmes fichiers dans le même dossier pour réessayer ceux qui ont échoué." }, "mail_export": { "menu_export": "Exporter le dossier…", diff --git a/locales/he/common.json b/locales/he/common.json index 10f957f4c..2d16c53ca 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -5434,7 +5434,13 @@ "pst_error_encrypted": "קובץ Outlook זה משתמש בהצפנה גבוהה ש-Bulwark אינו יכול לקרוא. ב-Outlook, ייצאו את הדואר לקובץ ‎.pst חדש ללא הצפנה.", "pst_error_not_pst": "קובץ זה אינו קובץ נתונים של Outlook ש-Bulwark יכול לקרוא.", "pst_error_unreadable": "Bulwark לא הצליח לקרוא את קובץ ה-Outlook הזה.", - "pst_error_worker": "קורא קובצי Outlook לא נטען. טענו את הדף מחדש ונסו שוב." + "pst_error_worker": "קורא קובצי Outlook לא נטען. טענו את הדף מחדש ונסו שוב.", + "error_over_quota": "תיבת הדואר מלאה, ולכן הייבוא נעצר. פנו מקום וייבאו שוב את אותם קבצים לאותה תיקייה כדי להמשיך.", + "error_forbidden": "אינכם רשאים להוסיף דואר לתיקייה זו, ולכן הייבוא נעצר.", + "error_failures": "יותר מדי הודעות ברצף נכשלו, ולכן הייבוא נעצר. ייבאו שוב מאוחר יותר את אותם קבצים לאותה תיקייה כדי להמשיך.", + "error_network": "החיבור לשרת נכשל שוב ושוב, ולכן הייבוא נעצר. ייבאו שוב את אותם קבצים לאותה תיקייה כדי להמשיך.", + "error_busy": "הקבצים האלה כבר מיובאים בכרטיסייה אחרת.", + "retry_hint": "ייבאו שוב את אותם קבצים לאותה תיקייה כדי לנסות שוב את אלה שנכשלו." }, "mail_export": { "menu_export": "ייצוא תיקייה…", diff --git a/locales/hu/common.json b/locales/hu/common.json index 31751e0f6..5b8daff55 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "Ez az Outlook-fájl erős titkosítást használ, amelyet a Bulwark nem tud olvasni. Az Outlookban exportálja a leveleket egy új, titkosítás nélküli .pst fájlba.", "pst_error_not_pst": "Ez a fájl nem olyan Outlook-adatfájl, amelyet a Bulwark olvasni tud.", "pst_error_unreadable": "A Bulwark nem tudta beolvasni ezt az Outlook-fájlt.", - "pst_error_worker": "Az Outlook-fájlok olvasója nem töltődött be. Töltse újra az oldalt, és próbálja meg ismét." + "pst_error_worker": "Az Outlook-fájlok olvasója nem töltődött be. Töltse újra az oldalt, és próbálja meg ismét.", + "error_over_quota": "A postafiók megtelt, ezért az importálás leállt. Szabadítson fel helyet, és importálja újra ugyanazokat a fájlokat ugyanabba a mappába a folytatáshoz.", + "error_forbidden": "Ebbe a mappába nem adhat hozzá leveleket, ezért az importálás leállt.", + "error_failures": "Túl sok üzenet hiúsult meg egymás után, ezért az importálás leállt. Később importálja újra ugyanazokat a fájlokat ugyanabba a mappába a folytatáshoz.", + "error_network": "A kapcsolat a szerverrel többször megszakadt, ezért az importálás leállt. Importálja újra ugyanazokat a fájlokat ugyanabba a mappába a folytatáshoz.", + "error_busy": "Ezeket a fájlokat már importálja egy másik lap.", + "retry_hint": "Importálja újra ugyanazokat a fájlokat ugyanabba a mappába, hogy a sikertelenekkel újra próbálkozzon." }, "mail_export": { "menu_export": "Mappa exportálása…", diff --git a/locales/it/common.json b/locales/it/common.json index 9c3513f4e..9a80d3a0f 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "Questo file di Outlook usa la crittografia elevata, che Bulwark non sa leggere. In Outlook, esporta la posta in un nuovo file .pst senza crittografia.", "pst_error_not_pst": "Questo file non è un file di dati di Outlook che Bulwark sa leggere.", "pst_error_unreadable": "Bulwark non è riuscito a leggere questo file di Outlook.", - "pst_error_worker": "Il lettore dei file di Outlook non si è caricato. Ricarica la pagina e riprova." + "pst_error_worker": "Il lettore dei file di Outlook non si è caricato. Ricarica la pagina e riprova.", + "error_over_quota": "La casella è piena, quindi l'importazione si è fermata. Libera spazio e importa di nuovo gli stessi file nella stessa cartella per continuare.", + "error_forbidden": "Non puoi aggiungere posta a questa cartella, quindi l'importazione si è fermata.", + "error_failures": "Troppi messaggi di fila non sono riusciti, quindi l'importazione si è fermata. Importa di nuovo gli stessi file nella stessa cartella più tardi per continuare.", + "error_network": "La connessione al server non è riuscita più volte, quindi l'importazione si è fermata. Importa di nuovo gli stessi file nella stessa cartella per continuare.", + "error_busy": "Questi file sono già in importazione in un'altra scheda.", + "retry_hint": "Importa di nuovo gli stessi file nella stessa cartella per riprovare quelli non riusciti." }, "mail_export": { "menu_export": "Esporta cartella…", diff --git a/locales/ja/common.json b/locales/ja/common.json index 5197b6c54..357bb8cdd 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "この Outlook ファイルは Bulwark が読めない高度な暗号化を使っています。Outlook でメールを暗号化なしの新しい .pst ファイルにエクスポートしてください。", "pst_error_not_pst": "このファイルは Bulwark が読める Outlook データファイルではありません。", "pst_error_unreadable": "Bulwark はこの Outlook ファイルを読めませんでした。", - "pst_error_worker": "Outlook ファイルの読み込み機能を読み込めませんでした。ページを再読み込みして、もう一度お試しください。" + "pst_error_worker": "Outlook ファイルの読み込み機能を読み込めませんでした。ページを再読み込みして、もう一度お試しください。", + "error_over_quota": "メールボックスがいっぱいのため、インポートを停止しました。空きを作ってから、同じファイルを同じフォルダーにもう一度インポートすると続きから再開します。", + "error_forbidden": "このフォルダーにはメールを追加できないため、インポートを停止しました。", + "error_failures": "続けて失敗したメッセージが多すぎるため、インポートを停止しました。後で同じファイルを同じフォルダーにもう一度インポートすると続きから再開します。", + "error_network": "サーバーへの接続が何度も失敗したため、インポートを停止しました。同じファイルを同じフォルダーにもう一度インポートすると続きから再開します。", + "error_busy": "これらのファイルは別のタブですでにインポート中です。", + "retry_hint": "失敗したものをもう一度試すには、同じファイルを同じフォルダーにもう一度インポートしてください。" }, "mail_export": { "menu_export": "フォルダーをエクスポート…", diff --git a/locales/ko/common.json b/locales/ko/common.json index 62c662d2e..8df89afd8 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "이 Outlook 파일은 Bulwark가 읽을 수 없는 강력한 암호화를 사용합니다. Outlook에서 메일을 암호화하지 않은 새 .pst 파일로 내보내세요.", "pst_error_not_pst": "이 파일은 Bulwark가 읽을 수 있는 Outlook 데이터 파일이 아닙니다.", "pst_error_unreadable": "Bulwark가 이 Outlook 파일을 읽지 못했습니다.", - "pst_error_worker": "Outlook 파일 읽기 도구를 불러오지 못했습니다. 페이지를 새로 고친 뒤 다시 시도하세요." + "pst_error_worker": "Outlook 파일 읽기 도구를 불러오지 못했습니다. 페이지를 새로 고친 뒤 다시 시도하세요.", + "error_over_quota": "사서함이 가득 차서 가져오기를 멈췄습니다. 공간을 확보한 뒤 같은 파일을 같은 폴더로 다시 가져오면 이어서 진행합니다.", + "error_forbidden": "이 폴더에는 메일을 추가할 수 없어서 가져오기를 멈췄습니다.", + "error_failures": "연속으로 실패한 메시지가 너무 많아 가져오기를 멈췄습니다. 나중에 같은 파일을 같은 폴더로 다시 가져오면 이어서 진행합니다.", + "error_network": "서버 연결이 계속 실패해 가져오기를 멈췄습니다. 같은 파일을 같은 폴더로 다시 가져오면 이어서 진행합니다.", + "error_busy": "이 파일은 이미 다른 탭에서 가져오는 중입니다.", + "retry_hint": "실패한 메시지를 다시 시도하려면 같은 파일을 같은 폴더로 다시 가져오세요." }, "mail_export": { "menu_export": "폴더 내보내기…", diff --git a/locales/lv/common.json b/locales/lv/common.json index 036bc7876..0c117a2ca 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "Šis Outlook fails izmanto augstu šifrēšanu, ko Bulwark nevar nolasīt. Programmā Outlook eksportējiet pastu jaunā .pst failā bez šifrēšanas.", "pst_error_not_pst": "Šis fails nav Outlook datu fails, ko Bulwark var nolasīt.", "pst_error_unreadable": "Bulwark nevarēja nolasīt šo Outlook failu.", - "pst_error_worker": "Outlook failu lasītājs netika ielādēts. Pārlādējiet lapu un mēģiniet vēlreiz." + "pst_error_worker": "Outlook failu lasītājs netika ielādēts. Pārlādējiet lapu un mēģiniet vēlreiz.", + "error_over_quota": "Pastkaste ir pilna, tāpēc importēšana apstājās. Atbrīvojiet vietu un vēlreiz importējiet tos pašus failus tajā pašā mapē, lai turpinātu.", + "error_forbidden": "Šajā mapē nedrīkstat pievienot pastu, tāpēc importēšana apstājās.", + "error_failures": "Pārāk daudz ziņojumu pēc kārtas neizdevās, tāpēc importēšana apstājās. Vēlāk vēlreiz importējiet tos pašus failus tajā pašā mapē, lai turpinātu.", + "error_network": "Savienojums ar serveri atkārtoti neizdevās, tāpēc importēšana apstājās. Vēlreiz importējiet tos pašus failus tajā pašā mapē, lai turpinātu.", + "error_busy": "Šie faili jau tiek importēti citā cilnē.", + "retry_hint": "Vēlreiz importējiet tos pašus failus tajā pašā mapē, lai vēlreiz mēģinātu neizdevušos." }, "mail_export": { "menu_export": "Eksportēt mapi…", diff --git a/locales/mn/common.json b/locales/mn/common.json index c9966e04e..f3c1ef0b6 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "Энэ Outlook файл Bulwark уншиж чадахгүй хүчтэй шифрлэлт ашигладаг. Outlook-д шуудангаа шифрлэлтгүй шинэ .pst файл руу экспортлоно уу.", "pst_error_not_pst": "Энэ файл Bulwark уншиж чадах Outlook-ийн өгөгдлийн файл биш байна.", "pst_error_unreadable": "Bulwark энэ Outlook файлыг уншиж чадсангүй.", - "pst_error_worker": "Outlook файл уншигч ачаалагдсангүй. Хуудсыг дахин ачаалаад дахин оролдоно уу." + "pst_error_worker": "Outlook файл уншигч ачаалагдсангүй. Хуудсыг дахин ачаалаад дахин оролдоно уу.", + "error_over_quota": "Шуудангийн хайрцаг дүүрсэн тул импорт зогслоо. Зай гаргаад үргэлжлүүлэхийн тулд ижил файлуудыг ижил хавтас руу дахин импортлоно уу.", + "error_forbidden": "Та энэ хавтсанд шуудан нэмэх эрхгүй тул импорт зогслоо.", + "error_failures": "Дараалан хэт олон захидал амжилтгүй болсон тул импорт зогслоо. Үргэлжлүүлэхийн тулд дараа нь ижил файлуудыг ижил хавтас руу дахин импортлоно уу.", + "error_network": "Сервертэй холболт удаа дараа тасарсан тул импорт зогслоо. Үргэлжлүүлэхийн тулд ижил файлуудыг ижил хавтас руу дахин импортлоно уу.", + "error_busy": "Эдгээр файлыг өөр табд аль хэдийн импортлож байна.", + "retry_hint": "Амжилтгүй болсон захидлуудыг дахин оролдохын тулд ижил файлуудыг ижил хавтас руу дахин импортлоно уу." }, "mail_export": { "menu_export": "Хавтас экспортлох…", diff --git a/locales/nb/common.json b/locales/nb/common.json index 1972509bd..d1bb21c19 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "Denne Outlook-filen bruker høy kryptering, som Bulwark ikke kan lese. Eksporter e-posten i Outlook til en ny .pst-fil uten kryptering.", "pst_error_not_pst": "Denne filen er ikke en Outlook-datafil som Bulwark kan lese.", "pst_error_unreadable": "Bulwark kunne ikke lese denne Outlook-filen.", - "pst_error_worker": "Leseren for Outlook-filer ble ikke lastet. Last inn siden på nytt og prøv igjen." + "pst_error_worker": "Leseren for Outlook-filer ble ikke lastet. Last inn siden på nytt og prøv igjen.", + "error_over_quota": "Postkassen er full, så importen stoppet. Frigjør plass og importer de samme filene til samme mappe igjen for å fortsette.", + "error_forbidden": "Du kan ikke legge e-post i denne mappen, så importen stoppet.", + "error_failures": "For mange meldinger på rad mislyktes, så importen stoppet. Importer de samme filene til samme mappe igjen senere for å fortsette.", + "error_network": "Tilkoblingen til serveren mislyktes gang på gang, så importen stoppet. Importer de samme filene til samme mappe igjen for å fortsette.", + "error_busy": "Disse filene importeres allerede i en annen fane.", + "retry_hint": "Importer de samme filene til samme mappe igjen for å prøve de som mislyktes, på nytt." }, "mail_export": { "menu_export": "Eksporter mappe…", diff --git a/locales/nl/common.json b/locales/nl/common.json index f909eb143..c60b4c431 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "Dit Outlook-bestand gebruikt sterke versleuteling, die Bulwark niet kan lezen. Exporteer de e-mail in Outlook naar een nieuw .pst-bestand zonder versleuteling.", "pst_error_not_pst": "Dit bestand is geen Outlook-gegevensbestand dat Bulwark kan lezen.", "pst_error_unreadable": "Bulwark kon dit Outlook-bestand niet lezen.", - "pst_error_worker": "De lezer voor Outlook-bestanden is niet geladen. Laad de pagina opnieuw en probeer het nog eens." + "pst_error_worker": "De lezer voor Outlook-bestanden is niet geladen. Laad de pagina opnieuw en probeer het nog eens.", + "error_over_quota": "De mailbox is vol, dus de import is gestopt. Maak ruimte en importeer dezelfde bestanden opnieuw in dezelfde map om verder te gaan.", + "error_forbidden": "U mag geen e-mail aan deze map toevoegen, dus de import is gestopt.", + "error_failures": "Te veel berichten na elkaar zijn mislukt, dus de import is gestopt. Importeer dezelfde bestanden later opnieuw in dezelfde map om verder te gaan.", + "error_network": "De verbinding met de server mislukte steeds, dus de import is gestopt. Importeer dezelfde bestanden opnieuw in dezelfde map om verder te gaan.", + "error_busy": "Deze bestanden worden al in een ander tabblad geïmporteerd.", + "retry_hint": "Importeer dezelfde bestanden opnieuw in dezelfde map om de mislukte nog eens te proberen." }, "mail_export": { "menu_export": "Map exporteren…", diff --git a/locales/pl/common.json b/locales/pl/common.json index f81146884..99d7ea5f0 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "Ten plik programu Outlook używa silnego szyfrowania, którego Bulwark nie potrafi odczytać. W programie Outlook wyeksportuj pocztę do nowego pliku .pst bez szyfrowania.", "pst_error_not_pst": "Ten plik nie jest plikiem danych programu Outlook, który Bulwark potrafi odczytać.", "pst_error_unreadable": "Bulwark nie mógł odczytać tego pliku programu Outlook.", - "pst_error_worker": "Czytnik plików programu Outlook się nie wczytał. Odśwież stronę i spróbuj ponownie." + "pst_error_worker": "Czytnik plików programu Outlook się nie wczytał. Odśwież stronę i spróbuj ponownie.", + "error_over_quota": "Skrzynka jest pełna, więc import się zatrzymał. Zwolnij miejsce i zaimportuj te same pliki ponownie do tego samego folderu, aby kontynuować.", + "error_forbidden": "Nie możesz dodawać poczty do tego folderu, więc import się zatrzymał.", + "error_failures": "Zbyt wiele wiadomości z rzędu się nie udało, więc import się zatrzymał. Zaimportuj te same pliki później ponownie do tego samego folderu, aby kontynuować.", + "error_network": "Połączenie z serwerem wielokrotnie zawiodło, więc import się zatrzymał. Zaimportuj te same pliki ponownie do tego samego folderu, aby kontynuować.", + "error_busy": "Te pliki są już importowane w innej karcie.", + "retry_hint": "Zaimportuj te same pliki ponownie do tego samego folderu, aby jeszcze raz spróbować tych, które się nie udały." }, "mail_export": { "menu_export": "Eksportuj folder…", diff --git a/locales/pt/common.json b/locales/pt/common.json index 8c0dcba2f..9c0f43c32 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "Este arquivo do Outlook usa criptografia alta, que o Bulwark não consegue ler. No Outlook, exporte os e-mails para um novo arquivo .pst sem criptografia.", "pst_error_not_pst": "Este arquivo não é um arquivo de dados do Outlook que o Bulwark consiga ler.", "pst_error_unreadable": "O Bulwark não conseguiu ler este arquivo do Outlook.", - "pst_error_worker": "O leitor de arquivos do Outlook não carregou. Recarregue a página e tente de novo." + "pst_error_worker": "O leitor de arquivos do Outlook não carregou. Recarregue a página e tente de novo.", + "error_over_quota": "A caixa de correio está cheia, por isso a importação parou. Libere espaço e importe os mesmos arquivos na mesma pasta outra vez para continuar.", + "error_forbidden": "Você não pode adicionar e-mails a esta pasta, por isso a importação parou.", + "error_failures": "Muitas mensagens falharam seguidas, por isso a importação parou. Importe os mesmos arquivos na mesma pasta mais tarde para continuar.", + "error_network": "A conexão com o servidor falhou várias vezes, por isso a importação parou. Importe os mesmos arquivos na mesma pasta outra vez para continuar.", + "error_busy": "Estes arquivos já estão sendo importados em outra aba.", + "retry_hint": "Importe os mesmos arquivos na mesma pasta outra vez para tentar de novo os que falharam." }, "mail_export": { "menu_export": "Exportar pasta…", diff --git a/locales/ro/common.json b/locales/ro/common.json index 37d56aa42..285eb455f 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "Acest fișier Outlook folosește criptare puternică, pe care Bulwark nu o poate citi. În Outlook, exportați e-mailurile într-un fișier .pst nou, fără criptare.", "pst_error_not_pst": "Acest fișier nu este un fișier de date Outlook pe care Bulwark îl poate citi.", "pst_error_unreadable": "Bulwark nu a putut citi acest fișier Outlook.", - "pst_error_worker": "Cititorul de fișiere Outlook nu s-a încărcat. Reîncărcați pagina și încercați din nou." + "pst_error_worker": "Cititorul de fișiere Outlook nu s-a încărcat. Reîncărcați pagina și încercați din nou.", + "error_over_quota": "Căsuța poștală este plină, așa că importul s-a oprit. Faceți loc și importați din nou aceleași fișiere în același dosar pentru a continua.", + "error_forbidden": "Nu puteți adăuga e-mailuri în acest dosar, așa că importul s-a oprit.", + "error_failures": "Prea multe mesaje la rând au eșuat, așa că importul s-a oprit. Importați mai târziu aceleași fișiere în același dosar pentru a continua.", + "error_network": "Conexiunea la server a eșuat de mai multe ori, așa că importul s-a oprit. Importați din nou aceleași fișiere în același dosar pentru a continua.", + "error_busy": "Aceste fișiere se importă deja în altă filă.", + "retry_hint": "Importați din nou aceleași fișiere în același dosar pentru a reîncerca mesajele care au eșuat." }, "mail_export": { "menu_export": "Exportă dosarul…", diff --git a/locales/ru/common.json b/locales/ru/common.json index 7f60a3f8d..60d2080a4 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "В этом файле Outlook используется сильное шифрование, которое Bulwark не может прочитать. Экспортируйте почту в Outlook в новый файл .pst без шифрования.", "pst_error_not_pst": "Этот файл не является файлом данных Outlook, который Bulwark может прочитать.", "pst_error_unreadable": "Bulwark не смог прочитать этот файл Outlook.", - "pst_error_worker": "Модуль чтения файлов Outlook не загрузился. Перезагрузите страницу и попробуйте снова." + "pst_error_worker": "Модуль чтения файлов Outlook не загрузился. Перезагрузите страницу и попробуйте снова.", + "error_over_quota": "Почтовый ящик заполнен, поэтому импорт остановлен. Освободите место и снова импортируйте те же файлы в ту же папку, чтобы продолжить.", + "error_forbidden": "Вы не можете добавлять почту в эту папку, поэтому импорт остановлен.", + "error_failures": "Слишком много сообщений подряд не удалось импортировать, поэтому импорт остановлен. Позже снова импортируйте те же файлы в ту же папку, чтобы продолжить.", + "error_network": "Соединение с сервером несколько раз прерывалось, поэтому импорт остановлен. Снова импортируйте те же файлы в ту же папку, чтобы продолжить.", + "error_busy": "Эти файлы уже импортируются в другой вкладке.", + "retry_hint": "Снова импортируйте те же файлы в ту же папку, чтобы ещё раз попробовать неудавшиеся." }, "mail_export": { "menu_export": "Экспортировать папку…", diff --git a/locales/sk/common.json b/locales/sk/common.json index 12be59cda..56dd2f3fb 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "Tento súbor Outlooku používa vysoké šifrovanie, ktoré Bulwark nevie prečítať. V Outlooku exportujte poštu do nového súboru .pst bez šifrovania.", "pst_error_not_pst": "Tento súbor nie je dátový súbor Outlooku, ktorý by Bulwark vedel prečítať.", "pst_error_unreadable": "Bulwark nedokázal prečítať tento súbor Outlooku.", - "pst_error_worker": "Čítačka súborov Outlooku sa nenačítala. Načítajte stránku znova a skúste to ešte raz." + "pst_error_worker": "Čítačka súborov Outlooku sa nenačítala. Načítajte stránku znova a skúste to ešte raz.", + "error_over_quota": "Schránka je plná, preto sa import zastavil. Uvoľnite miesto a importujte rovnaké súbory znova do rovnakého priečinka, aby ste pokračovali.", + "error_forbidden": "Do tohto priečinka nesmiete pridávať poštu, preto sa import zastavil.", + "error_failures": "Príliš veľa správ za sebou zlyhalo, preto sa import zastavil. Importujte rovnaké súbory neskôr znova do rovnakého priečinka, aby ste pokračovali.", + "error_network": "Spojenie so serverom opakovane zlyhalo, preto sa import zastavil. Importujte rovnaké súbory znova do rovnakého priečinka, aby ste pokračovali.", + "error_busy": "Tieto súbory sa už importujú na inej karte.", + "retry_hint": "Importujte rovnaké súbory znova do rovnakého priečinka, aby ste skúsili neúspešné ešte raz." }, "mail_export": { "menu_export": "Exportovať priečinok…", diff --git a/locales/th/common.json b/locales/th/common.json index 78b393c56..603c2bc09 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -450,7 +450,13 @@ "pst_error_encrypted": "ไฟล์ Outlook นี้ใช้การเข้ารหัสระดับสูงที่ Bulwark อ่านไม่ได้ ใน Outlook ให้ส่งออกอีเมลไปยังไฟล์ .pst ใหม่ที่ไม่เข้ารหัส", "pst_error_not_pst": "ไฟล์นี้ไม่ใช่ไฟล์ข้อมูล Outlook ที่ Bulwark อ่านได้", "pst_error_unreadable": "Bulwark อ่านไฟล์ Outlook นี้ไม่ได้", - "pst_error_worker": "ตัวอ่านไฟล์ Outlook โหลดไม่สำเร็จ โหลดหน้าใหม่แล้วลองอีกครั้ง" + "pst_error_worker": "ตัวอ่านไฟล์ Outlook โหลดไม่สำเร็จ โหลดหน้าใหม่แล้วลองอีกครั้ง", + "error_over_quota": "กล่องจดหมายเต็ม การนำเข้าจึงหยุดลง เคลียร์พื้นที่แล้วนำเข้าไฟล์เดิมไปยังโฟลเดอร์เดิมอีกครั้งเพื่อทำต่อ", + "error_forbidden": "คุณไม่มีสิทธิ์เพิ่มอีเมลในโฟลเดอร์นี้ การนำเข้าจึงหยุดลง", + "error_failures": "ข้อความล้มเหลวติดต่อกันมากเกินไป การนำเข้าจึงหยุดลง นำเข้าไฟล์เดิมไปยังโฟลเดอร์เดิมอีกครั้งภายหลังเพื่อทำต่อ", + "error_network": "การเชื่อมต่อกับเซิร์ฟเวอร์ล้มเหลวซ้ำๆ การนำเข้าจึงหยุดลง นำเข้าไฟล์เดิมไปยังโฟลเดอร์เดิมอีกครั้งเพื่อทำต่อ", + "error_busy": "ไฟล์เหล่านี้กำลังถูกนำเข้าในอีกแท็บหนึ่งอยู่แล้ว", + "retry_hint": "นำเข้าไฟล์เดิมไปยังโฟลเดอร์เดิมอีกครั้งเพื่อลองข้อความที่ล้มเหลวอีกรอบ" }, "mail_export": { "menu_export": "ส่งออกโฟลเดอร์…", diff --git a/locales/tr/common.json b/locales/tr/common.json index 4fccdc43a..227075d45 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "Bu Outlook dosyası Bulwark'ın okuyamadığı yüksek şifreleme kullanıyor. Outlook'ta e-postaları şifrelemesiz yeni bir .pst dosyasına aktarın.", "pst_error_not_pst": "Bu dosya Bulwark'ın okuyabildiği bir Outlook veri dosyası değil.", "pst_error_unreadable": "Bulwark bu Outlook dosyasını okuyamadı.", - "pst_error_worker": "Outlook dosya okuyucusu yüklenmedi. Sayfayı yeniden yükleyip tekrar deneyin." + "pst_error_worker": "Outlook dosya okuyucusu yüklenmedi. Sayfayı yeniden yükleyip tekrar deneyin.", + "error_over_quota": "Posta kutusu dolu olduğundan içe aktarım durdu. Yer açın ve devam etmek için aynı dosyaları aynı klasöre yeniden içe aktarın.", + "error_forbidden": "Bu klasöre e-posta ekleyemezsiniz, bu yüzden içe aktarım durdu.", + "error_failures": "Arka arkaya çok fazla ileti başarısız olduğundan içe aktarım durdu. Devam etmek için aynı dosyaları daha sonra aynı klasöre yeniden içe aktarın.", + "error_network": "Sunucuya bağlantı tekrar tekrar başarısız olduğundan içe aktarım durdu. Devam etmek için aynı dosyaları aynı klasöre yeniden içe aktarın.", + "error_busy": "Bu dosyalar zaten başka bir sekmede içe aktarılıyor.", + "retry_hint": "Başarısız olanları bir kez daha denemek için aynı dosyaları aynı klasöre yeniden içe aktarın." }, "mail_export": { "menu_export": "Klasörü dışa aktar…", diff --git a/locales/uk/common.json b/locales/uk/common.json index 001447b72..904ebfec3 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "У цьому файлі Outlook використано сильне шифрування, яке Bulwark не може прочитати. Експортуйте пошту в Outlook до нового файлу .pst без шифрування.", "pst_error_not_pst": "Цей файл не є файлом даних Outlook, який Bulwark може прочитати.", "pst_error_unreadable": "Bulwark не зміг прочитати цей файл Outlook.", - "pst_error_worker": "Модуль читання файлів Outlook не завантажився. Перезавантажте сторінку й спробуйте ще раз." + "pst_error_worker": "Модуль читання файлів Outlook не завантажився. Перезавантажте сторінку й спробуйте ще раз.", + "error_over_quota": "Поштову скриньку заповнено, тому імпорт зупинено. Звільніть місце й знову імпортуйте ті самі файли до тієї самої теки, щоб продовжити.", + "error_forbidden": "Ви не можете додавати пошту до цієї теки, тому імпорт зупинено.", + "error_failures": "Забагато повідомлень поспіль не вдалося імпортувати, тому імпорт зупинено. Пізніше знову імпортуйте ті самі файли до тієї самої теки, щоб продовжити.", + "error_network": "З'єднання із сервером кілька разів переривалося, тому імпорт зупинено. Знову імпортуйте ті самі файли до тієї самої теки, щоб продовжити.", + "error_busy": "Ці файли вже імпортуються в іншій вкладці.", + "retry_hint": "Знову імпортуйте ті самі файли до тієї самої теки, щоб ще раз спробувати невдалі." }, "mail_export": { "menu_export": "Експортувати теку…", diff --git a/locales/vi/common.json b/locales/vi/common.json index 15849883d..eb1c3aef7 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -5445,7 +5445,13 @@ "pst_error_encrypted": "Tệp Outlook này dùng mã hóa mạnh mà Bulwark không đọc được. Trong Outlook, hãy xuất thư sang một tệp .pst mới không mã hóa.", "pst_error_not_pst": "Tệp này không phải tệp dữ liệu Outlook mà Bulwark đọc được.", "pst_error_unreadable": "Bulwark không đọc được tệp Outlook này.", - "pst_error_worker": "Không tải được trình đọc tệp Outlook. Hãy tải lại trang rồi thử lại." + "pst_error_worker": "Không tải được trình đọc tệp Outlook. Hãy tải lại trang rồi thử lại.", + "error_over_quota": "Hộp thư đã đầy nên việc nhập đã dừng. Hãy giải phóng dung lượng rồi nhập lại cùng các tệp vào cùng thư mục để tiếp tục.", + "error_forbidden": "Bạn không được thêm thư vào thư mục này nên việc nhập đã dừng.", + "error_failures": "Quá nhiều thư liên tiếp không nhập được nên việc nhập đã dừng. Hãy nhập lại cùng các tệp vào cùng thư mục sau để tiếp tục.", + "error_network": "Kết nối tới máy chủ liên tục thất bại nên việc nhập đã dừng. Hãy nhập lại cùng các tệp vào cùng thư mục để tiếp tục.", + "error_busy": "Các tệp này đang được nhập ở một thẻ khác.", + "retry_hint": "Nhập lại cùng các tệp vào cùng thư mục để thử lại những thư không nhập được." }, "mail_export": { "menu_export": "Xuất thư mục…", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 00899c022..35258a250 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -5433,7 +5433,13 @@ "pst_error_encrypted": "此 Outlook 檔案使用了 Bulwark 無法讀取的高強度加密。請在 Outlook 中把郵件匯出到一個不加密的新 .pst 檔案。", "pst_error_not_pst": "此檔案不是 Bulwark 能讀取的 Outlook 資料檔。", "pst_error_unreadable": "Bulwark 無法讀取此 Outlook 檔案。", - "pst_error_worker": "Outlook 檔案讀取元件未能載入。請重新載入頁面後再試。" + "pst_error_worker": "Outlook 檔案讀取元件未能載入。請重新載入頁面後再試。", + "error_over_quota": "信箱已滿,匯入已停止。請騰出空間,然後把相同的檔案再次匯入同一個資料夾即可繼續。", + "error_forbidden": "您無權在此資料夾新增郵件,匯入已停止。", + "error_failures": "連續失敗的郵件太多,匯入已停止。請稍後把相同的檔案再次匯入同一個資料夾以繼續。", + "error_network": "與伺服器的連線多次失敗,匯入已停止。請把相同的檔案再次匯入同一個資料夾以繼續。", + "error_busy": "這些檔案已在另一個分頁中匯入。", + "retry_hint": "把相同的檔案再次匯入同一個資料夾,即可重試失敗的郵件。" }, "mail_export": { "menu_export": "匯出資料夾…", diff --git a/locales/zh/common.json b/locales/zh/common.json index e2a334b5d..3686dea1c 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -5303,7 +5303,13 @@ "pst_error_encrypted": "此 Outlook 文件使用了 Bulwark 无法读取的高强度加密。请在 Outlook 中把邮件导出到一个不加密的新 .pst 文件。", "pst_error_not_pst": "此文件不是 Bulwark 能读取的 Outlook 数据文件。", "pst_error_unreadable": "Bulwark 无法读取此 Outlook 文件。", - "pst_error_worker": "Outlook 文件读取组件未能加载。请重新加载页面后再试。" + "pst_error_worker": "Outlook 文件读取组件未能加载。请重新加载页面后再试。", + "error_over_quota": "邮箱已满,导入已停止。请腾出空间,然后把相同的文件再次导入同一个文件夹即可继续。", + "error_forbidden": "您无权向此文件夹添加邮件,导入已停止。", + "error_failures": "连续失败的邮件太多,导入已停止。请稍后把相同的文件再次导入同一个文件夹以继续。", + "error_network": "与服务器的连接多次失败,导入已停止。请把相同的文件再次导入同一个文件夹以继续。", + "error_busy": "这些文件已在另一个标签页中导入。", + "retry_hint": "把相同的文件再次导入同一个文件夹,即可重试失败的邮件。" }, "mail_export": { "menu_export": "导出文件夹…", diff --git a/stores/mail-transfer-store.ts b/stores/mail-transfer-store.ts index 8514b2485..dba14d677 100644 --- a/stores/mail-transfer-store.ts +++ b/stores/mail-transfer-store.ts @@ -23,9 +23,17 @@ export interface ExportRequest { export type TransferRequest = ImportRequest | ExportRequest; +interface JobBase { + controller: AbortController; + /** The Bulwark account that runs it: its sign-out stops it. */ + scope?: string; + /** Settles when the job has ended. */ + finished?: Promise; +} + export type TransferJob = - | { kind: "import"; progress: ImportProgress; controller: AbortController } - | { kind: "export"; progress: ExportProgress; controller: AbortController }; + | (JobBase & { kind: "import"; progress: ImportProgress }) + | (JobBase & { kind: "export"; progress: ExportProgress }); interface MailTransferState { request: TransferRequest | null; @@ -40,7 +48,8 @@ interface MailTransferState { close: () => void; startJob: (job: TransferJob) => void; updateProgress: (progress: ImportProgress | ExportProgress) => void; - cancelJob: () => void; + /** `reason` "signout": the account signs out, so nothing more is written for it. */ + cancelJob: (reason?: string) => void; } export function isJobActive(job: TransferJob | null): boolean { @@ -79,7 +88,7 @@ export const useMailTransferStore = create()((set, get) => ({ const job = get().job; if (job) set({ job: { ...job, progress } as TransferJob }); }, - cancelJob: () => get().job?.controller.abort(), + cancelJob: (reason) => get().job?.controller.abort(reason), })); /** Opens the import dialog, for a folder or (from Settings) for none. */ From d9b62dfe9dc740f8aa0ea298382f04c27d194928 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 06:38:57 +0100 Subject: [PATCH 13/22] fix: date imported mbox mail by its Date header unless the envelope line is the arrival time Thunderbird's envelope line is when a message was copied into the folder, so a migrated folder showed years of mail on one day. The envelope date now counts only on Bulwark's own export lines and on lines with a time zone. The mbox writer tests now expect the export's own envelope sender, bulwark-export, in place of MAILER-DAEMON, which marks those lines. --- .../tests/22-ent-import-export.spec.ts | 24 ++++++++++++------- lib/mail-export/__tests__/mbox-writer.test.ts | 6 ++--- lib/mail-export/mbox-writer.ts | 11 +++++++-- lib/mail-import/__tests__/engine.test.ts | 16 +++++++++++-- lib/mail-import/sources.ts | 18 +++++++++++--- 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/integration/tests/22-ent-import-export.spec.ts b/integration/tests/22-ent-import-export.spec.ts index 2068975dd..1cf3bb218 100644 --- a/integration/tests/22-ent-import-export.spec.ts +++ b/integration/tests/22-ent-import-export.spec.ts @@ -51,6 +51,14 @@ const MBOX = [ '', 'no state header', '', + // Thunderbird writes when it copied the message into the folder; the Date header is when it was sent. + 'From - Sat Nov 1 09:00:00 2025', + 'X-Mozilla-Status: 0001', + `Subject: ${STAMP} mbox copied`, + 'Date: Mon, 2 Jan 2017 08:30:00 +0100', + '', + 'copied years later', + '', ].join('\n'); const auth = () => 'Basic ' + Buffer.from(`${USER}:${ENT_PASSWORD}`).toString('base64'); @@ -160,18 +168,18 @@ test('an mbox lands in the folder with its dates, read and flagged state, and th await shot(page, 'c-import-dialog'); await page.getByTestId('mail-import-start').click(); await expect(dialog.getByRole('heading', { name: 'Import finished' })).toBeVisible({ timeout: 60_000 }); - await expect(dialog.getByText('Messages imported: 3')).toBeVisible(); + await expect(dialog.getByText('Messages imported: 4')).toBeVisible(); await expect(dialog.getByText('Deleted in Thunderbird: 1')).toBeVisible(); await shot(page, 'c-import-summary'); const stored = await messagesIn(folderId); - expect(stored.map((m) => m.subject)).toEqual([`${STAMP} mbox read`, `${STAMP} mbox flagged`, `${STAMP} mbox plain`]); - // The envelope line is the arrival time. - expect(stored.map((m) => m.receivedAt)).toEqual(['2020-05-05T10:11:12Z', '2020-05-06T11:00:00Z', '2020-05-08T13:30:00Z']); - expect(stored.map((m) => !!m.keywords.$seen)).toEqual([true, false, true]); - expect(stored.map((m) => !!m.keywords.$flagged)).toEqual([false, true, false]); + expect(stored.map((m) => m.subject)).toEqual([`${STAMP} mbox copied`, `${STAMP} mbox read`, `${STAMP} mbox flagged`, `${STAMP} mbox plain`]); + // The Date header first; the envelope line only where there is none. + expect(stored.map((m) => m.receivedAt)).toEqual(['2017-01-02T07:30:00Z', '2020-05-05T10:11:00Z', '2020-05-06T10:59:00Z', '2020-05-08T13:30:00Z']); + expect(stored.map((m) => !!m.keywords.$seen)).toEqual([true, true, false, true]); + expect(stored.map((m) => !!m.keywords.$flagged)).toEqual([false, false, true, false]); // The stored message starts with its own headers (no leftover "From " envelope line) and is unquoted. - const res = await fetch(`${JMAP_URL}/jmap/download/${(await jmapAs(USER)).accountId}/${stored[0].blobId}/m.eml?accept=message/rfc822`, { headers: { Authorization: auth() } }); + const res = await fetch(`${JMAP_URL}/jmap/download/${(await jmapAs(USER)).accountId}/${stored[1].blobId}/m.eml?accept=message/rfc822`, { headers: { Authorization: auth() } }); const raw = await res.text(); expect(raw.startsWith('From: Alice')).toBe(true); expect(raw).toContain('\nFrom the start of a line'); @@ -225,7 +233,7 @@ test('a folder exported as mbox from the UI imports back to the same messages, d const download = await exportFolder(page, sourceName, 'mbox'); expect(download.suggestedFilename()).toBe(`${sourceName}.mbox`); const mbox = await readDownload(download); - expect(mbox.toString('latin1')).toMatch(/^From MAILER-DAEMON Fri Mar 1 08:00:00 2019\r?\n/); + expect(mbox.toString('latin1')).toMatch(/^From bulwark-export Fri Mar 1 08:00:00 2019\r?\n/); expect(mbox.toString('latin1')).toContain('>From the seed'); await shot(page, 'c-export-done'); await page.getByRole('button', { name: 'Close' }).last().click(); diff --git a/lib/mail-export/__tests__/mbox-writer.test.ts b/lib/mail-export/__tests__/mbox-writer.test.ts index 018131ffe..0f9826e73 100644 --- a/lib/mail-export/__tests__/mbox-writer.test.ts +++ b/lib/mail-export/__tests__/mbox-writer.test.ts @@ -22,13 +22,13 @@ describe('mboxEntry (mboxrd)', () => { const raw = 'Subject: hi\r\nX-Mozilla-Status: 0001\r\nStatus: RO\r\nX-Keywords:\r\n old\r\nFrom: a@example.org\r\n\r\nFrom the top\r\n>From quoted\r\nend\r\n'; it('writes an envelope line in asctime UTC', () => { - expect(envelopeLine('2020-05-05T10:11:12Z', '\n')).toBe('From MAILER-DAEMON Tue May 5 10:11:12 2020\n'); + expect(envelopeLine('2020-05-05T10:11:12Z', '\n')).toBe('From bulwark-export Tue May 5 10:11:12 2020\n'); }); it('quotes From lines, replaces old state headers and keeps CRLF', () => { const out = dec.decode(join(mboxEntry(enc.encode(raw), { $flagged: true, work: true }, '2020-05-05T10:11:12Z'))); expect(out).toBe( - 'From MAILER-DAEMON Tue May 5 10:11:12 2020\r\nStatus: O\r\nX-Status: F\r\nX-Keywords: work\r\n' + 'From bulwark-export Tue May 5 10:11:12 2020\r\nStatus: O\r\nX-Status: F\r\nX-Keywords: work\r\n' + 'Subject: hi\r\nFrom: a@example.org\r\n\r\n>From the top\r\n>>From quoted\r\nend\r\n\r\n', ); }); @@ -45,6 +45,6 @@ describe('mboxEntry (mboxrd)', () => { expect(back[0].text.endsWith('Subject: one\n\n>>From x\nFrom y\n\nFrom MAILER-DAEMON Tue May 5 10:11:12 2020\n')).toBe(true); expect(back[1].state.keywords).toEqual({ $draft: true, $forwarded: true }); expect(back[1].text.endsWith('Subject: two\r\n\r\nno newline at end\r\n')).toBe(true); - expect(back[1].fromLine).toBe('From MAILER-DAEMON Sat Jan 2 03:04:05 2021'); + expect(back[1].fromLine).toBe('From bulwark-export Sat Jan 2 03:04:05 2021'); }); }); diff --git a/lib/mail-export/mbox-writer.ts b/lib/mail-export/mbox-writer.ts index deebc0118..d319d651f 100644 --- a/lib/mail-export/mbox-writer.ts +++ b/lib/mail-export/mbox-writer.ts @@ -21,13 +21,20 @@ const NOT_IN_X_KEYWORDS = new Set(["$seen", "$flagged", "$answered", "$draft", " const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; -/** `From MAILER-DAEMON Tue May 5 10:11:12 2020` for a JMAP UTCDate (UTC asctime). */ +/** + * The sender Bulwark writes on its envelope lines. The import trusts the + * date of a line with this sender (it is receivedAt), unlike the date on + * other programs' lines, which may be when the message was copied. + */ +export const EXPORT_ENVELOPE_SENDER = "bulwark-export"; + +/** `From bulwark-export Tue May 5 10:11:12 2020` for a JMAP UTCDate (UTC asctime). */ export function envelopeLine(receivedAt: string | undefined, eol: string): string { const parsed = receivedAt ? new Date(receivedAt) : new Date(0); const d = Number.isNaN(parsed.getTime()) ? new Date(0) : parsed; const pad = (n: number) => String(n).padStart(2, "0"); const day = String(d.getUTCDate()).padStart(2, " "); - return `From MAILER-DAEMON ${DAYS[d.getUTCDay()]} ${MONTHS[d.getUTCMonth()]} ${day} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())} ${d.getUTCFullYear()}${eol}`; + return `From ${EXPORT_ENVELOPE_SENDER} ${DAYS[d.getUTCDay()]} ${MONTHS[d.getUTCMonth()]} ${day} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())} ${d.getUTCFullYear()}${eol}`; } export function stateHeaders(keywords: Record | undefined, eol: string): string { diff --git a/lib/mail-import/__tests__/engine.test.ts b/lib/mail-import/__tests__/engine.test.ts index 06c779236..bef72ae34 100644 --- a/lib/mail-import/__tests__/engine.test.ts +++ b/lib/mail-import/__tests__/engine.test.ts @@ -120,9 +120,9 @@ describe('runImport', () => { expect(fake.imported[0].text.startsWith('Subject: m0\n')).toBe(true); }); - it('takes the arrival time from the envelope line and leaves the state headers out of the stored message', async () => { + it('takes the arrival time from Bulwark\'s own envelope line and leaves the state headers out of the stored message', async () => { const fake = fakeClient(); - const raw = 'From a@example.org Sat Jan 2 03:04:05 2021\nStatus: RO\nX-Mozilla-Status: 0001\nSubject: s\nDate: Tue, 5 May 2020 10:00:00 +0000\n\nbody\n'; + const raw = 'From bulwark-export Sat Jan 2 03:04:05 2021\nStatus: RO\nX-Mozilla-Status: 0001\nSubject: s\nDate: Tue, 5 May 2020 10:00:00 +0000\n\nbody\n'; await runImport(options(fake.client, [file(raw)])); expect(fake.imported[0].receivedAt).toBe('2021-01-02T03:04:05Z'); expect(fake.imported[0].text).toBe('Subject: s\nDate: Tue, 5 May 2020 10:00:00 +0000\n\nbody\n'); @@ -137,6 +137,18 @@ describe('runImport', () => { expect(fake.imported.map((e) => e.keywords)).toEqual([{ $flagged: true }, { $seen: true }]); }); + it('dates a Thunderbird message by its Date header, not by when it was copied into the folder', async () => { + const fake = fakeClient(); + await runImport(options(fake.client, [file('From - Sat Nov 1 09:00:00 2025\nSubject: s\nDate: Mon, 2 Jan 2017 08:30:00 +0100\n\nb\n')])); + expect(fake.imported[0].receivedAt).toBe('2017-01-02T07:30:00Z'); + }); + + it('trusts an envelope line with a time zone (Google Takeout)', async () => { + const fake = fakeClient(); + await runImport(options(fake.client, [file('From 1234@xxx Sat Apr 18 08:29:01 +0200 2020\nSubject: s\nDate: Fri, 17 Apr 2020 22:00:00 +0000\n\nb\n')])); + expect(fake.imported[0].receivedAt).toBe('2020-04-18T06:29:01Z'); + }); + it('falls back to the Date header when the envelope line has no usable date', async () => { const fake = fakeClient(); await runImport(options(fake.client, [file('From MAILER-DAEMON Thu Jan 1 00:00:00 1970\nSubject: s\nDate: Tue, 5 May 2020 10:00:00 +0000\n\nb\n')])); diff --git a/lib/mail-import/sources.ts b/lib/mail-import/sources.ts index 8eb0f14f9..8832441ba 100644 --- a/lib/mail-import/sources.ts +++ b/lib/mail-import/sources.ts @@ -1,6 +1,7 @@ import { dateFromHeaderBlock } from "@/lib/eml-import"; import { MboxSplitter, envelopeDate, readHeaderBlock, type MboxMessage } from "./mbox"; import { mboxState, withoutStateHeaders } from "./mbox-status"; +import { EXPORT_ENVELOPE_SENDER } from "@/lib/mail-export/mbox-writer"; import { listZipEntries, readZipEntry, streamZipEntry } from "./zip-reader"; /** @@ -160,6 +161,19 @@ function plausible(date: string | undefined, now = Date.now()): string | undefin return t >= Date.UTC(1980, 0, 1) && t - now <= 24 * 60 * 60 * 1000 ? date : undefined; } +/** + * When an mbox message arrived. The envelope line is trusted only where it + * is the arrival time for sure: Bulwark's own export (receivedAt) and lines + * with a time zone (Google Takeout). Elsewhere it may be when the message + * was copied into the folder (Thunderbird's `From - `, local time), + * so the Date header comes first and the envelope line is the fallback. + */ +export function arrivalTime(fromLine: string, headers: string): string | undefined { + const envelope = plausible(envelopeDate(fromLine)); + const trusted = fromLine.startsWith(`From ${EXPORT_ENVELOPE_SENDER} `) || /\s[+-]\d{4}\s+\d{4}\s*$/.test(fromLine); + return trusted ? envelope ?? dateFromHeaderBlock(headers) : dateFromHeaderBlock(headers) ?? envelope; +} + function mboxItem(message: MboxMessage, index: number, folder: string[]): ImportItem { const label = message.tooLarge ? message.fromLine : ""; if (message.tooLarge) return { kind: "skipped", index, reason: "too_large", label }; @@ -175,9 +189,7 @@ function mboxItem(message: MboxMessage, index: number, folder: string[]): Import data: new Blob(parts as BlobPart[], { type: MIME }), size: parts.reduce((n, p) => n + p.length, 0), keywords: state.keywords, - // The envelope line holds when the message arrived (Bulwark's export writes - // receivedAt there); the Date header, when it was written, comes second. - receivedAt: plausible(envelopeDate(message.fromLine)) ?? dateFromHeaderBlock(headers), + receivedAt: arrivalTime(message.fromLine, headers), label: subject, }; } From 7102a6f0cbe48b6331a26e2ca5f46b179ec7f5fd Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 06:45:33 +0100 Subject: [PATCH 14/22] fix: read Outlook files more safely and put their special folders in the right place - A message whose attachments are bigger than the server takes is skipped before anything is read (a 1 GB attachment took several GB in the worker and could crash it), and a worker that stops answering ends the import with a message instead of a wait that never ends. - The items after a broken one in a PST folder are counted as unreadable in the summary instead of disappearing. - Sent Items and Deleted Items are known from the PST's own entry IDs, and the Inbox and the others by their names in more languages, so a PST imported at the top level fills the user's own folders whatever its language. The hint about that shows only for the top level. - Message-ID, In-Reply-To and Content-ID from the PST can't carry line breaks into the rebuilt headers. --- .../mail-transfer/mail-import-dialog.tsx | 7 +- lib/mail-import/__tests__/engine.test.ts | 13 +++- lib/mail-import/__tests__/pst-source.test.ts | 13 ++++ lib/mail-import/__tests__/pst.test.ts | 66 ++++++++++++++++++- lib/mail-import/pst-source.ts | 16 ++++- lib/mail-import/pst/extract.ts | 61 +++++++++++++++-- lib/mail-import/pst/items.ts | 30 +++++++-- lib/mail-import/pst/mime.ts | 19 +++++- lib/mail-import/sources.ts | 2 + locales/ar/common.json | 6 +- locales/ca/common.json | 6 +- locales/cs/common.json | 6 +- locales/da/common.json | 6 +- locales/de/common.json | 6 +- locales/en/common.json | 6 +- locales/es/common.json | 6 +- locales/et/common.json | 6 +- locales/fa/common.json | 6 +- locales/fr/common.json | 6 +- locales/he/common.json | 6 +- locales/hu/common.json | 6 +- locales/it/common.json | 6 +- locales/ja/common.json | 6 +- locales/ko/common.json | 6 +- locales/lv/common.json | 6 +- locales/mn/common.json | 6 +- locales/nb/common.json | 6 +- locales/nl/common.json | 6 +- locales/pl/common.json | 6 +- locales/pt/common.json | 6 +- locales/ro/common.json | 6 +- locales/ru/common.json | 6 +- locales/sk/common.json | 6 +- locales/th/common.json | 6 +- locales/tr/common.json | 6 +- locales/uk/common.json | 6 +- locales/vi/common.json | 6 +- locales/zh-TW/common.json | 6 +- locales/zh/common.json | 6 +- 39 files changed, 324 insertions(+), 83 deletions(-) diff --git a/components/mail-transfer/mail-import-dialog.tsx b/components/mail-transfer/mail-import-dialog.tsx index aa057ae52..414132a51 100644 --- a/components/mail-transfer/mail-import-dialog.tsx +++ b/components/mail-transfer/mail-import-dialog.tsx @@ -19,7 +19,7 @@ import { startImport } from "./start-import"; const ACCEPT = ".eml,.zip,.mbox,.mbx,.pst,.ost,message/rfc822,application/zip,application/mbox"; const TOP = "\u0000top"; -const SKIP_ORDER: SkipReason[] = ["too_large", "deleted", "embedded", "contact", "calendar", "other"]; +const SKIP_ORDER: SkipReason[] = ["too_large", "deleted", "embedded", "unreadable", "contact", "calendar", "other"]; interface Picked { file: File; @@ -236,7 +236,10 @@ export function MailImportDialog({ request }: { request: ImportRequest }) { /> {t("keep_folders")} - {t("keep_folders_hint")} + + {t("keep_folders_hint")} + {target === TOP && <> {t("keep_folders_top_hint")}} + )} diff --git a/lib/mail-import/__tests__/engine.test.ts b/lib/mail-import/__tests__/engine.test.ts index bef72ae34..160105ca2 100644 --- a/lib/mail-import/__tests__/engine.test.ts +++ b/lib/mail-import/__tests__/engine.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from 'vitest'; -import { runImport, uploadWindowEnd, quotaFrom, type ImportProgress, type ImportOptions } from '@/lib/mail-import/engine'; +import { runImport, uploadWindowEnd, quotaFrom, FolderResolver, type ImportProgress, type ImportOptions } from '@/lib/mail-import/engine'; import { checkpointKey, deleteCheckpoint, readCheckpoint } from '@/lib/mail-import/checkpoints'; import type { IJMAPClient, UploadedEmailImport } from '@/lib/jmap/client-interface'; import type { Mailbox } from '@/lib/jmap/types'; @@ -363,6 +363,17 @@ describe('runImport', () => { expect(Object.keys(fake.imported[0].mailboxIds)).toEqual(['target']); }); + it('puts a top folder into the special folder the file names, in any language', async () => { + const mailboxes = [mailbox('in', 'Posteingang', { role: 'inbox' }), mailbox('sent', 'Gesendet', { role: 'sent' }), mailbox('bin', 'Papierkorb', { role: 'trash' })]; + const client = { getMailboxes: async () => mailboxes, createMailbox: vi.fn() } as unknown as IJMAPClient; + const folders = new FolderResolver(client, 'acc', null); + // A Spanish PST whose store says this is Sent Items. + await expect(folders.resolve(['Elementos enviados'], 'sent')).resolves.toBe('sent'); + // Names in the languages Outlook ships most often. + await expect(folders.resolve(['Éléments supprimés'])).resolves.toBe('bin'); + await expect(folders.resolve(['Bandeja de entrada'])).resolves.toBe('in'); + }); + it('reads the hour window and the quota from the server', () => { expect(uploadWindowEnd(Date.UTC(2026, 10, 1, 13, 20))).toBe(Date.UTC(2026, 10, 1, 14, 0)); expect(quotaFrom('You have exceeded the blob upload quota of 1000 files or 50000000 bytes.')).toEqual({ files: 1000, bytes: 50000000 }); diff --git a/lib/mail-import/__tests__/pst-source.test.ts b/lib/mail-import/__tests__/pst-source.test.ts index 81e08e023..74cad2a8b 100644 --- a/lib/mail-import/__tests__/pst-source.test.ts +++ b/lib/mail-import/__tests__/pst-source.test.ts @@ -102,6 +102,19 @@ describe('openPstSource', () => { expect(FakeWorker.instances[0].terminated).toBe(true); }); + it('gives up on a worker that stops answering (killed for memory sends no error)', async () => { + class SilentWorker { + onmessage: unknown = null; + onerror: unknown = null; + terminated = false; + postMessage() {} + terminate() { this.terminated = true; } + } + vi.stubGlobal('Worker', SilentWorker); + const source = openPstSource(new File(['!BDN'], 'x.pst'), { skipBefore: 0, maxMessageBytes: 1e9 }, { watchdogMs: 30 }); + await expect(source.next()).rejects.toMatchObject({ name: 'PstReadError', code: 'unreadable' }); + }); + it('turns a worker error into a coded failure', async () => { vi.stubGlobal('Worker', FakeWorker); FakeWorker.failWith = { code: 'encrypted', message: 'PSTFile::open PST is encrypted' }; diff --git a/lib/mail-import/__tests__/pst.test.ts b/lib/mail-import/__tests__/pst.test.ts index dbd2f7bdd..3511f973e 100644 --- a/lib/mail-import/__tests__/pst.test.ts +++ b/lib/mail-import/__tests__/pst.test.ts @@ -1,12 +1,12 @@ // @vitest-environment node -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, vi } from 'vitest'; import fs from 'node:fs'; import path from 'node:path'; import PostalMime from 'postal-mime'; import { PSTFile } from 'pst-extractor'; import { walkPst, type PstItem } from '@/lib/mail-import/pst/items'; import { buildMime, encodeHeaderText, keptHeaders, type PstMailData } from '@/lib/mail-import/pst/mime'; -import { itemKind, pstFolders } from '@/lib/mail-import/pst/extract'; +import { itemKind, pstFolders, readPstMessage } from '@/lib/mail-import/pst/extract'; import { registerPstFile, openSync, readSync } from '@/lib/mail-import/pst/shims/fs'; // Aspose's Outlook.pst (MIT, see fixtures/Outlook.pst.LICENSE). @@ -151,6 +151,68 @@ describe('pstFolders without an IPM subtree entry (OST files)', () => { }); }); +describe('PST special folders', () => { + it('knows Sent Items and Deleted Items from the store\'s entry IDs, whatever their names', () => { + const roles = Object.fromEntries([...pstFolders(new PSTFile(fs.readFileSync(FIXTURE)))].map((r) => [r.path.join('/'), r.topRole])); + expect(roles['Sent Items']).toBe('sent'); + expect(roles['Deleted Items']).toBe('trash'); + expect(roles['Inbox']).toBeUndefined(); + }); + + it('gives every message the role of its top folder', () => { + const items = [...walkPst(new PSTFile(fs.readFileSync(FIXTURE)), { skipBefore: 0, maxMessageBytes: 50_000_000 }).items]; + const sent = items.filter((i) => i.kind === 'message' && i.folder[0] === 'Sent Items'); + expect(sent.length).toBeGreaterThan(0); + expect(sent.every((i) => i.kind === 'message' && i.folderRole === 'sent')).toBe(true); + }); +}); + +describe('PST safety', () => { + const stream = (length: number) => ({ length, readCompletely: vi.fn() }); + const message = (attachments: unknown[]) => ({ + subject: 'big', numberOfAttachments: attachments.length, getAttachment: (i: number) => attachments[i], numberOfRecipients: 0, + }); + + it('skips a message whose attachments are too big before reading any of them', () => { + const big = stream(900_000_000); + const read = readPstMessage(message([{ attachMethod: 1, fileInputStream: big, filename: 'a.iso' }]), 50_000_000); + expect(read.tooLarge).toBe(true); + expect(big.readCompletely).not.toHaveBeenCalled(); + }); + + it('counts what is left of a folder after a broken item as unreadable', () => { + let n = 0; + const folder = { + displayName: 'Inbox', containerClass: 'IPF.Note', descriptorNodeId: 9, hasSubfolders: false, contentCount: 5, + getNextChild: () => { if (++n > 2) throw new Error('broken block'); return { messageClass: 'IPM.Contact', subject: `c${n}` }; }, + }; + const pst = { + getMessageStore: () => ({ getBinaryItem: () => null }), + getRootFolder: () => ({ displayName: '', descriptorNodeId: 1, hasSubfolders: true, getSubFolders: () => [folder] }), + }; + const items = [...walkPst(pst, { skipBefore: 0, maxMessageBytes: 1e9 }).items]; + expect(items.map((i) => (i.kind === 'skipped' ? i.reason : 'message'))).toEqual(['contact', 'contact', 'unreadable', 'unreadable', 'unreadable']); + expect(items.map((i) => i.index)).toEqual([0, 1, 2, 3, 4]); + }); + + it('keeps line breaks and spaces out of the ids it writes into headers', () => { + const text = new TextDecoder().decode(buildMime({ + subject: 's', to: [], cc: [], bcc: [], text: 'x', + messageId: '\r\nBcc: victim@example.org', inReplyTo: 'c@d', + html: '', + attachments: [{ filename: 'i.png', mimeType: 'image/png', contentId: 'img1', data: new Uint8Array([1]) }], + })); + expect(text).not.toMatch(/^Bcc:/m); + expect(text).not.toMatch(/^Message-ID:/m); + expect(text).toMatch(/^In-Reply-To: \r$/m); + const evil = new TextDecoder().decode(buildMime({ + subject: 's', to: [], cc: [], bcc: [], text: 'x', html: '', + attachments: [{ filename: 'i.png', mimeType: 'image/png', contentId: 'x>\r\nX-Evil: 1', data: new Uint8Array([1]) }], + })); + expect(evil).not.toMatch(/^X-Evil:/m); + }); +}); + describe('itemKind', () => { it('sorts Outlook message classes', () => { expect(itemKind('IPM.Note')).toBe('mail'); diff --git a/lib/mail-import/pst-source.ts b/lib/mail-import/pst-source.ts index fcb049108..9d6c8bbab 100644 --- a/lib/mail-import/pst-source.ts +++ b/lib/mail-import/pst-source.ts @@ -43,7 +43,10 @@ type WorkerMessage = /** Messages asked for ahead of the import, so the worker keeps busy without filling memory. */ const AHEAD = 4; -export async function* openPstSource(file: File, options: SourceOptions): ItemSource { +/** How long the worker may stay silent while a message is awaited before the import gives up (it may have run out of memory). */ +const WATCHDOG_MS = 120_000; + +export async function* openPstSource(file: File, options: SourceOptions, tuning: { watchdogMs?: number } = {}): ItemSource { const worker = new Worker(pstWorkerUrl()); const queue: WorkerMessage[] = []; let wake: (() => void) | null = null; @@ -58,8 +61,16 @@ export async function* openPstSource(file: File, options: SourceOptions): ItemSo }; const onAbort = () => push({ type: "abort" }); options.signal?.addEventListener("abort", onAbort, { once: true }); + // A worker killed for memory sends no error: don't wait for it forever. const next = async (): Promise => { - while (queue.length === 0) await new Promise((resolve) => { wake = resolve; }); + while (queue.length === 0) { + let timer: ReturnType | undefined; + await new Promise((resolve) => { + wake = resolve; + timer = setTimeout(() => push({ type: "error", code: "unreadable", message: "The PST reader stopped answering" }), tuning.watchdogMs ?? WATCHDOG_MS); + }); + clearTimeout(timer); + } wake = null; return queue.shift()!; }; @@ -100,6 +111,7 @@ export async function* openPstSource(file: File, options: SourceOptions): ItemSo receivedAt: item.receivedAt, label: item.label, embeddedSkipped: item.embeddedSkipped, + folderRole: item.folderRole, }; } else { out = item; diff --git a/lib/mail-import/pst/extract.ts b/lib/mail-import/pst/extract.ts index 08ec78531..3cab3039f 100644 --- a/lib/mail-import/pst/extract.ts +++ b/lib/mail-import/pst/extract.ts @@ -21,6 +21,9 @@ const SYSTEM_FOLDERS = new Set([ const ROOT_NAMES = new Set(["top of personal folders", "top of outlook data file", "root - mailbox", "ipm_subtree", "root - public"]); const PR_IPM_SUBTREE_ENTRYID = 0x35e0; +const PR_IPM_WASTEBASKET_ENTRYID = 0x35e3; +const PR_IPM_SENTMAIL_ENTRYID = 0x35e4; +const PR_IPM_DRAFTS_ENTRYID = 0x36d7; const PR_FLAG_STATUS = 0x1090; const PR_SENDER_SMTP_ADDRESS = 0x5d01; const PR_SENT_REPRESENTING_SMTP_ADDRESS = 0x5d02; @@ -46,6 +49,8 @@ function isMailFolder(folder: PstObject): boolean { export interface PstFolderRef { folder: PstObject; path: string[]; + /** What the PST says the top folder of `path` is (sent, trash, drafts), whatever its name in the user's language. */ + topRole?: string; /** A calendar, contacts or other non-mail folder: its items are counted, not imported. */ mail: boolean; } @@ -76,7 +81,25 @@ export function* pstFolders(pst: PstObject): Generator { top = null; } - function* walk(folder: PstObject, path: string[], depth: number): Generator { + // Special folders by their entry IDs: the message store names Sent Items and + // Deleted Items, the top folder names Drafts. The Inbox is found by name. + const roles = new Map(); + const nidOf = (entry: { length: number; readUInt32LE(at: number): number } | null | undefined) => + entry && entry.length >= 24 ? entry.readUInt32LE(20) : null; + try { + const store = pst.getMessageStore(); + for (const [id, role] of [[PR_IPM_SENTMAIL_ENTRYID, "sent"], [PR_IPM_WASTEBASKET_ENTRYID, "trash"]] as const) { + const nid = nidOf(store.getBinaryItem?.(id)); + if (nid !== null) roles.set(nid, role); + } + const drafts = nidOf((top ?? root).getBinaryItem?.(PR_IPM_DRAFTS_ENTRYID)); + if (drafts !== null) roles.set(drafts, "drafts"); + } catch { + /* names are the fallback */ + } + const nidOfFolder = (folder: PstObject): number => Number(folder.descriptorNodeId?.toNumber?.() ?? folder.descriptorNodeId); + + function* walk(folder: PstObject, path: string[], depth: number, topRole?: string): Generator { if (!folder.hasSubfolders) return; for (const child of folder.getSubFolders()) { const name = String(child.displayName || "").trim(); @@ -84,8 +107,9 @@ export function* pstFolders(pst: PstObject): Generator { // Outside the IPM subtree only what sits next to the mailbox's top counts as system data. if (!top && path.length === 0 && SYSTEM_FOLDERS.has(lower)) continue; const childPath = !top && ROOT_NAMES.has(lower) && path.length === 0 ? path : [...path, name || "Folder"]; - yield { folder: child, path: childPath, mail: isMailFolder(child) }; - yield* walk(child, childPath, depth + 1); + const role = childPath.length === 1 && path.length === 0 ? roles.get(nidOfFolder(child)) : topRole; + yield { folder: child, path: childPath, mail: isMailFolder(child), topRole: role }; + yield* walk(child, childPath, depth + 1, role); } } @@ -175,13 +199,38 @@ export interface PstMessage { receivedAt?: Date; /** Attached messages pst-extractor can't read. */ embedded: number; + /** Bigger than the server takes: nothing was read in. */ + tooLarge?: boolean; } -/** A mail item's properties, its attachments read in, as data for buildMime. */ -export function readPstMessage(m: PstObject): PstMessage { +function streamLength(stream: PstObject): number { + return typeof stream?.length === "number" ? stream.length : Number(stream?.length?.toNumber?.() ?? 0); +} + +/** + * A mail item's properties, its attachments read in, as data for buildMime. + * A message whose attachments alone would be bigger than `maxBytes` once in + * base64 is reported `tooLarge` before anything is read: a 1 GB attachment + * would take several GB in the worker. + */ +export function readPstMessage(m: PstObject, maxBytes = Number.POSITIVE_INFINITY): PstMessage { const attachments: PstAttachmentData[] = []; let embedded = 0; const count = Number(m.numberOfAttachments) || 0; + if (Number.isFinite(maxBytes)) { + let declared = 0; + for (let i = 0; i < count; i++) { + try { + const a = m.getAttachment(i); + if (a && a.attachMethod !== ATTACH_EMBEDDED_MSG) declared += streamLength(a.fileInputStream); + } catch { + /* read below, or skipped there */ + } + } + if (Math.ceil(declared / 3) * 4 > maxBytes) { + return { mail: { subject: str(m.subject), to: [], cc: [], bcc: [], attachments: [] }, keywords: {}, embedded: 0, tooLarge: true }; + } + } for (let i = 0; i < count; i++) { let a: PstObject; try { a = m.getAttachment(i); } catch { continue; } @@ -189,7 +238,7 @@ export function readPstMessage(m: PstObject): PstMessage { if (a.attachMethod === ATTACH_EMBEDDED_MSG) { embedded++; continue; } const stream = a.fileInputStream; if (!stream) continue; - const length = typeof stream.length === "number" ? stream.length : Number(stream.length?.toNumber?.() ?? 0); + const length = streamLength(stream); const buffer = new Uint8Array(length); try { // pst-extractor wants a Buffer; one over our bytes fills them in place. diff --git a/lib/mail-import/pst/items.ts b/lib/mail-import/pst/items.ts index 38375b1e8..5380d6563 100644 --- a/lib/mail-import/pst/items.ts +++ b/lib/mail-import/pst/items.ts @@ -13,6 +13,7 @@ export type PstItem = receivedAt?: string; label: string; embeddedSkipped: number; + folderRole?: string; } | { kind: "skipped"; index: number; reason: SkipReason; label?: string }; @@ -37,15 +38,25 @@ export function walkPst(pst: any, options: { skipBefore: number; skipAlso?: Set< let index = 0; for (const ref of folders) { if (!(Number(ref.folder.contentCount) > 0)) continue; + const count = Number(ref.folder.contentCount) || 0; + let read = 0; for (;;) { let child: any; try { child = ref.folder.getNextChild(); } catch { - // A broken item ends this folder's list in pst-extractor; go on with the next folder. + // A broken item ends this folder's list in pst-extractor: count what is + // left of the folder as unreadable and go on with the next folder. + for (let left = count - read; left > 0; left--) { + const i = index++; + yield i < options.skipBefore || options.skipAlso?.has(i) + ? { kind: "skipped", index: i, reason: "resumed" } + : { kind: "skipped", index: i, reason: "unreadable" }; + } break; } if (!child) break; + read++; const i = index++; if (i < options.skipBefore || options.skipAlso?.has(i)) { yield { kind: "skipped", index: i, reason: "resumed" }; @@ -58,10 +69,14 @@ export function walkPst(pst: any, options: { skipBefore: number; skipAlso?: Set< continue; } let mime: Uint8Array; - let read; + let message; try { - read = readPstMessage(child); - mime = buildMime(read.mail, { boundarySeed: options.boundarySeed }); + message = readPstMessage(child, options.maxMessageBytes); + if (message.tooLarge) { + yield { kind: "skipped", index: i, reason: "too_large", label }; + continue; + } + mime = buildMime(message.mail, { boundarySeed: options.boundarySeed }); } catch { yield { kind: "skipped", index: i, reason: "other", label }; continue; @@ -75,10 +90,11 @@ export function walkPst(pst: any, options: { skipBefore: number; skipAlso?: Set< index: i, folder: ref.path, mime, - keywords: read.keywords, - receivedAt: utcDate(read.receivedAt), + keywords: message.keywords, + receivedAt: utcDate(message.receivedAt), label, - embeddedSkipped: read.embedded, + embeddedSkipped: message.embedded, + folderRole: ref.topRole, }; } } diff --git a/lib/mail-import/pst/mime.ts b/lib/mail-import/pst/mime.ts index 43484f2fa..4aca45ee2 100644 --- a/lib/mail-import/pst/mime.ts +++ b/lib/mail-import/pst/mime.ts @@ -148,6 +148,17 @@ function textPart(text: string, subtype: "plain" | "html"): Part { return leaf(`text/${subtype}; charset=utf-8`, encoder.encode(text.replace(/\r?\n/g, CRLF))); } +/** A message id from the PST as ``, or undefined: no line breaks or spaces can get into the header. */ +function msgId(value: string | undefined): string | undefined { + const id = (value ?? "").replace(/[\r\n]/g, "").trim().replace(/^<|>$/g, ""); + return /^[^<>\s]+$/.test(id) ? `<${id}>` : undefined; +} + +/** A Content-ID without anything that could end the header or the angle brackets. */ +function cid(value: string): string { + return value.replace(/[<>\s]/g, ""); +} + export interface MimeOptions { /** Makes boundaries reproducible (tests). */ boundarySeed?: string; @@ -179,7 +190,7 @@ export function buildMime(mail: PstMailData, options: MimeOptions = {}): Uint8Ar body = multipart("related", [body, ...inline.map((a) => leaf( `${a.mimeType}${fileParams(a.filename)}`, a.data, - [`Content-ID: <${a.contentId}>`, `Content-Disposition: inline${fileParams(a.filename).replace(/; name/g, "; filename")}`], + [`Content-ID: <${cid(a.contentId!)}>`, `Content-Disposition: inline${fileParams(a.filename).replace(/; name/g, "; filename")}`], ))]); } const extra: Part[] = attached.map((a) => leaf( @@ -203,8 +214,10 @@ export function buildMime(mail: PstMailData, options: MimeOptions = {}): Uint8Ar // Only sent mail and drafts have Bcc recipients; a received header block never names them. if (!mail.headers && mail.bcc.length > 0) header.push(`Bcc: ${addressList(mail.bcc)}`); if (!has("subject")) header.push(`Subject: ${encodeHeaderText(mail.subject)}`); - if (!has("message-id") && mail.messageId) header.push(`Message-ID: ${mail.messageId}`); - if (!has("in-reply-to") && mail.inReplyTo) header.push(`In-Reply-To: ${mail.inReplyTo}`); + const messageId = msgId(mail.messageId); + const inReplyTo = msgId(mail.inReplyTo); + if (!has("message-id") && messageId) header.push(`Message-ID: ${messageId}`); + if (!has("in-reply-to") && inReplyTo) header.push(`In-Reply-To: ${inReplyTo}`); if (!has("importance") && mail.importance === 2) header.push("Importance: high"); if (!has("importance") && mail.importance === 0) header.push("Importance: low"); header.push("MIME-Version: 1.0"); diff --git a/lib/mail-import/sources.ts b/lib/mail-import/sources.ts index 8832441ba..34d5ccb94 100644 --- a/lib/mail-import/sources.ts +++ b/lib/mail-import/sources.ts @@ -25,6 +25,8 @@ export type SkipReason = | "other" /** Messages attached to a PST message, which pst-extractor can't read. */ | "embedded" + /** PST items after a broken one in the same folder, which pst-extractor can't reach. */ + | "unreadable" /** Imported by an earlier run of the same import. */ | "resumed"; diff --git a/locales/ar/common.json b/locales/ar/common.json index 65fb75fdb..c49b07ecf 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -5397,7 +5397,7 @@ "target_label": "الاستيراد إلى", "target_top": "المستوى الأعلى", "keep_folders": "إعادة إنشاء مجلدات الملف", - "keep_folders_hint": "يُعاد استخدام المجلدات التي تحمل الاسم نفسه. تذهب علبة الوارد والعناصر المرسلة والعناصر المحذوفة في المستوى الأعلى إلى مجلداتك.", + "keep_folders_hint": "يُعاد استخدام المجلدات التي تحمل الاسم نفسه.", "resume": "المتابعة من حيث توقف آخر استيراد لهذه الملفات (الرسائل المنجزة: {count})", "quota_note": "كثير من الخوادم لا تقبل من المستخدم الواحد إلا نحو 1000 رسالة أو 50 ميغابايت في الساعة. يتوقف الاستيراد الأكبر مؤقتًا ثم يتابع وحده.", "start": "استيراد", @@ -5439,7 +5439,9 @@ "error_failures": "فشلت رسائل كثيرة متتالية، لذا توقف الاستيراد. استورد الملفات نفسها إلى المجلد نفسه لاحقًا للمتابعة.", "error_network": "فشل الاتصال بالخادم مرارًا، لذا توقف الاستيراد. استورد الملفات نفسها إلى المجلد نفسه مرة أخرى للمتابعة.", "error_busy": "يجري استيراد هذه الملفات في علامة تبويب أخرى بالفعل.", - "retry_hint": "استورد الملفات نفسها إلى المجلد نفسه مرة أخرى لإعادة محاولة الرسائل التي فشلت." + "retry_hint": "استورد الملفات نفسها إلى المجلد نفسه مرة أخرى لإعادة محاولة الرسائل التي فشلت.", + "skip_unreadable": "غير مقروءة في ملف Outlook: {count}", + "keep_folders_top_hint": "في المستوى الأعلى تذهب علبة الوارد والعناصر المرسلة والعناصر المحذوفة وما شابهها إلى مجلداتك." }, "mail_export": { "menu_export": "تصدير المجلد…", diff --git a/locales/ca/common.json b/locales/ca/common.json index 5a0c2111c..5a834fd3c 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -5397,7 +5397,7 @@ "target_label": "Importa a", "target_top": "Nivell superior", "keep_folders": "Recrea les carpetes del fitxer", - "keep_folders_hint": "Les carpetes amb el mateix nom es tornen a fer servir. Safata d'entrada, Elements enviats i Elements suprimits al nivell superior van a les vostres.", + "keep_folders_hint": "Les carpetes amb el mateix nom es tornen a fer servir.", "resume": "Continua on es va aturar l'última importació d'aquests fitxers (missatges ja fets: {count})", "quota_note": "Molts servidors només accepten uns 1000 missatges o 50 MB per hora de cada usuari. Una importació més gran fa una pausa i després continua sola.", "start": "Importa", @@ -5439,7 +5439,9 @@ "error_failures": "Han fallat massa missatges seguits, així que la importació s'ha aturat. Torneu a importar els mateixos fitxers a la mateixa carpeta més tard per continuar.", "error_network": "La connexió amb el servidor ha fallat diverses vegades, així que la importació s'ha aturat. Torneu a importar els mateixos fitxers a la mateixa carpeta per continuar.", "error_busy": "Aquests fitxers ja s'estan important en una altra pestanya.", - "retry_hint": "Torneu a importar els mateixos fitxers a la mateixa carpeta per tornar a provar els que han fallat." + "retry_hint": "Torneu a importar els mateixos fitxers a la mateixa carpeta per tornar a provar els que han fallat.", + "skip_unreadable": "Il·legibles al fitxer d'Outlook: {count}", + "keep_folders_top_hint": "Al nivell superior, Safata d'entrada, Elements enviats, Elements suprimits i similars van a les vostres." }, "mail_export": { "menu_export": "Exporta la carpeta…", diff --git a/locales/cs/common.json b/locales/cs/common.json index ea4edc14f..c7296a926 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -5267,7 +5267,7 @@ "target_label": "Importovat do", "target_top": "Nejvyšší úroveň", "keep_folders": "Znovu vytvořit složky ze souboru", - "keep_folders_hint": "Složky se stejným názvem se použijí znovu. Doručená pošta, Odeslaná pošta a Odstraněná pošta na nejvyšší úrovni skončí ve vašich vlastních.", + "keep_folders_hint": "Složky se stejným názvem se použijí znovu.", "resume": "Pokračovat tam, kde se poslední import těchto souborů zastavil (již hotové zprávy: {count})", "quota_note": "Mnoho serverů přijímá od jednoho uživatele jen asi 1000 zpráv nebo 50 MB za hodinu. Větší import se pozastaví a pak pokračuje sám.", "start": "Importovat", @@ -5309,7 +5309,9 @@ "error_failures": "Příliš mnoho zpráv za sebou selhalo, proto se import zastavil. Importujte stejné soubory později znovu do stejné složky, abyste pokračovali.", "error_network": "Spojení se serverem opakovaně selhalo, proto se import zastavil. Importujte stejné soubory znovu do stejné složky, abyste pokračovali.", "error_busy": "Tyto soubory se už importují v jiné kartě.", - "retry_hint": "Importujte stejné soubory znovu do stejné složky, abyste zkusili neúspěšné ještě jednou." + "retry_hint": "Importujte stejné soubory znovu do stejné složky, abyste zkusili neúspěšné ještě jednou.", + "skip_unreadable": "V souboru Outlooku nečitelné: {count}", + "keep_folders_top_hint": "Na nejvyšší úrovni skončí Doručená pošta, Odeslaná pošta, Odstraněná pošta a podobné ve vašich vlastních." }, "mail_export": { "menu_export": "Exportovat složku…", diff --git a/locales/da/common.json b/locales/da/common.json index 2db8c6f0f..7aaa9a13e 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -5397,7 +5397,7 @@ "target_label": "Importér til", "target_top": "Øverste niveau", "keep_folders": "Genskab mapperne fra filen", - "keep_folders_hint": "Mapper med samme navn bruges igen. Indbakke, Sendt post og Slettet post på øverste niveau havner i dine egne.", + "keep_folders_hint": "Mapper med samme navn bruges igen.", "resume": "Fortsæt, hvor den sidste import af disse filer stoppede (beskeder allerede klaret: {count})", "quota_note": "Mange servere tager kun imod omkring 1000 beskeder eller 50 MB i timen fra én bruger. En større import holder pause og fortsætter derefter af sig selv.", "start": "Importér", @@ -5439,7 +5439,9 @@ "error_failures": "For mange beskeder i træk mislykkedes, så importen stoppede. Importér de samme filer til samme mappe igen senere for at fortsætte.", "error_network": "Forbindelsen til serveren svigtede gentagne gange, så importen stoppede. Importér de samme filer til samme mappe igen for at fortsætte.", "error_busy": "Disse filer importeres allerede i en anden fane.", - "retry_hint": "Importér de samme filer til samme mappe igen for at prøve dem, der mislykkedes, en gang til." + "retry_hint": "Importér de samme filer til samme mappe igen for at prøve dem, der mislykkedes, en gang til.", + "skip_unreadable": "Ulæselige i Outlook-filen: {count}", + "keep_folders_top_hint": "På øverste niveau havner Indbakke, Sendt post, Slettet post og lignende i dine egne." }, "mail_export": { "menu_export": "Eksportér mappe…", diff --git a/locales/de/common.json b/locales/de/common.json index 43ff4d62a..848f58402 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -5267,7 +5267,7 @@ "target_label": "Importieren in", "target_top": "Oberste Ebene", "keep_folders": "Ordner aus der Datei übernehmen", - "keep_folders_hint": "Ordner mit gleichem Namen werden wiederverwendet. Posteingang, Gesendete Elemente und Gelöschte Elemente auf oberster Ebene landen in Ihren eigenen.", + "keep_folders_hint": "Ordner mit gleichem Namen werden wiederverwendet.", "resume": "Dort weitermachen, wo der letzte Import dieser Dateien aufgehört hat (bereits erledigte Nachrichten: {count})", "quota_note": "Viele Server nehmen pro Benutzer nur etwa 1000 Nachrichten oder 50 MB pro Stunde an. Ein größerer Import pausiert und läuft dann von selbst weiter.", "start": "Importieren", @@ -5309,7 +5309,9 @@ "error_failures": "Zu viele Nachrichten sind nacheinander fehlgeschlagen, daher wurde der Import angehalten. Importieren Sie dieselben Dateien später erneut in denselben Ordner, um weiterzumachen.", "error_network": "Die Verbindung zum Server ist wiederholt fehlgeschlagen, daher wurde der Import angehalten. Importieren Sie dieselben Dateien erneut in denselben Ordner, um weiterzumachen.", "error_busy": "Diese Dateien werden bereits in einem anderen Tab importiert.", - "retry_hint": "Importieren Sie dieselben Dateien erneut in denselben Ordner, um die fehlgeschlagenen noch einmal zu versuchen." + "retry_hint": "Importieren Sie dieselben Dateien erneut in denselben Ordner, um die fehlgeschlagenen noch einmal zu versuchen.", + "skip_unreadable": "In der Outlook-Datei nicht lesbar: {count}", + "keep_folders_top_hint": "Auf oberster Ebene landen Posteingang, Gesendete Elemente, Gelöschte Elemente und Ähnliche in Ihren eigenen Ordnern." }, "mail_export": { "menu_export": "Ordner exportieren…", diff --git a/locales/en/common.json b/locales/en/common.json index 5ec102c54..547f3587e 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -5397,7 +5397,7 @@ "target_label": "Import into", "target_top": "Top level", "keep_folders": "Recreate the folders from the file", - "keep_folders_hint": "Folders with the same name are used again. Inbox, Sent Items and Deleted Items at the top go into your own.", + "keep_folders_hint": "Folders with the same name are used again.", "resume": "Continue where the last import of these files stopped (messages already done: {count})", "quota_note": "Many servers take only about 1000 messages or 50 MB an hour from one user. A larger import pauses and then goes on by itself.", "start": "Import", @@ -5439,7 +5439,9 @@ "error_failures": "Too many messages failed in a row, so the import stopped. Import the same files into the same folder again later to go on.", "error_network": "The connection to the server kept failing, so the import stopped. Import the same files into the same folder again to go on.", "error_busy": "These files are being imported in another tab already.", - "retry_hint": "Import the same files into the same folder again to try the ones that failed once more." + "retry_hint": "Import the same files into the same folder again to try the ones that failed once more.", + "skip_unreadable": "Unreadable in the Outlook file: {count}", + "keep_folders_top_hint": "At the top level, Inbox, Sent Items, Deleted Items and the like go into your own." }, "mail_export": { "menu_export": "Export folder…", diff --git a/locales/es/common.json b/locales/es/common.json index e18e98a31..dca463886 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -5267,7 +5267,7 @@ "target_label": "Importar en", "target_top": "Nivel superior", "keep_folders": "Recrear las carpetas del archivo", - "keep_folders_hint": "Las carpetas con el mismo nombre se reutilizan. Bandeja de entrada, Elementos enviados y Elementos eliminados en el nivel superior van a las suyas.", + "keep_folders_hint": "Las carpetas con el mismo nombre se reutilizan.", "resume": "Seguir donde se detuvo la última importación de estos archivos (mensajes ya procesados: {count})", "quota_note": "Muchos servidores solo aceptan unos 1000 mensajes o 50 MB por hora de cada usuario. Una importación mayor se pausa y luego sigue sola.", "start": "Importar", @@ -5309,7 +5309,9 @@ "error_failures": "Fallaron demasiados mensajes seguidos, así que la importación se detuvo. Importe los mismos archivos en la misma carpeta más tarde para seguir.", "error_network": "La conexión con el servidor falló varias veces, así que la importación se detuvo. Importe los mismos archivos en la misma carpeta otra vez para seguir.", "error_busy": "Estos archivos ya se están importando en otra pestaña.", - "retry_hint": "Importe los mismos archivos en la misma carpeta otra vez para reintentar los que fallaron." + "retry_hint": "Importe los mismos archivos en la misma carpeta otra vez para reintentar los que fallaron.", + "skip_unreadable": "Ilegibles en el archivo de Outlook: {count}", + "keep_folders_top_hint": "En el nivel superior, Bandeja de entrada, Elementos enviados, Elementos eliminados y similares van a las suyas." }, "mail_export": { "menu_export": "Exportar carpeta…", diff --git a/locales/et/common.json b/locales/et/common.json index 08a6b11d9..881a92474 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -5397,7 +5397,7 @@ "target_label": "Impordi kausta", "target_top": "Ülemine tase", "keep_folders": "Loo failist kaustad uuesti", - "keep_folders_hint": "Sama nimega kaustu kasutatakse uuesti. Ülemise taseme Sisendkaust, Saadetud ja Kustutatud lähevad teie omadesse.", + "keep_folders_hint": "Sama nimega kaustu kasutatakse uuesti.", "resume": "Jätka sealt, kus nende failide viimane import peatus (juba tehtud kirju: {count})", "quota_note": "Paljud serverid võtavad ühelt kasutajalt vastu ainult umbes 1000 kirja või 50 MB tunnis. Suurem import teeb pausi ja jätkab siis ise.", "start": "Impordi", @@ -5439,7 +5439,9 @@ "error_failures": "Liiga palju kirju järjest ebaõnnestus, seega import peatus. Importige samad failid hiljem uuesti samasse kausta, et jätkata.", "error_network": "Ühendus serveriga katkes korduvalt, seega import peatus. Importige samad failid uuesti samasse kausta, et jätkata.", "error_busy": "Neid faile imporditakse juba teisel vahelehel.", - "retry_hint": "Importige samad failid uuesti samasse kausta, et ebaõnnestunud kirju veel kord proovida." + "retry_hint": "Importige samad failid uuesti samasse kausta, et ebaõnnestunud kirju veel kord proovida.", + "skip_unreadable": "Outlooki failis loetamatud: {count}", + "keep_folders_top_hint": "Ülemisel tasemel lähevad Sisendkaust, Saadetud, Kustutatud ja muud sellised teie omadesse." }, "mail_export": { "menu_export": "Ekspordi kaust…", diff --git a/locales/fa/common.json b/locales/fa/common.json index 66a6dbb30..6af851e47 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -5397,7 +5397,7 @@ "target_label": "درون‌ریزی به", "target_top": "بالاترین سطح", "keep_folders": "ساختن دوباره پوشه‌های فایل", - "keep_folders_hint": "پوشه‌های هم‌نام دوباره به کار می‌روند. صندوق ورودی، موارد ارسال‌شده و موارد حذف‌شده در بالاترین سطح به پوشه‌های خودتان می‌روند.", + "keep_folders_hint": "پوشه‌های هم‌نام دوباره به کار می‌روند.", "resume": "ادامه از جایی که آخرین درون‌ریزی این فایل‌ها متوقف شد (پیام‌های انجام‌شده: {count})", "quota_note": "بسیاری از سرورها از هر کاربر فقط حدود ۱۰۰۰ پیام یا ۵۰ مگابایت در ساعت می‌پذیرند. درون‌ریزی بزرگ‌تر مکث می‌کند و سپس خودش ادامه می‌دهد.", "start": "درون‌ریزی", @@ -5439,7 +5439,9 @@ "error_failures": "پیام‌های زیادی پشت سر هم ناموفق بودند، بنابراین درون‌ریزی متوقف شد. برای ادامه، بعداً همان فایل‌ها را دوباره در همان پوشه درون‌ریزی کنید.", "error_network": "اتصال به سرور چند بار قطع شد، بنابراین درون‌ریزی متوقف شد. برای ادامه، همان فایل‌ها را دوباره در همان پوشه درون‌ریزی کنید.", "error_busy": "این فایل‌ها هم‌اکنون در زبانه دیگری درون‌ریزی می‌شوند.", - "retry_hint": "برای امتحان دوباره پیام‌های ناموفق، همان فایل‌ها را دوباره در همان پوشه درون‌ریزی کنید." + "retry_hint": "برای امتحان دوباره پیام‌های ناموفق، همان فایل‌ها را دوباره در همان پوشه درون‌ریزی کنید.", + "skip_unreadable": "خوانا نیست در فایل اوت‌لوک: {count}", + "keep_folders_top_hint": "در بالاترین سطح، صندوق ورودی، موارد ارسال‌شده، موارد حذف‌شده و مانند آن‌ها به پوشه‌های خودتان می‌روند." }, "mail_export": { "menu_export": "برون‌بری پوشه…", diff --git a/locales/fr/common.json b/locales/fr/common.json index b1169e59d..4da3ec0a0 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -5267,7 +5267,7 @@ "target_label": "Importer dans", "target_top": "Niveau supérieur", "keep_folders": "Recréer les dossiers du fichier", - "keep_folders_hint": "Les dossiers du même nom sont réutilisés. Boîte de réception, Éléments envoyés et Éléments supprimés au niveau supérieur vont dans les vôtres.", + "keep_folders_hint": "Les dossiers du même nom sont réutilisés.", "resume": "Reprendre là où le dernier import de ces fichiers s'est arrêté (messages déjà traités : {count})", "quota_note": "Beaucoup de serveurs n'acceptent qu'environ 1000 messages ou 50 Mo par heure et par utilisateur. Un import plus grand se met en pause puis reprend tout seul.", "start": "Importer", @@ -5309,7 +5309,9 @@ "error_failures": "Trop de messages ont échoué à la suite, l'import s'est donc arrêté. Importez à nouveau les mêmes fichiers dans le même dossier plus tard pour reprendre.", "error_network": "La connexion au serveur a échoué plusieurs fois, l'import s'est donc arrêté. Importez à nouveau les mêmes fichiers dans le même dossier pour reprendre.", "error_busy": "Ces fichiers sont déjà en cours d'import dans un autre onglet.", - "retry_hint": "Importez à nouveau les mêmes fichiers dans le même dossier pour réessayer ceux qui ont échoué." + "retry_hint": "Importez à nouveau les mêmes fichiers dans le même dossier pour réessayer ceux qui ont échoué.", + "skip_unreadable": "Illisibles dans le fichier Outlook : {count}", + "keep_folders_top_hint": "Au niveau supérieur, Boîte de réception, Éléments envoyés, Éléments supprimés et les autres vont dans les vôtres." }, "mail_export": { "menu_export": "Exporter le dossier…", diff --git a/locales/he/common.json b/locales/he/common.json index 2d16c53ca..52d2e767e 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -5398,7 +5398,7 @@ "target_label": "ייבוא אל", "target_top": "רמה עליונה", "keep_folders": "יצירה מחדש של התיקיות מהקובץ", - "keep_folders_hint": "תיקיות באותו שם ישמשו שוב. דואר נכנס, פריטים שנשלחו ופריטים שנמחקו ברמה העליונה ייכנסו לתיקיות שלכם.", + "keep_folders_hint": "תיקיות באותו שם ישמשו שוב.", "resume": "להמשיך מהמקום שבו נעצר הייבוא האחרון של הקבצים האלה (הודעות שכבר טופלו: {count})", "quota_note": "שרתים רבים מקבלים ממשתמש אחד רק כ-1000 הודעות או 50 MB בשעה. ייבוא גדול יותר נעצר לזמן מה ואז ממשיך מעצמו.", "start": "ייבוא", @@ -5440,7 +5440,9 @@ "error_failures": "יותר מדי הודעות ברצף נכשלו, ולכן הייבוא נעצר. ייבאו שוב מאוחר יותר את אותם קבצים לאותה תיקייה כדי להמשיך.", "error_network": "החיבור לשרת נכשל שוב ושוב, ולכן הייבוא נעצר. ייבאו שוב את אותם קבצים לאותה תיקייה כדי להמשיך.", "error_busy": "הקבצים האלה כבר מיובאים בכרטיסייה אחרת.", - "retry_hint": "ייבאו שוב את אותם קבצים לאותה תיקייה כדי לנסות שוב את אלה שנכשלו." + "retry_hint": "ייבאו שוב את אותם קבצים לאותה תיקייה כדי לנסות שוב את אלה שנכשלו.", + "skip_unreadable": "לא קריאות בקובץ ה-Outlook: {count}", + "keep_folders_top_hint": "ברמה העליונה, דואר נכנס, פריטים שנשלחו, פריטים שנמחקו ודומיהם נכנסים לתיקיות שלכם." }, "mail_export": { "menu_export": "ייצוא תיקייה…", diff --git a/locales/hu/common.json b/locales/hu/common.json index 5b8daff55..da80ac27e 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -5397,7 +5397,7 @@ "target_label": "Importálás ide", "target_top": "Legfelső szint", "keep_folders": "A fájl mappáinak újbóli létrehozása", - "keep_folders_hint": "Az azonos nevű mappák újra felhasználódnak. A legfelső szinten lévő Beérkezett üzenetek, Elküldött elemek és Törölt elemek a saját mappáiba kerülnek.", + "keep_folders_hint": "Az azonos nevű mappák újra felhasználódnak.", "resume": "Folytatás ott, ahol e fájlok legutóbbi importálása megállt (már kész üzenetek: {count})", "quota_note": "Sok szerver felhasználónként óránként csak körülbelül 1000 üzenetet vagy 50 MB-ot fogad. A nagyobb importálás szünetel, majd magától folytatódik.", "start": "Importálás", @@ -5439,7 +5439,9 @@ "error_failures": "Túl sok üzenet hiúsult meg egymás után, ezért az importálás leállt. Később importálja újra ugyanazokat a fájlokat ugyanabba a mappába a folytatáshoz.", "error_network": "A kapcsolat a szerverrel többször megszakadt, ezért az importálás leállt. Importálja újra ugyanazokat a fájlokat ugyanabba a mappába a folytatáshoz.", "error_busy": "Ezeket a fájlokat már importálja egy másik lap.", - "retry_hint": "Importálja újra ugyanazokat a fájlokat ugyanabba a mappába, hogy a sikertelenekkel újra próbálkozzon." + "retry_hint": "Importálja újra ugyanazokat a fájlokat ugyanabba a mappába, hogy a sikertelenekkel újra próbálkozzon.", + "skip_unreadable": "Olvashatatlan az Outlook-fájlban: {count}", + "keep_folders_top_hint": "A legfelső szinten a Beérkezett üzenetek, az Elküldött elemek, a Törölt elemek és a hasonlók a saját mappáiba kerülnek." }, "mail_export": { "menu_export": "Mappa exportálása…", diff --git a/locales/it/common.json b/locales/it/common.json index 9a80d3a0f..aec4abfe5 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -5267,7 +5267,7 @@ "target_label": "Importa in", "target_top": "Livello principale", "keep_folders": "Ricrea le cartelle del file", - "keep_folders_hint": "Le cartelle con lo stesso nome vengono riutilizzate. Posta in arrivo, Posta inviata ed Elementi eliminati al livello principale finiscono nelle tue.", + "keep_folders_hint": "Le cartelle con lo stesso nome vengono riutilizzate.", "resume": "Riprendi da dove si è fermata l'ultima importazione di questi file (messaggi già elaborati: {count})", "quota_note": "Molti server accettano solo circa 1000 messaggi o 50 MB all'ora per utente. Un'importazione più grande va in pausa e poi riprende da sola.", "start": "Importa", @@ -5309,7 +5309,9 @@ "error_failures": "Troppi messaggi di fila non sono riusciti, quindi l'importazione si è fermata. Importa di nuovo gli stessi file nella stessa cartella più tardi per continuare.", "error_network": "La connessione al server non è riuscita più volte, quindi l'importazione si è fermata. Importa di nuovo gli stessi file nella stessa cartella per continuare.", "error_busy": "Questi file sono già in importazione in un'altra scheda.", - "retry_hint": "Importa di nuovo gli stessi file nella stessa cartella per riprovare quelli non riusciti." + "retry_hint": "Importa di nuovo gli stessi file nella stessa cartella per riprovare quelli non riusciti.", + "skip_unreadable": "Illeggibili nel file di Outlook: {count}", + "keep_folders_top_hint": "Al livello principale, Posta in arrivo, Posta inviata, Elementi eliminati e simili finiscono nelle tue." }, "mail_export": { "menu_export": "Esporta cartella…", diff --git a/locales/ja/common.json b/locales/ja/common.json index 357bb8cdd..f5b9e1ce9 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -5267,7 +5267,7 @@ "target_label": "インポート先", "target_top": "最上位", "keep_folders": "ファイルのフォルダーを作り直す", - "keep_folders_hint": "同じ名前のフォルダーはそのまま使います。最上位の受信トレイ、送信済みアイテム、削除済みアイテムはご自身のフォルダーに入ります。", + "keep_folders_hint": "同じ名前のフォルダーはそのまま使います。", "resume": "これらのファイルの前回のインポートが止まったところから続ける(処理済みのメッセージ: {count})", "quota_note": "多くのサーバーは 1 人のユーザーから 1 時間に約 1000 通または 50 MB までしか受け付けません。それより大きいインポートは一時停止し、その後自動で再開します。", "start": "インポート", @@ -5309,7 +5309,9 @@ "error_failures": "続けて失敗したメッセージが多すぎるため、インポートを停止しました。後で同じファイルを同じフォルダーにもう一度インポートすると続きから再開します。", "error_network": "サーバーへの接続が何度も失敗したため、インポートを停止しました。同じファイルを同じフォルダーにもう一度インポートすると続きから再開します。", "error_busy": "これらのファイルは別のタブですでにインポート中です。", - "retry_hint": "失敗したものをもう一度試すには、同じファイルを同じフォルダーにもう一度インポートしてください。" + "retry_hint": "失敗したものをもう一度試すには、同じファイルを同じフォルダーにもう一度インポートしてください。", + "skip_unreadable": "Outlook ファイル内で読めないもの: {count}", + "keep_folders_top_hint": "最上位では、受信トレイ、送信済みアイテム、削除済みアイテムなどはご自身のフォルダーに入ります。" }, "mail_export": { "menu_export": "フォルダーをエクスポート…", diff --git a/locales/ko/common.json b/locales/ko/common.json index 8df89afd8..b30157e11 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -5267,7 +5267,7 @@ "target_label": "가져올 위치", "target_top": "최상위", "keep_folders": "파일의 폴더를 다시 만들기", - "keep_folders_hint": "이름이 같은 폴더는 다시 사용합니다. 최상위의 받은 편지함, 보낸 편지함, 지운 편지함은 사용자의 폴더로 들어갑니다.", + "keep_folders_hint": "이름이 같은 폴더는 다시 사용합니다.", "resume": "이 파일들의 마지막 가져오기가 멈춘 곳부터 계속 (이미 처리한 메시지: {count})", "quota_note": "많은 서버는 사용자 한 명에게서 시간당 약 1000개의 메시지 또는 50MB만 받습니다. 더 큰 가져오기는 잠시 멈췄다가 저절로 계속됩니다.", "start": "가져오기", @@ -5309,7 +5309,9 @@ "error_failures": "연속으로 실패한 메시지가 너무 많아 가져오기를 멈췄습니다. 나중에 같은 파일을 같은 폴더로 다시 가져오면 이어서 진행합니다.", "error_network": "서버 연결이 계속 실패해 가져오기를 멈췄습니다. 같은 파일을 같은 폴더로 다시 가져오면 이어서 진행합니다.", "error_busy": "이 파일은 이미 다른 탭에서 가져오는 중입니다.", - "retry_hint": "실패한 메시지를 다시 시도하려면 같은 파일을 같은 폴더로 다시 가져오세요." + "retry_hint": "실패한 메시지를 다시 시도하려면 같은 파일을 같은 폴더로 다시 가져오세요.", + "skip_unreadable": "Outlook 파일에서 읽을 수 없음: {count}", + "keep_folders_top_hint": "최상위에서는 받은 편지함, 보낸 편지함, 지운 편지함 등이 사용자의 폴더로 들어갑니다." }, "mail_export": { "menu_export": "폴더 내보내기…", diff --git a/locales/lv/common.json b/locales/lv/common.json index 0c117a2ca..6c7228cf4 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -5267,7 +5267,7 @@ "target_label": "Importēt mapē", "target_top": "Augšējais līmenis", "keep_folders": "Atjaunot mapes no faila", - "keep_folders_hint": "Mapes ar tādu pašu nosaukumu tiek izmantotas atkārtoti. Augšējā līmeņa Iesūtne, Nosūtītie un Dzēstie nonāk jūsu pašu mapēs.", + "keep_folders_hint": "Mapes ar tādu pašu nosaukumu tiek izmantotas atkārtoti.", "resume": "Turpināt no vietas, kur šo failu pēdējā importēšana apstājās (jau apstrādāti ziņojumi: {count})", "quota_note": "Daudzi serveri no viena lietotāja pieņem tikai aptuveni 1000 ziņojumu vai 50 MB stundā. Lielāka importēšana apstājas un pēc tam turpinās pati.", "start": "Importēt", @@ -5309,7 +5309,9 @@ "error_failures": "Pārāk daudz ziņojumu pēc kārtas neizdevās, tāpēc importēšana apstājās. Vēlāk vēlreiz importējiet tos pašus failus tajā pašā mapē, lai turpinātu.", "error_network": "Savienojums ar serveri atkārtoti neizdevās, tāpēc importēšana apstājās. Vēlreiz importējiet tos pašus failus tajā pašā mapē, lai turpinātu.", "error_busy": "Šie faili jau tiek importēti citā cilnē.", - "retry_hint": "Vēlreiz importējiet tos pašus failus tajā pašā mapē, lai vēlreiz mēģinātu neizdevušos." + "retry_hint": "Vēlreiz importējiet tos pašus failus tajā pašā mapē, lai vēlreiz mēģinātu neizdevušos.", + "skip_unreadable": "Outlook failā nenolasāmi: {count}", + "keep_folders_top_hint": "Augšējā līmenī Iesūtne, Nosūtītie, Dzēstie un līdzīgi nonāk jūsu pašu mapēs." }, "mail_export": { "menu_export": "Eksportēt mapi…", diff --git a/locales/mn/common.json b/locales/mn/common.json index f3c1ef0b6..4c3add596 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -5397,7 +5397,7 @@ "target_label": "Импортлох газар", "target_top": "Дээд түвшин", "keep_folders": "Файлын хавтаснуудыг дахин үүсгэх", - "keep_folders_hint": "Ижил нэртэй хавтсыг дахин ашиглана. Дээд түвшний Ирсэн, Илгээсэн, Устгасан хавтаснууд таны өөрийнх рүү орно.", + "keep_folders_hint": "Ижил нэртэй хавтсыг дахин ашиглана.", "resume": "Эдгээр файлын сүүлийн импорт зогссон газраас үргэлжлүүлэх (аль хэдийн хийсэн захидал: {count})", "quota_note": "Олон сервер нэг хэрэглэгчээс цагт ойролцоогоор 1000 захидал эсвэл 50 MB л хүлээн авдаг. Үүнээс том импорт түр зогсоод дараа нь өөрөө үргэлжилнэ.", "start": "Импортлох", @@ -5439,7 +5439,9 @@ "error_failures": "Дараалан хэт олон захидал амжилтгүй болсон тул импорт зогслоо. Үргэлжлүүлэхийн тулд дараа нь ижил файлуудыг ижил хавтас руу дахин импортлоно уу.", "error_network": "Сервертэй холболт удаа дараа тасарсан тул импорт зогслоо. Үргэлжлүүлэхийн тулд ижил файлуудыг ижил хавтас руу дахин импортлоно уу.", "error_busy": "Эдгээр файлыг өөр табд аль хэдийн импортлож байна.", - "retry_hint": "Амжилтгүй болсон захидлуудыг дахин оролдохын тулд ижил файлуудыг ижил хавтас руу дахин импортлоно уу." + "retry_hint": "Амжилтгүй болсон захидлуудыг дахин оролдохын тулд ижил файлуудыг ижил хавтас руу дахин импортлоно уу.", + "skip_unreadable": "Outlook файлд уншигдахгүй: {count}", + "keep_folders_top_hint": "Дээд түвшинд Ирсэн, Илгээсэн, Устгасан болон түүнтэй адил хавтаснууд таны өөрийнх рүү орно." }, "mail_export": { "menu_export": "Хавтас экспортлох…", diff --git a/locales/nb/common.json b/locales/nb/common.json index d1bb21c19..835c30835 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -5397,7 +5397,7 @@ "target_label": "Importer til", "target_top": "Øverste nivå", "keep_folders": "Gjenskap mappene fra filen", - "keep_folders_hint": "Mapper med samme navn brukes på nytt. Innboks, Sendte elementer og Slettede elementer på øverste nivå havner i dine egne.", + "keep_folders_hint": "Mapper med samme navn brukes på nytt.", "resume": "Fortsett der forrige import av disse filene stoppet (meldinger allerede ferdige: {count})", "quota_note": "Mange servere tar bare imot rundt 1000 meldinger eller 50 MB i timen fra én bruker. En større import tar en pause og fortsetter så av seg selv.", "start": "Importer", @@ -5439,7 +5439,9 @@ "error_failures": "For mange meldinger på rad mislyktes, så importen stoppet. Importer de samme filene til samme mappe igjen senere for å fortsette.", "error_network": "Tilkoblingen til serveren mislyktes gang på gang, så importen stoppet. Importer de samme filene til samme mappe igjen for å fortsette.", "error_busy": "Disse filene importeres allerede i en annen fane.", - "retry_hint": "Importer de samme filene til samme mappe igjen for å prøve de som mislyktes, på nytt." + "retry_hint": "Importer de samme filene til samme mappe igjen for å prøve de som mislyktes, på nytt.", + "skip_unreadable": "Uleselige i Outlook-filen: {count}", + "keep_folders_top_hint": "På øverste nivå havner Innboks, Sendte elementer, Slettede elementer og lignende i dine egne." }, "mail_export": { "menu_export": "Eksporter mappe…", diff --git a/locales/nl/common.json b/locales/nl/common.json index c60b4c431..064c3eb48 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -5267,7 +5267,7 @@ "target_label": "Importeren in", "target_top": "Hoogste niveau", "keep_folders": "De mappen uit het bestand overnemen", - "keep_folders_hint": "Mappen met dezelfde naam worden opnieuw gebruikt. Postvak IN, Verzonden items en Verwijderde items op het hoogste niveau gaan naar die van u.", + "keep_folders_hint": "Mappen met dezelfde naam worden opnieuw gebruikt.", "resume": "Doorgaan waar de laatste import van deze bestanden stopte (al verwerkte berichten: {count})", "quota_note": "Veel servers nemen per gebruiker maar ongeveer 1000 berichten of 50 MB per uur aan. Een grotere import pauzeert en gaat daarna vanzelf verder.", "start": "Importeren", @@ -5309,7 +5309,9 @@ "error_failures": "Te veel berichten na elkaar zijn mislukt, dus de import is gestopt. Importeer dezelfde bestanden later opnieuw in dezelfde map om verder te gaan.", "error_network": "De verbinding met de server mislukte steeds, dus de import is gestopt. Importeer dezelfde bestanden opnieuw in dezelfde map om verder te gaan.", "error_busy": "Deze bestanden worden al in een ander tabblad geïmporteerd.", - "retry_hint": "Importeer dezelfde bestanden opnieuw in dezelfde map om de mislukte nog eens te proberen." + "retry_hint": "Importeer dezelfde bestanden opnieuw in dezelfde map om de mislukte nog eens te proberen.", + "skip_unreadable": "Onleesbaar in het Outlook-bestand: {count}", + "keep_folders_top_hint": "Op het hoogste niveau gaan Postvak IN, Verzonden items, Verwijderde items en dergelijke naar die van u." }, "mail_export": { "menu_export": "Map exporteren…", diff --git a/locales/pl/common.json b/locales/pl/common.json index 99d7ea5f0..69309dec6 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -5267,7 +5267,7 @@ "target_label": "Importuj do", "target_top": "Najwyższy poziom", "keep_folders": "Odtwórz foldery z pliku", - "keep_folders_hint": "Foldery o tej samej nazwie są używane ponownie. Skrzynka odbiorcza, Elementy wysłane i Elementy usunięte na najwyższym poziomie trafiają do Twoich własnych.", + "keep_folders_hint": "Foldery o tej samej nazwie są używane ponownie.", "resume": "Kontynuuj tam, gdzie zatrzymał się ostatni import tych plików (wiadomości już zrobione: {count})", "quota_note": "Wiele serwerów przyjmuje od jednego użytkownika tylko około 1000 wiadomości lub 50 MB na godzinę. Większy import robi przerwę, a potem sam działa dalej.", "start": "Importuj", @@ -5309,7 +5309,9 @@ "error_failures": "Zbyt wiele wiadomości z rzędu się nie udało, więc import się zatrzymał. Zaimportuj te same pliki później ponownie do tego samego folderu, aby kontynuować.", "error_network": "Połączenie z serwerem wielokrotnie zawiodło, więc import się zatrzymał. Zaimportuj te same pliki ponownie do tego samego folderu, aby kontynuować.", "error_busy": "Te pliki są już importowane w innej karcie.", - "retry_hint": "Zaimportuj te same pliki ponownie do tego samego folderu, aby jeszcze raz spróbować tych, które się nie udały." + "retry_hint": "Zaimportuj te same pliki ponownie do tego samego folderu, aby jeszcze raz spróbować tych, które się nie udały.", + "skip_unreadable": "Nieczytelne w pliku programu Outlook: {count}", + "keep_folders_top_hint": "Na najwyższym poziomie Skrzynka odbiorcza, Elementy wysłane, Elementy usunięte i podobne trafiają do Twoich własnych." }, "mail_export": { "menu_export": "Eksportuj folder…", diff --git a/locales/pt/common.json b/locales/pt/common.json index 9c0f43c32..c526a3494 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -5267,7 +5267,7 @@ "target_label": "Importar para", "target_top": "Nível superior", "keep_folders": "Recriar as pastas do arquivo", - "keep_folders_hint": "Pastas com o mesmo nome são reutilizadas. Caixa de entrada, Itens enviados e Itens excluídos no nível superior vão para as suas.", + "keep_folders_hint": "Pastas com o mesmo nome são reutilizadas.", "resume": "Continuar de onde a última importação destes arquivos parou (mensagens já processadas: {count})", "quota_note": "Muitos servidores aceitam só cerca de 1000 mensagens ou 50 MB por hora de cada usuário. Uma importação maior faz uma pausa e depois continua sozinha.", "start": "Importar", @@ -5309,7 +5309,9 @@ "error_failures": "Muitas mensagens falharam seguidas, por isso a importação parou. Importe os mesmos arquivos na mesma pasta mais tarde para continuar.", "error_network": "A conexão com o servidor falhou várias vezes, por isso a importação parou. Importe os mesmos arquivos na mesma pasta outra vez para continuar.", "error_busy": "Estes arquivos já estão sendo importados em outra aba.", - "retry_hint": "Importe os mesmos arquivos na mesma pasta outra vez para tentar de novo os que falharam." + "retry_hint": "Importe os mesmos arquivos na mesma pasta outra vez para tentar de novo os que falharam.", + "skip_unreadable": "Ilegíveis no arquivo do Outlook: {count}", + "keep_folders_top_hint": "No nível superior, Caixa de entrada, Itens enviados, Itens excluídos e similares vão para as suas." }, "mail_export": { "menu_export": "Exportar pasta…", diff --git a/locales/ro/common.json b/locales/ro/common.json index 285eb455f..3ce4c4f98 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -5397,7 +5397,7 @@ "target_label": "Importă în", "target_top": "Nivelul de sus", "keep_folders": "Recreează dosarele din fișier", - "keep_folders_hint": "Dosarele cu același nume sunt refolosite. Inbox, Elemente trimise și Elemente șterse de la nivelul de sus ajung în ale dumneavoastră.", + "keep_folders_hint": "Dosarele cu același nume sunt refolosite.", "resume": "Continuă de unde s-a oprit ultimul import al acestor fișiere (mesaje deja procesate: {count})", "quota_note": "Multe servere acceptă de la un utilizator doar circa 1000 de mesaje sau 50 MB pe oră. Un import mai mare face o pauză și apoi continuă singur.", "start": "Importă", @@ -5439,7 +5439,9 @@ "error_failures": "Prea multe mesaje la rând au eșuat, așa că importul s-a oprit. Importați mai târziu aceleași fișiere în același dosar pentru a continua.", "error_network": "Conexiunea la server a eșuat de mai multe ori, așa că importul s-a oprit. Importați din nou aceleași fișiere în același dosar pentru a continua.", "error_busy": "Aceste fișiere se importă deja în altă filă.", - "retry_hint": "Importați din nou aceleași fișiere în același dosar pentru a reîncerca mesajele care au eșuat." + "retry_hint": "Importați din nou aceleași fișiere în același dosar pentru a reîncerca mesajele care au eșuat.", + "skip_unreadable": "Ilizibile în fișierul Outlook: {count}", + "keep_folders_top_hint": "La nivelul de sus, Inbox, Elemente trimise, Elemente șterse și altele asemenea ajung în ale dumneavoastră." }, "mail_export": { "menu_export": "Exportă dosarul…", diff --git a/locales/ru/common.json b/locales/ru/common.json index 60d2080a4..dd7684803 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -5267,7 +5267,7 @@ "target_label": "Импортировать в", "target_top": "Верхний уровень", "keep_folders": "Воссоздать папки из файла", - "keep_folders_hint": "Папки с тем же именем используются повторно. Входящие, Отправленные и Удалённые на верхнем уровне попадают в ваши собственные.", + "keep_folders_hint": "Папки с тем же именем используются повторно.", "resume": "Продолжить с места, где остановился прошлый импорт этих файлов (уже обработано сообщений: {count})", "quota_note": "Многие серверы принимают от одного пользователя лишь около 1000 сообщений или 50 МБ в час. Большой импорт приостанавливается и затем продолжается сам.", "start": "Импортировать", @@ -5309,7 +5309,9 @@ "error_failures": "Слишком много сообщений подряд не удалось импортировать, поэтому импорт остановлен. Позже снова импортируйте те же файлы в ту же папку, чтобы продолжить.", "error_network": "Соединение с сервером несколько раз прерывалось, поэтому импорт остановлен. Снова импортируйте те же файлы в ту же папку, чтобы продолжить.", "error_busy": "Эти файлы уже импортируются в другой вкладке.", - "retry_hint": "Снова импортируйте те же файлы в ту же папку, чтобы ещё раз попробовать неудавшиеся." + "retry_hint": "Снова импортируйте те же файлы в ту же папку, чтобы ещё раз попробовать неудавшиеся.", + "skip_unreadable": "Не читаются в файле Outlook: {count}", + "keep_folders_top_hint": "На верхнем уровне Входящие, Отправленные, Удалённые и подобные попадают в ваши собственные." }, "mail_export": { "menu_export": "Экспортировать папку…", diff --git a/locales/sk/common.json b/locales/sk/common.json index 56dd2f3fb..f44c36f36 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -5397,7 +5397,7 @@ "target_label": "Importovať do", "target_top": "Najvyššia úroveň", "keep_folders": "Znovu vytvoriť priečinky zo súboru", - "keep_folders_hint": "Priečinky s rovnakým názvom sa použijú znova. Doručená pošta, Odoslaná pošta a Odstránená pošta na najvyššej úrovni skončia vo vašich vlastných.", + "keep_folders_hint": "Priečinky s rovnakým názvom sa použijú znova.", "resume": "Pokračovať tam, kde sa posledný import týchto súborov zastavil (už hotové správy: {count})", "quota_note": "Mnohé servery prijímajú od jedného používateľa len asi 1000 správ alebo 50 MB za hodinu. Väčší import sa pozastaví a potom pokračuje sám.", "start": "Importovať", @@ -5439,7 +5439,9 @@ "error_failures": "Príliš veľa správ za sebou zlyhalo, preto sa import zastavil. Importujte rovnaké súbory neskôr znova do rovnakého priečinka, aby ste pokračovali.", "error_network": "Spojenie so serverom opakovane zlyhalo, preto sa import zastavil. Importujte rovnaké súbory znova do rovnakého priečinka, aby ste pokračovali.", "error_busy": "Tieto súbory sa už importujú na inej karte.", - "retry_hint": "Importujte rovnaké súbory znova do rovnakého priečinka, aby ste skúsili neúspešné ešte raz." + "retry_hint": "Importujte rovnaké súbory znova do rovnakého priečinka, aby ste skúsili neúspešné ešte raz.", + "skip_unreadable": "V súbore Outlooku nečitateľné: {count}", + "keep_folders_top_hint": "Na najvyššej úrovni skončia Doručená pošta, Odoslaná pošta, Odstránená pošta a podobné vo vašich vlastných." }, "mail_export": { "menu_export": "Exportovať priečinok…", diff --git a/locales/th/common.json b/locales/th/common.json index 603c2bc09..39a645601 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -414,7 +414,7 @@ "target_label": "นำเข้าไปที่", "target_top": "ระดับบนสุด", "keep_folders": "สร้างโฟลเดอร์จากไฟล์ขึ้นใหม่", - "keep_folders_hint": "โฟลเดอร์ที่ชื่อเดียวกันจะถูกใช้ซ้ำ กล่องจดหมายเข้า รายการที่ส่ง และรายการที่ลบในระดับบนสุดจะเข้าไปในโฟลเดอร์ของคุณเอง", + "keep_folders_hint": "โฟลเดอร์ที่ชื่อเดียวกันจะถูกใช้ซ้ำ", "resume": "ทำต่อจากจุดที่การนำเข้าไฟล์เหล่านี้ครั้งล่าสุดหยุดไว้ (ข้อความที่ทำแล้ว: {count})", "quota_note": "เซิร์ฟเวอร์หลายแห่งรับจากผู้ใช้หนึ่งคนได้เพียงประมาณ 1000 ข้อความหรือ 50 MB ต่อชั่วโมง การนำเข้าที่ใหญ่กว่านี้จะหยุดพักแล้วทำต่อเอง", "start": "นำเข้า", @@ -456,7 +456,9 @@ "error_failures": "ข้อความล้มเหลวติดต่อกันมากเกินไป การนำเข้าจึงหยุดลง นำเข้าไฟล์เดิมไปยังโฟลเดอร์เดิมอีกครั้งภายหลังเพื่อทำต่อ", "error_network": "การเชื่อมต่อกับเซิร์ฟเวอร์ล้มเหลวซ้ำๆ การนำเข้าจึงหยุดลง นำเข้าไฟล์เดิมไปยังโฟลเดอร์เดิมอีกครั้งเพื่อทำต่อ", "error_busy": "ไฟล์เหล่านี้กำลังถูกนำเข้าในอีกแท็บหนึ่งอยู่แล้ว", - "retry_hint": "นำเข้าไฟล์เดิมไปยังโฟลเดอร์เดิมอีกครั้งเพื่อลองข้อความที่ล้มเหลวอีกรอบ" + "retry_hint": "นำเข้าไฟล์เดิมไปยังโฟลเดอร์เดิมอีกครั้งเพื่อลองข้อความที่ล้มเหลวอีกรอบ", + "skip_unreadable": "อ่านไม่ได้ในไฟล์ Outlook: {count}", + "keep_folders_top_hint": "ในระดับบนสุด กล่องจดหมายเข้า รายการที่ส่ง รายการที่ลบ และที่คล้ายกันจะเข้าไปในโฟลเดอร์ของคุณเอง" }, "mail_export": { "menu_export": "ส่งออกโฟลเดอร์…", diff --git a/locales/tr/common.json b/locales/tr/common.json index 227075d45..8fea33739 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -5397,7 +5397,7 @@ "target_label": "Şuraya aktar", "target_top": "En üst düzey", "keep_folders": "Dosyadaki klasörleri yeniden oluştur", - "keep_folders_hint": "Aynı adlı klasörler yeniden kullanılır. En üst düzeydeki Gelen Kutusu, Gönderilmiş Öğeler ve Silinmiş Öğeler sizinkilere gider.", + "keep_folders_hint": "Aynı adlı klasörler yeniden kullanılır.", "resume": "Bu dosyaların son içe aktarımının durduğu yerden devam et (zaten işlenen iletiler: {count})", "quota_note": "Birçok sunucu bir kullanıcıdan saatte yalnızca yaklaşık 1000 ileti veya 50 MB kabul eder. Daha büyük bir içe aktarım duraklar ve sonra kendiliğinden devam eder.", "start": "İçe aktar", @@ -5439,7 +5439,9 @@ "error_failures": "Arka arkaya çok fazla ileti başarısız olduğundan içe aktarım durdu. Devam etmek için aynı dosyaları daha sonra aynı klasöre yeniden içe aktarın.", "error_network": "Sunucuya bağlantı tekrar tekrar başarısız olduğundan içe aktarım durdu. Devam etmek için aynı dosyaları aynı klasöre yeniden içe aktarın.", "error_busy": "Bu dosyalar zaten başka bir sekmede içe aktarılıyor.", - "retry_hint": "Başarısız olanları bir kez daha denemek için aynı dosyaları aynı klasöre yeniden içe aktarın." + "retry_hint": "Başarısız olanları bir kez daha denemek için aynı dosyaları aynı klasöre yeniden içe aktarın.", + "skip_unreadable": "Outlook dosyasında okunamayan: {count}", + "keep_folders_top_hint": "En üst düzeyde Gelen Kutusu, Gönderilmiş Öğeler, Silinmiş Öğeler ve benzerleri sizinkilere gider." }, "mail_export": { "menu_export": "Klasörü dışa aktar…", diff --git a/locales/uk/common.json b/locales/uk/common.json index 904ebfec3..4a64a248d 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -5267,7 +5267,7 @@ "target_label": "Імпортувати до", "target_top": "Верхній рівень", "keep_folders": "Відтворити теки з файлу", - "keep_folders_hint": "Теки з тією самою назвою використовуються повторно. Вхідні, Надіслані та Видалені на верхньому рівні потрапляють до ваших власних.", + "keep_folders_hint": "Теки з тією самою назвою використовуються повторно.", "resume": "Продовжити з місця, де зупинився останній імпорт цих файлів (уже оброблено повідомлень: {count})", "quota_note": "Багато серверів приймають від одного користувача лише близько 1000 повідомлень або 50 МБ на годину. Більший імпорт призупиняється, а потім продовжується сам.", "start": "Імпортувати", @@ -5309,7 +5309,9 @@ "error_failures": "Забагато повідомлень поспіль не вдалося імпортувати, тому імпорт зупинено. Пізніше знову імпортуйте ті самі файли до тієї самої теки, щоб продовжити.", "error_network": "З'єднання із сервером кілька разів переривалося, тому імпорт зупинено. Знову імпортуйте ті самі файли до тієї самої теки, щоб продовжити.", "error_busy": "Ці файли вже імпортуються в іншій вкладці.", - "retry_hint": "Знову імпортуйте ті самі файли до тієї самої теки, щоб ще раз спробувати невдалі." + "retry_hint": "Знову імпортуйте ті самі файли до тієї самої теки, щоб ще раз спробувати невдалі.", + "skip_unreadable": "Не читаються у файлі Outlook: {count}", + "keep_folders_top_hint": "На верхньому рівні Вхідні, Надіслані, Видалені й подібні потрапляють до ваших власних." }, "mail_export": { "menu_export": "Експортувати теку…", diff --git a/locales/vi/common.json b/locales/vi/common.json index eb1c3aef7..6c71133cf 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -5409,7 +5409,7 @@ "target_label": "Nhập vào", "target_top": "Cấp cao nhất", "keep_folders": "Tạo lại các thư mục từ tệp", - "keep_folders_hint": "Thư mục cùng tên sẽ được dùng lại. Hộp thư đến, Thư đã gửi và Thư đã xóa ở cấp cao nhất sẽ vào thư mục của bạn.", + "keep_folders_hint": "Thư mục cùng tên sẽ được dùng lại.", "resume": "Tiếp tục từ chỗ lần nhập trước của các tệp này dừng lại (thư đã xử lý: {count})", "quota_note": "Nhiều máy chủ chỉ nhận khoảng 1000 thư hoặc 50 MB mỗi giờ từ một người dùng. Lần nhập lớn hơn sẽ tạm dừng rồi tự tiếp tục.", "start": "Nhập", @@ -5451,7 +5451,9 @@ "error_failures": "Quá nhiều thư liên tiếp không nhập được nên việc nhập đã dừng. Hãy nhập lại cùng các tệp vào cùng thư mục sau để tiếp tục.", "error_network": "Kết nối tới máy chủ liên tục thất bại nên việc nhập đã dừng. Hãy nhập lại cùng các tệp vào cùng thư mục để tiếp tục.", "error_busy": "Các tệp này đang được nhập ở một thẻ khác.", - "retry_hint": "Nhập lại cùng các tệp vào cùng thư mục để thử lại những thư không nhập được." + "retry_hint": "Nhập lại cùng các tệp vào cùng thư mục để thử lại những thư không nhập được.", + "skip_unreadable": "Không đọc được trong tệp Outlook: {count}", + "keep_folders_top_hint": "Ở cấp cao nhất, Hộp thư đến, Thư đã gửi, Thư đã xóa và các thư mục tương tự sẽ vào thư mục của bạn." }, "mail_export": { "menu_export": "Xuất thư mục…", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 35258a250..d4daa93f8 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -5397,7 +5397,7 @@ "target_label": "匯入到", "target_top": "最上層", "keep_folders": "依檔案重建資料夾", - "keep_folders_hint": "同名資料夾會重複使用。最上層的收件匣、寄件備份和刪除的郵件會進入您自己的資料夾。", + "keep_folders_hint": "同名資料夾會重複使用。", "resume": "從這些檔案上次匯入停止的地方繼續(已處理的郵件:{count})", "quota_note": "許多伺服器每小時只接受單一使用者約 1000 封郵件或 50 MB。較大的匯入會暫停,然後自動繼續。", "start": "匯入", @@ -5439,7 +5439,9 @@ "error_failures": "連續失敗的郵件太多,匯入已停止。請稍後把相同的檔案再次匯入同一個資料夾以繼續。", "error_network": "與伺服器的連線多次失敗,匯入已停止。請把相同的檔案再次匯入同一個資料夾以繼續。", "error_busy": "這些檔案已在另一個分頁中匯入。", - "retry_hint": "把相同的檔案再次匯入同一個資料夾,即可重試失敗的郵件。" + "retry_hint": "把相同的檔案再次匯入同一個資料夾,即可重試失敗的郵件。", + "skip_unreadable": "Outlook 檔案中無法讀取:{count}", + "keep_folders_top_hint": "在最上層,收件匣、寄件備份、刪除的郵件等會進入您自己的資料夾。" }, "mail_export": { "menu_export": "匯出資料夾…", diff --git a/locales/zh/common.json b/locales/zh/common.json index 3686dea1c..abf66607f 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -5267,7 +5267,7 @@ "target_label": "导入到", "target_top": "顶层", "keep_folders": "按文件重建文件夹", - "keep_folders_hint": "同名文件夹会被重复使用。顶层的收件箱、已发送邮件和已删除邮件会进入您自己的文件夹。", + "keep_folders_hint": "同名文件夹会被重复使用。", "resume": "从这些文件上次导入停止的地方继续(已处理的邮件:{count})", "quota_note": "很多服务器每小时只接受单个用户约 1000 封邮件或 50 MB。更大的导入会暂停,然后自动继续。", "start": "导入", @@ -5309,7 +5309,9 @@ "error_failures": "连续失败的邮件太多,导入已停止。请稍后把相同的文件再次导入同一个文件夹以继续。", "error_network": "与服务器的连接多次失败,导入已停止。请把相同的文件再次导入同一个文件夹以继续。", "error_busy": "这些文件已在另一个标签页中导入。", - "retry_hint": "把相同的文件再次导入同一个文件夹,即可重试失败的邮件。" + "retry_hint": "把相同的文件再次导入同一个文件夹,即可重试失败的邮件。", + "skip_unreadable": "Outlook 文件中无法读取:{count}", + "keep_folders_top_hint": "在顶层,收件箱、已发送邮件、已删除邮件等会进入您自己的文件夹。" }, "mail_export": { "menu_export": "导出文件夹…", From 9d4d3261da1bf428affcc5ab56031b90b6770794 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 06:50:13 +0100 Subject: [PATCH 15/22] fix: clear exported mail from the browser's storage and say plainly where an export goes - The private file an export writes in Firefox and Safari is deleted when the app starts and when someone signs in, not only at the next export, and at once on sign-out. Close keeps it until its download had time to finish. Each tab holds its own files under a Web Lock, so one tab's sweep no longer deletes another tab's running export. - A browser whose private file system can't write files (Safari before 26) is told the export stays in memory instead of being promised a download. - The export's live region announces only the phase and pauses; the count next to it no longer floods screen readers, and the format group is named once. --- .../__tests__/mail-export-dialog.test.tsx | 22 ++++ .../mail-transfer/mail-export-dialog.tsx | 21 ++-- .../mail-transfer/mail-import-dialog.tsx | 6 +- .../mail-transfer/mail-transfer-host.tsx | 11 +- lib/mail-export/__tests__/sinks.test.ts | 104 ++++++++++++++++++ lib/mail-export/sinks.ts | 84 ++++++++++++-- lib/sign-out-cleanup.ts | 2 +- stores/mail-transfer-store.ts | 15 ++- 8 files changed, 234 insertions(+), 31 deletions(-) create mode 100644 lib/mail-export/__tests__/sinks.test.ts diff --git a/components/mail-transfer/__tests__/mail-export-dialog.test.tsx b/components/mail-transfer/__tests__/mail-export-dialog.test.tsx index 53656da6f..bce120993 100644 --- a/components/mail-transfer/__tests__/mail-export-dialog.test.tsx +++ b/components/mail-transfer/__tests__/mail-export-dialog.test.tsx @@ -129,4 +129,26 @@ describe('MailExportDialog', () => { expect(screen.getByTestId('mail-transfer-chip').textContent).toContain('chip'); expect(controller.signal.aborted).toBe(false); }); + + it('keeps the running count out of the live region', () => { + render(); + act(() => { + openMailExport({ mailboxId: 'inbox' }); + useMailTransferStore.getState().startJob({ kind: 'export', controller: new AbortController(), progress: { phase: 'running', exported: 1, failed: 0, failures: [], total: 4, fraction: 0.25 } }); + }); + expect(screen.getByTestId('mail-export-status').textContent).toBe('running_title'); + expect(screen.getByTestId('mail-export-count').textContent).toBe('status_exported'); + }); + + it('says the export stays in memory where the private file system can\'t write files', () => { + vi.stubGlobal('navigator', { ...navigator, storage: { getDirectory: async () => ({}) } }); + vi.stubGlobal('FileSystemFileHandle', class {}); + try { + render(); + act(() => openMailExport()); + expect(screen.getByText('save_memory')).toBeTruthy(); + } finally { + vi.unstubAllGlobals(); + } + }); }); diff --git a/components/mail-transfer/mail-export-dialog.tsx b/components/mail-transfer/mail-export-dialog.tsx index af90c91a9..0f7ecf6c3 100644 --- a/components/mail-transfer/mail-export-dialog.tsx +++ b/components/mail-transfer/mail-export-dialog.tsx @@ -9,7 +9,7 @@ import { useAuthStore } from "@/stores/auth-store"; import { isJobActive, useMailTransferStore, type ExportRequest } from "@/stores/mail-transfer-store"; import type { ExportFormat, ExportProgress } from "@/lib/mail-export/engine"; import { safeName } from "@/lib/mail-export/engine"; -import { canPickSaveFile, memorySink, pickSaveFile, privateFileSink, sweepPrivateExports, type ExportSink } from "@/lib/mail-export/sinks"; +import { canPickSaveFile, canWritePrivateFile, memorySink, pickSaveFile, privateFileSink, sweepPrivateExports, type ExportSink } from "@/lib/mail-export/sinks"; import { formatFileSize } from "@/lib/utils"; import { formatClock, TransferBar } from "./transfer-progress"; import { startExport } from "./start-export"; @@ -21,8 +21,7 @@ function today(): string { /** Where the file will go in this browser, said before the export starts. */ function saveMode(): "picker" | "download" | "memory" { if (canPickSaveFile()) return "picker"; - const storage = typeof navigator !== "undefined" ? (navigator.storage as { getDirectory?: unknown } | undefined) : undefined; - return typeof storage?.getDirectory === "function" ? "download" : "memory"; + return canWritePrivateFile() ? "download" : "memory"; } export function MailExportDialog({ request }: { request: ExportRequest }) { @@ -103,7 +102,7 @@ export function MailExportDialog({ request }: { request: ExportRequest }) { {progress.phase === "done" && progress.file?.download && ( )} - + )} > @@ -120,14 +119,14 @@ export function MailExportDialog({ request }: { request: ExportRequest }) { return ( close()} size="full-on-phone" title={folder ? t("title_folder", { name: mailbox?.name ?? "" }) : t("title_account")} description={folder ? t("description_folder") : t("description_account")} testId="mail-export-dialog" footer={( <> - + )} @@ -135,7 +134,7 @@ export function MailExportDialog({ request }: { request: ExportRequest }) {
{t("format_label")} -
+
{formats.map((f) => (

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 z zO({QxK+qxBf3e^NtrN-9#w(>48yiuXV8}He7WhULMBCl-Slg;el@;T=<_d-u^fF%hTn6!a83Pa zQzt@%tX0egX8R~f)!dH!vTz?7rNEo!6$yw%S_&pV@^k^x@pt?S)C57T$)D{XO^g6c z!TEnyeoYhx&}GX>>LT;p8S7&+eC?YM(CgEy-pjqKGhPAGD1&)cpy1`QTZl^Kmx!dm zy8;3-_#awn0pt7vMXpW4k%WSt))wC^?Gc19 z_#hIt0^~nRJIHp|zk{6uICtG7*^MpqIG>ml1I)7WN7kg95B+w}Cn*AwcHGiS&!ktu zgpW}3w}=N$&qOrVZDL5dVWUkIRT^bp#e8NPR-YfGqQ+q-3p!ru07ninp$TdRmjjjA zn~ws`j|8et1FDhI>c3weQ&@6k9b>WLcbf+Hgh_Piy&c$KHMZ0zu9^mTC#{2aT)AQ; zkwFnio7j0n1&L_icn|!687=a|I1_sGX<|!@}sgQa4b4ZmvH3*@Q+t>+p6R^fE0*I z^ZNY7_0-i2TE)<%Q#z2iF+5s{3(NKdGfFyf-%>AJ_zV0SaRd0^NlnJ|fXfAXZ1!6R z148NHtJ?#oE2o5d0Vo)9D4E8(wHz*cZvg|OY;2zkT7lg}v0pV)aHjCPF+vJ1>8oH+ zE&cyc6_-K#{T}R6NzK_1I+`MqH7V#*}&VlfOxKxh?G9tP%@s=+$a)KJwXMD&y)D zAv#4K+feIwowpP8LSXitS+#kPaSwP_g%}At6d;gT|9K_ts8(r@WZCBfjMfnzFMb%4 z+oc+4JP!Dr8}@2Lzxix;_`4t1@Cw4W$<8OJs)$waiqqLr@Bln(;Q$kO)wC}1MNXl0S^4SeEnlHpysTwAeSlHDD$jF&F2#GkfUg`&*j z-j8F-GLVKu-rnX@`Z0cnJd26fFi@s9-4@5NH22a{?Up;Ud6E*(j?1YHQXMB5VB&A< zs|aff4`ir1*>MOn8G4cmWs>q6+4~mG|W}QeAQx9*=(pnv#tXqm9jlL ziOFYEL*(Zod-s3Y;36P29omw;7a!QzHl}*>Uk>G@Vg>#UDN#CX2oT+f)0mFMSk z7oA+EQg~dtyq6_u2_>O7w!1#q;2t2KW;9sdKIU-5@|olT$CH6|b@31;SG_|TrVOK_ zR@9fVv_JK3P|uRmY*2ZuQ^)sDUzem{R+0@-9M__SBhe9xe%U@L2ds&hO5!e}`L zViop)O2I(#o4Wm2-WJb$mU1uOqz|K(=bn5n|A)uyfpNA6n z+0iv_=1)1P%TC85HpqJd5Eu~TvznqSouH}6J`r3&k!&E#?1J(1?V=({$W9UABHgF* z{5(L|WrD?7DEjO?Voq8@LML=j#74t0MA>Gg4%&T@KD{Z*C@2HC|7NQTqxpaD$=9>` zEhq^|Kr+#kKxML&$8a>s7JRh&nRdigJQ_n@`57M870EV~U%E}ASMG_^p^Z1 zqvh-9;`Y17C_COS-zp}4c?VE+r;eHuo%1;LY^5tEF^s*A`GST-2d2D>=PWMi$~Q>^ zlW-EuEJASq+}v2&TcsKz+`oWNEb4mZx%6ZKK9T^)^B9mFSm>91$_rd1(C$#KC}CXT!H8P+lKV1$TsiI^qQUc~RnAKl z9b=5iJR^X@$!V6%W#YYBwh(qSA5{N$Inb?%NDl z!SC`s44Y36KN%t)=<5?rCe7&eAv>2ysU|zvzH+&_KHcd5qgdI6xP^(%#-cabv6M&`5 zxRP39-v^a)0~6tEpxA9j_2Dbbd{tFiCY3>v3w+x{{c2gn+7nXOW38d;)|M`2dgSXCef693aG+%T94k1n(PV`F^b_qY{}*)q zD}cRH8x-dm5^^jW){-ug&oqwNzbW~fq;ewhqFk*!_HDImC3l;asdg7q2O`E{(qOJ4 z7B=~7U1l+=g}thOc~90S`VWmR5v5*bd&@+x7BzI-RSBvU-&{zW9qJbOOhr4uwCv$} z)^>3&UXW3dmMH;cbSj@ZnPhQ{nC z{rzKv>-}tG^756uOncwgfG-9ctr2NDJX$4hdtbMU1vNNT8ecPnas3s9kw38E=n%o#-+|t0RpHu%F24V{mupQ_7Iisfj8+p zQGgfeLnW+w?;^y2h^DnsihIuMSKkW4X5HR;Te^EBZJ@aBMssOVXbj8sUtYv(^*hN+ zHI!Vs)mH`1AD-y4JX$?QKlT5)K5P59zD*h`c8w@Ar9^|{T&RB_gPN)-w(vca)}Q7n zS;VJl&q3?v$p&>VhYV-RgGUBT@-;hzSlEHcliH+afbk(naJR61l$#P8ff6i&lGz7wKpNUnAZvIbw_+oB3(Pkkx(sXm zOU!qXX&h3SMJhW_=5_wQUbgyg)&SJ#k6)yAG2k#?T+bs^AJ20!+!#49Xqyte%=`VJ$F$7-IoK0?-LxtaB01#qdZA%+Ro@le0Tup?5 zaL@2ap4{&2%CN1SP#sAu3qEZYZ!tCRE_&m^$AXnf?)JtP!{b=}uw-kbd5gwSp)wa0 zeR`}^qihf2+K>*fV~17psHG98bEMK@31WFbmvqX87!CFLD`(W|K#5ObaC2XaFTmVwar4 z2@So(-E%Ks;UzcP?LsC9e_3MEaMAsUYvN0WMMW%byGrY`66U)%M>(p-E8o$+IvC*a z6G%-?Oi|)WZ`g$yHrY(-IeJ(AqC-lF8!w2u(4f!_o4IdgV?d-}sp{7MYt`)|>|$xk zxdRlTqm(7D{`@^eTDPww8PbPfAatnTIKzIWLaly1)@Y9@JHkK1pBR?Wi|2qpJ%<_Y z<|x}q$m3?l4C4tqRLM^{dwy+>n#fAH3fmN+1n}}CC37{j15Ch=#Tn27T;7mBt$dcu9U%xSo+F&4TbU{qJ44rJCOd7C2FYQ=(SD+?a}TgkU7uP ziB~fMbdVQnB}Sa=i1UsjN5DBFJQf3Xj^SNyp!&Tnxpr-Wt6lgXu9KVd*n>NyKd$W!37TVDc9pftUZy0o7i>}w%;lUx=g&U5LULoh$8I0 zR_Z3@2bt;Kq(F2$meG2&RPQ#bvV%Aqh1R~w6W}Q*KazIM@4>^RBd(@}Eha?!`hIz( zajohTqt|raQtFz01?m{$iTCop>=40za#!`fnIo!bD#qz>6A@x1g1~UV66iQak|T@K z?mi%epc8B#v_C7vxOXZIYf6o2ehepM`;^1@*|JM5U?MMaX~jCdw~~W(0~*BgBh0kZ zoSJ5piHXH639cOOE~gB~*CKzu9LIf+!+YX}BqS|b)+;j>;sBbDE+`shINi2(9`o~k zBGm$TBEEHM04_S3%=)V>|8NBYj2VEAA~wUW3!wfD3;{TRGt^#gQ3f|$+%L}dh^vXX zlxqWHhkn}(3oJtes-JUb!x^W$&ELB#Q6r)w^?i)J_j3POSFCwl%iH`?Xn@@FGBxDq zi9^2^Dy=z&30bx~mTxx)dVgVBa-UAu2p#$^{$u zdWGsBM|Nq390{_g&x90p!tEfQVFkoYAy+vkU2lfue*~Ff)xXe(MrRBAXg3pc7{H71Y0uiVC^g`8+@M#vfith zq6d+Y!~E3X5v~?1ZhHH{1&0@rtXdQ4l5oMphdYr!WnV>ryW}}PXSGYvG51seXr~kx zc~e03%03Esy;*tbHy~B8z%}jR`ve*Ts6O|*P9o5guW2pH=I)6G-Ikk-$M>aq67^QK zmTK03Mh;kWa<}gMcWR_4`MckQx9CSHDyb1o|7nCTA6s1C$oX#GbQ0tIEh&t0V;b)Y z?e$MkJllTxj{Bp42dfy2yu7}vdRhCuqr`|{GDdmC{gY-t(f1a}=y(NEm3+gL3SyLX z70p!9l!E4HpJH_-_+msVVOm9tJ~fqzh{>70Tjjw5vCMxKH8Bq6+~mPWB8}PorxVU! zY`8tz{4ExgiVbFljS=mx7XeT&5j`P@zyGyxWS^A3_ILP!x#?BtAdDg}B)i%a(4z!B z@3937(u7KR{BrB4e(jY@r|xpaguN2^4C6+*+F9z61nqTIF4aInoO>#^(GVL>Vi)j8 zeAyrERQ6Hh<->$H4~XLITL3crE=c4Rt5haFr7G8B{e8wJBw288bzJ-S1Rb%4`1g1{ z(6lcvfo*u1hjZMW3&!0VrEXfpnZnq6efy;9BclPnQGcNJ~03qs}GduA+p; zEJt72$I&?oKH>{VLLX#w)dvqa&3yLWK=NR8RJuX_%OK+_eQ(?SuNhstir+Wz!srES zVqV_B=8=$7)0By+mO}f}LTfONMu{|}MEjdXF0qRq6Y ze0bs)daC6dkcAKb&w`aKd-8p!%hxVSJDXl>u?TXrTphE^EI*sWdXg##kb?;3PFZUe zu~h%}mS*RnjT?N54;E8$T{76^{h)m%hHb^Fgy6~ix0*Y*XH;DHf*1u5eNU}pm5dTs zkgV#JWpi}9fS-{m;&{W5(lX3Iys2fSzRhC3(In1c!zWv|T zbsPsGBNVc0TOQIXm5{Iq4!)*fP5~XGe%OFw5^qp`N4!i$ zK~TW2WHNnf#!8hPte*y`C#4VF2f<9xAO#Q5;e3&@CIk8fIYuT22JqV(%X{5K&8BC~ z6qNfYVY;=$3vDI!%fPb0YY6;6)ngT>*kfJ}xw$+tfekLUlao87y85j!3|Iv`of~YV zy)akjSl4NQT4FgftqPM?KE)UF2J8s8w|u*l*N(Ju`~BHW)%Aka2r;*tS%lufDzki4 z0j zTGknMM`(u?+upP5U@v`GMPpfRfSdqsYEKIw6@KjCO@Ldo&Y7f6S%m>8-FkZfiSMpI zN#^9usZX+($QsGsn1tb07mp>-jQZMO%1<$xg)eYl8BQ+=K$V0N7}Eb8VeyjKU`)|o zfYB3q@{Cm|wNsV;UTDL3J4r$JO;b^*#lnhD08n;`sC@^B#k@JR6lhbhDp-Hb5|OBd z--EN72B@LJ82-4S!wz5Nz@DElo)mr#sq2ka-9qi6>%duhKNh%y?B${lZB#WAWc7v4 zsXPA(v+trqSdW6H5;6ER-YI!;0B4185FpobDb?)EP5h~q4(D~7t;u-j+X&%tY_Uyd z4ksiV`lizCDCN{)1)_Kyuu2R0;_xIlGMhEr1wY$-G z^}6&yn$;4*9NCG0F@bV~E3$OIB=HIbHp&k`&Bi`I6xt-O2;5!SKF}`kAL8@ptpAFr z9RL6T0000bxs0&SKk`|YUx(zsJeH+ep0)3;;+L0b1}C^B3D7SAVu8&LkpmtqRvIQX zUy~)9#wBl%eQdR>ekS4eG`zm;;S32&vv_>;;}zRmkiLvW$$#N@f;sME;#5FH7_<7Z zhPEDZ(4Qo)Ae|3t7H?AIGV#iNv)E6M9q^QH`Z44F0U01aiQRsere=lkBZ5BqSjUlq9wA zooV#{nvweV&q|iW-&|sV1r8Ujv#3p+rqv0sl2c4QSY_D@?y~y6D|LyU^Yj&)zK}PR z@eAp}-PrXNMVeDwUrq)WzH0Jvj0u8jR9P5ZQ@~Pg9J-FP0Ma@|T(0t@98~vEnOYf} zeSNY z%J?hZ$I0@(&#@T)zVaHNw~dG0%(EM#mBi$erBvT#7f#SvW#tF{@cAn`%|Cr?#5eFjDxXioV@3loN|rl+d)Zk$b&&1Dv$B*o1T?z$3aZb zv!&J1(5D9gU*ILQ))>zL_`gXz7RQs_ez^h9vkqKGwFjf`_~)d1(6F2B|U()H8nbIC|(l-1O-PMxlnR2h7V}YcmJ8cHQ;&D#J z3@Fvd?j)BWlfUmrS7*&{FSg+34`Tcd5=BZ6eN)dCYfYaYmucO=?J!FmTL^C|+#09r zz9w9Qn^B-@xgHnH{R8X5{ zxM-o2XbY71xcm4nj32;xC~sj8J*hI6fceSR7X~JxFg}^lp}4xQx0L&$YLvPzp?XVT zmZ!}QqmNBDoo$QNv@BimKowEB4P|gtO}R*^b_httjQzoX>`@FDv$7K!$(etCWIwu+ zii??^K*IGPuq*EjLscmY7%Q>0>yY)yDp`^X>-Q$q;X5fr-*0Q) z07U{1l`n;*Z@4EOpB*vI?`rPTMB_;2O@wP@dB;cUt#Fz#s2~F6W!VRsm{jhAnlN}- z)fyP!!QsJ(EZ;z!)!aBE7VLtf7h$90AEC{PE31r%6S;=~oCwJZ5OmXEIb;I;@5I29 zCMqYr{cW-Bi}I*naDQAMR1^+jq3VGtGdCz0@6dXU5o24aq$U~1RQF^=oa(@^gAk@~ zP<6zDu@c^}3_o~%J0j1ICH?s~B75#*9KATnI|~Sh3kOfJ>T6>cv-SLgC@(f53bE{Q znK}fRNwUT>0{bYpX$JmCh=KU`=)i%3{^AR57}OwvL~O9~j;?zAmV8V&cU4_fF2AC+ zf0VY@*{!RpM9o*NCl87a&t4I-y>3y^C^d2=uMTWuq($z@a1>Lgx!= zq0n{QMhhZ60-3hdW@XSZq0_`waVN=#0LhhQa^oP5+StI71El(ofWFXa8Lj967Rk$l7?|fyXu3@C7KqcW+Y;dY{tO-Tu z62emsDnsG``YBeG^3&=Xe45g;0jDB=ZQ=r$db8`i#yi2^8CLncSWOadE3fskGPCu(k)D2Psp*{Zl!!F0&~paFh9aCi3+9;+~x`Xjc3LP_i^| z;{}(qlJ^x(So9__8M`e-+f%2Hh<{1&kR_|gc__P84$??!#Skm5LGx;(Q>ruDZ{zNV zIQI-Aik;eyG8FCj^QNO_LpJxk+@<)bl=|9H`QDz~3c}eG)YBG`u|{uuZ&F zI$T{5yqJ3z_wP%uzE)4LZV6(Lqog?G{@0lSg`q0K2^1ZnyT}{QCawtqN_r z!m|zNC}>HY)Dd})$CNbUazMyXuhoKva}3m=(bo@a61P{bJ83i;)!X2mQy5%;t)t>7 zza-Taid<1lMV9K+ z{lj!c48U#KXv71_Wj^|MyiQ78#UUw`E)Q04nz_&ju z_cf6&{0?wgNYL8M!Fpo|-`_muc2t(dJ76;R&6b;rglSaG0vLSv+FMz`zzrlmlpj=D z95@ccc6|H-yGm@}dymw^*u zyEvL%c=1t74MpM<1ZGFx67$jz%v+|0lV4)9;_-B_m=m=KIGV#^6g&N!x57(<`!is_ z3ONKx6hLh2^p00Db4DLSJhaWthKPtP`?C9-qlnGAMw8TA0k9F>+kA4cou-G?e$ofE zss&snb&F`1+ORDFuIe;ybqy}fzCA97lk?Of-!i~Nn<)dEf!W_{%A)gO;0dsDQt=(h z#c#`HkBDycU(al}h`hj#`&`ly8sS@|9GHpqs`Fl(azU;8VJ#77J1DICOlOA1ky)So z=kz?i1<`@v@J{wgvo*U((Z|Z4kB{FW1^L8F^A93ht_qI+G#7ZGK%`X@l#gSljt(nK z_a6tta9(i{!smGF0ksF2A zhMHq=&|KGGtt7Bh3an8g;Qu)y<3plF!^wFpwURjNeI{jXB4N+b5{1k=b!iyDLhvqK zS<7>?URy$cFoo3056aH3F@(mHP(huw+p1yl<^nXmS#W zJmu3FcwLIa01gj#Vr(1c%YoFDvLaM|;)-Mp=^lx5>61sLWzM=MXgcu9&OF5|R-Xw- zkPc((3Edo>1YYZLWT%I2Qw@b;tnd%pR9k38wt` za&^Q+tir3D{)wITQQ!*prg=yETVNt@P{Ik4*;@F7Mbhg-Dsr^&%KBi><>c~gIOgy^ ze`NwUF{v2@&mXBU99ux4yR?8?J)^codu_n@ zCyQ)cHIcp12e+hgtXO-n3f7DS+|AAqqb}>MXHBtp>0c;Y(Ki1QmDO5Z`!Pjb%(4zp#FMM_>N3pzMqsDCF#3BXUu!}fK0Jt+%`%0{=7u6)U0 ztDBRALCCq|mHxL&y4Zg-TXhG}dW}sS8hh|xtTD6784h$YVBmL4KaDkS_m_ihN`Zec zTx1zXM+sMNp382rNb1mXON+JCwRX9%J=nj$e{vi$94`w9HmfXJ+v%{W{8zPZp2pb; zRl2#K`++l=U5(V?GHLgM8y)144lFHe%HC`IF-i}6uC%&Aan|06+hc1!>`_Zd1hT@d zW66jFW9m4to5yLd=5$l#B`>Jyl}#*+q1=;=f_=d*Kx@rBQOQh~6Aoj)wCVKLlXD>h z>cuHVI0dz@T8C0P&CR!mTOYB?+<_6mxo@tkqtUJtg9Ypd2RNyj*Dz(DbB9{oSR!J) z#}GI2TWW#L#)n&)#t7Aeyjf&zw||iM;9@k zaVFHnFDu2es~nc2qVk0s)$af7+AEZ=KcfC!lf4K<0%|vcTsNq2{M7YoraJ1tWXd)p zK*QfuUCLR<3ML~)K;k?Q8pEJa=c4K*w($6YB#)057eQiTv4CrfFVhq z-o_YNxpBA)cspc$e=F6_E07%3Eyxp>>EkLxYo%5L2`J`c`7!%wBy9HbFN<=@yJ9q0 z5a+uU14YcVG1cZGSMS;+6x1Odwsl3^f2w;>kGavT^* zXk+;*SVNKg42R?!>~YjNmAX(NIoO{kTP5h?#8Sw)l!@3`{}U#6F)zZCh;SS}9nq~c zc)6XPYN3|DZ5kQa%;?{=@`@MUT(Hl&dV(IV^3rBxRwqGyM7RYsFj}PtYB!E)1qm~j z0^fGrS_qg~Du4s2Kx>KTB6dOQ$6;oebKfjJ-KokvnKjgtGTbm*x>UGR5ke4G#nl&w zhpX9VTmneHmDi2pDwt5i_5O*vKrg%}nHF9sYNG*;~3#8O$hB6D#(75<@f%v$FawZ%cI=G%Lxl16$bEXcfQmFi&m@JvdLhjFi z*9nSL#4v9|Lm=md`@YT4lmVfXy1KK03NjfMUB?6#Ka@!_m*fEw>>0OsByeJ5PBR^8j6nsszd zAb)LlmQY3qHl@BRJ{DwG`U}g#kYrm2lBJ)UMfP22{sn&55}v)tqmW+OAN5j?!jK*u zU+{d~XIr~OIv8r9(9jDTuP59XxYQB9nd)W*ZS2Q^B85)Og`5yAspvsc&aMuUZ6SRU z*_c{gPP2acoDOn0NQlF972&duW)cp#GEL=EV*^4sxkszWk5@t=zXBklP@89i8Wz~K zZ8Qc&Fv_6-MoSDzqa}xM90{r}HOmgLTZcye??lkn2wr~d3u-^s*%g$wV1U;xzY0RJMS7mV$;cipwShJIQ!qnV`q z%5zs~h>3rmll%pmm5UgriNr0wxvq8@>FAQ@H*=`S9Pez_ou1LCJWt3At8~fWieuEw zrnVvU6l|40SxJRiLknw0dxHh_njCj)}R? zX)D3Vu{d9t{?@SRy`mYrleO5;ygY(XCnLjc4n#G57}6F((~~7RmIaExq>ZuIYTC`a zG;go>=<4DAf}{%vjIov{U5Un^UrqcDuVqEPnEPCnCQC^?v^CTOZ;Sm^8NiadX{g;?=+fo!mO-+H6QwPI5L2dtOd zh5ro%!MVl8Bwqt?I&=ztR_;Dm=XP=`3+Q`uRiR z%J?zwVQdrm`QvvDu;z;KYsmEl&TwqRIv-3iUoCw+{BCGwkcE#0VBGm*YFGanZYBrs zYUOiix0s^<)tBw9j5&%0y>fTr^lc(_jIer(4b~Juhw=45L+QE&hFc_g~S2T~o)Z*V6rh4=jh38$Gu~|(jZcJbLY7LbpYUy&7SJ&f%;sV+&w`M(8 z(`iBW>fBl5eaEIExJ|l|l$LvL@?Xht>9;T>NBtwAQ*CiNQJ+c`DOkLy=9rG0_3QQP zj6Vk~6Cb@KD~0#YTbD?gQDol5y#=4|Ci)oJ2=Y~vhN$|q!RU%gEOKk}{dz+XV4{6Z z6^(X`d)D;E0ubx3EqbuwUPXVWHN)~f?ao4M=;$*B2)d2MXJ`E=<{;PxX z2>6eL#aePBt)dj%Zw@U*fby0O0$NR_rzm+$jBREZpPDlR2 zQW&)GhPangucK^>q5tg*T0>i3!iMKCm>(-gem{j=ksX#Nn!#hIjZg;5uTWAD266g< zS{@Ja@%hNSQ^gK64R9s$`}M2rWRj~1TAmx~4`$QpX0la_Y-;$v>DVXKmXp+1vYimx zgAu>lL_Pj^>KgZFb^C{>AjTJ!TaG1SgA^PQ4Bm8jX#mlTyqc z8Kl~cTG^o$Nr;92oogEGA8giEBQY4zSZ-v;W9uMwp91`*_zl^n3+w;AptK~&%KSN;g z9CFpxpU4uAS3KBDN17E9V2{3)?eMe%=*|YkY<{>7@2FcjgLbKYQKdEHXin$sAY5<~ zC}m2!A5TW5I0mw+537Ks4+x!=Q`dLLszFkTL(ON1fMkeIo=N|`|B1L>0HJb)O%zM zuWD^rB~?`@$~In49!cn3_>ldXR`nzigPQOH0y3vr_B$5>dno(xkHZo;sc2$q4Zr+g zTYP>WG5BFwY7V>gN;X*j*ffArQs_E7bN54Exop%uOm0zI67%IGcwwnYC4Kr){T@Ih z@qd?F6r4P`h_3ikL)0`0H2@;?r0F8;!;ajLBYdT0go)AKRH5#VvDZZJ-I^eWK5G&? zbYgC{M?P~9n#=Y1J|j2}yO!bGCpz>*cUmo23C`|?zw|{=i8&@Nx>tv!z`(d?hdPH_ zA3fTK3q3D~B$S4_8jUf|t4C?w1@tzi!N@;_%J-#SVXZB$8gI-w}q!PsF-XcM&bXoMi{t z4$(C|`+%9qu9tH(y$G`^$`-B*4lU>W6+2rB*ony87Ia9DZh0yUAblH}MkC))JNz4D z!T&Tkzi>z8kE_OZ#OhFsJKz>kO>`uh@2yW4=nHp>9519m)VV!Safh6vh`)9FfybxC z0lezf^rqT%FCQX@Wg-WvuRCp<{~`F871yf@?`T+25DrsDX30=dnSeP?1$LQWItXz2 z%wFpw+Dr8pjTFG_#dFJ-Sl}fif4FUx_kKYL1S>;|0czJC+y)vEt4M>Q<82Dv9JQvIeK+Cx2!zdg0F^g zT3fbB=v^J8USw-X5;w3HUCuwT%xLY8F^_1(In=ai-(InIhf3g=MewPb6$vEck9wQc zp?pmr*MWI=M6s{{zae7kc7r3#bHrXwA9j3rUo~Wsxt6%5z0X)o$;icvS*#RMK5F{^RPB0k>@G*$-%V9}V{r+_=vnaFH`KR1B&tE0CCjT~;&T0m`H_+?aXe`X-yf?Gu9zaSb{) zla5J+bKQQsF=QMuU+hti)c_Lu+x0bAJB_nMzu{lG$M@}pnX3=wsMYQJ)Mbq4n#!gb zin4TsQ_M?@7j&Hy9WOEhaqoq#9lFdc;yd%rA5$fGen?5lbemHY&S|ZOFV{MB%p9r% z;LNgnL}$fODcveI_*YuN#hDRHIGT3ab0(-JCu_^b65G@1vVCMQDLAqa)R3)#-b{W= z{UPkbpuSn^WVR16iEX4nB58ZRoiLQ+R$E)hj_i^a(nK?|XBi4LW35K&Cs5-A2iKk% z(5lavTwP-BUmJ|sGg~5(c~(j# zj&6@i&L-)0H#J!TFuSn^hncd9TfCh6GC!TGDWL8DG2(l4Ra86gQ;PmipDL8s3woAC z)RFkCRmWB@uB}D4^N^YASd63aALkRb57SMo9RL6T0002epBo|oYss2Wl%v)-rA+CH zY{wee{5&C9cJ`DYz;#zIalnNN3}a8tTinfCSVyZ{i}vlyfGHOQIpwhc`AM;aC~6|7t=2}L#2oc zLSA{XMR#ywyVDQ{2c&(_{6VxEkznwvaM?r~HtC!hW1bfSgOmX5@{Hwl9pD{uC19Qky8-zwjAY#YtQQLyyXUWe>6_73dYnWY+m>+lee{y%` zh>4#0Ra$d3lBOmOsf}z>d@#F4@f;0Z#^NmjU_ITs>F~7t-M|cUnXtwKh(qqP6>24)k;!OS?oV?_ zS{|1mv!dSmLTXV13w9jT$)k_^$J~O326q$%j$Dl3eR;mm8@}xvs^CVk`LKwKXy4rx%f89O( z&08+8v{3!HwBeuJ2I@<>*enB8&S@`~lZPJelENoC2Px?>cv(K$I^Es6BcUR9Y1Ch!jtHWqm&>A?E8AZEABZ zl@2KuA((Tx?RB>P;^ls7^%sEoBAvZW18_E=)manVD!;igP7#TM**tQy;>&P;*z11^ zA#hJ|vT%=*!}j9)@p^);O#o{}wJBgQ#t5E+U)x+`Lyc@}x6hkeUZUKR&!MG+{p=oq z^eek!(ZO~Z7Izmw8r|oC3op26+FSlETRoG=K*_BSk{6Q@FLyC5VZ$>gK+*GC-`IqI zPOSPA+e$Rh3IEOeBq!Vo=+J@WBp>HVE&=n+0g%|cQ6k==ezM36r{q1}|1>^6hO+3a zJO#aeaS1oc6nFFrUh1=9Mci&zDNj2{qA6Pf%0ow_di%QN(l4FmRozjOs@1Vab!MV| zbTIB)-fZ^VHuGwS3c_GW#c(kV-c1VZ+ZLRzV_LXrvIy<~nCrVEgN0rOOHhYa*sxa$ z7WVK48tur=GW;ek%i)!T{C9UIpyKv!$_$;vKzCM~wN3HQ(i+o6RD*xh>7wV6`Mm)Gw`p zzvz^p%Y^JK2EdnA8E)gGA^TV+UXj;TAcZG9vh05Mx)`NtuP@eM!>#xu=I*VMYX{TB z21eEHvp0pBZ;6?;Io_<5CEuy25&?r^!j{cvBy|fCqm;d9J(K|rdlXrONn}h{V zcYlr_X-Y}jdvn8EgwGIFWkXNP$<>lP)V_~@(__wd!o5o(E3gL|1Yq`zPk4&S=Koue zbn(j{8ARQ?azCr6a+Je6GH4~c)HnO$SxEQtGG{~ThN=F1=EV>(4Dq?XpN#2I2?#7I za5kFYr*UKzmY#P)5h8Sy3=VIhjlhEB!QOsk5kA2%s+#lQv^%ow9o$>LXew!ebu39d zPjUjiBu)Q)*5Bk86pH#uT6V{lvOxXB2+=F6a?qb^q8z!Fh*cz_z94suADPq$3kaY@ z=Q=|R)WP{{G=a*CcBkoK)90IE$&EMNWePB^y()K&LjyQNAc`8vTMF9r=0cZaJ@9gO zSflbb*qD5jUW)Jc-1b1z03-Rw=Vm9d=B;riSOfrz-2b!Y)oFO!dkLx-F`k`XJ<9>?NXKXuN5-w~ktXZ^BN z+JFi^C=ruVn%Z^EGHHRAhuX(O)F&>dl7bbn1%0s#Y@y&QPMIQZ;cnTDMxYG&F?J5i z^3g*za`Vp{gldeD0`OG8OL{RZ_Dyu|BSctDYv6E%+HJfYlQt+rgI)$WrZTyc2?NDB z-DA?19X>}GJO>cthML0gggQ5*mPkmjRZ7WyNEV}7h19bnR%U&Iice2Pu-p}vPRK!W zrRak+W5jS9U*8)MnK5Z;Yyf1N=Uzp4B&2CCuZP`>g~j+e{Sl-KuUXJHu4TUGj1k)o zO`C%G0TbZO6k@tvQlkGtaFOJ0Wkp4BFaKpQ#`hem#k6Riz#OH#QFZ=9Ap3|435}yY zdsv`Bp_mxr`)-V_$)chQ*9h&oQJlaJjnop`L|oV`fPy#k!t`k>=fECq>Xdn+V-Wyd zq|-+O9ND`d2ArXi$o9fjXTm3KCRFx=8+7+It+NlH+@!9yl+Q$)JJ%z+wf{y5a6(&g z_-9`S?vu-uH#hWSh93HO`Llg2NRzOHOBjHieNwo#_0cm&z(5v%zoY`Hq7|9p|HNp>}D}vvu~j_&oA=T_|`*kZVRMwx0c?t5j-zEjxy|-@kp5y zK*yOKt+GZ2)g?`DdNu8#m-oEwYXe(ij`hFH2>u$V52nWbwpa{CYvK1389Ta5h}Ku` zsLVImd60_N1P9Ou7FA6$^6Rv~Qj*zyj>F+6N$GawjC$GCGwYc!tsnL~91KUsdF43MunY*7qOFr-gKR*mqvt8=LT#ZP%nxA<`MmXvkh#HjKnF|>A zE}{P1hv<~&u8r&i)A;$tWDc^Ba^9}UpBKJ>bn0kK94~pFSw<>9$G(veU5YcPK!s*2 z4ox(NZ8EF4qeub+nM^I7!Mj#pR1UJ@5YbN<;O}exy~qu7Bj>N=KAZ~18h1q=1>n~g z4pdjtcuR6rCbjaucDX)Y!JgJkfl1YLvsI<%f`zqjf?q~d0azb;dQg8;F+RHOG|+mE z=VHXCYsRT~geQzTj}J%e5+ASUFL+vKhodreFZfhC@aK^DuY0yS`_x0Cw*@Nl_0>?} z*gNUm8vmk(Dejk-+`iR3aqY)MHk6mITZ*BW=lQh1^?;Z?Nv@Nk2mXx=&Qvk$+MB82 z&iZ@`-QZkZgzUI1clyUj3=V1P<)Y4WZn|W-E_U}#Y7H%MPlT$~?b6)Q22?a_6;^K!&W{jA zgW}2!m{HN!*N=BF8~U>&yXPVYA_)2ei^!+^@2EPu5J-xev)42;26V+@8bKmYy>W*i zX&Yh3*){OH5;m)H(<=ZJV_FvoWcJ`IeD2GDAY}=iR#hwXxw#_8OQ#r)^ufW0Ialn7^sQ`vO zOJ8<5B(85i8v%2>FH>-?L6n4tfNL<#{ zHg3o&gu1;bMG(GY<5YoK0J}RPG|wua0SAZi$?u{#eubeG2RLfc4-DbODdI&BzRY&t z2X!#-K+A1uVZ6z9emEICDd`fdhG`3Gp;u5Oj8`gc-LFvnfq`|D3uq*Iw{=&B?ZH}~ zBJEc#QTs8c(!V(D?&DkY@IpArXz|R8eqby7q3Sw^^nnSvWm*ZBugluzEAu|N1nfRl zwFxf7$tk(U>V{F-7qE->*>g&aca;L6E~7@qDQce!?RsI91zLp**|L2jCBj9F`W-ac zo7TuV!Bmy(6c4h@BL2^=de%R7w}VkTdywzv%PXnypH9EW5Uzk#uiC4QSGW^d-#@Q;xP@)%bEO%tDB!^dE%}!(NS+$~s|mmC&GW1mK-7aKqF@C>I|l4aoQK|LNt>^~ zd{`ZEwe-1ev2d!kw+7&R%9my$Hey}F%0eZ=XE|l>*9l@U?-)qVe@#q<7H-NSFD<}z z20X5#&;xGjY;Sz52VNzGqAizRTH_vwAR+N##~POgoB`&kkk~eDbYX#evFO6i2d37|9(@?;q7AQ#jlm-9QR~B57$E z7%&|R81eXRQ?H<*udyfdtUhU`Syh~{F?3ENyM89AZyWo|4{*!+SS7u$GN6(uX#DE< z(3ta4c8P7wRlM?tFgnqjyNVy5#8X7A$N<*Tkim(%KPkg8#Pm?%*7_>kt(rSlY5WK6 zu1xHLYT;IwJGyN;_FPxq@OL$qzEOmi&TPV~*l%$P=Dl}?wwkO=Zbe{h6noHG6%3#^ z&~To;x&1w`tWzAh^|cKyXxcvKq!_q7{C?0Kp{q%xzb2jp*g8@s7y0LFsdpW5+mg!Z za!BgTQZ$90=(;W-VpxQlporZRQ=Da6?s6c7w;=*j%}Q8 z{RL~5O?8zf8UyUu)plKoSYqDD54D)Y$>c(O098P$zb=v@?}iT8A7Ezm<(rR{(d8r! z43#N}{v$95|2h;W9714Ry(>&vZ8kzLB_PMid7G&%A%UPi z3Z87Ss9?%w%xf#2ZAuYYPNdaipke>&uFGX~VNC`^BwbO@qV5~(q^?^ueJ%*wOT|Ez5lT1@(EE^WCMh{m+$}8*>XnM}$OJnf0MC;~?|j%@O+IA)TRkKZX;2HH zFS-GGAES0MJAh? zZ;9dbAKY*P=*)X~VZ;I+pS=s7s+YFu{{KN!dEMP%OYPov3tDd^;wzU` z&Fd&*-~?#E;-9tk>VT~cOdF8H!elhHtH{emK$JBK=;3ZlV;JjoULvC`J%z3+acLMU zxC^*yw1!i-l$pvOv_BEl&QB3S`!B#cpBZbengWrL%>nU`32MXenY$rmpeFQX>H`zd zg-*hcNKV2rgtP3fdwYHZg48-+Uchwp}NC^5<4NR z!_xU~f-Oh_H_QJsA+)K#8L$d#>#}8CQM&838XZ3OroiFVoW#qONH4Int@-$-_NAS@ zNq3#uJ0=LS2GFP9?2_drUJ+ixvh&p{X(x9-F4&PLadkY%eq^m-H+msk4l5MwmpE9D zbnjD|bTb}8)7lwS*%neDcAjl8#7mo1?`4$84Y1ziQ)3_By~l8|UTi#a2wh~YX@8uu zc;M>&D>s|zmfCNJS3DPsG?+WVkl&LK2)hAL7+q#W`V|<=SYSBxP<2z#t)1;<#S!o1BI#`MacZRTXrzk_2IOde3 zLld3N%1m_(fRV$V3NvqS2C*c%5-CMHh9+1EgB->Amj4n7;adZmNs^Qx%@81ntVQ8b zHM<>lJ32PmISzA^S;(9MCNk9nqGAIDM(4DXv$|J?qe+ba@pkd2zstU!!&)zAP$R^31-Q1&RLo(SPG6s67wj@0FW7jD%BVY(n|``<4~E+lvn z9JewUGpiMbLcqDrAB!>=FE4FRJB#?E(vWgov&`CeO8CpGdDM(T>C-=z@1UVf zL)LC~*DZd7fFS*CdGyYhWo2f2bqr#QsEo~W%jrCm5fZyRkb94a>`?-^_O1%L5!{X9 z{Ju+ZfQ2Bx3Qt#jd$N3>+idKaJJuUvt_YH=3G(3V8UA|snw5J`Jrsm-X$xfMz19>a zoIq;Fpu0#Sg0!e&*b+$eBZfC>%yS#5WPl>Hy%^xDUDs5Un$GsQ83M>gmTn{G`t3wh zj(&upogG0Tifq5?B6h33WG4IuGX!c3YJhRWrQY#AIEWgi28S3us|e)KcPs&MQBP(C ze&^h4`q{m<6)qkOTm%G_6NIxmF*Gt}KO@?4pf>~{+ z$VM0M4i3#pAQ8|MPEEoQ_>JvCDCfJ7FO?nq-eM8H=18iUCHsC3x6I`v-t;KM8kQ2O zEuYHir6g;r1F1*oa!*WM%Aw!{YB=&Za6#@rfY$_yZ29_9k@Y=s&-=Tj>#v73Z~`vSIJhj!ifs0;PhcJ zAq)p}=?_#D&BHGAr=4^ht0=0rvGW-lzj$z(S0gFBTub zGq0uCl%XA&nE|eLGOySMe?TZ;o$mq0LjzqH;~mt9G!BKZQ}OTpE06dj7^OB|w_Oz93ZGdJO{khh@hr=U_KMNUW0Z6lW|Nfc$D ztp|x^;T-Sw`eT;lMtuD326l8jI0%g;?t>p!G-FI9CX$p!uRjSlLFnVT1g6NGFw-52 z38!4=Ozt+BI_EeK3s?Dtoza31nozTu41gQh6U)pGsaZg`DGrO1w+e;kpSMF~Z9>f% zNDgDA1au3((2myVMBMXL?m)8r?8fVpN|h{j)=Numj}Wbnyo`v+Vh>LnIf|7&CQEzf z;_Qi)n$i)7GC2H#Sct|3`OHW1`TI=7x-B?vBS)d)3(*#TlV`bH<^>qwI;!6uB|Hpi z)B2_)S9S`uc8!HJ7f{a*hKORVY zTCQv@g=YmbbYg~Xc%r?OfmspH#C5N^zl&PHo!$F$Fm?C~ksnL=lMO>0U=A4*MU@`J z6-1jBT?Nhmm)KkzFaDw#0Km>&=iGpoQ>ThMexJcq^>DiSRz4hJ<{1IjbKt_J<9;`f z?(@ERC7PTRS^#1*^f*;sz0cx(J8$qWeoaPm$UN*(rQy?qw{Z7aNB#%s8~R&sVz+wY z;HJ%1JV)oH8-UlQe074y1i;9_b~_#^-{i@+(JNozNKcjKEi9{xbQ!Vy;CIF)5izDN z(lP5$YZ=rq8XoI5R@|M2bypF*nfhA5!fNxBZzN?>i*;wfxLa%-TclZbJZ%W>;-QY- zVU7hJa{A^5rOzYpXdczycUJbx$UH&&Nj0%(M!pN2vtsOei=dQhp_f~8IJgX0Ds*1k zi2i@dFJb<6tGW6$$BE$)-50C8@<%kJ{587Hl?nL`JCD#|;(Hu?vNJ9=XVbGOKFBU0Hlj^0OP9YsuF_QCu9dw>^Q1hJx3*lA7oVlzlu z15Fzf5k3)azil2r_e@241H~(|6EK5=0vhPfm@QvF(xQa;ikv)*AyIs87i6} zLdmF2cz!h*j7N0{Y<~Qk8aV89i01ezN`M_~okF-{5{pRS=1g?Hy#+MVDte18eLt_) zDoc63dK>C23#1=9M80v0b(EmlcA*ji+*j4TobiAXbJP9nD75NN19At{Nq?ZX(DC4u z{Aq}4Q%MN{`I}W7&~QkCqYRt}d$@o|6WCtu-{uUWXwxXBiR$n8E~mokNSKf={h^8l z6@AI!D4Spz$WAv_--;>B2@=XU+1(t^fM0#4yJI+iIz1&+F=v3P z@2SbA70&h9b!BVievC~g)MBKOP#f%!$uoM*4jnbRPw)eb$pL&Fcp)HvU5Evc{Unhf zyl-?>61FvvU!QxwwQ!!(E1iE&GsIE=TcWj#>+$E>|IW47xpSBto6 z;_%G5Z&_+$BnfFand6oTK!N=9S>2`)iq=S0a#68swg(gIWWu5@!rE6 z@YhC)m0u!6k%a)p_KZ7q{^WdvYSr77@cMLQ^Np}HjT;xhfv7Q=SZ-0S)3Aj~dNW(| z@E`j#C@p0GupIyZ000001tk}Ah$+XYu3M*J(>A0$G#>|fXebpkd#nta<%(M^n)Q>A zWMlWDJ2YV*!-{=j>C&I)nkXk9IG z!&R#xaUIT$@EQ9=I z9G2G0_&yc#kT}Q2P`=wR(EvY*Z`(AmS-5N)`AhshSuYsK3roxyEIzm&Ib`nN&jkorZ&Nme9B}1uc%lMK)zz$ zUmxT>;gZ$JbgZjRIaR*rn!)QTDeVIVx~bFPp379I9jeGK7N@+Wa2h5po|=-2ZJ!^c zfu@a_CZ`O zE1IeIulgrt;8W13k^LeygfO8YneG9R zSfE1vB&5Tr&on9?1f`;{=Csiz)ZAS#uCzJM-{_1%Gi(o0(q zv{jpF_s2e{ElB#_|q^y{5^wqIeVeB}BB+JJolkZYtl%sC?+T4a%EKT8U!Yj=@`ZQBQr8X@Sr@#do6*{j zN)*{uNU!iF=A975GNh2bD{2mz6a)Un!TicJr9MBzNgWj&F@RD7Xq<(>Q-Rtb7s-Z3 zr}Ff3%}{kBt!vtIv^?y752 zmnsp5$OJy6o~sxBH2$0Fg{P|m%`vfbZUwj4yiGiF0NoYGBw!3?aAm^tB}zysys;uC zMc>j$TQ~P$rv$2R0m_fUDyVKYgxIf zg-bq@cP+l5VST)E1v(oQ1#%)8O5tXr5ZaV8>6kFK{p@B&1h=i}z`i+UQPPorn%9@Y zXPb-f*l#;oX$pu)?1mLy;8bY z(1^80v{&pU2a}|V)%YNJ%`15McnRp6nlil66S_@|+&R4tf93sNet2SE*(LQLL@F!| zmMzJ1G{sfP(ESnabPaz%c;YI6RG8=H)BJ!(nv_66Oj!CEiL_Cs>&XQOS;fpd8H8!1 zC0w8LUTH7UNl9s@&7+g!__#qZ^(C|pXxvb7%pcSlt2y#z8GRc*Ud#_;zik;H$fe0= zYkV;2cP0l2jbL+^p$I9(*y5>&>F0gfxJFAB{_$4R&TKzaJlIi};K(y|gOEO&vxzSw zZZ=-#A5xXHW$UCdhVO`9Z0XAO5_aKemskeh>!qFt>Vg8QFC+_MWG9;0u#&BlaAXB| ziFN?6S-LB3GwrcK6ajPjq8u(-N^6+r4_d<6q#zcJiC{dnbEbj{@=o4)=`l{BQF(?0 zc}Uv=Q`m7X;GAVcm!^4Y65irs-jwjX{Z2u?Tu7 zmn4Io@6%PCs5_#ku{`nzG)K?+EXwsngs|I$3A-GfvS&wpp@l$z&Nt5h!6T+CnqGkP z2hOpYFSbS2{d22<{~hLgCXM>w$!KNO{Mj3B^1(oektAE(xo;n%y%n>{esD*qe+2m` zZ_qh+eN9ygzL!}b0&f;x=mT_m+4GDhSAGjdegfZVicK>dDvW*wQ~`beCOLG%4s4&}rj$A28q}aw`j3U*6?$C(O%?SG2yvL@mXv28QYRZD!ky?tA+MrJuchG|HhW1w!}x?teUMWqr$pFKYloCW z@enrXW8V~A;-j(`=uO-ET0$7#dEB>t3^01?<0`IAnoHu8So7~y+?t3-Wvx(jjOPw? zw170(au1ddK*yKpjHf@2x8|bmtt)zw1NLF=5bdZ&x@k-&Oo%|`le4||Oi8R$_eN^l zZ4A*f$Y4)I9?jK?j~iGHXoo6NfeNDuK;*_2k%mNSAHc^%j&dP~d$Dlk;TzDrq6*Y6 zD__kj4FiJ05vhXtcM#-zE6h$Dq2g)NIHw4aHx^2grpvCL_yJn%jwxn4lDYm4IcCU> zh<7g@J&P5C!Q(}MCJdbwekrrI|Iwq06U6fz8{h#!olwUTJU66Kan@)VW%~QYj0LG9 zbYGQ&MzCJ&CH;6TU`pM}b~}Akgo5xa+be$5l95Htcj&ZzCl~pDh@OnBt!qi*Hlzu0te=LjQ=^OCq9D zpxxbjYliCjh~ES_=U|0e<~e0TB~us%J$eV+P}tC4K8WL)Zx79jh%e5xsm(1Ryp0~n zeo8a@FH3w@CFaXbZ|D3|6Dh2~py<*Vw87C7L!Ia@vmoswM>m90(u4LqunY`r$IIin z{Nf9HJ>TSN1s6rE#9?J_G+tzKb0aGugK;OQXB!MHbbmj1nkIuVG2f;5IRWF5{AyOJ`5C9n9foQ6F7>RAxoWvW-}3gI2eI|G7icJe z+S!O}513rdVh?rMx>#nt0gnh-nF&PQTqAJ)H)9eA>Gy=3%i1F^!I|@rb1CvPgqWs^O%G4(>p> zPK+r4<c3Sfo3 zs_wR#drShfQ8cATA z#mAR9u`+AmtV-BAn2}`PE9e`MWM-nCDXZhDcDWX8y5lC627{5pR-aWMIt6dTNY0aw z+#a>@(Ch@72FaIi$4F}?QYmj-vZ#g+g($(JR%?A_6r}3xSnEq@P&_=OoOSr!w5MT# z!;9G*JJZp;8t>JErcu?cO~M7okHQxd5RU=mctUOIq{~*tgIzTWyx$M5Ie-RWGZ5D` zY1Z7Rnl*ux!lI1kAfGB*uZ+a`fYR*^vQie$&bfTmzkP%;N9-o`C{NktkZ4T-(pm;9 zoai_>t;tNZ4Y)2PZ{=$xHRKc!ZE+K8v-NI z3_F<6<8DLc2%$UJn{_d+A&ai-3>bp?81Qes>79Ds5r2d|9nJ!9n9G#}j%~Dr;%4?L zA$Lk(bW|9nOBQ2XG}4$3Pm8z5UkJ6A2krgoRqsN~k2)tr(|M~DU8U>Db{9g1C ztPLjfmz*tsmQ`MO1=zIE7OOo;YB~3QtJ!8bH9+FLl}^?BK}sg~+9;_s5khmx{6Vd( zcTH5k>VyU8W^7c@-@CN`d=L>8^@LyDmjne-YH)?jO}Kyt>CRYK(Nicyp43^;;T*X* zgF4?I3TzKs?% zA&T2jUrNjY<6MsY>TE<3R$ja6k>jc><7AG7`9TT?9!w~J8Xr1{z;)aUuhpwf?us6;f>u)Z-SK$UG!bPZP?jZcPeAVAO@ zs6scIAWITGse690nlPcZ5Z453>FhEO#X)=Blvx=6x^_3YRSk)=0Ap`1(s284rWc=t zG&@-~mry3k<`5lt_oP^IgodVfPFCBFU)g}qQXq|JnUoHa*7qZ5eH7v|97GVp{|*T% zGmMn7bxuBi1DAcOGtdJV1t69M{(Z$!rdHxj|E~ZQx>;$E>r1Vg&$nntN`rIx;f=d^u}FU= z(pZXy-6&3VIk0R5b_6>PYvr#+<(tlC8Zp{cd=eTTpld4-1VQ~mkWdJ z=1rqt&h7u?NJsxok^fU+Q_VMG3=<1Y;xVr>6 z-i?_~^jWdK|8kNJsAA!OHZnF1Uc?dPaE2hM*;37(B6~AC5fqx~&C!=e_1gybW0llpFl)*8E5EnmYzIol zQ1lo_*(|6Sv5)AHb{tqm85J=geill$y#=QSqvGU*@VfN;NB}{$^p)y05;r zI7eAkll%R=&=CC_flGICaNJ1$%;rpqw0qTF>ZxQI(KwJ|g-PnBN{hkx&Md&k{=8Yr zljf-motKu!r&|Xr6W=iTL*uU!c9Qp%ynQA84_i|1K%TrkSm^?1!+=49rCa)uQ}`Go zI#6-qK!1Io#4b6#s!ygptU(42MO*e!yFrX(Bimz4_4K9+C4Cc_VoS;@1SM%QnO9KV zhD$;>_d&{OT+k0H6`^Lc1tnCmHv7TqKo~Dx?aqMb(15XpZ~Q?4Uk~lwt7PfNr_yiy zHSO6;KNUcI_lcW(Qw|<;Cyoe3XfsIsBDg#_Kf)=SB{im z#T?RZu6EH;Uw&vj-}F82NhcIh;54%~hEe5U(R;C1rgv;R3#=}UOmJDUp)W)IfmgyE z`c8F%C-Sau{P|~GZ2KE2yD_>yXU?QLE14;u7|X>qWf2$ zqR^FS=W3r*qWJ7U_ZHcVV%Em{ypQsmM47-WTlocp4lbgy*P4wnD@Cb>-R4+#|Ko0F zO57&qj5atL{l_a*MbUuw^kQ~`gb}`(hVp-StEi?&iXluC_M5t8Y-i?4K|n`>vzx~K z)g61~c+_*q&b^73u=s1;U||-r53&&y%m-+FB=H%-`xE9_)2@xr(XE(t{5c$KHzVc` zR7o*x-^iPM2Pw$S)+*OkcuO%j4lkXTz4z1Zt?q%4^19))5qbh7uX|1^p@7_4JL@&} zyH7FY2@)@#G(pz_91!1iy=X+;dt592espGCisfCJp)5V&9K9=g%jDjP!VNb6|CG>T z{U^h~hFz#?gV*iZiFpEbm}v-`!}>ZzYj64^uW7sxI#wbLqFxF?kz5v-4cD`mDdUc; zM<5+ghwVn^vL)nwjG(U{bhFNfTOa#}jpF?jDSfT-pI^p8Lpka*m0Pu&51Bw{ zpwT}3{(cD!E?tCe;7&DPLo>#}Y}%fKO-=9*dlk2>M}RT_j0qMFYjL=+xUz4ft`rBL z))A+NB?BDm=o7UIQCMwhsOynSuH@zaK!7ERuAP>1>D+pjc|#|?lX|G_Pucv6kQ{c3-qWf z+3?IDj?y`66##551%rh|dtd3&XoitsmHFgNt)fj6mIH4!{kwnfrvXzGBd;$YNMY^5 zbhE__>81J#-K~{Xk$kpchXN)A&`gG7nnChL!e7%vc@pbPA2(T;-A0%OX*{Ea6L7*M z***4st@re)PP((Ux8omSd4XJ_txYw(0!*U@<%WJ>bqi-C=L4RYNBo43WX_G-Nk=VU~c@$QJAw&P=wmgDxFAflLpua3Nk@ ze5E?5s`tp;LZTQsqS@^mCuQIUMh`HOBd95}%@<7feJk))Y4$!_% zT1Yh>Z^936y2zL`>b%REScnwuetaU9kh*(~5dG#VFdyVRKzZAe1qXN^8nNR3-tt?i z(%hAOM<%n3|1m9NcWARg=x>ZH6&BiQWEcvaG&09y6o*Eby~|c-8RSyU+4_Cig7FLH z+JtAm)$n!XVqTE zIwrbx4-kijLWhiRRABwD^TYFpVZEW0v;um(P6enCo)KPT1v3u9+7haW_7usP{uFRX z>$meAi88V!UG{Hl(sEgb&+=7ga@A7ZHCY^7uaj=71EMHIqR2(AA`h#brJQ3`p5|X} z^PMKxL9VT0BRy8%m0*Qw7vqCgTK+eyD~#Qz#hTFTt=aDZ^ur&mc4s`Uq_yMf!xfbU zZ*KLgVd&*U&yROI4GHRi)vc7g4rL*K?b38hgVxD#QZnPPmy;+1FV?`PFp6ck$XF!JjQ_v?75A=zXJnuP(jtRyJ{^T}#iCoY$)?3kxut-nkqB6RVm4%BxeBA4Zo z{LldiO(lk2o9Xf4ukfgE&DKtDz8UnbtijGxZ|D)_gdvtfz)pecfoTJ~BU@V9SYz?3 zi4^;q1`a>URk;~GO%wQ2ABjTJZ;?#vSh0uE`=Nj=h4g!8qKLd%WRSq9K~jk3Z~uKK z_fAZ&HJfQ?WbHc4P2VA^XEnoJCliN#^P8DxOP_E)05O>+>QkLBPEHvq> zOJi(8o!|gouuspRamCH|`!C+=%q~bpZ<9}m2fd2@`8K@{&4#!!rDDFOw_LhX7h3U$ zi7rpVLIyT@?Arb_EV}cdQ@YSXiQO3@JutZ}T9WK@ZM~^Nh&??uy2ieK*67jmc$ZX} zMnNZenY_KXzR2NCiHuH8W~p+f5#lmv|9};?j~>e1Je7fB z*2tu<&DiwXmqay5#G|coO0&@wM9)@te9h2Dofr+2xCc}98DTz4I| zFaM1~t@E;)b`?`gKa}_gBd00000TEgyIoRM&p zhdn=%;cE?lKC~k>{6qS-p}X*4Oh?-3uQBsK(2^r-rL1OS7nftoM_+q7k$F6HZ|Pq< zdBOQRD&Z2aCNz+6dKao~=w#@~eX1%u@WxhRwjBh<6v_~z37nK;R>J&axMn-kS6FtN zUFwbfpao|HP7x>NJ4&F&x>|Kj-h%!z)R69n=X{u4)Px22g+oP!_&G+OvTYRC|ilLnu7v5A8@ zAV@rH5uRyqpTXW2;GWidV?J4oe`=5@%k79vVj3@{!!L3U_lv0s*c}mHymx#r`QZEb zHk3N~pA|r!)mOXKjdM|5IIb2?c~-0J=0}W()Wcguj6#y&y|xtfU67N2F~-7oC6S=N z@WQ%3RYYmrJ_JK7{}5_2QqFDepz3*Q6b-ItR+juIx90(<&D(%>WDE}Lo@){XOP$6W z2^u=8`I|;N{@f813$gl7YM*(z6xZsne_pCSkft7k>kh&jlC)y#-#N^hJ-RO1FNkN_ zo|pyOPWXDKpLz9>UA5Zd8fI{hwr~>37jl5>!uk~D*g*yrg{!$ZNj&C|(twvLkqxaJ zmDS$P!$^vbMdk)V4Eif+J*~{S-tBAM^SWI5vQU%VC@O^!I$)D%$wF}S$73c{ zaIC!R5iso|&vIAhKUD^j3F=v64Dw8p-UFcWu zBlOap73o(B#k@)t?U0uH1>|vaXK?GQSs({MJzl5ECrcvoFQG1aYzlD36`rGNOuNCO zhU7=bJ8PHDjb`$6~?9Q-;HE{wQKU!cA1f1T_rmt z`IIZZE&VS9-^n$yY@f=@1tYy;1)q=y>1x=S2rEaUip@H9ow*R3jcE_)M-QQh)Pn$x z$TGl}exB7v|KSOf4z!~O0(!QnKJ^}WS4h0IlNL^NB z$J7*|@Kj;XQ(hqIFZ4u^cIo1F>Ji*y`PV!!r>zS?LTxKUxSLXrwUZ*Ou8sl z>a8S^l9B>pC0h~(auPW)5UG2KK4Z_(sATGCEY^yo`Gi2yp<$9ST=fuN2b+~oBR$ha1 za+IO#o*wW!5q9(u+3}^H;pjP7UxpJ<)(~{<(Wc6j{6+RSuhIjbICkby^i=Fi_<#wt ziW_Zoo>-!rta3m+)cK`(>j(bcZRNN$fS0_H_JlMWKh->O$lKY9?_bFnhX~eb`^)0Olk)zFB#w@&1%_Ac z!Ef)!46x_-s$qd(EG#m8GZk&4#<@TC*aJJH?V8|02HW|ZhYns2K@LiIF1H=$Ei}M6dzNyhe)vK1Q1%2}b=*9_2R0$Pga&Fnq4Mga>D9 zcoI4`VkK#_si#AjkS1`5#gW7?mU3<~N@AW9%8Uy>L?du39VT*)gHcLNJOyb2sXd4_ zwi?LwOkdMDjd70_a&*`xuvw7jC@hH~ChrJEZOo4bww6-W{r~k?dHBp=H;HvCNEWAE zp@j7k+hd;?Ma8RKWe)uK(}MZ2a$9=CHe*LzWb5@9-lXq%r1rQkc^VbJ6!%P4hQws= z4j|IZ1=sbq>g)ezh=}%cUcoMZm6^F@@yg&8%Gbe+ILd*31CMw0^=^C@Wb~2cZ1&eR zPopB1)v4YwH*9C`LT`!4OcKK9xnfCXb#zD3td}yv;`Y_TZnF>b(D~XPo^4uvQG=nn zs*pe8Icjha)BR*}v0WtCq(r?nEFEB+g82 zoguT#J!K|ol=JPN+uxjTw z_;i_#-@A2gB>pX%pphpFPnB+pmM7Y6xmcL+Zvlfygo=QjN*mU~Nw06~`4rN{uDW7c zGPl^k7sL?qL(G41D$(%)YdjVV=-j7IG>=$Q+>7fANF1He+3El34rivtO(1p0 z)tx3?yImT+<1>-nEV){GI&hp#b|;E9{kV3_isIM34rxLcudul2pfNc+_JF0!;p+VK5uJ6@xdNXi};7kR>vJT%h$sqT4NpIK%L>bv1gK(vv$*{d8ed#6z4 z`{on=d_$k^5GPSHzg{LE7v`3T|Cswjw<%u?xPXZ8sVdIm_ zEwr4)7WFF8uwMcijcw+#;h0&bqJoKvKWP0`X+tN|6CK3Zw z@QvHyGc%xGwcX1b`Pd&!add+^S|xIc__?hq0AE#!x0`gnpq%?q8#%9&{+L;MpDd&+ zWn`4e1|n=LQtyph_4AYrhSi6Z6<{GYs)ClsDsuoIZflH70ES`HdK1=-((pt@Xq-Am z`|R`CYXy_!ePuMdM>X{bqFUw+eldM&&F^$$=I5OX+4;vso3l2Fgb&64Yq|qItX@!;sq*?O!mM6VgZy zQNd=r0e{yE0O}mWHa!kly*}q^^GnPj8mi&EZM?jk*Z{dx;#lTtR>|6)py)HO zVAr)(mo293q$tX|bQfaq|MbdXNprlNnOgs+fjLT?+ z>@?KHoyQt1;K@7|dtCEx%<)J1K7nL#gLJ##gkRE`>|Ag)QUNadt$~l8O!j>~$ znBca>!VCz2F;ey?G~~aMKVjYT-Z;fY{{ z`Q*Ozj1xU7@WoNRdBvy2%5PDUX4S$I;MEC+(+lC#gF$x^OHE=g?8$^bY8K?nr;Z0$ z|7g0Vik6+}>WQ&~%doCd$jbUUlW?Q*<)pi0I zy^L7xy)5I#4gXQjgVHPE|1<8s-TdaeWFG}n@G@*n2XmNB4Ot*jC3QC0EQwV0Rdhvm z>4)EY7i}I?>`V3Di>@|FXoIG{&tfVBqm~i%^ZT zuKQ{0seKSd6~i+3;IN#-F!PV<;eKJ5&EcJ4Zdwn*6~_g!$pC6YQupw6ck388a#~8H zGrln}^VqxgP_TILc$8=aSD{osk;hT;NmPTZRE@)H*HC8$bAopPi|Z>#kdIa_9#}0_Qo-c4dD)IRu!CN6EYAn<~CASr3TR%h-PYF{S@{@)dYx5}{idu>D>N zqzv;WCerlgQib3OlWtxCprI{`2IBIe7Euh{}Rq-WzrC@MqwRWtb^u21~FQH zi3oZr%i zK0RhT`g@cnx_PO*14TCvk7G|;!c?jgCldNvClPH@iMv($ z%O2-J{fyvgHZ&gY7MPX^|d1}O+Q*b8H|K^j4HL>x=wryu(+qOC3#I|kQ&cvA5w(aCi z^3U#X@3wYpcW?Gob)TxP>Wl7kx~u#7oag(rLmoit-g5mm?MHCQQ_ihzH4dl0&vz>3 zMIHjCFO>i!hB2o>#*s}q#yaZu#(t*lz4x4#t&@|Y)=+<)pO)ygjIu!xFwcIgtvP6v z)M&666zA`F>iq^}8r?>590F8lNI}4Fa~KbIN7ib?irj^aYtoS~C<&Sv;31#q=kD>W z^bJ~#VSZ2E_05$XgcB6}nE^Ul%+)lJmnE1?O0fC!DJlWgw(NGB;-@ zhZ6(0V?DBK!}Puv8chL{+IgI1T(bL7$LgAizc{RyGV}9|PmJ;(3$tLlw(KUnSZ15h zDu=dUl-9W;b$718_oN?i2KUGEj6Fh`aio7wDD@~;5kkj$8;hVu#6($Qs|tf^73nab zZ9l67m5y8azx$m{yg1nECsnA1>mTC+k&S>DEw9-DOTn@*Fun}NT-zC@Uh(ftY z!T%1A0IR!8hgw4R8)PvE|Dg@Z{*MOy2z#Adn1<{p%gx5A)FD9sFNwVauk;*vJ&ST;eMs9cyZL z1hY?X{F?_Q?)5}Nl%@w!MC`k=`8Fg_f~uo%(ICUp&b{ba+8G0Zi`u{NNm@~3o>;II zH+|YqvVQ4KIAiQgJK$j#ROI0yJhqI}rzLuHB?YYWiz^)-lBFp1hXiFThFE13l-{cydg5-$B=6X-z2~z9Ym7`X2SQuO%}kt3DFK=L^Zg38 zI^B12nE;)L!={iQAiw{~RvCH}>;y*d3zCe0HNRUyp~+j(Ke_@(L~L!j)tk_?c%B-ch8t2VOu`LyV-c$ITXV!|DgxMVD=p?t7EQBm1 zudScZfF>?REhZLX;CR8u-Uc2*DSc&^NzdZc#;S!i={i^7X$d#gg*1+^%5OZlL?>!Z zoQGWeFiNW}uf`6t5A3>!&K4=bC6>EUaT4y~7_)Y9{5yyr(_zbhvD0~?DS&T`N?NtL z@-r@(`WV7>97$H{xHmSPx;GaGzZ2RAm4SF$4RMpVE)Kw}lbYn8ALzeky+aT@d;zBu zzF94J){rxkVXwYTWfte;x3tFD8B%TfEvs8%l?d38qjGOnDOp!(J=Hf^k>iB1r%0!N zqouCjkOl??=K)MrnB@4eN+U-Ye6zLh*4sKZ~?Q3kIG z(>6p${@9xG-Xk!cl4GY*iL2+F9xJ`uN+@PbEp-|wjpBsmBJM3PA@5GvR=>>Non1!x z?fN&MUBhRPf~<9Q@F`5}iVf(c(OT5mCX1qWfBu96oo;vuE)1;E?I8afpHaT&!uJ^( znn+#KU(MD>%MU}IA(=7r7Buq2zs)xl@pq0sfJe3ukg7zZzgnovI&r>Q?aOW$8*kal zRDc5wFi_mdVhUV?|K)_Hbx+5O#Cs>HN4hFb5t;wlH+>ydw>6UM0vwPCBIEX)-A3h2 z^|XA-K!uK>eReL31e>P|bo|Sb;o0IX*77Sr#sBqhiMQ~-1xvsWk5iX`|g)s|w^RY6?v-xd+I5^P_(yPl)!q65K|Tv<(~)@H}MH8oxbi29KOqhl{~0 z-BH()8^t2wnKQ(5N`QDro@$QmrI&H9?hfB%SC{pGEQksopBzUUq9VD#dIe{9mTExr z@sZfm8dDJ+v?nKt(nm)>_5n5O7w#jqj{^t3o!QPE^lkAfIWe<(%ItEy;iwK>@ zH^=)^SfdV?80$ux+k_Zfs|oeja8L1ByxUSt{2aXKxBRomuZ6ZvjI@;hX100YbY@7lyiCSFwWSKwOpnLz-;yW2g-hbbj4m-U@_puq1avAM z+E^5}`1{^)v{$}%YQe*XLOFh~#tO|{1up3AIw8&?J!{4(g2_2Mdl5%1nW7Y`cuM?8 z`lW}28EDkYTG>kHyn1j0`c{*QNdSoPJw=&GNE4O6ssqZfC^dZ78*|*&b8N2iRA<=p zSsqzKV1XJsp8_0vAV*%^%t<)w{wo9n4&bx-j;+{|)ze`#M z=I*|I-^`PhT=#Ydm7|Zli!wAxd+CJI{TL#g7#_3Dbv0&9J~GXdXgU(Z7-7{N3oyU+ z%TT@hczx&H*S-`G~3HtI+}c5#0F38}or*M{9pZ!K*))7y~BR1j^ybwa(-kO*qgROa8znx^nP< zB%FDHTNN#=@B+k7Ni2P5xDA=iKNrpNNgk?44Sgy>UTV5w8=>+e8m2z$((K0VLT}^) z=u+hyWOq&NE@g#|uA)+H0V(pBkmkiV$)FxEYL9HDOtX6I1#0dk_sd?;mP;#inA4+- zcEr6z4SeLi2>BaRj=>Icgk3`c<#g5R->I%hiNZHq*fk7}9Ao_dGPZ|u>aMAYRzI41W zF2cmG=GzDxtG>#l=dzGHWil_deksPZpGnKGbjC#ChzA&1!utCcMNFdx$@BA|DOcXt z4szOR@`SV=UUgiNsc43Rbw7&VoO%tl<<+x%cZq7vbz|fmb;T0|Dh?(6%x#d9j^kpE z`93W-TC}TdC%3%IwK?25B$5ogT%#E*MMWl&-pbQ0(UkD8epwI*2M*W*d~Vi6t5~ouaSS>cKo{aPs^+auXH@U9-oL$tCYq! z1iywY_V34RIEs@?_9WQ?^CgZYzt&n^)(T^m07O#m7^)b^r!kDy)NzfCx5delcx@ge zw?&#iGy4c-;b0|BQCh1hAfNjcft@p!JEEmhrfFl7LR(NApyf!9u-87Dg$37{*kE{v zxs0g_f_M+E%Gd-j{y{HjZG#!|{#%wGqVF_r&vTMcyk`&l_DaImb$jD?~`ZYGV z@6#(*`V$0Z)DNz2C&>Oz>rR!i_!ZV>j9he~kd0<}6C%}AoRG5_pUK$rmz1Guya>VT zKRcu~WyD#EG}7&*b;I^bJM*cTnHOuA2_#WG$6iP51d0LasfH zA;Di%ae!-ebsfXCU#wjI^M%o}z_qOi;LE0Us{ruuoZzGzC)416(TOcOLs}H5`wiWG zA*~@bR4|dkZJh*qt>}c=^(I##|5$U#Cq1hjkjuBF@{wX^sxW6%T;%(sHR_$~56mM> z>J*LJuW`;Ni``|%(oIYu3Pt0BNXVebp)8K*W4~u>4&msvm2h1vQF=U}I=0iyTIz#_ zGL}+mo`Mt-IU*1!3cBR&nTV>aof*PBg7`q$eHq>zx1$rzMa<98qZy-_|G zeed@aX8XRofKET+ zP2`Q<+zNWZw%NM&Xr^yFLzi(av{(4+U;($PPko1_A~hyS?T_An7}!9cZ-w2S3Md)S z@jQAAhf2IkF!Wc)op$Id1c1U@;y!v@o;MH?O}x}N(}Q*6D*aG7ti6H#Yfbn>Rbq%q z(SW@*i;KBSC?J4I+32jpyTcpetTQX>wa6b;S0ARwVe5qB7TCtY9AO5Ny9a5GGkctz zaX~fvN}<~PV$%hOt02qG@`IQqH=*0hX_lAQ5{CnNn~R}Plr-O-fqpeJOA+lhsUn5I zEGAjSGHQ;14i|6gh`E=c27egkXUu5DOn!ICk(S`!x{gBJJ~&+9X>a~5$Gz-``7Kzh zEZ*xY%FQ%uw{4GF#xIpcZqv9chWz0^Y^%Qfojz>*zWhtLd@49>E4W)ATt0r`m(9%< z%IYO>l~EHP*PgWIwtJ(9kB_gt#pYZ{NMdzNY~DE9^v7$RqJ&HvF8>HV3B}~j6}yXZ z*?I!X)l~zBH7a+PI`2sY=1{Ds{O5+lo) z|C}#Ei`0>!WyZ8OX~w;6+*1KFeB33WYtpaC8n!{m^B3zGxzUh#+S+#F>SXAk(a5)J z0ecur5L2Hj4*jmHha=j0xJ|P#a~v?3_LF35BiFh4FI$ESqTX@l^E6CUfLUA-e14$# zRPSq;-AHjWpl#>G-|L66vJE{w{qgMfOed{VQf?bA#}vpv%wgZx?Wdt{qgi1oy&sF# zK8)4dwZM&UY%e!z?*ia``N+Yec(vM|<{rX?`$_MDZ7>^iKU(xJ6yLFYu2~$_C>x9M z4D_CLrNKsc(Y(eWCa8M+J93U_ATlcH6WUQifw;U_Am=q=Mb_O9b@vf?UU!7BOVKe?8c9YPK`pkm_=>4_xAa_g6gqycQ z`BDz2waKCgcfPsQq+92rejD8Aw5Lx>VX|tG%26+-LQ)%{9KHy<5)Vq8zkf?iLv~P~ z^s@6!V`AN7apxB2!Snvo`+>fPGhh;NPWVj5SEC4u8S%1TZ=E7d{E>h>R4{CWW<(kA zwk2P(zzfO%w|)6@a@^4@_T;4Cd@t1AzOyV#k~MTN2NR(Jxh~J?08kPpB+v_JvPDP& z?u7~;3$o#FxHFCb_rALg5&)~ToS2d z?jAK1r-Rzlu^R@)vMM+B*M z!HzXb<^Il)-Gvh<%kRkF@EI$7e3~l|(9V-or9i6Yl;I)53kKA3y5iuE?!@0d{ejdpq{b*VEIHLwK=IzY^m? zN3#5?Y-Uv%Af^GRs_exhme@lHO%;z?d$GB;UKH6=mFqIG>`Wod1e?=Qd!)CG^`d?P zy8&G$stVundz2yJcag?DO^zK9;nyQB88uM}h$)WKksLW|yAt1-xukNtl!Y??kpFFR zdoF{zx#5^pJ!&N4{NX-Gf96#yGI#=YBEU|Y{CG{6b&gr9s$UV}lijG5<~YHz>|TeN)c=IuSeRUsP8glH5& z{5yUC9^HpdA$GzFcqgg$viAqJ#|mtN`&FVZm-$L!$W0`h9T>-5TbWLW0N!1-5)Z8o zF*C3{>T>acjC`HM8e*J11Ce-ZGW83qk+Z&gh7fOXOOR$fIQLZL)SS3Fa3W6F4gE5> zST8r$48=u;cuUw-#=BusbR3dBkyT{q-D!ddD|kDEC^RHd*d$!HnN&h#c}pg!Clp4M zlJ{ez%^7@!CZKWXu0fGNPZIaRF+!$@mu`pj^Hua^hnqd1AAJL&!Q@gZeDGYsRqq3y zj#kyfs3z(ajK(wOV9ngo5WR>fbF>LMHyEY^k4j6!N@i2~<`2;54hh^%d0W*8_49i$57nXoo)S%vkY9457 zKmPeK@#z%`3sHAI7JIb2yRJI;oLs%EA(l(3&iidQI#UB#u8E!EcCK4zbM4MssWbi? zV;eTrtBt#C%y{i-{yJ1EmbQAaxJ^(Kqm6f>ohStCl!JY2Gl>vMnU+^&ESv5*G60Dy zojEvRRKoVTVqb8FW1kmgbOm%7?fA_jJ|1Sgz5U?efL7z>f}d@4+Sg$2=!~QGQZR+{ z>*Awykkrq>7L&G%CQT4Z$BKMh@rSJj;d{|J7X+mEEH}lfqwf9;s)|XDQ-#Sy%p`N% zvX!Uja5KP>7#4I;Hho1R>~FvwKXirrFkuWf?7@L3|AE374{F>{uiO1(obXmKc@$6b z1lQBf*{B5QgvAkUv##oM6JZXjf@;Yu&8x}vfqwXBv2VP+LF#>kDQf@fPYHziCP`KB zrxaQRYb0BIXK8F~)R1*w=hZjJ(R^n|IEW|v{ISJ$^v*3@$4Nua74e;- z^%Il~Enp9Z78_p!@DEfiBjL1SKHh_i6ELt*BIzp_21eKjSCVobFCD#z88RGIKDR&m ztw=G=5xq8O4_e>U%Bs`KIL?<$irtxzHeX`z?oVu-T7LIMcTgNHFn{{^r^8S1G7l6x zvK&b*Th#Y|q0uoYm8xDnZR~9m=h}r6u-Lk&Yq&6+KB^9h0c$T8XyC>28 zs%J^91_aq#`p=E;V37zvjpOr;y-H~G}Hi6tB((T2NdYOhM2Qrb$bd&-2} z+aIOIQYI%SJtmV^6RU8{K=Wu)Gl?;+le@!Cr*cz`w>JB>Q!OqD#BJKL)tflbLE1Vk z>4FaV?7evy6b~t!z9|8c%k-LrH$pvQ36WNb4_ncQ`eq3vJ;&L>Sj-alk`sS|U|4P6 zIxM5!2{}oZcO7F{M0ADVTWD!dSxj`9J|X|miWu}o6MC&*)SwNcCnqa*BiLx12*BFo zOB{0avrfEBE+;1y2u(Pt7vRvE%7>B_fsikXdr7)PWA}xR@I~bEy6)+`@E6w<+t*bG zbb+;R`}i|Fqxcn$$XdptL9K*4BZ0=yq&LW)Ed;o+7nFy>K_t?7^|E}j1tFw4c^+D$ zW-K}+SAv(adMomx{l1~j;wr9Jh8SC3NjN|0~+v-T%T9Agc z;v@e=#;93ITGs{ga9_ZkA4t9m@8iXCaXVilsN1bXxVJa!P*zfD{7dVc?wS9j) z=Qc?3p<+y27CkoYeu5F6?te)Xed>uNCPW-%KtK|00xUTPuct!F+`EOiaexx$jI54x zWT&6nX!8X>yNIIN;?vx6j+}!rdt!&F{3@k_5G@*^h}@ zPY>6MH&G%{luchL8eTyD!Y`y=(|LH8ii&lEyPH0~jRK#_J*Jt4UjY3@m`bN1=r89} zk5XAOqKmHfK8Ut|%(V;V8~@3NlVkg^XfE)gzqmL8xpfTTQd~(LVaN%BJ(7Bk@Z;hG zoo&+Ur`5a#r&!O_+Se8Q3jN`+;6I5$QV8f*GR9Y zva>{7Bfy;@rQY&#czZnEdpd5e1R+X}dVb`InW~a-fi7LnItoC0T4SGCCMB(|+SmX> z-$&@`lQ)4q&&rrfmdh^*&^Xp?N|O5y5R2dt&p4xy3LhwqKs}(-1Wx+g?}s7;3B)v# z+FmbO4W*5|guG3L@E7_TGL7?!t0 zF>Abiu6-bWW;$UYuj%=6V))Yg8078(;`$GHM~4LNM`5C)WQpPdTl@o=P^yK3j4mYO z`&cZnDzc=nFHW49*bt7n)cPBNREC))T%B^ukBKo}5`Tb|nLx z?>|C#o^eMfyi}4=Khpm>+NIDqKfZMC)S`kI)ZxdY+&6L6i&E9Bhiw1GwhTA2O(F<4%tk-yCev9}n%FrU|>glDCpJ zGFt*rw|CLo%9d`A8q)1zpxosZpp@&85cf=ki}0Z|!;qGzPnbqOnwsK}k+ZG#G#Ne5 zdIfMe)fsUU<*Jf&%pAd)T9eq;Fmi?=_U(M#8vi}CTBSRd-CK;8G&pV0;et2@8T_6| zDK^W=SZxQG%wEE|Z>$hjsQMDiJA2+CSzExXr6O-7`w6}jM95W;~T_$#K?rbv!E`ut=2&K?0~iumE>OBRih4V_CF?Y} zS~Kt`sE=@zLW3>TZ`2JN|8dL5%{l>mK{3;_&}*1T!JRA zdXI#H?#3e6S!AWRiMjU;Iz(#ybFS&xzk;mC%+$h9cNT{&Vu*E?(;>yi8j`>Kwqwy^ zmhgeWb!hjEi%+@#1`u2Flq-?-QrtsAn|zI%f5xGwFJ0C@i?~EeMqaH%J3Ct+`7ieg z_%bpzI>ht5fL^xa^|0PF+R@rOKu{Hoewya+M@qnSjZg^YguZ4GwNSdR|2olP&snmT zybko6MEI+L0giS664Yx3r%pt(LJbTITvSrO+PmaR{?Xra6KTyL!+CuK+iA1H1UWiUM^;3@ zG1%SvZQ2whvDLDCF}jYuBLw5c{z?OmEuzhH^9z9--0yNB<*STm4vnl32%+T6=Qh0 zFts@q@5$!YXJ2vw-$`N(7wXY(P`TYO@Pp6dC|rXvGu!ZnVV z{bjFmn`QdpM=W41YP1e!kkKcik2bS`o9d^RUV2t}c&t@cx!y1`xS@Xp%i>Yi}*w_xq zmBV|5c|GAkYXL=R%~4el2l}g=c4_eVR<95a*J#BiV*?sjjlYGen&p3)8P&fca1e>^G?HAB9=ar_809!;J5`sGr3jCh2;00QLro89DRnn^B6 zzha-Dm32(A?D*%lyhMm(`=g*gaRD4*HU#p56xUUsex#Eu9E9!TE%Hx>$7^qzD84Gy zqFWa0vaplPXe66_UHKKb;xuiWqXAups-F}(*Pd#&;7441NJ`}KJ8#fgsW9>8G;M#P191?~VvP6ZdBR4YKyF_d z13dN#E1y}M6e_S~Ie5!7MPFr|g8%U-J>yG@mCPJR4)h?Urz-ljDo4uDCq0+?A#2`u7duWG@tS?lDSMvS?d-WVn~1h*_B=@;e;*De zo?fySN8CC3?d{js&aq(G?imU3+%PMctr!J>3yeMHMNq>)jx_q`Bc`8v^|Io|%7G}i zZ?eG%GbTt`lYU<;5Mq*6WNP40BQA=vtJLCNN-K%e%u3F!7s)NM{)yGvUID9U&tpV# zpT);_Q6OL&GFpFVFq7z7S^SutxBolC*2k; zJF5&(KUf0eI8|3$gr^^mf2pLTKxB%KKA$WEw%lthHNv`7=uizES;w><`?2QYMH?uf z2ufNNzn%B?J#E4KDr`!3W>I3q155s~+oIXnlZ9kyJ6fbj{8=(+N>2b2yrkHv5|O&d zLfqoc{|F^-=n1J8+E=h!*8TUwUxpJqX(Fo)t3)_ZJn&k~p(MAj%il~o4MI%0UO$JNP8!K2gJ24Vj;nu?8$xUyoh7Kh?2 zd$y-LFa^@7oF5~?q#Q3kf$!sd` zX3*5Z^+!lZP73d^38Gby79imb|K`SuF%%E)x@V`QD0wA-rFf5Vs8z_-En3Ks(h4C$ zb%iqkTs0JGmvHDYu*I+Mw~dn_ey{dt((N+{<*?^mb})GFKf~T8ao<7@tp}dztTmr+JVgK{b-2(W|7JNeBQVu zYp_T6+r-2ZH4rkH0EJODv8Z5j#7f2bE1bqhKD`5 zfFr%nz?f*Hhzqd$`9Q;CcwHuAVC9)k&cSm0*4~rqUgU=G(It?0U`h!) z3;G$c-DD)PqUieII;zR2XCE+f6qw9ckv;`vcUtRnNJF$XBHF5!Z2bwg$t`Br-eeI} zPH56Y*Xb-{;&ZhI+50@%8^PjcQq$h{_K0$|9v?UbEd2E$u*ly!xKvlNBdQ!{*|RJL z`G$YnfjgdT3B2ldJLj1~Ei4m=(Mly)K+XOPa<#I~JY4rh7<=e1sSP?I`}Xs1p;p_Q zds`+i&c~O1|L!bnR8CGa3hq(~x72_9apAPnMOoM6mxgwP=g!=?amXO^kDs|{YgD$g zWEuX!2VjY?_>!U30;%K%_~f5Cr`C~Q$xo_WXP%HjgVlXq$IO1m^}HH zu&n+ULA!G9f{HfTOy7~Tp+_2JDesTb$L(uOX%@Ms;L4hWRMOg@rDn8(AJ%Sw+Y)SLg`1YxSmZxw;f0^TRY8Cfa4P>w zTLVk!WL8{l==2SzOxg|$`_7f4afU)ZYM0<&CCTN2wss$WXy+rE1dr5PM7T@uu576S z%&g6XuV$tKHUcLzi)-`Rh5zl;#$mbDPCAQkaO1kN3EpYq2xHW<7{ceo$Qt}6UuQRI z2f$rFD1SaEB`xGpJfg`}#^3p`a61wwLXzB`?iv>YQDrN0PKmel%p@zJhKsAk4(%UU zqMr)%;Pg}4TdoX+Kp-8`DeaL!kgL?=E}P;zL#luLpc?1mX<=mHE6I-S6Zw{i_F5AE zdk3m+vpfsW%*3mCJAUKKCR>vvZ}3;iz1!9wpkU+(x3+fIrhmShWyq9^XIH0en3YVn zy?uys{Nz?QzrapeFT;1raiOSavuK!WmVtP*cym1Y-KsA=wiW2FtVMg9F`YjLgK5l( z`(V{<;-Lj56nq`vUZfUnQy8!*(FSofscVqCSP9VXYT(fyw&lF&C__)gY!t_92ymO< zwIZAX__i?pUDW;CJ$$F~*oG47zN&X1dI>0+L`86#nq5_Z1$~`-s4E{*c_v^;{FL;$ zH}+qIGr8VPqB9B#k4*^S#@)(E<`zU5IA0EGp(&ho1@#ZMg@l%)hyO~h)E@r~#{g%j zw=F|M`EHN@@YNPHy8^_4iZU|GH$AM(8_n>F_-w0e`$RZ3^Wh-DymK#3L;qa)L<6vY z=Wq7LhrAnv73vxUN|6|Oy8MJ$vsmjs!urfSrgdq>JE%=gX3dz?+f&28QoX)nVZpjf^aB?65iQsd#<1 z@_+v-DJj|8t8BHcIis!BJg;3uP{+b#GBPrL|4ZoT=oAzb?C$Qu!NEmDM8uvkpPUff zqOBnyAlSgKfp)B2T!{Vvw$TgLPV3B|DP_J`tO4VN>t0(C*48fDaSp6zHtg3p4Pv>? z`hV(O|E5yX{TEz`(wf(!3(BkdVw7)1aw#c5t>5by!Fj z@kW>0oZAKA46hlx@o|?nopEt-zY6%gI~Z_S+S;+!Zt>gr7(O}DFBupZNJ&Yp+Pno< z)34$Nkl?L*GQW}RCUfkPGGP=9(q{8@?8l-R2N57f+rCE@MEqo zU?Taf+ia}cyJFC}dkgWA@s8{-s}*b~yuB;g=1$-WeCFFITqH8A?&aeSAoq_YOgsty zor;NV+)AljHcQK$NC;lMC@P0P>-Y(A+%VuMmX^Ti|Mf-`7S*>MbAPk4J(Ke05!Mo& zckE0JskF$+Q|2G!{(X zZ|#KB-v!aXo)_3RCO>vx_icRV<{T<9X-*F({F&F=U_43_Jl6$LseDRQNP^+;QC@Lj zrDag{FI2?T6|-J0;eM@06Ix#5HZ*EM(uE|f5AV?$sv{|clYH!-y675ga!1eLN>(~8 za39^07VW-tDv7=6dl`th)VfR5N}JC)5yL?n_LZ7=t>Yx^32xABsQio=lc4FJBYZ$a z1~Vzn5Z#C_ru-SS8oO-^A;e1CCKvUT3!A)iduoCBacq8W_;uAMHcG z?oe+`9a9hiGwB{obq_xj{F9<0!g{kjVUU!1EE(N!T^{rnGESQ!V6wN~ zSD`c^tV$x)cY|NgfF0GXuOF#J?RB%_yX1d_2vWzmafyG{#mQh%IsSi{N0G`gt;a>7AcZO7G0vK#%#5$F{>N` zWK+8>cO(bE6T%#KrWhkze*uq-A%4vu5i?*ia3?n$nj#A~OtmsW{*r{>Z z2>hhVPz_bXBSUHH{3>8awmiDhknECQ)zv4lxs3{rS~|~h}osCm^F3VDE_2YQZ0$byFreHbe%21>F;7&?{Z3GUd@z2o123CHnWt@gyL zX8#-71zYkaA|^pRwca?7^o`%TtbA`*d!(cZ2+3y{+{U#Nr7+VJ@Ho0)<_<62OMWoR0fY9psF9L?RyD}YU z!{ghj2A#vxCz1c8c~J+=?4j!DGxt6AaAc$k9yXuZbHzi1T%`{46aU~B8A6+_4F=K& zM0We+x-Zs_aTlUy^7F{;=!Z(Z_6F9ypUjwoAC|}{fuTmb?-KzZ_`W4Cf4l&#%BUNr zFWX9%vsCHK$6=df0f8V35mA@Bs}@iD`Bw{LY54Ru!V+3s3w4GL z5V)is-OWFrdr8il4DuJ~`}6dKdU%=PW%8an>N&ldXyvaCzhrkZFp& z4Yf&o)OUk5AI@Zbu99=kF~cT?ZO_{mN@SkPux~+UMNM{v!AoCiRB$OE~zA*{9-2b zq$L>%=gP@Kfo>g6#-}CTlHGFchiQa|n`V9U;x3=}ezrHCCWj)nzj^V9Tw1#&mQwtx z_HM|?cez6Rq<-$0sFBU>9Nf4g32!#5AT+v&kgtxw(tH0TSRHPD$X%*!$_Um{{myXT zCNnsy*~KT`reAiv>^{Z%VKjikGC*@3g^lNXm+B^IGO$?e=eMY9WY4wz=VJ?s%$_(m z6Xj777y4C6EG^|IK2Elf$G6EY3#NzB)xzM|!7j%D1Maa2r>Sni7ca47 zWDltTeAg(Lgl%QC;_Tr=2CkSY;mE1*+hE2`Q6f))RS)XEpY^L#sCv7;=&tskhcOHA}mAQ@t zbU>H%f&MZgi8kCX1d_}?It{eW&iZV+FgEx2v~tvk!RPN{=mKVab$}`D4*5h~R+C{R znN;$@3|ey9bBx2A{$*z?!umzO#1Y7vE*B-34J0xISSp9ef^sk_XXf|NuM-y$G$L*T zRF4DP6qgV4O0iS}M$$W+xwP!BMt8>N(0C+yx;cht??xb_M@>O`PTv%PWpola0?*H; zVyAle`tmo+E7KaJ2Qxpq<|7oyVJC z^O5=6I}2B?I8v1GNv87XNj+qR3#2rh*t|)blfOD9y&wch1cF+5q>G!jBOAi)wN_Yj z&v!v~o8JJ|j}j)slC&{ySNemLkOr2<6}jzUzv>D_fBk+lql#M&mA|%pU^-;8y<=?{ z6W`Xh0=r-}l1^wVko)FMQrHa%=Smv!5sT1g)U`N2od+BS zwsvA@A2b(CkN~BuI6R6%NQ2T>&kbiB7ix6HhzA(Q6_U}Y1rRt&XTiyTwFV&M?;wNh z6LJO0OE1+5&+Xxx?XowlXhrRB2sv{J*>e&9`9w}hj1u|mf;<>d^X$!c&h_W7vnFGU}xX-RE^@l_69uP0jpgA-A+rPlr@Bueko+u zM~0C4m}mLMIDxInG8sq1uKatKffNnO+C+tWtsLxU z=*h;iJV&2oT_z~iK&GU3u>ziMm>&&G7J{rW9`3|hBDGxDg`Llqxyj4a`>rywgMM>c z!jnC3M8wz+s-u(bjHioS%gw^>wB)Zoxm|G2-n;`0NW(^wpzE?v;=_`r}?rqL~>i*0Slrb9t|)l z0YvL&3(Yk6M7J%JuMV>B>m6w$gu7Lf+Z}1_--BqZ(+6r?DJ+bTGr9uHIr(%%QM-WE zX0dcDK!gSeXa24%dTX@S-*F4M@6}iajzWU7+cTQMV`rb{-8?g}<64@D(ZL};o zyZ(hkf#giF7*lYv#?LSElY#A?R+QoAAYd&;ftg2?EIrtS#}+9<^q5FdKvrBN5{F=b zLg|NNY-P2x^GSe!>nfcCA3ZW7S=1Mfqg{^#SGn~eV>(WE5Fzmybn)j(bpC}e53kbN z=Tpt*T65te!lPfQrYwKkVtyjn>YH7S{S;0o?%L@51YKph};>Z zzJQrNnyt}yvQN2!-mI2lTRy-#>4jZ(ED7rbS3#`gKY=T2NPXJ|M@BG*0d1BrF#`Fb zxiYJJ{2c2tf+|!x3z7b~{q)bM(b^!*pril*a$6{Dub0Xvzp#+;?1uQVtHC`C!LB(sd= zF2>vi-AnS_&i7wt9S!sarq_@io0v1wmC|HUS5hwAs{zE0qLQ7*$Oexa2668xeM&f8 zn{Goj!7rn});&4~T7+-zr4Oj2!9ymcc;c4k%5B);8Q9O*Jvb`K?B59c#+tCjHOd6qGG3op(>l&S z^C#S6X8Xk)%_qMW7LUL|X-=57SEft(*O~A5;p#ZdiiOynLS+DL`Jfz+G8=yc;bHkl z4OXR|)~(ES#zru6fw9(jRSRW|xRK?09Pz2QzFlfoRo)5?{?1bPIFJP3K+d;JNpl>< zgxKnyTujxYya8Fyp44NgP{JF26IRzQ%>0twSi73E?Xc7{AgO*EQ5{for@N+E>U4f< zBw5kp@PYj~oz-GdPMT3z%cxTn!*RCDv2Erp@W6`chiA;JdkVhRj=-ABp$;(w&C2Vw zcQMXi7>M&R7|j$IqF`^BOM2oPw?@XQT*+%Iesd{^661N!MQ-otA=&KFb2zpD+qqai3QgL~Ku#LW9 z|54mBO($hku650;6>c`X_~xykK%eyGWs5jnDA5+nS>%0+(WwcW`-$unvO{uHh@=&YYUcE!v()~F`AiPUYgYBd>`!&w9 z&URd}>Zg80uN#j&x{PhdL%PxBT~gV>LnR&*S#5W5<`i3j=M581yo9FUWeq_C;Sl-w z)npqc`!LMng)3@)s3&fZy7-euQ?x-QxxXj&WEMG@Nf1*N2;3B=p*O|jDl>VYRducn ze{${6;af59;#*abOg)$ZS+IIS)^Cn+{@#SyUgb`P$56roPe#<%>PMproj+tZjuy*geLfp0R6x4&u~AWexI6FSMlF$ zH2)|58OfOc=!9Pj4!(|U9$;9teX8*}|Ha;021nL2S%YR~Y%?>onW@awW@ct)rZ%(N zOl@XnW^6MvGqc-lZ$0nK?8L@?^UcJ@#{OC<;z~-DbStvpN|||%=+i3a0gi&TKi%qI z{~*bYxh?51NF7YAm%xtFASpvea-o+{E7)h^r`R=w-mfJ~)ng2DZbqU;+~X)=BiVa^zKAMIp)-6yfos9{&5^LiRRveL z)S~VLd}E%4s!7^Y)!B-QnFoyaYz4^7e;3*)86LM z`XbHe=cl)~x1phax=y0-@g6gZzi_mm zwIEsv2ND4RK|(^pW3@;BLxQ&x6ojyg1vn$~MEQW-nVh%)1L*iJV$bRBy zY#AX~SXdZ!oB&3?Qp2>NAGF2y;AUqR;0rDF^z_Otot*3-!3UTpI!_;_(H+4c?}BCH zm~Y2o?!xCa+Xt)WMtJ$?q|;e6R++`=&#U@SSBdEXd+ZK!{D~6Xp|N z3C&QVKusfjTFRPRQ|$6P?11 zn+-!?xb+u?KZV#ldB`>@*cJ*S#sX?h4eEGfd%It$3L#YvVX6uAwmQN_okW4$W;lNS z`|2vJsF|_1dP8H!9&?EFN%)&~ihnQ;JIfskj4booIIEcSXQD=R<0NBS;LKOuOP^5* z1=@*|Rh=J3cY6cm&;*UfBl1x=O>g^D>8d^Py&=Ra)aGD_>G@~O=_)7% z(Vm&=p0)*W>US~=jV#HWY9+ZDrnQ1z28Hhh_)#NG%ExP8QhB#3qf!X97Gh~>{)jr= zY_5CzXLe0%$5|Fq9U1t0C)00tN>oJU$;&umOXVc8f+$a^S>%S`;O+?TnrP}XgrRW_3lKJ!qKx|z}zesi!zt(K_j%UcqS&;Q;0ZB1p$_RCQe zs$!vx2~N8`cWnVu|I>UeeNr1dVA9-KWh~TO8*FH*!E)+(>9yUgttb%;$p9ZQ-cRJn zo|ai5o&nT@(nQTs2|t-H;H_59($u-7qaK$s#yZJ>(f`v1WhEYPe?$H$!oeA1Ud~}2 zhPY-KHlvAGWxIg2w?OXwru}5N0Y^*@Za;~5)5&>bHnIsrpd;DQ%w@IA=PRs_ZoF6*{5St)ZC8Ow(?(AfS1=a}$5cPHCH64!`axB)u8zSzLIPxV43N32_{^ z0rU5&MGc9B)#Y9QrB3p=t#jNwVrv&+#xfV5By8sKjXzeS)zmrSFUw&;QMW%A1+N(U z4#j79WZk#UMGDyd0Sb(QPKB!6pLzpm?eersu&MSNiBELh?004DfIU6o@Sx;AK_o-? zMUyRZ+)#l=Jn^k_1aew~lst{Ox*FF|a}mUyx3!JQI&yHWHo3OyhGet@Qj_!dU+90( zrLjU`pa0%e+Ox9L?R|U%eV7rDP{zNtm;}4u{Wy3(}(cyT#=@l5# zReq9!>Z5$6Lu%nUtY{nK`czw@7bP;UWr~fC4fe!kc7C1u<^5+lK_UjEa8vucCM9=D z|Lk8paPKDjz%`c-x4OHD*JQcY_z)X^OKC5jlJ&!y}i9wQYuR}n&>ZO?WXh_+}zx^q8co5%vP8&l~G8o z1IuwA`1mPc!^_Y|iKvqrWCk0FO+d(A;G-uifjuNw4stN)+sxBiTE|9W5O^64w*7g! zPv-)rhvx){e4<8E#s|}ax00gno8Rb->CZX5$tGR?)%t?{DLsxwYW5cNA@vG>c9=i_ zJkSZ-+B3;8y;$6A&1I=quO{;G@ulLH{WHv0_`z}1vTsOH$qsU_CBlD$`hfm#{Jp6- zYM#6DhOk1)1WfX5-+c)Q8rvR41{MPu_4=m+ktVEmL!Yj?@n0G z%Yg5w1xn{VX&?cbF|4G9NL{2c28Ku0<=H<>VTw^ahAF2c^&SL|GnnL;nX=j?MWiW; z(bS!Uez*r&{YW!L@2&HCkh*h*+7a@(=!5yi5N*8w8z59GmBS;t#YV@R^YU`<#X;DV z&gaS0`&{PmsQ7s6QO~xV_rAY5BY$18Eu{G3FOf-+`2J)3dMV>rU2@Frs0cpjV-Yvw z5;o@7P4;8tuVcZZy^&Gm39w(p=iprjjlFFw!M@){Ua50bJ`q82jtF6MuY9TcgDHC+ zbtpk`PhBdcSDnrpf&8>i8012?Pzs#rF8(WSA0^UEYgT~@T8uG61w%S%2o^E;k#9fA z0vJB*_Hm^i9%BU%uP;TjAvSe6Ifuuf6-zM?W*x zAeU3ya+^%sr8HYdDcqpB zh}cJXWRha?5EhjW?=m2UpQn3(3!93{Mi1GPeETXPxIjoia)EF$`Xx&nzdyG_v)Jq_ zh@wYaV9A-7z&anfcRWV(IFuIbGCMWfxG0$rS862V_S+Mi<|pzEW%e1Z9cSR=8r<>Q z6D8cRjr4ip1nRtH}>cA-U^SiluC1=L2UixECx>2aEuJNJsr=F}^6_#0M$@ z({JtuCExZ|L)*yp5)K^@I&V0xClu_8%*~qGFE1tpPP$J3(v76) zv3;O2I5q%iz1=FhqPPhBKzY^8$V3`)!($qH?0&5j^U2q_l0m2tU-Dn zEFNQ?O_~)CI0x`te_$20uFe}8o#{wRUqLmlf)l`!b=!!6TwT-);>0?&U`cGqu0ET% zSJWY#@h%Enu}F#79GMv#P!#Tk+)WJ+6k}xQt2%{VYJ{mjWB%ncJuz%`@4UY!gq7Fi zJ^E{}puQi$;QG1bm39zP1B?jfnR2j(+~!+#$#&=L<>sRs&==pgIm`x4nz;W+wE-hJ zrdo<(WM?N_ozfCOVqj+G8&;>Ly4g~H;yxwV*?AzzDH9}@n7t*3Z*^E5T5S%2W~sjc zc~uZya3y-KwdhH-V6-SOQ>d<{l#IEZ8RRF=4s1eL2q0x7si3Iuy4JEu8QE+3W%Zso z4xVtdZHYz#HGE6TeR8(LuJ+Siz4`cJcK6H@49s6wR}te%NRl_SV{T?uy!U=x5Z4X8 zUZBfQ->eWc$ev2@4U`jjSSP>e{Xw(x1+54SE81U`0wq}LMc>(^7` zPF$T+EjDL~6(3Z6rQajJf`ht)!SNE_fFZ8UR+`uu3gBgSyJ8wHe#oN{%TO&Re#02i zI5(-j7&c*Rxv9c@y4n3vZSB;;R~bPm@dyi!q>DQ}g^Ck2>B1k8>2=;++=#xF0c z@X4lX``%4xch?o&o}@zvFKruNl!ye!<;No+R!}^b~bu&?OgU@(1n|pK>A{_k)=HuOd%Z7EXe(AWY2CjVR%`f$dFFa1oP86 z=3_W+NC<6I@9u0nAY#7643swcJ|mRDgZ6D|hV(_}(7crV@Zey#WI9+V7=cT^Gr zkqH8PWcHA^+}N9%&T(U&FrTL*dl2w@`4`n(27a+} zjn6FaA)-AMUP6xZ(rq3x%Fwxvh~_i`2VP6>#)`VHGTcM6Py+5J6ptWYw`z;u&E0(x z1a=hMkVZo_Oztn{g&<0OLAE;aM zMn`n0?CB&V(-A8}W*C{ljl)kPs;wC!r_?OnxW-V9 zv$JyewJb$dz^Em4CGkOqYoV>R~Y#<0Br+EpG^I*-RPmm6#guvZ4%jU29gH%8-fz z@d1KHA=_tIy6nFgBrUz$1<^OdZIroaIVJn?$W}ft{W;c)h?K3!Td^6KoKa{@_Uss; z>r~kA=iC7ETNFJp1Ln(#ZAi)PveK>~?T3ep=(QVb{3CL0m7*!vCf>6c7kw))D|WKO za7Jc5m1-+ZJ(84^&*5=r<5eMkD2cN*1N?~|9|^Ih-9aRVa6*5{n9VOC!f4;S>Z^>8 zd-OYT7IhWITbdC;rvMY7?a^-r84d3{T*cr8cor?c9QYtae&AW#$2~u5a9gEw=0|xJ zVd7RbCx9B&Jy{Fi*`9XTEMS|%}Us8Tl_~@ao z6)ZZDAYJUKzs$vHDaluEAcQkq^e8i_$(-s^U(Ax(#T$KqaM35{0?LWW5V{n_!GlcR z-WR%8VQaP??jKN(^ay7`gqf||B7CcD0y$?*TlFz1`y;8E%g`-T=i8arRN57xoiyF3bj|u}SOzfHrL&kU|`V82EJv$>NdRPPnnwa>Dxs7%}t zff|t2a!~aeU5*MHA-;eTF{l0xJ?b*S=<|1opo*zd4J4d9m+2xPX^9c%#XM<0<6UL7 z8p$e0@Heha z*fO$>B%x%!YY0VJ1sX>O>nK-w{&Q`7g=$(|54?n|4T>L06k=*)ZFCJK`J3_u=}&eB zUd#AA>NT-tE8jIn6Ikw-U|*M(8MUu*8ax%@xR))1wpK_f>wZD6=ZjcF>P6Ez=C6B>m?oJrCLO{}+T zvBy|1e-nWtud)1h7u?8+ltasUap4UR8lC*Uv<2BiSRzM5voF249pBW#2AdypHBi_W zF6+SvUh;Zit$DrA&r+&}#{1Rzs5M2+%*yvY96^goYOQ?~C>m4skS$>JBb`L&5p{`< zE8tL{oRpJwE}l};ao~Z2(eQo{3mK`t5-=~JSZ5qCgv0GM`ii8n5}#u->}-McAR!I} zIM34dNI^?R!9+;A^r(%f?%;YnA+T1z6SM=_DP}p0Ggakr-OfL~gy_?CC!JI2*2#ndg$d9;`v^OKJln19lzm>Ochl?xcMc0?O8!UX$S!&4e$ zoYRhhaL!8MEg=ZLD!iQ z(!53=e6(o9-XSjGq9-*Joj>Do3I^g+nrpz(=4~>z8?jNMJwScVSo{t>+DL>nXgVN| z!^^R{S2(6=a71icQ_HeEfQI{kB6MCE*IK#$v+Z$m*0;S4&M1N@jH2VN{)!gOEp%tQ z?NS*9fz-#xkNto&PEU=OW{c6xa{k$nY;L&=&V>K;9z(6L&7fIGqJ%<#04tkFfbcdW z#^0Hmvj3sZ&Dx*xh2h!pG^ZW-Ry@Rie-=l;F-}i6<{~v(N(lC&P~E`i1ZCJH2$^0B z8JQL$Qh+KQl7y0r+qmLT2tOqPCa84jWx;h9jm>9+#UvU7zXGN;&BR2+$%&dE09z0Y zI}ZH&(mVIQN*pvlxMFq`;|U*g)O9j~RtD>phvuj`Y>ZRXcdvr+M+!@?3agKZx11zT z8kjk=DmhIOb-rV>k_5S3BE1D|%$KfK={jeq7R2L?YsmnJGo>?vG^;{s`U=Dyya>UJ zn~nx0+0G;DC_K{BeXmXEbu~S>#J^hw4%C-3@EBicek|E6#gLW_k5H9oz`yvD?zqnH z+?Z6oeu;8$2){#@1-+QL|%kXN~@Y5FXVIgMj1a51V&uo%wXoRjX5!cxb`)7!-WqXbcPsV5orZJNL3bY zbVq4|va=xsqI60G<2T1yf$Pk|Ld|;!IIc*}Rxte`7N+t-)2j+363*YdU8fPOWVS9+ zEz9jq{MQY2-(jVstLh;=q^v2APl&9!_>Ns}G9T!ltC4dbPMtKF_cjqk`G!KNY!=X+ ztxGjXI+?xpH*Ahqz$#54V1i{PXGhxgdQw6oFPur}AOp<_wQ3y+N{~Cz?R`C4n<~1Z> z)4Uam8wEWu@0ZtMXD5)TpVgpTUzFJf*zyO3p5uYu&aoftir8l2VoZH6nMAt)HHE@- zIr58=!L5-JGS;H=dd&tW<1r9EaMX^)^-TyVKVDhUh?kdEJ;$@`dy@zQVVNK&j4GoTH)td8n!jfFr;VV3)OuUjlYf61aT2wp!ivG1? zL`J6V*aXW)epY;B5|t3XQlDsPJX8&o^cv~|wU>__q*%P`b zKQMA=!?F0WQhK;zlbE$WB*RQ$N)ya@Y?3#4L{=vb>lau`AJP}Ua2XEf#XLWcEtLSCI%oeb zQI69Mq>Q1uxjf%RcJB5fzgl&6bQNAYyW1}(8J{bF=4M+1@Brg75PbZ^ehQ8L50`V0ZH4|@+K#K%fZV3T07WAIahg>D;J< zg(}e}A6Q?2nxFZ7h@G8Xd2W{P&VJ3bCr*?+X1Cn!2H3}*V)0c9wnJuOODEVC9Hs1! zy!1~VDy~-aFst~>Tb*0Ox&YY9Umb(ZZ(I|ZL#myZqM|kKP7w8M(ZWGaNcfc6+8Ihd z^z?&(`QBmzhxK}!uUmqEDQ4-s+QukoZgG98RwQT`&UsI++XYJvkzp_rduuF1lxz)2 z&jLq?YDlmL5DO85lRb;i4^q6yaV={Js&58;XG0So7gAmM7YN!=F@s9`5Hi;o2Ag#>Fqbbn%rawi3+#9$o9! zm#ohzf3?}@#J$z$=jXjf92Xl8^vs{Cx)6CVsz#1BAI%;n$5IRG>P(g0<^830HV2B-r>0sH_w!1jM7|LX+pKy-~#vn3;_B8ZU9RFBjA4u$AbYi z{#q)D+&cMh^j8C9BK=2yVgC>H_we89kNCgP-+#*g$NKxvA^#uf@B9P7|93Vq(Z6Z@ z>;F&xYYn`AeG&byd;XE$fc!@v(Z5%E|3mQal^enT5Pa}-F$t4{72V+TLP#0-=!On;9w9Cem5j8kOTOCeE}-V?NRjNmH5}b z+k1}|{vIl0%Om+lmk9~ju)@AJOuw-7yPv(Dk-@r-&^P-td3m)hWzD?k!36zBe!XJS zbUTQM-QPtE6LFce&H92k1gAmj5}_;Tc>u!{L-v5VvlOtqdeZ?L5`yxCii^90zmGru zEjh`4yWi;@YETibJB1jXHo$rMF%Qlq=I)*BPD0N);DhBnR*GAx zP(6dOt&-vjHk7P6xHg?48OokewXEh-@T<`~=v@^0lys-;nj*CZX2^+w zHx<*jqIkX!ZWhzCoq9v}VV4;ew=#M*iAkiQDpT}cemq>=i$UvUWG zCmHg0o|_v5+!JpE^9851nR(t2khC|J!QwMM_^^<*_^iNKemjYuIyzd)t3vw>rx*Yh z7q{K~YPR1JBDQpTJaCwCs@S*#iD#u4aKdpmZ5Q=92F?@_sVk^a>-r@i@1$OKqdyuY z4K*#mhcW8MA#XI3^u~Xgm*rajEB8ksu2X|!57)Dc5|j-N@8xY=(d=0}Pxf{!%+4p( z2-G@P{D*xq*cI3Br691??sAz_{PgO^-tn_L@=7F!XM;bn3Z{m}6K!{KPS1&gr2p(> zA?%c^QWw5k-0GN%d%l^aRMM{3)S$biMYGl zwQTp>q4^U5Z2i2%gKZknpavf=_f~km1Hs;(jClc0PMijWY;4C8wrZ{Anm13EFpmLD z84PnNuZuEuGXf~n!Uw6@%2pjg{WKRU!`L#-`};VyCh6${o#Q#|Xz}ay!a=%7{i4-W zxGJIP07RSe=0Ng6vTK$|PJXxB@|Oazj#}X2IBveErhim=a~?C=^hjXldoa0%sTP61CkI%E(2x=FmMuLc6L0( zUuT5q-B`)z;^X&Af6&f6y`KQpz*sL}`lEWJ)%s?p5}Yv_oO9J`QY;XWcY7D7m1|VDu6@}#=xRF7Z_4e%+d(H?v} z8mT$~o$JCmY@Hedr&sAYBFC%Nbt!yaG-lATIZRZGR2h8IOeN;Bfu^kgh@`tT_xm?N zDKmKZy)xX2!-;u`jI<3g>1p_4?H8rzJ zM3Up_jiob-(p5>Mkh~33fqbloE(fyv8}AKvEc3#M&iNqu-8EP5Y;E}1;nTQA*c}cr z=>BR<9a}<*DvDZrp36|#e#p)GZ=Z3i(nmKdbbQ=FuK85aTzq?@6PX?O^bCX7-4w}s|5N@mzu0Vg?XFq37 zU{}cQT9Z|gB}lSnX~>lJ-fDhtC6gAQw4@>6eJW@9d^c=KSiftE&NKbz7yGF6G**@h zB|2gAIo+MY74m?!BDOt2B@qy`F~ zg5-4)TZ!KjxqF#Vf0#al6mFhok$+x5l#@2dxX0`Y&~J&z9vi;xKp^dokIo*r7hrUVs4T?j%j+nL6jD zwPsSvbRq7cWb{YQMm6S>V%$!}acNzsH)a*+m=%EhJu#fz@h26JF~(d9K9&lo-{Syg zk6VXS6-d_xw5-J%<*+&2a1r{1spP{{Vc{A0Z)9j^oHYG}j5Ze|GG)O6zAj0Gv`L^p z2%1;}it#DSH$c=C?6DVQZ3pX9o4}ED@9z0SS(x_L9x#BHqnoO=4Ioj680)uuCa3Yg zz_s^(Mbwc8!}_dP3u(;t|8o5FkkP-#PdhdSxm3WlL~8#;wHIMy7dd1X`7t6}I_^Uz z3BN;j)Ce99ax-PH=GD;}9fjKHuFlXp>3r_4%(Ti?lojVZfA+fmS6;`m-~&*1jw++ZwtsX(Eg@a(v}4@ShJB@mIX5fz1m^DuBevMB=eL=T&^(4 zvT=J6dlA>@VE^O>7qlTXzAHfCKiP$(o}}JQvV0Jjk2Qj!-zcDP?BKgJjjnyn6EFXY zue>&W6=pL~8M|l+@9EhjsTZX7sbyU`)6zI^O>2R6vGbdoA~uzbN{@HrC`C)8>jcqZ zC_ni+E--)10ulmwNV;_8vV9l~ogAZBh^eZIE}g5+FDCy@4D;#?D)U!J zOu5N~ro6H<%;#v+e#X?LbSvRl1vGvqmniPS0^J)fv(V_00#>2<*17 zu<-4ZLk#W&q2;(ZvUI(p*wVu2Y!>GA_4>>fG*m>-VMy2KZ!%$jDZt^*O1VOmYdk#K z7puDNSJ@!sie^uUWXG^?4?#geNnOoz6`XVOcjOe#e56y=-QBg8IkFoY#YyAMNrlqb zimtD(-SvAfY80EyQr@b1Ff4Fm@ZfLfKAB{sTFQdNLKj#Z!qaBg@Y3dPPgU4t!Zp87 ztsRniH$+Db<((0W4tuFR%Laayu^;3r*))Lz#4!*YwSB~(OWKvA49fa3FuJ=>Bnd@E zID$Mp$X5i2cfz@Nc`GX`c`Sbe83>?OUIt}azWD6}F@~&}^v}**o4%9<6cqe^cLwWM zRaGr4EHq0 zg6-xDn^we0L~~!7Q#G@AOFiUAgs?i_LQph#x!dM532S2P4%2@@F-zRJ{X4C<0&!Zs z==i0=L+{<&ZI$(8$Joy64vlgTve^v6W9!%fomO8DG=}5O<4@b#+9V|@2%$_n=hD%! zvZgEM*!$#2D!caf5>LWmt%Kg^Ab8|ZD4(54&;J0a=Nxd`n^<6pT2N|q^#%r;wsQ{X zo~gr9)kR$;`mPm}mA!1av&4ZD3)z95>cixH!mj>6tb&8E$mF}0jsycU> zPV^YOB6??s5QyNtb8TwWQpZ6RJ?RkvVF5Gk9(2>CNY&{q=Jifu4aX5GRH~`=dDT83 zAOR8J(@LaKC;tBZ<_gFYu%#mDG?kUEa-d?s@S4j&eQ>sroS<=f5{oHB^473TR~bBl z4*ft9$zD!@-mPj9v+!T&7D3bDv)hF(Xh+U<<1|;;mOITMJXvMO;X@C zK6uR!UW>?9Z@f7+bhNU znhy8{d@uV#lHJIlS#I+P%SckRO$hc01<~PTsHg`$a$Tg%BIH}A_t2CF4@ZO^uX4NJ zp3%!?#WZdi!I$BJ=kqf2i2n?XrCD{iJj#`FadrIA0J0AuQ~6d_9v?I1B`t1O*QTQR z_;uRn_F^I*>B9z?o$K@)<4fP}b8GdpuYGgZAwW!chf0yY`?b1xrhIT=8iw%CIctCsd)%&Q5A-|I6qnH0Duw6>|ZRCLE^f4V2~X zraPIqcyI4iubm?d(&OX#D~YFDM!ClBwoa9}o=k=v~3}`A9Q5`ne(T--A&38D|-#AFVJxmFY zS>GYEjb_)(-1=j~RkEg~rNui#utx=r^iXkJen1svDlw zGz^L-BvfS^Q5@vmAPZ5>@jWjvyagM(vCJ@4M~CZsdplV;)D9)$I*Zr{(gMe{DsAB- zUMS)MWBHqZN+H_^^Ex|oByMC1HVCEE3N{^O-`{Jg8zrpF&72S%_9*opjKrlkvVm;~ zmREOWnM&r$a#=M}%7WZkhqr}DHynJKii!#%Rys!(Ov;%%kyqE%(9(KH{RhUZc8rAF zH#xs&(>_1+6@=3cib6Bg?ZH8ad&=Q(VyFuO-8Xz7)Jpp^ee%>z5zl%q2!^?`@{W5Y zG&x&gY)rjEv+$7G_34{sD8)h2wULF^%$3h$hRB69mN5-6mNXP+CyXaC-UbgHG~8{OV68#O4~p{-tX2B*0a5zt!LjRnF?p2f-h z*+Tk?6u}C512=V~37G0_L`A5miQHR^Jn< zA>CI&n`m=OWtBf~Gwe1yi!mo2#u@dy4MBHHk?{bPX#TF3m&3O)O?K4A=?M8%qNf}4 zJET-KkbFbTo+Y`=UoS2{ zRBCB$ZBn&}gu`hhZ1I3*gY+srBcGh&gaiU>uOCYteY}JrnvR~Q2j-VFW`i}*aarg4 z?`|Zb$HKi<;*O^ZsdVJeHW6L9?}%F{)CLO@$%^ zM~cj3n(P8!vj$Hm>NWWzfE;svdNB!~CA@U0s_U?X{|oPcbG*gS(@;Bm(~(PgJ` zM1&d*EPc)j#QDi}t58;qqE3T)kvdOkXn-kOGpdlpz?i5=+>|YzmslA*{Ji-(Lu9LA zD60c=u_z?kDcW%V2}=aFn_z&unlXANvw6pa>04h8_cC8;JroIijQ|%wj(Z-*$sx#v zhpy{5It%uqZS%4TJ>r2L#2T4^`Mg&HaJa{q%3xryQG3nG3ah;A6vDI-N_BW3h;ywH znE+N6ZBgGrlVy zK!t~TndO7{$;vl)VfGX++1%W0075bW2b}0rhQWX7bB8TqG)6EDa6cV?+u)uLd$0VU zr2lOiY!x+~HG3!c!DEc``vKmDE%j$`C@kTm>@Lg21tjrs5R&&0A~2Ha#Ok%Yg>Qr% z{1qmnhJ7S@pYkFVnVnJnkaV#(bF{DTee|LhQ$r>qf?3K_x<>T}Nm4N4zL%Z=6#+67 zTJ`VXQbz+H91l7}5fPEt*9_TGDL_TUDazw8EhL=1=6lylQl3%$);?VJ!gRDZ>N@}i z24;YQO6B4SotqlKgw9>P$`Q2Z(?K)O=FE+gu+RGzc6SQNDT<5bSm^!L^tFYE z5kD5vvd6JHY6*E$H6TLM&qt~UPtF1U6=YfoASu&zYUw_zGwCxpPEyXRjywZWni3=k zlHrH9O>YwXj{jVLrrlp>{>+^!g$W8FBOtz-^OA4;)K_Nt7rPL6<<#OGhP0VMQq>voZ8CQcg~`+`uTs>a6%khPXO4+w6)P z?&2tn08AWPFLsxPRE)l9f9@Z406U2!M$C09Jn*Oi-^4qxrn5v|ft#RTz*BbbGOMb* zU@7>DKT+g$*YX1>@bFef4wZ2*AMOk!Kx~=TnQ)l4KkyEu= ziu^6;j;7{a-65HkR8Bs{0_%y0C-w|rf~#v(%7NQez`8kY(O8ex(b=&Z;Ow-duK^W$-GFdmgt8v_|6hZ&XxNWqlv;iF&9kI)C1V#h7=T=Aux1 zwUx_DZzqn{PL{U7&K{*;rs!J!blV?M9KT1QV}Mm1L*Nzz^JVEC`A80!JCwQv)B6 z+5Yn5M_f-&?#YkXwP>|bgbBVRE2i*{=2C|`wBwGN))rUY3Jrrj+YLjLDTr0>a3p;!sva}F-b-Ba_KD~XiIj9q^PBK$BDWl`{;8?UdlWaft*1Ou zFGV6~drmPnWCovwTaz+bjAi3^73w6|RpMz(I0LWkp%oDFJMx8}hq6oHfgRav){|74 z;djS`<@Xgi#CPzxILDMT_rZ7&9YrCC+`hJ{<4JUzRg>s_FO=}dkcPgjm6=m2nxgl& zTV!6~B%E69QfUJ&s2J@O@&f@RWTdYFRh`7w=Qr~;nT7_HSRkC#hQfS#rS(EYI$FS> zs7Q2ACr>bc?L0{ig4H5)`%xaul)+rP@kC;;NU;eb6KXd)K|w*Gt!<`Tya4fANBH!% zSgh~Fd=U;>?jJJ)X1L&-AL=0=ILxt4=p}^Xmk3Bfju-~*QY?AmMez^LL~T)hm241T zhazM25gEHE6nOj=-^Bm%GK>d<=jDZNCMF9q+4e1KS^!NeOmAz57c`62)ow{y-&gP~ zfPl>u1HTAQ6q`@aOMhR8EynwFl*OSIG3+GZ$Hj zCrZ34dY!@TwLD;8V0CSO?|B^i=e{&5uJm)HCY0%Q4k<^mBUT21d!}s(40jBhiREPl zYr&cLZ?l0576LinM*e}|x=`~P-RbO}MGNeCr!mQmIoPXGk*IzTduVtJHF92AD7O&^hfJtUgrxzJn$` z3Bbtj^r!w2@H1DPpl6O%R#v0mydQxG138%Y;_m0qSTFYwizgC^zt^I_^P>O@pE9DI z>y`_%gjt31Ub*1P<2SMqP>l#SIIr zqp89F-u@c%FaC-#yImOIT>lgPioV`|v%iWiVEs5NDFG)QS@_=g3027IxzJlyi0h5F z>&@miUp_YXPMBh(_zr{RN|J)b5{EuJFI08&qoN9(idO?Gj?E6Hpal*xk|nat6u75} zhB9wu>1{(z8Yu`ADZ5xW5U4n)_+v{E*<+5MZl=T-*vr4h-<0Q=;d6&dWAb*}Zx1`? zgxf-){Wt7fi$Wyo3%gmW!r?{LobCG)Jyg${g30eNeY9cm_pJGl5h|ICP}?4*!kpgk zPSWcy;o?CRP(8vq6Rzq!rZ#=)tmL;DKYbV5P%0$YdA?M6m@x&mrFWn@BvD%i-zKO4 zc_i&Y6;BfJ@=OLv36Ds5ZrN=(E0}%ZLkkX>Tgi}n-ym1Z-cM&8Ug+W2S|Z%o$Pbl& z`!|8Fyu>kM`%YJ%Y$7>x7m}-&1W-ZjQb%Wh`^mtip2(D~mIjR@%ec`BZ;QXs!I7EI zW;3qC5@ptIVHx&Z*ZtE0H`n^tw*~UL*8BVWw>6wMZP3S_rZSnZn%hefho7b4Q6ART z2r}B$GI01$e{jb(@T&(y7vQxf%Tl!=r>5A7IBP7l;ZbjGc(xon>AnEwflV}lUu9)A z2(`@^3yD`87lUnrlRi1^S?W|V*(p6UP&i4$&l~*h8@)hY2uD1orX42HN&cBha+ygV zE2LI3Al_YvrR^(_|1;2Q5{R4y5=P)R;e@|=l(|G@O>KU|P0oQ=cqIX@hT7WNI!;nz znn103K3QcVl`=@JOCl1k=yt}{$mWq$GAa!3C3i5Ihat&D zGRR$h=BMNY=}l)p!*6keor#;IM!6|*$zMoMujB!40bYR?65vjsE)8*CsO1f-%q zhO8pYB>BhV0KE$~BIflaUH=dkxvQX*!>b5ANhX0)G(Q%L>ww$bp9es%2a0euDnmCM zEG%sBhHo&QB)TE089t+=)z^AlYVH$Ka+>zupNpzhW#^LppzL~kdQsE#ndb1h`{`^X5s#Oscq{?o4N1QjkS_VU?;&cf7SS9PY7KZ z-M7ibUDf?}fV({XEdw;!!T7`T&gH6f(-Ls6qpX}}E`ml!N#ZKTQl09G%6ft1n#=OW%v zbd6i4w=Xad=DxD(`Lc=(Z*vM{|I=%Q=tH>1>YIZMcudN(%`Zu_ImdQ%`md|+!A%c^Cei$iUI`PB@YjNit zCu`(XbnIi{p?1JPQ^TKw>oe%TO}HWcH|JXjG~Fi;E2x90p}EF{DAV#_fHys$2PNO#y@+$J4|=xq*!4y5uT{m)e@8;ZVY|BO#>513HVgVzRQTv%O^=GDEBN5o z-imNMWp-lM`ibX!3D*IMT!Wmrqs&?}1kMPaU)4x}675w+f4r@vUcbAZLI1Kl>&(<^ zs!T!PM3PdllD99?*TCR?70i0?);?%bVX%5Z_r-vlW~R2@#2xL}UkF0zhAdSTQ^TK~ z#kCGm=fh21qCe_#ML4B>47fcgMu(o!!`Qj@Yzr*Ti{Bh5z6QD%2F^3M3tIg<&H0qV zhyFsKdJkN(j2U{Rypslf4*g-^rk_f6Rae)vEa&46{jTxlyseOuOxJr%I=FwUK$^rG zC;hyZI4ffxw%Ty8dXA|6y(+~@oqgd9%+YJf|NcloeU#Qz5W!8@U*q$LfIp634kbC} z=r?V87(Ov%F<{e(x|R%4XB~nnAQ#u+EaMB=?5|)GQBTt5xPsUQQXL-7f1EBgNt>mg zMSmiPzDHx5{@djG3yS>b9EI+_3i`o1^p6eAO-E0faPY&rHU}(W_D_9d?b$U}txX!} zCnUy1ZqUvApFQc4qAB;KHhB4F<|pa4`=QFlsNvfi(*_~Q4w*^^e(%P{#^JRC^|)+F zL;9g4G|Jt%3=&hA*K#F!sJ?|1+W~~D6v!I*k2}60*loy@oakJ|351-gz}rcZ-8T*_ zO~*23rcg3VJ&*+g@jRu?7pXz5(@59sMck|0Lax_#h|r7x_PG{$k*Lfj;ioIB6tW`% z0*YI|7!QukECy<3Q;~!chKqiAdvAN?}vjU$fc}Fe~lMvZ!gD}zq-hA(R5HossJLwdr z*cGPK%%1D6nzWdIw|7@tGM+@d(YM_ar)-0l3Va=JAXlI70zRusYT=5{*F6LKk4iNcb=G;~O>hiM<;iyK!t)bz)*@{!}E{36qds@*v zprwdIA6nXbz@=?0pe?CSx}rLKLh=)VmO^FHTXW58Mu^9IjISzU^lf2aEo{fbk`^8D z&&RNzM$zHru@^ z$JBun(aj}@yT~YR5i1`|JD)qYz+wZ(ya5O1j!m0gito6QUmux*8mt)jw_qIERkKMY zK-%z$Ye~IGWNeD|9p>13XXjS{VEq}$y+83ML(1`R4-%M@ov zrH-Ly9vH6>si8)5qh<)R`?J6$jM<6A)9xc8o7vLWU%;jL{19{Mug_vFf{I9a-XL<3 zqC}so*V=eI)Lvl@i0C>J53xQ2?WQd>=1e~D7s)wJ5HrSg{+S*k>{f|I+{G{Yt2cW7 z)lu+cDCc4E-B35WU=QPYifXd~r_9Lt+Uq(Rcn17$SsE9SE^rJpLsPC>1r9DI1m&np zEpDTR2eplgVqhTohuVU60cBJnZDVGUM7c)_%RQ)Vv*W~vT|_Q%7@sC&)$keoD=n`m zU37w23IN4uVYhB0`V&Sl<|aC`G+OrFpkiU>7-hYAl9w^uqj?mDa~1{l&G=h{_oP`I zS#^miK(YbH%ECe_)AY1ZI%}#q1UCX7Cm&dUB{L`Eb_x`6V6X=#b;|HcfGG(1y~j8n z4WaawTz3~Qq3ZZ%f}_IAtw!TsH6qCn58-FO6)-jrhuIN6P2q|Wt7cxB=!``9@^EIZ zNJc-&e>TjE(HAXACM(qr&C7z`?|LKtZr(&n!qe-)A1 zgU7#{++itx@yKI+h?Hi;a%@>q*Px?_C&F_7)zjb+&eo&sOO1zr>a4q)Cb}1{_)R%V z(q#qPAifqyNQv;NgGzARe3kzqB&E~zJ?`n?U~ZsmfnM@*CETub$d2Q0t5Z4SOFJ5$ z2+o`i3|W24JaNdXJj&BxMbLsEVWC}cS1}f)CQp0F?LsMWt%GIG{6l`EqKDwu-{hf}3XaY8SwOnJ+_ewvr>@x~Q?MFTJkL<+yhTp+_kGa!^^W3#Nif_pR#Eo#*qz*I; zcuQ;P^KDsnsQz==g-NfNKD=L#4~8EVE^3vpyJT#7Y3hHOZa%LfQ5U;Y43HfrmG zoNN+o+yWB;%f>e>!bfKXAq4Vd15vXeIjTYQ`lwy7oXOZAJn5 z%z(GON3|xp>P%EH<^4Y@8ng|2<~mIOnZT+l|x%r3N<6fW(#>e55BR*yDyh&en492c| zMUDY^8!YwqORm7LE#Zw0fmzaYq8T2-vjh$yIkmGpavndiU)?97F+D6Xro|x{VQi!0 zjT+K$KoYxIsH$-HwEynL2(!%i)Pgpas{1hHVH5n1=@KLq@0ZXA%>5v{@N|2zawKNk zXpMZ4V8mKk8BHSebH!C~HQLJ-C=&sN!v^ zBnAtrzW%QW?39!ex+zuEzg~}$He^mivs-juM-B9Y41CmDRuqH8t{T}(>5a4)=eg|~ z9yA=ehg0rjb!{yEM)zf~s3d$ZTPRg&TM`Tcg8_@K-M!q+~ zBi?nFqy}B|n5(_7I^6F4pkLGQ{}7^gC4)OHjI4{IvZmJvEKDhgcu52m#I(31HhNp6 z5+b&5J1mOA>%h!47;0SIV*i{gL~uC}3{TqFB&#j6ZU6McdO~{;`2}S52e|Ru2feE; zl6{ud?GKby+6gJXp_#GVj!b-en6DQi(S|v0+(Rn+#ULcfb8sT{_8!0AvO|xe5Y9g+ zg;{WH&@N=q%mh9J^|N)tvpD-!7jhgvf<2ls2~B4f()Zq6tS|P9S?2f4QikLRX@@q= zDVIz3SD5iIM_Jfo!wh>+F}Vz+AiT2E&d=ZvLUBCZ0#MT%q))>J?88k3vg<8h8@A|G zmW5lN#)KQ`PSM2VPEK}uNs z^U#OffBazSd#RDbXgfYKqc9VZIgSqWD$dZ-9O$D>jK0yKr@@UjHkh=z5dvoz^<$}} zWL?ph$$EF86D$1zrXZVj4?8`T@W_oH>l^CD(B$7^!K0=)ZF&eqE!dZYx?R7Bw9+%9HZ<~`=S_PVjUd2GKbHhrbSw|!5gxZUCF0F%-eHISR)m|_*5qW=e(}b+HU+1#mdnTIb6NDGaY8=s*XW|i z2R0hL`p?Z9k0vB6tGhbWcBW}njZZdVEUz$$1x+_BP9pa(dx?yTZ()#CKsEB+fFwPH z?v=*g=mu)~<7N%l!n#tiBgn-~fvqj=kzy|KYwq-43uRc)ijFH2eC(w!CQ0{#67{%L zON+QnkjX)#krnzry;kKs3z%WKev8Pis-v!w+j+}AN(mNdqZy6o{BiRdjb+1K12Ej{ zM}*mG#F66${bJViLDdpgi65qh=pRv?jS!u=DrR8;iy7sVFtb0jqNug{xs#ngOQ`_m zUJ**ItS21<3wB~Tt(%kyqe&=Z2+sWo$RYNaRHsD0G2HRsmzJtjzh?zmWKbm8Jau|HH*hn8zvm6-^vS6P)Is9v0Ks0xS4)%vj5j1buNla9bUKPF z%@Mh63ydFi&F#AX$nShR{#iriT}I&}d_-(y;UqBf4?46kQ;~IF?_Ir>p&2DJ*8e_} zen=sMtmLjrIm~J9>Lua6xiK1SIEbFeG=G;<%GTJw(IxiU5VQY=_3U85|2O`5rgQID zFS1Kc35SYZ9a^pAhSPhRa`>+06y9a@YoL2(DO2tx-Y@7O6~A^_l9X zL+|;y&t#(N<3rK`nn(QGLm83hJurbS!=YI<)6+AC=~~<{b_ypP3PX=>%AowAOfp@E~_29fcGno+;B70#! z*L5bU8sKCC{-%IfMx?nX6;0xgB!p`6g6Cyf;bSAF_E-oV#3GyFVnOeE+NLqTWEx{g zw)jQALl{irxYo!vkWq2z={IF7t88mL+$*xUGyHp`oewgTkje!$YenbQz&v-nme?I$ zricj)!>o|C`L&DjmFLTn$1kh2*WG=3OuG-`1fiWZ%M+OW(oaj!ft&R3P_K7&9QMJF zou|{ql}#i4P0!mYb8e9w1cvX@QSgK`>eH^NW?y8eJM{g-tm|i9Xw6=A#%u+p@Ie7f zHSZr_@AYx^d32Qk3c9L{4~|^Q0koVv1zd)<%ijpBZb3t#l;;j(bI7!o`m~iDnwiT; z?<_31B5>V3u&l)j@CpMkv`M_b36{Q!|~ z)8Q|hGHyXWm?i9~Bcc-q?l*S~slq*S=Rl2fMKSb5t5E1cTODI+ z<+LI$gW+0W!QCfgIwm6mlG1Jx?qMTHl!@~2c=WeP)#pVgMx!xKICYLlIfIVJr;*%j z{FEEJFp^==h-n=dPYMJ;0-Z`gVkBZ=NA?QApQ03FLGJ<`s}HvGUq77y*N?cIOH=&j zpxcTH|C&W-iT#ic)U!9rB;{3n<(U(`f?h1c?ml;~{9ZHoCr-IP0>fbv1_3@8&?UPs zJIL92s0=cp`wfpSwywRw_Uoy7l7V( z0Voqc95s`MO6JW7=fZ1B;Ij>ztvWY=8oQ6~QZ0fYHtHcJ6hP%W97N8=IqwL`E$inf zgjMwAlY^LpRu+_yYBn6duR!`$1rGTVC^f5D4NYe9E2+UmCcn+4BYdaF32&5GEg*0# zm}=T@`OA2=c>3R=5OOLjD=bya_bWgLJUlhW?TLgVcF`v5mBAPcn@v(3oJtm5(EH@i zB=z2;ST2N=i`9C=1jc!n{enDrU>FB-{XW4Q8T>#ojOUuNsJ+U#)Tm{|wZx@$rGTD> zWQZ}+vkNRe?jb81JOLMOL__%5!Qlko_$co(yZ>mX4}Z<_-B(N%+7<;`2z=PBH!cOi zo7PI0Pa701*;=;>1UrMwT^NuJGLU$rrH-{uosld(jpmpS*e_Mqu~of(7zK4!j-wp; zkb*(Mi=%u9nS1hzodq>M$<4RPE*#0e-#ZYAHy3`{%m0Gt zb?G-E5+{v%u9y`i1Fw{GK`$52BVt)EZ{4GwKJ4+7cO>Z6SM_Znt2sn}Z&sP1{CqC$ zNjOBvefszp^uUMqCPGpaUCz>+#az_U>mYf?8u#>0bHMooe9UT&HY#vn?(44yDWY6n zM~}#^M~!-9L-&!hiWcD4fFqLY#!on#m^_tIj;F1Sgm+Hz#*(M>AcYa1F)QT3#UPZhtUY7X&VegRfU~Mw$nDj#CmjbKTbG$M zm*?gA#W8Tv@O74^XTIxz#od!%8uh2AEL!aXSB2Tvcr)C?0>~A}vZh%FzJQ ztj1_^_I!7r(PEIXA>se=kt^+1z;D5$EWXZ)yoI3Cu?%FNT|5GVqzoCt_p=5=vGr~s zKM{QZTm~`^bSDJZK6?g_P4;APiJq|RyUij2p0a%Wsj>0A<&I6nx#R=M7^QNnkNNHr z=QZ+fFsbcGDvx7z*Kw?;!3FtIn8W2z`(hw)KT3R!!&?|}zRnN|4LXEKf5Cf$-zB@% z#~s2CkKGvwG%R}9xA8wZD|54Zv(+m8b*YBfM(zGrp0{=gQ?{kVJzo8Z_e44s`3`Bw zv8d&2E3F|)X%bGMz5@{uyYTTq42|~OsP;7AkH^6}<>I^8%?&X{@8nY=kz}_O^VB4v z@{sC~1WwHy*jEqw&0muDSA3k`Sd%pg(P`BaY!%C$`5Q9zbz2!Q=C`Lvwjw8PRpThR zge38cPISU@->9}+-zix&m+wuZO^^+vr$N1>5*|~WB*i%FDO&krn{2DMUTJyYlnzew z_bk;W5j7xkrj4+9b6o_d=QnNI1WbF0%G+#t<2XBAx@Aa;n=wQDUKfYl2S4ta>jS=M zpSOMCI<>)>d3QH(HdAGJ%sz-ymR7Ph2hu(F2=iA0t?k!Fhax^#0~*9!7BlXt^O?AZ z!pA-OzP4hxb{h}I3%Mayytx-QEd%1XGUOfHFKf$GuL*{n%xvM8gX!H6>^0l9qc8-Q z0|?YRqG4PX`zr0I|K0yj-q#UAN`kqMoi-Qd1Da0xyVLAv9V{-=XJ9!s4`dg44la_t zBa!dK8)MK&M%BO%@oc+N`_iSP-WC|n!SVzR2ZMQ^MC%rkFo7H1R8KY-e8FAv`cXJI zhEHrkKC;|0@iCsviZKS`+1Ulwp>1S8SSiEZcAId?{5JH6rhr`^Ld*t-fehY=wf>tX z^5z|OSJ7CoT?kS_j}*sv47ULb)dj>bTt<_g;J86GMHRT|OM7$tp%63bAo}CJn6#&2 zO86gGmy9Yv_MCXkv)f`IxoR;@?Y<&^aska&*xrnV-zLG=6)H~06G{*q%pnme{-C7f z2!be6HNpCp*7kux{mp^&OjCk*6uK7bj~#z<%|k&f)cDQLI$RY-iJ$LpCVy@e8&TG%x~&mCG5{>`LH?=}7vtF%&6AI|$;w|c zK9+S_Tu)8P@mKe(7crCKJIyIY&Sw z8t;(F9LoaHr-zIO>c`=NFdd>88wuNqgcPXFS{czFuf6LRuKHOD99aUcQ_7|v=!-VB zv2O0?XVh3)DxOmXbu9u%&x(B;Jz7`0ky!Mq)*Z z?8UGdtUI&43kXvFST~G>qcy!P8LW0x_V>j|kv+mTH(IXWkh@6X*gcViQT5h60``Y%>I@JfPZ5 z6oc977)cl`!sdl^^8`V^Egfowhl=%Q9O#^7@;w101ax2fBgNM%SBboRoMqzbmwIj| z-6z&gXbhbhX3sR_WRsQS^=Uk@F&eQ2&eR7}WMQHJ0AS<`D1e!ZJ68~KzRsE}F~SwBpIAR4#={iPl8i@^UWrr&(q9nFgkE86Qb!ZM zK`e9f$K5%2{z5h`{?}9)y_{SJvNrnXMJT<%I#d7BSIklI||hbC#x29tbEc_>^YcQ%%={1v_Xa>vc}-#$e(OLrkMXD zVR8ZG;|NN5Tq0ZH^>_WNa>6M!GxO8bv7jI@U!RRaUqryH%B|budrs`dLkJL3!gwxi z*&q)pH2I()LcwW7{`E#B&2cCmno#*r2X%CdunB_2TwQOX5@ZZNjP$q3g3!Z>a88O; zo1H`t^{;7O=uf|artimlaX&ke13$YS1tEPUuV(g_pO);^7IH#`~2qH884-0>rbANGH3ovRe@L z*kd~~k#GQcmq^vv>ZnPtZ*lMFM*Q<0+X{KoN*GQvKY3=x9cbaJ(dow(HE^l8;_Ean zrJ{p&(RdLgi3kaw&=PCkE8mJu!4Yl(Fl}zq9*9Blc^zo|K+8JK4mQN#w`r3LTl2%( z@I!sQMGN}8QFup>f{&k?kHQ6E(+Zq;bp(Z-4L$s&+@3yE_UGkYQ;o;g0U4_OBkpcl zGWEZA6(zmCVPoab8o0U3__)WT>Pt#~%<|?yF!XsVi%P5=Ey&iMYXSA^CJnB|*pTc( zfk7;H24`n%b%9Ma#1%cbAH?HB>-XK$n~;}FwY2uvJ6FAIQ}A4Tq1RB0CVRKDt&Ws| zIOSrMY{N=HvrU9zYU)QpC1o=&S2Ta=%Z;!#)RF)>zGk~t;G(|Dcg~Y>7juG)0@kL? zDLx)6Zp5(CBUx|z>l%(A9B>d3@%BqCXCW3Q@ZH_byE>;Nu077>MKG6wU&`&1lcc1E zQhetmO}Miwbq;$q!1)((U=|Cwk~t#c&DvpdNxV8bBoB~w7&=!2^mQFsdPDER7Ep=V zgeYA1x7E3!O=O}Y??;#LXgbqkCea<{W8W*F=*i5tRN3*w!}K2E!ubAK9QuX9t=6hb zk}opqk@#?ZR-G;sW0S8ZqcKYab5;oM(?AQ>Zy>jl#WRmlhE0)6tXQ5z_77k+%@fmw zf(;?b%zzZt%@6%@66&uqBF*0NNfz!be_tPuRC3&l`1H@sktsW~ zt7ZsGXRft`eX-##P7ivXX;Mk%0Trn>e!2Qe1C*=?idz2BL)!%}W6zg|e9*P9Fvu>0 zBCnDG_Jp-MV)d*UgLQap*6r=P&$_c4>ydpZBDKck)CBhKdJ+Y`@yS+}^mdBy^N>pF z+Oowv)ZvTkykQ{6xr39-+Kg_7`KRhd?E60lzi^Lkr>il;=y`dwv$GF5D=@*tU6)6( zn)_ymnvKt@4Rc)5;S{7d5@KC5yyEtX{rtT?eQ&NzyP&*-UtPj7&R1Czhy zRuO5RG^JHq&wPxD8xsg@Bc3K*1cRWtWcwb;llm<7UZ_qq55UPqBBdm_r-gdl>Q2j1O9wFp zW1#B4q~(+eH}}Kytl=deV6-c#>9Wf9>fh|c4SzDSIni<2`^&$3pps6jzE=rN_GKH? z`ZN}9>iaGBl+Gl^isHD#99sK@FUX}zydYJLx*&0QwT1`g$ovw+J^2S^jGIoaSfJIkf5{em%qGhDq)rG)Vh%R-Q!*bH78lJ* zf59>h`w-h~c@AtyvJ!3W?XiBiVLHcLBVetYod{(t3&w}`X#jYr)!u6R+0M}*({=Ab zX~%{r9o*}G1`kh`qOvzd+xuwu4Pw`n7kJ;OM_7x+1rS9ZNRg+Kw@rm{3JUk03e^qj z%|&E;eH#RT8#bG?ek9xW%~)k+J3-p-^GhVEE#`N!KV{MW7KW(8g5n;=QU{1rkpD0W zP$FKgyMMq;6$9MuzntYTkhB2N=!(v+=Pw1gKUT+8t3Ip>2oh!qQ<7(yqM}EUBUoqT zLW@tL(#_tL6q4#JN=3X1OiJ511+(!{!ZVsBv^lK5n;VX>wRI zwN#e&zG(#wC!x-4PQw|b>k28sxM%}Jr^t>E2Ro&u4x0R|fy{l^KtX5` zfqJ-4WA-2qg_r&K&IM%8&~XA`Vg8J3hd#WzpKf4c8cU(ac>1%>-=;uWt*HnX*;pkj zeK3X#q<}_IQEvm0M$&FF+iqNraTJR%cn(v(t*wdvjNF-^6D7j}QUlpT+XO8jHv_e2 z#HL$uCl1|nk!&(Z$-KY64+BH;b`q{UyhAzVN=pl$B&bJp%hbl}v|i|ts@wAN-Nbm4 z$_O_&u!{1V-BT@oLIu@(+Fz1_AjQ{+KzFvsY+(dm5XvRJ zhJEfBckSq&SS5@`P_SZ_$l6*r1 z+VOF{n*b1AxeK-4Djf6Trw)Jaf4ORvwEz`bvKN#w=8n$U?Ojbauh~Awnax>EoHjm1 zk{DCT1iwSDvw8})98_FtDl)~b39IJYn!eyvAvT7FV`Y2Z3_}ObVUS0m?}(+$F$mR- zlz;=zh@@e-meMevr$|Up$x^;4$2qX)A8t5Nx#q3J7yxMC+NZ;2N4}Vy8kBox^&qh8 zE%k(@3?=dKSni>vA?E@#4|Xr0;CWb61jA8cb?jF@P*y2*835C!w_J|8V)_ZC z)`$xR>t80&6oHa}(W=V`ivrw`(uk0FZ+)|QB#_-25{Y_;GCln_&2T7y)Jh`YCLjm# z*JHGn-G%4cmHqYl`Vxv(KhaomwHgQnnxVZL#OnmAht{E2)y0$(6cl%X{f!DxFZ6Ii zha>)NpJ@B0LsHYN}*I1(nC(-V`+3)Ha&Y=<2_{L8(>XoEGUp6^r z_ZdGMMLxm$PaVGXj0B!W5Sf1MEle)(aaQYs=N3kx4ba1#@*AtPTpZTk zX_DlU_x5x2KiSuQ^Q0oVx!juNxESkaK zJN+ykhs{pG89c-tR4%xV`t^OWise%1 zCo>}fV00|dBpQ?>TyB0(6dY4&9lDy0H~CqgD$&$TH^enB3<{2l+9a!hsuIY$64FMD z5rc5CZ5}B@|1d6lpiQ$dI5&reMZu9K9G=j3MCD{MW?$&cM}JWA!RZ0RMQ1c2t?uZv z<4bsz)MWzIbel4F(|9Km-2Fm>x)H++q!~P~175nhPQ@KY>&1()GD^VZrnu_vRWAAm@NjUAG!<4o1EJpI& zghEUeJwvi9wVGUV6T0~;RItE3Zhd^qGs@uT4Hru{-tB7w;FGjQ$C~gMz#6$+{10nK zR}ncF*>Rc5-3 zp7F|!>)dSAdNM>u`3S}u!NYO|VE9&W8N}C6K;KX82YPT~X;OwmC`14tQ9>%3ei1%8 zlUWMh-MKm70*20RottVT$c?z6(k& z@=@{8AN)nkm1|5(UPa!FQil$7MO=3tieOuKfiSyz;Z7nkrc$Z*^UwGsv71{-VZjJY zt{CR#;MtSj-!vd#5966{eSTCU?b&yMi2E19Gr~)CR9N4nxjpYfRj7mqq^kSg*|F@f z0Eu7k`fw%?WG8S9G<-?mjbmY_xT@;u!$s+!HmJuvKCxu-QYM$qneDG=tsA*f1(UXkG!PeR2Rx6H@5dDC zG0L)?`8g(cA>)012f?a3K3+*L=zZ7+W<-ga`=yOmG0L2=+5F@-nB_GRS47a}x}dTg zjkngf;q%XGh8Ts!G!@G^*4&%Vd&LNtcb9ZBU{mq%N*ol&)*l>MPk!(&kx9!~r>f@4 z627pi0-bl(F5p62ia{avoRqC^lYS{TrG zrK)Dn@n}tEAyZWmw=K7KBtuNPvuU)x2a=Si1=GpF)g(%$#(E`l8$^50x}WjL3P5TD z!*q7rc@WFEeNIjTjhG62bt0O0>$&t;RMb^3H@frf^h67!Pz# zU@}_i88l0DTP;MG87ghANPheIjvbKfmn4Jic_iUP4~ZWb)<+5{USX6ARl&VH4|UrEJu!Fj$xwKxW=F?2dgt; z)A(}cV9+7r{cSRZ2d-@hJn}9){kIg({A*gOfOFAo9%_niqyVe(_~e=?r*=ns%g~?C zd%vE&Z!|O6ChiRzMl|mNS#jAFP?w-mxMaZe-P$>;k=R+jbdPr}V6#Qg0%T?4WiIwC zmL?srbN-3TouQd2hCfB6A%MpNRGsux(BwukS9@@qM2*M(4;IN+`sx%K9=snOOD5&+O+#Acv%64~M zD10Y~yAwCndCcx|Mr=hi%Rwa`_J6FC8nhuIu+^yV^*E)Nr<_v=V?CT4EQD|}cL)m$ zZ*TW5tDnfY2IZ-C-R+CEr`Ni8(H;e!o!JGeQy@CrV2l|p+;!c>6cR62FSnuE>Zr$P zbR&y4D{%R{9V1J06ZXZpe$P@6k_4tLLAU29+aqk-q0)|2zgJYJ_h#Qt9Xc?q$eUDK zT+s?&9Okswx;6<{V!5?(86c+Ex?I+FaH8tWd?kEytZ`a$q8#?TxEBk<+57=sR-jGd z%gw|G3%pZ$nBqCa6II_(^JBzkCXRrZ7a^_a505&B0K@J-^+Pq3Bs*7ddtIA10D;Hq z2udQiMZ%@boDyx)I)W45EmvjN2B_6{E&(GzFHul1A2hBU~q*OwIhY^ zcJ7Z!4VtCrrnI>Bu%P!~2I5{C&B$M0{#nsnv(+=nPh+*Ymd81r(6fp)VT5C+BfRB{ zj`#isF<~r6T{&3cnz8p9VqLh!6n&EtLO9inYAXb9(8|)M zcGhjofwHai58>AF$D}6m2AO6mzr1vRHh90jf;J}=f#GtY7ojuNo%vcEMf`NdO2U;+ zq<*>vRXbeg2v;cX?`G=3lIwQYKXYxwx4vxKyU+sPOu z_YAFzKC$=`*Z>oB|DKzpf&Ws#D{(kcb?)e z$q+W&U6|i~yMD{q-Z?X;xSeVYK6GPu&)sn0P?ExJe7RFlG8fOi)OBx^j+RW%%V48_ zU#rU4^!ydh+KVU!y_(w~O}Rt1y0SuEX-kAn?qT%Yf)_1+!5}tJPaU0~v`UkJf%XdB zL-yr*u&@mdI#*?cRj;~wMfm-J!X0E`udaN2FqAqp!x-F}{oO<}AOiG!@B zHMH;H{hz;DK%nT(69V{y9fUOhK>D9&{=YqnG?L`0i2ye*|C3AfE_hP0*mKoZgRFdd z9OE=kGdHmGqE`5fO#IDKw-2F}+>FH;`h2_fv~P(uhW%_=(BCJpUWxw+hhkN$TfN-3 ze#H1jQz>W#q4^y}QcD=BFu5?ae31k2qUQ6PJx0_oG}78wVd2t4ZS99a%};`k(^>yJ zV|m!%1{#0Z5^>6@ZZ*B5zxz+jSzD_0rx3J{6H}5v7nuJy%z)#>oaz7tJP!^fQjwkNEu=9~R)BYlhh3)H=JQ zq`AN$EUajev-4bb-*tEoj?_!jEP%4X6H201u_U60HI3c}+ws21M6vTk#3)tJ_5DzswVG{~XG=(skV?TI2jr;E{bj|wtPf01xmX8-7Z`>P zvMOi$aQ)DW)oDe3+)ip4t3 z>w+y-{3GeoQal_tI{(KSvzGR&vlqXQ9$7iXTK)A6dhNmjT}{4cN39MJyEyIJu*Qz0 z7yvIFYbnQBf|oT6{OvwtHy@&%-9}*r>6-)+*rJ%d)y~8j_^aOBMXYfipFDqg`t(jG zBx+$pv%?Tj9xkxGZDDL$bU9xi5Y#x$Sx)zkEY{>kdCW>gQ;fwL*c!}ug5!$LTcr60 zPS?@D^yCOo}zsn*%hx2~!a@lH| zXyTs3-alFNw2X{r@oc*BEuF1ja7BRBi2I$GqKpVe;zbF`aGc0C0nygEzQ}R>Hem62 zd(gI3Cu6i@Sk;m}*0c1UuNx^jeiI_}nBiffu=@L_xc}*I1#=X^wmN8NQt}pdR zE=eT*d_FM^v3=G2(NonF`(#}C#z)<7Qim>KhB2%OzFw6f=p&5g2NL@WZnFWbfTGnL z1x{{@wPZk~9%H_DzOpUWh@DDc#-FUHwpi_LlO(dbv4?K(&<~p=NWFDFD$#F3vnD>fp5on_fPN<w$b!z4Sy5g#-T>rk(Ws z$oar;Y>_raV+&H#MbD&*oq+(qv$F|@=lLoj`5>95$@ zhe+B-^068VhqU&g}Fdme61%-H0_w?ZzU#g5mGI~4D2b+}GogQ)Nw(S<(p|1_>-dIo}u4~-k_V%nnBA+9NRH0LeJJP>NB=J`Lo!Y*}y=O@}}vzhRr^WaeygS6pG{K28faouoJw}re8E8>y|nW0f@*~x<9$%$flLi zkcPs&wHK`Jx{_iALYU+-xLZNVW%^{X9myp1t%x~w;VWT?2yHtROAGb*vZJsp&!8T( zw9{Q{!o51@7A~K80wPmdOYP?Lkx4THKNJIv!4uX^(oRmz6k08@+ z+510q%?S18>I5TFH(G`C9|!|)lKZtyELK)2XX~m^o`$Y})63XuZ=KFRNlOfbklc_v zBB}86ZhU0)y8g|Bv$&p}HS^u`YxG^{ zifo-xCB!y@7K_W0INM1G|6wgs9Mck4qP%r4KUgMa{ynA6##Ca^pI=*{a{J@d6^pv} zuOe7cJ>^COj2)!pEGYOdtDRmvQRO)GR=_~Hn$P|pK;Y#X=TQwWhRp; z(?r{^NYzK84vtFzmQfF*FAtGCx;hp^K#U?+ZDbb_VEcLMsEpb+F4M#f#Run1qvefh zX^CkR;9DfHv{digyoGIwUt08*y9CiSEFcoKxK@gXmDgkQKm;%&+LbBMhTMf^`;2IF z$FkDWcmuX^O+RpoU9!p_?QcA3`LDuFaZ;O)i1@S@^scD7Ns+jwshH(S^|8(=!AgWJ zu*$~3Y>>6}+#%%)?9G-Kgbmd_ET}pQ z$&eZ)Cvh_$KiJ=_X1n0KO0TpLJ~pbM-bJ)=uNV(pyIk313r1Hbf%GPmcI<;hRJ+)K3~aWlreBLX$XT2Yw=>r=M+DO)MO zyWJiDYX#w~+jdQ{k~i1PYw)`Wc0>Na_x>v?uA67rwnID7kEQO}w~2}xWY0D?TknDB_H-j%}fw%6bQ zmkD(ap*mIUj^&WeB*qDDys)w8N897(40!gfFsI}g1ClSFh|=}nb!X5LlHEyEbcQK! zL!rydo;@7XnqjC(t1i|=vvzb0STpR0v9p^=`G8Yg>4Vy)g;c zd=qKh@`&u21-6-m^P>!yhZW+EypTD}|SH);O@K-wt)jgxXR7Ve*L?_5y^WTaKQ*VjCvSu)9{ zZ{3{0@zVfeQgg`l!Y})mW_?WqV3i_7Nk>=-1J)h`Cr5sRV0>on5S#WC-mv9?If-_^ zo#;H-h)piAvL;#?I6`u+bvyBsQ(UVfl4Du#3;|C6d;{C)pO-;Ly&UJZa3nW*Z{Cv< zmfJi;nar5O!f7Kq48Zf5+^czjhRV=P<=D=9$R=v>VwQGplg@0nL5yc+`966mR8&z1 zoTHXgqi$gR8NS2t$dAoedW1UufEsTzp?=K4z{IoYkl6gOuoW$@ZjDo;6o-(ShtwhP zZ{qOdqInLNKbQJ0oQp1qF84n-#uvb{=3_fMci2b?sZ5N8kR)nSNo;qq7KgY{{{5-! zn!ywJCEt2i|Fw-_3k-B>bTuLG_dIkqx7wx-d@CAN)ch!WU^J}69=O6@I7dckb6*Q_kv zm&v$Hg&)2mlic81o(oer%7B?FEHnbYNW471c0n&>*!ibVLq{1si(HMuRom;*ML!ywasrY+ZZmiWt% zvx&cP-t(x)2bQz=X-8};()gtwVDxqu*r=&#slM^vCm=X4NY8<@kAB%6z+o>_-J?= zmI2wEUM;wIw$Tb>YdcFf3h23arxxR;mF)cm0xY>h^H6RvYvrhV%LxXRBtVPBw)a#sCQ>y6-I=f^bjd43*sS17BMkvWRk z(6=Z*t3^D}mW6lnO7?k3?>S!OfISDBPUJ@43iD(7i9)W4_aI1LXWHI_JI|;Z0d@L( zlt%asJYbui&a3m9R4Q1lT~ z8)L!QPgY(-Cp~f=I-E_`asbf-8jri2?~ocp#^a7t`v^eM^hKut=mPx%g#i4uWYBR^ zU1zVrD)zDhl0B;{vV3{g>k;#XLiF0nG{5%Ff)4~~E$f@=yV3B`Ai~)Gv9FG+z$X?e zt9X+*BpByt>GTyWtZTu+$uuHc1+$@WiferWr|RHM(Mv>uiFZD?J^c9+ECU^pAL0WU z`~X|wj)tR~)(C8vHZ8P_7iG(Zpx^v?Tm0A>URnCO{;KjJz(gX8yY~*lTCW;y;Z>Iv zFkLp*UxfB3)?X&NC0gN6V+sS-LxXIL>mYN+COCS_Y z{o!B3kB1BmvfK3R(f6v~#CbtpQ@t-h<+mtUbnB3J5o6IAqs`IG;l_sSUJRdo;^x(Z zpn)rX5eRdPz1CkV23H!ee8(tE^Uh&#ZsOBu{gVI5N6%?yRs>~_&P^Z+D%4_)6grPn z8K&}1MlOLtfq3G&+pGA1epNwC!4!L^!Mlkkm~LHLXGxKwFON7$2#* zC2~Bkq_|T!J9?UGG<<}6KZlhq$kzz!U7(c8D@F?yIq;W z=^!z7 zeqBHkvZ`<b3r_j^Its`<&5LYSzrsW)qK#i&w^BisA8 z*VMUl@a?9CqZ1j}80UwSk1Jp_=&~aD%tc_|p$wh!GpGR{r|3|tq6M_zZSOrzOmu%U z*6m1$Z10p%XH%!oE_)tncRj%MOhEo?eL&(iSFcZ?FlSkes=zyJ0s&CxWXBSK-Yb-q z#1&_#6%q7j^JkMIUBxj&T8@2uPW&LYK8710_hH@aRfddc*V}Ttc=QEV*e0OGgO%68A=vhFJNT|_QT9!wX9G~#Ggp5uohHijWFYoLqYc|j zZ74guf~dczpyT8?T!xrjV~uS;elhWhR2$+u1ZPS!Wu&qr1s{6rqOkXKZ!cmlFBzvO z1`Q7&L?deO85?ADV#%D=HHQ=Itov{h);_cl?i)TZ38)15Em>zy%=GPVA$vn6vjKc9 z4-u0Lqb4tZ*T&@8-mbe}sEtD5Q=cY?N6}K|Ri%XIl47&6q&-2B(l1K+*kul+-K5GM zINBIi5bEcng=}zN@*wwB@h0AvNDN$2M1@+p8T>qX2;VY zH9sodZZzkW4_J*-?T-qKNOt#1WBH<_C-Ts#s(krHCX>&`&~@XJftHrE@UddFhV7$M z#TqF7QrL!aWybesVJ-JZEhw9Xxd6SP^8G*Ho&O-7|LxO>=|pT@$xYe%|AhbKeXWt> z(h})gaSp`o|EK|G)@mvZ=Qm+VHYm4wamXF5rlL9GKnsP}>0 zeiPGV$A7JT!NED>Qe0dm5#wUF?%ZHNV{O8Htwr3tlyw&T>Iqypg+8$AB-=!p2fqdL8fF4bMiz?A-gv z7v983gZ>uL;k#Vz#ZtJ0PVx+iGfOIHy2ggpY@y?h+hpZS?T=4&65Z#0={4ou9I$?@ zTqZ*Nh=6E$`ESlMLo;3a{}=xeQ~6)~r&iv0$x-bT@ABUR>*%!2)qv042jNwN4#|C~ zx^Y_1q8m{K^REOg zUyX-dD%F9ZfCTQd(>`C;cc{1IwPY|&K9#^-X04Bj^-ZJA#jpH@uh3)5udan?(rSW| zBcI2psNX=8s?1U%bYDS0)GNqFL(E0f<*%Y0lBX>ZWNKOtt`80X%L2b9Z)Z``b09JS zzvH>6@i=u@oL9`P8Kx_f3)|ZR^^=c+SeGki<=G)Tb~Oeq@4Y_XXp}8;g!OUsZ{_A5 zWDnAeeW{)E%YY?Sa>IdTBKX_o05{`b`L_r0Y3zdoEhz=NHv#qC%E&HzqweFb(owKC zj$3oQLGOR*2oa(EdF6Z#<5}pxfl}Dt8W<^ zD1UH5^OuKn$J#`#+DD*AXt7UeyWkv4P0|k~!<-w|k? z`Y+@o(H~ftg^OcF;qJx7dd+Vv`?zH_7{KBa(9j@BnE8WWI?JVC^17m@8L~aX8g{q zlJ{SfI-zrp(x#pPI&=<>0IX{3z!z_!1{#)-TnZN1UDsqYzIE!?%m^C1#gK<%ZUzE< zjJ_}PE@>1r_{+{g{>b|^Pjy9)WnnlUFA&{c966r+dg`*ipq>+G$^l-0*uh`E z=5-C8OC8gCpl@QT8gq_9?ZPbx!$y^R$bj`i`2m{UiM?5jRTU2%;F<(EA~?l$kBQ9% zf@Q!HESH#fNjRo$F;pqx?Irq7My<%d#B@qzW1v{^3Xyy${$$v>5&?3ri@ZD;(ii*y zLU8)>&=hi4b;wx$>C>jf^LOV1mxlw68L4`Z!?%lbli#YRQ8L{y{!6XJa3>>TG zMRy^e3UT`KjY4wteN$-id3`~8g`B_gucATdV`oJ+lwNi?18HiP1_M{i9HgGB)6ap0 z4U;)F>-lIxn!rCL+~VZcLxpdin!`tt;Ol|bMXe!E9p5+xle|IZY(kp%KU0*DMVX#g z!Xn`|sQv0i-F}qMCIP=PhaFxI7IP1~G8BYw%=l`qW)Bn8Qu6|pZT-%5#4?tXGn1C+ zUC&I^7D};{506E)FCX1QQG;E|VaN{2*ub%A(>>Tn>dRjox@DemtZ_B-6<;BbSJtP~ zWHVgyIxEBP%kY>WWiepc*hU*ZmVj^yrL}k~T9$)PFMlb{2fXUe=8n5Qxy{WX+f5|d zgj{nEs|E#sW^OVBmh4{fuTzw~HQ0_;RPehWB8yK<$a$wwig;J(6i@7VGoB_28a(@T zjcpD;y}9W{93>qe*h;=V2CB2cZAPo04LzNsH1FGvFI4Gb(~$Nx#E91}&HL+5%SCzHAp;d7y;bO%5t!y^2C`l*hh zh8=x1)onJcZdYc?(=M~%YkTqvB`wQ@3Ovqt#qgaivg@NxvSr8*pAH{$cE0c6pk_7M z#n@Z+oNa9RGOd@O)W^>$%m8=o$|cx`%|q8anfYZWkOYF!5X4^(`GU_jD6ZZh7dxJ# z;~TlBgh)Mw?;E#>+5$W{EP@f99{v<7^sATd({0k-!K}5?E?D zD*F5S1#S1!)=qIZRV(Tc_RC57?*Uv`3yddoJ1qm=za%D&m}zLc(T8JgS(^pL+k1jg zfW&c7VMD8Hu)2$~6kh#f_FLV&6Volh@MvG@@>B7zV#%~LE2E3*=gnO+xqt+EvRk+L zel>YEAP5iN}c8%K~nXUK{2n4n~0O zy$wZem?RWTeW1|dKLsl~JU>WrLIv?px5m}rXq%nKeTs?qkps!y0jvyxt}o(tk4@b5 z$Fk{_H8@DyFLwyr*W6V`dA}qsw~#*ejB%f}%vXjxk{GTSlT%s56`hs_4!pt$l%aBDs@EODHPK}~zF3bM>6 z?Z{4MwmK*2QxXmO58MDRm(-vJWO<)^iy)E7Q1vhZx@&!vh~@p3A0Bfn<%8qJ18LTK z=HIdXv~FHpKh;i9Q+!ujUObJ5A;V7QcYWF|lQ2q%JMKKh@f|W>t6^?+K&m(uk6DdT zI+`EOl!ANpwL}bZ&~Sm7<+!^aW*b;1DHniNhf-u%O0RUwU%9G1vl+=b+}$my=AcGB z9R;!MW75(?o+zd-@H1=eO&WkP=;p;=#2mfmC^+()7-Dre+wWTgT$F8(skf%Avjh;8YMu zzg?E+%}%m9O(cn*$;tkZt2lUSIStG=7wLF7dp}^^*+x&+A7 zRP5X+-tWJiu&ClAilMDh#{6k%IWQK=%A?Q`Y*(L@Hp_PUSDz8`Isdd z1}^vt{T*ar5umBnU`O83kqf^43*5obb;8ti7tvV38ZEXBVu7X*b_BVp5;{}sFzZ4e z$7oHOub6y>QfiGkSv~O=v|j}a`vJn#^3*xhhNPO9278zk+`yhQscKl9nGBe{_*O>i zkDdmPm1hRrkMWEc@$r&&KgxhizS$x56m!V)j3dm`@_5Yn#7a|=1Pu&0z|AuuEVY%Z zjq64(p2>L;dq#R%TSiA5?kjITA7^?aX>wZ%4V38NFo&)n*)X~F@173w|6p($(G4AIK=BxMwoij_Bs51zD9 z7UJMRY*6GJ@jy|ch1OnHZ4ob?AvVQEWO26Oqs$T5K2XKUJ>vjRoU**EA-f#EG~$hn z2KqOr%hDjBI-OSYhgbylMgsG*Q-8YuEw^f^8; z)D`$_;fKStSNfuvBm4tiOZom}sw9W^EDdMb;>b=XLESAz(n9jA$HHf&^j%Uy9z@(G zwg#f5IE1nKrFt=yFPvp#kt^I?m(t5!+%?aEG)q;kXTB*rfW9#>`?saorb7k>`s2eb zoLEtqwElNrRD&3f6p~F^cyz>1TFhn{<8zi{k_m|B#Baz9o&tV$RB3Qg17Yi~Irsn# zuZKlX!QP_+C2F;YZ1AIq(+^{cJLh{h+e4uIb~&Z$=rA3cM3nBe{fNHU3MQ(0{pd3R ze@;QuM=o%JcXkBR91KhY40FTu6OHLKVKBDO+l31tE{)$Ej4k7cX_G~C6%NF3k}q!9 zRh=KB#!I|Z0A=O;ojg3EpyiHWp6!BzLZDTO_)$#kp#=U&pa+#_wIy6Xx%+QTX|=nqodOn8px~e_z2g zY$~Jo6^-^p-g{-5;y8)uIox6skQ(^RaMK^a_v)Pm+#=;SL@b!`;CE5j%r)W6Rri*D z@epU^(n;xa)rhbjxNp`u?-YeohJSgPEv;*Sm(@%^sfGq?w>IlLi`D#mX9Z7cGaLk* zbjlQ*HCndpX~ePXLO#I8!oCH}SJSeSzW_~<3*k%(MaK|(#0QR(`bG?ajOw$p4v$UA zNRw!8d?iJZ0d3-GViDJYOw7rG_@CBNUo8^licMHdvF}%>bs^@~!yyx{0;~(70OhPA z*(7(Pt(KG=a)O}^e%l+lBJ30-gFvV#zU`95$G5#IHkA4dp5y*FsWM5EA<}$LBr2E< zw^|Q{+!1jZlO!SM7;wo(urn+P=x*z*+-EEo%mwDYYxVbFYl%A$cEJl&*DyCRCaI4U zsrBiCfn$^D4qi_(B72ort!9?s4z}J+ddwdljdO*C(Fj)GUs;dD{V7m-WcpGkb_uu{ znMCFIhka$M9brvPB`8xP3QpfdIQSmqqTmYx^2CtPvqFN}er36*gmMaU1(%38!u(+M zO?b3M0q0eOlvj23^Kv1CCqd{_5-WrFLNJQfQ*8rfQ{jbejgIz(Aqvl1mgzkRyx=qj zUa~@4VX+Byj2_wxlODyeV5;XXh|S{LZCD1qxHNZ88I!FVV|v85*=CX75C`L`hi*w| zXJo-GFfna8p=5~INmy$r8FR5$_?-U~P4sNR$i?@(LD(p6H;p~30RML6tdbd%mzEr- zM*aoJQU$6(r{QhggQ*?H(S=I1mgp{9%ja8UKH11m?%iv(VX$Vu6FM#nah{8*an)DF zPP-42-je{9B{n5QW!1U23vSw1o}p65{-Rw3$1o_Z?iAbXsO9afFmH4L2B*Q zH-jKpyQ5uUH~3Hja)HsperFLqv{$=F1`L3xKfmuX;U2uC zs=dh|{YW;`Jl>zXgImn0mo_M1#;|XAUvuWRK7gtT7Z#@uaTI*G;1GNv>TrNDCe5DS zi>B6C;^s373Rew;z7QBP9yZllI>@r9o4{CZBo^+kC=T-R`!5>gu_}x+E6nd}mY@ zTe_iVJUrZct;X#uSlCzw@WzR)%AMnhHNr3(#H`&qoa!?seiFpO!SpZyIZ{;fd%Uq* zAjM=J7V?;1Rt2*La4Xw7eNi@#unO%A5mQN4^G|s;yKg203T(72^8WJ^^zv`&O6zoZ z%NL7cFpwA+1kVCOLM!yMMaPNKPDp5bfF@kV|cR zuh)Lpn%)&_yDJs*fMR{TTe@R4|?0GYVn zhXPLQs-j0uEG}VswFoM4?aImNv}sRo+TFEZ79NBQ+yS!4gvIaGaR{>KTW}{HTH!dW zf~UHjg&?P>PmaOZ@RiEMwA5f>)o8g6HijAl17ie3LZP^8*|%M@czf-$fKLo25G|?Q zhLckPtq~Cs3q##x6aOoe$|H`M;uv%Nc?rAO^rRON3wYG|F7EWh$ab|2iL(U^X8in} z#TY~Tz@NLP2m#79fH1D53`ZRY;@R9*abOr>e|tlhwlkRAMMDN=)7Let)1_ZCmG)U_ zbPDG)nHmONmfKX)&AHM3Id8#N-K(!`{BK8cES4+@zS=YI^Ms(?axQEd9OcRV5IYN< z8GZ;4FS@#8-!qWn7IYz(?$Nr?X4JC)v8KW2%c-04N^Q7GnY#nhZ_45(mHB+!4SKn!?%*)L{}KpFHE3PNs0QK$eeHkXvAuxj%ksO?}?H zN@L?38V|!14{IDLB21kbf}p8DF$FkWS$4^`z~amkG1gcmMILqg=-j050~a{VX7rAH zA~h{7!P^>C^{KP9*6|fj-Ifob_n2xN(oynq36ATtMeE|+aRr?pX>!9q!#8a+Z+{@= z{2{w`TtbD(UFP@Gg_M=J>C&W2trDnr!vSev=O|tOjbCdPWRG6#4Q>N(2|Cgzq)Ni! zW3=spB4rLtTIW7158&34TLK4Qr6MfF zPca3BnY<5D>_*VO1lE=GsIE4-w(B>tTO+0PXkRjKsr2OQaoopt8e7SN-em(dP~>(n zQr7M@6`)G&-(VYujpF{!U3cgslv9RAb;>G( zCQ{;>wPsKI^o-zYX&(hUj$mzW`D}feWuG&7nbvWlijZ3VRq)%m10`Z^hwT6MD?cU6 zewhIV0NGqHb+h%5Tn;ER6oN`F_es?5Oh>lR(k1O!rMzkJglO3yfBKXltGo0Z0-owF>6-6S=1&r)VvRpQ{u3B-1gDez&qD8V~{{=RdTJhVKgh{0|N; zAytrn=m`3rAGN0=-C+W1!p#X=-nK4IN_A>z9d~Q|6H@lNCCHp9{dQm4h=cj!w;pD znH-r+#ZC$s?q`9?5j^zRj|=&Es53pXGM9+=TTC7nQUR#H5XQ3QsKWoZ?DIchv=s+Y zveHjeedB-PpC~`8{U7wNRh|Af=elX@5Z?b&ag0T0ySX;(8FN4p=sYk`3KDay>4hnQ!h&CK|wF*oBy^5HH&2Tp!g)d2$gR-;#Mh`t>sGatRb z4sNrN3cYfXk+y6wEB{YECtM>OBjod(HvL9w>ROD@uI){X3V|dXCD=ylKoxAtEqPO& z9;!3fD4L@XP`=LXSQjPeuh4=}y&DDr^ID<^!-~ES7@31c1GnvFGxuJAzrL*LC zO=oZ(<27;vlca|PZUEz9n9{SGcVq6R=;YVAIG-~_CRIu>SymZ0`ZZ;0M^*03R$l>A z$R;8>CbIzcxs%{e8LFj4=Qj9BbKi@ca#-nYYug9}n`z!oLpn>lZ3{$O^QT$qJh(t)sxR3z z5^In^cc3481tNbeiQ!3j*i(I{HsD5Qa_57;JEKV!qJ?mN1{Q+koUVoD?I|vtHAkCD zrJ=8g4~MZ?>`>F?YS>%tW=$zztn0`HB7N1g)-8?b;X=cD!zwi1w*!{5@eFhYN2>2n z_5}2HCe77ZjD5ORK-b{8Zs*E+IaI)B2sE zTS$q}V7cDWC?_0k#PYG89sui?xA7Q>4w|9Zx4}+qdzv;4;*AT*gk`s`93m-t zl9VcxX?1$4UQzKnQ{i0aqu{YgWc6-PG^u3D9uNCdDP>m-_)r?vbNTbbT4ULi@%;lI z=aEq|^($KV9QcY#Y15XEOYvoi=cABo2uUFz#m!on#Q zH98wJr2Vf8stJ}4?P3W#X1hOo{O60@mB_JnUYzTP&<0dzvZ|!2oF+V-90x}k{j?N& zGfT;0`|cp*0ikgEjhZu=#b|>qsa?um?g@}oCD&@eQkHrrDu=xK2~c4ACqP;2Pe4pD~y4z_=gF5Ri1Wbr(66x?fiTtrAw4oE{BbFTzrP2dnPn9kJod`tmdJ$RlfBH+d4U982vQTZE3dcB| zO^l&--GS%GzH5IOio+w_un z5)f1l<2v^!i5>^e2kNzM){K^>$NlwCimS+Fo({k=hNQ$}rQSbY^ zGNr%Ex>^77`P}a1jRzDnoHdtfW5(`C$Jz~u>scj+71?lFxS=r^d0NVx5ImxIxkO!A zCCrDT0uUR#mB8*U0^vD)vZb)g=T+`f<5xB+pZ+kbjXBU$e9}6E18;d}1}(<~Vk!}{ ziCO5dOkWdDU=4eBIpo%SYJy>h9>527HMy{Uc z1zr&`s-fjzY6kZwXiFW0q~37 ztGieFWo_>E!<~=Xz>Dc^ih@q+{S(2Y2h;*w%)XIHYVb#&9GVw;VwT<(u@9Uthh$rM z+)l(+w1qi>L|=6VoI_-vgD#6sC8xIKl@ABHr?v~??}=drkx@$AMTT$RAK23e4qR#{ z(?^2FmjS2cH5+Q=k~fh`{X$^prOHNRgG%Kf*98__Pe#;EZc~|A;4Foc5 zHY#l{1opBQho%_|w0ze(juYZ{E8$bC4WtK?7U4`NSHxe5)G z%)UyYivSM$b*^5$h37cIBIbjiX7eEsyT*KQ?as=Wx8AMt_QE3EQxw@8f#eg;gus>d z^Io+$G+=VhwG^lH4T;CdNhv)aN8g5{%_?#CyFgGr@MYY!X$Y^kjGRVgZf zUM_7<9~iy)UM)wLTz2NcxB3aK02$>8gq=C%tM$EWt~AnK=95L6^)G!2?ADoSZ?t5^ zy%l71?to7WxW{Dq@btF1+S=#sRv=V&M;MA9=Ggk7+9trC%-UO?ASd)9+BnLI{nRoB zU-+C~bm)dXC>=b5kJ@v+glx7Bl>pzQgBQ<5TUwt3Kp1lIYT_)I9%OCI>XLky52j`Z zx=^sRQb-2tx7_7m=LOGmUi$2JGQ*a0j6*iX_)S_Mw}uE=gC z!Va+w{$Fss)@DS33NfD2Qs^e5+vlNlwt7A!j5dWjI}U2wEx29bVSQ|jh%#k}r~3i$YaL#dl> z%qRTh56MU$C3z3*!oMpkRDOCMjdw12Y-r5~7GiHopHQFoU4)OCLSr^b+|#ST2c1rr zRuoSj)*<DW`3Y{(q8#Z~1cr0?FTaUv;LRg$u?(_7=etdbL z2LlGJ@0=GKg^Z-);hG!gK!o?{!Na0om|h-GD!tQT2Z>HIB=&uU<5n@Idf8d$@I)Va91569G^~BY_Ugt*f8aWb)5eM$7#sDlK;*yJngDa8cA@ zkf*>DE6mO+*ydC~GED%_YZ3=B!pQ???fOG~$88^wYY(_Nqq@1}243-)2;>^f6u?ng z&X;7O)Lj<NwD`qbJYKhJ~9Mvh1tBkUnv?V?lI@FPE?5T0|>oqp-XT%^W zmGk(Bo6LSs@R};9ajg2l;jpJ-(lg{kPYDiA2oF>d=DZA15<1DEQ{` z)9?f$llw9A1Y8VQ7mB4PfBd<=r6N)A6vVGD6Cz@LhLrAx>$bGgU)7^}w6%-sr3z#f z6voNbez53AdA_g<1X2|@DlbPYKS60$HAd?y_~T1XbQ`Plyem<;ah0AlS-aG9RWn-e zYxut2NG~cz-$MugvVM%Dr;g;Oyx!nGKa-p(fZ}AJA z@XPs^BC}BVLoENlt%q)vjwZ3AlMc=XzdH2OtMFboq(U2$$d}tGAzFh1IhL>WTdCqY z!on?3MF7+)K;-=uDEIl&;tw2$w$Vn`q{AQ>n}IMkSI%31eG z3~!MSy9UWHw0FOHXL8;niu)8tV*|}*%fHOq}fW@ zC5>=3qvZowzyB(g`tw!OB5&*6&7K<~8a z40;u{fyc>QcgF@O@}Mp8U+6S3>k`l5wMH#^1@2ebbg)^s-km^5Xp+c!l8Y`HJ=QF}i>!7~Ng<0mMG<8bgcvmP5)NHACQxou=hiI1dSr}tWSJ0y=OGbp zkZC@*r+;JaCyMPmg)JvzZr==3S<8mWB>#}3bV%4YBcHOX6$}>8Nn(v{cZu9coq`$X z!om&jTNKec%-<*t;dx)##8|#wAZ9nFbCcj`p zpj>TMpjN;Fn7C!IcggLtezWSbyxeXefu+w9ZS4M^{xpq|eeqtV8X30nWwnFr*@*ap zA$xR3+MK^ZQEd0GZyp;rwT{|lPyNk=;6EL0+$PzL9}mphCM{-+b{n@f^UBn}VYb0I z7Ezpxl1yS?Xe$cs(ZjVQ%PmRy1PQDl<_Ik&;@G2CBt!&jaWPM)e7dOb*<+c&`+!G$ zI6tPUSMZKnRW3A>Tcq@gfy0?Nw!BsvLZk5LhE5%1k$;LT0i*tPw5xSsiyLy z*S?!W>su{&RHUH*Vn~2eAy!+b4;!gffM;o|*!M?jkr!u0g0(D1{36&?Dvj0ZFpQfd zinJw+E9`JfMW#-){LGR^a{M!DO zoZ?xWPt#I3|80z>Cb(v)vSnETU4hBj_m!uRi94sAxhO(RPu9{9tBh9MU#&$)qZHg% zLY-%ye!PE!o1P%!6TB;6e>nctP#Ef4JRvG;kSa-?6|btm)gxoV6P529CEJlO%^NRH zMKD?vrDJ;HROfB{d$A&Bqm8W;$R_W%lgCuL%UjMWUGXKd1&L##MUCxr{q8P^oe?)LI%eGb2kE6S_ec5`-K|Ikwt$sm!H~ zD#}sZD-sQ<%e27s%YEj6`3{y!`GfpW$pJ>QnFwgmiL(=u{4zTq89xn`Rv`dqj{3qy>5Q% z)j!ue5gX_RG?OP#UHVgv)%`M|pKfzL4(Ttnazot5jDUv>I~JDB?(A0u46l1j*}d); zzd3(Y(MwaeHUnZ%E>ATEHw7fD;`YeH#F#0wi<|@V!3NWvgNI-2Q}+jG#c)u{)h_$f zjkfe@Ks`xE>pNPztT)H_cK|c3sw$u`U3R>gC!Tj{{fu005X~3j48Tjvd5wA zPsW_%@ZhDpS+*``WTd<-M7&c;zdG^Ppk;ZmPlMT31^|*pZmz59C*~u@7l?EY*^?O} zf6+HeHuu4aYIPhZ4ffr150%{(vT)4|5^!ILet^^IN1qa_{Vn=kTON0qE@I_-P ziHc=4VHsdI5f6VP-qV%bD&8l_t(;N>kBAZNE7t9|%k5}3T{|`P)yN2bFrx?nuRVMX zb*N2!k*Nu=8?~ny>G)!=`1;1ta)bFc931^T`#kBz=OJE06=%7P_HC#VxSTsQun~&p zCYW#kAi#ar2B(yObb6Z4*h^+Sv9-LQNZy!{P~wZ#R47$845bw?qOmf#drIOn6|aB8 zVT$ik8D&R&HlDkpe}0}8dq-9Z!C2$87_FLGuRzk@X?o~-h|_Qo3VtPw15V(j_a#lkGwmqG7-+t_u@$RkDH(l7x)(!u(HbW&>8RD@XiPg<9jN>9z? z0%$TQ5bkjj5km_y!84Mxz2`O3Kb=>pK}4tRyZmz5ZEx@(Oka#dz+QVe@xpDLmyA$g zOvkluo;V7|Y3@fBf5%tt35xW)W#IkmA{fJ?)*j-7eA*d2irm{R!aXYm@QGvo7}2@* z8-<5MV0y<{TZcW?ZEmGyfooL}ZASH`9nh3OSD%#AWV^2HQ!+55QM+hLxq5l^*GVyG zl%c!~Ci1`AM`cYY|DpDmYHN}Qiit%5G^sfgOz>WtRq@QCDQ~60AW$Kdl_&t)pkavV zY7FhQ!(T8k8V)_WB=Z{O+0(u2Gy%ma3Z_Xc__0AL z&UAj}>v_fBL<%=PgC@ObhHTtiQAPiXfcQW=BpR<+I6Ki?AhLBn`PP)bJQ_OX48-$U z_!d!^qE?jrIlZVl%XDO&Q<&vl>Iq=W5nW;vF1R+6sCdVcH4}KFQ~q22y7j^miqro`3tB~R{NG=e@1rzJ^OIX2Ej_&)avrnXTy`oNeV8sP@IKwfyFKTrd^+UP`=x$XJ z*9FMPo6$U#SO+lO-gIJXNihU9heQ5I_fBOz12^B;QC+iOc%cV%fJQ<4Qpb=SIYs8* zWy+vju+O!KYR46xyqd?KK$!*DXB{Ov7z>huH^m4Itr3;je-dc}Re-3f?@gI3;h82g z*aPOve;)|R>MJ%yFB^DH+;Nq0h62GB^lqmP)EjcYFA5HRV`!vrp1t5i^C+7{}w@47q_rghy4zK5cC9Iz|AWRqJR~Jz|}O*+V2z zsd_rdRe6M@lud1*f{?)5!F&*IC86%hyVO!uwGYNa#@VDpL7@9PF=VPv36$qHjBiO2 z>T7WD9)>Nt^pu@o9--&=2rS+crbm6MW~-YrV*^)roNx#?u41Rtg!5)T$D;B-h^)2@ z!wQ^1eKC=yP(;)!v4@BmNG|@Nk5R1ePsMY$IPpnGF_-1qDEsq-697r?=omlmEn5eS zj8#s8MqztHty3;cK|`T|xeB>n+e*Ba@w&OLlo%XFZr)aX`SrLx+|ersLtg}AOjCm7 zdFtZdR*-8Th@^go*ry8sY6fsoe{^Q0Ki8l}G{tq`ciruOC{NeRWfFj~#{>od#N5ADz8SjpTGyj&V$&4AL$(|SZXnq0#GN8g?VU+0|U7v4Ln3CgsT)qDupFRuzs z{!H1288&Jwq{8L1|wQ=qetTuFio#*q}bD%v!Qs?Z|=^*Ukfb3!YV~9qi2AM7y zgZf>9pTVUz6e(_xBXf41=y3;{_aPcOzAK5?_UhKx(Nm8<{XLEp%KJ&gXFql~+RC(i z+tKh$Udl&V0VX}fS=YlGru#Y$XZ=&kjQ1}=$+b=LJqo{yxtnUsDeG$RM}kJQbLyhS za}SFz#ktZFeY<}WtzKMgz|1O*$GD{zc}{hb*gxkLPFK0Nzc305EKgj2qpvzTx>YxC zU!i#XqtQW(CqV))U=<}xz1zwKdulkkgpJ5?()u^$Swg))fObUfD8q}i^LJXkvkV5TZRh-<=7UGA%zoxFg1XPx{ zio5(aUYN+^a$Z^-r~0Kfg^~wzaH>*muDrX|%~kPBv&$x5N`zf~O%+6JzyEURm%bqP zVuWyvJdJO5O-&e#!t~e|DRd|pg2PHQyWa{(vPIuH9)!v{#5*{RyJIpPRaO@PgYVTP z#ec2P>tYUD1&011ib4{jGkMX~zo*K5Z>Tc;sX?EX3#4PO9FK9P7?RPlEY1+yD1^*5 zk$rpe#g*gEX=gEjm3#rf8+-J{PWI%IUFoW;^V%BEFZnYw4l`Pb-XO->UAIh zEx2#g@dV|36IZzyP>Kdho{0GCWK7`z)m~(pQ<36w_aTx3$-bMlJa7K(G12UmjIL7+ zqqhX)0C8sK&LD0V#2}RVjLHU)8VNjN^A7(+mIxVmRx|{POSV+$CfOSWnzkv{z{<+) z7)L5|PjQ?Iu3}X2{il}Tez#04#=)5t!>*k0iN=Vml_#D@-cl1CKhLIo5Z#IgSiw1D zPkt>U^x&y~q}#sPs3-3DNCs1K=r4*7>W4rX4%EQq=U;XO8u7lq1fOfvAmL;C%u$~d zLj+5SKqo5?${|FEuCzsVH2%U-5-QvB_fE9%HxRx@Gc}Pd9bR{y34fw=Sw2dVJ)%yC z82&{uQsh`b{kA&AA^2|EBTzoK7KDUG+;EFPs->cFe(>3Hdh^ySA&3S0A=xOGT`)Su_re&3}I}icY$smEayhw6##!zc|Be zBq@D+gV;QLElo-T76T&kSjCX&!*7wz(+jSog6aNA1#?Y3pnOllNCsc|ePmt7ev_f{ zDD_osc#pm(Mp}Rc?`k2y`;;zd=)px>JeZK+Fy-9Ku}_h_W^mp1XqDDkia8o)1P2>| zTS7y3OTm;5n9+9DLCf9!9FSnsJF7mbF#d4<&2R3~Q=1ZqL}(NAe4uSi_)pwoym zdyo~4Vofn$F}B(|&BcO^lc6^62$!pgsi1R{bLjP6@XBph?72A`z%QyKMl-<$!l5_b z{Y2iX)wJvSK@uY(Q#%x=dtVWva@9aXJEg^dse)^sob_={oRP6k45VkJm59(-*LmU_ zut>J{Fj**5RLoY%{pgvSbA z9>#c`V*lP3R63M*Kq$4m`X-!^6;L(&A!7r@!3Zl!s+UDz3KiX)u}6f#Ozp(8{;}Up z5En+?9PJua`#P;9V*|W9sg!?G9!$-)?*B+qyx#$phO-93N+{p;nA{7tElvvMqo24B zD-SN{Vzc#^%tbS0`FWY-CrRsIkl_X?#bs#$+7)8-pM5OC%);gTct37JdbvKUlP4+AdT7QHPm_Qv?-X{x94__^RH@pSZcN=Ir0T-&Z+N4~ z6C~BevM({^GJg4y4==L!*ayiUwq1Osvb=wAy!jf8rb3XP8V_-VXL-FST&ENbY|KUG z2OR+s=ET!>E5F=HhJY}*Sg+271DVlM>I;O{xob0FOYCUeM&YeKOlCtHf)5fh2WV4^ z|5oGV8MIj&rXgI4<72iLt1gg{|6X$J2uUx+2E)UO$=1Qj_OzkoZa9Y@-nsDbk1yM= zpuuRM4nRAo83nMOd6PnPtL9%5j@aM}Cc=Od{?9aNc&k$`fc-d@<}lEPej6Eo*clOk zoQB*fq!#dRJD`?L*?Q zY5`u6E-2tk=u7UN z>D{@xcE1>h!?2VG_b;uL-;ETBzNHtN&Em2LG0X4=mEp4dPb4*AOlz%8>)H;VLMN5CY?*q;bq5%M;^edtThlj@M^2`Q zWN3S*D3oQfR}eb!EO)&r=@=1exuMI1XNsN^IaOO6$JA*dU)_5HRL9C=5uBG4J9B2@ zgY@H6_u0eowB~VyH}p%JQ@LM)&Jj)3sfPuS%4ZI%)#cWEAnI4rDe(HH9An@kj}E{4T88^p?!%sh$H6#7pUrzzf^qkTb& zrH{MW(9*vpC1$aH!#WTYw6hzT$osfQb#Qcll&LYfG)SX8E)XlsMNdNp3Ut|7Z+EHJ z#TJixy-vmk1Q}fuf;UHLyA=i(Otzc^#~U$^WuKM60F;eNbXG#2^EO4JuG>MjjL`YJ{{RuudBbb}dwI&15|-*{g9 zAZX-D04qFsC4XMX^JqXhzwA%E?#J@Si*mz5e6$jbBAY}3Yn0;A<&->*S=o0l9pJ*e z^AS>H3AzanbJOrK$1Tn-k8(r>_=3e@>8(7Eh=&W5d)tCWVO}ZNvw4|Va*xZ+5$~}l zi!srMx}o$@&~3g)ox)$Kp+X}BSd3%rmoG(L8W;3>)`7^)iILZw3)$>43S+Is9Oi?` zPU3x;O~pnV)Mtkddl(67;+vX)eiEy?&qIi=2#*=R<{#;3?z%~$dc~Y4iHSvuqdnvm z+g0ko?bf|Trdx|PL)&>t+a~DtwtKA9BbS`h#wOoMiV#df6w)}8>ftn@sXVaoS#OQe zTID~A8U$TpkOL1*WPD`>KVK9oIw9-J-49Ht>fbVFf(^WN_Hbq|B)TPGKk{c$6?c z{N=@%NE6hrRZcub21&$#)yF!>;=4?eoyh~enkNUfBC%1#K?NibRnj|gr(3R88~LFVew~@L6VptRvO37EjxE7Uoa`B>@fl8*Iuv_qZv1D)Kre7a6Z`J z08IM;L^`ZBrOr5Sj1#Z%SSvR;zn1|eTJZSyr~@P?C)0poZ7SJzJm@(iq?W3i-h2us z=pb5D0=4c|5?5eVuGU=+D>4z&-T^_xK;?8OO@qVO$-3*PC z-N+=-RXH1kKR1Q{h>)fk9RFq*#;9kti^xZrZN~KiBQbybZ*?Rg3#1Fg*HVO6%W=?0 z5{&SW9IUzPA@V^u&+wNMBI0^cxa*NuuAlhog5Ptjf_{^>nX%D%c1p~br|E&BO-8CHk2&PrAy8DOExp<)o}kCv_>Ag&TkON+6@K%ZX`-(mz)j#7Z00dQ`H6wo^xg zVd5KbXdU@iWcTeV=Xyfl4s~Cbn)GBdBQPK1VuIK>5`79o+5~MV5Kfk=e~_7+3Tksx zYb-r5K!RRhX&g1zWt8j)q&nUg_BSdSm4oq*1OCv{clgRk4 z;4qx6lm`To_M2&9Axc7L%QBH-5pK5Ke?DBw{w z9j#g@r;qP8_J*(?ELvl!eL2@x~L?uW=Hdf5xASAzKqKlVl`P!col6%d7m>9PxQ|M7eg9zf0S)iBI*?ySaut8ql+`=XxzKKomy>pxCcxzkL(@`p!6qVd z07{hsr02nzLK=~_2|gCdqAUpStt zPW(j=3o-{^tAyH$U%I(fOvXeKp=}o$%!~!dSBx#|Jw-TCUtbKvT-|S#S@*j&Sqd|b z*@9z$r>xYFGCaCO0Ss-;V?yQF>-cfo3Z1m^_wV zkE?{qcvDAHNdgrwM(E*cR_;sio65-@!q3s=%_#c3ODT#71HlNy2R})hRJjOi@TLMF z3Xh&kmLk&NRJkUsWwEH$v8Du@w93XYM)zUxY-(C zSI^7~W!Tw2eM?4oDBc8r^(>|PqqH6$WNeSzc}}hg%aZBq_L@g^b3K)3wO0?&3RbRn9I`v-h6`ee1mJF4(XOgfOcJ zJfeGRX^fd>i@|tREQanpDM_TFccgyD{*BvG=?FQwrt(6J9eDgqToiTyWDr0i9tilZ zm^iNEu+xrg#!#W@MznK4%5ygP8*eS4U)SVwygJf)!p+wixw(SZo_!C$b6;aFvmP}o zP=y-ht?ju0y}lk!aPvevvyEDN=Ie-;ggOJ34Zo{?!^%!Bg|8y}*s*RuEL&V#{c8$q z&AE z(8e-#Kkxf_WTThav~`@d7w|D19d>(zh;wS@;OEYvw2#jxPBOk-r!f%LL`{JF_hOtx z%0Kr&KJVfzBtgl-1JHUT+z^G`@M?X#-!hEUzij|93k zh@up%nkd_VV~Yn{%BNc`x>k0^-2<9!NWpIh$xxVeG&uyfr9KNDHPOf1UoQSc=*L25 ze$uv``83(u_6}Wz5~LuLl08MBK)p@4H}Q~G78jOAfXjh_ohwtWy{Kb7M*9`6XNseF+Pr^A8paJC zpr#o5)frrPh`2YrxiKLnX24i0-&&sqn%Z3YoV7bi;aJ`o-T`rcmZ_2MX+A^N{?_1p z)4+Uz@{PKvu~Em#7asjj6E)kj$9yObsd3BJ3VvrDM3k4qc>lt6zifK1k>g#@V;Q9g*Wc4FeEe}g$@p(=b?}lS?-%s9*IjewDV-X94ht&nTBb;am%mEpR<|ej-+|sb-Nu$&n+SO zpK4tSX*$d}C|$G2)(978iHc@zDR=)M&Tm>mI*4Y6JupL!RC|m~b04hpxa14;nXNu# z;ZAabd0UQ5;qAm?b$_U{l)LN+k}GK-B{q2GRvUYE9{kn%lmGS1IObmwT#96C1tScz zThV?2Ny8c~T;uas){*!$DNp+-1X#q2+#%sXu`!u{A>Er5~mvXTZOvgu9nI!Xw;GsMQkTY2GZX4 zN&bHOaQojOWFkkf&d*_wpODEgR>hW>PWS!2R(gvpY%0lmoA)AfnGo1uM&&1!M`u2N z;zovh-QNJKC$FVXgxT5}|FV-4+g;T4t5z1A-OXU>5>@BbYsQMF!UdH`y;KgYp`-A8 zpIcr&yJs_VgjGP59;{GfS{pFq5;VFWJYQJ~haMe3yv+zH-xXpJy) zN{gdFR~ovZJEC7LUR=(|&Fx(bfQi*Wob8Cwvz^H=4KtP#dpG2!SjM#dD@sAKvcr`L zJBeEvw_(-xnw0H>>wbek+En~frn?vSkHr_kBrS){_5dfMJeh#z1oTD=qs)dM;o4@@ zXk)k-3{CWk0R#4v;0w;4MY8-u8~d1IxWPQUu(<^Q^83OMk!KkKZM-@BGdIcTwlVs` zTUBBykzkmb`Cy%gh3>>ejT-IFQ!+{8GE1Y(!%e6vA16)yUf}LXa7t(P_(Y^QMbuVO zCbgaEm}XNtq6i@R9+Jz8e$6~au4TvDtWe!l@Ye+Uuu=>+v;IoNh72P>#fnEq`7CSS z>>$|y5SC_A!4&|}KAlEY_UHDFUx6`|GpU*WYO1Uyr6q+Mpn|1UTsDgpadTi}`RME- zQ?4-2*pO2qc788{a~%Ke{wFY9EzJUU;Ko~7l!))^D>fewl3fBLN0?g2 z+IGN{t~>O>#)CRp5WBiO7t=4Cu(H{<4x*;ljFX&Qyf)Y6>uS^rDaQ16_-(mD#t>Lpo?tH*lZ9cVy&KdcXG9JO+2Uq_G<|JtXwezA)(C^lS(tqAmF8dK&&Ph6Lf zo*AF5UWKy4MgAx4DlNA89>X&3BAL7*9>~AWI&yn38+gp*T zaohEt_5IszA|Ix=Im*x}N6TJfJSRqSANz~K1n@coj3gASo%h^l0g+7SS%t{--gy|T z_=Hh~GjrEdJ$JU;`kNs(1xg}A(4l#+%HM=j7&1dU=w|$dAR@_&Y317Mecc<}7-6W#aEa zirIjT7H@dEfJ`{_QbSgO@}tR+&P3kxH74JiscGzvIwHt|lvlz%Rll-Cl5sz~V|l%a z)?In^GBD>DCudI?yoE=L4{Cgf3#z2O{g8N^(fIEZd!LT;Qo1#R6f)6hLVh2JN`~Jv z%~i1*9(4&;F&KJ|wy(CcC<%1j4p&(l<THA1>*HDitUf^v8Xki8%Mi1 z6~1Oa0u%t$Zxx7D@Od<)CZ0G`?1U$(yduRnjM=Wal%LCJt*mSUzbeqxVmi8hMv=zZzDx zoLFs%RqHjPdn*%oRl`aRnA-=dFU-+n;tXr<`<` zBexE>-&OC#;--suCVhv>As70sLPL^dGdBTM7q(;cE)F_IHU~R zv$YEuRV!oPf!|PV)9{ImVn8AiH+TV4IOG@C>j@2x z7<)j1==PP%kfo$9Z`(U3t`1c6>v_Pq@))-2m;y$Iao2@>OmD1FGwz^`#4TN~f}6GM zZE^|Svi&SgZ3+gmr_<6EG};HNq@9hIhaQ(Zm92#0vs>%v$*Nr{^prrKD5#rjLfj$@ z^fJMHu5epa?%Cn@+Q%n%<7Q3yvl-BnpJs-VI=jFnj zmeK+rH|bO=svNR}+3in~gF;pq^qdL!%4SjmXp6p+Rw9qZ9E-ovxY4OtaWqhSGt#M9 zx+Tf-T&_P_KbD4}Z*XW~+g)JITCIye2pd_Pq$`pgwDvB43#+X}ERp-3?>9@=MLNJH zhk?c3VZ#e$5$h^nZnF$oqNfSeBhoMHI3GFwv@TQA9)~XU*N0cBO7f@)!gkxLpg|>1 zQQ6B!mc0yjP)XM(ca&LDHp>c;!ItpoQCi7f#H$Yy#JO5(xH|#hg#nX?TZ$jTWDoUa zri-!+g;B}GeT8T;Gy0LW-!Pi;XMhfZX;pW(QT!zOQB||csk1|lgZL{)ksR4SgIK$? zAg9ES-PwOgRbTo`gk!qn+oY$~>1@SR9{lxkGbVB38K1v2d7dqC6UIm28M>9W5iJjT z3G_f5+x9`y(%P{NCLbmE9Rx{?#2##fAvh8xeKFn7>TYb3T3h+aTGQA*62A|c`2Kf| zHsr(wd{H+IGS2p|!NIQFFXZYH60njZ^DG0^04x$M*VXKmdlBGBskRp`KIQzdA^jn@ zx8DdhgH}%o8nOn-stf*IbLT%#!!PnJ+$jlZ6(+~rT6Roog`})>8Ke|n zhR|V!+~f7j#l`#lQt6d8wXoaOq61T0cu9xdjiRFm)(Bjj`(o-VOM7I}e8C75{>;^q zmJuo?B8hNL`#OEvpgNswzFk)<>1TGYXi7Cq!O;6yio;u)xplVG#T|mj+Fd=4D4X%q zF28#^3f%)bEgo_arDiCApIv=4-{`#KF%{F|SlbdyNz~to!hBUkuN23{D;23iApDr5 zHKYt2hXn)fimjKV(PoXLn`A-+i!W9j)e3lObsF`1$)nQ9pUg14mCGfg`a12N z!O}40F}n^GF;IyF))B)cpubJ|J`syI%R~?;@UY?scvft~P!%!b9y}js#Rsb+_^KmJ zgnulntx*wd-A~wL#IA*&STVM_2ouo}71xn<7!Lq37=q7P3co%-h<-DCdd?}c%kU=T zrTQ<2@a=a2EKS>r9C3uZPP01j$e;(>6hvH#;mYfW6Eqy7{1w3O`w3{KskweboIOkr z?`{z~Vp~>~`P3w8{jA3dYgxSMU(5AD9K!7IObv+Jp6BG1KkEvFtSp)wG?P1L7En_Q z!EvD>$`S>DY!h2NCi^Tj8TMO*(8eE~aeoyhvFVL2WG zM5oF*KwIe%@+}@cGaHmINKwbcRmU)h znl+9^B=Tv;6#NC~`te9yP{Ed0sX~K)+ga+Gm0uSJQ7tGX3RV5ue!xjARS~AcMT?qA znTTKFr=%JtU;cG$|6EbSem(oqdeeycgdPqzqfaxz@fysMR!&10&vkS!|XsQ@(USY$ZY}{hQX|cg;zid zE>5Hd_Yb^cRP{qdBz!h@@qxYBdK0I@#b$s($`lm@M2lXP?Inmd+LIO%V!!aJJU8|S z_Gm}+CIoTpmEQxr$0hW|C4jPSM{?uwuHL;!*iGENm3DJ*=}DldN;Y&VURSD@5kH!s zboB$NRft}+KJ^F32r+5BUM~2}20-57HNKSwte+;*!N+GioMDTJD9MJx1l7^o{^zs| z-2nHJk#EkBs9tlaV3(pjMJ^Imuht_TKFd`&ClQv|XwU>=_?gxcjVn5pjLaOZh-o}; zqtf^3uT(+mrJ|%}fur*pVzC?|Iqc`zNoQJ|Yy702XxiZZCT{Rz zaPz?S&mpA4!nEzF&^cvAb?Qu#uhp4XzWALTOR#1zVNNQ=-gg6`Ht}mMg!hRb>m%IP zm^BjwI_C!=zBAqveQ3UG5(^gde;WbPQ> z18;*c#%j|BUUTOh-_}bZNJHL5ZM^r=8QvL*mT3R%b=q!&yg>qyI+A~O>MnZpek&l? zggw!DjJ{&<|B3%Eo>}>;4eE3wCTkxNzpV&;*hi5$>TCQ=pFyAXYes>zZEXm%7dPLM zt3LgBf14W$flO2rc^ZSMrHrbC0r^Pd#z@7i02uST-?jl$G{QR{R zx-MU^T~>A`$h%|G{%pE%(<~(mpoODj?U^w?((}wXYNdqRp3pO;kTH1Q^{IcG$qR|1 zE}_RC0hU-X4)Zdo6Xp8|mTt82hNdSls9iN#hcqzc%JSO5nYOJ3x90i(;02f-C)`Mo z2XLfkgD{YPe5RQu?3Av;$lX;#!Rgy#)o(jOig2;)6Xm1g3o-FPJGjjz&@xEZ0 zBNYat0Qx1%7Q3-S3X2PrAGY$Aq62-)M6N9Zw-kH$wG+q|x<$ug5uWnC5M2Cg#>e>B z)v70GgK_efY6p9M1N=2NzVQsrgUq?`94j<~LZc|AGmqL}nAwNqm7rsF#}-KUVnKmI z3m?gQ4%YpPZgLRUK#J^DL&JFFy;c;e>R((+iBZO`Dp$;U&K&#NHcbwG&MS_ESm3-M zM{ltpe3ObZr@WgpNx|4NK7u?eGPdf=2N<4U}W}orrQ7?G+=_HQW?6tAU@K z9u{)GsIfZon-1>m5E^u8Dpb39k~c@ym7a}!jKQA_&YH5;BC?M?ycXS6qfB;)*=AQQ zlNXr8!Hfwh!D~6DcqH+auAsA34#|lgqT52*!-hLdOCB(K`Jd!z5H~Fws@=5U={76^ zmvh^9h|0OEq4@OPDI`mu07&GXRH;YmD`gt@>wu_ZoM|s108)e|c2fx1M&brI4WmA@ zx_l3of`V1}5hhm1v($PGix-1-y1h+aRFpnruKDwfzds5?iy-997mf&!OiL3Erz=`$ z-f-9u=7;xU+iURs_nPl?S4fsg0n4_J`XJu0>Imcxsffd|eLOq<Bf9XQ7@M>lCC1zaoCHNZUM1k5O}I2uu(v z{EpNXxBSe=373_v{bV9&l#1rf*Ovw1R{)P2LpCfP$euQyryxs^l}@*!U5o!@RBncu zDcP9RYX3Py_uD6*hM6tIY=8W{-Fhr`V!_wa$RzJ2?^2%XjQ@JnlK}LFR7e82N*<4B z;!c$G=AY^{Qz85_O%PnHn-`9eR$J(wsXnU(A!`!rUrU+v(ndO<>^9xrNuYB52=I1! z>hb_bfHtf>!UehZ8^e{Ac>$4GxM@_kH}>!vSz6A?G~0t4Ms@F>A^ogCq-u89CpaXx zlU#e3cqTVu0#~5qNmAHlRynx`ko+rkkTie!#argG zyGSD!+~mQdS57sj3h&pOoa@p(+McSf@D=2|vDG#hU8@tj5O|{8Hf8dH5Vxa+XN|YO zgVlu$eYf1!S1v%CU7qu?J1@F|{YvWfp(kVg9mo3~W*GH@nNzE@;N9ba~F z6XDqoG}l-JCrGOS>_JcsogN)S+Mt;es+nOG&yWiLIG%k{(K&|n|ARxHQsh9z;c?HMg$bUo7#y)D&IZ@tssr&FlHeJmGIQ?)Q6! z1v~-)eNu=!K|AP>05P0x;xS2GLxTY_oYt<8CkDo;v^h%lSYsd^iL!x0>~BM55*GpG zz{85}!X8v9cj#t_$_4~@iotl28SnUTp+JS96heY)L1zXRTIu+`<$ItJ4(Xwnh(yF~ zG36!kjzG-ysn^ambfRMz))2H2jE(y3hbhFsE znYSV%Ep7HMLh|iI!irdYU`R&)AiRc%!)fNz^4Y#x4N z_v|*90+=VL6G?siXvbMzXJN?j{4W=wQ&ph^o*8xc=|7@q47F&8qVkh5O_s4`ios3F z_5CG;&V}k)j@gLRjRmGc?QknnG!44ejPiAS)5|-fT4UuVvhx=BQQDhxZ(p|HgoT0; z<~ca==$AWx>nkLC??(QWPMt4zUu3zGQ<*|o99{JqmhVbFIO5i)!|RqI4h&+`6tLh1 z`1BZq#xEnA`UqzoYhc9GVY+jk$3r@`?QTm{9kVz4M7gO2mw4OXsnoP1IP}XI(XrWM zTfm>>eg*9;8-U{N(mJ9RxE}zD~k~F~{A!@B5iFZjs*x)qE9G zJ#s?&t5JL8b%4VboQ5LQzAEd*1D8rnUtOOa?ku)`C9bGiY=2#)iv5kvA;X<@l=w5|-+LcUV!v6XO7hK!r)+Dj6dLg@A+`YNQ zn2J6oqCgskKFaaWzxU)xt-Ehr)QSd(yD^;KwoqkHkiUSPD=KZ6jE%kX@5ejk2vtKz zH~AmK;e5p<7T}%F0zWPy-=j~d>=Fz0JYW2~KdKDYBtUGGuT?Cu2~!1PYXKk-BPvO- z1Y<=RUXnWoTVK$zc_hv2D3kBTdoyr^0vR}rvyA2g?o;jKtLnIQ9NqrRaU4uU4gJAZn!_ROC zfYb-$XbvZ8mImeooT(gS;Y)XqLb6q2xG?o*Irk^WAIwJ>w&Duln$9tPQS}0Cp95oZ zJ315CY4h->aCk@b#a?;<6~|GOnu)`*j{Fjbz_KqyM?gGoJG6PG((q{%q_3y4X?gYoh}^ig*n9J&FzaKe`6_6f|V@fV`jr~ z>~)>8eiCIPf89ZJZ#J<~DA<;pQ`k1whxtuNU2Rli_JPq^_rus!4-e|ivrgod%fDvL zT{i_3>?=ztM7hz;f70C!o_*s};^d}sVJ*}7N%``!0anl)ozTIr+qi~Hhz}+73#McN zcGIo1AAEAthtNzaEI1CMK79--E2VEdXA-AlOY|RWek}~@_5EeCqCSP)y^D9{RmB^M z#UXIMN*26S6Ls|3|op%P4$ z&l893pLM#FREALd3iNv>dzs;cSqn-Xb_Oxeue z7mj93{;5H5Fv_CEnmx*kNW*7>)I{>{@?@dLafF_3G|y=j3I3VS-RmlfX;BIlOjxfi zey7sSTD^q4UN7p8La}q>iyXO$n|R#Q0o-8Zp{tc|7N56>?4k1&Pt<#=aOi^{fov5M ziu%r?$CmG=oSMPg_>znbtG{L_3vzhKl&nKpE2}J0eSjqn` zlz1_=u-z8SNHkmFLD9?@S1N2M=E<6~#u0DLO6fZZJ_}37P(1_%^89)6$0;a6y@oB@ zo(ij~x1*${W|#1SEirkJ*hMTZc*}%zT3srXX>m_W@LGpyyEdY>xSM~}!o!u893z>j zJjydJQ845!r+hF5D^|BD=z2I#W9yV zYNT$|d8U4#O5I?d5$7|He?0^=U2?~R9}GfH<(YRz6xAxDSBV6oN1yQo8Fpcv8V7U` zR>QHv1zv=TKF(oU2`lps@d+JBe;DSrEmt!6pMrJVSwl;3v48qf+apru6I= zeZ_m+;ke?2Gq$0+Q-XPsU3L8BX(;?B&NS0W|2V8^FM({Vv$ zYvE6{%b32?m7yHTA~=?V8%bIh$ol zgQOd9aB0cYf5Hlu)oJN*@mkvM;%!~)dj)eV>M#^EL4^mNFcsP-+P4Qq77HmN?*Ji* zdh1T-WfSh?Vb&{P=k)4wT#AwTsLGvvl5g)+dEhxVJx$HRYE;{_GGE3#+Gn>C2Tf)= zZLFw2tAJ2nrH9I@66m4b-kzKWg&f7yH%P3R6z$gMe}x}uBoo(#Vozo+N4so)Gv#KT z#P@+`D54ZQM2mmHWWduL-`w41k{_L$n-$_k_o*{Az9pZ z$jtih#8S>{dHV&(=Cfsd>;cOulvs&ety&3_Z-bxo6?ChE4zyl6im1foCw8gl;su`f z1?!h;r@rK}&aRh5#DWf%@&2g8MIfP)00!-q$ktW6~{*EBURG zLqKKW8|IJ&PVUeNWsKRB^avH8vdmtJ_OQK<%XN(Kd)JZ5C#+FpugvOC3QVe$I`sEq zF%@a=y%X8gz|~!W#{Uw4=pbk5NUBCJ<|R`??4t$vrc%lw>B1D*VRwtYIhAJPu;H(A zftJ2SMb#t*Z8w%;W`X}L?Dnj`)J_CDm0j=>PPjqZ`Xww z+3;cIaDo@VQ+9+pxSEn>(0QNGsw-HU)6!4Y;$2t*SivDg#9;a+5z#FnTT2+)Q($ko zKZ5FH7w=KftH4-e-4W0&y|g)HivLQHr&-Cl6!2Nf&XmM_<-3h%di1BAST&}kz%`5m z0S?l7`L3iVW!#EsjVO%76lZd*c9T|1Ht%rO+NHb}1sHodv-lFGQLyaj8}2m7@dNuX z+q(V5EtU$W%1n9{)DNQqqOGefat9{!9-!|sA9RIeFzopZE+Svisfs#Na;3<$4$o*X+zI8jG>v#D- z`yU;k9GsmDCQAR;4M+cvCguO5YXzVJF#1~~0+N3}LkI@&25<@Wv%*)y z;ifIELyjQPop?YI7;o}~vp9sXD-Wy3lpPG@gLIrMk34AcHB@oB506?4aamqTq-?|W z?KI=#Zo^B=zMQCAg-^FoTZ5rL$7n!gNhx*OB2(7Oa$#hg{^=((b3RGIK-?rOrgCk# zL<*<055O8b2jP8LM|5zfkbkK;P=svJqM{v6049;EAkCHx%fgiX3n{+ z*|&J^d6GfwL!|pXw1kY~(w_sJh3c~B>gt+eLi$ZBXnK>!US$OBZj1MG3I{ruXE;MX zUj3$ZA4){!&wfHDijdoE5FMmug(YN(2zNY|@^ZAdYHD5C7YJT}l?9@t!>h^|&U?H$ zouIwO+S&|VgHqpxC-g(SCp&82zV&8^ZP!6S$*G&2Us&rVDvQhR?0ZeW9A(t3A#?LK zy0O09gW3ya2|8TY^BtcAF3Pk% zPf4%?sy&1dR_k{p9KZIg+*lYM0RbUgD?!+_x?FP64;q6(pwPLE_i9S21HScoNMJ}9 zy=p-X2k%W)C`#F7xSJAXuk(}rx&ZRS&W!A72^(I(@rJsU#O#ydwExGurEl9BZ_ARf zZm-CCBz1087)1??t#O{*t6^Ik0!U{Hm}d`?U~o6rAV+=T4A5Wg-0vj`u7Cj{aN}wKkx!qz6TiazsCgdo)^fA znJn(3k;H~H{%F7Lv*V)saZlR^nTf}VDS)oA{n^Vz;Fch_Oq`;vB+w)oNIjk>h)wp>mh zCTn!MmOg9Dy~NUcHf;!W@++~d5N>csBKa)+s9nXJ#COTy9oN1+xZZP(ub-b93K;CGlo zASU%_;2W!8P_>Ju;ACVUGyffNXYJ8heihKyF<0A>05*Lg4GAFeZROW_WGgK z=DRF&GoSqTm1K8Cga8uP_um0<-l?Ce3MO=0uF7hBIRNH@;o@uRLQDuao`4xIbK9=x zZZ*F;S2Yc}OU&6xx;?JpoL+`n(oZg#5( z!Wul61yqTEk@mCsjm*}Q3cJ=%;Ma70B6h9T-w3sfDSODK-HUD`2~*y+P+pFwgK(X6 z2Ly+tf`^V<#y1e7*u1}dp9s7&7;rto9HPfhFQQ;e=Ou`dt6+z2$62x%bhy#w^p%8; z-4x;8i>k%YLluku*wq;%@RdD}Jq}x*rvVa(cn1R66zj*4G<_)b%8|cOF6T?3NT*4G zCs%4d{b*u75=ocuPU`5;zba7Woc@cwH-U?)djH3*bX*I!Tyl4EM@vnF8Mhl@I$lvk z5h{(uvcz=U0#iqua7t0IG{tbuNW|Q*v@$bN1I^sXJw-`HXWVy@ssHmnXAbwyf=hy* zzW<-{LT2v0=bYy}=h>g1Ya_pYq1Iaox|Yul_}&t5>V6iY}j-Tw-TbMyblhOP-c}TFR17iqD0xtXi|8cKT?G zl^^hNcJbnE;@c*dyd-UYDy81_?0|Y^Q?us#hLtIC%ExP`54|4fi|P~JJp06!Tc%3I zE+!n&n5V`8Kp^u`4x_`!u|2eY1L%?zQuLFTGLe-6w2opE{NOTQ6yZRJr5F z>!{-x%crti9ts^eV1KPGTdqob&OUZ=&#{Z^Pj?xY(PjF?hB3!a|5&+e-Gi6v z1%CT@@{5&AZFzC}-tAY;oSxr({nfKOHhD#a+P-}dn}5*EIZS%YOET1B@H&A6WKD-n+Lq`m|T}{D7+=lID1E|1te1%+gNm*Jw$p zHEqu~k2+PU%}?F0XYP|$_r4lBRHGlR4KH_jVfmq3%5;kGZ`@`=&+^$F8+B+>Wq7v= z&Eyf;$>**~Cb`YoT^6q`FHF5UO8T>Xz3DaU{=Gl!*qP!#tv>VW=;xl_Uh~X=pA)}~ zt^7s30r8gx)LWfX)Aqj4>B=F$>>e}mw=$u{)7I>NdCRnOmydi=Z>BDLR)Ab-^|ANQ zd~Pd0_=D)S;YUoHGRilcKPqa-LVNar7r&^qKC64BPxl^pfo$N;D%0@ZK9(7E=)>4X z9ewX&f9Lh%_ILY#X@8UXei!>&Po}X+(lLWtZGS7*J=))QZt@W9?-O*`$C-(5JMgzE zzI3sH+h4|X2W~AHu!b2PlBB&lls+ZS{S1Cc6;sJ2HgZ#%;@3x_!+pqiS!fIjijPmV zm@T$ci%H%Z6eOE8mMEj$DoNAc-0c+{;Wf5@AeAhH%-fm$)I7_YEm!3=A=k1;z0mUd z_$sxV?Hqr+!)yR}(x^T+x`jWsd0p*A*9lW6UJLJexbCyFqbN(kj^F5Q%uY8LQl2q}9Ei~H}yQUpHz3$+_te>`>-srdVqoM6%A|tCGS~0V# ziG0sX=2tr%qO!S{vpWyX4m))I>&3~bFBY#DJ^EVIYyDEbk}8gUvs()ja5L_B_nsX( ztT=miU$eF$uNYd>d**}BZ4B+U%l`MP;opvawtTO0htK%Fb*5PIljYBC_;J#Evu1u; zWmDu!Pds1j+jok$|F-$Cn6_IZd+xtJs`~QZcKj4@t8ok8_hMFseENFVuY0%8EjQ@E za|7lMN|UCSnCtV+3x22GN*M6=ytTFWn?uWyr+1@*CGTRFs{^uAdt92C-u~y?yNXr( zTFQ>v`sofydNJT|Y((p+w+CO9q=1TNu20XL|6|^s1EE7_<<)yBulTnOvo?hsZhJ1I zcF6YJ>{*(Iua9XI(q+Vu>CLY<%(WICj)3N849X%ar>i2`;YMHz)GtN{= z&OCOtsbTz^JAEQA?C{|p+g-nPWURQ=cxuSZ|ZZt_?29<{?_yTD=xUAdHm(T z##zS~U8$Pd@6G4u1`XDRR^GEduB}}gTccLU`>PLB*_$xu{g^Qms@8aU`s6VKDy(`o zVdnek$-kW$7g{ANIXy%+e4bfy|D^q;_mw;)&pMoO`OPW~stnk){M_1@y{G0q-tw8t zf4AKe>8qc(zvO|ABY%6oX-@5Lb_TU=aQL@XLB4BycCWPc{SPC0|JJ)tZ{3=XV-LKs zAfx2V9rreQGuQjI^>v>*Aa6ca>g?{bwa&(!oqu-xjaRnsj!fC{;geqFx~yqkqSNK`c~Z`J^F8*_uiN1+YNg; zwN2`1Eu5{}?XKb;`ttt=rF@3_o5nFl;-WlBGvjuzB?+N}aeb1jK zC;j!=uxp`PAjxXyq-z)fy&Zj0$2|i_sxc%X)OLnaqx}?^m zS_^|dTwd}0jn!w=kc%gLHg(#8@!kWgj)*>!*0$yQLps##d%eQRUCk?83!L)C@w!2A z&EmREUDM*JsN3tSpWN}}FXqx!7Im53s>Q1pj*R;G^>VMz|0`osPrY@x_mujr>hG^w zvg-1x#mau4weIHZ-G6;QuELV%&YaF1+-&XewZ(#;KUi<~BCif!b!Km$-9Irf>DhI$ zQ@r1sf4o9K`bWCii%*v6^m7Yc17$8`(gI8#U|_@I(5R~q17fF z+!Nk4ZHFbidCuT!k1Y>+t5!-E1nPwG5r@Q@8d&JP)RB5gsS{yX1A3l=T-Zo!~;8a_Sk z=>ywNZ98h+`r6s2|5~zm+YDW)r|T|xZ~wBV-pT5a97ux)=Bf+O1mzteSHt;Ku4-}}cif4sXZaQ(oE9Zpwm z{&e$KZI5e4B~4B`a_itPi*NtY^~d^SbAtEJtUYsIT=i{D=e*w5xZ7*Nuwub&f_vI} zwAguV)x1V?Vq49PT`<2!onzmhZDxLU$6J?H_I`Hvf~gD6-8@z8&&64bjf*F~^udOV z4_+^`B+hoF*0G@S%U5)|UHpS~TJJ$)UR@Zp@J#Smi#p~cw(lg(&i>jbc)+SN znP*<9^xTsxYmWcur`t)@tZ%>8;bvyziaj&8WIo^M%SJ2mz8s%=?4Rymjvu|~Bf3v|e8PD9V5gWB+!>{{y@84>lH>+;!^>tY~d#^#?-fS_k-0pJ4 ztAEqxWX`d-O#a6Yp8K)X?EY`W{&pa#e#)esrB^@oMP}{mr}{Vk`bN|AuYcO^f3Eqt z;Fz3FS)F$73mSg?^U^m;UQb;4=|3;bNb^73HSWCwD|&8P`$qQ}lLk(zvmmKV|66_K zFyFwW@AW-CeWCk~kSXI6_ihVb8vI05-#KHfrCvMKRd?;3oX^{>Z{j_*`?ZvrBAoLH)38l zzvla&_=Q@)%Di&A{*+cvv^uRD*x}8`2M(J2_|0eE9#<`=)sQaL zD)iVE)bo>%!jI0d&TevWNax!TA4dEb@k2z`;@)+>GJN->uG8ZA-^_SBEb-Tv{3l z=Dh}$?smKXcs2I#ml{?(zwMhv2?qz2=x6!)ncC({y~nk`wy5)r&IgCzIBLG} zTD28TyB+Oz>(8W(VN1Bpws3HTUn<;M*K@dc&&4ah|LU8l(PQVAX>hR4kKg-0 z*1!G8U(3`Dtn+<~m6p+6M?X8Lf1Ot~Yj+)-Q=vop+QnbaSU6+jk101ZI|S94`0MRg z4!yMR=a+sj5t?4HMW>gFjUHO6$IL@7Jo|jVpnA`rcz(`^fg{S&+v@7pEq7b9YwWkP zgJ$N=Y+0twq4S64tZUOh=hY{FE&JWUPA5AZZ*`;9-jBj+zPD>>XxY#HtXAV(jn8{E znHHyi@yE%}-ONjWDZTm6pA71^@N7-__b%;I|9<~juPd{k+xywxz3r0+oNV!JLY!YU zvNz>~l4}wrY30|Fl-)y;{%%iy$4XK(#lx0T96jlzB(<@8y0L2?3au~auU-1*>4mAY zmd!epb;3HPN&hkLMkboyJ2~aCnDgf=AFuIy!+wW)cDcT?cE4>Q;iJO4TnNegq@vt& zd;hN6roFL0=kti(Yc79TW$e>Nd&?THy{~`$s|qa^{qcF!6F0wU5j^$sGnRQz?J?!T1-+ra> z%iSL}t+u>#`rPo@f7UzuN0YZt#eTk5no{eP-9dYsZVu|!t;RD0kD6EZI<#xf&!6|S z{PXtpb<^iu8uj(DX-76SoaPrWeC8LmH|_i(eq`D2I>rAsZCLbpywu z_l@q6F=5Eh5g%Oa*F-aA;Aewgsk$fm%3>i0 zc|CjJM;}MOQz`q40fTBctki4%rlnOko%Y+18hLTe@Hc8`-rD)mqTg#QUi5M3wVA!% zZCyV5?S#Rv-s;-+xj`c)JU2pWx@pnM%k#EZ*pPK|`t$M0>#RO)>No$V+H<4Iy!6%7 z-D65kc&^5tmcw(T0ml=!t{<|g(&pn=zdZWwle;?J%&T!Jar=AYKYlxWW52Bz`Ymd- zY*KjC-#up!t~NMjPW?WSPZ3*4&Z%^iMta zZ)|sNPjbsY_O+14lMvj#c+=n4ca$7bV@;JBmSFAQ9rQy2%Pq{gFd?w*^u-??tGA$J z^L8;^&ac1rRMN@DwPHrq&NE-270P(;r)p(q&%SbW`lx6BST_F?-=kHE58Jp^bMuVf zzLnMMMAiCj&$?~3JG@~n6FK;gw}1b3=;CLp*WZw5skWo(FJo)nSh;lYXEV0_yrBCZ znO<`bXf|9kdL4c|+_zfy@2+PQ-*dQ2z!M*uUpvvTO#6AZB>!8_oZWHqLc_5(!-!R7 zQ|7$gaPaNP+ctl8&AZX18C$wF81VS|Ya?5AI}$Ee`|0CfuISPajj6t>&IeuIIr)3l zgv(bZ?_ToP5PSQma-TI=(!YP%OC#fRd=6eaKDLQ|M~Qw}Ge>{Z@VigLzP<3-v<_$L z9saG==PNUf#%f#Y?%mj`-iO1!{JdJLmeXslJF>O%hI;keyxT(#y*+1M;LtsxPu6;V z-Inn0y!__08_;9g7sm%{s(m!K=Bf3Qp84dgPph!^kNTwi^;v0YbGwoKzR>(#wdBV; zd-nOnr{(DSIr@E3Q_n0t)_quXsMp3`wztpzGvU^d8qZ|D_SVzeLytr>srSl)-Fb)l zjXq$WZ(4PDcUN1n4?48y^z6++&yE>V?)&s3zw8_K%+9e--)c*J*i1K`ZRb5{My%hV z*~{{IxYG3ZZw!()rTb5=xM<&g5=7G0ais&+g*0rWgeDq#P2c&{tNqWvc9;0WtK>K% z|M&64f0zF&PNUyB|L03^d&>VQ*FDA`?%d=d#2+U9vwyFR44e5F&TpFaF(L+@V?9h+D6=!Rj#hOHX* zcE>ZF9((MuN(Z`Uy!THxugGVgeRg5jUF#}*_e{^8)qZX5J+l3b8sGKm)hlbkii;O7 zW-Nbq*REZQpBOf>uYKyeTPt2$eX9Gr?G~Ir_}$XKuI(sSuH2y-b*E3Ceyu{~nz!bB zP-gd>wvR7fym;TfeHD(CAMwl4kt0WL+qUhovK4-P?73Oywjb}G{?bb?QTFTc6U!R+ zutaaF-?nYr6)RR`Uprx*v8YLtCab0`qvYB35=Vc1Y0$K(zrOkAoBf;J`nJWsT}O64 zS8mLP4I7M&w+$LJXx@k>uYG^4caNH#dhQ(Dt=Zu&4ZF^{;`QEkN^KjT6SLF--ci45r+{}_bS~L1M%Urpm8v9#-w=m3u$>>Yby*>fEUH&#rT4pKQFh%*>lVEdHn5(!CeTefR77 z&L19YJ><}?=h{E_A2wCoaHaRfVV!2yI3M)u#bM<>iv0PR zrxSN~Xuqyw+3E+&eNv`+_3C3clqxsm#NsPwN@wRynz*lXWPPv3Z&sM`{f3!WDt^`t$*S3_a@n%)m#O;rwbfZq z4SH$ApGSWEsF<-;<)43EqdPTe%8E79$IhNTyG7f)lV@I;^2zz55hdR+EP1`*sT|X5 zDY@?r8ngMIy-n-XsdMSlr90&RN=~ns*W+qa%KuG2UTfln*;n?&OgP{5TJj$sR;eBI zxzCP|U-XOY;qyt8O{<4%OMWqBMb`NFZF|bKQ_5T$7?Rp}wf_t8x4MM<7SLhayVgzf zGgdyC@cyZf2gdoNez9p!`5tGkUisFq#G3XHRC^+M$~*=Q^Cv z{&SXBy*;mgkqK3;a3J(Sj1(max4wqJjG zs{EhZ+kJ7a*vHb@_@zB#HBD0d+CH}F+{cY)mGk-d`|$BW17}>6uGP41|L4s5mpAOl zY_56gQrFs(o^7$@MQsg3=vM1XEhc)(OYkHPAIY=Os1V*L|n&-t6U#v))a;RH{kfgveqC`XW|QG<}C} zaj!|2iYI3uHuauhkamQO-#Dp#r67He3FD`E+4qGGoUnJ&jZ&MR4=+FQ_LZx>I*jUG z!B(=t`ZBp8huiG@;@vJQO6G-}J@8|E{Q)bk#`45 zxV2*V3#Hrq@L|ZnEeS_=w0>-MM(>^TBjPg?um7;2Q$1h*r;>7g$2=a{_2UH*=1*t! z3-4X8-`2zL*GgWRU9r~>ORxCmonFykd1jm20SSL}2~6#jQv0il$453Dek`QkEoowl z*DEFWFIlltX2qSO^Du8UR?&miRPnK%;4GprEG11`)bllF#?3jb0js5J$2Zm|K z$3#;7{ z#zCD2>^L{zr|Df|qZ+n(>d6TMb=@0f<%U+eu+OK`b(7EViz#hPvyQE;7~1c$xzV-$ z`-0Zjzml!%Qt|S*W1*i*RVJ2h93rjCIJWj^!=0tS$o}lHeL+JT>7{x5BcBM z?XkV#x%J00b~e4`lbn2@e7)bUJ|X!&*1BOc0;zILtz2?4zx8SoRqhDK$#_ z;)T%Uh>DHYho`Q;e7<7F_V|=N=R&2SHOmk9rnl|-vp@O_P3=13uW<)QbvFh0yyjz>q*2?oj=08(wMB|k6qXJLt>~;RVfhWgjyf^lFb5_Hb$B)Uo zy1ZfN@{?Ek{XQYD`1Q9^=e4=9X2R8uPban*9QO9f`JIlAySjhK(uG4BK2`fl!vlR1 z$4;31OvJ=1O{@I2W$2f0M)~Gtev+j*KJV;L*GIm;Zd8?iX{Q6r8W%q9`{SS4H#fgH z=laZZFCUg&Qe1zP+WvQU>${sKI_#e{RLy0Uq9qMwlKiB2s_!yOsZx?8OE&tvgKE1X z$D=E@6$~(nU^_4|RpeuH1ptM~=KMC|}l={;DxvYmMYG-z?@AvHV4t!Gio^8VfNK0!)0G5<)5G++0kvLDaAr8Jq}+uhtz)E3l^J~pyhXdQp!XjZtQr*cDe8DbD!3nJ(< z9J7vL>$9KwO{9K|1Ow&-Y$npT1o~{D8CnQ;X1cbJ{-RP6Jc4i0bQK(Ak-X`<@S^8TYM)L&k#sGJysr$#lXhyIOZU8qy2FS=qltfXv_|_0UL9RY zAZ#rsECe!$UGY1KMnV=jn*M?+Fb;64y|${JSNnwy`4OIK4ztVC z!t6R*Qs2OsXpJt(mXK-G#kg-J;1A&xZxZq?*m9q?{ZFFtPc-#1b$$y!RYmtu`Y^@0()@%LgvMVS0LcYhFtm-o;WTdQ!W`15$`u{`LeB(17m_LIItF>wV=el;}2FHP&1?%$jSA4tt$-GTzVc6=Re$u3m^ZARhMgwddtsG4RScmW#D!>=epd7upkFmql`ME*=W((D_A|Y0*)#^=V z++Yi6Gp@8I>GiQ%)wL)CThtf_7Gn;?*bF+GpUtWk~7@_%@G(`DEvwu?Ex)?9PVkbt3v`1U<4b176YfZ2a-w}x=MeA}|A67m< zU!%=vw&r>mqmRXIx1?Kh?_n%cw8mt=`)D?^#-a-@pg&*Vd|U~R-YHo3GlU0y@< zp#y&MQDZ8g8;$}J5`K>LFk4K12D>@Q5N)<#1jl8KF*wznzjVsueEB@Juv??@8>p7l z*W;4j5p*nNW&#OCvsKVo{#OT)IlkX@lAHiQ5=k*323jzSPL7}zmxD+U$0+m7pD82R zI}+(?&l6%i>Q17JG{=P*jCRIQ`UcJ*n~7qRh-hsVoxu)WX(Ey3w$Pp#fO_JjoNmTU#p$XhPA>sKeHi{@M|$8VQP#p(ILB~iw=vlYW#>am=>hwQA}_$(M4-d zCP$KAtgotFq%qg%=e!^;vI6i}>}*O-BoBnrt>}Vpab2y_;oR^DHd|$yja>Jwx#46I zl0KKiElJEEaF`o5xo;BDV_*zfE#?>!+Q!^4!?eH{Uu~K;M$Ul+kb{Qh++|Kxh)VFZ;*^Jq=IK62-|0c7x3pD9DUor-dfP%J1as(g=1jk}N!{CQvIU zSnYP=;{+p(#J`ygxw-5c<`!<&@F|*e<-TNM1nUBca%9GyfomfT3EsgP3n{XsjzJ;# z>Fj8oBd}hYG76daf>Vqh}gxXjMZ9*PCgLkAa18xnX(~% zA|lRzfS$O^?9x+7HRsQbC|6efb}pz_lPm7$h8x3z6~JZQFi@YhS~lhe#%!EMriyl> zQLpt)i(#{pZJLC@m;@35#xN2iWXQytbipZFeNa%Gwr+#ikUrXw%%BEZ$J3-;ojePJ z4MLvzBBZ-*cB`4Jd0UP)%U7${=ddnqIs73%UH$`pphA)YC8Bj!KRk&sW9AZ-n}}@r z|0cGEMEW^qI4HCL^of&7KF!dzyWzFtxB&bVgjvS|Qa=wyD-K{)klh1pcU*G;8TxrJ z*!VgV0y#a$=(XCfNo2t$4qVI0nJ&d@zgC8Hng^eW<9We-`ZUnL?CFoHn|p^nUnV#~ znGd=P`?)`#1oO>=E#@-XSI>?emr`^%** z>2icc-C2m$V3s3Nt@2h~g2rmFS?PI#)-x|TFq6+w`QM#TSBRK&ECA^fEAc#+j0|gQ zNi!KSm}EXd(fH=Shla ztWj2t#!3c^u`fMIdfH?lfgTf*d5urLl+oBwgE5|pfRND zWRr24)jo;7c7qm@9ae?XA@7nmv_*%>DOy>7_ouY7R!$KXgQ_!t$NQqq2f1`#)SYyaFk|oV7R$L>}w$A&?-`5er+H{O_Fj@DRVbNFgE5;cK4 zRaS^iugz%S!gUq!z{7q_+l%itL>wUHP2ED^6 zagdU$5ntfMxq}57Q!m@UWHATl+X7=;SzVdU+;Es9>cJemsIsG&E7jS_j&ekJ@4fZvfr99cPB4>n zu|#X+sA#)g&g8C*S1K`WJgIlHtRZKaqIi%$G@8FmGL(d@3~v@89d&;+f}?$a(Lmxj zjLE6k(&HkC9CF3I(pEENd%W4bW`@27*9sSqo_M1;SB*{mZMEX>SQY_BSct; zV`JrF1YKs5r$&@9a4k(fVEX&x+2(ZG)P-T*XRzjegW@&Ju z#B_p*fU%q8FoU-heQ3xgin7XN+8Swv0l3E67s8t`ZnQZbF_=ttk~Fwxi*<4%xiOq0 zyh+Y4WaHn-GA+Q|=VYp$`#q4XF!mi}8ek{UnT0|nYB@W}`VqLGl?*NI%f0Jd226=Wtp8m*Fb8sak`W3x3ORR?h`lfwZZ)H78PCbbP_uu^LP!s%QrsS11|LAIQcPLOE~ zq$sH=R@N~&ier0ldH&PuxRrQ{<{oAxmZWIp0X42DJwkq%9A&lIPAMren=ayA@QTAR&NP`Qb4OFNf$i1!!*u#YlYokXR}5~1`e>Dyx zHI{V7h>V9#CWdJyUlSl%45)b^#LFZStR#ZXW|HD0W@Qo(UW-(e_;EC0bT8d1L94$zXKqFU(&Kv*@h;3>eZ? z8p>J9pjxJvY!-s<-q}eBI=V(BgR(M$3?_Ieu_a8wj`$`g0~#}!3L9cJMw0UMCa%*^ zTocMsXX(p=2{Ot-KyTqWMaV{lp>RUf%WTy7(GsfHPRJ9e2a7k!GolxN8BzD~v@)b{ ztVq`|`H^n5dNZ?wbQK{1D#J!b1CqS?%)^!p1eHp$9x^8&9TSZ7IFy#gNS3jM^~H`l zfTch&Ej*4K6PaznM0rFJ>JB3<(UiQU$Qa!&ZPb-8$XmB=Wy655$+%(cZ#0vtA)Trt zf(;`LXGS+`!w5hc*V*==>A~ob4N(-Pkqy+pnQ2c)J7##vItxq*(z;}-P}YN>hv9)y zoQ)r{XRN_|b{3iF)&yIJq!MA-#sG-c*^S;-!URn=nv{Yem^65i$=$*r7*DK)l&_h> z3}ok8G`18a=pQq8>)Kp7(kgpft)vkxup>3xYBQ&@m9v3ElGH0o>Wi&;6bbN1){HDY z7@KBbjwk~1T=17{(a1*rJgEaFqt-eZP9RrmmdlJ^P(ZLv4vVHBY8XV3Rae5|kLdYr z(hx-44qGMIVxjm5+1g}nszGBmC()ykoCnYeC)O|v(H`0VRuWlE1d%AB>;;*SgiJT7)^snn8G_`Oee^>Ry)~|;RtfwQ#(tsssS-!)Ci!Q-J=SV&c`X3|I2+}Qgt1F0mATv}SO92BFHkjilsa>NqnO{3Ja zSU;d%4TU;dC2G~~=oJV=qkfjgaq5d>!IEq6ccU9-NB);mcP>R81reqG!I(=VK{Ls; zrrak~zawsj3SpZ}i4g?&^8;syk_p`1fz|%Ka1;4$w=6N2X|8hYPH9XJNsu^?Nsug; zY(+sy5GTwSiI6GHQDA06O?bpTs9w#_E%0w{X%0tbgQYw~VgvPHig_>JbC#pP#D-IM z?oV-`+^>to#$B4{XE_SUU5I)!)szZ&QprlNEQixmB;i~V8*X5^B|((_c$VXV5*u-a zo!D@g^w@h~^RrumEi8lJo*;a92@YRgb>&zn*91p_2;eH#V}ipI`xKPIh$EFC3i|KG z#JFd5tR@tfIFb{5o&+C{0yCcpjwgiyb7R&0p$r7d7p^M|1oGc`C{klZbeU`|1au-T z6o{egCWQFO31j7`gfGn=L8VMFr;s+OzO`u1X3U9G8G;YS@Br1MGpliv;3lfn`dkD=KFGjl-Q zr^bpBcX(?1(SWEKHn1L0i zMN*&pOB4t*r#iXMFu|CQ=7^by1*eW^&GoS~t8|^+LXitkp1fl>5jF)WUVM&g%voF4 zH-jnx5pyO+P|rX;GohH_;lodVkn!TQX?5>5o`V3{aAp`$`N7`*f+gTqiP=&YX)|?p z^2A)SP!G*Yaf7V@52KKbIjYj9L@C+(7OFxgPYx{pUozT+(WLS$llau{SeE9&p388t z+&omcP$^o0_MA?J6(`&L!$>Iew3tJ3J%j?5)vSIxzk((1Q3VULHSS{QdA#jv+o`1_ z)tayr?7ypE$@q7MUcp%pBqTRFh-@SCAy9h#KP+U?-_g!1aQ=6)^X|x21zCKQTz%*k zAB#ylVy%=N(AX#wL6!wCdVt`hhzpg~F?&NzQ?}?xTLPt1Sa<|k{V1yb(*?79zldRn ziINGD!fYQPtNLJZU=$OmyVF?xXnU{$Tt*w3j>eiq+c9aY5XF==vKMW@v?KmR#SJ!_Q!ZV3XyLfT z1F{!J=Z}Yo$N5FN%|~;->j5;%50LE(TgxJ+Bb-e+9ZHT{X-jdmjWRXfIx?V;x*Uht zBxTKM6&&E(oXl-UD2orQFV)|4VjWficU%@YWxI#p`ObGJFM#gJ}fP3f>aoToj8U zYUh$mw&B68x|+<>0&P!T-N>V?umfByS*K*xUERg}2^C2|(#2BEcodR+SRqL=UL&LF zj04)7V!ofk>$HDowidjFFd!mmBf^pouk89?7wl;j_9l~(s<+iTGm_$7Y*#Gbi>I#W zrihQRvA%5TX6C6QJg9S!5Coj}L=O`PjpV4YI#lft1; zzI4I>oswateIvp|rwFal&i0D}9-GXnveB7|O6n*8>}0{JFiIs@*n3C@eIix9usyj# z={vkds^N3Y0;Mw;om;-@dGatbpm8!3m}5c}b<9K+y-*_EVD~0W98lX7>fVqfCYWi4 zgf6nMX$EAmKS(hx@n(a#)k5{AYbcm2JCQov0J5(sG7dCQ(GL|+Q^}((RnDc5Fqni2 z%XkW%KS~G#Slo$I15fnAb`-F5dl+>_0c9$Gp&}$!+(_khyqjDjD~C*{LtO|KGL-=U zxoN3Rr8*RL<{5a_14@ozSLa%&M+!-&-=>2=$c3URZ|2dU^d7ZP-jpK$FXxp1-!-b^ zId#ZhCfc=zeX(-Oj;qNg>?|{L8^v(&iFtV^E|6C&GY=(3nBM#>}T3A z^&p~|47dD<39REp1MP!S<5|ggKytG%H$sxcg)Bx*VP2KmWN|AV03j_JJCAK2a4rRh zwaSz#mHopCgpudawy_j(hQ&>A=Sv4dcV;kh0Ol{gmn!1vLxs#|eGK#{s z`8K1<4>+I%Pb10MrHU;wd)WpB7Yl$!C_(X5GDRthP1vAACCp@-)@GY6{pT0F?Y>-A}I!d`c-%%o-E)(;XC zv_e3HWAx1Ut%Wc$)j;Hrte!5MDFdYiXjtS$(#b_I(R>Z$Juz2~Trw(+plv9$da51A zNOiEXSev>tmbhWCBd)JP8}qPe_Y`e^GA3Z}6(fWPKxJIUYDbMlgP6HAhe$ez)Oqtx zz`{hCP{&5@V@Lo&M>(o_fX;l+2OC*b+~l!n$iY{9w=}5PCI{2r0vmIO=&TfG^D@qY z$AS%F7GR9!aGX3JgPmL)G-WNx8Kdk0n2#1570I*`*qp&INJk|kL{mW#7KUG;b%+jg z!ncL?{IK)HSPQDsBbV5zMg!uItiOj>s-YZk#p4CaF;|Z7MR%~#|GWNIME+*yOb34n z|BHKOU|u?`$83a1&2`j?IS~@k9PKqzs?)IlOmGE{s-r%N?dpHzmr-4fQ<&xcvJ3hQ zE$JU#j0Qm@@cHl4C3NhJ2fvJts42)VQ|Lv=;|I=}k_x6|%FK zxGI#iNqEFA(}r#5ZbmVL7LN>Bkdb9jUvMNu{FJDo-c z#=x+paxGn73yv9paYm)-Y-6O$IfuM6zN?oup~Avb6!QTJo14Tr7b-Yyl$_DffhLHT zJ1K8CfZWzb%-v=sE0Xqx2Z3 z&rr>pA(wzx_ecGc`3@%$^;hs<2+cU3g^U(DNe1h{=Ro&zX(ENCuGgS|$;Eqh2qNxM z{BLbS*{3%C>z2YlDkT}^1JfX&->bNYy?o>S& zsWH&5az93{_K#U3muOKu7i% zi>C`2w2HhVZ&6{A4W~QM-sX7c$E|1M( z-SX{Fb+Qq5VYbahWcyThh-zyUkiOeD^ZZS<$@ zR#rL=3HO;Rz;+|ZzT&{82Q)TSq|wb_CX?O8+7xJD=H?@7^FMh6exi|%NM##MBIt}T zKe-znqRTxbI@ub;#NHu-4-TI*h^n2O{3ZB2`5S8|dqpgKWM4j5%#~jBQ^>V2bv@db zbf9`c_NC{&%1$-HY3jq7`O6Uf@bWJ@Ee7=$W(&QUka9d$9`sU6t^2f1^U+cCw(06e zw&{P}HmzF-w(0$muJftka(Gdk!|?zHs=^)*XrLB^SvBh_2CDPbdwVm{ec@d_z+Fw= zx`k=-=415Xn!MPN%A%z_aSul1CD>F&BdVcMX-r{GJDt9Uqcx3qXbwXX^5!&QH zvBX4!H;;BYZr~~fddZxn1J_}$VFV-9dQ-`LbQqP?*evP}>7-RMNAVbiQpx&toc+y? zg`^#v?7V%t$v|axCm+UX8>!Fd@^31J0|2O-$xH9WX}eUAl^4(KAYI z)Ib@ywPDruHBvYXl|~0xeL@07rl~3$$9q#XHl__@gl*GAQMTi@(doz$r)WB}Y#z)c zR+A13NK+PD4Lcpan$GJm=))tkm=-DN9#8vv$meLxvs&39JrNc<(Dac(OGhxKk_U|4 zBLsrKTm!D+x=j{@q!F{W?+xUo}+OjK5;ZVRfhzDQZxu@;-vp@{wq4; z2j_d}i$*y>d)8_DIETn!X8@3pBpBJzMb3e7oQ%tY=8nTr@1^SKfA*S!2T1=kh|9>p zQ9g0Lz7Bop*n#rzt2OlGB)FGr#F0bc!F5oR1m6+Y2Ss2H9G+I@RcJ0i2Hqg#ICL*A zOZ7G(yxN%;f3g1`9l8f+ju^fW58b<1r)7|)_gg1^bmafV+lBsRaXlTq6>N<1@Y+o3 z?@qB^CvVoNegpMIevW`3z5W(;jDC6y@^B7oaT11int|KdVJb$sn=_D=|30IP)vnd` z1!7dq8j366&gyry*Ga4tUVVl#Ya#Xu1B2+R@apr|&W*=GeNKRhLrYLGi*si7nMA~N+&HZ3hGI5v(s$;|Eq}8e6tE!5mPqUgCeHP@x3Wx z+7%s%*GxQU?PP;EF}0%C#d?=F<~>rzg|CbsRQVS4&gy@ye4`wc`hTHVQpF4pq;CsC zqFRc&>04)xzPrlR7plq?QlKzYuDY+nRJn8xJl?vh#v-eOBFBp!ByO=ct+h{LB|RahpNvMDnduX zQ@jfNUBFALy0ODnNUpKu1AD6#uSQlK>8PF{ojt=|EyX;LbiBciYFhC+6e^%eML1GO zbkw-Oi{fvHBA0}Yalw({>dyE{FHVEsz6Z%!bVngNam=GCXjK(7MQ7wS{0x0wzqE|M zd{=1e|3L9FojJu`kr=4wnojSunsT_^=$f<;zP!@`SL{toj<>mXI`a)?0{1 zIv#SoXoI3O2x7XQr0sZhlg*rD*V2{_IwVUk+oLRc$J>q648dZY4i)ScV7Plw!L`nX zb$4G8tGd3vdS!!QL2m+b1M^;92gKjh<)~U>FKTw1IkF5WT@plI(MpzoMabQ@h4^v@OqppA_k(HC_){BmEXv`lo;?R&sBC7DH zEXz0l7|kLSQC@bc6>}=|z87WaORv5X$9Xt1sH~PFK0YGRsVt0j79a1G=r|@deqlr+ zo}}_&ZtuI1993qijehVI$Yfs_8OL446(MhTZgU#EWgf7?L;k=aDaVS0_hZ zxb#rsigCW${I^;bA?bt|G(mJ~i<=gKO;aH8_{L_rYNUnnLCn#bWHp*`42)Zck#y=R zU8nR$f+d(K{r_VnRw+Jb#VAZw8YO+v0 zE^Tn}qV4E(coV&k%;60QQ@+6XwHsYI#j`2n;@Y)17xht)vGC$VadBx6!-FpqwpGVYjAb#%M-r&;B+$c& zGZrj?n$gy7tA^9jeFz_-6?geVDSOB3w4Hi=BzFvjZ%${is^p?}y6zA;bXsDMQb+G> z8&MS6akRP{<#4G3L(6EhMN>4JRf5oKnMgS@VZ@@_vW+&LlCFFdJuF21)xbCZVE7`m zfbMKt-%SGPW7AxWY&QxDutuA0l&h85-V9-SGunYYF3f1f#D1j13Qvcv&dB(m27r{K zl|0HJk;L#+{ozJP4h5!kd0*NqJjY^pDhj#FvGjBsZ<$jyacPJ-%IzOg7 zq&yOye-?hYIad@inJAn}!Zk@tl?$rhwGP(zK(QA2~)RzS_tbCJn_qv!TkvOmD5gAq~1* zBOMnOO>bFXuh!F8gN?L#Swj^eP-+I+|4hIe5=aA5BXaj0rA(AmrMA33+|}f=w=yv8 zhoVZnCY)aCKyT0PYe?{?l5={4wtuX=gw8;~yYMU~>WubI!)}i@M{69R29#{lO419Y z{i$lrn2>*NM6}NF%JWFPi(BVSM?u)>MBDI4jaX#m6TP32-Zvc>Q}FRYlh_HvIcuz8 zRGW}!jU_{J+HJhudTT(SJ}ym5XV)!L21I3OO;KQar>?QZjJ@PB2T04YQe7#9V{xh) z8-Y4y)q`?8zj)96y6v#683vQ((!9TGWwG>mhf&*l&X@>V&KmRx_w$5e9|`q@^^;cuo?Nuq zk-tlo%=BZDlB68^NoPN?l0mZ3H4FW3m13m?`V>ZYOVhV>x@M=pXl0c&^esoyN?DRG z{jaB=9LY$}8l+&li+k#x{3M-+9{i+Kx@wSeS$}>~2`QRA=a=7SEWx6q9tTR>*)NAf zmf~rABSD9eWN8xhftCr>pDZ<_&vwb5KI!SMng06GZwOt*wbC@Bbov`Xe{mJ#gaoz6zS;C zLU7<3R!}B8Iaug<^d3cj?err{nRG{?0xz1Qfu67u-Yt%CW%`a;V71kF;Ijax z@jRdclz=OjevI_lNPPg094_aP0_ZL%2z>*W0)w%%ZsTZ;&=1F)b34$5ndS$4d#l2y!SRt4kA^a-P^r8ZaxP6eO=3w;-3V-4}y$lwHK`I-ZpGNU!j2zP;Fj0cSK7JO`6 z1H7(l>b&ym^(zQFYowM^0QJQojHJ1U`T4W{6{?D25CK9ybI1YA5>R8Fz#?Ym3Ap0*PP1hpCIIp6!g=7HSLZ*Qp?KCpDJ(|JG*N@XUW(xU$ z(fMkCn!uTydaTkk#4S>TF~B4(-PVgV1A zW}HXdG0Hhfp%q9uPRm#uF$>Rl=W@qYKPW2>+0WM*eFFQSDPRnr!Hb|6PIY(!R0vAN zC(IDnIIc~E9Sy@7C=}NLCtpc?bA>^)!&96a1cnsK;oP3UMiRY-)2iIN7KaRm#xM|M z;3^Gi>D%-f8pJ9ElD^bRY4j5(HDDSY-+ZMEX)ZymU@4QWyDWLJx$>1&(|kVNxJc-~qjIKEkZg z9z2WtkY1c8p|io|(9BpJE*oTep7Rpt%or(%T8GgO@CC}mvsr|n7)Bc$YEXGELe6oG0g1!m6O_R@ z99qamt@zwQMOHRLp*3`L51J8Ab6JF^)vF>)_eWja!ni*hF^J(Wh~O|0B;XlPLkap8 z>_T-RZ0$U)J*di$^#G0G%6V98@T7&=1sofoFyJAQ{(=+6(`R5EqvHw1b`tajsbi%l z(Hpk}xDMd>6LJzV8`6}^II&Km570p{Q3I!C+y(9v=ohOkRuTM(D?%cO);4NsQC*KD zY+(-KzRc2wYfyV*kV?YZjc&~qkKiV zR<$`bU=pJo!tR_Hceg+Ae&?QBm3({;#dScrg2 zu^NCwZdY>tF*4`{WPBz09WHx0w*XE~spwCUvw#h>$7vXM0I$DFuXQ1h3b<6mCv1CQ z7_b9dOcs)ClU-gHUkcij#dOd zHEe-qG?piCTa7=gJLp^OPe%|OkbIDF%9@}@XU@PCSM3IS1f0ZISc!dfB>j*>y()YK zdq`L;5d?vSzJot(j@jTld=OX<(eyuhyq(`-E3L4=BIE?8Y|zZT^aBigUTN-yJJU6! zFV-DaLISl@R>(jw2>#$!3HKx+MF6poszS~yy3626U@W=|JJGc+f@HK%1<9-dNPA&%_0Wm(TGteM(I_BvNC*%Ov zJg)10=g1c*Oha6S|M^PhFsf5V%3(YiMR#G>!G`0sGR?(;$t377CmyL1GEYs>u~IiS zPA%d{d3Lu__ zXM`QHpQ3zT^bOpKzPyMk`>-(zf2w+yF(FW~ zvlZej1q4MZv%5cOiT;Jv0O{`|5M0Q^vJab zB(0bS=n9q(mo!*uL8*`cTn~u2EVrg1rMaAeq~Nkev4DX+P%@WMevA^}Kf&td8b$Q) z?%@M_+)fr6hT}$X4y<3y3|avr@Ui(wI{F3)1Z;`t3&s|oweUT;#CLsK^a#8e2$xpI z#fT+ie&9i19(3+SBSTw3N=N47mdk!F1Kry!q%24xlaOBd^HKMMI2&Ihr61)<-da72 zJGJFeF@ z^L-}a5RfST#~RiG@+v*wnl5a*s7v@ltk~C$F4S{y*2ao{C=6&W*7@YymG$}#{L_*D z2NeQWfdrW+=-}lRgv-K!=WgQ*tAJa@kS2fz)McRGRHCd%`YS9J!9yHkXGtq)fy*ys zh^<5)Jgy1(#x)!w`_NL{djPju=^KwT32)84cfi9n9B5ugztF$nJH_r7uwll|qwt8B zkc{AmTxLTkS_=6%t}#HLsBr+i&Kk}@^v-=@5f$Swf&KwsOZw$w!;b}w+_#FQ>wHE6 zLkhRR%H*CE^d0yG@fyflQSX7)YTAV!R{J*?*>%Pu5@#R?(5G6G^Vk(uSUpc2J>?lj zjL&TlSY5z)!Fa|!=vr?kIf9u6_1vq_+I;o7b`q-(s3A3gL9X|>{)2xfYzr>iK&{+9 zKwAZO_{OOPGz@GbK7$!@f2U}qbp!G>R=dd^;LlmF3at&u`I?KlI$N8ZBb6AVkfofz zKqH76Vcs^v4D2i1;eGi#vMuTu7$USvJ}jNt0-o{7Eyh$vhujO~D+`WRj|S_NkIS*0 zOQZAq+)qrWcpRt)asu`jtQ55+!*v6vD9ps0>0x2LD|SEXkRV05cMF>i9EOM_&-H*R zo60iz&*%jd4@(4Bh2;m171sp)s<|qbyzyL;CGL@3;1pH>s9!)<_;T5o<$%@sU@n)P zVH6PzV6kwuma~>{hXz`zD7eObBtCEF8Ue^ep63@41?T){Q9#DEwYwd`d4uyBm+;&l z;+zcHLS)KWW-HVsB(n3ma@vcP=FzI6t&%|zGFPENP`4uAgye9R6_7dbM}!{Z6t83Q zSY#=n&G;+~MEUrn#Ho32N<9hh4>L=ZR?`1~0ohIPe-!Igz~L;xxa?P32|SjBo@xQ*1sp;aLK;Gz z8t4}}8u$jNbAx4omZB#iL7Z2`c@2t+CexUParb#ZS%Nau(o%TqTta(>Jd>m!^4Eng z*WjI@(#4aP1}qDE3H%Mu6m+Cu8I^`u4McM>C*&Nka-bbPA8xa8>y+oOkvm7vI$D3F z!a&d^f1Xn=#zIfRmq$y1AFRE59cR&C2(>?+;&eh*@RbFIz;6i-npX1%$6_So9l<{V zRO^~ZGZJendh5fmD0m(|8?YfF>4iVTIGP>2b#=6_fg@+*Y8hcAz^|U9Bd9`Cz&#;3 zA)Cbt3W9t9O5uC36D z=QUMJRnM!cL=}`ZMO>UqB+jkatH3q7urOTd3p_7q1719$d4d+XB> z5CgT+Y`FKyc^f>chJkxjfQfS-@9|Es;8|!HNL7VnJ>v+{4sr=zDY#DfQ{WjPuhmus zpnzmSJ1##k3a+|hMU4}#H5IMOZ6Cl1y`iDD3XgK>2sw)^45StJ=b#^eJ&q+&q3ufh zu=hl61GXLdRU{GQ6Z(K$6VSlwL#$rN6Fdzm2C1yHMN8BqswD|vgN)}D!y@Y@`T^Xo zI6->_tH3&^8zJ5AB%u@v6WHW1#ZsSJX^z5@;d2&N4W}=xk0Rj^eO4$G`a|HB_W>ND zS42y>r7bY%itVDX)?kUa%RRs&q^1Aee7Tt9f;|KhreJad5>W#B_ZL4YHk&jvL`(rDB2Aub9$HE^+LxO8;Kh=`{G zBSotjTp=nt&EPtc&>gotmDI4(L=B;ppo0X!Ga83H=YB5e8uofLafOgf zsJT#R6l21ogv}>YTZMmMlRI;lC+R9GwYcT#X?~DC0s|NWav3WlYQ_?2OlUmViSUvz z6VI!3cTN--cb=o*Z}2Fz4j_VT<8^${?$Bm{O|)0EzLL*IL>$@}cm!r3Ie@)x)CyI1 zYJK63yN4$OJY~{=V?q`EJ3 zl~P?I@H&}RHiG;@ATg^zQB)GyeI$-^QLn97m1_T3p)fALAiJz2r#O9a zc>*g+P#&&>`e14C3LIF8;Av2oVyn4656ef`G>GGRGx}A>T9g*zS@ev2GNKa+GzP{M zRuG34mE@2k&<&8UVFUwa2N}j~OUzFV7q>^boaSu=Wr&&aIU^PbUQ|X@FvU4fKqBDA zxULk({b*j10V=%Hta;81_CFw2TfdkK*M4}0j}A+V`wHBS7tlc#@@OblP2qj?3BAHC zFhne&tpKx#gaB4x3i6%Lny<9lgAZlLkl=~x~7;qS0Wl)RQks)9}f1m`= z!@WVkp$2uJ#tK}<^9*9%pju_Fz#xYLS{$EpsRhR?s2Z*C4N^kM@AEN~wJB_RK>ijL zHzHCQM>5=(MJoF+nunADl$;|AUuSq4zi4!Zr;+$faq<9%GAHpndMM3Yryh z$cMKc3a!Kc6Ep_d3CdHfM|F&x`xrc8fVe5Deg&@y2`Hkam>G}Yz&8ht@_GXWAKbo& zFE4ypeh+lZpXX8%w1Qd`E_Kn58YZDhz$;4aWYJPMY^W?pK1NO9h-&}}3;iI|RkU>O z3PsV;x$8)r&iO7D$VJ!}>i7(oeVopDu0)NMqM&il5Q4_L5q!u#3Z7I~`GJ-Sf)e^1 z5;cNkE;O=`_*|lMdjvgmZLRoX_apJS&JntA9MQP&*9GNtV^SPr3tb}U9r7Mya~}D( zdo6(>=gJz`qKK!0%TPmv@q```ypEUzau&c3>PMj81bnbQgfwy1uDnOI5wZ_*0r5F2 z&5}z4l^H+APDUmhq0r40h@uPX9-+l`u?pCUP{k#C z7C{hm3scBzwRKq7^bEfTH52YJL>>_|5PrB!V={|uN#{f)q{^ZpCPo>j-gmAsa( zhne5mCoYPaXLzgtlL;HKbO*m23!oivC};=xfX#y)@;q+ISDITx>b*xouOadTEdy$V z{y}U(m#<@l9TiWzd-|{^L`4**K8(oiK~Gvv&=*FSMq`M)A)t4qtZuYcB6}b#M`fLg z2EU$nv?%*9H&u6WAufG1H| z_Hb|nZQ@=>#%T~-qIX~j^u_l)^SU}gb&%tzIzrW`mC+W@j|=?4kA(jzCi0jik)O!_{i0 z3tVe|uqQ8;Xq3|;;-Ao?JPYY;cMD8#>l;rAZLVY|75^}i;S+6?%#xZ)L0hghVQT*6 zm;+p{bawai(uzXbaX-`@d(iUu6_i#u*h7RTfYF}79_PE&9`NDGrciSDg^vsD7u4r_ zEs$_L!uen}g#xi>o1(6VnS0WCN|5w{CzWg@?*W<< z)|EQf14%FB&}8yhO4Ijy>z8vNB!Ih2^2CFv8GyIQD+PJ{%!@(Ommq~Mh3qd1Dn*5- zd#&_?@#nqG#xw5%8h4zzT4MJ$Lebz0`xo71JD|p|LaTSPt`yorW)IX1y8%0@K#Rhz z<+%l=9#LEu_Pw6zWLG}+L}TD#WY9q4YWx)yWff%4!pev332sC^4Ybz1dXsmWLqYW9 z$+Gusf8MG0qM#*Mq#~lHmV1yI&T@}qt6&>Qk<`cyz=GuxLy3j(eR5pFD3k|ZN_Y*Z z|KeULNwdrmcgxXqp(x4(;RsY%$Dcjl*qZe77@~4 z=o?q*4^DN}8bbDaw&dMoI-YAbih|x&vn+|b?0OK6^bEacUW9+6#LAQ$qPq46@(mO$ zsFLTp5JltOKaX^RPT|YKtAK9-EeUTJ*CEdk(L)XlS|2suJXeKph`k7Z6H*A#P)J1Z zL;{mZs9ZsufyZc_v!GmNiAarTfmjMC1$tT?krq)yRD&a$%IngB8$gWK{`5TbKD=gp z1J^B~@6q%ZoQeCWIpXl}{2Y(A;j{@+{m-9;9OYgXFc8Vw!gEK@-i|wnuW+izEFmY5 zy+9R_kv;)>-Usps_>P)^bb?C6DVG({>i(!z)QN!KM2tI!=_KSo5eH7B-&mSIWXUwz zi@40ON?Xa(!8xpP#NArb!^l3=q`OK?RaCnRU-EvfCceVzoz6M=G?GJ5@%T5VZ!v}f zDTjxjzOJk=B#c<$`-KNS;@wV+i6d=+U(VUc4ROqfRa1CQ`Q&~I{?Xb=*DQ}E{(jn# z|Be{=Pt6pG|NAOAS!zfhYx-W1u1F)LI)rzL)`kB|k+w*WOIQ5{NNuE1QgXeIrI)1% zQu2TlTB9-2)dK?vws0xA-^c$SdrtxoRn_%?h-favD4O{Z!w8z1so9IgNp9xyGr(?t7%{u*!S3x*B1Aa=zoe3I5e{G*$)9j#dg#x= zM7Sj94uTCQ;eej-WjREcuO~uT0TCPwB&tkJ%x0eCmAMf?;EB0RNrXbkzsyVoIZyVK zwIxDdo?Ix~MTB8`veT`a2#@f@=r)N6dwCM*7D$A3JQ?8Dod_p+($CF61o)Yir-|85 z&N(P^iLgx0^-w-R1SL=8C0;~0%995x77(EaPp$+JA`qVR4ibpK@r3jJfe4!&h^uil z;f5NBgHcP&Y6F>X+)d1h1`=WHLd^5@ZehkMBD}>DH)8@3E<2E!#?J{iP)}AFQ;9Is zfjnZoNH{%DmK&!N;c=emjmL>FLC$qE#t~sTPj(xBB*Gq^95TL1gd_*@i}4l0t`#k^n{!Qr~o(^U=+YAfO!D-!|7@T ze1GT*0Ou{sXAV+b0bfOZ{Sn;5A@vs5)!JQZJHQ+iXBXs(1b`ed>LDI%@cZ0nO5X_) zD@*-ee)UZ8uV;hTzj}Jyn743!ps6Oo+*O;Z0cMLhC^^nsbT< zul8RGw3YSJT5DB_Yk;#ao@56BZAYqU{r0;#CbU%OR_6ywW<6PXvS;%xPd&G_aL}$< z*7H6H^|IAclx9=gQdQ6{RnS5)FssmZ#FokdOa=Ungf_$4Hl(F?zvtqiS~+6DcQIe( ztSjs0U)xG}*SMC#b&T!}EO!hzgX0QL<*slo#Qg=XX1E8WJAf6?t}>f#doR+)-dG*j zWAfVvM>jJlI=}n=x4upryEV2qkTOz#++uIwn&1xofoBG^OmY%_Ib!W%pjRSA%!0#L2PMuC~Vb-KB~>(7#xb-F;WH z)h#m;4(E;>l(f&#*d9PS+08wG)T#oQOJd+}mDt{qFn%jw*1!>lvr0q0hwRj~zZ~rR z`TmiWc4b{W+kM;69>Dq6a@2d<9L+b!#FiPE|G)AL!rB-1SN_n=@Y$^YMyzdY%ix&1 zxn*#Uh=DPPQlNBB#W^Aduu5nd9M27DnSn3=pLS=$3q8+vnf}bxp?kd=+A`MWRLlDN zHMphx2)0z{@;7=t|IMS9i*Iy)@Yr{w_h;VM_?EIZhH%Zn@#6<$hPDgZKdORJwG&1V zZM)ll#kY29rnCC~Cp@Z+HrL1WbWI!2lpG(g+8RDq}S63=)gnE@!>I`@6 zwdc0RrqtN@C?R7#XY8?){msoeQ`^>tE`1Jd_kL!mSFEiWCLHK)p0EqW{WE%Xa0gDN zJ3I%&{?kY78|)Jw;B=0Hei{XLJK^sbi0dxmis5ewdB+uP7khaHPn)CZJE7I)_V%`08D|A0 zwC`MKyI^Q7A80k4S+Q+V%N$z?TN!0IvE}JW2ItWd5sPoT!#}B&->ouD^r%iRw%;_r z^V%OwGaK5<6+Ly$A6uUC<{GAy{=5L!X zM9hph8Bq{1IAT)7%_qd$VA@{rc*fd;eu#y)zByQi;YJp&X+|Tw&Y*`zCWfECP5u{Hc%qH`f4WWn_F9_n z(~cC5U{K1x4GXtb|F$gLiN0R%FV?Re3%6B&4;H?%RlQeb9(J_;N*PUA&&st#Djy5? zV&V1Qrca%m95mfncy}uW#PUg(Q#*I@Ha>n^n=bsqqq!Yu$Vk?H7d@nywGiLuS$H^0 z>?H=J_M67SZM9z%3xC>1|GmP(ZMC14g$pd-T#NjZZ;IaA^X*6k z#Kr;L+94T*ekQ>}--yZ0mCgBA$pxUC*^yq{0PYC)5+{{kf~W1i^PQ5?)x+7HV$SFN z_9ms{5b}XMN~FJ8?r$FldJa-}h7vujjRaO$J#hwzD87-F?;JLj}AUB8y;XT~sTs)+^6P`mL-WY(+7+(qRK{PYK5P%L!IcEg; z?KPgnw(v0pNe9mCWh;!1>~z%HA9I zux9LuG=AO(xWY)#+6l^!7@r(H7Gt$rF)1~xUGe(;!ATp7J9H>sZ#bTsv>;We0tRHIr^qSdj z^u||O>Pfnzpu5Am)IX%6+?(@P!#e`)IUyegJ#kclb!FuEj}mwC$ag^>0X<<_L0bqY z|0@&k#=^cAc|mjGbWsBDqou^2uz$uo8BkL90Cz?WH1cAoO^$fi6LP@xTEMA07)~qb zOD_P1AiWz4Ib;Ct9iS}cimAK<>Bd3oLtxK61WtMT;H_LpuLa~;3~#Q7=V-Va1$*;i z2oI_W*TFM#*XqV!54d14CYGrfY|UY~3GQT2zGC2QN$Jbrt{8GeT~{i-??-k3?*~ia z3BQk_mME8k=d2tk3!WJ~#aiM?0qq3ii40F`=pzk7ktc-IFbvBPbHkDpz+Coc)C(<2 zPzxtm3+mC(7a;C#@pOZCDWJ<#0Hx1@y8^(-#HnHk^c=Q&HPpNXV6fF|TP+=)J3u&E zvMz>^g1vMv+p4Zzr~sgSYenBaNa?SDCzMV>*(v1F4S^CPm6~e$W86W&n@fbAp#Mfd zeh2NDM1oLtV`2m=n0D^I=& zI|+O>Ij0H(KAS>MI3@7+qVz_jnI0I_^?%<;UF%+={ya02bF2;~lKc4IdPDx>`3phsG~uAk$1o z(e3w-Ud{8r+kUV+>;G_cc&<(kt@br#jUAB~>*$+hO3$qb4KVqHMyEUKOu87Y4|tf* z8XH=j$l~d46;Brv6P;d>MS=9jz6QLpPv=-A#Sa~kn5)&sWtEn)!X(ucCJ72tN>eZO zRw@fh{k%(cp*4l8d2!#qeM$RLl5~zirIR+6Zk#*H2WwiOOr(z9b1MR>;4XZ{+)2gi z7Lz8W`pjMb;r>M<5=ZHLwH3ZvpGkw_DiYPBGFncW+j7!~L~TW~wiHtsqE62Za5PnD zN5q)j6uuX;LE+S4b_X9uAdM`+2K}#Y9(Y zD;z7T)S(qs(XpWu#Y9ZnLDdzQ+B_ehh(dMgW%U?HLOr-5CfYYz8?Ey-sV|p8fFTCZ zJ~0956%mE5AG3$h=;}dPiK7BSBORjy)GGqCxrsv{I4;0fXR7e=%TlUu`Zwguh(g;;r`Esotsf7XKBfQveoVDp=NbSo)g_8<(`gq-&{j3=OD0p{|au`&1P@ zw)PRL5*pwb6z!X(jP}J`>61^SPOF6hn50xH{o=G3AlBFvy`dt@&o44PgO;_4tg2-BrDdpN=&1IW-vC|6s@hOvXaLbU3Il?Uqw{| z@`{UfjH|wVZelg&h6Y4ei;eH+SwCql)GiAuXwvxxRO4412ScHz0dL({HPzl1?OUbW zhn2?qVWW-8je{W>5L>Oal3hh|oX%0}qw}pEgz*h=0YMe&b`|N^RDKv=?6;hxozcE( z?7g~Ooh3f2Z~BV~;aI7g45phUAuxiE4}LkNb-$r;(J|^EZG}42q*J?3UKtM49K=zF z>#|I_0dW;ov=S!u*es~sh(!1jwTUn^qN_p!l1C)^DZNV))um#p_-6RLaTUH*(J_-D zC;UQhlX`W)AYH7sUfP&B=8hS})eBKq1!w~xOPZfLyp$D@CbBZT)Up6rRW^sgZKSSh zkh(g+H)zD%mOdZ0gU`&j*9aW1>apT29Zrxv#D#`br&kAg9QB8lg{-BI8 za*gP2u}ak2uhscM=>s)L0gZ#*AL^c>W(;ytP=XroahEa~ykX)V6Q$sgyP_8Nfx{uq zDp3myPYBj_)_4Y}-C4UsoIQ{pN_HqkNj5AQm7PP)HQbvbXBzDagg0Ubxl&AP4D1?F zjv3!hw0Ue#yrZ;Hiu=w=)ed&$kzh{&dq69uQGZX160*3j9W2(J`e3^IcCF6?^Z~h{ z!L0mr*N=aHpHn#2Wd-E70?rIR79I+*1yLTD_PyiseT1*3p~-FOf1TiU~i!;EuPn~E$FFpAJlL-v>ftU zyFqpn;1{$kNn5e`@KaWrklmRkh1cp zax(5Iy`S}@MDPFoB-0vB>S@ew;_fM_qi#V--3fc&;@^#uT7TDvbZu!~tJOBZ9XI+b zrB+9%4||Mdi_LX&3V%#4s~D~ABssx_bAy3u8_6~2oVZ^(g*-y;Dc>*OA%96eMgFN= z&Y$25cn>~=e@^&OC{t(^+0BQve_(F;R6U!&|GMkTU6w4<1v87Cew9CIU`mwEct-r^=nST8Ho$Bzs@pq&jH}uqp z_4~8v$7_JYG8U-p1t7at0&-0ma0(27lV26khu6TVuorL%RsfB+3Z7a*jG()qA7ef1 z&ngXn@^%QAL3bm1w@0?RYhoI zMO76@YprM}v$g?l_@-9rXq-A@-KF=|)!DyVmojKL4H?(7KYt&b1N&f<$gtc8|6ODs zY}p;4R@EA2D*}7s&UU-DTJP4o-Fu}4c{4$i%B}o9yIu71hStZc?tyeT%FWxB4zJ78 z_%nC60l@EA4^k&6SAiD5szB!9kUT;=DJUn8c6x(;$K7qPgKyNhW7WMr zX7f*>h5R>C{JShmhW^<=o`Y|>_Oi6w)DU}3$;@8UpnG7iNzN(Dz~0e>nIxsmi}$sSS9De%fSB8P&qfE4D2|8{iIrZO_PXuFWPBB`$@L!Gl7PdD2HEo zlg?e;(?NSXw^`X0F;32&4BNTAc=L*eK8MK}emupWXZR%&ejLS1dZ&Hpqf8b~ZzO$2 zNk3KAsjZlxq^Fz3^cH1PX+C|J{_7_a{v#THn9Ny>U~Ea_$(B+DSqD3J5wS*!|2hl* zhlBgtce2AO#>am~QrE11ZX>IIFV>Iwduaoh?Wlt5FoxH>)=muQ;Y9JHCA^l#zbCC_dJe;y-10%@~@mH!J@(DVza4(5l#W0@sP-)VbcWY3d>4kNrI5*-+jkOg zHJT^=s~`L3t3IwYlVAZn)w08v8)uT{rCS<(BIahz)z{u=vihIg@Ea_&SK>WQEgH@wg?-h6s{Q#s|`Xn0?k6?(8?v(28+O5lv*0%xEc$sKPZg~V)h{UvFK%O%P?t?{3p59P`WN7i>AaNQ5wmU;b zx**zUL;N+M{@sZj_RSK{)^eW&xmJT*bUNhb4*Q@;*tKXtQ^w5-KPB&4SheO2Zi>m-je;1WEQ@S ztw%H#iwhuyg-iC1K45l_Fb0mCR2DAP3-Qv0;h7rx^fDVqQkazgdKPZW{>(=#{9>Ja zr8F@84J_PN{dckOST+u{3`+SIv2a`YAC$tG{g+k@%HYEM53z7t`5RgI8+2I18}yRw z2c6f@XIuHR=(1Y-FZ*A8`9%L|``GwAaUYGLnMdLGP^tlTUd?*30{OpAGv*!bW!=xo zW#?)NO7YLYJ{IGLx1sOn(_@BgJnV-NKU2bgOvBSz`WXzbIYQGJ0Q>W8mF=7i4DWxK z#-9rDoy_f=${1d@oyK3%(q5K+shv|F`nkwtFHwBk7BpXf`q|0yH59*;m1i`=%YLa} z9*q=#HI0AJfu{31!}~i?JeCLR^=ILoSbhB$(DICja;l*nY~g7wWwJIk`~Lwm`SQ)r z^<&?E&A17nF;~j&7wbo7Cz&jt;lGqM7x7Z}=ZDhx>)@P??IBG&nsF3gD02|;((zvN z9L3}L598C*h)m``p5h}IzAJr{<%iLm@eI$VH<|wgik~a96VtgFPt#9i>B#2Ln-CWN zX1uNVe+-^2KQh^Cw4Ba%cCy&-Q%__vYGUzjJ2DDZc)?!o2REd z+dp_K`~Fwo*ZCkfJzWFGJ2GuuPUo$M3tPJUI3P+p?Tyb7=QfX?cP_K@$w`vH*>i7l zPjFH4GWkS#fBEzBOZ-H>KmR=c1V4p;kG~{j2o*x8&`*dL9v1u*`xRA+YK4cQm4ZZF zj+zwJB`P?tI&XYln>@2wZ60QpO4u-{b#k2Vb@je^P6`^oiU0hM;y*X(?=vd~P5S%Q z6+TV+`%U`$ccH&ODz_rNm)Zo@OwhbQg<2P*uFKxOBS!Yij=n?Iy&YI$cBkvlA47lp zBkTa6ydhfKvZTA!rG)*Jb@%Vi4nERX?u>J7tKDqVE2)ORH9h}(V=@2U(Axf3%{=Q| zY){b0L;DqDfU-Ck=w+0e_h-pyc7SrA)$=t#d0YY1wG5H!HVI@i_rR|dsGZSJ$_k*z z?S$_Lt+Uw$=9K$hFZSPbh{oRiif|**Ev4>EqP=4IF|86MiHciyeIUk>@BbEiEd^S~ z80X^UT^panmJ;Jzw$mTYSCVYuwgYNDwG)9Ji7H@pQGSz_bSJyuME4nYj%&}UY~Z0gq9(Q_B= z=&5#q|En-ddwOkg=)r~11MgvYG|Zl!_!|8AH`~>Fos1{%6IX5|x0?Hao5I~RFdB&X z)$Yk>6Y?MK7wtt~Kr5^b^_OAIJ^@%xgnyQHdqABZ{@vY<08_XZGI5xW6+cvfZK!v! zFc^FDNniM$Hj=jFX>x@u=bqwbbLY9QxS{e$d9pm=HeYRR=M>nF7(43frtSWWj=ezs z+bn5(qvyqjS_a)L`f2><$F`+=Zwue>7X0VMEEC6R7OXQKXS!MroA&ka7Dbtcj5LY8~WO?5{3EA9-VegIs_8P=mAv+Ee9}Fz0)~r-B_- z!4v%{!g}C|eh&2{&lBvc7V3%F6YR3O%DF;M^ry%Z2T$~Ss3#GgV7Jw*CuN@0e`1&? ztQQe_a>*0?C&GGiz!Uu(>PZhz^vB4PnVw+x73{gb`ki+B2;Ue^p=s>QZ`VQ>AC*Bkb?c32pCxcSI1}zW4TTCU;p*?Uz_X?)SerpyS6QS$D{p${*3?n&!T@@ zvt#ggp?_QMov4MeJEv(|Yl~acw0}=ur++`1wi0A#t!ZYJh_q}!*vV4=rGG9>`;WFe z(8pm$R(R_Vn-7rY4{P+pz0*Mselqy-s(b!J{U`LRf%;EJi8&ATpYR-q`cHVy!*d~? z?+oB4E1a0&+$Jx<^CD0958Vuh7_E8dTlxi+=XiRrv@q#+8st}LXdc;fQ<0mGn%|Wvbc>Flsew-s9{fMzwzT4 z{b#0y{s+=;C7SWga_>pNqOoA&!o9L44cL2}>wa`vnGUXGnQ|p))GwVMW)zHQlkT=O^hoXfwqHxoXGDqhhWeko#{AD2fct>{=g=RW5g_`bL;rK5;2HhT z4Tfj*@3tPEks+}WAo`s{e|6}04*k`k-#PSGhkobKUmg0Ji-l+OCpQ)#`jguS5beSy z14O&D=+7?RiTblcJGAJ}&I=&=vqQgh=+DjtAo{Zl0f@8`XXq%jXX^~l*xx`d5c_-1 zS&k?{TgRXv@V(9?Mt}wI>;(|*+!BCj=e7v)LqB)e;ppd14bav&vGj8%fF4rX46dHP zJ2j+>{_YHr4*I+E0yq?)5#TC-E&$OUZV14|4-Wrs&$Hb}8PDCf0a5&pYG~^NI%z|EJorwVwYUSN~gd&8>o92sF-@WkZJy zco_7Z5V5k<@8ws|6#sfQc>SxV$BlWb@xG55|AzIR_yhej-czf_?8~E8jZ>YX9{Fc7Sb_He)gAAWp+4vzaX)ZihXWoxf26}Y6e-?m0p@_t z)L+eu;b7>EQBYe?_~k$i(0Ab&sC%(^haS<<=REa%jyO*U9VNw(?5Ep2BHm2>j5Cp&yi;`g*K_yI|;7^lFH{AtS|S^l~^#d|o5QSHgV@xI)TnW z+a4&jf!c=18^gV9uE+<$_|z*1o{(#8iP!pJ{5^AN7PZAWaOdJt$9Bs#!-c zszu<1P9EVp0YB>N@lB98xyU`undNTsJ@RAnj`AM-9{w2Lk$;f?nSYJ%Aq)}L3u}at z!Y{%H!Xt{Eid4mMMK{Gyir*9yqSDNp%za&3x|$2U3x6p5rm)PRuR~IeDvLy`vIgI{ z@2U3}U%cesnkkM@ADQ>IUw=PenJ*6Pk=*9U>HS~yyMFvM`Q#H@?Y)a>ZPd26J!ouT zi?8Lba1ruY`80V~c?2KJPvd{&|HJ=}@5<*1j|l5PcVoCvF1#jiie-wEik6B+itiMy z72)PX=JDni%r35vxo&bzDqLB(sPL&8l@n|2Yzw!oRp)gR;{GpbDLeoFb?v`*gcA*3 z2v_4hGv zP%j5hzJIn(PLtowNTBbawXB{%63>s_ekCIgnK326~OZ-2=#&RQBVU6McZKR5Q<@_XM*&t0{HhO)GZz8GQm(|q>jyn zzX2jOEE?`cK@9ZP8dMXmgJ<-&RyRIU)G#%P`nEs}H^H+^%;{!-qLYBDRv}t&!dh7K zvy76fwSJZhAdXCADGaGYt7`!m>PwcEMg}D<20L0<9;E6G7HfkPo8V99d>N#R{Npgp51_=~E`~264rp#j z(?e@w)Y~zpjH#h@Lg~W%l<<>_Av8P?o?T8Xkz-uE#G2H^pjvKNIw{;J#*(zmIN-HX zEi+784)w70jIRN+Qq)?*{4q^@&V(Q4kM|1r-Klj%sf=JPC9|$ptJw{_q)pQnEN=Fn(?L00$q5m@OD2kN7r89LBV?!uTampYj2G>B7Q*bcPJBK?HjD5A*(1Wmq*R0nBu9j^ zh`R{S5^oW{O$tQ#BRL?#=gH$De4R`XVMp?a2xpUdB3w=MBHTiji|`cTMVLnVif|4| zb;1!no~#$)8)T9Q?MajfKOhrC_yu`fgf9}wesYcd-rA-H3qms4AZA}VyEzTG(1JqA z{@4%}Ugx!%+fGgnnxQOQ>aUtWxWV+FVd0X!JInA%EZpCTh6FGu@Dq%ee-e>4l1>{nTaC$R9PEosOq2BrLUx5fv>PXAHyL zs(9f8yKskU>%!e<~U}xmjoEJ_=}c{(Oxf7>ykj;Ax=D*EdUvq zxp3Co555}w0N)eNx-xQkMvED*&g2UPDKRyO+0;)?3Mqwzs$sK(M}V5wtmPFA=L0HX zTPo&`C&!u>f*}n&2CCuciTPARL8U@kdr^*O3#yx#$OX!Ol)ovzD1S))s@#E}$q(bh z`MG=m@MfiNw=P`SEcfjf$9=w3vOh1`e}7t9-z@CHOYgeW*#F!3-YbLpdwj;ee=iIB zA$8?f@1^|jD=<=Q$#vG1`^5oMSMC?b`2r^QyMoCHw*-h|9_4}Y%Sr`^<9vNCoF4&7 z^1&lMDP4JJ&{M(P%9sD9`MCeMkSCv`^1%#RN(N)zmb+!N*^WO;so^4woO9-WH^oP8 zL(klACVMU~Iu8o0rZ|%}sj9jfgGtN%lZtOF{Ej296VE4o5i<%%0n_0)uY(PIY|W7q z1-8&!a}~gL;t%>2zsJetihaox}I`SCCsrdG)tIPTdP2%B|xu z8|BtfULEDuQC=P8)=^HquG~734^VC$sv*j)j{}Dg4Ut>N7f@~;<<(Jc9p%k!&2K2T z{@P)nD1p5CoABOSG4P&(%B7=xI?AOZnLv_De-xfkPJPeBK*PFL=^3BAdFi!vG1=>l z;T!jaf{eL(R+bVWl~2dd+Yvr(6Z~S}(?okJG2()kE-pz8wXQM=J-@;K0AF>5PiR1N zb&VT1(Slw;tX3Bqpp8xssIDHQjvS<35uNU+GwEWq>Wr9}==2J8My$44r|YHOmm6JO zq1INIw9&pnU~#VkOz>&(-lUGvIjRTgVja~1>FK&yUu{xEVMO6Ags-7ftca}taU$c^ z^XH5=Pn@@Ibpn$(6(c9mPYt!2Laa>Cx69|xZLeE&<*kBP+cdmo8r%MAkh4WvBYqwA z^`$@vKlEgOb92trwzZ*4pF`WdpBd^EEA>{xOVs!`{yCbn1+b~1+>bp#B}ch>C87G7 zbv4uKda51;9G+m{=b+rSCvbC+zk`bN7 z4eJV8F${ulXSnF!B^|=Yz!nnuQfS%mpWxR8$LZf~mcOS{sqwr1Cvh_o4xiY){U{qg zc{V&|tr7v=*;XWwT=&$T^Be9k$kutQR-~Q^c;ksp4aJV<5C6=p4+N{iGzX~__y(s7ApNvx`rCkrVAWBH{vszd!!{ml>zBIiL?+MeB^4# zQwL?jJ5(n?u;vosD<)iDN1I_6 zSmQ&l2!-;I@>cRFemQWkzUE7WmO`|!Tj(x)ArQp`#d1Xp&|=u25L|b=+Pf}w-RjB} zx)zQrbSgYmxV3OfVGoBr4y)!nM68NPi5L=L{oJ;^&37xV4*Bh?!wMRHHOdP8R(YD7 zCSQZ*GV0G?6X!SdfVUSoY-S%zKc)uwkY*F}iH7(yJPAy^FX~@m=XM`4n@9k0ue4dkeSAp z3AYX{zkWuzu zNSVBC60j`G1XeqtOe6FESH3}5`@;UpAG#SnoAuv_wRc~pyR!spt4;4NRjh0OVnufM zUCmav%t$z#J91FcKEvJD^MLkv7(QbzqOlZyy}p%%JG}?)%^RI&JKB_w~Nn z#;UDa+#D0jwAJ~6l37nyp6uCt%Tv#7EgZCK*4>w>p{n4vO4`u(q5M`<$s1SxHirBM zUV7)osGqi09_;MZ`NTVwjz>P&72WdPFP98{WcCZc`mBG}Bj|x{J>Sf7eD}<;s97^g z(j9z`KAh2ZTC*KdPbl7LZ=8KK{lcUBzn{4O;qM0YZteqf81gt?=UxD3j5nn!Jj`YGyal=b!TcGxP%FHHAc2|VS%__=={`_=n!k$?-~AiRhD zNyGHf#Mj`@zgaKs-r29dRMGv>*N$9gei;8g6nZ^<&) z^Ku4X06Ykb4cfz=w==ka;-4sE&qfBb0KY+Il1ctsJ2QW+djbD~OeK4oJwM6d4}gDv z(3`WSvS*&b=7}=dk>>iKH`ud|!GQ~*Jkw<_X0T_e-6kUbdD-+_3;cY*Z+HIO*=T{6 z-jiU4-OS5lkIrvX#rh}9^`F5%(`QruSwrnUq5Y(~cAxODjCQ`ZGyWO!x6tmB7eKW8 zWB`bEpU_?u+I^DvXL#gkh}|bk{uzPv(5@5m-H?BVd^F^rA>R!7XUI=`x5_lpqdL9V ze$)KUYkx4!42*9f^3TX`$9CMBe}<%*Y9NT{M86`ocn5dEG5fc(%IY@oy=9KoPx;@> z4y)S|w`71-f0Ul=8+EalSMansn!XcSZEkPhcy?Cf-+&3h3Uk*MbG2@5e!mfOZ%OMx z=^s^#r1cyiRV#tKJ{R~W!N5iF0S*`1sz)wY0dTUAm@U~iLLM0P9gjAvN`T{4AinJm zycvn#)o_A&!(;yYNb7Znlv=c)^-m+F|B*GEo^V#@6aMZ34{fs_7HPdl*&?4WOT-T! zq+8%eZ2?Kt^G%$uJX>xjcjZ6kkMIhxN0TG86UGTn!gXP*Fhy`xTv7~CtX3>ltW{i5 zEQ>lBH8#p6>anORQJ+R7<&DW3W-fFc>gw&9#e7>!d$zi8S<7)(ROj#et-S}uhMVtv z|AyZq?qmK+a!}U}HEth=`=z@3ILUtJi@{V53inyKkHh^N?&EM@hx<6(|KUDP5Bc4V z{mw3%sazCF5~Exc?hmc)>u^8!OR`JPcV4;Ja>?TRy+ zUQj5>8aFZBTPz092v*jvKVLA(79sIYS4`XK{kiqOMc3RasL_H(_hL9%CwMo0zts3Q ztdG*ashv;htE6^5(egljJD*ix!!rQ5Ywp0{M#}`KBYqNoIe@DIoF|Zb?zNrId%wG? zYw-`QYiq$O=)JUnh;mxitU9>EPSX?GZ71wEvE_bG3yAmPqe8hI{N2m6+SpC{tmB#D z1-rK;R&(W#?}jDGssKPGven_b3_rSqnGL*uw68Z)zD({ZkC(qF*YloyJpUp;n*SAS z>*nOY-JS5CjOjaZ-jEdJ4;}tWbRz~GU1xEGE)8>Wb%{MPI7nsA%b$2ZF z@0U3lFUOVUj6e4<#m@pwoy)K`qL;AUpe%u9M(reoM)*k=+6T9Gq1G~LGB@s0sYA&B zLC$+v&h5ayBj$+O`(n;@Qbfz+)-JR}j#kJUcVW&SLCz;w&JTiTG|UmTlMsrmbcArB zO>=7((zv{AgjtJEahS7WBq2U5=MxiIj*zWb=Q=5()VZ|_rO;6Vy>S=jybf|sVmWWx zOLIi-k635GOBc$cTf2}>hBUIqU6}J+$hj2W#1YoMgyx7^H)5UZq=-97YZvYmaaY&4 z3v;f3oR#n<<~)2T%@Os1#5yZ#(^J>Euf>AI3*fzMgw>5#{~mL8f}H(W&eGU6N_DQ2 zqD!6ox~YT!-q}W2-FWryG3Qy3vp36m!$w+H)NK;$OdS=7FRAO?*Gr2) z$DIEUa!z46zY9W5OY?5r^B5r=jU8mjxZ--&c-?^y41O^n^Fkioo$5G zjaUC3bFM$DH~eX9^Cx%Rw;+4pMaF*sZv}X5x)FK=PC`WabgMG}>*gQf>Zwkz*F$-bcgbFT0!$JD60?6l7!VyJ%P<4|^A=)}s-0B?^ap<=}m9ourd{c`R zU%ZgEd8e`T-RL2&nfXm5qw||-9Uoek9Jf&<+J}Nj)lR}WKpk;?z5>z*ugdrGJNXrS zG+)H`;os#O-s1IRZf)ju=z|~HZkWpS$eru>&#hI*Ls@jJa3qLSnn2Lfx~!Hp>_5wM zG1kz!SZjUClr0X6?f36Q!^v8E{~P>AW4&#Cau{~2&kpH9xAQ$6B6n`E|7oXY9~~Gm z{@G@y+4pnKn*ZOV1dxi{0@A9u(KfP~3guSOk1yKEq5ghLz#}&L>Ggzj9{PU@hWLWn z0qT)i8^2VR}$mMfp(h7hDGZp1{wqPzL^;Ko?LU`V%$){c9L87xJJ7h<<)~ z;A4UxCV$fwfg@mquOE6mB$#3{O=>bbe5f)5dMhwup?=bfBWO*mwZp@pG;ldzXu&z z4YmK&l@4QRIEoNQx@7;UD_alhM!@iUW&i0*8~OiH_MZ-0*l)smV*UPj`%m_lx-YqZ zfMhRfsRx!sCd;2h8)!AmhXd@qnx!+mCKO94lP!nRB7UV5|2c|3$ncp`{IPaoe4PI= zzT_uQ_Bx&4dPDlnAV!V-hRqbOg?SR=UzF0BOw02mnkV8 z)4}wwGkm@$#hY3B|5Ut#Qfzja{{ytXZm=$6`%tV*)~d$-i)-cPk7KrWV&l<)MK~#a zA2*tdnAHp)C5_t;7=m`M?0pHJPG1bSr{Tw>bnIz4B)f5~r1)txo}S4yPLS}=P`qSk zZL)+P#_-I()+-Wz9mPlAN8vIFpF*F{F@soFr1HpUh6S`(vQJt5vX5x|GAf2CTPwwX zgW@N=?$6#qLKFP7oWQoiG9Ixn$x_!kL3;nsLv zN1EP$r1Zzp_?H>JlZ1bkm6w70+jkNpYBYvlK3nMU)oRvW=QT8fG!E*%gr(^^zsTfbzhO`cPh{Z*?lSS(#h?`ay&Xk3Da2q|l|{INJw>QlxG#g!`(0VM*p31vJYCCxAV8qHdw~i z+XyzFYqnB6p0{v4d5PikV<}!*=LLrMUqSIpS^RZU{8jbS$(7O%tDnwADV;=$*RyoY z3=bO-@si3@CB?T$N7IbP^Ut7gGfO8?!oNrHQaZ0QeEuAYm+JLb!`rIwb=D5~U()i= zwOIdK*R22Fv}(6HJX2byB|l7>bh-~a&Gu)Ke+pFP_7(Uah70`)K@dmcBE?%YrCg!`eZb zPyCNj{IZtzvei)EPE!0>d$9nk*}k)@l>QuwpJ{I==G&X${V&q;y36dubZp_>rF2Ho z@@KH}+rraSWwLfP{eQOgtl%-5&#~`cqW?(r0GXNAr(bva=6@tvx~Vij8Pl8dtijXz zNcl9uzh(16m_)z~d=MaNHP_B}}F&wOW!FJyQ|hLrhtq4>;ZG@brz|1Wc=_)pAk&3KycpN0QX>X-1^_HTV{ibI)%zfAGcd6z!PWKK2TzpvZSmSK*M zv;H3=@~NtTON9(ROLG?}p7Fff8z+N@nu~Y8k8(t#n6w{Mx6>#M0sc#f!fCY0UN~EYszHFY87u{ug658OV=7 zeukFgB9NaE%W-AM&lpSu;71$;%eTS-#@7IThRD}&Kz@drn9az~K>h{eX8?cW@iNMn z*i-f!5!})4MjIl8quq@sh@eEf8^AXJdm4dm?TL_sz7Nh3!Beukp(mU&mlK8?z|NtT zm>)s@$0lN4iFPpLR&=*fhv z>6|c0Pae;z;)F$dax}|}6F!7?$!fs~8G5oR>o_O0)01UcU{_^s)?d~5k0wVM1GT=>=Esb=*bC@AB6lJv@?SCM@|#L&w+F| zCUe{fJ=tyqdn&-^2{Z;0VW6HYF#?|_5%^Q(Zk$jI5ur_()K4!hYz#@&Zoi3H?`|!wK)`Nnv?7CoF(|F0bZ<<8p3uc^oIK*)6AH za0s_C+PoxrhTl9&WT8A5aWO>tqHF?#Ti-&Y#l>j(h^;FxhB9K(h4MEj6=DQjrbm#U zm?j3ob6sC8h_WVEhLUcQ?drN#vFlpW#&;WaNp)-BOO2MaWA#Vry&9Pg*49MzbM%Nq z(a^skVFt+%OMR2Zw_^O-VhG#9qx?mrVUgVp?ZZ`bgUk2r?m6*fheTVkb2e!DytM1# z!N($fLx1yHJgqh!sy3nO6BXY@=ST%223=|6KQsx5|2D$%u>t;}vCLvvp3qT`36_(= z@EZ$1RL_h9=*Z%u3n)~SqW|`RI?p~>q|l9t^k$N1<0`aX$|rzjTwAt=i}U_g+N@~K?TYlI`hGlD<(b5;qDD>f_q6mu186cvhtijj(biZ>K5D_&Ko6ceNRN7b%y zx4tWIe7F0)3-TqBKLWIB)D2c@ysUoxOxISJvvw@%YAl6MwPqyK*J?O<_=vK8651Lx zT0`C;E~Xhr;s$;$mWw8#va9BWfoP#=B&(AoZ-_G*&Kfui5%1w6u`b*5r;4x`WY$De zPBanmFU~ZQb^P1aNv){!;$7Z~bvkfGEEi3R)(Ndm?Phht*#xVBKb(y*Ov+4k@<{RfcIsswWdFVxpa@Cb0zFnPcN#)*2<;3sl zv8iIY==-#vZaqud4*k_((TuN2*)A-gb3z)Uh22%K(RS z`V!KbnE4XECI1w!<-7B{_$T=P^6!J~q~$`1Fh)2oBnyFXdU;c5Cd^cvRIF4iQp70g zmS%PH;ZO9v@Y!JpCig?lQ~n(t5LkL-XqZES1FR)Gz{&Bf{9E}pxxg#un=v^72{6dr(whE2z~j}IsV+=p&n4{H z6FkG=UK+Jc@oPbCQ)dN%O%YhP2N(?DI5SlNGy<#u7y>X3AWHoogn0PXp>=%xs?a(< zeobf{A7^Q_j*s&BXdNF{B9xrN3$;S=3$1G%ANTH}b$pQXMy*hk3rDR`FO>TRNjw73 z6|_RHWy6;Z-t%x(l-n5W@jFAcSi^gAoi~ zLJ03o0EjE~;{b65KFY$E0mRj=FMHn1;4lUUGdP#Q^#F0TL+kygzz2>UKv#iksiO2f zdK<*+l2IY*bf9NH$PP|@wEgyi6%yP9Ji?y!5RT`R&49-hY8XK5Vjq@{1|ZCAMTY?5 ztTPS5aaG$65Lc;VEM8j%ud;OCWpEe6A7StUrVrQi@QmeJ&7RX({5cHTBOdDY0egOi z!8chtcHngrW&V2`4dSYjS5DKNU(UPXPA&oXU|y-UIU0Lq=7vbGiZ4fsX05c zv)io>Np%k)wZ{akl_&frWWi_vcocphatY^d*dM_UuewK;x~bGZaNK;DX$Oavjqq#e zF^L{`8h-e>)xoH?hU$8UB-?oE)1cwUriRDFHZ0$xPk45*+gnF`$wOS&Bp4g_UUhX0 z^we>nXV(73obkc~X%ZY$CAx*JRZ7P%qkQs)=Q-w$&D)UoQ{IKV*YnQj)!mIX{=T2n3g6de?(g~~qdzx3h5B#Q_}%=H zitw9(uY%Vwwm+2hp@L35nEdx>hSz*V<6j3p4_@P?_(N#?b1a>3hWG!9#$V0iM@aDp z(DCC_dJe;y;z}Yiat(FUD(|l#V+s=StvH;kAq5^9yMFY!+X#_m{7v@g;fg#^RSs z<+1Sdmd@}RhVR1i6$=G_oNM?$MNcF}uj{>w$)nxu@hr03`nS@}2L6H#{CU80#$~Xy z9~Bd{{PtQyxNwW`QQ*f`ev9yt5H4MoX%B=j+>$P{@+)93S-yiFjbR%EGXedb0Ej=l zEaRR8%7Zn0BBVh%Rv3!AJ)Bu)lP|y;do%J4nZPA+W4Ncd^I#LVOukWm9Jo|c4>b&G z?XTEf*CB=`EYHT@aIQE2m{1kuSvaBnNiz$RD~Agf-_$ z?;ruJkXM3^FJAsk*LO5PSbE7T0C|8H0b)CS4iHrzw*piE{0boU$ddrE?Oy;`2=E+0 zSAh2c#GN0ezaQX7IAO!(0zUzcCT%doHSpkrxxt0Z12F(g07WFREC5eKVgMFN3lE>L z0Nh530a!*HmI^E|TzH-k1F*2$0kMJw;09IKG4Oa0IcR>Kn%b-!-ZR1 z@eQo+Gk^%d%EE(}|cAHce|N(TzkJ zfbA^?SmVc9;;<%`I4r9rPGJRyMYoKHeQJrDZw2RS1&94>`EHpN91=_|slOZwr4~il0c+cWh4M-;AgE zHsNVIe9zj=|0Hd<@k}0No76AQ(|#Eba&2npmpu~xaT-63^`kVB?Q7P5qleGFbA2Sa z_467ULzJ15A~T3N{9WX~C3;-lDq#5~`R~KDS!CZZ=%JB`JEVsC|Gr!K@2+gWey9Gw z-L35Rv3`yB|LyAe{%=E74={Nl`_8n*DkQxDrcdC&@Czk86a0;1_*b0hd$JjH9G-fR zK0hwa&Zp_k$}SW?MUtPlrvtS;lQXwtdK$7DH2!La?;@q|;3y^_*{A-GgmT=#ol{Is{n<9GoK7@fy*qtg?#%GnG`=JUE3kbrC}xS*Fb@jD*!LwXqwzy%9BsyZ)NzYOrJz{l;WlHm$V<3dC>ERgx@N~KSkq9@|pvr^4rq_OY)kl zB>Z6-pOs2htN&5xxAUQmXLn2Wlp-|2rWR;wfu?LFt zP~8@E7!%umLSTL#iQ+g1FW(?PrFYCu5Grs?kBg`*Bi@SnUg;5oV^aKfDuX>qSCiJ1 ztB8x#rssx@E^*uKN-jC%rj(^!8X6fLwlyLlB_(@i-M2D4O9C}Ny;yM)laTcKB2s@Sx9WR`Nf%)DH#PjRHZ7vFmL_x4ENHkYeO=^cc{WL z%iKnsTDkDrQp1$u{7YV{fklH$*TwfewfV(?$4ABY)O*fdIA*DQWP#7ZmKEM9KjpMM zr_KvikD9x&AU>tE-PsY2lXM@N203ml7&oGieo=}3|Bh$KT>X13+*lB->hS6!zi|us zZj;P$`!*J|$X))c=fL$l`taR`{nnx)(JQZV;>Lm%J0ka~CS9wVkmts^l%-X54dN=| zC+kP%vl{xV-|^uSs<6?a8L?A#?^sh2=6^7|Lm!o9;q)!p7ZzRXQnX`=*GJhGR5|*{ zBAu@4YW9US`7uSu@>RKTJ9%Nz!d`vK$Nl1U^_t38rz?t^mVKeywUHTzN}Ynf^_vl< z(I+g;&hgI9|4H>|_poV0y{F}GR|SUkKboM<-=bTRe@r!CzV`rCzcBk93%~a%L);_2^3vEIl{n%8s^&JB0`tujz}{ zmVO_;Nog+nHRJsq<;{1hGOyWX6!z(@+ca<6sjdq@jX&%Zw6jgojXsZG%g#C6`YpZc zaQru?rpdV0%T!#v{M52B`NB0t#sMf=iCO1b)OYD8-}{uzkB>N2wy=C$iQ6gFrG=MO zJ*F({|J zuf8OHRhUzd=h1xk{Eu5epX4lCIwE6v{?#%ru`b_kW=Fz#@qUK9-Rn<$Yr&WhFzn0sl;_$S}9LEl(gQilg zd#+X+xzKUx*!Z!NW2b1t_FZ(}n5z2tWS4U7;x0zV9li3#E{r8-%I933UR9B?J}&*z z9;fqz=a{w_)W#Wc0XNF$9js1wo0}TsveYHWGYGje~*+!%Kq{gZRIh?uhWyg zBAt)*@QphbJwPZ z?=x?jF*5T=+}J~z+3GXS$)VA?kEk>&`j-TC>IkoZ zWUp>6xi0E4I$y0(tL;x)@{6_N_jTz`~0hIJesL~I9Zi;pCQ}p2hI3P-?uomNq6e{ z_@1-lVnSkur+N5As%AqSk9d7Af3eTiHra*|ahgcycFx1olTl~X+ zO77RcS^CM4Z*rd1FDwnYcD?vWYJzjCI8~7GdeJ9W&M!^V&2@u$U`){`l^z%VZ_0zs z`W@;QFsstk=ZM!ccG>Ef%FsAnZ|DCDd2a4xLv(e!?BL;PFUE{CrcH4h8#FJQe0bgY z#-Y@Gz86bE{4b9Fy!UnCqfP;#{pRJScC6f*eq`fgDIL5*-*f8~tO@>V*06T5kxyPS z{jk1z%$N5m)1SN)nK?UTmtV`&(2Lb8MtB8%)!VVtKBNC{J<~h+b-c2Fu}e_q2$%QX zbAIxY#-Lp=J@Cm(F>aF_2YtKV&p0xw3ii`T{0Q?zNuEos`gtX-w032E7ZlMSY% z2h+Bsx6Jf6wyI1T&6WO~nHY1QS4HW*AW~I2B`ek5t@SINQey(Ack4f^@`Zg`XYYr8 zKG!3+gZGn{RGrG>ogaFoQ@KmvM^hjApi}t^kq`YRXtnbpXGcxImN->!=Z?m7w~p&u z#dS2keW_Jtj&Ef1*-*^yQ+4T&wmS1dT(3hR-@E)Tb8TCXZAJNUbFLdhj(8pN|H>uE zXxcT%!M795o>y1U?!>9z2sAWQ?gi68BL5AeT+1fbU&2hD_6x z^4|UJ)-Rk=KC7~2=7_kKsfSXN=eRuYoTeKTlsVlaE-u?x6y%nnJd`^7;M;DqL)IFr z-a8T(q@0o#S@pIHV6R_G2=TD{5-U>#8k*8bkTPN4ss>y(z^m__y`gLGwNdoS7O{xs*(CnN$Aic-7+muZPG%53{2&NZG1f9sn_d+@4=Q#pSCx zZGOH16?pWkIUO_OgwY%fG{m$+YvDWS6C&v9{ALpmB~ zpNUpwsPkSO<`y(BWXbHf5pIW4Rmonh;#QAt9XLDi-Fa8~Kbg5O+u&h0H+75Gq2k5; zeZM+WbR=_TT0&*s&k2ejn&Q?!`tfI0|VvW&LLBSI*$LXe>#-0N09of5dDGH>B^|_d%ZK`w8@d1lBb*9 zK5!Wk=ixOwka#`%^Ss`dsucn0Zd2_}7)DU#vbC zSZ+K70jhq=fte!$r@9=f>=>x6)Py`+?U6az0LGK!rntl&BC3}Ube!Yi*WUl#_3Z~} z96ewLa9QhjwQ^SF4VUF9vj;z7xR$Mc?LXO^F>SS1`|H(XRd$zF2mGA2I&j_`jdPG& zqBk`EL;bJ0oSSpCa?YG=ZnanM$CVmqb<)Mb^S;=anz&K*^1Q@_1DBqhlIXTbx!TKE znZMQu`^SaDmR5K0TJ80}z*qa~_FOwLcjCGePX%5MOHv&^u`Y6QT+VuB>AVL9r}->Z zdB>-mDp;7v4X-RYkQnb-q{``|JTje>=!cvNUpQiEBB$yZmvzdJQM@C#;v9E2?i^9g zfr0-r-*Now@f}aiUbuRxYRk8>jTz-3@BL4grWzmihF)b~`oXmUKd#AanV*rle%aS& zu3ehbDm7$lOi23m(GN!yE|hKTCUo`BJdi!AMTqHF@<4#|WJ9udNtSWknV3w~+R7lK z(O(lVZq9>m8~v9B7L7Y|_4HGHJwBQo)o+H;-~Ai+t%m9QJmL~U%+4+5Om41vBKWDF z(sw_zGvlCFcKYt=3929RB2T3+tXkSHq2-*IXfF81Z}ZfCL8j=X&8PHw@5?j$s!xS3 z?A19%7aw=3*RFyh$5W<-y>>lk7dvHaey`3YMafGCP3g7kqWa_fE~;J~b6$Kdzev?< zmwS+@-;9)zyAHpTnOZfwFYeP-k)+fR#E&G>)zz67p` zqy0Z1-WL+3wjzQQw0K2Z$8(kO7K7G$l&V1u+IX~hl!_-1lqA*ztC3o-rna_`)(f?- zE!xDhC`7GVwTf4&2Jt8&9wnmx=b7D!5TS3|*Z27R{#{+ZnP+C_+TGbXp7}oM+uJE} zrlt?ie@#C%{rXMeC*#!geY={N8i#I_$1S2ELZNrw-GlZ)igaxoV_v?o(y_S7(W~q) zZTxY2+?x8N*~A{olvN(qh#a5vOCINggx|EEb%jIF-+9ASi*3lg7v-`zg zx+llC3_3gLBki@E(fSV>#EqI1zlJo5ynADY;&W}tmOTUZzSMP@QWjk>^QX_x$2Csz zXnd@Kpka z3y|NOGG!BV5*2{jz%KUVYHTao(tZa(1h? z@y>gVj4$f+tvvni{AR0E>r+!*OJ4q$cYUT*^>40gpm<_>Xz@uNXilklHx8c3N5VRt zhwZaYuPkfX#;O%w);;X_b85>ruL_R^z3~;J$D>b`Css^M3A|gwbT&t5XQRebFUl=y zo!Rz^;?=7{hO^M*Yk5cWvo*mn#+hpp25Tqf6xgy9q1v66Q^ircUp#E``C?n%o2SFwvH4OcVUAah5^4Si@w65an#1Q>!?}VG+wBr5`q*-j`lO& zH2mP$lhfT4yj$4r^fRqD4%dx(Vz*q^IjzC~#n6N*wyBEQsv(9D<2{wo)X>bbMmJ2O zuto@`oD{uK%hFlb*cL6!S6#{ZO`m8H>Y0XFs_T4I-4wSS8#m8&2-BP^6h+$kqemM) zaHQLg<_HzF|4`)^-ZIW~>Z}Vbo;u-%;jm(D(+fGT>lZoeXxh&ZzBRs~IcbR$f_LAt zHWZrJga(FDPcErmF(hg~b`DK=$07XeY^s0Pc+ECgKgqaM^^-yPP_a{cDyNO*s;-Bs zk>R|Q`tixrvL2Uek_Fn{z`TY%zy&2iOZp}$U8Z9C>j(+Z}c z`hiBdZMIdIKB}_nkRe06#<_XQDo0hPkf2Yo*!;GcYFi34U)lEU75eE;+oFVe+AYhb zDBjkl8iW>(V9i9s3dbDXJX?EfoFJsDTI;_tHrEx~-cp=Vbueh-^+$}|be$~ib;^rx zI)opbd-NBLS1PjyLfb1xsP?7Zaoy=uSo9}ysu-p^gr1tCrXj+wx_h=}j(4@oo(Kwk zgwbSLYQ2?EOVimJtrM=QCY-qbxt#_FcxV9n_{%_ z1I=VZMaO3CKXP*PZ1BF&fFFz-`Xu88UFhq!U!so0E%SLRJjrY3xkYbw`sfUFVUyB| z^OUpSivNz@u=qxPt9iq!7Zz1%Kl98XTLpWC_?O~u85?TaStExp9x(IFt-Nh{ae0+0 z#0wQdE?Hj@K346H8jw1n!aG*Q&%#|*nBi!`P@S&Dy4BH(B5(QRhwjeU`()_ko)e1~ zMGUGGe#zcll^^<^Zuh{C&P1CAD$SMiz6ZJu z#Nm)*HBuJ87<7|*d)|Cw(K_#qWI*JKxK5q&g2HchnphzvJkMMCsiAJd%EcAp>w0*n zO!A3~3pyLHsqqG#w@3B<)mzM3`f!A>q4C3NpS4|Q@NhhEwlwXsKnpm|_P(N~_HoX4 z*kEXcW7e|*RS|>Sp>~>0ffgY~mtiYZ_-eCryz~jiF&ZJ)suBuRS%y$Ytn+{=$|AJY z9k(r4^wzG;N!7P7uGHuaR;4gc)zxs$@pWp^*9U}$)4oylF^qKFckVZxv2@U}hdD^w zDW|=Du`yG#$0}SDwyFXQ>m9wEqfGrRt963YmZmtN9hx&x-_tl-GsG&$ghQ%khBc04 z=PFZGi%BP>*fuJ*X=~?1>%TJ2*YvXrr-gc|VTLJ=x1HTg7E6du2)9jCtk4$aJY4u< z(5B0ukDo7Qe$X#63f(oEt=ol@Dr=4{;iTfaL1^L_;M`||Z@(_lc2u!NEBu;sPv6vd()zvC zu6j+i)FAxisO9WzYHVqvyK1|l_)RO|m-}5!HH|{>5pJtKG+5?3o^IqiVPpMI3~ zS0;!%*Bk!gj84OLas7ceGM@N>6+w1GIffcpnIugeC5c) z5ybe*)xl?gryTekf{|B5O#_2LuhR@X4#9n>IU9H!f{_`83 zPdW07<;a7}k#8+W9#Ezn85S0>GR2iD7j7#mt6bt{7p#NQ;nBoeS>a2{k;tYK9SHx> z$ZY`7*erlei9_J`P}o*85Zpl}^5BqawVc~gUbR1!KiA~SyPHqt&o#O79+Ic>=bBu3 z&)!q{b4{+iNBXJ!xh7ZMoAOltT$3yB1$-)huE~}CUgP`4-^b^eT=~t#(`Mw)CMPx~ zWxnR+w?a=_n?IYJ*jAU59JU_MHf|*wy+50r7#n_@afe4X>?XfK?vW2k9WsejCu;dx z`C|DR`3m`V`A_l}-|XA5m{n_f_vydjsoOChXZanFpCaG=E`!!~C8( z3-;`H2%H^wJ@BW%y@8&A>tI)zS4`I!OU#j&%`r=3eva{unH57a`e&FjPG`ht%*c4- zPwpq{Rwa7EsJto%Z%X@xn)#N$f1NYDx$jsnxg#Av)&EoWj}yh6?zm$bce;lF;!bzm zNsK$)apy4ZbjO{-*w?`w?q%6OKKinn?R3YT@wn4n87S^_r+~QA-3IBP{bLK@zheLR zg$r`(AE#z}LD2p&?#%av9oX0dD$fq`zE6D`3#|HOXp zKYsHtH&6d9hBm0|Ys<1xjP~w|VOW&}SOi}LvE7*t1FIsCR)Y4YKiCxRApPqPHi)Z2 z$VCu7h508jtHhW(S|`T3%PiAQ@j&n!NN0nuSXci}JH_dcyCfK{tOpy+4n`j=xFg!e!PF+B}an^E*7u_p+nn+bzpaE5Y$CSxN&VUPlooLLW|cC zTv+RQ@UE6Lpo?v=_>JiZ*A%n&xFZ2#?sO;;8!gc@Jy~Ot$cG|-!8U@C8 z9N*1iM%lUo%5eiuy#M$AK$ulhp8D;?oGrB`*D#KVBXveS10rTu$vQ-QM&%!?m;)3ztgO%-w=6Vz`p}Gc?dpmVe+2XE=w(GMq=dG3-h$ z4A0RJhF{YI3|rFyEI;4SCJg%kI~nG0Bydh6-lrcj+)r;aJVOafuLBKXxPYcIbkLOy zgQ$Ta%tJA3Pt#aBi|Gl5nRFn-J#;9;i?k=hU@Bv{l~!gLKz$jmr`>)`CPeXHzx9A@m$W8QsVb);$;=qJa#X z(H0EX(8>&x=`etwH4L{=6T{jxgJCo+WcU^JWjLRPGVDjc zW_X&mX80w|Wmu1@7!ISc45!dT4Bw{y47<^;3@vmO!w{OlFr2PrIFT9{uApBrETU%^ z=F#~K<7fwlgXjW==`@YuWO{;OMLLvWb2^YAv;hp?qO%!xq(d0$sElDet;+BSox-pZ z&1QIm&S98DgBVVyofy7J7c=~Uwr9AA_G8$chBMquCooN4BAq-E_w;2wn7KS!j z#L!Na46oB`?D=a#gV}pGfW|ZTK6;s973#xqD(%Yr6X`bQK1$oN=WYuf$?#X2p`bL2 z-UIG;d>@)Jez*ksF5So6-!rbb1gfC5m^*^;#wF0#=v?MrO8r^_Q_Lj-{O$)~9V4_MopZY(yI~JWmr~=%S(;`U!J;)4dGu z&^{m!1eRGXbH7MuGQ3ImGWjN0KSuuwbOe)sN*6M$OTS<^iymgUfsSWbonB!0guc%3 zA$^JAXLK1uD~)CGt)t&Ew+DTL;RC8?*pfyv+(jQTe3{;2IF2@C_&)W6>s7QSEj)^> zO^>NB!|&*BCT~Rp*?YKyni(FWgyC5l!%(1VhA`E^-djQjviDC;wlP$bjSS5sg<&8u zF^nM-8D@}FhJ~azLtnC%VKzx)=tWvHOd#Jd97Fmr%q1fksz?k&f0E0vfPe`OXfLwJ zDTbjWgJCT3Vt9aj%`l4GXV{j^W_X+oVYr;g81^QW8LlOV7^af?3|o+~3|EpK3=O0a zLnWEVa1QCpa2{#Kuq%mTc#iC6_%*rCur;Z|@EbCfVIT4u!;z#j!~3K$!~Ntk!!u+W z!w%#Zh6~6Nh7K~5VGtR?uoGFxus!i)xR`vwFq0f+xQ9eCyht7~3?{c2ZY2#F29S0P z*OR^sdyxhVN0GY>`;#z+tI1%7PBMvM8p&sPf*fQxl)T4qAUVUZC+Wa&Hd(-M2>Fts zjLc_Pne=0Lh@58FjGSP&hIC<=OlmV+MUoj-C2JU($X5(gNHoKZWGKUJq$k7LWEI0` zQkCIX!#$IwDrGYlb0 zhT)_S!--@+!xbckVG)7tEznQSBkdW+kxYhzNHD{6vX$Xv(u-k5GKyhyGMiyB8N%=_ za)@C^LKx}@vaZF@c(R`15u#>TiDWapLCg%3$lDC3lhq90Bnb?`z6$hjqv#?6uRCCO zqGq_6bY-}moMY%m%nVPGK!(E!YKBBn8^OMR6p(tM21yjXPEdm+iZ&s>3!maMme`UM2S#ULio| zMg!?@B#GfCR&U3tmetpB;4Z`b9H*B_gDN*_pW?;*s9}rK5FOZuIXAnLv8&}iPt$zOs+c^(o zPvC~%3}NsZB1KNOf5&0>g;=+G zO~ti0F@9lPIy^9uMNqiyQsIyVju#I3-I#F*X}N$e&EcY>yNMWP`URhEINS{~h*GFH z@JH(8f5+hih2sm0;;n(goq@dUgTnbqg)7tg!k7M{bpr{<4~0y^(^&GrRoW0qh87OW zIwC>tQ;uI*;s1;*5(*de$&Fv1xpOz6a3`h0q1GAPJa!_Xx)M(N_ylH-g5ywle{di7ls$I=X}x5K4G?NZ8Ez_dUp9Gvz`R%Snh zqYoAipOySN!;cQajkft3EEL;fuPJ`3l zWiT`N!tpM&ype7NKw5jGJjX+cUxu;{kbApbS5~mUB2z-))_p&F1}^H0*GmmbPbeIJ zq|zV*=!>Tveuuzmzwp|*rx#H=3|b*?ke57sz#S>c((rRv#-T)cIeujX|5KtgD4cTI z`k<%z6qbmsB^2(ubmu}Kab7qd_-)5?(27mPt{MRzPy8nPmQFho!g<4qM~IuBD21m0 zwhgx3sBOcV&=Phf@Hdk-b;JRAXkCB(lH(wk!!?ODu|((4NL6tFjP4(%!cwwCgPb|A19xd8J4 z>19gc_rUddT$FIDgaHz|+l|;FUH7XL@4wS-1a?li-H5}GZ|sDO2gDAEyB!I%AAubh z0`?!E*t8$meLzb-^#Ji0CgBtb-55P zy3Jr$0{bc30VyOox!0^h^$u!!Y1xt&8hyPeURi5jN3=eavg_yJT#Kq7TXks;YZ$mP zj_V7!`Y5i0u}s6CZLp?+t5}Rle#hgte#9m!s7JNSfO3)vk5F5%;3=*PD&cLzRS;Z* zt_?Ba+L)*xfh*H|4THcNwi>QG;r4mSKl}T((Rhv!|9dI-p)#ffv>&t{_P{zWYIBTa zrNb4XbhfI4?HF~dE0?_mF0XO)t52{lxm&7J|DK!M;C+H7048m`X;RQn?cQmuOF3NOxqNcOJqUJ{)JY8kItEi*$SFTxLo8ot^b!XZ40iREASpAoG z@xdxf2PEkn-aAV7!-+Z|?dfLquj-}h- z+Xt=>tQ7bGSdr)vcr)-xpgATXCOPI!u*Nt#W@d~>%-W2L85J|CW&9(fLB`LLo^NQC zAENxe8Be%<1{&O@Bc?nM-n1#O(u$Rbx-Ga$o&>dn`Yxg-O>Ne8@z(1Ggf8!SoC2$g z{3c*Kg>7ER>%@Kuu1w>NLLXe!RYL0M3Wu0iF~5>p9#h<`M7z6Dj|nvkpygU5HhVbw zhhs>2cD-H&a0p}2vNjkeGH9*5QG!A1UQ6#w2F{?R>$jvN zgI3E&>llMpTx8fm`FmYIeiZnWLF+@~kJ}#k?~ITbv^FsYt&L9^v^KgiXmwd!ss4D8 zLF-5<2Ca<}gVsioLF+ZfpcR1(S_iWkF?(BL+d~CiKuX~(aZH8jh%R2{U;c-mM{jb-ToW$OEYMByBM_YxEQn| zfI;g*Nd~RU`QDAfBnGV`z@QaTia{%)6ob}G&Y%_cltF8u=26QqkJt2?M{W#S>&j!$ zij)|%)|Jbk6#)!dw}C-xFfeEx0S2vwz@T*m7_<(3M>vDltsU)l-0*1fse}V3s?B*2WrlfkCU_VcIhcT7|)7F=*Xo3|e7hgWMRj z?nn$;g>!&G%iEnn>-sD4)&er_h{&Mj5txMxS}i$))(Uy0JCreKg{@e(#9VujvI{kY z&I1Om2L~#xTOu)NrMMWhd|KTJZ@IBJ@bU`(_d5ZDR;LmST4Bhb$5RHac*dZWG-n(zXe|}qIWbQ-r~2A3FNHH`MYgtnBaBpu3|dRx zZ=JK>cBab6uZw{}%j9q#JCi3eXzAv4?YU%uN@%ZHY|Rwrod5={ZSyq2dSR>B)nU}d{aNmX&qzO+OT6xwrpt=^SFX_+qMim zM8CiBFzrc7o*fysB86}gx30Rs$gmY&&|tg(6kB)0$#0idOnrUN#JDz79|Of!z)pMk zPGs2fRSK0oCcfHk+lme8H-Ta6QQjvs~PDkWN~RTH{jFeFOVir!j+whC+QsV zPR$Y7$;*{gHw;I5FHREPR5gmKTi^Cl@XZ()*l4#n4YwlgzZjP|tyCe*cT`JtW zbp2ds)xAsmGTRuyk=XN!bBmhei!`FO2xS#{g+Ve4r#y4(?-q|E*0nsc=2 z1Iyq@pgHT(DtSrE_(3xSWH4INy7}H8Ci$#60sLAsts{iIS$j4e-4R_d0tmLk54T*n z`eE~NDIV{w?G*hW{IF0*CwQu+Ig2xAgw%ZWBpknG$MR-fY`*t=MDm>(A&+M^Gzje+ zefv+FF~JNXp9z{vR-tdjCE%>ncDLdkZQn2E)0dmSwCcFeYIE~0GimSu_{eY-(gd>-7((XCY)E?bo5O& zkqV|yqhEB?HQM?PJG!I7cR%cUlJijCJTv&^=6icqO=4JCdP4o zV1`~OH2MOoma(SD96QM=LgH71?76$Z z;Bc(8-Lci!BBVMpEqj1Tx5AUHx&YgHg|IU7KV>31iwN*m2fjH!&`Wa3;zH|oaV~oO-6;DiW8?#Mbmie>7 zH509Z%IR;K;u~iiq=|J5u|%1K=K5mea$861TI~^4CBsUikmOW8dDHTPZjZIQVrv3$ z9s4PIsfOqDKV#Qk&q;F(FbYGHtLUd1XPb^%WyQa0?pcLqx;3^qM}+P*n@OKy40j4h z9P^cGzTt}gH{&OodKO`rPOI`UG}T*jLiFp5({#cL>qm4b$kPumTLa9BItbV2{RagnW#Rr%x%m7q7A)IM@HU=jMCxnz39 zlBoIEHZ=Ji-Oo0msc=^-D;}(vq+M$G$?>^HIA!SKKmwa<)*ixn)n2{uqw$WehV4ZK z64+GGe`@?RU-yyanogPd#PQJif?k+moL|$!`M|V9J2PjB?>GmLmwll)s2`ux%CN)n zlIE;QSSC;%u|+!6TICZ~#eGeM)~yNI8ZYasx?ENNGXioE85N<9p~(jfQ4S!9J8qH{ z_jax|buo_BysR)71*Jw~@OG@zB3g@Z!}v?#g?~pXAB)2@kZf@rjoUjuDuOu)>arV>K_Lil^3@w{ItCcqa4+)3HU@f zg%hd|U$3H=s_khN@(eQ7ueN)NX3lqWk}V3O5TP+CmI}9$I~y81qI0gAgbBsJIX^Ld zY5Y;QyQ$XVqZ7JWZ!5xP%(YEZ6sbPSd2hEcTKhrHWJg8kK5LF+iL;|k&?#)t{Cr`! zVNB9ou)VHuzNGDL*z7=}wHMwz5;yY}1&Xc1`%b>sX+~3u6kEu!wd<{TA>>9jZRl)g z>T7xVXAR2jF!7PA?(eg ze(wyKWXiW3)V*hWx414)Y;{eE9ejmc71m_}!&aMhngQ`kecG(66Zz?q#dRZQg%~QtZdf@yWz)3}6l*VZN+S1i}|&RMHZHMY>Kv>F6qj%uEvs{_dcTU)--^|7@YskrY~ zbborwz%#lIwgn2op$*FEq;GG;hk1_>tO6o*&K%!gx7y}Z2x;0AIY<%LQ#0E-M5wG1 z4jGy`);N<*t1MM@CR>U^*r?r>Q(GTx{7N(5+E4gWCDbzvb4+o*ZR%#R=t6Aa3Spvl zMNW}E&lsl}WK9<;sqb;3EyJ6S2+rJe^SjJVmoI*Hy|{gy$l?O)P~j~>r;0Zm zaa3|v*jRnurpxy#>h?EYF(V!rwx-{$H*xy9#dX8$Dg$oPxapt97u6*btKZ$FTX(dm zl_vhJCBen(W-Zz7zbU?@AZ!-=R3{DC5wM%CTbk&EmsR_Un^>pX5*0_aTXKHY3-^po zH4?JPiJFC)_dX8-V3nvRyKghT60D zNeay5E9s+hg5#b$e)3DPl)G`s91dPe48u!_B}6xE-;e1toq({c)F^`^KI;LK(N3DHd#_5KJ;usd2RHOk;v z@?C_eZ3w^pyX^VtKT}FGs0ofyvkW}*2iLFo$vv@@dr`F)4t_5&3@;^?5Z&~RMz3NC zE;Uk%rA8SXK_fRYehK!waMp!?V=1xH@x8pBLeD`dE0$AA)YIiB<_ZriWppb0=_)0L z;ibe9qMJ4=%EFa=)h!~H8f9?AzdDZT{Nit)9m9M48%v2v@YV;^&+3#@O4rMVwNm0p zohLe);Nyu0_+t<}Sqb3>S6f4MH73Jwc$82Ri_liy|F4u%jPQ5=HdLHtUlJ(gx8;-) ztz`4FWL&4_h%R1A41*=a6aS!_YaXJ;q3D60;8#M^&pv9~f2fqG1gbBml&I^)Pce_; z-@R_7#4zxmomfJ2msA=>89z!YYbw7V=lXVX|L2U-y+l3UN9B|fHQD%CGWBS;Qeqe^ z2%cC%baTDmx3O4a zbW=(De$0BYrug+CiNAcoOBu0E>-u(b|L2TSDRD!@gmOxWx_taBnR-ow=;Ea;xsd`( zh;GiiH2XY!FVW49fUoQGQgW6w?)97zQ^iu+p|=jDe5IUHqGl;SOXeU1H~7H8ONn9l zy~Glto9}_Ct%dI;xLwD`8&mN+itd!3GTQRrF8}BIgFms*`n+FGDVd@z_Ws;@fTy~O zSs4nuec1v2m~A|7!r>oDjt~91#)CTjE2U&r<%;;}j}l{`hKd~v zVAp3mD5Y0<<4edrYQW-I^5EKFxVIV)^yj6-5~7>y1fu41$%AX5qO9a^R~W7h=@OLx zgO!qp=NhS{gF`9r&$0Yd){Lj)?_RYVeSbZjtUV@;zjw!q{E=7%U&v$F_zKl)TY*~+ zTBtZW0ljPDg!jS>Xxjn;(B#IQHv|wLaycTn)qvh`*Uf-RkOu-rf;$E<#0xaC0et`q z0qrol%|>BIo-u&l&=1K)gm5bKhaC_8fVd5$0Ob%*79gnWF&;-HU@W-P z01sd|z$idFU|T>Rz~d-~XJI)aq|+PF8#+~M(H}-#seqtpNL!#g5H#Qc?SKZrNVr}J zh+8}6pd9l14WKt%*9ZM!Bj8AM1MWvR$j<0 zKqsIb`vibK5YGvegL@##!95!=5^xBh667*KZ*W%zq)?ti=nv_v0Zao-1`GjQ1!xDX z3g`ovg8mSGBcKv68W3BDet@t^k)8&$!&vc4KpZpH1B?V52IvjfPeBCt+kilsLc0Mf z0WE-OfFXcBP|x9rkk5&LNUX8~PzhLs?&Yxa0sY~@jsr{s`5?d$z;r-6P|_MmNac0!#z!2p9qwj|k}>0ki{F0>r&lHvl66-$Xg2_X8l3cr5}<1Kf`O z5bspL5O6m|g#5n-h??w60qt<#egedu8MV+K;^~ZX2-g@9%F_nW2cCngfZqO;UI7dN z`EP)zng0o38o2i&LVf>;2=Z!x*k)-FLGA+>0_j~we~AA!Anqjj5D<44%>~2;ZyF%( z82JSdcW`_J7y{4lHFQIKqXBVe$p?UG5Z?=ck&v$$=!WO*b3kk_|AB4@mxFHbUxIGH znTQ3HjsWzA>lXrsfZG!h{J#K<1o>e=JIKc)LcAByKNof=pg+{dOMtlDs}A}@I?Di& zGX7hX!}V_fDj_~SAbxiv5#c?41ZW5OEkGZ@hJd)+svXK9Twg%kN!0)lcUnyV#Gd9Q zL~y?Xh`p6p(I3+L7!XH@yU`!&?Hxee`1CU%Zh|rb;s&R?fVhDv3=lUn4Mu;!N$7@r zxD(L(IHjimaa&s#K%~wei*hJ`eL&m_*8}Cyel!A90-gs90n`Ad z0eS;QLVex=^aiW}h&#|;1XRNFa}yBAPv|DVNJy^&x}zxl6#W6~0(!&qJ_`_c3~d0k z!~Lj^Ziw#*Acg1TAt3Gu`V0^^Gg{H@1z&%3Lp^u^+M%2e0DS;k0;a)x@;)H$UaN`z z5dJY>B;a@G5An&ReSvJI1)8fuH@Di&ZFX}{Rl`FG2fsee%|F)7?d9f<7<;Ot*X*_a zSI5#1s53M$BbOGe{o=i}oH?&v>o#`g-B87Y6T1e)o|v}pv-)g|wsS zcEY)d^@xwV!IM)E@zT#XhoCAV$VQ~%_ z&WXjjX*f3)=TPDtF&9~WGF%)?oHL7aBysL6&S^Ba3T{4pmP3p0`FH2iim{{Sx4(<4 zfbU`NwQB5wK=`12!Qz`m$FguM34wlDHZ~+;J&{xN73wBQv7`98wA$T&C7QWow$eV65DKH=PCeOUN5 z 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 void) | null = null; + onerror: ((e: unknown) => void) | null = null; + terminated = false; + pulls: number[] = []; + private items: Generator | null = null; + constructor(readonly url: string) { FakeWorker.instances.push(this); } + postMessage(msg: { type: string; skipBefore?: number; maxMessageBytes?: number; count?: number }) { + queueMicrotask(() => { + if (msg.type === 'open') { + if (FakeWorker.failWith) { this.onmessage?.({ data: { type: 'error', ...FakeWorker.failWith } }); return; } + const walk = walkPst(new PSTFile(fs.readFileSync(FIXTURE)), { skipBefore: msg.skipBefore ?? 0, maxMessageBytes: msg.maxMessageBytes ?? Infinity }); + this.items = walk.items; + this.onmessage?.({ data: { type: 'opened', total: walk.total } }); + } else if (msg.type === 'pull') { + this.pulls.push(msg.count ?? 1); + for (let i = 0; i < (msg.count ?? 1); i++) { + const next = this.items!.next(); + if (next.done) { this.onmessage?.({ data: { type: 'done' } }); return; } + const item = next.value; + const data = item.kind === 'message' ? { ...item, mime: item.mime.slice().buffer } : item; + this.onmessage?.({ data: { type: 'item', item: data, fraction: 0.5 } }); + } + } + }); + } + terminate() { this.terminated = true; } +} + +afterEach(() => { + FakeWorker.instances = []; + FakeWorker.failWith = null; + vi.unstubAllGlobals(); +}); + +const box = (id: string, name: string, extra: Partial = {}): Mailbox => ({ + id, name, sortOrder: 0, totalEmails: 0, unreadEmails: 0, totalThreads: 0, unreadThreads: 0, isSubscribed: true, + myRights: {} as Mailbox['myRights'], ...extra, +}); + +function fakeClient() { + const imported: Array = []; + const blobs = new Map(); + const created: string[] = []; + let n = 0; + const client = { + getCapabilities: () => ({ 'urn:ietf:params:jmap:core': { maxConcurrentUpload: 4 } }), + getMaxConcurrentRequests: () => 4, + getMaxSizeUpload: () => 50_000_000, + getMailboxes: async () => [box('in', 'Inbox', { role: 'inbox' }), box('sent', 'Sent Items', { role: 'sent' })], + createMailbox: async (name: string) => { created.push(name); return box(`c${created.length}`, name); }, + uploadBlob: async (f: File) => { const id = `b${n++}`; blobs.set(id, await f.text()); return { blobId: id, size: f.size, type: f.type }; }, + importUploadedEmails: async (entries: UploadedEmailImport[]) => entries.map((e) => { + imported.push({ ...e, text: blobs.get(e.blobId)! }); + return { id: `e${imported.length}` }; + }), + } as unknown as IJMAPClient; + return { client, imported, created }; +} + +describe('openPstSource', () => { + it('loads the worker from the public file, with the build in the URL', () => { + expect(pstWorkerUrl()).toMatch(/^\/pst-worker\.js\?v=/); + }); + + it('imports a PST into its own folders: Inbox and Sent Items into the account\'s, with read state', async () => { + vi.stubGlobal('Worker', FakeWorker); + const { client, imported, created } = fakeClient(); + const file = new File([fs.readFileSync(FIXTURE)], 'Outlook.pst'); + const result = await runImport({ + client, accountId: 'acc', targetMailboxId: null, keepFolders: true, + files: [{ file, format: 'pst' }], scope: 's', resume: false, signal: new AbortController().signal, + onProgress: () => {}, openPst: openPstSource, sleep: async () => {}, reportEveryMs: 0, + }); + expect(result.phase).toBe('done'); + expect(result.imported).toBe(14); + expect(result.skipped).toEqual({ contact: 1, calendar: 1, embedded: 4 }); + // No folder was created: both exist with their special roles. + expect(created).toEqual([]); + const byBox = imported.reduce>((acc, e) => { const k = Object.keys(e.mailboxIds)[0]; acc[k] = (acc[k] ?? 0) + 1; return acc; }, {}); + expect(byBox).toEqual({ in: 8, sent: 6 }); + expect(imported.filter((e) => !e.keywords.$seen)).toHaveLength(2); + expect(imported.every((e) => typeof e.receivedAt === 'string' && e.receivedAt.startsWith('2011-06-2'))).toBe(true); + // The worker is asked for a few messages at a time, never the whole file at once. + expect(Math.max(...FakeWorker.instances[0].pulls)).toBeLessThanOrEqual(4); + expect(FakeWorker.instances[0].terminated).toBe(true); + }); + + it('turns a worker error into a coded failure', async () => { + vi.stubGlobal('Worker', FakeWorker); + FakeWorker.failWith = { code: 'encrypted', message: 'PSTFile::open PST is encrypted' }; + const { client } = fakeClient(); + const result = await runImport({ + client, accountId: 'acc', targetMailboxId: 'in', keepFolders: false, + files: [{ file: new File(['!BDN'], 'x.pst'), format: 'pst' }], scope: 's', resume: false, signal: new AbortController().signal, + onProgress: () => {}, openPst: openPstSource, sleep: async () => {}, + }); + expect(result.phase).toBe('failed'); + expect(result.error).toMatch(/^pst:encrypted:/); + expect(FakeWorker.instances[0].terminated).toBe(true); + }); +}); diff --git a/lib/mail-import/__tests__/pst.test.ts b/lib/mail-import/__tests__/pst.test.ts new file mode 100644 index 000000000..dbd2f7bdd --- /dev/null +++ b/lib/mail-import/__tests__/pst.test.ts @@ -0,0 +1,181 @@ +// @vitest-environment node +import { describe, it, expect } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; +import PostalMime from 'postal-mime'; +import { PSTFile } from 'pst-extractor'; +import { walkPst, type PstItem } from '@/lib/mail-import/pst/items'; +import { buildMime, encodeHeaderText, keptHeaders, type PstMailData } from '@/lib/mail-import/pst/mime'; +import { itemKind, pstFolders } from '@/lib/mail-import/pst/extract'; +import { registerPstFile, openSync, readSync } from '@/lib/mail-import/pst/shims/fs'; + +// Aspose's Outlook.pst (MIT, see fixtures/Outlook.pst.LICENSE). +const FIXTURE = path.join(__dirname, 'fixtures', 'Outlook.pst'); + +function readAll(): PstItem[] { + const pst = new PSTFile(fs.readFileSync(FIXTURE)); + const { items, total } = walkPst(pst, { skipBefore: 0, maxMessageBytes: 50_000_000, boundarySeed: 'test' }); + const out = [...items]; + expect(total).toBe(out.length); + return out; +} + +const parse = (mime: Uint8Array) => PostalMime.parse(Buffer.from(mime)); + +describe('PST import (Outlook.pst fixture)', () => { + const items = readAll(); + const messages = items.filter((i): i is Extract => i.kind === 'message'); + + it('reads the mail of the IPM subtree, not the search folders, and counts contacts and calendar items', () => { + expect(messages).toHaveLength(14); + const skipped = items.filter((i) => i.kind === 'skipped').map((i) => (i as { reason: string }).reason).sort(); + expect(skipped).toEqual(['calendar', 'contact']); + const folders = [...new Set(messages.map((m) => m.folder.join('/')))].sort(); + expect(folders).toEqual(['Inbox', 'Sent Items']); + }); + + it('keeps the read state and counts embedded messages it cannot read', () => { + expect(messages.filter((m) => m.keywords.$seen)).toHaveLength(12); + const unread = messages.filter((m) => !m.keywords.$seen).map((m) => m.label).sort(); + expect(unread).toEqual(['message 1', 'message 2']); + expect(messages.reduce((n, m) => n + m.embeddedSkipped, 0)).toBe(4); + }); + + it('builds a received message from its original headers with a fresh MIME body', async () => { + const m = messages.find((x) => x.label === 'Multiple attachments' && x.folder[0] === 'Inbox')!; + const text = new TextDecoder().decode(m.mime); + expect(text).toMatch(/^Return-Path: saqib\.razzaq@xp\.local\r\n/); + expect(text).toContain('Received: from xp'); + // The original multipart boundary is gone; ours names the new structure. + expect(text.match(/^Content-Type:/gim)?.[0]).toBeTruthy(); + expect(text).toContain('boundary="=_bulwark_test_'); + const parsed = await parse(m.mime); + expect(parsed.subject).toBe('Multiple attachments'); + expect(parsed.from?.address).toBe('saqib.razzaq@xp.local'); + expect(parsed.attachments.map((a) => [a.filename, a.mimeType]).sort()).toEqual([ + ['Blue hills.jpg', 'image/jpeg'], ['Sunset.jpg', 'image/jpeg'], ['text file.txt', 'text/plain'], + ]); + const sunset = parsed.attachments.find((a) => a.filename === 'Sunset.jpg')!; + expect((sunset.content as ArrayBuffer).byteLength).toBe(71189); + expect(m.receivedAt).toBe('2011-06-21T23:51:07Z'); + }); + + it('puts cid images in multipart/related with their Content-ID', async () => { + const m = messages.find((x) => x.label === 'HTML body' && x.folder[0] === 'Inbox')!; + const parsed = await parse(m.mime); + expect(parsed.html).toContain('cid:image001.png@01CC3097.BF0BAC70'); + const inline = parsed.attachments.filter((a) => a.contentId); + expect(inline).toHaveLength(12); + expect(inline[0].contentId).toBe(''); + expect(new TextDecoder().decode(m.mime)).toContain('multipart/related'); + }); + + it('builds a sent message without headers from its properties', async () => { + const m = messages.find((x) => x.label === 'Multiple attachments' && x.folder[0] === 'Sent Items')!; + const parsed = await parse(m.mime); + expect(parsed.from?.address).toBe('saqib.razzaq@xp.local'); + expect(parsed.to?.[0]?.address).toBe('saqib.razzaq@xp.local'); + expect(parsed.date).toBe('2011-06-21T23:51:07.000Z'); + expect(parsed.text?.trim().length).toBeGreaterThan(0); + expect(parsed.html).toBeTruthy(); + // No mimeTag in the PST: the type comes from the file name. + expect(parsed.attachments.find((a) => a.filename === 'Sunset.jpg')?.mimeType).toBe('image/jpeg'); + }); + + it('reports items before the resume point without building them', () => { + const pst = new PSTFile(fs.readFileSync(FIXTURE)); + const again = [...walkPst(pst, { skipBefore: 5, maxMessageBytes: 50_000_000 }).items]; + expect(again.slice(0, 5).every((i) => i.kind === 'skipped' && i.reason === 'resumed')).toBe(true); + expect(again.slice(5).map((i) => i.index)).toEqual(items.slice(5).map((i) => i.index)); + }); + + it('skips messages larger than the server takes', () => { + const pst = new PSTFile(fs.readFileSync(FIXTURE)); + const small = [...walkPst(pst, { skipBefore: 0, maxMessageBytes: 20_000 }).items]; + expect(small.some((i) => i.kind === 'skipped' && i.reason === 'too_large')).toBe(true); + }); +}); + +describe('buildMime', () => { + const base: PstMailData = { subject: 'Prüfung', to: [{ name: 'Jörg', email: 'j@example.org' }], cc: [], bcc: [{ email: 'b@example.org' }], attachments: [] }; + + it('encodes non-ASCII subjects, names and file names', async () => { + const mime = buildMime({ ...base, text: 'Grüße', date: new Date('2020-01-02T03:04:05Z'), attachments: [{ filename: 'Bericht ä.pdf', mimeType: 'application/pdf', data: new Uint8Array([1, 2, 3]) }] }); + const text = new TextDecoder().decode(mime); + // 7-bit: every character is ASCII. + expect([...text].every((c) => c.charCodeAt(0) < 128)).toBe(true); + const parsed = await parse(mime); + expect(parsed.subject).toBe('Prüfung'); + expect(parsed.to?.[0]).toMatchObject({ name: 'Jörg', address: 'j@example.org' }); + expect(parsed.bcc?.[0]?.address).toBe('b@example.org'); + expect(parsed.text?.trim()).toBe('Grüße'); + expect(parsed.attachments[0].filename).toBe('Bericht ä.pdf'); + expect(parsed.date).toBe('2020-01-02T03:04:05.000Z'); + }); + + it('attaches an RTF-only body as message.rtf', async () => { + const parsed = await parse(buildMime({ ...base, text: '\r\n\r\n', rtf: '{\\rtf1 hi}' })); + expect(parsed.attachments.map((a) => a.filename)).toEqual(['message.rtf']); + }); + + it('drops the MIME fields of kept headers and keeps the rest', () => { + const { text, names } = keptHeaders('From: a@example.org\r\nContent-Type: multipart/mixed;\r\n\tboundary="old"\r\nSubject: hi\r\n there\r\nMIME-Version: 1.0\r\n'); + expect(text).toBe('From: a@example.org\r\nSubject: hi\r\n there'); + expect([...names]).toEqual(['from', 'subject']); + }); + + it('splits long encoded words between characters', () => { + const encoded = encodeHeaderText('ü'.repeat(40)); + for (const word of encoded.split('\r\n ')) expect(word.length).toBeLessThanOrEqual(75); + }); +}); + +describe('pstFolders without an IPM subtree entry (OST files)', () => { + const folder = (displayName: string, children: unknown[] = [], containerClass = '') => ({ + displayName, containerClass, descriptorNodeId: 1, hasSubfolders: children.length > 0, getSubFolders: () => children, + }); + it('reads IPM_SUBTREE as the top and leaves search and system folders out', () => { + const pst = { + getMessageStore: () => ({ getBinaryItem: () => null }), + getRootFolder: () => folder('', [ + folder('Root - Mailbox', [ + folder('IPM_SUBTREE', [folder('Inbox', [folder('Projects')]), folder('Calendar', [], 'IPF.Appointment')]), + folder('Finder', [folder('Saved search')]), + folder('Freebusy Data'), + ]), + folder('Search Root', [folder('All Messages')]), + ]), + }; + const out = [...pstFolders(pst)].map((f) => `${f.path.join('/')}${f.mail ? '' : ' (not mail)'}`); + expect(out).toEqual(['', '', '', 'Inbox', 'Inbox/Projects', 'Calendar (not mail)']); + }); +}); + +describe('itemKind', () => { + it('sorts Outlook message classes', () => { + expect(itemKind('IPM.Note')).toBe('mail'); + expect(itemKind('IPM.Note.SMIME.MultipartSigned')).toBe('mail'); + expect(itemKind('REPORT.IPM.Note.NDR')).toBe('mail'); + expect(itemKind('IPM.Contact')).toBe('contact'); + expect(itemKind('IPM.Schedule.Meeting.Request')).toBe('calendar'); + expect(itemKind('IPM.StickyNote')).toBe('other'); + }); +}); + +describe('the worker\'s File-backed fs', () => { + it('reads any range across its 64 KB pages', () => { + const bytes = fs.readFileSync(FIXTURE); + (globalThis as { FileReaderSync?: unknown }).FileReaderSync = class { + readAsArrayBuffer(blob: { bytes: Uint8Array }) { return new Uint8Array(blob.bytes).buffer; } + }; + const file = { size: bytes.length, slice: (a: number, b: number) => ({ bytes: bytes.subarray(a, b) }) }; + registerPstFile('t.pst', file as unknown as Blob); + const fd = openSync('t.pst'); + for (const [pos, len] of [[0, 4], [65530, 20], [131072, 1], [200000, 150000], [bytes.length - 3, 10]]) { + const out = new Uint8Array(len); + const n = readSync(fd, out, 0, len, pos); + expect(n).toBe(Math.min(len, bytes.length - pos)); + expect(Buffer.from(out.subarray(0, n)).equals(bytes.subarray(pos, pos + n))).toBe(true); + } + }); +}); diff --git a/lib/mail-import/pst-source.ts b/lib/mail-import/pst-source.ts new file mode 100644 index 000000000..c63270feb --- /dev/null +++ b/lib/mail-import/pst-source.ts @@ -0,0 +1,114 @@ +import { withBasePath } from "@/lib/browser-navigation"; +import { LITE_BUILD_ID } from "@/lib/lite"; +import type { PstItem } from "./pst/items"; +import type { ImportItem, ItemSource, SourceOptions } from "./sources"; + +/** + * Reads an Outlook .pst/.ost file through the PST worker (public/pst-worker.js, + * built by scripts/build-pst-worker.mjs), which is only fetched now. pst-extractor + * is synchronous, so it can't run on the page; the worker reads the file in + * pieces and hands over a few messages at a time as the import asks for them. + */ + +/** Errors carry a code the dialog explains: encrypted, not_pst, unreadable, worker. */ +export class PstReadError extends Error { + constructor(readonly code: string, message: string) { + super(`pst:${code}: ${message}`); + this.name = "PstReadError"; + } +} + +/** + * The worker's URL. Its name has no hash, so the build goes in the query: + * Stalwart serves Lite's files as immutable, and an old worker must not + * meet a new page. + */ +export function pstWorkerUrl(): string { + const build = LITE_BUILD_ID || `${process.env.NEXT_PUBLIC_APP_VERSION ?? "0"}-${process.env.NEXT_PUBLIC_GIT_COMMIT ?? "dev"}`; + return withBasePath(`/pst-worker.js?v=${encodeURIComponent(build)}`); +} + +/** A PstItem as posted: the MIME bytes arrive as a transferred ArrayBuffer. */ +type WireItem = + | (Omit, "mime"> & { mime: ArrayBuffer }) + | Extract; + +type WorkerMessage = + | { type: "opened"; total: number } + | { type: "item"; item: WireItem; fraction: number } + | { type: "done" } + | { type: "error"; code: string; message: string } + | { type: "abort" }; + +/** Messages asked for ahead of the import, so the worker keeps busy without filling memory. */ +const AHEAD = 4; + +export async function* openPstSource(file: File, options: SourceOptions): ItemSource { + const worker = new Worker(pstWorkerUrl()); + const queue: WorkerMessage[] = []; + let wake: (() => void) | null = null; + const push = (message: WorkerMessage) => { + queue.push(message); + wake?.(); + }; + worker.onmessage = (event: MessageEvent) => push(event.data as WorkerMessage); + worker.onerror = (event: ErrorEvent) => { + event.preventDefault?.(); + push({ type: "error", code: "worker", message: event.message || "The PST reader didn't start" }); + }; + const onAbort = () => push({ type: "abort" }); + options.signal?.addEventListener("abort", onAbort, { once: true }); + const next = async (): Promise => { + while (queue.length === 0) await new Promise((resolve) => { wake = resolve; }); + wake = null; + return queue.shift()!; + }; + + try { + worker.postMessage({ type: "open", file, skipBefore: options.skipBefore, maxMessageBytes: options.maxMessageBytes }); + const opened = await next(); + if (opened.type === "abort") throw new DOMException("Import cancelled", "AbortError"); + if (opened.type === "error") throw new PstReadError(opened.code, opened.message); + if (opened.type !== "opened") throw new PstReadError("unreadable", "unexpected reply"); + options.onTotal?.(opened.total); + + let asked = AHEAD; + worker.postMessage({ type: "pull", count: AHEAD }); + for (;;) { + const message = await next(); + if (message.type === "abort") throw new DOMException("Import cancelled", "AbortError"); + if (message.type === "error") throw new PstReadError(message.code, message.message); + if (message.type === "done") break; + if (message.type !== "item") continue; + asked--; + if (asked < AHEAD / 2) { + worker.postMessage({ type: "pull", count: AHEAD }); + asked += AHEAD; + } + options.onProgress?.(message.fraction); + const item = message.item; + let out: ImportItem; + if (item.kind === "message") { + const mime = item.mime; + out = { + kind: "message", + index: item.index, + folder: item.folder, + data: new Blob([mime], { type: "message/rfc822" }), + size: mime.byteLength, + keywords: item.keywords, + receivedAt: item.receivedAt, + label: item.label, + embeddedSkipped: item.embeddedSkipped, + }; + } else { + out = item; + } + yield out; + } + options.onProgress?.(1); + } finally { + options.signal?.removeEventListener("abort", onAbort); + worker.terminate(); + } +} diff --git a/lib/mail-import/pst/extract.ts b/lib/mail-import/pst/extract.ts new file mode 100644 index 000000000..08ec78531 --- /dev/null +++ b/lib/mail-import/pst/extract.ts @@ -0,0 +1,241 @@ +import type { MailAddress, PstAttachmentData, PstMailData } from "./mime"; + +/** + * Reads folders and messages out of a pst-extractor PSTFile. Typed loosely + * on purpose: the objects come from the library inside the worker, and the + * protected property getters used here (getIntItem, getStringItem) are plain + * methods at run time. + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ +type PstObject = any; + +/** Folders under the root that hold no mail of the user's own (search results, views, system data). */ +const SYSTEM_FOLDERS = new Set([ + "search root", "spam search folder 2", "ipm_views", "ipm_common_views", "reminders", "to-do search", + "itemprocsearch", "tracked mail processing", "freebusy data", "finder", "common views", "views", + "shortcuts", "schedule", "deferred action", "spooler queue", "conversation action settings", +]); + +/** Containers whose name is no folder of the user's: their contents go to the top. */ +const ROOT_NAMES = new Set(["top of personal folders", "top of outlook data file", "root - mailbox", "ipm_subtree", "root - public"]); + +const PR_IPM_SUBTREE_ENTRYID = 0x35e0; +const PR_FLAG_STATUS = 0x1090; +const PR_SENDER_SMTP_ADDRESS = 0x5d01; +const PR_SENT_REPRESENTING_SMTP_ADDRESS = 0x5d02; +const ATTACH_EMBEDDED_MSG = 5; + +export type ItemKind = "mail" | "contact" | "calendar" | "other"; + +/** What kind of item a message class is. */ +export function itemKind(messageClass: string): ItemKind { + const c = (messageClass || "IPM.Note").toLowerCase(); + if (c === "ipm" || c === "ipm.note" || c.startsWith("ipm.note.") || c === "ipm.post" || c.startsWith("report.ipm.note")) return "mail"; + if (c.startsWith("ipm.contact") || c.startsWith("ipm.distlist")) return "contact"; + if (c.startsWith("ipm.appointment") || c.startsWith("ipm.schedule") || c.startsWith("ipm.task") || c.startsWith("ipm.microsoft.scheduledata")) return "calendar"; + return "other"; +} + +/** Whether a folder holds mail (not a calendar, contacts, tasks, notes or journal folder). */ +function isMailFolder(folder: PstObject): boolean { + const cls = String(folder.containerClass || "").toLowerCase(); + return cls === "" || cls === "ipf" || cls.startsWith("ipf.note"); +} + +export interface PstFolderRef { + folder: PstObject; + path: string[]; + /** A calendar, contacts or other non-mail folder: its items are counted, not imported. */ + mail: boolean; +} + +/** + * The folders to read, depth first, under the mailbox's top (the IPM + * subtree): PSTs name it in the message store, OSTs call it IPM_SUBTREE. + */ +export function* pstFolders(pst: PstObject): Generator { + const root = pst.getRootFolder(); + let top: PstObject = null; + try { + const entry = pst.getMessageStore().getBinaryItem?.(PR_IPM_SUBTREE_ENTRYID); + const nid = entry && entry.length >= 24 ? entry.readUInt32LE(20) : null; + if (nid !== null) { + const find = (folder: PstObject, depth: number): PstObject => { + if (Number(folder.descriptorNodeId) === nid || folder.descriptorNodeId?.toNumber?.() === nid) return folder; + if (depth >= 3 || !folder.hasSubfolders) return null; + for (const child of folder.getSubFolders()) { + const hit = find(child, depth + 1); + if (hit) return hit; + } + return null; + }; + top = find(root, 0); + } + } catch { + top = null; + } + + function* walk(folder: PstObject, path: string[], depth: number): Generator { + if (!folder.hasSubfolders) return; + for (const child of folder.getSubFolders()) { + const name = String(child.displayName || "").trim(); + const lower = name.toLowerCase(); + // Outside the IPM subtree only what sits next to the mailbox's top counts as system data. + if (!top && path.length === 0 && SYSTEM_FOLDERS.has(lower)) continue; + const childPath = !top && ROOT_NAMES.has(lower) && path.length === 0 ? path : [...path, name || "Folder"]; + yield { folder: child, path: childPath, mail: isMailFolder(child) }; + yield* walk(child, childPath, depth + 1); + } + } + + if (top) { + yield { folder: top, path: [], mail: true }; + yield* walk(top, [], 1); + } else { + yield { folder: root, path: [], mail: true }; + yield* walk(root, [], 0); + } +} + +function str(value: unknown): string { + return typeof value === "string" ? value : ""; +} + +function looksLikeEmail(value: string): boolean { + return /^[^\s@<>]+@[^\s@<>]+$/.test(value); +} + +function senderOf(m: PstObject): MailAddress | undefined { + const name = str(m.senderName) || str(m.sentRepresentingName); + const candidates = [ + str(m.senderAddrtype).toUpperCase() === "SMTP" ? str(m.senderEmailAddress) : "", + safeString(m, PR_SENDER_SMTP_ADDRESS), + str(m.sentRepresentingAddressType).toUpperCase() === "SMTP" ? str(m.sentRepresentingEmailAddress) : "", + safeString(m, PR_SENT_REPRESENTING_SMTP_ADDRESS), + str(m.senderEmailAddress), + ]; + const email = candidates.find(looksLikeEmail); + return email ? { name, email } : undefined; +} + +function safeString(m: PstObject, id: number): string { + try { + return str(m.getStringItem?.(id)); + } catch { + return ""; + } +} + +function safeInt(m: PstObject, id: number): number | undefined { + try { + const v = m.getIntItem?.(id); + return typeof v === "number" ? v : undefined; + } catch { + return undefined; + } +} + +function recipients(m: PstObject): { to: MailAddress[]; cc: MailAddress[]; bcc: MailAddress[] } { + const out = { to: [] as MailAddress[], cc: [] as MailAddress[], bcc: [] as MailAddress[] }; + const count = Number(m.numberOfRecipients) || 0; + for (let i = 0; i < count; i++) { + let r: PstObject; + try { r = m.getRecipient(i); } catch { continue; } + if (!r) continue; + const email = [str(r.smtpAddress), str(r.emailAddress)].find(looksLikeEmail); + if (!email) continue; + const entry = { name: str(r.displayName), email }; + if (r.recipientType === 2) out.cc.push(entry); + else if (r.recipientType === 3) out.bcc.push(entry); + else out.to.push(entry); + } + return out; +} + +const EXT_TYPES: Record = { + txt: "text/plain", htm: "text/html", html: "text/html", pdf: "application/pdf", jpg: "image/jpeg", jpeg: "image/jpeg", + png: "image/png", gif: "image/gif", bmp: "image/bmp", zip: "application/zip", doc: "application/msword", + docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", xls: "application/vnd.ms-excel", + xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ppt: "application/vnd.ms-powerpoint", + pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation", ics: "text/calendar", vcf: "text/vcard", + eml: "message/rfc822", msg: "application/vnd.ms-outlook", csv: "text/csv", rtf: "application/rtf", +}; + +function mimeTypeFor(tag: string, filename: string): string { + const t = tag.trim().toLowerCase(); + if (/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(t)) return t; + const ext = filename.split(".").pop()?.toLowerCase() ?? ""; + return EXT_TYPES[ext] ?? "application/octet-stream"; +} + +export interface PstMessage { + mail: PstMailData; + keywords: Record; + receivedAt?: Date; + /** Attached messages pst-extractor can't read. */ + embedded: number; +} + +/** A mail item's properties, its attachments read in, as data for buildMime. */ +export function readPstMessage(m: PstObject): PstMessage { + const attachments: PstAttachmentData[] = []; + let embedded = 0; + const count = Number(m.numberOfAttachments) || 0; + for (let i = 0; i < count; i++) { + let a: PstObject; + try { a = m.getAttachment(i); } catch { continue; } + if (!a) continue; + if (a.attachMethod === ATTACH_EMBEDDED_MSG) { embedded++; continue; } + const stream = a.fileInputStream; + if (!stream) continue; + const length = typeof stream.length === "number" ? stream.length : Number(stream.length?.toNumber?.() ?? 0); + const buffer = new Uint8Array(length); + try { + // pst-extractor wants a Buffer; one over our bytes fills them in place. + const B = (globalThis as { Buffer?: { from(b: ArrayBuffer, o: number, l: number): Uint8Array } }).Buffer; + stream.readCompletely(B ? B.from(buffer.buffer, buffer.byteOffset, buffer.byteLength) : buffer); + } catch { + continue; + } + const filename = str(a.longFilename) || str(a.filename) || `attachment-${i + 1}`; + attachments.push({ filename, mimeType: mimeTypeFor(str(a.mimeTag), filename), contentId: str(a.contentId) || undefined, data: buffer }); + } + + const { to, cc, bcc } = recipients(m); + const submitted: Date | null = m.clientSubmitTime ?? null; + const delivered: Date | null = m.messageDeliveryTime ?? null; + const verb = Number(m.lastVerbExecuted) || 0; + const keywords: Record = {}; + if (m.isRead) keywords.$seen = true; + if (safeInt(m, PR_FLAG_STATUS) === 2) keywords.$flagged = true; + if (verb === 102 || verb === 103) keywords.$answered = true; + if (verb === 104) keywords.$forwarded = true; + if (m.isUnsent) keywords.$draft = true; + + let rtf = ""; + let text = str(m.body); + let html = str(m.bodyHTML); + if (!html && !text.trim()) { + try { rtf = str(m.bodyRTF); } catch { rtf = ""; } + } + if (!text.trim() && !html && !rtf) text = ""; + + return { + mail: { + headers: str(m.transportMessageHeaders) || undefined, + subject: str(m.subject), + from: senderOf(m), + to, cc, bcc, + date: submitted ?? delivered ?? undefined, + messageId: str(m.internetMessageId) || undefined, + inReplyTo: str(m.inReplyToId) || undefined, + importance: typeof m.importance === "number" ? m.importance : undefined, + text, html: html || undefined, rtf: rtf || undefined, + attachments, + }, + keywords, + receivedAt: delivered ?? submitted ?? undefined, + embedded, + }; +} diff --git a/lib/mail-import/pst/items.ts b/lib/mail-import/pst/items.ts new file mode 100644 index 000000000..95327e133 --- /dev/null +++ b/lib/mail-import/pst/items.ts @@ -0,0 +1,87 @@ +import { itemKind, pstFolders, readPstMessage, type PstFolderRef } from "./extract"; +import { buildMime } from "./mime"; +import type { SkipReason } from "../sources"; + +/** One PST item as the worker hands it over: a message's MIME, or why it was skipped. */ +export type PstItem = + | { + kind: "message"; + index: number; + folder: string[]; + mime: Uint8Array; + keywords: Record; + receivedAt?: string; + label: string; + embeddedSkipped: number; + } + | { kind: "skipped"; index: number; reason: SkipReason; label?: string }; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export interface PstWalk { + /** Items in the folders that are read (an estimate: Outlook keeps the count). */ + total: number; + items: Generator; +} + +function utcDate(date: Date | undefined): string | undefined { + if (!date || Number.isNaN(date.getTime())) return undefined; + return date.toISOString().replace(/\.\d{3}Z$/, "Z"); +} + +export function walkPst(pst: any, options: { skipBefore: number; maxMessageBytes: number; boundarySeed?: string }): PstWalk { + const folders: PstFolderRef[] = [...pstFolders(pst)]; + const total = folders.reduce((n, f) => n + (Number(f.folder.contentCount) || 0), 0); + + function* items(): Generator { + let index = 0; + for (const ref of folders) { + if (!(Number(ref.folder.contentCount) > 0)) continue; + for (;;) { + let child: any; + try { + child = ref.folder.getNextChild(); + } catch { + // A broken item ends this folder's list in pst-extractor; go on with the next folder. + break; + } + if (!child) break; + const i = index++; + if (i < options.skipBefore) { + yield { kind: "skipped", index: i, reason: "resumed" }; + continue; + } + const kind = itemKind(String(child.messageClass || "")); + const label = String(child.subject || ""); + if (kind !== "mail") { + yield { kind: "skipped", index: i, reason: kind, label }; + continue; + } + let mime: Uint8Array; + let read; + try { + read = readPstMessage(child); + mime = buildMime(read.mail, { boundarySeed: options.boundarySeed }); + } catch { + yield { kind: "skipped", index: i, reason: "other", label }; + continue; + } + if (mime.length > options.maxMessageBytes) { + yield { kind: "skipped", index: i, reason: "too_large", label }; + continue; + } + yield { + kind: "message", + index: i, + folder: ref.path, + mime, + keywords: read.keywords, + receivedAt: utcDate(read.receivedAt), + label, + embeddedSkipped: read.embedded, + }; + } + } + } + return { total, items: items() }; +} diff --git a/lib/mail-import/pst/mime.ts b/lib/mail-import/pst/mime.ts new file mode 100644 index 000000000..43484f2fa --- /dev/null +++ b/lib/mail-import/pst/mime.ts @@ -0,0 +1,213 @@ +/** + * Builds an RFC 5322 message from what a PST stores about a message. Outlook + * keeps the parts, not the MIME: received mail has its original header block + * (PR_TRANSPORT_MESSAGE_HEADERS), sent mail and drafts have none. The header + * block is kept without its MIME fields and a new multipart is built from the + * bodies and attachments: + * + * mixed (when there are attachments) + * related (when the HTML shows cid: images) + * alternative (plain text and HTML) | the one body there is + * inline images + * attachments + * + * An RTF-only body (no plain text or HTML Outlook could give) is attached as + * message.rtf. Every part is UTF-8 and base64, so the result is 7-bit. + */ + +export interface MailAddress { + name?: string; + email: string; +} + +export interface PstAttachmentData { + filename: string; + mimeType: string; + contentId?: string; + data: Uint8Array; +} + +export interface PstMailData { + /** The original header block of received mail, if Outlook kept it. */ + headers?: string; + subject: string; + from?: MailAddress; + to: MailAddress[]; + cc: MailAddress[]; + bcc: MailAddress[]; + date?: Date; + messageId?: string; + inReplyTo?: string; + /** 0 low, 1 normal, 2 high. */ + importance?: number; + text?: string; + html?: string; + rtf?: string; + attachments: PstAttachmentData[]; +} + +const CRLF = "\r\n"; +const encoder = new TextEncoder(); + +function base64(bytes: Uint8Array): string { + let binary = ""; + for (let i = 0; i < bytes.length; i += 0x8000) { + binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000)); + } + return btoa(binary); +} + +/** base64 in lines of 76 characters. */ +export function base64Lines(bytes: Uint8Array): string { + const all = base64(bytes); + const lines: string[] = []; + for (let i = 0; i < all.length; i += 76) lines.push(all.slice(i, i + 76)); + return lines.join(CRLF); +} + +const PRINTABLE = /^[\x20-\x7e]*$/; + +/** RFC 2047 encoded words for a header text that isn't plain ASCII. */ +export function encodeHeaderText(text: string): string { + const clean = text.replace(/[\r\n]+/g, " "); + if (PRINTABLE.test(clean)) return clean; + // Each word holds at most 45 bytes (60 base64 characters), split between characters. + const words: string[] = []; + let chunk = ""; + for (const ch of clean) { + if (encoder.encode(chunk + ch).length > 45) { + words.push(chunk); + chunk = ""; + } + chunk += ch; + } + if (chunk) words.push(chunk); + return words.map((w) => `=?UTF-8?B?${base64(encoder.encode(w))}?=`).join(`${CRLF} `); +} + +function formatAddress(a: MailAddress): string { + const name = (a.name ?? "").trim().replace(/^['"]+|['"]+$/g, ""); + if (!name || name.toLowerCase() === a.email.toLowerCase()) return `<${a.email}>`; + if (PRINTABLE.test(name)) return `"${name.replace(/(["\\])/g, "\\$1")}" <${a.email}>`; + return `${encodeHeaderText(name)} <${a.email}>`; +} + +function addressList(list: MailAddress[]): string { + return list.map(formatAddress).join(`,${CRLF} `); +} + +const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + +/** RFC 5322 date in UTC. */ +export function rfc5322Date(date: Date): string { + const pad = (n: number) => String(n).padStart(2, "0"); + return `${DAYS[date.getUTCDay()]}, ${date.getUTCDate()} ${MONTHS[date.getUTCMonth()]} ${date.getUTCFullYear()} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())} +0000`; +} + +/** A filename parameter both old and new mail programs read. */ +function fileParams(name: string): string { + const safe = name.replace(/[\r\n"\\]/g, "_"); + if (PRINTABLE.test(safe)) return `; name="${safe}"`; + const pct = Array.from(encoder.encode(safe), (b) => + /[A-Za-z0-9!#$&+.^_`|~-]/.test(String.fromCharCode(b)) ? String.fromCharCode(b) : `%${b.toString(16).toUpperCase().padStart(2, "0")}`).join(""); + return `; name="${encodeHeaderText(safe).replace(/\r\n /g, " ")}"; name*=UTF-8''${pct}`; +} + +const MIME_FIELDS = /^(content-type|content-transfer-encoding|mime-version|content-disposition|content-length|content-md5)$/i; + +/** The header block without its MIME fields, unfolded lines kept, CRLF. */ +export function keptHeaders(block: string): { text: string; names: Set } { + const lines = block.replace(/\r?\n/g, "\n").replace(/\n+$/, "").split("\n"); + const fields: string[][] = []; + for (const line of lines) { + if (/^[ \t]/.test(line) && fields.length > 0) fields[fields.length - 1].push(line); + else if (line.includes(":")) fields.push([line]); + // A line without a colon outside a field isn't a header; drop it. + } + const names = new Set(); + const kept = fields.filter((f) => { + const name = f[0].slice(0, f[0].indexOf(":")).trim(); + if (MIME_FIELDS.test(name)) return false; + names.add(name.toLowerCase()); + return true; + }); + return { text: kept.map((f) => f.join(CRLF)).join(CRLF), names }; +} + +interface Part { + headers: string[]; + body: string; +} + +function leaf(contentType: string, data: Uint8Array, extra: string[] = []): Part { + return { headers: [`Content-Type: ${contentType}`, "Content-Transfer-Encoding: base64", ...extra], body: base64Lines(data) }; +} + +function textPart(text: string, subtype: "plain" | "html"): Part { + return leaf(`text/${subtype}; charset=utf-8`, encoder.encode(text.replace(/\r?\n/g, CRLF))); +} + +export interface MimeOptions { + /** Makes boundaries reproducible (tests). */ + boundarySeed?: string; +} + +export function buildMime(mail: PstMailData, options: MimeOptions = {}): Uint8Array { + const seed = options.boundarySeed ?? Math.random().toString(36).slice(2, 12); + let n = 0; + const boundary = () => `=_bulwark_${seed}_${n++}`; + const multipart = (subtype: string, parts: Part[]): Part => { + const b = boundary(); + return { + headers: [`Content-Type: multipart/${subtype};${CRLF} boundary="${b}"`], + body: parts.map((p) => `--${b}${CRLF}${p.headers.join(CRLF)}${CRLF}${CRLF}${p.body}${CRLF}`).join("") + `--${b}--`, + }; + }; + + const html = mail.html?.trim() ? mail.html : undefined; + const text = mail.text ?? ""; + const rtfOnly = !html && !text.trim() && !!mail.rtf; + let body: Part = html && text.trim() ? multipart("alternative", [textPart(text, "plain"), textPart(html, "html")]) + : html ? textPart(html, "html") + : textPart(text, "plain"); + + const isInline = (a: PstAttachmentData) => !!a.contentId && !!html && html.includes(`cid:${a.contentId}`); + const inline = mail.attachments.filter(isInline); + const attached = mail.attachments.filter((a) => !isInline(a)); + if (inline.length > 0) { + body = multipart("related", [body, ...inline.map((a) => leaf( + `${a.mimeType}${fileParams(a.filename)}`, + a.data, + [`Content-ID: <${a.contentId}>`, `Content-Disposition: inline${fileParams(a.filename).replace(/; name/g, "; filename")}`], + ))]); + } + const extra: Part[] = attached.map((a) => leaf( + `${a.mimeType}${fileParams(a.filename)}`, + a.data, + [`Content-Disposition: attachment${fileParams(a.filename).replace(/; name/g, "; filename")}`], + )); + if (rtfOnly) { + extra.push(leaf('application/rtf; name="message.rtf"', encoder.encode(mail.rtf!), ['Content-Disposition: attachment; filename="message.rtf"'])); + } + if (extra.length > 0) body = multipart("mixed", [body, ...extra]); + + const header: string[] = []; + const kept = mail.headers ? keptHeaders(mail.headers) : { text: "", names: new Set() }; + if (kept.text) header.push(kept.text); + const has = (name: string) => kept.names.has(name); + if (!has("date") && mail.date) header.push(`Date: ${rfc5322Date(mail.date)}`); + if (!has("from") && mail.from) header.push(`From: ${formatAddress(mail.from)}`); + if (!has("to") && mail.to.length > 0) header.push(`To: ${addressList(mail.to)}`); + if (!has("cc") && mail.cc.length > 0) header.push(`Cc: ${addressList(mail.cc)}`); + // Only sent mail and drafts have Bcc recipients; a received header block never names them. + if (!mail.headers && mail.bcc.length > 0) header.push(`Bcc: ${addressList(mail.bcc)}`); + if (!has("subject")) header.push(`Subject: ${encodeHeaderText(mail.subject)}`); + if (!has("message-id") && mail.messageId) header.push(`Message-ID: ${mail.messageId}`); + if (!has("in-reply-to") && mail.inReplyTo) header.push(`In-Reply-To: ${mail.inReplyTo}`); + if (!has("importance") && mail.importance === 2) header.push("Importance: high"); + if (!has("importance") && mail.importance === 0) header.push("Importance: low"); + header.push("MIME-Version: 1.0"); + header.push(...body.headers); + return encoder.encode(`${header.join(CRLF)}${CRLF}${CRLF}${body.body}${CRLF}`); +} diff --git a/lib/mail-import/pst/shims/buffer.ts b/lib/mail-import/pst/shims/buffer.ts new file mode 100644 index 000000000..1fe8d502b --- /dev/null +++ b/lib/mail-import/pst/shims/buffer.ts @@ -0,0 +1,4 @@ +// Injected into the PST worker: pst-extractor uses Node's global Buffer. +import { Buffer } from "buffer"; + +export { Buffer }; diff --git a/lib/mail-import/pst/shims/fs.ts b/lib/mail-import/pst/shims/fs.ts new file mode 100644 index 000000000..b4252c13b --- /dev/null +++ b/lib/mail-import/pst/shims/fs.ts @@ -0,0 +1,67 @@ +/** + * `fs` for pst-extractor inside the PST worker: random access into a File + * without loading it whole. pst-extractor only calls openSync, readSync and + * closeSync. Reads go through FileReaderSync (workers only) in 64 KB pages + * with a small cache, since the library reads a few bytes at a time. + * + * Register the file first: `registerPstFile(name, file)`. + */ + +declare const FileReaderSync: { new (): { readAsArrayBuffer(blob: Blob): ArrayBuffer } }; + +const PAGE = 64 * 1024; +const MAX_PAGES = 128; // 8 MB + +const files = new Map(); +const open = new Map }>(); +let nextFd = 3; + +export function registerPstFile(name: string, file: Blob): void { + files.set(name, file); +} + +export function openSync(name: string): number { + const file = files.get(name); + if (!file) throw new Error(`no such file: ${name}`); + const fd = nextFd++; + open.set(fd, { file, pages: new Map() }); + return fd; +} + +function page(entry: { file: Blob; pages: Map }, index: number): Uint8Array { + let bytes = entry.pages.get(index); + if (bytes) { + // Most recently used goes last. + entry.pages.delete(index); + entry.pages.set(index, bytes); + return bytes; + } + const start = index * PAGE; + bytes = new Uint8Array(new FileReaderSync().readAsArrayBuffer(entry.file.slice(start, Math.min(entry.file.size, start + PAGE)))); + entry.pages.set(index, bytes); + if (entry.pages.size > MAX_PAGES) entry.pages.delete(entry.pages.keys().next().value as number); + return bytes; +} + +export function readSync(fd: number, buffer: Uint8Array, offset: number, length: number, position: number): number { + const entry = open.get(fd); + if (!entry) throw new Error("bad file descriptor"); + const end = Math.min(entry.file.size, position + length); + let at = position; + while (at < end) { + const index = Math.floor(at / PAGE); + const bytes = page(entry, index); + const from = at - index * PAGE; + const take = Math.min(bytes.length - from, end - at); + if (take <= 0) break; + buffer.set(bytes.subarray(from, from + take), offset + (at - position)); + at += take; + } + return at - position; +} + +export function closeSync(fd: number): void { + open.delete(fd); +} + +export default { openSync, readSync, closeSync }; diff --git a/lib/mail-import/pst/shims/iconv.ts b/lib/mail-import/pst/shims/iconv.ts new file mode 100644 index 000000000..165e4def4 --- /dev/null +++ b/lib/mail-import/pst/shims/iconv.ts @@ -0,0 +1,16 @@ +// `iconv-lite` for pst-extractor, which only calls decode(bytes, codepage). +// TextDecoder knows the WHATWG labels (windows-125x, iso-8859-x, shift_jis, +// gbk, big5, ...) and keeps iconv-lite's 490 KB of tables out of the worker. +export function decode(data: Uint8Array, codepage: string): string { + let label = String(codepage || "utf-8").toLowerCase(); + if (label === "utf8") label = "utf-8"; + let decoder: TextDecoder; + try { + decoder = new TextDecoder(label); + } catch { + decoder = new TextDecoder("windows-1252"); + } + return decoder.decode(data); +} + +export default { decode }; diff --git a/lib/mail-import/pst/shims/zlib.ts b/lib/mail-import/pst/shims/zlib.ts new file mode 100644 index 000000000..5c7885645 --- /dev/null +++ b/lib/mail-import/pst/shims/zlib.ts @@ -0,0 +1,9 @@ +// `zlib` for pst-extractor: it only inflates the compressed blocks of OST 2013 files. +import { decompressSync } from "fflate"; +import { Buffer } from "buffer"; + +export function unzipSync(data: Uint8Array): Buffer { + return Buffer.from(decompressSync(new Uint8Array(data.buffer, data.byteOffset, data.byteLength))); +} + +export default { unzipSync }; diff --git a/lib/mail-import/pst/worker.ts b/lib/mail-import/pst/worker.ts new file mode 100644 index 000000000..97679289a --- /dev/null +++ b/lib/mail-import/pst/worker.ts @@ -0,0 +1,80 @@ +/** + * The PST worker (built by scripts/build-pst-worker.mjs into public/). It + * opens an Outlook .pst/.ost File without loading it whole, walks its mail + * folders and hands over one message at a time as MIME, only as many as the + * page asked for, so a large file never piles up in memory. + * + * page → worker { type: "open", file, skipBefore, maxMessageBytes } + * { type: "pull", count } + * worker → page { type: "opened", total } + * { type: "item", item, fraction } (item.mime is transferred) + * { type: "done" } | { type: "error", code, message } + */ + +import { PSTFile } from "pst-extractor"; +import { Buffer } from "buffer"; +import { registerPstFile } from "./shims/fs"; +import { walkPst, type PstItem } from "./items"; + +interface WorkerScope { + onmessage: ((event: MessageEvent) => void) | null; + postMessage(message: unknown, transfer?: Transferable[]): void; +} + +const scope = globalThis as unknown as WorkerScope; +// Attachment bytes are read into a Buffer over our own memory (extract.ts). +(globalThis as { Buffer?: unknown }).Buffer ??= Buffer; +let items: Generator | null = null; +let total = 0; +let done = 0; +let pst: { close(): void } | null = null; + +function errorCode(error: unknown): string { + const message = error instanceof Error ? error.message : String(error); + if (/encrypt/i.test(message)) return "encrypted"; + if (/not a pst|invalid|unrecognised|unrecognized|magic|!BDN/i.test(message)) return "not_pst"; + return "unreadable"; +} + +scope.onmessage = (event: MessageEvent) => { + const data = event.data as { type: string; file?: File; skipBefore?: number; maxMessageBytes?: number; count?: number }; + try { + if (data.type === "open" && data.file) { + registerPstFile("import.pst", data.file); + pst = new PSTFile("import.pst") as unknown as { close(): void }; + const walk = walkPst(pst, { skipBefore: data.skipBefore ?? 0, maxMessageBytes: data.maxMessageBytes ?? Number.POSITIVE_INFINITY }); + items = walk.items; + total = walk.total; + scope.postMessage({ type: "opened", total }); + return; + } + if (data.type === "pull" && items) { + for (let n = 0; n < (data.count ?? 1); n++) { + const next = items.next(); + if (next.done) { + items = null; + pst?.close(); + scope.postMessage({ type: "done" }); + return; + } + done++; + const item = next.value; + const fraction = total > 0 ? Math.min(1, done / total) : 0; + if (item.kind === "message") { + const buffer = item.mime.buffer.byteLength === item.mime.byteLength ? item.mime.buffer : item.mime.slice().buffer; + scope.postMessage({ type: "item", item: { ...item, mime: buffer }, fraction }, [buffer as ArrayBuffer]); + } else { + scope.postMessage({ type: "item", item, fraction }); + } + } + return; + } + if (data.type === "close") { + items = null; + pst?.close(); + pst = null; + } + } catch (error) { + scope.postMessage({ type: "error", code: errorCode(error), message: error instanceof Error ? error.message : String(error) }); + } +}; diff --git a/locales/ar/common.json b/locales/ar/common.json index 07c78763d..9da6fe53b 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "ليست رسالة صالحة", "untitled": "(بلا موضوع)", "cancelled_hint": "للمتابعة لاحقًا، استورد الملفات نفسها إلى المجلد نفسه مرة أخرى.", - "close": "إغلاق" + "close": "إغلاق", + "pst_note": "لا يُستورد من ملفات Outlook إلا البريد. تُعدّ جهات الاتصال وعناصر التقويم والمهام وتُترك.", + "pst_error_encrypted": "يستخدم ملف Outlook هذا تشفيرًا عاليًا لا يستطيع Bulwark قراءته. صدّر البريد في Outlook إلى ملف ‎.pst جديد بلا تشفير.", + "pst_error_not_pst": "هذا الملف ليس ملف بيانات Outlook يستطيع Bulwark قراءته.", + "pst_error_unreadable": "تعذّر على Bulwark قراءة ملف Outlook هذا.", + "pst_error_worker": "لم يُحمَّل قارئ ملفات Outlook. أعد تحميل الصفحة وحاول مرة أخرى." }, "recover_deleted": { "tab": "استعادة العناصر المحذوفة", diff --git a/locales/ca/common.json b/locales/ca/common.json index 3593795aa..ae1c1c8e8 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "no és un missatge vàlid", "untitled": "(sense assumpte)", "cancelled_hint": "Per continuar més tard, torneu a importar els mateixos fitxers a la mateixa carpeta.", - "close": "Tanca" + "close": "Tanca", + "pst_note": "Dels fitxers d'Outlook només s'importa el correu. Els contactes, elements del calendari i tasques es compten i es deixen fora.", + "pst_error_encrypted": "Aquest fitxer d'Outlook fa servir xifratge alt, que Bulwark no pot llegir. A Outlook, exporteu el correu a un fitxer .pst nou sense xifratge.", + "pst_error_not_pst": "Aquest fitxer no és un fitxer de dades d'Outlook que Bulwark pugui llegir.", + "pst_error_unreadable": "Bulwark no ha pogut llegir aquest fitxer d'Outlook.", + "pst_error_worker": "El lector de fitxers d'Outlook no s'ha carregat. Torneu a carregar la pàgina i proveu-ho de nou." }, "recover_deleted": { "tab": "Recupera elements suprimits", diff --git a/locales/cs/common.json b/locales/cs/common.json index 52e923127..04504cb34 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "není platná zpráva", "untitled": "(bez předmětu)", "cancelled_hint": "Chcete-li pokračovat později, importujte stejné soubory znovu do stejné složky.", - "close": "Zavřít" + "close": "Zavřít", + "pst_note": "Z outlookových souborů se přenáší jen pošta. Kontakty, položky kalendáře a úkoly se spočítají a vynechají.", + "pst_error_encrypted": "Tento soubor Outlooku používá vysoké šifrování, které Bulwark neumí přečíst. V Outlooku exportujte poštu do nového souboru .pst bez šifrování.", + "pst_error_not_pst": "Tento soubor není datový soubor Outlooku, který by Bulwark uměl přečíst.", + "pst_error_unreadable": "Bulwark tento soubor Outlooku nedokázal přečíst.", + "pst_error_worker": "Čtečka souborů Outlooku se nenačetla. Načtěte stránku znovu a zkuste to ještě jednou." }, "recover_deleted": { "tab": "Obnovit smazané položky", diff --git a/locales/da/common.json b/locales/da/common.json index b2ccff94f..82cc9cfdd 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "ikke en gyldig besked", "untitled": "(intet emne)", "cancelled_hint": "Importér de samme filer til samme mappe igen for at fortsætte senere.", - "close": "Luk" + "close": "Luk", + "pst_note": "Fra Outlook-filer kommer kun mail med. Kontakter, kalenderelementer og opgaver tælles og springes over.", + "pst_error_encrypted": "Denne Outlook-fil bruger høj kryptering, som Bulwark ikke kan læse. Eksportér mailen i Outlook til en ny .pst-fil uden kryptering.", + "pst_error_not_pst": "Denne fil er ikke en Outlook-datafil, som Bulwark kan læse.", + "pst_error_unreadable": "Bulwark kunne ikke læse denne Outlook-fil.", + "pst_error_worker": "Læseren til Outlook-filer blev ikke indlæst. Genindlæs siden, og prøv igen." }, "recover_deleted": { "tab": "Gendan slettede elementer", diff --git a/locales/de/common.json b/locales/de/common.json index ba2cff1bb..824c4b2ec 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "keine gültige Nachricht", "untitled": "(kein Betreff)", "cancelled_hint": "Um später weiterzumachen, importieren Sie dieselben Dateien erneut in denselben Ordner.", - "close": "Schließen" + "close": "Schließen", + "pst_note": "Aus Outlook-Dateien werden nur E-Mails übernommen. Kontakte, Kalendereinträge und Aufgaben werden gezählt und ausgelassen.", + "pst_error_encrypted": "Diese Outlook-Datei nutzt eine hohe Verschlüsselung, die Bulwark nicht lesen kann. Exportieren Sie die E-Mails in Outlook in eine neue .pst-Datei ohne Verschlüsselung.", + "pst_error_not_pst": "Diese Datei ist keine Outlook-Datendatei, die Bulwark lesen kann.", + "pst_error_unreadable": "Bulwark konnte diese Outlook-Datei nicht lesen.", + "pst_error_worker": "Das Lesemodul für Outlook-Dateien wurde nicht geladen. Laden Sie die Seite neu und versuchen Sie es noch einmal." }, "recover_deleted": { "tab": "Gelöschte E-Mails", diff --git a/locales/en/common.json b/locales/en/common.json index 6c04c2c30..7861331f4 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "not a valid message", "untitled": "(no subject)", "cancelled_hint": "To go on later, import the same files into the same folder again.", - "close": "Close" + "close": "Close", + "pst_note": "Only mail comes in from Outlook files. Contacts, calendar items and tasks are counted and left out.", + "pst_error_encrypted": "This Outlook file uses high encryption, which Bulwark can't read. In Outlook, export the mail to a new .pst file without encryption.", + "pst_error_not_pst": "This file isn't an Outlook data file Bulwark can read.", + "pst_error_unreadable": "Bulwark couldn't read this Outlook file.", + "pst_error_worker": "The Outlook file reader didn't load. Reload the page and try again." }, "recover_deleted": { "tab": "Recover deleted items", diff --git a/locales/es/common.json b/locales/es/common.json index d2a079456..153a60cfd 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "no es un mensaje válido", "untitled": "(sin asunto)", "cancelled_hint": "Para seguir más tarde, importe los mismos archivos en la misma carpeta otra vez.", - "close": "Cerrar" + "close": "Cerrar", + "pst_note": "De los archivos de Outlook solo se importa el correo. Los contactos, elementos de calendario y tareas se cuentan y se dejan fuera.", + "pst_error_encrypted": "Este archivo de Outlook usa cifrado alto, que Bulwark no puede leer. En Outlook, exporte el correo a un archivo .pst nuevo sin cifrado.", + "pst_error_not_pst": "Este archivo no es un archivo de datos de Outlook que Bulwark pueda leer.", + "pst_error_unreadable": "Bulwark no pudo leer este archivo de Outlook.", + "pst_error_worker": "El lector de archivos de Outlook no se cargó. Vuelva a cargar la página e inténtelo de nuevo." }, "recover_deleted": { "tab": "Recuperar elementos eliminados", diff --git a/locales/et/common.json b/locales/et/common.json index 64749372d..6eb510729 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "pole kehtiv kiri", "untitled": "(teemata)", "cancelled_hint": "Hiljem jätkamiseks importige samad failid uuesti samasse kausta.", - "close": "Sulge" + "close": "Sulge", + "pst_note": "Outlooki failidest tulevad ainult kirjad. Kontaktid, kalendrikirjed ja ülesanded loendatakse ja jäetakse välja.", + "pst_error_encrypted": "See Outlooki fail kasutab tugevat krüpteerimist, mida Bulwark lugeda ei saa. Eksportige kirjad Outlookis uude krüpteerimata .pst-faili.", + "pst_error_not_pst": "See fail pole Outlooki andmefail, mida Bulwark lugeda oskab.", + "pst_error_unreadable": "Bulwark ei saanud seda Outlooki faili lugeda.", + "pst_error_worker": "Outlooki failide lugeja ei laadinud. Laadige leht uuesti ja proovige veel kord." }, "recover_deleted": { "tab": "Taasta kustutatud üksused", diff --git a/locales/fa/common.json b/locales/fa/common.json index 4e273d67b..c45fa898d 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "پیام معتبری نیست", "untitled": "(بدون موضوع)", "cancelled_hint": "برای ادامه در زمانی دیگر، همان فایل‌ها را دوباره در همان پوشه درون‌ریزی کنید.", - "close": "بستن" + "close": "بستن", + "pst_note": "از فایل‌های اوت‌لوک فقط نامه‌ها می‌آیند. مخاطبان، موارد تقویم و کارها شمرده و کنار گذاشته می‌شوند.", + "pst_error_encrypted": "این فایل اوت‌لوک از رمزگذاری قوی استفاده می‌کند که Bulwark نمی‌تواند بخواند. در اوت‌لوک، نامه‌ها را به یک فایل ‎.pst تازه بدون رمزگذاری صادر کنید.", + "pst_error_not_pst": "این فایل، فایل داده اوت‌لوکی نیست که Bulwark بتواند بخواند.", + "pst_error_unreadable": "Bulwark نتوانست این فایل اوت‌لوک را بخواند.", + "pst_error_worker": "خواننده فایل‌های اوت‌لوک بارگیری نشد. صفحه را دوباره بارگیری کنید و دوباره امتحان کنید." }, "recover_deleted": { "tab": "بازیابی موارد حذف‌شده", diff --git a/locales/fr/common.json b/locales/fr/common.json index 75c1f268d..245078762 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "message non valide", "untitled": "(sans objet)", "cancelled_hint": "Pour reprendre plus tard, importez à nouveau les mêmes fichiers dans le même dossier.", - "close": "Fermer" + "close": "Fermer", + "pst_note": "Seuls les e-mails sont importés des fichiers Outlook. Les contacts, éléments d'agenda et tâches sont comptés et laissés de côté.", + "pst_error_encrypted": "Ce fichier Outlook utilise un chiffrement élevé que Bulwark ne sait pas lire. Dans Outlook, exportez les e-mails dans un nouveau fichier .pst sans chiffrement.", + "pst_error_not_pst": "Ce fichier n'est pas un fichier de données Outlook que Bulwark sait lire.", + "pst_error_unreadable": "Bulwark n'a pas pu lire ce fichier Outlook.", + "pst_error_worker": "Le lecteur de fichiers Outlook ne s'est pas chargé. Rechargez la page et réessayez." }, "recover_deleted": { "tab": "Récupérer des éléments supprimés", diff --git a/locales/he/common.json b/locales/he/common.json index 2b6e185c5..91b1b5de5 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -5429,7 +5429,12 @@ "failure_invalid": "זו אינה הודעה תקינה", "untitled": "(ללא נושא)", "cancelled_hint": "כדי להמשיך מאוחר יותר, ייבאו שוב את אותם קבצים לאותה תיקייה.", - "close": "סגירה" + "close": "סגירה", + "pst_note": "מקובצי Outlook מגיע רק דואר. אנשי קשר, פריטי לוח שנה ומשימות נספרים ומושמטים.", + "pst_error_encrypted": "קובץ Outlook זה משתמש בהצפנה גבוהה ש-Bulwark אינו יכול לקרוא. ב-Outlook, ייצאו את הדואר לקובץ ‎.pst חדש ללא הצפנה.", + "pst_error_not_pst": "קובץ זה אינו קובץ נתונים של Outlook ש-Bulwark יכול לקרוא.", + "pst_error_unreadable": "Bulwark לא הצליח לקרוא את קובץ ה-Outlook הזה.", + "pst_error_worker": "קורא קובצי Outlook לא נטען. טענו את הדף מחדש ונסו שוב." }, "recover_deleted": { "tab": "שחזור פריטים שנמחקו", diff --git a/locales/hu/common.json b/locales/hu/common.json index 3114c2f2b..7c9066bf9 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "nem érvényes üzenet", "untitled": "(nincs tárgy)", "cancelled_hint": "A későbbi folytatáshoz importálja újra ugyanazokat a fájlokat ugyanabba a mappába.", - "close": "Bezárás" + "close": "Bezárás", + "pst_note": "Az Outlook-fájlokból csak a levelek kerülnek át. A névjegyeket, naptárelemeket és feladatokat megszámolja és kihagyja.", + "pst_error_encrypted": "Ez az Outlook-fájl erős titkosítást használ, amelyet a Bulwark nem tud olvasni. Az Outlookban exportálja a leveleket egy új, titkosítás nélküli .pst fájlba.", + "pst_error_not_pst": "Ez a fájl nem olyan Outlook-adatfájl, amelyet a Bulwark olvasni tud.", + "pst_error_unreadable": "A Bulwark nem tudta beolvasni ezt az Outlook-fájlt.", + "pst_error_worker": "Az Outlook-fájlok olvasója nem töltődött be. Töltse újra az oldalt, és próbálja meg ismét." }, "recover_deleted": { "tab": "Törölt elemek visszaállítása", diff --git a/locales/it/common.json b/locales/it/common.json index 2a5d26089..c7f446ad1 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "non è un messaggio valido", "untitled": "(nessun oggetto)", "cancelled_hint": "Per riprendere più tardi, importa di nuovo gli stessi file nella stessa cartella.", - "close": "Chiudi" + "close": "Chiudi", + "pst_note": "Dai file di Outlook viene importata solo la posta. Contatti, elementi del calendario e attività vengono contati e lasciati fuori.", + "pst_error_encrypted": "Questo file di Outlook usa la crittografia elevata, che Bulwark non sa leggere. In Outlook, esporta la posta in un nuovo file .pst senza crittografia.", + "pst_error_not_pst": "Questo file non è un file di dati di Outlook che Bulwark sa leggere.", + "pst_error_unreadable": "Bulwark non è riuscito a leggere questo file di Outlook.", + "pst_error_worker": "Il lettore dei file di Outlook non si è caricato. Ricarica la pagina e riprova." }, "recover_deleted": { "tab": "Recupera elementi eliminati", diff --git a/locales/ja/common.json b/locales/ja/common.json index 23bd8190c..8ac38e620 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "有効なメッセージではありません", "untitled": "(件名なし)", "cancelled_hint": "後で続けるには、同じファイルを同じフォルダーにもう一度インポートしてください。", - "close": "閉じる" + "close": "閉じる", + "pst_note": "Outlook ファイルから取り込むのはメールだけです。連絡先、カレンダーの項目、タスクは数えて除外します。", + "pst_error_encrypted": "この Outlook ファイルは Bulwark が読めない高度な暗号化を使っています。Outlook でメールを暗号化なしの新しい .pst ファイルにエクスポートしてください。", + "pst_error_not_pst": "このファイルは Bulwark が読める Outlook データファイルではありません。", + "pst_error_unreadable": "Bulwark はこの Outlook ファイルを読めませんでした。", + "pst_error_worker": "Outlook ファイルの読み込み機能を読み込めませんでした。ページを再読み込みして、もう一度お試しください。" }, "recover_deleted": { "tab": "削除済みアイテムの復元", diff --git a/locales/ko/common.json b/locales/ko/common.json index 95bda3dbb..f43c3107f 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "올바른 메시지가 아님", "untitled": "(제목 없음)", "cancelled_hint": "나중에 계속하려면 같은 파일을 같은 폴더로 다시 가져오세요.", - "close": "닫기" + "close": "닫기", + "pst_note": "Outlook 파일에서는 메일만 가져옵니다. 연락처, 캘린더 항목, 작업은 개수만 세고 건너뜁니다.", + "pst_error_encrypted": "이 Outlook 파일은 Bulwark가 읽을 수 없는 강력한 암호화를 사용합니다. Outlook에서 메일을 암호화하지 않은 새 .pst 파일로 내보내세요.", + "pst_error_not_pst": "이 파일은 Bulwark가 읽을 수 있는 Outlook 데이터 파일이 아닙니다.", + "pst_error_unreadable": "Bulwark가 이 Outlook 파일을 읽지 못했습니다.", + "pst_error_worker": "Outlook 파일 읽기 도구를 불러오지 못했습니다. 페이지를 새로 고친 뒤 다시 시도하세요." }, "recover_deleted": { "tab": "삭제된 항목 복구", diff --git a/locales/lv/common.json b/locales/lv/common.json index 3751f5cbb..4f62298de 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "nav derīgs ziņojums", "untitled": "(bez temata)", "cancelled_hint": "Lai turpinātu vēlāk, vēlreiz importējiet tos pašus failus tajā pašā mapē.", - "close": "Aizvērt" + "close": "Aizvērt", + "pst_note": "No Outlook failiem tiek pārnests tikai pasts. Kontakti, kalendāra vienumi un uzdevumi tiek saskaitīti un izlaisti.", + "pst_error_encrypted": "Šis Outlook fails izmanto augstu šifrēšanu, ko Bulwark nevar nolasīt. Programmā Outlook eksportējiet pastu jaunā .pst failā bez šifrēšanas.", + "pst_error_not_pst": "Šis fails nav Outlook datu fails, ko Bulwark var nolasīt.", + "pst_error_unreadable": "Bulwark nevarēja nolasīt šo Outlook failu.", + "pst_error_worker": "Outlook failu lasītājs netika ielādēts. Pārlādējiet lapu un mēģiniet vēlreiz." }, "recover_deleted": { "tab": "Atjaunot dzēstos vienumus", diff --git a/locales/mn/common.json b/locales/mn/common.json index bccb42c7d..00a5218a9 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "хүчинтэй захидал биш", "untitled": "(гарчиггүй)", "cancelled_hint": "Дараа үргэлжлүүлэхийн тулд ижил файлуудыг ижил хавтас руу дахин импортлоно уу.", - "close": "Хаах" + "close": "Хаах", + "pst_note": "Outlook-ийн файлаас зөвхөн шуудан орно. Харилцагч, хуанлийн зүйл, даалгаврыг тоолоод алгасна.", + "pst_error_encrypted": "Энэ Outlook файл Bulwark уншиж чадахгүй хүчтэй шифрлэлт ашигладаг. Outlook-д шуудангаа шифрлэлтгүй шинэ .pst файл руу экспортлоно уу.", + "pst_error_not_pst": "Энэ файл Bulwark уншиж чадах Outlook-ийн өгөгдлийн файл биш байна.", + "pst_error_unreadable": "Bulwark энэ Outlook файлыг уншиж чадсангүй.", + "pst_error_worker": "Outlook файл уншигч ачаалагдсангүй. Хуудсыг дахин ачаалаад дахин оролдоно уу." }, "recover_deleted": { "tab": "Устгасан зүйлсийг сэргээх", diff --git a/locales/nb/common.json b/locales/nb/common.json index 87a01f677..fa8501a8c 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "ikke en gyldig melding", "untitled": "(uten emne)", "cancelled_hint": "Importer de samme filene til samme mappe igjen for å fortsette senere.", - "close": "Lukk" + "close": "Lukk", + "pst_note": "Fra Outlook-filer kommer bare e-post med. Kontakter, kalenderelementer og oppgaver telles og hoppes over.", + "pst_error_encrypted": "Denne Outlook-filen bruker høy kryptering, som Bulwark ikke kan lese. Eksporter e-posten i Outlook til en ny .pst-fil uten kryptering.", + "pst_error_not_pst": "Denne filen er ikke en Outlook-datafil som Bulwark kan lese.", + "pst_error_unreadable": "Bulwark kunne ikke lese denne Outlook-filen.", + "pst_error_worker": "Leseren for Outlook-filer ble ikke lastet. Last inn siden på nytt og prøv igjen." }, "recover_deleted": { "tab": "Gjenopprett slettede elementer", diff --git a/locales/nl/common.json b/locales/nl/common.json index 494aa0e9d..0588ad1bb 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "geen geldig bericht", "untitled": "(geen onderwerp)", "cancelled_hint": "Importeer later dezelfde bestanden opnieuw in dezelfde map om verder te gaan.", - "close": "Sluiten" + "close": "Sluiten", + "pst_note": "Uit Outlook-bestanden komt alleen e-mail mee. Contacten, agenda-items en taken worden geteld en overgeslagen.", + "pst_error_encrypted": "Dit Outlook-bestand gebruikt sterke versleuteling, die Bulwark niet kan lezen. Exporteer de e-mail in Outlook naar een nieuw .pst-bestand zonder versleuteling.", + "pst_error_not_pst": "Dit bestand is geen Outlook-gegevensbestand dat Bulwark kan lezen.", + "pst_error_unreadable": "Bulwark kon dit Outlook-bestand niet lezen.", + "pst_error_worker": "De lezer voor Outlook-bestanden is niet geladen. Laad de pagina opnieuw en probeer het nog eens." }, "recover_deleted": { "tab": "Verwijderde items herstellen", diff --git a/locales/pl/common.json b/locales/pl/common.json index d44b6768b..5cb9b6aef 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "to nie jest poprawna wiadomość", "untitled": "(bez tematu)", "cancelled_hint": "Aby kontynuować później, zaimportuj te same pliki ponownie do tego samego folderu.", - "close": "Zamknij" + "close": "Zamknij", + "pst_note": "Z plików programu Outlook przenoszona jest tylko poczta. Kontakty, elementy kalendarza i zadania są liczone i pomijane.", + "pst_error_encrypted": "Ten plik programu Outlook używa silnego szyfrowania, którego Bulwark nie potrafi odczytać. W programie Outlook wyeksportuj pocztę do nowego pliku .pst bez szyfrowania.", + "pst_error_not_pst": "Ten plik nie jest plikiem danych programu Outlook, który Bulwark potrafi odczytać.", + "pst_error_unreadable": "Bulwark nie mógł odczytać tego pliku programu Outlook.", + "pst_error_worker": "Czytnik plików programu Outlook się nie wczytał. Odśwież stronę i spróbuj ponownie." }, "recover_deleted": { "tab": "Odzyskaj usunięte elementy", diff --git a/locales/pt/common.json b/locales/pt/common.json index c9f6357dc..f6dd85f8e 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "não é uma mensagem válida", "untitled": "(sem assunto)", "cancelled_hint": "Para continuar mais tarde, importe os mesmos arquivos na mesma pasta outra vez.", - "close": "Fechar" + "close": "Fechar", + "pst_note": "Dos arquivos do Outlook só vêm os e-mails. Contatos, itens de calendário e tarefas são contados e deixados de fora.", + "pst_error_encrypted": "Este arquivo do Outlook usa criptografia alta, que o Bulwark não consegue ler. No Outlook, exporte os e-mails para um novo arquivo .pst sem criptografia.", + "pst_error_not_pst": "Este arquivo não é um arquivo de dados do Outlook que o Bulwark consiga ler.", + "pst_error_unreadable": "O Bulwark não conseguiu ler este arquivo do Outlook.", + "pst_error_worker": "O leitor de arquivos do Outlook não carregou. Recarregue a página e tente de novo." }, "recover_deleted": { "tab": "Recuperar itens excluídos", diff --git a/locales/ro/common.json b/locales/ro/common.json index 93e0a1ccc..ce94bfda4 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "nu este un mesaj valid", "untitled": "(fără subiect)", "cancelled_hint": "Pentru a continua mai târziu, importați din nou aceleași fișiere în același dosar.", - "close": "Închide" + "close": "Închide", + "pst_note": "Din fișierele Outlook vin doar e-mailurile. Contactele, elementele de calendar și sarcinile sunt numărate și lăsate deoparte.", + "pst_error_encrypted": "Acest fișier Outlook folosește criptare puternică, pe care Bulwark nu o poate citi. În Outlook, exportați e-mailurile într-un fișier .pst nou, fără criptare.", + "pst_error_not_pst": "Acest fișier nu este un fișier de date Outlook pe care Bulwark îl poate citi.", + "pst_error_unreadable": "Bulwark nu a putut citi acest fișier Outlook.", + "pst_error_worker": "Cititorul de fișiere Outlook nu s-a încărcat. Reîncărcați pagina și încercați din nou." }, "recover_deleted": { "tab": "Recuperați elementele șterse", diff --git a/locales/ru/common.json b/locales/ru/common.json index 747c4f1dd..1ad24d970 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "недопустимое сообщение", "untitled": "(без темы)", "cancelled_hint": "Чтобы продолжить позже, снова импортируйте те же файлы в ту же папку.", - "close": "Закрыть" + "close": "Закрыть", + "pst_note": "Из файлов Outlook переносится только почта. Контакты, элементы календаря и задачи подсчитываются и пропускаются.", + "pst_error_encrypted": "В этом файле Outlook используется сильное шифрование, которое Bulwark не может прочитать. Экспортируйте почту в Outlook в новый файл .pst без шифрования.", + "pst_error_not_pst": "Этот файл не является файлом данных Outlook, который Bulwark может прочитать.", + "pst_error_unreadable": "Bulwark не смог прочитать этот файл Outlook.", + "pst_error_worker": "Модуль чтения файлов Outlook не загрузился. Перезагрузите страницу и попробуйте снова." }, "recover_deleted": { "tab": "Восстановить удалённое", diff --git a/locales/sk/common.json b/locales/sk/common.json index 6e0fc7ec4..e5f1b7087 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "nie je platná správa", "untitled": "(bez predmetu)", "cancelled_hint": "Ak chcete pokračovať neskôr, importujte rovnaké súbory znova do rovnakého priečinka.", - "close": "Zavrieť" + "close": "Zavrieť", + "pst_note": "Zo súborov Outlooku sa prenáša len pošta. Kontakty, položky kalendára a úlohy sa spočítajú a vynechajú.", + "pst_error_encrypted": "Tento súbor Outlooku používa vysoké šifrovanie, ktoré Bulwark nevie prečítať. V Outlooku exportujte poštu do nového súboru .pst bez šifrovania.", + "pst_error_not_pst": "Tento súbor nie je dátový súbor Outlooku, ktorý by Bulwark vedel prečítať.", + "pst_error_unreadable": "Bulwark nedokázal prečítať tento súbor Outlooku.", + "pst_error_worker": "Čítačka súborov Outlooku sa nenačítala. Načítajte stránku znova a skúste to ešte raz." }, "recover_deleted": { "tab": "Obnoviť odstránené položky", diff --git a/locales/th/common.json b/locales/th/common.json index 4b789fbdf..415c0b198 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -445,7 +445,12 @@ "failure_invalid": "ไม่ใช่ข้อความที่ถูกต้อง", "untitled": "(ไม่มีหัวเรื่อง)", "cancelled_hint": "หากต้องการทำต่อภายหลัง ให้นำเข้าไฟล์เดิมไปยังโฟลเดอร์เดิมอีกครั้ง", - "close": "ปิด" + "close": "ปิด", + "pst_note": "จากไฟล์ Outlook จะนำเข้าเฉพาะอีเมล รายชื่อติดต่อ รายการปฏิทิน และงานจะถูกนับแล้วข้ามไป", + "pst_error_encrypted": "ไฟล์ Outlook นี้ใช้การเข้ารหัสระดับสูงที่ Bulwark อ่านไม่ได้ ใน Outlook ให้ส่งออกอีเมลไปยังไฟล์ .pst ใหม่ที่ไม่เข้ารหัส", + "pst_error_not_pst": "ไฟล์นี้ไม่ใช่ไฟล์ข้อมูล Outlook ที่ Bulwark อ่านได้", + "pst_error_unreadable": "Bulwark อ่านไฟล์ Outlook นี้ไม่ได้", + "pst_error_worker": "ตัวอ่านไฟล์ Outlook โหลดไม่สำเร็จ โหลดหน้าใหม่แล้วลองอีกครั้ง" }, "recover_deleted": { "tab": "กู้คืนรายการที่ลบ", diff --git a/locales/tr/common.json b/locales/tr/common.json index 4dbf796a0..246755415 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "geçerli bir ileti değil", "untitled": "(konu yok)", "cancelled_hint": "Daha sonra devam etmek için aynı dosyaları aynı klasöre yeniden içe aktarın.", - "close": "Kapat" + "close": "Kapat", + "pst_note": "Outlook dosyalarından yalnızca e-postalar alınır. Kişiler, takvim öğeleri ve görevler sayılır ve atlanır.", + "pst_error_encrypted": "Bu Outlook dosyası Bulwark'ın okuyamadığı yüksek şifreleme kullanıyor. Outlook'ta e-postaları şifrelemesiz yeni bir .pst dosyasına aktarın.", + "pst_error_not_pst": "Bu dosya Bulwark'ın okuyabildiği bir Outlook veri dosyası değil.", + "pst_error_unreadable": "Bulwark bu Outlook dosyasını okuyamadı.", + "pst_error_worker": "Outlook dosya okuyucusu yüklenmedi. Sayfayı yeniden yükleyip tekrar deneyin." }, "recover_deleted": { "tab": "Silinen öğeleri kurtar", diff --git a/locales/uk/common.json b/locales/uk/common.json index dfa3fd6a4..f3a34b421 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "недійсне повідомлення", "untitled": "(без теми)", "cancelled_hint": "Щоб продовжити пізніше, знову імпортуйте ті самі файли до тієї самої теки.", - "close": "Закрити" + "close": "Закрити", + "pst_note": "З файлів Outlook переноситься лише пошта. Контакти, елементи календаря та завдання підраховуються й пропускаються.", + "pst_error_encrypted": "У цьому файлі Outlook використано сильне шифрування, яке Bulwark не може прочитати. Експортуйте пошту в Outlook до нового файлу .pst без шифрування.", + "pst_error_not_pst": "Цей файл не є файлом даних Outlook, який Bulwark може прочитати.", + "pst_error_unreadable": "Bulwark не зміг прочитати цей файл Outlook.", + "pst_error_worker": "Модуль читання файлів Outlook не завантажився. Перезавантажте сторінку й спробуйте ще раз." }, "recover_deleted": { "tab": "Відновити видалене", diff --git a/locales/vi/common.json b/locales/vi/common.json index f6610f6a5..c4249585d 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -5440,7 +5440,12 @@ "failure_invalid": "không phải thư hợp lệ", "untitled": "(không có tiêu đề)", "cancelled_hint": "Để tiếp tục sau, hãy nhập lại cùng các tệp vào cùng thư mục.", - "close": "Đóng" + "close": "Đóng", + "pst_note": "Từ tệp Outlook chỉ nhập thư. Danh bạ, mục lịch và công việc được đếm rồi bỏ qua.", + "pst_error_encrypted": "Tệp Outlook này dùng mã hóa mạnh mà Bulwark không đọc được. Trong Outlook, hãy xuất thư sang một tệp .pst mới không mã hóa.", + "pst_error_not_pst": "Tệp này không phải tệp dữ liệu Outlook mà Bulwark đọc được.", + "pst_error_unreadable": "Bulwark không đọc được tệp Outlook này.", + "pst_error_worker": "Không tải được trình đọc tệp Outlook. Hãy tải lại trang rồi thử lại." }, "recover_deleted": { "tab": "Khôi phục các mục đã xóa", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 6c61c7c9c..113ad5ee4 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -5428,7 +5428,12 @@ "failure_invalid": "不是有效的郵件", "untitled": "(無主旨)", "cancelled_hint": "如需稍後繼續,請再次把相同的檔案匯入到同一個資料夾。", - "close": "關閉" + "close": "關閉", + "pst_note": "Outlook 檔案中只匯入郵件。聯絡人、行事曆項目和工作會被計數並略過。", + "pst_error_encrypted": "此 Outlook 檔案使用了 Bulwark 無法讀取的高強度加密。請在 Outlook 中把郵件匯出到一個不加密的新 .pst 檔案。", + "pst_error_not_pst": "此檔案不是 Bulwark 能讀取的 Outlook 資料檔。", + "pst_error_unreadable": "Bulwark 無法讀取此 Outlook 檔案。", + "pst_error_worker": "Outlook 檔案讀取元件未能載入。請重新載入頁面後再試。" }, "recover_deleted": { "tab": "復原已刪除的項目", diff --git a/locales/zh/common.json b/locales/zh/common.json index 47bb2946d..5f2d5cc1e 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -5298,7 +5298,12 @@ "failure_invalid": "不是有效的邮件", "untitled": "(无主题)", "cancelled_hint": "如需稍后继续,请再次把相同的文件导入到同一个文件夹。", - "close": "关闭" + "close": "关闭", + "pst_note": "Outlook 文件中只导入邮件。联系人、日历项目和任务会被计数并跳过。", + "pst_error_encrypted": "此 Outlook 文件使用了 Bulwark 无法读取的高强度加密。请在 Outlook 中把邮件导出到一个不加密的新 .pst 文件。", + "pst_error_not_pst": "此文件不是 Bulwark 能读取的 Outlook 数据文件。", + "pst_error_unreadable": "Bulwark 无法读取此 Outlook 文件。", + "pst_error_worker": "Outlook 文件读取组件未能加载。请重新加载页面后再试。" }, "recover_deleted": { "tab": "恢复已删除项目", diff --git a/package-lock.json b/package-lock.json index 390986c08..b4785ce20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "@tiptap/react": "^3.32.1", "@tiptap/starter-kit": "^3.32.1", "@tiptap/suggestion": "^3.32.1", + "buffer": "6.0.3", "clsx": "^2.1.1", "date-fns": "^4.4.0", "dompurify": "^3.4.12", @@ -44,6 +45,7 @@ "otpauth": "^9.5.2", "pdfjs-dist": "^6.4.299", "postal-mime": "^4.1.4", + "pst-extractor": "1.12.0", "qrcode": "^1.5.4", "react": "^19.3.1", "react-dom": "^19.3.1", @@ -4922,6 +4924,26 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.0.tgz", @@ -5001,6 +5023,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -6542,6 +6588,22 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/icu-minify": { "version": "4.14.9", "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.14.9.tgz", @@ -6557,6 +6619,26 @@ "@formatjs/icu-messageformat-parser": "^3.4.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -7619,6 +7701,12 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -8533,6 +8621,20 @@ "prosemirror-transform": "^1.1.0" } }, + "node_modules/pst-extractor": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/pst-extractor/-/pst-extractor-1.12.0.tgz", + "integrity": "sha512-mO87iT2FGXc3MZVQ8YVIHn8GghM3WI8Dvqn872JEYXo0Bi5EFewS6hlmdPTasfFzrAJgJgnuA8EXSYSxIrCcyQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.7.1", + "long": "^5.3.2", + "uuid-parse": "^1.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8825,6 +8927,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -9728,6 +9836,12 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", + "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==", + "license": "MIT" + }, "node_modules/vite": { "version": "8.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", diff --git a/package.json b/package.json index 0e355eb0e..d7745c563 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "typescript" ], "scripts": { - "dev": "node scripts/build-plugin-sandbox.mjs --watch -- next dev --turbopack", - "build": "node scripts/build-plugin-sandbox.mjs && next build --turbopack", + "dev": "node scripts/build-pst-worker.mjs && node scripts/build-plugin-sandbox.mjs --watch -- next dev --turbopack", + "build": "node scripts/build-pst-worker.mjs && node scripts/build-plugin-sandbox.mjs && next build --turbopack", "build:lite": "node scripts/lite/build.mjs", "lite:serve": "node scripts/lite/serve.mjs out 4173", "test:lite-smoke": "playwright test -c scripts/lite/smoke/playwright.config.ts", @@ -62,6 +62,7 @@ "@tiptap/react": "^3.32.1", "@tiptap/starter-kit": "^3.32.1", "@tiptap/suggestion": "^3.32.1", + "buffer": "6.0.3", "clsx": "^2.1.1", "date-fns": "^4.4.0", "dompurify": "^3.4.12", @@ -74,6 +75,7 @@ "otpauth": "^9.5.2", "pdfjs-dist": "^6.4.299", "postal-mime": "^4.1.4", + "pst-extractor": "1.12.0", "qrcode": "^1.5.4", "react": "^19.3.1", "react-dom": "^19.3.1", diff --git a/scripts/build-pst-worker.mjs b/scripts/build-pst-worker.mjs new file mode 100644 index 000000000..a61c2cc01 --- /dev/null +++ b/scripts/build-pst-worker.mjs @@ -0,0 +1,49 @@ +// Builds the Outlook PST import worker (lib/mail-import/pst/worker.ts) into +// public/pst-worker.js. The page loads it only when someone imports a .pst +// or .ost file (lib/mail-import/pst-source.ts). +// +// pst-extractor is written for Node: it needs Buffer, `fs` (only to read the +// file in pieces), `zlib` (only to inflate OST 2013 blocks) and iconv-lite +// (only to decode text in a codepage). In the worker those become the +// `buffer` package, a File-backed fs, fflate and TextDecoder; iconv-lite's +// tables alone would be three times the rest of the worker. +// +// node scripts/build-pst-worker.mjs (npm run build, npm run dev, npm run build:lite) + +import { build } from 'esbuild'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +export const PST_WORKER_OUTFILE = path.join(root, 'public', 'pst-worker.js'); + +export async function buildPstWorker({ outfile = PST_WORKER_OUTFILE } = {}) { + const shims = path.join(root, 'lib/mail-import/pst/shims'); + const result = await build({ + entryPoints: [path.join(root, 'lib/mail-import/pst/worker.ts')], + bundle: true, + format: 'iife', + platform: 'browser', + target: 'es2020', + outfile, + minify: true, + // The licences of pst-extractor, buffer, fflate and long stay with their code. + legalComments: 'eof', + alias: { + '@': root, + fs: path.join(shims, 'fs.ts'), + zlib: path.join(shims, 'zlib.ts'), + 'iconv-lite': path.join(shims, 'iconv.ts'), + }, + inject: [path.join(shims, 'buffer.ts')], + define: { 'process.env.NODE_ENV': '"production"', global: 'globalThis' }, + metafile: true, + logLevel: 'warning', + }); + return { bytes: Object.values(result.metafile.outputs).reduce((n, o) => n + o.bytes, 0), inputs: Object.keys(result.metafile.inputs) }; +} + +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const { bytes } = await buildPstWorker(); + console.log(`[build-pst-worker] ${path.relative(root, PST_WORKER_OUTFILE)} ${bytes}`); +} diff --git a/scripts/lite/build.mjs b/scripts/lite/build.mjs index cfa36568e..cd5684625 100644 --- a/scripts/lite/build.mjs +++ b/scripts/lite/build.mjs @@ -22,6 +22,7 @@ import { makeBuildId, normalizeBasePath, parseLiteTarget, resolveRepoRoot } from import { runPrepare } from "./prepare.mjs"; import { runPostbuild } from "./postbuild.mjs"; import { verifyExport } from "./verify.mjs"; +import { buildPstWorker } from "../build-pst-worker.mjs"; const repoRoot = resolveRepoRoot(import.meta.url); const argv = process.argv.slice(2); @@ -72,6 +73,10 @@ if (!argv.includes("--skip-prepare")) { runPrepare({ argv: argv.filter((a) => a === "--in-place"), env }); } +// The Outlook PST import worker is a file in public/, which the export copies. +const pstWorker = await buildPstWorker(); +console.log(`[lite] PST import worker ${pstWorker.bytes} bytes`); + const require = createRequire(import.meta.url); const nextBin = require.resolve("next/dist/bin/next", { paths: [repoRoot] }); console.log(`[lite] next build --turbopack (static export, ${target} target, build ${env.NEXT_PUBLIC_LITE_BUILD_ID})`); From c43d776b40421b5ddaff4acd2f12b8033ecefeab Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:26:55 +0100 Subject: [PATCH 03/22] fix: keep an mbox message's arrival time and leave its state headers out on import --- lib/mail-import/__tests__/engine.test.ts | 15 +++++ lib/mail-import/__tests__/mbox-status.test.ts | 18 +++++- lib/mail-import/mbox-status.ts | 61 +++++++++++++++++++ lib/mail-import/sources.ts | 18 ++++-- 4 files changed, 107 insertions(+), 5 deletions(-) diff --git a/lib/mail-import/__tests__/engine.test.ts b/lib/mail-import/__tests__/engine.test.ts index d21b4a4cf..302ea6738 100644 --- a/lib/mail-import/__tests__/engine.test.ts +++ b/lib/mail-import/__tests__/engine.test.ts @@ -106,6 +106,21 @@ describe('runImport', () => { expect(fake.imported[0].text.startsWith('Subject: m0\n')).toBe(true); }); + it('takes the arrival time from the envelope line and leaves the state headers out of the stored message', async () => { + const fake = fakeClient(); + const raw = 'From a@example.org Sat Jan 2 03:04:05 2021\nStatus: RO\nX-Mozilla-Status: 0001\nSubject: s\nDate: Tue, 5 May 2020 10:00:00 +0000\n\nbody\n'; + await runImport(options(fake.client, [file(raw)])); + expect(fake.imported[0].receivedAt).toBe('2021-01-02T03:04:05Z'); + expect(fake.imported[0].text).toBe('Subject: s\nDate: Tue, 5 May 2020 10:00:00 +0000\n\nbody\n'); + expect(fake.imported[0].keywords).toEqual({ $seen: true }); + }); + + it('falls back to the Date header when the envelope line has no usable date', async () => { + const fake = fakeClient(); + await runImport(options(fake.client, [file('From MAILER-DAEMON Thu Jan 1 00:00:00 1970\nSubject: s\nDate: Tue, 5 May 2020 10:00:00 +0000\n\nb\n')])); + expect(fake.imported[0].receivedAt).toBe('2020-05-05T10:00:00Z'); + }); + 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/__tests__/mbox-status.test.ts b/lib/mail-import/__tests__/mbox-status.test.ts index 249eb8ff4..163e6250b 100644 --- a/lib/mail-import/__tests__/mbox-status.test.ts +++ b/lib/mail-import/__tests__/mbox-status.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { mboxState } from '@/lib/mail-import/mbox-status'; +import { mboxState, withoutStateHeaders } from '@/lib/mail-import/mbox-status'; describe('mboxState', () => { it('imports a message without any state header as read, as before', () => { @@ -40,3 +40,19 @@ describe('mboxState', () => { expect(mboxState('subject: a\r\nx-status:\r\n F\r\nSTATUS: O').keywords).toEqual({ $flagged: true }); }); }); + +describe('withoutStateHeaders', () => { + const enc = new TextEncoder(); + const text = (parts: Uint8Array[]) => parts.map((p) => new TextDecoder().decode(p)).join(''); + it('drops the state headers, folded lines too, and keeps the rest byte for byte', () => { + const raw = 'Status: RO\r\nSubject: hi\r\nX-Keywords: a\r\n b\r\nX-Mozilla-Status: 0001\r\nFrom: x@example.org\r\n\r\nStatus: in the body\r\n'; + // Split in odd places, as the mbox splitter hands them over. + const parts = [enc.encode(raw.slice(0, 7)), enc.encode(raw.slice(7, 40)), enc.encode(raw.slice(40))]; + expect(text(withoutStateHeaders(parts))).toBe('Subject: hi\r\nFrom: x@example.org\r\n\r\nStatus: in the body\r\n'); + }); + it('leaves a message without them as it was', () => { + const parts = [enc.encode('Subject: hi\n\nbody\n')]; + expect(withoutStateHeaders(parts)).toBe(parts); + }); +}); + diff --git a/lib/mail-import/mbox-status.ts b/lib/mail-import/mbox-status.ts index 16cd892f1..f1e43de70 100644 --- a/lib/mail-import/mbox-status.ts +++ b/lib/mail-import/mbox-status.ts @@ -86,3 +86,64 @@ export function mboxState(headerBlock: string): MboxState { addKeywords(keywords, fields.get("x-mozilla-keys")); return { keywords, expunged }; } + +const STATE_HEADER = /^(status|x-status|x-keywords|x-mozilla-status|x-mozilla-status2|x-mozilla-keys)$/i; +/** How far into a message the header block is looked for. */ +const HEADER_LIMIT = 256 * 1024; + +/** + * The message without the mbox state headers read above: their state is now + * in JMAP keywords, and a stale copy in the stored message would only + * contradict it (and differ from what was exported). Everything else, + * the body included, stays byte for byte. + */ +export function withoutStateHeaders(parts: Uint8Array[]): Uint8Array[] { + // Find the end of the header block (the empty line) within the first parts. + let end = -1; + let offset = 0; + let lineHasText = false; + outer: for (const p of parts) { + for (let i = 0; i < p.length; i++) { + const b = p[i]; + if (b === 0x0a) { + if (!lineHasText) { end = offset + i + 1; break outer; } + lineHasText = false; + } else if (b !== 0x0d) { + lineHasText = true; + } + } + offset += p.length; + if (offset > HEADER_LIMIT) break; + } + const headerEnd = end === -1 ? Math.min(offset, parts.reduce((n, p) => n + p.length, 0)) : end; + // The header bytes, copied, and the rest as it is. + const head = new Uint8Array(headerEnd); + const rest: Uint8Array[] = []; + let at = 0; + for (const p of parts) { + if (at >= headerEnd) { rest.push(p); continue; } + const take = Math.min(p.length, headerEnd - at); + head.set(p.subarray(0, take), at); + if (take < p.length) rest.push(p.subarray(take)); + at += take; + } + const kept: Uint8Array[] = []; + let skipping = false; + let found = false; + let start = 0; + for (let i = 0; i <= head.length; i++) { + if (i < head.length && head[i] !== 0x0a) continue; + const line = head.subarray(start, Math.min(head.length, i + 1)); + start = i + 1; + if (line.length === 0) continue; + const folded = line[0] === 0x20 || line[0] === 0x09; + if (!folded) { + const colon = line.indexOf(0x3a); + const name = colon > 0 ? String.fromCharCode(...line.subarray(0, colon)).trim() : ""; + skipping = STATE_HEADER.test(name); + if (skipping) found = true; + } + if (!skipping) kept.push(line); + } + return found ? [...kept, ...rest] : parts; +} diff --git a/lib/mail-import/sources.ts b/lib/mail-import/sources.ts index 80c787c06..48c9c6ac6 100644 --- a/lib/mail-import/sources.ts +++ b/lib/mail-import/sources.ts @@ -1,6 +1,6 @@ import { dateFromHeaderBlock } from "@/lib/eml-import"; import { MboxSplitter, envelopeDate, readHeaderBlock, type MboxMessage } from "./mbox"; -import { mboxState } from "./mbox-status"; +import { mboxState, withoutStateHeaders } from "./mbox-status"; import { listZipEntries, readZipEntry, streamZipEntry } from "./zip-reader"; /** @@ -143,6 +143,13 @@ export async function* emlSource(file: File, options: SourceOptions): ItemSource options.onProgress?.(1); } +/** A date between 1980 and a day from now, else undefined. */ +function plausible(date: string | undefined, now = Date.now()): string | undefined { + if (!date) return undefined; + const t = Date.parse(date); + return t >= Date.UTC(1980, 0, 1) && t - now <= 24 * 60 * 60 * 1000 ? date : undefined; +} + function mboxItem(message: MboxMessage, index: number, folder: string[]): ImportItem { const label = message.tooLarge ? message.fromLine : ""; if (message.tooLarge) return { kind: "skipped", index, reason: "too_large", label }; @@ -150,14 +157,17 @@ function mboxItem(message: MboxMessage, index: number, folder: string[]): Import const state = mboxState(headers); const subject = subjectOf(headers); if (state.expunged) return { kind: "skipped", index, reason: "deleted", label: subject }; + const parts = withoutStateHeaders(message.parts); return { kind: "message", index, folder, - data: new Blob(message.parts as BlobPart[], { type: MIME }), - size: message.size, + data: new Blob(parts as BlobPart[], { type: MIME }), + size: parts.reduce((n, p) => n + p.length, 0), keywords: state.keywords, - receivedAt: dateFromHeaderBlock(headers) ?? envelopeDate(message.fromLine), + // The envelope line holds when the message arrived (Bulwark's export writes + // receivedAt there); the Date header, when it was written, comes second. + receivedAt: plausible(envelopeDate(message.fromLine)) ?? dateFromHeaderBlock(headers), label: subject, }; } From 15f0bd198cde7ce881529a0797631f068466a3c5 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:28:58 +0100 Subject: [PATCH 04/22] feat: export a folder or the whole account as mbox or ZIP --- components/layout/mailbox-context-menu.tsx | 10 + .../__tests__/mail-export-dialog.test.tsx | 97 +++++ .../mail-transfer/mail-backup-settings.tsx | 22 +- .../mail-transfer/mail-export-dialog.tsx | 193 ++++++++++ .../mail-transfer/mail-transfer-host.tsx | 8 +- components/mail-transfer/start-export.ts | 50 +++ lib/__tests__/jmap-mail-transfer.test.ts | 54 +++ lib/jmap/client-interface.ts | 8 +- lib/jmap/client.ts | 25 +- lib/mail-export/__tests__/engine.test.ts | 201 ++++++++++ lib/mail-export/__tests__/mbox-writer.test.ts | 50 +++ lib/mail-export/engine.ts | 353 ++++++++++++++++++ lib/mail-export/mbox-writer.ts | 102 +++++ lib/mail-export/sinks.ts | 195 ++++++++++ lib/sign-out-cleanup.ts | 3 + locales/ar/common.json | 40 ++ locales/ca/common.json | 40 ++ locales/cs/common.json | 40 ++ locales/da/common.json | 40 ++ locales/de/common.json | 40 ++ locales/en/common.json | 40 ++ locales/es/common.json | 40 ++ locales/et/common.json | 40 ++ locales/fa/common.json | 40 ++ locales/fr/common.json | 40 ++ locales/he/common.json | 40 ++ locales/hu/common.json | 40 ++ locales/it/common.json | 40 ++ locales/ja/common.json | 40 ++ locales/ko/common.json | 40 ++ locales/lv/common.json | 40 ++ locales/mn/common.json | 40 ++ locales/nb/common.json | 40 ++ locales/nl/common.json | 40 ++ locales/pl/common.json | 40 ++ locales/pt/common.json | 40 ++ locales/ro/common.json | 40 ++ locales/ru/common.json | 40 ++ locales/sk/common.json | 40 ++ locales/th/common.json | 40 ++ locales/tr/common.json | 40 ++ locales/uk/common.json | 40 ++ locales/vi/common.json | 40 ++ locales/zh-TW/common.json | 40 ++ locales/zh/common.json | 40 ++ stores/mail-transfer-store.ts | 36 +- 46 files changed, 2585 insertions(+), 22 deletions(-) create mode 100644 components/mail-transfer/__tests__/mail-export-dialog.test.tsx create mode 100644 components/mail-transfer/mail-export-dialog.tsx create mode 100644 components/mail-transfer/start-export.ts create mode 100644 lib/__tests__/jmap-mail-transfer.test.ts create mode 100644 lib/mail-export/__tests__/engine.test.ts create mode 100644 lib/mail-export/__tests__/mbox-writer.test.ts create mode 100644 lib/mail-export/engine.ts create mode 100644 lib/mail-export/mbox-writer.ts create mode 100644 lib/mail-export/sinks.ts diff --git a/components/layout/mailbox-context-menu.tsx b/components/layout/mailbox-context-menu.tsx index d723bbf64..3a2569113 100644 --- a/components/layout/mailbox-context-menu.tsx +++ b/components/layout/mailbox-context-menu.tsx @@ -21,6 +21,7 @@ import { FolderX, RefreshCw, Upload, + Download, Users, EyeOff, Bell, @@ -28,6 +29,7 @@ import { RotateCcw, } from "@/components/icons"; import { FolderRulesSubMenu } from "@/components/email/rules-menu"; +import { openMailExport } from "@/stores/mail-transfer-store"; interface Position { x: number; @@ -141,6 +143,7 @@ export function MailboxContextMenu({ }: MailboxContextMenuProps) { const t = useTranslations("mailbox_context_menu"); const tImport = useTranslations("mail_import"); + const tExport = useTranslations("mail_export"); const handleAction = (action: () => void) => { action(); @@ -322,6 +325,13 @@ export function MailboxContextMenu({ onClick={() => handleAction(() => onImportEmail?.(mailbox.id))} disabled={!onImportEmail || !canAddItems} /> + handleAction(() => openMailExport({ mailboxId: mailbox.id }))} + disabled={mailbox.totalEmails === 0 || mailbox.myRights?.mayReadItems === false} + testId="mailbox-export" + /> ({ startExport: (...args: unknown[]) => startExport(...args) })); + +const box = (id: string, name: string, extra: Partial = {}): Mailbox => ({ + id, name, sortOrder: 0, totalEmails: 3, unreadEmails: 0, totalThreads: 0, unreadThreads: 0, isSubscribed: true, + myRights: {} as Mailbox['myRights'], ...extra, +}); + +beforeEach(() => { + startExport.mockReset(); + useMailTransferStore.setState({ request: null, visible: false, job: null }); + useEmailStore.setState({ mailboxes: [box('inbox', 'Inbox', { role: 'inbox' }), box('acc2:shared', 'Team', { originalId: 'shared', accountId: 'acc2', isShared: true })] } as never); + useAuthStore.setState({ client: { getAccountId: () => 'acc1' } as never, username: 'alice@example.org' } as never); +}); + +afterEach(() => { + delete (window as { showSaveFilePicker?: unknown }).showSaveFilePicker; +}); + +describe('MailExportDialog', () => { + it('asks where to save first where the browser can, and exports a shared folder from its own account', async () => { + const writable = { write: vi.fn(), close: vi.fn(), abort: vi.fn() }; + const picker = vi.fn(async () => ({ createWritable: async () => writable })); + (window as { showSaveFilePicker?: unknown }).showSaveFilePicker = picker; + render(); + act(() => openMailExport({ mailboxId: 'acc2:shared' })); + expect(screen.getByText('title_folder')).toBeTruthy(); + expect(screen.getByText('save_picker')).toBeTruthy(); + fireEvent.click(screen.getByTestId('mail-export-start')); + await waitFor(() => expect(startExport).toHaveBeenCalled()); + expect(picker).toHaveBeenCalledWith(expect.objectContaining({ suggestedName: 'Team.mbox' })); + expect(startExport).toHaveBeenCalledWith(expect.objectContaining({ format: 'mbox', accountId: 'acc2', mailboxId: 'shared' })); + expect(startExport.mock.calls[0][0].sink.kind).toBe('picker'); + }); + + it('does nothing when the save picker is closed', async () => { + (window as { showSaveFilePicker?: unknown }).showSaveFilePicker = vi.fn(async () => { throw new DOMException('closed', 'AbortError'); }); + render(); + act(() => openMailExport({ mailboxId: 'inbox' })); + fireEvent.click(screen.getByTestId('mail-export-start')); + await waitFor(() => expect((screen.getByTestId('mail-export-start') as HTMLButtonElement).disabled).toBe(false)); + expect(startExport).not.toHaveBeenCalled(); + }); + + it('exports the whole account as a ZIP of .eml files into memory where nothing else exists', async () => { + render(); + act(() => openMailExport()); + expect(screen.getByText('title_account')).toBeTruthy(); + expect(screen.getByText('save_memory')).toBeTruthy(); + fireEvent.click(screen.getByTestId('mail-export-format-eml')); + fireEvent.click(screen.getByTestId('mail-export-start')); + await waitFor(() => expect(startExport).toHaveBeenCalled()); + const input = startExport.mock.calls[0][0]; + expect(input).toMatchObject({ format: 'eml', accountId: 'acc1', mailboxId: undefined }); + expect(input.sink.kind).toBe('memory'); + }); + + it('offers the file again when done and frees it on Close', async () => { + const discard = vi.fn(async () => {}); + const download = vi.fn(); + render(); + act(() => { + openMailExport({ mailboxId: 'inbox' }); + useMailTransferStore.getState().startJob({ + kind: 'export', controller: new AbortController(), + progress: { phase: 'done', exported: 3, failed: 0, failures: [], total: 3, fraction: 1, file: { name: 'Inbox.mbox', size: 1200, download, discard } }, + }); + }); + expect(screen.getByTestId('mail-export-status').getAttribute('aria-live')).toBe('polite'); + fireEvent.click(screen.getByTestId('mail-export-download')); + expect(download).toHaveBeenCalled(); + const closes = screen.getAllByRole('button', { name: 'close' }); + fireEvent.click(closes[closes.length - 1]); + expect(discard).toHaveBeenCalled(); + expect(useMailTransferStore.getState().request).toBeNull(); + }); + + it('runs on in the background and the chip names the export', () => { + const controller = new AbortController(); + render(); + act(() => { + openMailExport({ mailboxId: 'inbox' }); + useMailTransferStore.getState().startJob({ kind: 'export', controller, progress: { phase: 'running', exported: 1, failed: 0, failures: [], total: 4, fraction: 0.25 } }); + }); + fireEvent.click(screen.getByRole('button', { name: 'background' })); + expect(screen.getByTestId('mail-transfer-chip').textContent).toContain('chip'); + expect(controller.signal.aborted).toBe(false); + }); +}); diff --git a/components/mail-transfer/mail-backup-settings.tsx b/components/mail-transfer/mail-backup-settings.tsx index 0758d52c5..d1ba7eeaf 100644 --- a/components/mail-transfer/mail-backup-settings.tsx +++ b/components/mail-transfer/mail-backup-settings.tsx @@ -3,16 +3,24 @@ import { useTranslations } from "next-intl"; import { SettingItem } from "@/components/settings/settings-section"; import { Button } from "@/components/ui/button"; -import { openMailImport } from "@/stores/mail-transfer-store"; +import { openMailExport, openMailImport } from "@/stores/mail-transfer-store"; -/** Settings > Backup: bring mail in from files. */ +/** Settings > Backup: bring mail in from files, and save all of it as one file. */ export function MailBackupSettings() { const tImport = useTranslations("mail_import"); + const tExport = useTranslations("mail_export"); return ( - - - + <> + + + + + + + ); } diff --git a/components/mail-transfer/mail-export-dialog.tsx b/components/mail-transfer/mail-export-dialog.tsx new file mode 100644 index 000000000..20a46d437 --- /dev/null +++ b/components/mail-transfer/mail-export-dialog.tsx @@ -0,0 +1,193 @@ +"use client"; + +import { useId, useState } from "react"; +import { useLocale, useTranslations } from "next-intl"; +import { Dialog } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { useEmailStore } from "@/stores/email-store"; +import { useAuthStore } from "@/stores/auth-store"; +import { isJobActive, useMailTransferStore, type ExportRequest } from "@/stores/mail-transfer-store"; +import type { ExportFormat, ExportProgress } from "@/lib/mail-export/engine"; +import { safeName } from "@/lib/mail-export/engine"; +import { canPickSaveFile, memorySink, pickSaveFile, privateFileSink, sweepPrivateExports, type ExportSink } from "@/lib/mail-export/sinks"; +import { formatFileSize } from "@/lib/utils"; +import { formatClock, TransferBar } from "./transfer-progress"; +import { startExport } from "./start-export"; + +function today(): string { + return new Date().toISOString().slice(0, 10); +} + +/** Where the file will go in this browser, said before the export starts. */ +function saveMode(): "picker" | "download" | "memory" { + if (canPickSaveFile()) return "picker"; + const storage = typeof navigator !== "undefined" ? (navigator.storage as { getDirectory?: unknown } | undefined) : undefined; + return typeof storage?.getDirectory === "function" ? "download" : "memory"; +} + +export function MailExportDialog({ request }: { request: ExportRequest }) { + const t = useTranslations("mail_export"); + const locale = useLocale(); + const id = useId(); + const visible = useMailTransferStore((s) => s.visible); + const job = useMailTransferStore((s) => s.job); + const close = useMailTransferStore((s) => s.close); + const hide = useMailTransferStore((s) => s.hide); + const cancelJob = useMailTransferStore((s) => s.cancelJob); + const mailboxes = useEmailStore((s) => s.mailboxes); + const client = useAuthStore((s) => s.client); + const username = useAuthStore((s) => s.username); + const [format, setFormat] = useState("mbox"); + const [starting, setStarting] = useState(false); + const mode = saveMode(); + + const mailbox = request.mailboxId ? mailboxes.find((m) => m.id === request.mailboxId) : undefined; + const folder = !!request.mailboxId; + const progress = job?.kind === "export" ? job.progress : null; + const active = isJobActive(job); + + if (!visible) return null; + + const onStart = async () => { + if (!client || starting) return; + if (folder && !mailbox) return; + const zip = !folder || format === "eml"; + const base = folder ? safeName(mailbox!.name, "mail") : `${safeName(username ?? "mail", "mail")}-${today()}`; + const name = `${base}.${zip ? "zip" : "mbox"}`; + const mime = zip ? "application/zip" : "application/mbox"; + setStarting(true); + let sink: ExportSink | null = null; + try { + // The save picker must open straight from the click. + if (mode === "picker") { + try { + sink = await pickSaveFile(name, mime, zip ? ".zip" : ".mbox"); + } catch (error) { + if ((error as { name?: string } | null)?.name === "AbortError") return; + sink = null; + } + } + if (!sink) { + await sweepPrivateExports(); + sink = (await privateFileSink(name, mime)) ?? memorySink(name, mime); + } + startExport({ + format, + accountId: mailbox?.accountId || client.getAccountId(), + mailboxId: mailbox ? mailbox.originalId || mailbox.id : undefined, + sink, + }); + } finally { + setStarting(false); + } + }; + + const onClose = () => (active ? hide() : close()); + + if (progress) { + return ( +