const fs = require("node:fs");
/**
*
* @class
*
* @abstract
*
* VolumeDriver is an abstract class that serves as the base class for different types of volume drivers.
*
* It should not be instantiated directly. Instead, you should create instances of classes that extend VolumeDriver.
*
*
*
* @property {string} _path - The path to the file.
*
* @property {Object} opts - Optional parameters.
*
* @property {boolean} opts.readOnly - Whether the file is read-only.
*
* @property {number} _fileMode - The mode for file operations. It's determined by the read-only option.
*
*
*
* @method partitionOffsetBytes() - This method should be implemented in a subclass.
*
* @method partitionNumber() - This method should be implemented in a subclass.
*
* @method readOnly() - This method should be implemented in a subclass.
*
* @method checkSectorLength(dest) - This method should be implemented in a subclass.
*
* @method readSectors(_i, _dest, _cb) - This method should be implemented in a subclass.
*
* @method writeSectors(_i, _data, _cb) - This method should be implemented in a subclass.
*
* @method parsePartitionsFromBuffer(buffer) - This method should be implemented in a subclass.
* @method readPartitions() - This method should be implemented in a subclass.
* @method sectorSize() - This method should be implemented in a subclass.
* @method numSectors() - This method should be implemented in a subclass.
*/
class VolumeDriver {
/**
* Abstract constructor for the class. Should not be called directly.
*
* @param {_path} _path - The path to the file.
* @param {Object} opts - Optional parameters.
* @param {boolean} opts.readOnly - Whether the file is read-only.
* @return {void}
*/
constructor(_path, opts = {}) {
if (new.target === VolumeDriver) {
throw new TypeError("Cannot construct VolumeDriver instances directly");
}
let fileMode = fs.constants.R_OK;
if (!opts.readOnly) {
// eslint-disable-next-line no-bitwise
fileMode |= fs.constants.W_OK;
}
this._readOnly = opts.readOnly;
this._fileMode = fileMode;
}
/**
* Returns the offset bytes for the currently selected partition
*
* @return {any} The offset bytes used for reading / writing.
*/
get partitionOffsetBytes() {
return this._partitionOffsetBytes;
}
/**
* Get the value of the partitionNumber property.
*
* @return {any} The current partitionNumber, or 0 if no partitions exist.
*/
get partitionNumber() {
return this._partitionNumber;
}
/**
* Set the partition number and calculate the partition offset in bytes.
*
* @param {number} partitionNumber - The number of the partition to set, or 0 for no partitions.
* @throws {Error} If the partition number does not exist.
*/
set partitionNumber(partitionNumber) {
if (partitionNumber === 0) {
this._partitionNumber = 0;
this._partitionOffsetBytes = 0;
return;
}
if (
partitionNumber < 1 ||
partitionNumber > this._partitionLBAList.length
) {
throw new Error(`Partition ${partitionNumber} does not exist!`);
}
this._partitionNumber = partitionNumber;
this._partitionOffsetBytes =
this._partitionLBAList[this._partitionNumber - 1] * this.sectorSize;
}
/**
* Get the value of the readOnly property.
*
* @return {type} Whether the file should be treated as read-only.
*/
get readOnly() {
return this._readOnly;
}
/**
* Checks if the length of a destination buffer is a multiple of the sector size.
*
* @param {any} dest - The destination buffer to check.
* @throws {Error} Throws an error if the buffer length is not a multiple of the sector size.
*/
checkSectorLength(dest) {
if (dest.length % this.sectorSize) {
throw Error("Unexpected buffer length!");
}
}
/**
* Abstract method for reading sectors. Should not be called directly.
*
* @param {number} i - The index of the sector to read.
* @param {ArrayBuffer} dest - The destination array to copy the sector data to.
* @param {function} cb - The callback function to be called when the sector data has been copied.
* @return {undefined} This function does not return a value.
*/
readSectors(_i, _dest, _cb) {
throw new Error("Abstract method 'readSectors' must be implemented");
}
/**
* Abstract method for writing sectors. Should not be called directly.
*
* @param {number} i - The index of the sector to write to.
* @param {Buffer} data - The data to write to the sector.
* @param {Function} cb - The callback function to be called when the write operation is complete.
* @throws {Error} Cannot write to read-only volume!
*/
writeSectors(_i, _data, _cb) {
throw new Error("Abstract method 'writeSectors' must be implemented");
}
/**
* Parses partitions from a buffer. Reads a FAT16 MBR and returns an array of partition offsets.
*
* @param {Buffer} buffer - The buffer to parse partitions from.
* @return {Array} An array of partition offsets.
*/
parsePartitionsFromBuffer(buffer) {
const partitionOffsets = [];
for (let i = 446; i < 510; i += 16) {
partitionOffsets.push(buffer.readInt32LE(i + 8));
}
return partitionOffsets;
}
/**
* Abstract method for reading partitions. Should not be called directly.
*
* @throws {Error} Abstract method 'readPartitions' must be implemented
*/
readPartitions() {
throw new Error("Abstract method 'readPartitions' must be implemented");
}
/**
* Get the sector size.
*
* @return {number} The sector size. Currently always returns 512.
*/
get sectorSize() {
return 512;
}
/**
* Abstract method for getting the number of sectors. Should not be called directly.
*
* @return {Error} Abstract method 'numSectors' must be implemented
*/
get numSectors() {
throw new Error("Abstract method 'numSectors' must be implemented");
}
}
module.exports = VolumeDriver;