diff --git a/lib/cli/etcher.js b/lib/cli/etcher.js index edd0a2d2..15e28f88 100644 --- a/lib/cli/etcher.js +++ b/lib/cli/etcher.js @@ -65,7 +65,7 @@ form.run([ }); if (!selectedDrive) { - throw new Error(`Drive not found: ${selectedDrive}`); + throw new Error(`Drive not found: ${answers.drive}`); } return writer.writeImage(options._[0], selectedDrive, { @@ -120,7 +120,8 @@ form.run([ log.toStderr(JSON.stringify({ command: 'error', data: { - message: error.message + message: error.message, + code: error.code } })); } else { diff --git a/lib/cli/writer.js b/lib/cli/writer.js index 8bff86e1..74a44d82 100644 --- a/lib/cli/writer.js +++ b/lib/cli/writer.js @@ -58,9 +58,8 @@ exports.writeImage = (imagePath, drive, options, onProgress) => { return umount.umountAsync(drive.device).then(() => { return imageStream.getFromFilePath(imagePath); }).then((image) => { - return imageWrite.write(drive.device, image.stream, { + return imageWrite.write(drive, image, { check: options.validateWriteOnSuccess, - size: image.size, transform: image.transform }); }).then((writer) => { diff --git a/lib/gui/app.js b/lib/gui/app.js index c598b0ba..0e1bdd13 100644 --- a/lib/gui/app.js +++ b/lib/gui/app.js @@ -156,6 +156,14 @@ app.controller('AppController', function( this.tooltipModal = TooltipModalService; this.handleError = (error) => { + + // This particular error is handled by the alert ribbon + // on the main application page. + if (error.code === 'ENOSPC') { + AnalyticsService.logEvent('Drive ran out of space'); + return; + } + OSDialogService.showError(error); // Also throw it so it gets displayed in DevTools diff --git a/lib/gui/models/store.js b/lib/gui/models/store.js index 29bb3215..fd1923f1 100644 --- a/lib/gui/models/store.js +++ b/lib/gui/models/store.js @@ -206,6 +206,10 @@ const storeReducer = (state, action) => { throw new Error(`Invalid results sourceChecksum: ${action.data.sourceChecksum}`); } + if (action.data.errorCode && !_.isString(action.data.errorCode)) { + throw new Error(`Invalid results errorCode: ${action.data.errorCode}`); + } + return state .set('isFlashing', false) .set('flashResults', Immutable.fromJS(action.data)) diff --git a/lib/gui/modules/image-writer.js b/lib/gui/modules/image-writer.js index 3cb32816..c356a492 100644 --- a/lib/gui/modules/image-writer.js +++ b/lib/gui/modules/image-writer.js @@ -252,7 +252,8 @@ imageWriter.service('ImageWriterService', function($q, $timeout, SettingsModel) }).catch((error) => { this.unsetFlashingFlag({ cancelled: false, - passedValidation: false + passedValidation: false, + errorCode: error.code }); return $q.reject(error); diff --git a/lib/gui/partials/main.html b/lib/gui/partials/main.html index bcca9434..19c02a33 100644 --- a/lib/gui/partials/main.html +++ b/lib/gui/partials/main.html @@ -98,10 +98,13 @@
- + + Not enough space on the drive.
Please insert larger one and +
+ Your removable drive did not pass validation check.
Please insert another one and
- + Oops, seems something went wrong. Click to retry
diff --git a/lib/src/child-writer/index.js b/lib/src/child-writer/index.js index 06443ec9..b4ccff5c 100644 --- a/lib/src/child-writer/index.js +++ b/lib/src/child-writer/index.js @@ -86,6 +86,16 @@ exports.write = (image, drive, options) => { }); child.on('message', (message) => { + + // The error object is decomposed by the CLI for serialisation + // purposes. We compose it back to an `Error` here in order + // to provide better encapsulation. + if (message.command === 'error') { + const error = new Error(message.data.message); + error.code = message.data.code; + return emitter.emit('error', error); + } + emitter.emit(message.command, message.data); }); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index bfcf3a72..f6ef6420 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -307,9 +307,9 @@ "resolved": "https://registry.npmjs.org/astw/-/astw-2.0.0.tgz" }, "async": { - "version": "2.0.0-rc.6", + "version": "2.0.0", "from": "async@>=2.0.0-rc.2 <3.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.0.0-rc.6.tgz" + "resolved": "https://registry.npmjs.org/async/-/async-2.0.0.tgz" }, "async-foreach": { "version": "0.1.3", @@ -1278,9 +1278,31 @@ "resolved": "https://registry.npmjs.org/etcher-image-stream/-/etcher-image-stream-2.5.2.tgz" }, "etcher-image-write": { - "version": "5.0.3", - "from": "etcher-image-write@>=5.0.3 <6.0.0", - "resolved": "https://registry.npmjs.org/etcher-image-write/-/etcher-image-write-5.0.3.tgz" + "version": "6.0.0", + "from": "etcher-image-write@6.0.0", + "resolved": "https://registry.npmjs.org/etcher-image-write/-/etcher-image-write-6.0.0.tgz", + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "http://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + }, + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@~2.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" + }, + "through2": { + "version": "2.0.1", + "from": "through2@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz" + }, + "xtend": { + "version": "4.0.1", + "from": "xtend@~4.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + } }, "etcher-latest-version": { "version": "1.0.0", @@ -1615,6 +1637,11 @@ "from": "glob2base@>=0.0.12 <0.0.13", "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz" }, + "globals": { + "version": "9.9.0", + "from": "globals@>=9.2.0 <10.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.9.0.tgz" + }, "globby": { "version": "4.1.0", "from": "globby@>=4.0.0 <5.0.0", @@ -4364,6 +4391,11 @@ "from": "shell-quote@>=1.4.3 <2.0.0", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz" }, + "shelljs": { + "version": "0.6.0", + "from": "shelljs@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.0.tgz" + }, "sigmund": { "version": "1.0.1", "from": "sigmund@>=1.0.0 <1.1.0", diff --git a/package.json b/package.json index 53f9e78b..d2505922 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "drivelist": "^3.2.2", "electron-is-running-in-asar": "^1.0.0", "etcher-image-stream": "^2.5.2", - "etcher-image-write": "^5.0.3", + "etcher-image-write": "^6.0.0", "etcher-latest-version": "^1.0.0", "file-tail": "^0.3.0", "flexboxgrid": "^6.3.0", diff --git a/tests/gui/modules/image-writer.spec.js b/tests/gui/modules/image-writer.spec.js index 5c67a02e..3430b539 100644 --- a/tests/gui/modules/image-writer.spec.js +++ b/tests/gui/modules/image-writer.spec.js @@ -241,6 +241,17 @@ describe('Browser: ImageWriter', function() { }).to.throw('Missing results'); }); + it('should throw if errorCode is defined but it is not a number', function() { + m.chai.expect(function() { + ImageWriterService.unsetFlashingFlag({ + passedValidation: true, + cancelled: false, + sourceChecksum: '1234', + errorCode: 123 + }); + }).to.throw('Invalid results errorCode: 123'); + }); + it('should throw if no passedValidation', function() { m.chai.expect(function() { ImageWriterService.unsetFlashingFlag({ @@ -445,7 +456,9 @@ describe('Browser: ImageWriter', function() { beforeEach(function() { this.performWriteStub = m.sinon.stub(ImageWriterService, 'performWrite'); - this.performWriteStub.returns($q.reject(new Error('write error'))); + this.error = new Error('write error'); + this.error.code = 'FOO'; + this.performWriteStub.returns($q.reject(this.error)); }); afterEach(function() { @@ -458,6 +471,13 @@ describe('Browser: ImageWriter', function() { m.chai.expect(ImageWriterService.isFlashing()).to.be.false; }); + it('should set the error code in the flash results', function() { + ImageWriterService.flash('foo.img', '/dev/disk2'); + $rootScope.$apply(); + const flashResults = ImageWriterService.getFlashResults(); + m.chai.expect(flashResults.errorCode).to.equal('FOO'); + }); + it('should be rejected with the error', function() { ImageWriterService.unsetFlashingFlag({ passedValidation: true,