UNPKG

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.

289 lines (288 loc) • 10.4 kB
import { logger } from "../console/logger.js"; import IgirException from "../exceptions/igirException.js"; import ArchiveFile from "../models/files/archives/archiveFile.js"; import Chd from "../models/files/archives/chd/chd.js"; import ChdBinCue from "../models/files/archives/chd/chdBinCue.js"; import ChdGdi from "../models/files/archives/chd/chdGdi.js"; import ChdRaw from "../models/files/archives/chd/chdRaw.js"; import Gcz from "../models/files/archives/dolphin/gcz.js"; import Rvz from "../models/files/archives/dolphin/rvz.js"; import Wia from "../models/files/archives/dolphin/wia.js"; import Gzip from "../models/files/archives/gzip.js"; import Cso from "../models/files/archives/maxcso/cso.js"; import Dax from "../models/files/archives/maxcso/dax.js"; import Zso from "../models/files/archives/maxcso/zso.js"; import NkitIso from "../models/files/archives/nkitIso.js"; import Rar from "../models/files/archives/rar.js"; import SevenZip from "../models/files/archives/sevenZip/sevenZip.js"; import Z from "../models/files/archives/sevenZip/z.js"; import ZipSpanned from "../models/files/archives/sevenZip/zipSpanned.js"; import ZipX from "../models/files/archives/sevenZip/zipX.js"; import Tar from "../models/files/archives/tar.js"; import Zip from "../models/files/archives/zip.js"; import File from "../models/files/file.js"; import { ChecksumBitmask } from "../models/files/fileChecksums.js"; import URLUtil from "../utils/urlUtil.js"; const CacheMode = { RESPECT_CACHED_VALUE: 1, IGNORE_CACHED_VALUE: 2 }; class FileFactory { fileCache; constructor(fileCache) { this.fileCache = fileCache; } /** * Return every {@link File} represented by a path, expanding archives into their entries when * the path is (or appears to be) an archive, and falling back to a single non-archive file * otherwise. */ async filesFrom(filePath, fileChecksumBitmask = ChecksumBitmask.CRC32, entryChecksumBitmask = fileChecksumBitmask, callback) { if (URLUtil.canParse(filePath)) { return [await File.fileOf({ filePath })]; } if (!FileFactory.isExtensionArchive(filePath)) { const entries = await this.entriesFromArchiveSignature( filePath, entryChecksumBitmask, callback ); if (entries !== void 0) { return entries; } return [await this.fileFrom(filePath, fileChecksumBitmask, callback)]; } try { const archives = this.archiveFromArchiveExtension(filePath); if (archives.length === 0) { return [await this.fileFrom(filePath, fileChecksumBitmask, callback)]; } const entries = []; let wasAnyParsed = false; for (const archive of archives) { const result = await this.entriesFromArchive( archive, entryChecksumBitmask, CacheMode.RESPECT_CACHED_VALUE, callback ); if (result !== void 0) { wasAnyParsed = true; for (const entry of result) { entries.push(entry); } } } if (!wasAnyParsed) { return [await this.fileFrom(filePath, fileChecksumBitmask)]; } return entries; } catch (error) { if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") { throw new IgirException(`file doesn't exist: ${filePath}`); } if (typeof error === "string") { throw new Error(error, { cause: error }); } throw error; } } /** * Return a single {@link File} for a path, with its checksums computed per the given bitmask. */ async fileFrom(filePath, checksumBitmask, callback, cacheModeValue = CacheMode.RESPECT_CACHED_VALUE) { return await this.fileCache.getOrComputeFileChecksums( filePath, checksumBitmask, callback, cacheModeValue === CacheMode.IGNORE_CACHED_VALUE ); } /** * Wrap an {@link ArchiveEntry} into an {@link ArchiveFile} backed by the underlying archive * file with its checksums computed per the given bitmask. */ async archiveFileFrom(archiveEntry, checksumBitmask, callback) { return new ArchiveFile( archiveEntry, await this.fileFrom(archiveEntry.getFilePath(), checksumBitmask, callback) ); } /** * Assuming we've already checked if the file path has a valid archive extension, assume that * archive extension is accurate and parse the archive. * * This ordering should match {@link ROMScanner#archiveEntryPriority} */ async entriesFromArchive(archive, checksumBitmask, cacheModeValue = CacheMode.RESPECT_CACHED_VALUE, callback, shouldForceChecksumCalculation = false) { try { return await this.fileCache.getOrComputeArchiveChecksums( archive, checksumBitmask, cacheModeValue === CacheMode.IGNORE_CACHED_VALUE, callback, shouldForceChecksumCalculation ); } catch (error) { logger.warn( `${archive.getFilePath()}: failed to parse ${archive.getExtension()} file: ${error}` ); return void 0; } } archiveFromArchiveExtension(filePath, fileExt = filePath.replace(/.+?(?=(\.[a-zA-Z0-9]+)+)/, "")) { if (Zip.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new Zip(filePath)]; } if (Tar.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new Tar(filePath)]; } if (Rar.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new Rar(filePath)]; } if (Gzip.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new Gzip(filePath)]; } if (SevenZip.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new SevenZip(filePath)]; } if (Z.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new Z(filePath)]; } if (ZipSpanned.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new ZipSpanned(filePath)]; } if (ZipX.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new ZipX(filePath)]; } if (Cso.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new Cso(filePath)]; } if (Dax.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new Dax(filePath)]; } if (Zso.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new Zso(filePath)]; } if (Gcz.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new Gcz(filePath)]; } if (Rvz.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new Rvz(filePath)]; } if (Wia.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new Wia(filePath)]; } if (Chd.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new ChdBinCue(filePath), new ChdGdi(filePath), new ChdRaw(filePath)]; } if (NkitIso.getExtensions().some((ext) => fileExt.toLowerCase().endsWith(ext))) { return [new NkitIso(filePath)]; } return []; } /** * Without knowing if the file is an archive or not, read its file signature, and if there is a * match then parse the archive. * * This ordering should match {@link ROMScanner#archiveEntryPriority} */ async entriesFromArchiveSignature(filePath, checksumBitmask, callback) { let signature; try { const file = await this.fileFrom(filePath, checksumBitmask, callback); signature = await this.fileCache.getOrComputeFileSignature(file); } catch { return void 0; } if (!signature) { return void 0; } const archives = this.archiveFromArchiveExtension(filePath, signature.getExtension()); if (archives.length === 0) { return void 0; } const entries = []; let wasAnyParsed = false; for (const archive of archives) { const result = await this.entriesFromArchive( archive, checksumBitmask, CacheMode.RESPECT_CACHED_VALUE, callback ); if (result !== void 0) { wasAnyParsed = true; for (const entry of result) { entries.push(entry); } } } if (!wasAnyParsed) { return void 0; } return entries; } /** * Return true if the given file path ends with an extension recognized as a supported * archive format. */ static isExtensionArchive(filePath) { return [ ...Zip.getExtensions(), ...Tar.getExtensions(), ...Rar.getExtensions(), // 7zip ...Gzip.getExtensions(), ...SevenZip.getExtensions(), ...Z.getExtensions(), ...ZipSpanned.getExtensions(), ...ZipX.getExtensions(), // Compressed images ...Cso.getExtensions(), ...Dax.getExtensions(), ...Zso.getExtensions(), ...Gcz.getExtensions(), ...Rvz.getExtensions(), ...Wia.getExtensions(), ...Chd.getExtensions(), ...NkitIso.getExtensions() ].some((ext) => filePath.toLowerCase().endsWith(ext)); } /** * Return the {@link ROMHeader} for a file if its contents match a known ROM header signature, * or undefined otherwise. */ async headerFrom(file) { return await this.fileCache.getOrComputeFileHeader(file); } /** * Return the {@link FileSignature} detected from the file's contents, or undefined if no * known signature matches. */ async signatureFrom(file, callback) { return await this.fileCache.getOrComputeFileSignature(file, callback); } /** * Return the set of {@link ROMPadding} entries describing trailing fill-byte padding that * could be stripped from the file to recover an unpadded ROM. */ async paddingsFrom(file, callback) { return await this.fileCache.getOrComputeFilePaddings(file, callback); } /** * Return the TorrentZip validation result for a zip file, indicating whether its structure * conforms to the TorrentZip specification. */ async tzValidationFrom(zip, cacheModeValue = CacheMode.RESPECT_CACHED_VALUE) { return await this.fileCache.getOrComputeTzValidation( zip, cacheModeValue === CacheMode.IGNORE_CACHED_VALUE ); } } export { CacheMode, FileFactory as default }; //# sourceMappingURL=fileFactory.js.map