57 lines
2.3 KiB
TypeScript
57 lines
2.3 KiB
TypeScript
export interface SelectedGameFile {
|
|
path: string;
|
|
file: File;
|
|
}
|
|
export interface SelectedGameFolder {
|
|
name: string;
|
|
files: SelectedGameFile[];
|
|
size: number;
|
|
}
|
|
|
|
/** Directory inputs include the selected folder in every relative path. Remove
|
|
* that one component and retain the complete game directory below it. */
|
|
export function selectGameFolder(selection: Iterable<File>): SelectedGameFolder {
|
|
const files: SelectedGameFile[] = [];
|
|
const paths = new Set<string>();
|
|
let name = '';
|
|
let size = 0;
|
|
for (const file of selection) {
|
|
const [root, ...parts] = file.webkitRelativePath.split('/');
|
|
if (!root || root === '.' || root === '..' || !parts.length || (name && root !== name))
|
|
throw new Error('Choose one game folder with the folder picker.');
|
|
name = root;
|
|
const path = parts.join('/');
|
|
if (
|
|
path.length > 1024 ||
|
|
// eslint-disable-next-line no-control-regex -- Game paths must reject control characters.
|
|
/[\\\x00-\x1f\x7f:]/.test(path) ||
|
|
parts.some((part) => !part || part === '.' || part === '..' || /[. ]$/.test(part))
|
|
) {
|
|
throw new Error(`This file has an unsupported game path: ${file.webkitRelativePath}`);
|
|
}
|
|
const key = path.toLowerCase();
|
|
if (paths.has(key)) throw new Error(`This folder has duplicate game paths: ${path}`);
|
|
paths.add(key);
|
|
size += file.size;
|
|
if (!Number.isSafeInteger(size))
|
|
throw new Error('This folder exceeds the supported upload size.');
|
|
files.push({ path, file });
|
|
if (files.length > 200_000)
|
|
throw new Error('Choose a folder with no more than 200,000 files.');
|
|
}
|
|
if (!files.length) throw new Error('The selected folder contains no files.');
|
|
for (const { path } of files) {
|
|
const parents = path.toLowerCase().split('/');
|
|
parents.pop();
|
|
while (parents.length) {
|
|
if (paths.has(parents.join('/')))
|
|
throw new Error(
|
|
`This folder uses the same game path for a file and a directory: ${path}`
|
|
);
|
|
parents.pop();
|
|
}
|
|
}
|
|
files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
return { name, files, size };
|
|
}
|