Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions src/modules/candidates/candidateExtensionCorrector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import path from 'node:path';

import type { Semaphore } from 'async-mutex';

import type ProgressBar from '../../console/progressBar.js';
Expand Down Expand Up @@ -166,7 +164,7 @@ export default class CandidateExtensionCorrector extends Module {
await this.readerSemaphore.runExclusive(async () => {
this.progressBar.incrementInProgress();
this.progressBar.logTrace(
`${dat.getName()}: ${candidate.getName()}: correcting extension for: ${romWithFiles
`${dat.getName()}: ${candidate.getName()}: correcting ROM extension for: ${romWithFiles
.getInputFile()
.toString()}`,
);
Expand All @@ -180,8 +178,15 @@ export default class CandidateExtensionCorrector extends Module {
correctedRom,
romWithFiles.getInputFile(),
);
if (correctedRomName !== undefined) {
if (correctedRomName === undefined) {
this.progressBar.logTrace(
`${dat.getName()}: ${candidate.getName()}: didn't correct ROM extension`,
);
} else {
correctedRom = correctedRom.withName(correctedRomName);
this.progressBar.logTrace(
`${dat.getName()}: ${candidate.getName()}: corrected ROM extension to: ${correctedRomName}`,
);
}
} finally {
childBar.delete();
Expand All @@ -208,11 +213,10 @@ export default class CandidateExtensionCorrector extends Module {
);
}
if (fileSignature !== undefined) {
const { dir, name } = path.parse(correctedRom.getName());
return path.format({
dir,
name: name + fileSignature.getExtension(),
});
// Replace the file's existing extension (if any) with the one detected from its signature.
// A strict extension regex is used rather than path.parse(), which would mistake a period
// inside the filename for an extension and truncate everything after it.
return correctedRom.getName().replace(/\.[a-zA-Z0-9]+$/, '') + fileSignature.getExtension();
}

// Strip the extension from files claiming to be an archive
Expand Down
42 changes: 42 additions & 0 deletions test/modules/candidates/candidateExtensionCorrector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,48 @@ it('should correct ROMs without DATs', async () => {
}
});

it('should not truncate names with periods when correcting from file signature', async () => {
const options = new Options({
// No DAT has been provided, therefore all ROMs should be corrected
fixExtension: FixExtensionInverted[FixExtension.AUTO].toLowerCase(),
});
const dat = new LogiqxDAT({ header: new Header() });

const tempDir = await FsUtil.mkdtemp(Temp.getTempDir());
try {
// A CHD file whose name contains periods, but no extension on the inferred ROM name
const gameName = 'J. B. Harold Murder Club (USA) (En,Ja)';
const tempFile = path.join(tempDir, `${gameName}.chd`);
await FsUtil.copyFile(path.join('test', 'fixtures', 'roms', 'chd', 'CD-ROM.chd'), tempFile);
const inputFile = await File.fileOf({ filePath: tempFile });

// The inferred ROM name has no extension (this is what a raw CHD entry produces)
const rom = new ROM({ name: gameName, size: inputFile.getSize() });
const game = new Game({ name: gameName, roms: [rom] });
const outputFile = inputFile.withFilePath(path.join(tempDir, gameName));
const candidates = [new WriteCandidate(game, [new ROMWithFiles(rom, inputFile, outputFile)])];

const correctedCandidates = await new CandidateExtensionCorrector(
options,
new ProgressBarFake(),
new FileFactory(new FileCache(), LOGGER),
new Semaphore(os.availableParallelism()),
).correct(dat, candidates);

expect(correctedCandidates).toHaveLength(1);
expect(correctedCandidates[0].getRomsWithFiles()).toHaveLength(1);
const correctedRomName = correctedCandidates
.at(0)
?.getRomsWithFiles()
.at(0)
?.getRom()
.getName();
expect(correctedRomName).toEqual(`${gameName}.chd`);
} finally {
await FsUtil.rm(tempDir, { recursive: true, force: true });
}
});

it('should correct ROMs with missing filenames', async () => {
const options = new Options({
dat: [path.join('test', 'fixtures', 'dats')],
Expand Down
Loading