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
JavaScript
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