import type { SourceKind } from './worker/onenote.client.js'; export const MAX_SOURCE_FILE_BYTES = 512 * 1024 * 1024; export const MAX_SOURCE_PARTS = 256; export interface SourceFileDescriptor { name: string; size: number; } interface SourceFileSelectionFailure { ok: false; title: string; message: string; } export type SourceFileSelection = | { ok: true; kind: 'one'; source: T; cabinetParts: []; } | { ok: true; kind: 'onepkg'; source: T; cabinetParts: T[]; } | SourceFileSelectionFailure; export function sourceKindFromFileName(fileName: string): SourceKind | null { const normalized = fileName.toLocaleLowerCase('en-US'); if (normalized.endsWith('.onepkg')) return 'onepkg'; if (normalized.endsWith('.one')) return 'one'; return null; } export function isCabinetPartFileName(fileName: string): boolean { return fileName.toLocaleLowerCase('en-US').endsWith('.cab'); } export function selectSourceFiles( files: readonly T[] ): SourceFileSelection { if (files.length === 0) { return invalidSelection(); } if (files.length > MAX_SOURCE_PARTS) { return { ok: false, title: 'Too many package parts', message: `Choose at most ${MAX_SOURCE_PARTS} files for one cabinet set.`, }; } const sections: T[] = []; const packages: T[] = []; const cabinetParts: T[] = []; for (const file of files) { const kind = sourceKindFromFileName(file.name); if (kind === 'one') sections.push(file); else if (kind === 'onepkg') packages.push(file); else if (isCabinetPartFileName(file.name)) cabinetParts.push(file); else { return { ok: false, title: 'Unsupported file name', message: 'Choose one .one file, or one .onepkg file together with its .cab parts.', }; } } const validSection = sections.length === 1 && packages.length === 0 && cabinetParts.length === 0; const validPackage = sections.length === 0 && packages.length === 1; if (!validSection && !validPackage) return invalidSelection(); const seenNames = new Set(); let totalSize = 0; for (const file of files) { const nameKey = file.name.normalize('NFC').toLocaleLowerCase('en-US'); if (seenNames.has(nameKey)) { return { ok: false, title: 'Duplicate file name', message: `The selection contains more than one file named ${file.name}.`, }; } seenNames.add(nameKey); if (!Number.isSafeInteger(file.size) || file.size < 0) { return { ok: false, title: 'Invalid file size', message: `The browser reported an invalid size for ${file.name}.`, }; } if (file.size > MAX_SOURCE_FILE_BYTES) { return { ok: false, title: 'File is too large', message: `${file.name} exceeds the current 512 MiB safety limit.`, }; } totalSize += file.size; if (!Number.isSafeInteger(totalSize) || totalSize > MAX_SOURCE_FILE_BYTES) { return { ok: false, title: 'Package set is too large', message: 'The selected .onepkg and .cab files exceed the combined 512 MiB safety limit.', }; } } if (validSection) { return { ok: true, kind: 'one', source: sections[0]!, cabinetParts: [] }; } return { ok: true, kind: 'onepkg', source: packages[0]!, cabinetParts, }; } function invalidSelection(): SourceFileSelectionFailure { return { ok: false, title: 'Choose one OneNote source', message: 'Choose one .one file, or one .onepkg file together with its .cab parts.', }; }