workers/bag_creator.js

const { Bagger } = require('../bagit/bagger');
const { Constants } = require('../core/constants');
const { Context } = require('../core/context');
const fs = require('fs');
const { Job } = require('../core/job');
const { Util } = require('../core/util');
const { Worker } = require('./worker');

/**
 * BagCreator assembles a bag by copying source files into a
 * bagging directory, writing manifests and tag manifests. This
 * is a thin wrapper around {@link Bagger}. It catches events
 * from the underlying bagger, wraps them in {@link JobStatus}
 * objects, and writes them to STDOUT and STDERR to communicate
 * with the parent process.
 *
 * param {Job} - The job to run. The job object must contain a
 * {@link BagItProfile} and a {@link PackageOperation} to be
 * valid.
 */
class BagCreator extends Worker {

    constructor(job) {
        super('package');
        this.job = job;
    }

    /**
     * This runs the job's package operation, creating the bag. It returns
     * a promise.
     *
     * @returns {Promise}
     */
    run() {
        var creator = this;
        if (!this.job.packageOp.validate()) {
            return new Promise(function(resolve, reject) {
                let errors = Object.values(creator.job.packageOp.errors);
                reject(creator.validationError(errors));
            });
        }
        let dirError = creator.errorOnExistingDir(this.job.packageOp.outputPath)
        if (dirError != null) {
            return new Promise(function(resolve, reject) {
                reject(creator.runtimeError('start', dirError.Message, dirError));
            });
        }

        var bagger = new Bagger(this.job);
        bagger.on('packageStart', function(message) {
            creator.info('start', Constants.OP_IN_PROGRESS, message, 0, false);
        });
        bagger.on('fileAdded', function(bagItFile, percentComplete) {
            creator.info('fileAdded', Constants.OP_IN_PROGRESS, bagItFile.relDestPath, percentComplete, false);
        });
        var promise = new Promise(function(resolve, reject) {
            // Finish never fires. Why? But promise resolves. How?
            bagger.on('finish', function() {
                let result = bagger.job.packageOp.result;
                if (result.errors.length > 0) {
                    creator.completedWithError(result.errors);
                } else {
                    creator.completedSuccess('Bag created');
                }
                resolve(result);
            });
            bagger.on('error', function(err) {
                let result = bagger.job.packageOp.result;
                if (typeof err == 'string') {
                    creator.runtimeError('fileAdded', [err], null);
                } else {
                    result.finish(err);
                    creator.runtimeError('fileAdded', null, err);
                }
                reject(result);
            });
        });
        bagger.create();
        return promise;
    }

    /**
     * errorOnExistingDir causes the bag creator to stop with an error
     * if the directory to which we're writing this bag already exists and
     * contains files. Writing a bag into a non-empty directory will usually
     * cause bag validation to fail, as described in
     * https://github.com/APTrust/dart/issues/280
     *
     * @param outputPath {string} - The path to which the bag will be written.
     *
     * @returns {Error}
     */
    errorOnExistingDir(outputPath) {
        if (Util.isNonEmptyDirectory(outputPath)) {
            let msg = Context.y18n.__("Output directory %s already exists and contains files. You must delete it before writing a new bag into it.", outputPath)
            return new Error(msg)
        }
        return null
    }
}

module.exports.BagCreator = BagCreator;