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.

155 lines (154 loc) • 5.3 kB
import fs from 'node:fs'; import Defaults from '../globals/defaults.js'; import FsPoly from './fsPoly.js'; /** * A wrapper for readable and writable files */ export default class IOFile { pathLike; fileHandle; size; tempBuffer; readPosition = 0; fileBuffer; constructor(pathLike, fileHandle, size) { this.pathLike = pathLike; this.fileHandle = fileHandle; this.size = size; this.tempBuffer = Buffer.allocUnsafe(Math.min(this.size, Defaults.FILE_READING_CHUNK_SIZE)); } /** * Return a new {@link IOFile} from a {@param pathLike}, with the {@param flags} mode. */ static async fileFrom(pathLike, flags) { return new IOFile(pathLike, await fs.promises.open(pathLike, /** * "On Linux, positional writes don't work when the file is opened in append mode. The * kernel ignores the position argument and always appends the data to the end of the * file." * @see https://nodejs.org/api/fs.html#file-system-flags */ flags.toString().startsWith('a') ? 'r+' : flags), await FsPoly.size(pathLike)); } /** * Return a new {@link IOFile} of size {@param size} from a {@param pathLike}, with the * {@param flags} mode. If the {@param pathLike} already exists, the existing file will be * deleted. */ static async fileOfSize(pathLike, flags, size) { if (await FsPoly.exists(pathLike)) { await FsPoly.rm(pathLike, { force: true }); } const write = await this.fileFrom(pathLike, 'wx'); let written = 0; while (written < size) { const buffer = Buffer.alloc(Math.min(size - written, Defaults.FILE_READING_CHUNK_SIZE)); await write.write(buffer); written += buffer.length; } await write.close(); return this.fileFrom(pathLike, flags); } getPathLike() { return this.pathLike; } /** * @returns if the seek position of the file has reached the end */ isEOF() { return this.getPosition() >= this.getSize(); } getPosition() { return this.readPosition; } getSize() { return this.size; } /** * Seek to a specific {@param position} in the file */ seek(position) { this.readPosition = position; } /** * Seek to the current position plus {@param size} */ skipNext(size) { this.readPosition += size; } /** * @returns the next {@param size} bytes from the current seek position, without changing the * seek position */ async peekNext(size) { return this.readAt(this.readPosition, size); } /** * @returns the next {@param size} bytes from the current seek position, also incrementing the * seek position the same amount */ async readNext(size) { const result = await this.readAt(this.readPosition, size); this.readPosition += size; return result; } /** * @returns bytes of size {@param size} at the seek position {@param offset} */ async readAt(position, size) { if (size > this.tempBuffer.length) { this.tempBuffer = Buffer.allocUnsafe(size); } // If the file is small, read the entire file to memory and "read" from there if (this.size <= Defaults.MAX_MEMORY_FILE_SIZE) { if (!this.fileBuffer) { this.tempBuffer = Buffer.alloc(0); this.fileBuffer = await FsPoly.readFile(this.fileHandle.fd); } return Buffer.from(this.fileBuffer.subarray(position, position + size)); } // If the file is large, read from the open file handle let bytesRead = 0; try { bytesRead = (await this.fileHandle.read(this.tempBuffer, 0, size, position)).bytesRead; } catch { // NOTE(cemmer): Windows will give "EINVAL: invalid argument, read" when reading out of // bounds, but other OSes don't. Swallow the error. return Buffer.alloc(0); } return Buffer.from(this.tempBuffer.subarray(0, bytesRead)); } /** * Write {@param buffer} to the current seek position */ async write(buffer) { const bytesWritten = await this.writeAt(buffer, this.readPosition); this.readPosition += buffer.length; return bytesWritten; } /** * Write {@param buffer} at the seek position {@param offset} */ async writeAt(buffer, position) { const { bytesWritten } = await this.fileHandle.write(buffer, 0, buffer.length, position); if (this.fileBuffer) { if (position + bytesWritten > this.fileBuffer.length) { this.fileBuffer = Buffer.concat([ this.fileBuffer, Buffer.allocUnsafe(position + bytesWritten - this.fileBuffer.length), ]); } for (let i = 0; i < bytesWritten; i += 1) { this.fileBuffer[position + i] = buffer[i]; } } return bytesWritten; } /** * Close the underlying file handle */ async close() { return this.fileHandle.close(); } }