Files
onenote-tools/src/source-file.ts

136 lines
3.6 KiB
TypeScript

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<T extends SourceFileDescriptor> =
| {
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<T extends SourceFileDescriptor>(
files: readonly T[]
): SourceFileSelection<T> {
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<string>();
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.',
};
}