diff --git a/lib/cli/etcher.js b/lib/cli/etcher.js index 77ffe11a..60fc5b11 100644 --- a/lib/cli/etcher.js +++ b/lib/cli/etcher.js @@ -74,8 +74,9 @@ permissions.isElevated().then((elevated) => { } const progressBars = new Map() - let lastStateType + let lastStateType = null + // eslint-disable-next-line require-jsdoc const onProgress = (state) => { state.message = state.active > 1 ? `${bytes(state.totalSpeed)}/s total, ${bytes(state.speed)}/s x ${state.active}` @@ -83,21 +84,25 @@ permissions.isElevated().then((elevated) => { state.message = `${state.type === 'flashing' ? 'Flashing' : 'Validating'}: ${state.message}` + // eslint-disable-next-line no-undefined if (state.percentage === undefined) { state.message += ` - ${bytes(state.bytes)} written` } // Update progress bar let progressBar = progressBars.get(state.type) + // eslint-disable-next-line no-undefined if (progressBar === undefined) { // Stop the spinner if there is one - if ((lastStateType !== undefined) && (lastStateType !== state.type)) { + if ((lastStateType !== null) && (lastStateType !== state.type)) { const spinner = progressBars.get(lastStateType) + // eslint-disable-next-line no-undefined if ((spinner !== undefined) && (spinner instanceof visuals.Spinner)) { console.log() spinner.stop() } } + // eslint-disable-next-line no-undefined if (state.percentage === undefined) { progressBar = new visuals.Spinner(state.message) progressBar.start() @@ -106,12 +111,10 @@ permissions.isElevated().then((elevated) => { progressBar.update(state) } progressBars.set(state.type, progressBar) + } else if (progressBar instanceof visuals.Spinner) { + progressBar.spinner.setSpinnerTitle(state.message) } else { - if (progressBar instanceof visuals.Spinner) { - progressBar.spinner.setSpinnerTitle(state.message) - } else { - progressBar.update(state) - } + progressBar.update(state) } lastStateType = state.type } @@ -125,28 +128,28 @@ permissions.isElevated().then((elevated) => { scanner.on('error', reject) scanner.start() }) - .then(() => { - return (new sdk.sourceDestination.File(imagePath, sdk.sourceDestination.File.OpenFlags.Read)).getInnerSource() - }) - .then((innerSource) => { - // NOTE: Drive can be (String|Array) - const destinations = [].concat(answers.drive).map((device) => { - const drive = scanner.getBy('device', device) - if (drive === undefined) { - throw new Error(`No such drive ${device}`) - } - return drive + .then(() => { + return (new sdk.sourceDestination.File(imagePath, sdk.sourceDestination.File.OpenFlags.Read)).getInnerSource() + }) + .then((innerSource) => { + // NOTE: Drive can be (String|Array) + const destinations = _.map([].concat(answers.drive), (device) => { + const drive = scanner.getBy('device', device) + if (!drive) { + throw new Error(`No such drive ${device}`) + } + return drive + }) + return sdk.multiWrite.pipeSourceToDestinations( + innerSource, + destinations, + (destination, error) => { + console.log(`Error "${error}" on ${destination.drive}`) + }, + onProgress, + options.check + ) }) - return sdk.multiWrite.pipeSourceToDestinations( - innerSource, - destinations, - (destination, error) => { - console.log(`Error "${error}" on ${destination.drive}`) - }, - onProgress, - options.check - ) - }) }).then(({ failures, bytesWritten }) => { let exitCode = EXIT_CODES.SUCCESS diff --git a/lib/gui/app/app.js b/lib/gui/app/app.js index 48dc93f7..0912227a 100644 --- a/lib/gui/app/app.js +++ b/lib/gui/app/app.js @@ -232,13 +232,13 @@ app.run(() => { }) }) - app.run(($timeout) => { const BLACKLISTED_DRIVES = settings.has('driveBlacklist') ? settings.get('driveBlacklist').split(',') : [] - function driveIsAllowed(drive) { + // eslint-disable-next-line require-jsdoc + const driveIsAllowed = (drive) => { return !( BLACKLISTED_DRIVES.includes(drive.devicePath) || BLACKLISTED_DRIVES.includes(drive.device) || @@ -246,7 +246,8 @@ app.run(($timeout) => { ) } - function prepareDrive(drive) { + // eslint-disable-next-line require-jsdoc,consistent-return + const prepareDrive = (drive) => { if (drive instanceof sdk.sourceDestination.BlockDevice) { return drive.drive } else if (drive instanceof sdk.sourceDestination.UsbbootDrive) { @@ -261,9 +262,10 @@ app.run(($timeout) => { } } - function setDrives(drives) { - drives = _.values(drives) - availableDrives.setDrives(drives) + // eslint-disable-next-line require-jsdoc + const setDrives = (drives) => { + availableDrives.setDrives(_.values(drives)) + // Safely trigger a digest cycle. // In some cases, AngularJS doesn't acknowledge that the // available drives list has changed, and incorrectly @@ -271,32 +273,37 @@ app.run(($timeout) => { $timeout() } - function getDrives() { + // eslint-disable-next-line require-jsdoc + const getDrives = () => { return _.keyBy(availableDrives.getDrives() || [], 'device') } - function addDrive(drive) { - drive = prepareDrive(drive) - if (!driveIsAllowed(drive)) { + // eslint-disable-next-line require-jsdoc + const addDrive = (drive) => { + const preparedDrive = prepareDrive(drive) + if (!driveIsAllowed(preparedDrive)) { return } const drives = getDrives() - drives[drive.device] = drive + drives[preparedDrive.device] = preparedDrive setDrives(drives) } - function removeDrive(drive) { - drive = prepareDrive(drive) + // eslint-disable-next-line require-jsdoc + const removeDrive = (drive) => { + const preparedDrive = prepareDrive(drive) const drives = getDrives() - delete drives[drive.device] + // eslint-disable-next-line prefer-reflect + delete drives[preparedDrive.device] setDrives(drives) } - function updateDriveProgress(drive, progress) { + // eslint-disable-next-line require-jsdoc + const updateDriveProgress = (drive, progress) => { const drives = getDrives() - const drive_ = drives[drive.device] - if (drive !== undefined) { - drive.progress = progress + const driveInMap = drives[drive.device] + if (driveInMap) { + driveInMap.progress = progress setDrives(drives) } } diff --git a/lib/gui/app/models/store.js b/lib/gui/app/models/store.js index 13c336bd..19756d37 100644 --- a/lib/gui/app/models/store.js +++ b/lib/gui/app/models/store.js @@ -24,7 +24,6 @@ const constraints = require('../../../shared/drive-constraints') const supportedFormats = require('../../../shared/supported-formats') const errors = require('../../../shared/errors') const fileExtensions = require('../../../shared/file-extensions') -const utils = require('../../../shared/utils') const settings = require('./settings') /** @@ -46,7 +45,7 @@ const verifyNoNilFields = (object, fields, name) => { return _.isNil(_.get(object, field)) }) if (nilFields.length) { - throw new Error(`Missing ${name} fields: ${nilFields.join(', ')} ${JSON.stringify(object, null, 4)}`) + throw new Error(`Missing ${name} fields: ${nilFields.join(', ')}`) } } @@ -162,7 +161,7 @@ const storeReducer = (state = DEFAULT_STATE, action) => { const drives = action.data - //if (!_.isArray(drives) || !_.every(drives, _.isPlainObject)) { + // If (!_.isArray(drives) || !_.every(drives, _.isPlainObject)) { if (!_.isArray(drives)) { throw errors.createError({ title: `Invalid drives: ${drives}` @@ -407,6 +406,7 @@ const storeReducer = (state = DEFAULT_STATE, action) => { const MINIMUM_IMAGE_SIZE = 0 + // eslint-disable-next-line no-undefined if ((action.data.size !== undefined) && (action.data.size < MINIMUM_IMAGE_SIZE)) { throw errors.createError({ title: `Invalid image size: ${action.data.size}` diff --git a/lib/gui/app/modules/drive-scanner.js b/lib/gui/app/modules/drive-scanner.js index 3de66c2c..7a624411 100644 --- a/lib/gui/app/modules/drive-scanner.js +++ b/lib/gui/app/modules/drive-scanner.js @@ -21,7 +21,16 @@ const process = require('process') const settings = require('../models/settings') -function includeSystemDrives() { +/** + * @summary returns true if system drives should be shown + * @function + * + * @returns {Boolean} + * + * @example + * const shouldInclude = includeSystemDrives() + */ +const includeSystemDrives = () => { return settings.get('unsafeMode') && !settings.get('disableUnsafeMode') } @@ -31,6 +40,7 @@ const adapters = [ // Can't use permissions.isElevated() here as it returns a promise and we need to set // module.exports = scanner right now. +// eslint-disable-next-line no-magic-numbers if ((process.platform !== 'linux') || (process.geteuid() === 0)) { adapters.push(new sdk.scanner.adapters.UsbbootDeviceAdapter()) } diff --git a/lib/gui/app/modules/progress-status.js b/lib/gui/app/modules/progress-status.js index 6e2dae99..de0875fa 100644 --- a/lib/gui/app/modules/progress-status.js +++ b/lib/gui/app/modules/progress-status.js @@ -59,11 +59,11 @@ exports.fromFlashState = (state) => { return 'Finishing...' } else if (isFlashing) { + // eslint-disable-next-line no-eq-null if (state.percentage != null) { return `${state.percentage}% Flashing` - } else { - return `${units.bytesToClosestUnit(state.position)} flashed` } + return `${units.bytesToClosestUnit(state.position)} flashed` } else if (isValidating) { return `${state.percentage}% Validating` } else if (!isFlashing && !isValidating) { diff --git a/lib/gui/app/os/window-progress.js b/lib/gui/app/os/window-progress.js index 0053cc69..2efb4c1e 100644 --- a/lib/gui/app/os/window-progress.js +++ b/lib/gui/app/os/window-progress.js @@ -90,7 +90,8 @@ exports.currentWindow = electron.remote.getCurrentWindow() * }) */ exports.set = (state) => { - if (state.percentage != null) { + // eslint-disable-next-line no-eq-null + if (state.percentage !== null) { exports.currentWindow.setProgressBar(utils.percentageToFloat(state.percentage)) } exports.currentWindow.setTitle(getWindowTitle(state)) diff --git a/lib/gui/app/pages/main/controllers/image-selection.js b/lib/gui/app/pages/main/controllers/image-selection.js index 1a73b98e..5962a209 100644 --- a/lib/gui/app/pages/main/controllers/image-selection.js +++ b/lib/gui/app/pages/main/controllers/image-selection.js @@ -159,35 +159,36 @@ module.exports = function ( const source = new sdk.sourceDestination.File(imagePath, sdk.sourceDestination.File.OpenFlags.Read) source.getInnerSource() - .then((innerSource) => { - return innerSource.getMetadata() - .then((metadata) => { - return innerSource.getPartitionTable() - .then((partitionTable) => { - if (partitionTable !== undefined) { - metadata.hasMBR = true - metadata.partitions = partitionTable.partitions - } - $timeout(() => { - metadata.path = imagePath - metadata.extension = path.extname(imagePath).slice(1) - this.selectImage(metadata) + .then((innerSource) => { + return innerSource.getMetadata() + .then((metadata) => { + return innerSource.getPartitionTable() + .then((partitionTable) => { + if (partitionTable) { + metadata.hasMBR = true + metadata.partitions = partitionTable.partitions + } + $timeout(() => { + metadata.path = imagePath + // eslint-disable-next-line no-magic-numbers + metadata.extension = path.extname(imagePath).slice(1) + this.selectImage(metadata) + }) + }) + }) + .catch((error) => { + const imageError = errors.createUserError({ + title: 'Error opening image', + description: messages.error.openImage(path.basename(imagePath), error.message) + }) + osDialog.showError(imageError) + analytics.logException(error) + }) + .then(() => { + return innerSource.close() + .catch(_.noop) }) - }) }) - .catch((error) => { - const imageError = errors.createUserError({ - title: 'Error opening image', - description: messages.error.openImage(path.basename(imagePath), error.message) - }) - osDialog.showError(imageError) - analytics.logException(error) - }) - .then(() => { - return innerSource.close() - .catch(() => {}) - }) - }) } /** diff --git a/lib/gui/modules/child-writer.js b/lib/gui/modules/child-writer.js index d76f76bd..eba81709 100644 --- a/lib/gui/modules/child-writer.js +++ b/lib/gui/modules/child-writer.js @@ -16,7 +16,6 @@ 'use strict' -const Bluebird = require('bluebird') const _ = require('lodash') const ipc = require('node-ipc') const sdk = require('etcher-sdk') @@ -86,44 +85,67 @@ const handleError = (error) => { terminate(EXIT_CODES.GENERAL_ERROR) } -function lastMapValue(map) { - let value - for (value of map.values()){ +/** + * @summary returns the last value in a Map + * @param {Map} map - map + * @returns {Any} the last value of the map (in insertion order) + * + * @example + * const last = lastMapValue(map) + */ +const lastMapValue = (map) => { + let value = null + for (value of map.values()) { + // eslint-disable-next-line no-empty } return value } -function writeAndValidate(source, destinations, verify, onProgress, onFail, onFinish, onError) { +/** + * @summary writes the source to the destinations and valiates the writes + * @param {SourceDestination} source - source + * @param {SourceDestination[]} destinations - destinations + * @param {Boolean} verify - whether to validate the writes or not + * @param {Function} onProgress - function to call on progress + * @param {Function} onFail - function to call on fail + * @param {Function} onFinish - function to call on finish + * @param {Function} onError - function to call on error + * @returns {Promise} + * + * @example + * writeAndValidate(source, destinations, verify, onProgress, onFail, onFinish, onError) + */ +const writeAndValidate = (source, destinations, verify, onProgress, onFail, onFinish, onError) => { return source.getInnerSource() - .then((innerSource) => { - return sdk.multiWrite.pipeSourceToDestinations( - innerSource, - destinations, - onFail, - onProgress, - verify, - ) - }) - .then(({ failures, bytesWritten }) => { + .then((innerSource) => { + return sdk.multiWrite.pipeSourceToDestinations( + innerSource, + destinations, + onFail, + onProgress, + verify + ) + }) + .then(({ failures, bytesWritten }) => { // If all destinations errored, treat the last fail as an error - if (failures.size === destinations.length) { - throw lastMapValue(failures) - } - const result = { - bytesWritten, - devices: { - failed: failures.size, - successful: destinations.length - failures.size, - }, - errors: [] - } - for (const [ destination, error ] of failures) { - error.device = destination.drive.device - result.errors.push(error) - } - onFinish(result) - }) - .catch(onError) + if (failures.size === destinations.length) { + throw lastMapValue(failures) + } + const result = { + bytesWritten, + devices: { + failed: failures.size, + successful: destinations.length - failures.size + }, + errors: [] + } + for (const [ destination, error ] of failures) { + error.device = destination.drive.device + result.errors.push(error) + } + onFinish(result) + }) + .catch(onError) } ipc.connectTo(IPC_SERVER_ID, () => { @@ -152,8 +174,6 @@ ipc.connectTo(IPC_SERVER_ID, () => { terminate(EXIT_CODES.SUCCESS) }) - let writer = null - ipc.of[IPC_SERVER_ID].on('write', (options) => { /** * @summary Progress handler @@ -209,19 +229,21 @@ ipc.connectTo(IPC_SERVER_ID, () => { /** * @summary Failure handler (non-fatal errors) - * @param {Object} error - MultiDestinationError + * @param {SourceDestination} destination - destination + * @param {Error} error - error * @example * writer.on('fail', onFail) */ const onFail = (destination, error) => { ipc.of[IPC_SERVER_ID].emit('fail', { - device: destination.drive, // TODO: device should be destination + // TODO: device should be destination + device: destination.drive, error: errors.toJSON(error) }) } const destinations = _.map(options.destinations, 'device') - const dests = options.destinations.map((destination) => { + const dests = _.map(options.destinations, (destination) => { return new sdk.sourceDestination.BlockDevice(destination, options.unmountOnSuccess) }) const source = new sdk.sourceDestination.File(options.imagePath, sdk.sourceDestination.File.OpenFlags.Read) diff --git a/lib/shared/supported-formats.js b/lib/shared/supported-formats.js index d526643a..280b0b3b 100644 --- a/lib/shared/supported-formats.js +++ b/lib/shared/supported-formats.js @@ -18,6 +18,7 @@ const sdk = require('etcher-sdk') const _ = require('lodash') +// eslint-disable-next-line node/no-extraneous-require const mime = require('mime') const path = require('path') @@ -40,7 +41,7 @@ exports.getCompressedExtensions = () => { for (const [ mimetype, cls ] of sdk.sourceDestination.SourceDestination.mimetypes.entries()) { if (cls.prototype instanceof sdk.sourceDestination.CompressedSource) { const mtype = mime.getExtension(mimetype) - if (mtype != null) { + if (mtype !== null) { result.push(mtype) } }