"use strict"; const _ = require(`lodash`); const { existsSync } = require(`fs`); const queue = require(`async/queue`); const { processFile } = require(`./process-file`); const { createProgress } = require(`./utils`); const toProcess = {}; let totalJobs = 0; const q = queue((task, callback) => { task(callback); }, 1); let bar; // when the queue is empty we stop the progressbar q.drain = () => { if (bar) { bar.done(); } totalJobs = 0; }; exports.scheduleJob = async (job, boundActionCreators, pluginOptions, reporter, reportStatus = true) => { const inputFileKey = job.inputPath.replace(/\./g, `%2E`); const outputFileKey = job.outputPath.replace(/\./g, `%2E`); const jobPath = `${inputFileKey}.${outputFileKey}`; // Check if the job has already been queued. If it has, there's nothing // to do, return. if (_.has(toProcess, jobPath)) { return _.get(toProcess, `${jobPath}.deferred.promise`); } // Check if the output file already exists so we don't redo work. if (existsSync(job.outputPath)) { return Promise.resolve(job); } let isQueued = false; if (toProcess[inputFileKey]) { isQueued = true; } // deferred naming comes from https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred let deferred = {}; deferred.promise = new Promise((resolve, reject) => { deferred.resolve = resolve; deferred.reject = reject; }); if (totalJobs === 0) { bar = createProgress(`Generating image thumbnails`, reporter); bar.start(); } totalJobs += 1; _.set(toProcess, jobPath, { job: job, deferred }); if (!isQueued) { // Create image job boundActionCreators.createJob({ id: `processing image ${job.inputPath}`, imagesCount: 1 }, { name: `gatsby-plugin-sharp` }); q.push(cb => { runJobs(inputFileKey, boundActionCreators, pluginOptions, reportStatus, cb); }); } return deferred.promise; }; function runJobs(inputFileKey, boundActionCreators, pluginOptions, reportStatus, cb) { const jobs = _.values(toProcess[inputFileKey]); const findDeferred = job => jobs.find(j => j.job === job).deferred; const { job } = jobs[0]; // Delete the input key from the toProcess list so more jobs can be queued. delete toProcess[inputFileKey]; // Update job info boundActionCreators.setJob({ id: `processing image ${job.inputPath}`, imagesCount: jobs.length }, { name: `gatsby-plugin-sharp` }); // We're now processing the file's jobs. let imagesFinished = 0; bar.total = totalJobs; try { const promises = processFile(job.inputPath, job.contentDigest, jobs.map(job => job.job), pluginOptions).map(promise => promise.then(job => { findDeferred(job).resolve(); }).catch(err => { findDeferred(job).reject({ err, message: `Failed to process image ${job.inputPath}` }); }).then(() => { imagesFinished += 1; // only show progress on build if (reportStatus) { bar.tick(); } boundActionCreators.setJob({ id: `processing image ${job.inputPath}`, imagesFinished }, { name: `gatsby-plugin-sharp` }); })); Promise.all(promises).then(() => { boundActionCreators.endJob({ id: `processing image ${job.inputPath}` }, { name: `gatsby-plugin-sharp` }); cb(); }); } catch (err) { jobs.forEach(({ deferred }) => { deferred.reject({ err, message: err.message }); }); } }