igir
Version:
🕹 A zero-setup ROM collection manager that sorts, filters, extracts or archives, patches, and reports on collections of any size on any OS.
94 lines (93 loc) • 4.39 kB
JavaScript
import async from 'async';
import { ProgressBarSymbol } from '../../console/progressBar.js';
import Defaults from '../../globals/defaults.js';
import ArchiveEntry from '../../types/files/archives/archiveEntry.js';
import ROMHeader from '../../types/files/romHeader.js';
import Module from '../module.js';
/**
* For every input {@link File} file found, attempt to find a matching {@link Header} and resolve
* its header-less checksums.
*/
export default class ROMHeaderProcessor extends Module {
options;
fileFactory;
driveSemaphore;
constructor(options, progressBar, fileFactory, driveSemaphore) {
super(progressBar, ROMHeaderProcessor.name);
this.options = options;
this.fileFactory = fileFactory;
this.driveSemaphore = driveSemaphore;
}
/**
* Process each {@link File}, finding any {@link Header} present.
*/
async process(inputRomFiles) {
if (inputRomFiles.length === 0) {
return inputRomFiles;
}
const filesThatNeedProcessing = inputRomFiles.filter((inputFile) => this.fileNeedsProcessing(inputFile)).length;
if (filesThatNeedProcessing === 0) {
this.progressBar.logTrace('no ROMs need their header processed');
return inputRomFiles;
}
this.progressBar.logTrace(`processing headers in ${filesThatNeedProcessing.toLocaleString()} ROM${filesThatNeedProcessing === 1 ? '' : 's'}`);
this.progressBar.setSymbol(ProgressBarSymbol.ROM_HEADER_DETECTION);
this.progressBar.resetProgress(filesThatNeedProcessing);
const parsedFiles = await async.mapLimit(inputRomFiles, Defaults.MAX_FS_THREADS, async (inputFile) => {
if (!this.fileNeedsProcessing(inputFile)) {
return inputFile;
}
return this.driveSemaphore.runExclusive(inputFile, async () => {
this.progressBar.incrementInProgress();
const childBar = this.progressBar.addChildBar({
name: inputFile.toString(),
});
let fileWithHeader;
try {
fileWithHeader = await this.getFileWithHeader(inputFile);
}
catch (error) {
this.progressBar.logError(`${inputFile.toString()}: failed to process ROM header: ${error}`);
fileWithHeader = inputFile;
}
finally {
childBar.delete();
}
this.progressBar.incrementCompleted();
return fileWithHeader;
});
});
const headeredRomsCount = parsedFiles.filter((romFile) => romFile.getFileHeader() !== undefined).length;
this.progressBar.logTrace(`found headers in ${headeredRomsCount.toLocaleString()} ROM${headeredRomsCount === 1 ? '' : 's'}`);
this.progressBar.logTrace('done processing file headers');
return parsedFiles;
}
fileNeedsProcessing(inputFile) {
/**
* If the input file is from an archive, and we're not zipping or extracting, then we have no
* chance to remove the header, so we shouldn't bother detecting one.
* Matches {@link CandidateGenerator#buildCandidatesForGame}
*/
if (inputFile instanceof ArchiveEntry &&
!this.options.shouldZip() &&
!this.options.shouldExtract()) {
return false;
}
if (inputFile.getSize() === 0) {
// It can't have a header
return false;
}
return (ROMHeader.headerFromFilename(inputFile.getExtractedFilePath()) !== undefined ||
this.options.shouldReadFileForHeader(inputFile.getExtractedFilePath()));
}
async getFileWithHeader(inputFile) {
this.progressBar.logTrace(`${inputFile.toString()}: reading potentially headered file by file contents`);
const headerForFileStream = await this.fileFactory.headerFrom(inputFile);
if (headerForFileStream) {
this.progressBar.logTrace(`${inputFile.toString()}: found header by file contents: ${headerForFileStream.getHeaderedFileExtension()}`);
return inputFile.withFileHeader(headerForFileStream);
}
this.progressBar.logTrace(`${inputFile.toString()}: didn't find header by file contents`);
return inputFile;
}
}