136 lines
3.6 KiB
TypeScript
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.',
|
|
};
|
|
}
|