Skip to content

Commit

Permalink
Feature: create & extract raw (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmercm authored Mar 18, 2024
1 parent 780bf6d commit 2437e2b
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 2 deletions.
6 changes: 4 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import ChdmanHd from './src/chdman/chdmanHd.js';
import ChdmanCd from './src/chdman/chdmanCd.js';
import ChdmanVerify from './src/chdman/chdmanVerify.js';
import ChdmanDvd from './src/chdman/chdmanDvd.js';
import ChdmanRaw from './src/chdman/chdmanRaw.js';

export * from './src/chdman/chdmanInfo.js';
export * from './src/chdman/chdmanBin.js';
export * from './src/chdman/chdmanHd.js';
export * from './src/chdman/chdmanCd.js';
export * from './src/chdman/chdmanVerify.js';
export * from './src/chdman/chdmanDvd.js';
export * from './src/chdman/chdmanRaw.js';
export * from './src/chdman/common.js';

export default {
Expand All @@ -20,13 +22,13 @@ export default {

verify: ChdmanVerify.verify,

// TODO(cemmer): createraw
createRaw: ChdmanRaw.createRaw,
createHd: ChdmanHd.createHd,
createCd: ChdmanCd.createCd,
createDvd: ChdmanDvd.createDvd,
// TODO(cemmer): createld

// TODO(cemmer): extractraw
extractRaw: ChdmanRaw.extractRaw,
extractHd: ChdmanHd.extractHd,
extractCd: ChdmanCd.extractCd,
extractDvd: ChdmanDvd.extractDvd,
Expand Down
75 changes: 75 additions & 0 deletions src/chdman/chdmanRaw.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import util from 'node:util';
import fs from 'node:fs';
import { CHDCompressionAlgorithm } from './common.js';
import ChdmanBin from './chdmanBin.js';

export interface CreateRawOptions {
outputFilename: string,
outputParentFilename?: string,
force?: boolean,
inputFilename: string,
inputStartByte?: number,
inputStartHunk?: number,
inputBytes?: number,
inputHunks?: number,
hunkSize: number,
unitSize: number,
compression?: 'none' | CHDCompressionAlgorithm[],
numProcessors?: number
}

export interface ExtractRawOptions {
outputFilename: string,
force?: boolean,
inputFilename: string,
inputParentFilename?: string,
inputStartByte?: number,
inputStartHunk?: number,
inputBytes?: number,
inputHunks?: number,
}

export default {
async createRaw(options: CreateRawOptions): Promise<void> {
const existedBefore = await util.promisify(fs.exists)(options.outputFilename);
try {
await ChdmanBin.run([
'createraw',
'--output', options.outputFilename,
...(options.outputParentFilename ? ['--outputparent', String(options.outputParentFilename)] : []),
...(options.force === true ? ['--force'] : []),
'--input', options.inputFilename,
...(options.inputStartByte === undefined ? [] : ['--inputstartbyte', String(options.inputStartByte)]),
...(options.inputStartHunk === undefined ? [] : ['--inputstarthunk', String(options.inputStartHunk)]),
...(options.inputBytes === undefined ? [] : ['--inputbytes', String(options.inputBytes)]),
...(options.inputHunks === undefined ? [] : ['--inputhunks', String(options.inputHunks)]),
'--hunksize', String(options.hunkSize),
'--unitsize', String(options.unitSize),
...(options.compression === undefined
? []
: ['--compression', Array.isArray(options.compression) ? options.compression.join(',') : options.compression]),
...(options.numProcessors === undefined ? [] : ['--numprocessors', String(options.numProcessors)]),
]);
} catch (error) {
// chdman can leave cruft when it fails
if (!existedBefore) {
await util.promisify(fs.rm)(options.outputFilename, { force: true });
}
throw error;
}
},

async extractRaw(options: ExtractRawOptions): Promise<void> {
await ChdmanBin.run([
'extractraw',
'--output', options.outputFilename,
...(options.force === true ? ['--force'] : []),
'--input', options.inputFilename,
...(options.inputParentFilename ? ['--inputparent', options.inputParentFilename] : []),
...(options.inputStartByte === undefined ? [] : ['--inputstartbyte', String(options.inputStartByte)]),
...(options.inputStartHunk === undefined ? [] : ['--inputstarthunk', String(options.inputStartHunk)]),
...(options.inputBytes === undefined ? [] : ['--inputbytes', String(options.inputBytes)]),
...(options.inputHunks === undefined ? [] : ['--inputhunks', String(options.inputHunks)]),
]);
},
};
76 changes: 76 additions & 0 deletions test/chdman/chdmanRaw.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import path from 'node:path';
import os from 'node:os';
import util from 'node:util';
import fs from 'node:fs';
import ChdmanInfo from '../../src/chdman/chdmanInfo.js';
import TestUtil from '../testUtil.js';
import ChdmanRaw from '../../src/chdman/chdmanRaw.js';

test('should fail on nonexistent file', async () => {
const temporaryChd = `${await TestUtil.mktemp(path.join(os.tmpdir(), 'dummy'))}.chd`;
const temporaryRaw = `${await TestUtil.mktemp(path.join(os.tmpdir(), 'dummy'))}.hd`;

try {
await expect(ChdmanRaw.createRaw({
inputFilename: os.devNull,
outputFilename: temporaryChd,
hunkSize: 64,
unitSize: 64,
})).rejects.toBeDefined();
await expect(ChdmanInfo.info({
inputFilename: temporaryRaw,
})).rejects.toBeDefined();
await expect(ChdmanRaw.extractRaw({
inputFilename: temporaryChd,
outputFilename: temporaryRaw,
})).rejects.toBeDefined();
} finally {
await util.promisify(fs.rm)(temporaryChd, { force: true });
}
});

test.each([
[path.join('test', 'fixtures', 'hd', '512.hd'), 512],
[path.join('test', 'fixtures', 'hd', '3584.hd'), 3584],
[path.join('test', 'fixtures', 'iso', '2048.iso'), 2048],
[path.join('test', 'fixtures', 'iso', '16384.iso'), 16_384],
])('should create, info, and extract: %s', async (hd, expectedBinSize) => {
const temporaryChd = `${await TestUtil.mktemp(path.join(os.tmpdir(), path.basename(hd)))}.chd`;
const temporaryHd = `${await TestUtil.mktemp(path.join(os.tmpdir(), 'dummy'))}.hd`;

try {
await ChdmanRaw.createRaw({
inputFilename: hd,
outputFilename: temporaryChd,
hunkSize: 64,
unitSize: 64,
});
await expect(TestUtil.exists(temporaryChd)).resolves.toEqual(true);

const info = await ChdmanInfo.info({
inputFilename: temporaryChd,
});
expect(info.fileVersion).toBeGreaterThan(0);
expect(info.logicalSize).toBeGreaterThan(0);
expect(info.hunkSize).toBeGreaterThan(0);
expect(info.totalHunks).toBeGreaterThan(0);
expect(info.unitSize).toBeGreaterThan(0);
expect(info.totalUnits).toBeGreaterThan(0);
expect(info.compression.length).toBeGreaterThan(0);
expect(info.chdSize).toBeGreaterThan(0);
expect(info.ratio).toBeGreaterThan(0);
expect(info.sha1).toBeTruthy();
expect(info.dataSha1).toBeTruthy();

await ChdmanRaw.extractRaw({
inputFilename: temporaryChd,
outputFilename: temporaryHd,
});
await expect(TestUtil.exists(temporaryHd)).resolves.toEqual(true);
const temporaryHdStat = await util.promisify(fs.stat)(temporaryHd);
expect(temporaryHdStat.size).toEqual(expectedBinSize);
} finally {
await util.promisify(fs.rm)(temporaryChd, { force: true });
await util.promisify(fs.rm)(temporaryHd, { force: true });
}
});
Binary file removed test/fixtures/iso/16384.chd.iso
Binary file not shown.

0 comments on commit 2437e2b

Please sign in to comment.