const { PassThrough } = require('stream'); const { stat, access } = require('fs'); const fs = require('fs-extra'); const { createReadStream, createWriteStream, utimes } = require('fs'); const { createCipher, createDecipher } = require('crypto'); const { join } = require('path'); const { createGzip, createGunzip } = require('zlib'); const combiner = require('stream-combiner'); /** * @function getStats * @description Returns the directory/file stat * @param {String} path Path to verify * @return {Promies} Null if an error happens or the object of stat */ const getStats = path => new Promise(resolve => { return stat(path, (err, stat) => { if (err) { // File does not exist return resolve(null); } // Return stat return resolve(stat); }); }); module.exports.getStats = getStats; /** * @function exists * @description Returns if a path exists * @param {String} path Path to verify * @return {Promise} Returns a boolean to indicate if path exists */ module.exports.exists = path => new Promise(resolve => { return access(path, err => { if (err) { return resolve(false); } return resolve(true); }); }); /** * @function delete * @description Deletes the path * @param {String} path Path to delete * @return {Promise} Resolves after deleted */ module.exports.delete = path => new Promise(resolve => { return fs.remove(path, () => resolve()); }); /** * @function copy * @description Copies the file from the source to the target path * @param {String} source File path to copy * @param {String} target Path to put the file * @param {String} crypt The cipher algorithm and key * @param {String} decrypt The decipher algorithm and key * @return {Promise} Resolves if was OK or rejects with the error object */ module.exports.copy = (source, target, crypt, decrypt) => new Promise(async (resolve, reject) => { const [ statSource, statTarget ] = await Promise.all([ getStats(source), getStats(target) ]); if (!statSource) { return reject(new Error('Source does not exists.')); } if (!statSource.isFile() || (statTarget && !statTarget.isFile())) { return reject(new Error(`It's possible just to copy files.`)); } let cryptOrDecryptFn = null; let zipFn = null; let unzipFn = null; if (crypt) { // Set function to crypt cryptOrDecryptFn = createCipher(crypt.algorithm, crypt.password); zipFn = createGzip(); unzipFn = new PassThrough(); } else if (decrypt) { // Set function to decrypt cryptOrDecryptFn = createDecipher(decrypt.algorithm, decrypt.password); zipFn = new PassThrough(); unzipFn = createGunzip(); } else { // Set empty stream just to pipe cryptOrDecryptFn = new PassThrough(); zipFn = new PassThrough(); unzipFn = new PassThrough(); } const destStream = createWriteStream(target); destStream.on('finish', () => { // Change target modificate time return utimes(target, statSource.atime, statSource.mtime, () => { return resolve(); }); }); destStream.on('error', err => reject(err)); const combinedStreams = combiner([ zipFn, cryptOrDecryptFn, unzipFn ]); // Read the file > crypt/decrypt > write to the target > resolve the promise return createReadStream(source) .pipe(combinedStreams) .pipe(destStream); }); /** * @function readRecursive * @description Reads recursivelly a path * @param {String} source Path to copy * @param {String} target Target path * @return {Promise>>} Resolves with the paths if was OK or rejects with the error object */ module.exports.readRecursive = (source, target) => new Promise((resolve, reject) => { // Read things inside dir return fs.readdir(source, (err, files) => { if (err) { return reject(err); } // Return an array with new source and target const pathsParsed = files.map(f => [ join(source, f), join(target, f) ]); }); });