feat: add datVerifier and tests
- Añade `datVerifier` con `parseDat` y `verifyHashesAgainstDat` - Añade tests y fixture XML para validar matching por CRC/MD5/SHA1/size - Añade `fast-xml-parser` en `backend/package.json` (devDependency)
This commit is contained in:
@@ -1,23 +1,100 @@
|
||||
/**
|
||||
* Servicio: datVerifier
|
||||
*
|
||||
* Encargado de parsear ficheros DAT (XML de listas de ROMs) y de verificar si
|
||||
* un ROM (por tamaño / CRC / MD5 / SHA1) coincide con una entrada del DAT.
|
||||
*
|
||||
* Actualmente este archivo contiene stubs mínimos: `parseDat` y
|
||||
* `verifyRomAgainstDat` devuelven valores vacíos para permitir que las pruebas
|
||||
* unitarias/integración opcionales se salten cuando `INTEGRATION` no está set.
|
||||
* En fases posteriores se sustituirá por un parseador XML completo y lógica de
|
||||
* matching detallada.
|
||||
*/
|
||||
export function parseDat(_xml: string): any {
|
||||
// Stub: el parseo completo no se implementa en esta fase.
|
||||
return {};
|
||||
import { XMLParser } from 'fast-xml-parser';
|
||||
|
||||
export type DatRom = {
|
||||
name: string;
|
||||
size?: number;
|
||||
crc?: string;
|
||||
md5?: string;
|
||||
sha1?: string;
|
||||
};
|
||||
|
||||
export type DatGame = {
|
||||
name: string;
|
||||
roms: DatRom[];
|
||||
};
|
||||
|
||||
export type DatDatabase = {
|
||||
games: DatGame[];
|
||||
};
|
||||
|
||||
function ensureArray<T>(v: T | T[] | undefined): T[] {
|
||||
if (v === undefined || v === null) return [];
|
||||
return Array.isArray(v) ? v : [v];
|
||||
}
|
||||
|
||||
export async function verifyRomAgainstDat(_romMeta: any, _parsedDat: any): Promise<any> {
|
||||
// Stub: verificación mínima para que los tests de integración puedan ser saltados.
|
||||
return {};
|
||||
function normalizeHex(v?: string): string | undefined {
|
||||
if (!v) return undefined;
|
||||
return v.trim().toLowerCase();
|
||||
}
|
||||
|
||||
export default { parseDat, verifyRomAgainstDat };
|
||||
export function parseDat(xml: string): DatDatabase {
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: '',
|
||||
trimValues: true,
|
||||
});
|
||||
|
||||
const parsed = parser.parse(xml as any) as any;
|
||||
const datafile = parsed?.datafile ?? parsed;
|
||||
|
||||
const rawGames = ensureArray(datafile?.game);
|
||||
|
||||
const games: DatGame[] = rawGames.map((g: any) => {
|
||||
// game name may be an attribute or a child node
|
||||
const nameAttr = g?.name ?? g?.['@_name'] ?? (g?.$?.name as any);
|
||||
|
||||
const romNodes = ensureArray(g?.rom);
|
||||
|
||||
const roms: DatRom[] = romNodes.map((r: any) => {
|
||||
const rname = r?.name ?? r?.['@_name'] ?? r?.['@name'];
|
||||
const sizeRaw = r?.size ?? r?.['@_size'];
|
||||
const parsedSize = sizeRaw != null ? Number(sizeRaw) : undefined;
|
||||
|
||||
return {
|
||||
name: String(rname ?? ''),
|
||||
size: typeof parsedSize === 'number' && !Number.isNaN(parsedSize) ? parsedSize : undefined,
|
||||
crc: normalizeHex(r?.crc ?? r?.['@_crc'] ?? r?.CRC ?? r?.['CRC']),
|
||||
md5: normalizeHex(r?.md5 ?? r?.['@_md5'] ?? r?.MD5 ?? r?.['MD5']),
|
||||
sha1: normalizeHex(r?.sha1 ?? r?.['@_sha1'] ?? r?.SHA1 ?? r?.['SHA1']),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
name: String(nameAttr ?? ''),
|
||||
roms,
|
||||
};
|
||||
});
|
||||
|
||||
return { games };
|
||||
}
|
||||
|
||||
export function verifyHashesAgainstDat(
|
||||
datDb: DatDatabase,
|
||||
hashes: { crc?: string; md5?: string; sha1?: string; size?: number }
|
||||
): { gameName: string; romName: string; matchedOn: 'crc' | 'md5' | 'sha1' | 'size' } | null {
|
||||
const cmp = {
|
||||
crc: normalizeHex(hashes.crc),
|
||||
md5: normalizeHex(hashes.md5),
|
||||
sha1: normalizeHex(hashes.sha1),
|
||||
size: hashes.size,
|
||||
} as any;
|
||||
|
||||
for (const g of datDb.games) {
|
||||
for (const r of g.roms) {
|
||||
if (cmp.crc && r.crc && cmp.crc === r.crc) {
|
||||
return { gameName: g.name, romName: r.name, matchedOn: 'crc' };
|
||||
}
|
||||
if (cmp.md5 && r.md5 && cmp.md5 === r.md5) {
|
||||
return { gameName: g.name, romName: r.name, matchedOn: 'md5' };
|
||||
}
|
||||
if (cmp.sha1 && r.sha1 && cmp.sha1 === r.sha1) {
|
||||
return { gameName: g.name, romName: r.name, matchedOn: 'sha1' };
|
||||
}
|
||||
if (cmp.size !== undefined && r.size !== undefined && Number(cmp.size) === Number(r.size)) {
|
||||
return { gameName: g.name, romName: r.name, matchedOn: 'size' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user