chdman
Version:
💿 chdman binaries and wrapper for Node.js.
95 lines • 4.31 kB
JavaScript
import util from 'node:util';
import fs from 'node:fs';
import ChdmanBin from './chdmanBin.js';
import { CHDType, CHDCompressionAlgorithm } from './common.js';
export default {
/**
* Return info about a CHD file.
*/
async info(options, attempt = 1) {
try {
await util.promisify(fs.stat)(options.inputFilename);
}
catch {
throw new Error(`input file doesn't exist: ${options.inputFilename}`);
}
const output = await ChdmanBin.run([
'info',
'--input', options.inputFilename,
'--verbose',
], options);
// Try to detect failures, and then retry them automatically
if (!output.trim() && attempt <= 3) {
await new Promise((resolve) => {
setTimeout(resolve, Math.random() * (2 ** (attempt - 1) * 20));
});
return this.info(options, attempt + 1);
}
const parsedKeys = new Map();
for (const line of output.split(/\r?\n/)) {
const split = line.split(/^([^ ][^:]+): +(.+)$/);
if (split.length === 4) {
parsedKeys.set(split[1].toUpperCase(), split[2]);
}
}
const metadata = [...output.matchAll(/metadata: +(.+)\r?\n +(.+)/gi)]
.map((match, index_) => {
const tag = match[1].match(/tag='([\d a-z]+)'/i)?.at(1)?.trim() ?? '';
const index = Number.parseInt(match[1].match(/index=(\d+)/i)?.at(1) ?? String(index_), 10);
const length = Number.parseInt(match[1].match(/length=([\d,]+)/i)?.at(1)?.replace(/,/g, '') ?? '0', 10);
return {
tag,
index,
length,
data: match[2],
};
});
const metadataTags = new Set(metadata.map((m) => m.tag));
let type = CHDType.RAW;
if (metadataTags.has('GDDD')) {
type = CHDType.HARD_DISK;
}
else if (metadataTags.has('CHCD') || metadataTags.has('CHTR') || metadataTags.has('CHT2')) {
type = CHDType.CD_ROM;
}
else if (metadataTags.has('CHGT') || metadataTags.has('CHGD')) {
type = CHDType.GD_ROM;
}
else if (metadataTags.has('DVD')) {
type = CHDType.DVD_ROM;
}
const chdInfo = {
inputFile: parsedKeys.get('INPUT FILE') ?? options.inputFilename,
fileVersion: Number.parseInt(parsedKeys.get('FILE VERSION') ?? '0', 10),
logicalSize: Number.parseInt(parsedKeys.get('LOGICAL SIZE')?.replace(/\D+/g, '') ?? '0', 10),
hunkSize: Number.parseInt(parsedKeys.get('HUNK SIZE')?.replace(/\D+/g, '') ?? '0', 10),
totalHunks: Number.parseInt(parsedKeys.get('TOTAL HUNKS')?.replace(/\D+/g, '') ?? '0', 10),
unitSize: Number.parseInt(parsedKeys.get('UNIT SIZE')?.replace(/\D+/g, '') ?? '0', 10),
totalUnits: Number.parseInt(parsedKeys.get('TOTAL UNITS')?.replace(/\D+/g, '') ?? '0', 10),
compression: [...(parsedKeys.get('COMPRESSION') ?? '').matchAll(/([a-z]{4})( \([^(]+\))?/g)]
.map((match) => match[1])
.map((compressionType) => Object.keys(CHDCompressionAlgorithm)
.map((enumKey) => {
const compressionAlgo = enumKey;
return CHDCompressionAlgorithm[compressionAlgo];
})
.find((enumValue) => enumValue === compressionType))
.filter((enumValue) => enumValue !== undefined),
chdSize: Number.parseInt(parsedKeys.get('CHD SIZE')?.replace(/\D+/g, '') ?? '0', 10),
ratio: Number.parseFloat(parsedKeys.get('RATIO')?.replace(/[^\d.]+/g, '') ?? '0'),
sha1: parsedKeys.get('SHA1') ?? '',
dataSha1: parsedKeys.get('DATA SHA1') ?? '',
metadata,
type,
};
// Try to detect failures, and then retry them automatically
if (chdInfo.fileVersion === 0 && attempt <= 3) {
await new Promise((resolve) => {
setTimeout(resolve, Math.random() * (2 ** (attempt - 1) * 20));
});
return this.info(options, attempt + 1);
}
return chdInfo;
},
};
//# sourceMappingURL=chdmanInfo.js.map