UNPKG

mp3tag

Version:

A library for reading/writing mp3 tag data

178 lines (157 loc) 5.55 kB
const fs = require('fs') /** A generic file class, which keeps track of the current writing position and * provides some additional utility methods. */ class File { constructor(fd, name, size) { this.fd = fd; this.name = name; this.pos = 0; this.size = size; } /** Read data from file at the current file position into the passed buffer * * @param {Buffer} buffer the buffer to read into * @param {number} offset the offset in the buffer to start writing in * @param {number} length the number of bytes to read * * @return {Promise<number>} returns a promise that resolves to the number of actually read bytes */ read(buffer, offset, length) { return new Promise((resolve, reject) => { fs.read(this.fd, buffer, offset, length, this.pos, (err, bytesRead, buffer) => { if (err) { return reject(err); } this.pos += bytesRead; resolve(bytesRead); }); }); } /** Writes the given buffer into the file at the current write position * * @param {Buffer} buffer the buffer to write * * @return {Promise<number>} resolves to the number of bytes written */ write(buffer) { return this.writeSlice(buffer, 0, buffer.length); } /** Writes the bytes from the buffer into the file at the file's current * write position. * * @param {Buffer} buffer the buffer to write into the file * @param {number} offset the buffer offset to start writing from * @param {number} length the number of bytes to write * * @return {Promise<number>} resolves to the number of bytes actually written into the file */ writeSlice(buffer, offset, length) { return new Promise((resolve, reject) => { fs.write(this.fd, buffer, offset, length, this.pos, (err, bytes, buffer) => { if(err) { return reject(err); } this.pos += bytes; resolve(bytes); }); }); } /** Returns a function that can be repeatedly called to write a buffer into * this file. * * @return {(buffer:Buffer, offset:number, length:number)=>Promise<number>} */ bufferWriter() { return File.prototype.writeSlice.bind(this); } /** This function accepts a file offset and a length and * reads out the data as buffer. * * @param {number} offset the file offset to start reading * @param {number} length the length of the data to read * @param callback(err,buff) optional callback to receive the result. * reading less then length bytes is also treated as error. * * @return {Promise<Buffer>} a promise that resolves to the read buffer */ readSlice(offset, length, callback) { return new Promise((resolve, reject) => { const buffer = Buffer.alloc(length); if (length === 0) { return process.nextTick(() => { callback ? callback(null, buffer) : resolve(buffer); }); } fs.read(this.fd, buffer, 0, length, offset, (err, bytes, buffer) => { if (err) { return callback ? callback(err) : reject(err); } if (bytes !== length) { //TODO create a type for this kind of error to handle it programatically err = new Error(`File end reached. Only ${bytes} were read instead of ${length}`); return callback ? callback(err) : reject(err); } callback ? callback(null, buffer) : resolve(buffer); }); }); } /** Sets the file position further by the specified amount of bytes, relative to * either 'pos' or 'start' * * @param {number} nBytes the number of bytes to move forward * @param {string} relativeTo the position to move relative to 'pos' = current pos, 'start' = file start */ seek(nBytes, relativeTo) { const offset = (relativeTo === 'start') ? 0 : this.pos; this.pos = offset + nBytes; } /** Closes the file */ close() { fs.close(this.fd, () => {}); this.fd = undefined; this.pos = undefined; } } /** Actual open function to create a file object by asynchronously opening the specified file. * The file read position will be positioned at the start of the file even if "a" is specified. * * @param {string} path which file to open * @param {string} mode the filemode to open the file with (one of: "r", "w", "a") * * @return {Promise<File>} a promise that resolves to the opened file object */ File.open = function(path, mode, callback) { if (mode === "a") { mode = "r+"; //<- Portable way of making positional writes to existing file } return new Promise((resolve, reject) => { fs.open(path, mode, (err, fd) => { if (err) { return reject(err); } fs.stat(path, (err, stat) => { if (err) { return reject(err); } const file = new File(fd, path, stat.size); return resolve(file); }); }); }); }; /** Utility function to read a whole file into a buffer. Simply an * promisified version of fs.readFile (usable before NodeJS v10) * * @param {string} path the file path * @returns {Promise<Buffer>} */ File.readIntoBuffer = function(path) { return new Promise((resolve, reject) => { fs.readFile(path, (err, buffer) => { if (err) { return reject(err); } return resolve(buffer); }) }); }; module.exports = File;