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.
125 lines (124 loc) • 6.21 kB
JavaScript
import path from 'node:path';
import { ProgressBarSymbol } from '../../console/progressBar.js';
import ArchiveEntry from '../../types/files/archives/archiveEntry.js';
import Chd from '../../types/files/archives/chd/chd.js';
import { FixExtension } from '../../types/options.js';
import OutputFactory from '../../types/outputFactory.js';
import Module from '../module.js';
/**
* Correct the extensions of output {@link File}s when:
* 1. Not using any DATs (i.e. there's no correction already happening elsewhere)
* 2. The DAT-supplied ROM name is falsey
*/
export default class CandidateExtensionCorrector extends Module {
options;
fileFactory;
readerSemaphore;
constructor(options, progressBar, fileFactory, readerSemaphore) {
super(progressBar, CandidateExtensionCorrector.name);
this.options = options;
this.fileFactory = fileFactory;
this.readerSemaphore = readerSemaphore;
}
/**
* Correct the file extensions.
*/
async correct(dat, candidates) {
if (candidates.length === 0) {
this.progressBar.logTrace(`${dat.getName()}: no candidates to correct extensions for`);
return candidates;
}
const romsThatNeedCorrecting = candidates
.flatMap((candidate) => candidate.getRomsWithFiles())
.filter((romWithFiles) => this.romNeedsCorrecting(romWithFiles)).length;
if (romsThatNeedCorrecting === 0) {
this.progressBar.logTrace(`${dat.getName()}: no output files need their extension corrected`);
return candidates;
}
this.progressBar.logTrace(`${dat.getName()}: correcting ${romsThatNeedCorrecting.toLocaleString()} output file extension${romsThatNeedCorrecting === 1 ? '' : 's'}`);
this.progressBar.setSymbol(ProgressBarSymbol.CANDIDATE_EXTENSION_CORRECTION);
this.progressBar.resetProgress(romsThatNeedCorrecting);
const correctedCandidates = await this.correctExtensions(dat, candidates);
this.progressBar.logTrace(`${dat.getName()}: done correcting output file extensions`);
return correctedCandidates;
}
romNeedsCorrecting(romWithFiles) {
if (romWithFiles.getRom().getName().trim() === '') {
return true;
}
const inputFile = romWithFiles.getInputFile();
if (inputFile instanceof ArchiveEntry && inputFile.getArchive() instanceof Chd) {
// Files within CHDs never need extension correction
return false;
}
return (this.options.getFixExtension() === FixExtension.ALWAYS ||
(this.options.getFixExtension() === FixExtension.AUTO &&
(!this.options.usingDats() || romWithFiles.getRom().getName().trim() === '')));
}
async correctExtensions(dat, candidates) {
return Promise.all(candidates.map(async (candidate) => {
const hashedRomsWithFiles = await Promise.all(candidate.getRomsWithFiles().map(async (romWithFiles) => {
const correctedRom = await this.buildCorrectedRom(dat, candidate, romWithFiles);
// Using the corrected ROM name, build a new output path
const correctedOutputPath = OutputFactory.getPath(this.options, dat, candidate.getGame(), correctedRom, romWithFiles.getInputFile());
let correctedOutputFile = romWithFiles
.getOutputFile()
.withFilePath(correctedOutputPath.format());
if (correctedOutputFile instanceof ArchiveEntry) {
correctedOutputFile = correctedOutputFile.withEntryPath(correctedOutputPath.entryPath);
}
return romWithFiles.withRom(correctedRom).withOutputFile(correctedOutputFile);
}));
return candidate.withRomsWithFiles(hashedRomsWithFiles);
}));
}
async buildCorrectedRom(dat, candidate, romWithFiles) {
let correctedRom = romWithFiles.getRom();
if (correctedRom.getName().trim() === '') {
// The ROM doesn't have any filename, default it. Because we never knew a file extension,
// doing this isn't considered "correction".
const romWithFilesIdx = candidate.getRomsWithFiles().indexOf(romWithFiles);
correctedRom = correctedRom.withName(`${candidate
.getGame()
.getName()}${candidate.getRomsWithFiles().length > 1 ? ` (File ${romWithFilesIdx + 1})` : ''}.rom`);
}
if (!this.romNeedsCorrecting(romWithFiles)) {
// Do no further processing if we're not correcting the extension
return correctedRom;
}
await this.readerSemaphore.runExclusive(async () => {
this.progressBar.incrementInProgress();
this.progressBar.logTrace(`${dat.getName()}: ${candidate.getName()}: correcting extension for: ${romWithFiles
.getInputFile()
.toString()}`);
const childBar = this.progressBar.addChildBar({
name: romWithFiles.getInputFile().toString(),
});
try {
let fileSignature;
try {
fileSignature = await this.fileFactory.signatureFrom(romWithFiles.getInputFile());
}
catch (error) {
this.progressBar.logError(`${dat.getName()}: failed to correct file extension for '${romWithFiles
.getInputFile()
.toString()}': ${error}`);
}
if (fileSignature) {
// ROM file signature found, use the appropriate extension
const { dir, name } = path.parse(correctedRom.getName());
const correctedRomName = path.format({
dir,
name: name + fileSignature.getExtension(),
});
correctedRom = correctedRom.withName(correctedRomName);
}
}
finally {
childBar.delete();
}
this.progressBar.incrementCompleted();
});
return correctedRom;
}
}