This commit is contained in:
Alexis Svinartchouk 2018-07-05 19:48:44 +01:00
parent e68dbcf4ee
commit caf5f10326
9 changed files with 164 additions and 119 deletions

View File

@ -74,8 +74,9 @@ permissions.isElevated().then((elevated) => {
} }
const progressBars = new Map() const progressBars = new Map()
let lastStateType let lastStateType = null
// eslint-disable-next-line require-jsdoc
const onProgress = (state) => { const onProgress = (state) => {
state.message = state.active > 1 state.message = state.active > 1
? `${bytes(state.totalSpeed)}/s total, ${bytes(state.speed)}/s x ${state.active}` ? `${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}` state.message = `${state.type === 'flashing' ? 'Flashing' : 'Validating'}: ${state.message}`
// eslint-disable-next-line no-undefined
if (state.percentage === undefined) { if (state.percentage === undefined) {
state.message += ` - ${bytes(state.bytes)} written` state.message += ` - ${bytes(state.bytes)} written`
} }
// Update progress bar // Update progress bar
let progressBar = progressBars.get(state.type) let progressBar = progressBars.get(state.type)
// eslint-disable-next-line no-undefined
if (progressBar === undefined) { if (progressBar === undefined) {
// Stop the spinner if there is one // Stop the spinner if there is one
if ((lastStateType !== undefined) && (lastStateType !== state.type)) { if ((lastStateType !== null) && (lastStateType !== state.type)) {
const spinner = progressBars.get(lastStateType) const spinner = progressBars.get(lastStateType)
// eslint-disable-next-line no-undefined
if ((spinner !== undefined) && (spinner instanceof visuals.Spinner)) { if ((spinner !== undefined) && (spinner instanceof visuals.Spinner)) {
console.log() console.log()
spinner.stop() spinner.stop()
} }
} }
// eslint-disable-next-line no-undefined
if (state.percentage === undefined) { if (state.percentage === undefined) {
progressBar = new visuals.Spinner(state.message) progressBar = new visuals.Spinner(state.message)
progressBar.start() progressBar.start()
@ -106,12 +111,10 @@ permissions.isElevated().then((elevated) => {
progressBar.update(state) progressBar.update(state)
} }
progressBars.set(state.type, progressBar) progressBars.set(state.type, progressBar)
} else if (progressBar instanceof visuals.Spinner) {
progressBar.spinner.setSpinnerTitle(state.message)
} else { } else {
if (progressBar instanceof visuals.Spinner) { progressBar.update(state)
progressBar.spinner.setSpinnerTitle(state.message)
} else {
progressBar.update(state)
}
} }
lastStateType = state.type lastStateType = state.type
} }
@ -125,28 +128,28 @@ permissions.isElevated().then((elevated) => {
scanner.on('error', reject) scanner.on('error', reject)
scanner.start() scanner.start()
}) })
.then(() => { .then(() => {
return (new sdk.sourceDestination.File(imagePath, sdk.sourceDestination.File.OpenFlags.Read)).getInnerSource() return (new sdk.sourceDestination.File(imagePath, sdk.sourceDestination.File.OpenFlags.Read)).getInnerSource()
}) })
.then((innerSource) => { .then((innerSource) => {
// NOTE: Drive can be (String|Array) // NOTE: Drive can be (String|Array)
const destinations = [].concat(answers.drive).map((device) => { const destinations = _.map([].concat(answers.drive), (device) => {
const drive = scanner.getBy('device', device) const drive = scanner.getBy('device', device)
if (drive === undefined) { if (!drive) {
throw new Error(`No such drive ${device}`) throw new Error(`No such drive ${device}`)
} }
return drive 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 }) => { }).then(({ failures, bytesWritten }) => {
let exitCode = EXIT_CODES.SUCCESS let exitCode = EXIT_CODES.SUCCESS

View File

@ -232,13 +232,13 @@ app.run(() => {
}) })
}) })
app.run(($timeout) => { app.run(($timeout) => {
const BLACKLISTED_DRIVES = settings.has('driveBlacklist') const BLACKLISTED_DRIVES = settings.has('driveBlacklist')
? settings.get('driveBlacklist').split(',') ? settings.get('driveBlacklist').split(',')
: [] : []
function driveIsAllowed(drive) { // eslint-disable-next-line require-jsdoc
const driveIsAllowed = (drive) => {
return !( return !(
BLACKLISTED_DRIVES.includes(drive.devicePath) || BLACKLISTED_DRIVES.includes(drive.devicePath) ||
BLACKLISTED_DRIVES.includes(drive.device) || 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) { if (drive instanceof sdk.sourceDestination.BlockDevice) {
return drive.drive return drive.drive
} else if (drive instanceof sdk.sourceDestination.UsbbootDrive) { } else if (drive instanceof sdk.sourceDestination.UsbbootDrive) {
@ -261,9 +262,10 @@ app.run(($timeout) => {
} }
} }
function setDrives(drives) { // eslint-disable-next-line require-jsdoc
drives = _.values(drives) const setDrives = (drives) => {
availableDrives.setDrives(drives) availableDrives.setDrives(_.values(drives))
// Safely trigger a digest cycle. // Safely trigger a digest cycle.
// In some cases, AngularJS doesn't acknowledge that the // In some cases, AngularJS doesn't acknowledge that the
// available drives list has changed, and incorrectly // available drives list has changed, and incorrectly
@ -271,32 +273,37 @@ app.run(($timeout) => {
$timeout() $timeout()
} }
function getDrives() { // eslint-disable-next-line require-jsdoc
const getDrives = () => {
return _.keyBy(availableDrives.getDrives() || [], 'device') return _.keyBy(availableDrives.getDrives() || [], 'device')
} }
function addDrive(drive) { // eslint-disable-next-line require-jsdoc
drive = prepareDrive(drive) const addDrive = (drive) => {
if (!driveIsAllowed(drive)) { const preparedDrive = prepareDrive(drive)
if (!driveIsAllowed(preparedDrive)) {
return return
} }
const drives = getDrives() const drives = getDrives()
drives[drive.device] = drive drives[preparedDrive.device] = preparedDrive
setDrives(drives) setDrives(drives)
} }
function removeDrive(drive) { // eslint-disable-next-line require-jsdoc
drive = prepareDrive(drive) const removeDrive = (drive) => {
const preparedDrive = prepareDrive(drive)
const drives = getDrives() const drives = getDrives()
delete drives[drive.device] // eslint-disable-next-line prefer-reflect
delete drives[preparedDrive.device]
setDrives(drives) setDrives(drives)
} }
function updateDriveProgress(drive, progress) { // eslint-disable-next-line require-jsdoc
const updateDriveProgress = (drive, progress) => {
const drives = getDrives() const drives = getDrives()
const drive_ = drives[drive.device] const driveInMap = drives[drive.device]
if (drive !== undefined) { if (driveInMap) {
drive.progress = progress driveInMap.progress = progress
setDrives(drives) setDrives(drives)
} }
} }

View File

@ -24,7 +24,6 @@ const constraints = require('../../../shared/drive-constraints')
const supportedFormats = require('../../../shared/supported-formats') const supportedFormats = require('../../../shared/supported-formats')
const errors = require('../../../shared/errors') const errors = require('../../../shared/errors')
const fileExtensions = require('../../../shared/file-extensions') const fileExtensions = require('../../../shared/file-extensions')
const utils = require('../../../shared/utils')
const settings = require('./settings') const settings = require('./settings')
/** /**
@ -46,7 +45,7 @@ const verifyNoNilFields = (object, fields, name) => {
return _.isNil(_.get(object, field)) return _.isNil(_.get(object, field))
}) })
if (nilFields.length) { 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 const drives = action.data
//if (!_.isArray(drives) || !_.every(drives, _.isPlainObject)) { // If (!_.isArray(drives) || !_.every(drives, _.isPlainObject)) {
if (!_.isArray(drives)) { if (!_.isArray(drives)) {
throw errors.createError({ throw errors.createError({
title: `Invalid drives: ${drives}` title: `Invalid drives: ${drives}`
@ -407,6 +406,7 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
const MINIMUM_IMAGE_SIZE = 0 const MINIMUM_IMAGE_SIZE = 0
// eslint-disable-next-line no-undefined
if ((action.data.size !== undefined) && (action.data.size < MINIMUM_IMAGE_SIZE)) { if ((action.data.size !== undefined) && (action.data.size < MINIMUM_IMAGE_SIZE)) {
throw errors.createError({ throw errors.createError({
title: `Invalid image size: ${action.data.size}` title: `Invalid image size: ${action.data.size}`

View File

@ -21,7 +21,16 @@ const process = require('process')
const settings = require('../models/settings') 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') 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 // Can't use permissions.isElevated() here as it returns a promise and we need to set
// module.exports = scanner right now. // module.exports = scanner right now.
// eslint-disable-next-line no-magic-numbers
if ((process.platform !== 'linux') || (process.geteuid() === 0)) { if ((process.platform !== 'linux') || (process.geteuid() === 0)) {
adapters.push(new sdk.scanner.adapters.UsbbootDeviceAdapter()) adapters.push(new sdk.scanner.adapters.UsbbootDeviceAdapter())
} }

View File

@ -59,11 +59,11 @@ exports.fromFlashState = (state) => {
return 'Finishing...' return 'Finishing...'
} else if (isFlashing) { } else if (isFlashing) {
// eslint-disable-next-line no-eq-null
if (state.percentage != null) { if (state.percentage != null) {
return `${state.percentage}% Flashing` return `${state.percentage}% Flashing`
} else {
return `${units.bytesToClosestUnit(state.position)} flashed`
} }
return `${units.bytesToClosestUnit(state.position)} flashed`
} else if (isValidating) { } else if (isValidating) {
return `${state.percentage}% Validating` return `${state.percentage}% Validating`
} else if (!isFlashing && !isValidating) { } else if (!isFlashing && !isValidating) {

View File

@ -90,7 +90,8 @@ exports.currentWindow = electron.remote.getCurrentWindow()
* }) * })
*/ */
exports.set = (state) => { 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.setProgressBar(utils.percentageToFloat(state.percentage))
} }
exports.currentWindow.setTitle(getWindowTitle(state)) exports.currentWindow.setTitle(getWindowTitle(state))

View File

@ -159,35 +159,36 @@ module.exports = function (
const source = new sdk.sourceDestination.File(imagePath, sdk.sourceDestination.File.OpenFlags.Read) const source = new sdk.sourceDestination.File(imagePath, sdk.sourceDestination.File.OpenFlags.Read)
source.getInnerSource() source.getInnerSource()
.then((innerSource) => { .then((innerSource) => {
return innerSource.getMetadata() return innerSource.getMetadata()
.then((metadata) => { .then((metadata) => {
return innerSource.getPartitionTable() return innerSource.getPartitionTable()
.then((partitionTable) => { .then((partitionTable) => {
if (partitionTable !== undefined) { if (partitionTable) {
metadata.hasMBR = true metadata.hasMBR = true
metadata.partitions = partitionTable.partitions metadata.partitions = partitionTable.partitions
} }
$timeout(() => { $timeout(() => {
metadata.path = imagePath metadata.path = imagePath
metadata.extension = path.extname(imagePath).slice(1) // eslint-disable-next-line no-magic-numbers
this.selectImage(metadata) 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(() => {})
})
})
} }
/** /**

View File

@ -16,7 +16,6 @@
'use strict' 'use strict'
const Bluebird = require('bluebird')
const _ = require('lodash') const _ = require('lodash')
const ipc = require('node-ipc') const ipc = require('node-ipc')
const sdk = require('etcher-sdk') const sdk = require('etcher-sdk')
@ -86,44 +85,67 @@ const handleError = (error) => {
terminate(EXIT_CODES.GENERAL_ERROR) terminate(EXIT_CODES.GENERAL_ERROR)
} }
function lastMapValue(map) { /**
let value * @summary returns the last value in a Map
for (value of map.values()){ * @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 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<void>}
*
* @example
* writeAndValidate(source, destinations, verify, onProgress, onFail, onFinish, onError)
*/
const writeAndValidate = (source, destinations, verify, onProgress, onFail, onFinish, onError) => {
return source.getInnerSource() return source.getInnerSource()
.then((innerSource) => { .then((innerSource) => {
return sdk.multiWrite.pipeSourceToDestinations( return sdk.multiWrite.pipeSourceToDestinations(
innerSource, innerSource,
destinations, destinations,
onFail, onFail,
onProgress, onProgress,
verify, verify
) )
}) })
.then(({ failures, bytesWritten }) => { .then(({ failures, bytesWritten }) => {
// If all destinations errored, treat the last fail as an error // If all destinations errored, treat the last fail as an error
if (failures.size === destinations.length) { if (failures.size === destinations.length) {
throw lastMapValue(failures) throw lastMapValue(failures)
} }
const result = { const result = {
bytesWritten, bytesWritten,
devices: { devices: {
failed: failures.size, failed: failures.size,
successful: destinations.length - failures.size, successful: destinations.length - failures.size
}, },
errors: [] errors: []
} }
for (const [ destination, error ] of failures) { for (const [ destination, error ] of failures) {
error.device = destination.drive.device error.device = destination.drive.device
result.errors.push(error) result.errors.push(error)
} }
onFinish(result) onFinish(result)
}) })
.catch(onError) .catch(onError)
} }
ipc.connectTo(IPC_SERVER_ID, () => { ipc.connectTo(IPC_SERVER_ID, () => {
@ -152,8 +174,6 @@ ipc.connectTo(IPC_SERVER_ID, () => {
terminate(EXIT_CODES.SUCCESS) terminate(EXIT_CODES.SUCCESS)
}) })
let writer = null
ipc.of[IPC_SERVER_ID].on('write', (options) => { ipc.of[IPC_SERVER_ID].on('write', (options) => {
/** /**
* @summary Progress handler * @summary Progress handler
@ -209,19 +229,21 @@ ipc.connectTo(IPC_SERVER_ID, () => {
/** /**
* @summary Failure handler (non-fatal errors) * @summary Failure handler (non-fatal errors)
* @param {Object} error - MultiDestinationError * @param {SourceDestination} destination - destination
* @param {Error} error - error
* @example * @example
* writer.on('fail', onFail) * writer.on('fail', onFail)
*/ */
const onFail = (destination, error) => { const onFail = (destination, error) => {
ipc.of[IPC_SERVER_ID].emit('fail', { 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) error: errors.toJSON(error)
}) })
} }
const destinations = _.map(options.destinations, 'device') 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) return new sdk.sourceDestination.BlockDevice(destination, options.unmountOnSuccess)
}) })
const source = new sdk.sourceDestination.File(options.imagePath, sdk.sourceDestination.File.OpenFlags.Read) const source = new sdk.sourceDestination.File(options.imagePath, sdk.sourceDestination.File.OpenFlags.Read)

View File

@ -18,6 +18,7 @@
const sdk = require('etcher-sdk') const sdk = require('etcher-sdk')
const _ = require('lodash') const _ = require('lodash')
// eslint-disable-next-line node/no-extraneous-require
const mime = require('mime') const mime = require('mime')
const path = require('path') const path = require('path')
@ -40,7 +41,7 @@ exports.getCompressedExtensions = () => {
for (const [ mimetype, cls ] of sdk.sourceDestination.SourceDestination.mimetypes.entries()) { for (const [ mimetype, cls ] of sdk.sourceDestination.SourceDestination.mimetypes.entries()) {
if (cls.prototype instanceof sdk.sourceDestination.CompressedSource) { if (cls.prototype instanceof sdk.sourceDestination.CompressedSource) {
const mtype = mime.getExtension(mimetype) const mtype = mime.getExtension(mimetype)
if (mtype != null) { if (mtype !== null) {
result.push(mtype) result.push(mtype)
} }
} }