diff --git a/lib/gui/app/app.js b/lib/gui/app/app.js index d57407e9..48dc93f7 100644 --- a/lib/gui/app/app.js +++ b/lib/gui/app/app.js @@ -33,7 +33,6 @@ const _ = require('lodash') const semver = require('semver') const uuidV4 = require('uuid/v4') ->>>>>>> Show raspberry pi usbboot update progress in devices list const EXIT_CODES = require('../../shared/exit-codes') const messages = require('../../shared/messages') const s3Packages = require('../../shared/s3-packages') diff --git a/lib/gui/modules/child-writer.js b/lib/gui/modules/child-writer.js index 035dcdb8..4db75dec 100644 --- a/lib/gui/modules/child-writer.js +++ b/lib/gui/modules/child-writer.js @@ -86,21 +86,20 @@ const handleError = (error) => { terminate(EXIT_CODES.GENERAL_ERROR) } -function runVerifier(verifier, onFail) { - return new Promise((resolve, reject) => { - verifier.on('error', onFail); - verifier.on('finish', resolve); - verifier.run(); - }); +function lastMapValue(map) { + let value + for (value of map.values()){ + } + return value } -function pipeRegularSourceToDestination(source, destination, verify, onProgress, onFail) { +function writeAndValidate(source, destination, verify, onProgress, onFail, onFinish, onError) { let checksum let sparse let sourceMetadata let step = 'flashing' let lastPosition = 0 - const errors = new Map() // destination -> error map + const errors = new Map() // destination -> error map TODO: include open and close errors in it const state = { active: destination.destinations.size, flashing: destination.destinations.size, @@ -109,6 +108,9 @@ function pipeRegularSourceToDestination(source, destination, verify, onProgress, successful: 0, type: step } + function allDestinationsFailed() { + return (errors.size === destination.destinations.size) + } function updateState() { state.type = step state.failed = errors.size @@ -140,7 +142,17 @@ function pipeRegularSourceToDestination(source, destination, verify, onProgress, Object.assign(progressEvent, state) onProgress(progressEvent) } - return source.canCreateSparseReadStream() + function onFail2(error) { + errors.set(error.destination, error.error) + updateState() + onFail(error) + } + destination.on('fail', onFail2) + return Promise.all([ source.getInnerSource(), destination.open() ]) + .then(([ _source ]) => { + source = _source + return source.canCreateSparseReadStream() + }) .then((_sparse) => { sparse = _sparse let sourceStream @@ -156,18 +168,15 @@ function pipeRegularSourceToDestination(source, destination, verify, onProgress, return Promise.all([ sourceStream, destinationStream, source.getMetadata() ]) }) .then(([ sourceStream, destinationStream, metadata ]) => { - destinationStream.on('fail', (error) => { - errors.set(error.destination, error.error) - updateState() - onFail({ device: error.destination.drive, error: error.error }) // TODO: device should be error.destination - }) + destinationStream.on('fail', onFail2) sourceMetadata = metadata return new Promise((resolve, reject) => { let done = false + let hasher sourceStream.on('error', reject) destinationStream.on('progress', onProgress2) if (verify && !sparse) { - const hasher = sdk.sourceDestination.createHasher() + hasher = sdk.sourceDestination.createHasher() hasher.on('checksum', (cs) => { checksum = cs if (done) { @@ -178,6 +187,13 @@ function pipeRegularSourceToDestination(source, destination, verify, onProgress, } destinationStream.on('done', () => { done = true; + if (allDestinationsFailed() && (hasher !== undefined)) { + sourceStream.unpipe(hasher) + verify = false + resolve() + return + } + if (sparse || !verify || (checksum !== undefined)) { resolve() } @@ -196,15 +212,24 @@ function pipeRegularSourceToDestination(source, destination, verify, onProgress, updateState() const verifier = destination.createVerifier(sparse ? sourceMetadata.blockMap : checksum, sourceMetadata.size) // TODO: ensure blockMap exists verifier.on('progress', onProgress2) - return runVerifier(verifier, onFail) + verifier.on('fail', onFail2) + return new Promise((resolve) => { + verifier.on('finish', resolve); + verifier.run(); + }); } }) .then(() => { step = 'finished' updateState() - //onProgress2({ speed: 0, position: sourceMetadata.size }) + return Promise.all([ source.close(), destination.close() ]) }) .then(() => { + // If all destinations errored, treat the last fail as an error + if (allDestinationsFailed()) { + const lastError = lastMapValue(errors) + throw lastError + } const result = { bytesWritten: lastPosition, devices: { @@ -220,16 +245,9 @@ function pipeRegularSourceToDestination(source, destination, verify, onProgress, error.device = destination.drive.device result.errors.push(error) } - return result - }) -} - -function sourceDestinationDisposer(sourceDestination) { - return Bluebird.resolve(sourceDestination.open()) - .return(sourceDestination) - .disposer(() => { - return Bluebird.resolve(sourceDestination.close()).catchReturn() + onFinish(result) }) + .catch(onError) } ipc.connectTo(IPC_SERVER_ID, () => { @@ -315,39 +333,32 @@ ipc.connectTo(IPC_SERVER_ID, () => { /** * @summary Failure handler (non-fatal errors) - * @param {Object} event - event data (error & device) + * @param {Object} error - MultiDestinationError * @example * writer.on('fail', onFail) */ - const onFail = (event) => { + const onFail = (error) => { ipc.of[IPC_SERVER_ID].emit('fail', { - device: event.device, - error: errors.toJSON(event.error) + device: error.destination.drive, // TODO: device should be error.destination + error: errors.toJSON(error.error) }) } - const destinations = _.map(options.destinations, 'drive.device') + const destinations = _.map(options.destinations, 'device') const dests = options.destinations.map((destination) => { return new sdk.sourceDestination.BlockDevice(destination, options.unmountOnSuccess) }) + const destination = new sdk.sourceDestination.MultiDestination(dests) const source = new sdk.sourceDestination.File(options.imagePath, sdk.sourceDestination.File.OpenFlags.Read) - source.getInnerSource() - .then((innerSource) => { - return Bluebird.using( - sourceDestinationDisposer(innerSource), - sourceDestinationDisposer(new sdk.sourceDestination.MultiDestination(dests)), - (innerSource, destination) => { - destination.on('fail', onFail) - return pipeRegularSourceToDestination(innerSource, destination, options.validateWriteOnSuccess, onProgress, onFail) - } - ) - }) - .then((results) => { - onFinish(results) - }) - .catch((error) => { - onError(error) - }) + writeAndValidate( + source, + destination, + options.validateWriteOnSuccess, + onProgress, + onFail, + onFinish, + onError + ) log(`Image: ${options.imagePath}`) log(`Devices: ${destinations.join(', ')}`) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f12098a6..0ca43503 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3068,7 +3068,7 @@ }, "etcher-sdk": { "version": "0.0.1", - "resolved": "git://github.com/resin-io-modules/etcher-sdk.git#bda51535715edb3691b783973801434bb3d78b30", + "resolved": "git://github.com/resin-io-modules/etcher-sdk.git#9d483eec059a9e149d0f1c2e2746b8a816f87bb4", "dependencies": { "@types/lodash": { "version": "4.14.110", @@ -3126,6 +3126,10 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz" + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" diff --git a/package.json b/package.json index c35eed1d..487262f6 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "debug": "3.1.0", "drivelist": "6.4.6", "electron-is-running-in-asar": "1.0.0", - "etcher-sdk": "github:resin-io-modules/etcher-sdk#bda51535715edb3691b783973801434bb3d78b30", + "etcher-sdk": "github:resin-io-modules/etcher-sdk#9d483eec059a9e149d0f1c2e2746b8a816f87bb4", "file-type": "4.1.0", "flexboxgrid": "6.3.0", "gpt": "1.0.0",