ui/controllers/bag_validation_controller.js

const $ = require('jquery');
const { BagItProfile } = require('../../bagit/bagit_profile');
const { BagValidationForm } = require('../forms/bag_validation_form');
const { BaseController } = require('./base_controller');
const { Constants } = require('../../core/constants');
const { Context } = require('../../core/context');
const { DartProcess } = require('../../core/dart_process');
const { fork } = require('child_process');
const fs = require('fs');
const { Job } = require('../../core/job');
const path = require('path');
const { RunningJobsController } = require('./running_jobs_controller');
const Templates = require('../common/templates');
const { Util } = require('../../core/util');

/**
 * The BagValidationController presents a page on which users can
 * validate a bag against a BagIt profile.
 *
 * @param {URLSearchParams} params - The URL search params parsed
 * from the URL used to reach this page. This should contain at
 * least the Job Id.
 *
 * @param {string} params.id - The id of the Job being worked
 * on. Job.id is a UUID string.
 */
class BagValidationController extends RunningJobsController {

    constructor(params) {
        super(params, 'Jobs');
        this.model = Job;
        this.job = new Job();
        this.form = null;
    }

    /**
     * This displays a form where users can choose a bag and
     * a profile.
     */
    show() {
        this.form = new BagValidationForm(this.job);
        let data = {
            job: this.job,
            form: this.form
        }
        let html = Templates.bagValidationForm(data);
        return this.containerContent(html);
    }

    validateBag() {
        this._startValidationJob()
        this._initResultsDiv()
        this._attachEvents()
    }

    _startValidationJob() {
        this.form.parseFromDOM();
        let job = this.form.obj;
        let tmpFile = Util.tmpFilePath();
        fs.writeFileSync(tmpFile, JSON.stringify(job));
        let modulePath = path.join(__dirname, '..', '..', 'main.js');
        this.childProcess = fork(
                modulePath,
                ['--job', tmpFile]
        );
        this.dartProcess = new DartProcess(
            this.job.title,
            this.job.id,
            this.childProcess
        );
        Context.childProcesses[this.dartProcess.id] = this.dartProcess;
    }

    _initResultsDiv() {
        $('#btnValidate').prop('disabled', true);
        let processDiv = $('#dartProcessContainer');
        let html = Templates.partials['dartProcess']({ item: this.dartProcess });
        processDiv.html(html);
        this.initProgressBar(this.dartProcess, 'validationInfo');
        $(`#${this.dartProcess.id} div.validationInfo`).show();
        processDiv.show();
    }

    _attachEvents() {
        let controller = this;
        this.dartProcess.process.on('message', (data) => {
            controller.renderValidationInfo(data, this.dartProcess);
        });

        this.dartProcess.process.on('exit', (code, signal) => {
            Context.logger.info(`Process ${this.dartProcess.process.pid} exited with code ${code}, signal ${signal}`);
            delete Context.childProcesses[this.dartProcess.id];
            controller._renderOutcome(code)
        });
    }

    _renderOutcome(code) {
        let job = Job.find(this.job.id)
        let [detailDiv, progressBar] = this.getDivs(this.dartProcess, 'outcome');
        if (code == 0) {
            this.markSuccess(detailDiv, progressBar, Context.y18n.__('Job completed successfully.'));
        } else {
            let msg = Context.y18n.__('The bag is not valid according to the selected profile.')
            Context.logger.error(msg);
            msg += `<br/>${job.getRunErrors().join("<br/>")}`
            this.markFailed(detailDiv, progressBar, msg.replace(/\n/g, '<br/>'));
        }
        // Button exists on job "Review and Run" page, not dashboard.
        $('#btnValidate').prop('disabled', false);

        // The child process stores a record of the job in the
        // Jobs db. Delete that DB copy, so validation jobs don't
        // cause too much clutter.
        job.delete();
    }

    postRenderCallback(fnName) {
        let controller = this;
        $('#pathToBag').on('change',function(e){
            let element = document.getElementById('pathToBag');
            if (element && element.files && element.files[0]) {
                var filename = document.getElementById('pathToBag').files[0].path
                // Our form includes attr webkitdirectory on the file input.
                // If user selects a directory, it will have more than one
                // file. If we see that, change filename to the dirname.
                // User could not have manually selected multiple files because
                // we did not set the multiple attribute on the file input.
                if (element.files.length > 1) {
                    filename = path.dirname(filename);
                }
                $(this).next('.custom-file-label').html(filename);
                $('#btnValidate').prop('disabled', false);
                $('#dartProcessContainer').html('');
            }
        })
        $('#bagTypeFile').on('click',function(e){
            let fileInput = $('#pathToBag');
            fileInput.files = [];
            fileInput.removeAttr('webkitdirectory');
            fileInput.attr('accept', '.tar');
            $(fileInput).next('.custom-file-label').html(
                Context.y18n.__("Choose a file...")
            );
        })
        $('#bagTypeDirectory').on('click',function(e){
            let fileInput = $('#pathToBag');
            fileInput.files = [];
            fileInput.attr('webkitdirectory', true);
            fileInput.removeAttr('accept');
            $(fileInput).next('.custom-file-label').html(
                Context.y18n.__("Choose a directory...")
            );
        })
        $('#btnValidate').on('click', () => { controller.validateBag() })
    }

}

module.exports.BagValidationController = BagValidationController;