const { BaseWriter } = require('./base_writer');
const { Context } = require('../../../core/context');
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
/**
* FileSystemWriter writes files directly to the file system.
* Use this for creating unserialzed bags.
*/
class FileSystemWriter extends BaseWriter {
/**
* Creates a new FileSystemWriter.
*
* @param {string} pathToOutputDir - The path to the directory
* that you want to write files into.
*
*/
constructor(pathToOutputDir) {
super('FileSystemWriter', writeIntoArchive);
/**
* pathToOutputDir is the path to the directory into which
* the FileSystemWriter will write its files.
*
* @type {string}
*/
this.pathToOutputDir = pathToOutputDir;
}
/**
* Returns a {@link PluginDefinition} object describing this plugin.
*
* @returns {PluginDefinition}
*/
static description() {
return {
id: '92e69251-0e76-412d-95b6-987a79f6fa71',
name: 'FileSystemWriter',
description: 'Built-in DART file system writer. Writes files directly into a directory.',
version: '0.1',
readsFormats: [],
writesFormats: ['directory'],
implementsProtocols: [],
talksToRepository: [],
setsUp: []
};
}
/**
* Writes a file into the directory. This method is asynchronous, emitting
* events 'fileAdded' when it's done writing a file.
*
* Files will be written in the order they were added. You'll get errors
* if bagItFile.absSourcePath does not exist or is not readable.
*
* @param {BagItFile} bagItFile - The BagItFile object describing the file
* to be added into the output directory.
*
* @param {Array<crypto.Hash>} cryptoHashes - An array of Node.js crypto.Hash
* objects used to calculate checksums of the files being written onto the
* file system. All digests are calculated during the write, so adding
* multiple hashes will not lead to multiple end-to-end reads of the
* input stream.
*
* You can omit this parameter if you don't care to calculate
* checksums. If present, the digests will be written into the
* bagItFile.checksums object. For example, if cryptoHashes includes md5
* and sha256 Hash objects, bagItFile.checksums will come out looking
* like this:
*
* @example
* bagItFile.checksums = {
* 'md5': '1234567890',
* 'sha256': '0987654321'
* }
*
*/
add(bagItFile, cryptoHashes = []) {
super.add(bagItFile, cryptoHashes);
var fsWriter = this;
/**
* @event FileSystemWriter#fileAdded - This event fires after a file
* has been written to the file system.
*
* @type {BagItFile}
*
*/
var data = {
bagItFile: bagItFile,
dest: path.join(this.pathToOutputDir , bagItFile.relDestPath),
hashes: cryptoHashes,
endFn: () => {
fsWriter.onFileWritten();
fsWriter.emit('fileAdded', bagItFile, fsWriter.percentComplete());
},
errFn: (err) => {
fsWriter.emit('error', err);
}
};
this._queue.push(data);
}
}
/**
* This is the worker function for the FileSystemWriter's one-at-a-time
* async queue. This fuction writes data from a single file into the
* target directory, calculating any necessary checksums along the way.
*
* @param {Object} data - An object containing information about what is
* to be written into the archive.
*
* @param {function} done - A callback that indicates when the writer has
* completed. The async library creates and manages this function.
*
* @private
*/
function writeIntoArchive(data, done) {
try {
_writeIntoArchive(data, done);
} catch (err) {
Context.logger.error(err);
Context.logger.error(err.stack);
data.errFn(err);
done(err, data);
}
}
function _writeIntoArchive(data, done) {
if (!fs.existsSync(path.dirname(data.dest))) {
try {
mkdirp.sync(path.dirname(data.dest), { mode: 0o755 });
} catch (err) {
// Windows AppVeyor problem. Lame.
// TODO: Fix data.dest. It should not include "C:"
if (err.code != 'EEXIST') {
throw err;
}
}
}
if (data.hashes.length == 0) {
// Use fast fs copy if there are no hashes to compute
fs.copyFileSync(data.bagItFile.absSourcePath, data.dest);
data.endFn();
done();
} else {
// Udderwize, pipe the data through the crypto hashes
var reader = fs.createReadStream(data.bagItFile.absSourcePath);
reader.on('error', function(err) {
data.errFn(err);
});
var writer = fs.createWriteStream(data.dest);
writer.on('error', function(err) {
data.errFn(err);
});
var cb = function() {
data.endFn();
done();
};
writer.on('finish', cb);
reader.pause();
for (var h of data.hashes) {
reader.pipe(h)
}
reader.pipe(writer);
reader.resume();
}
}
module.exports = FileSystemWriter;