mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-23 11:16:39 +00:00
feat(GUI): remove angular from image-writer (#1680)
* feat(GUI): remove angular from image-writer We remove Angular from the `image-writer` module by using Redux store updates, subscribing to them while flashing. Changelog-Entry: Remove Angular dependency from image-writer. Change-Type: minor
This commit is contained in:
parent
52af3e8aa8
commit
13758c9568
@ -16,11 +16,7 @@
|
|||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/**
|
const Bluebird = require('bluebird')
|
||||||
* @module Etcher.Modules.ImageWriter
|
|
||||||
*/
|
|
||||||
|
|
||||||
const angular = require('angular')
|
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const childWriter = require('../../child-writer')
|
const childWriter = require('../../child-writer')
|
||||||
const settings = require('../models/settings')
|
const settings = require('../models/settings')
|
||||||
@ -29,10 +25,6 @@ const errors = require('../../shared/errors')
|
|||||||
const windowProgress = require('../os/window-progress')
|
const windowProgress = require('../os/window-progress')
|
||||||
const analytics = require('../modules/analytics')
|
const analytics = require('../modules/analytics')
|
||||||
|
|
||||||
const MODULE_NAME = 'Etcher.Modules.ImageWriter'
|
|
||||||
const imageWriter = angular.module(MODULE_NAME, [])
|
|
||||||
|
|
||||||
imageWriter.service('ImageWriterService', function ($q, $rootScope) {
|
|
||||||
/**
|
/**
|
||||||
* @summary Perform write operation
|
* @summary Perform write operation
|
||||||
* @function
|
* @function
|
||||||
@ -49,14 +41,14 @@ imageWriter.service('ImageWriterService', function ($q, $rootScope) {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ImageWriter.performWrite('path/to/image.img', {
|
* imageWriter.performWrite('path/to/image.img', {
|
||||||
* device: '/dev/disk2'
|
* device: '/dev/disk2'
|
||||||
* }, (state) => {
|
* }, (state) => {
|
||||||
* console.log(state.percentage);
|
* console.log(state.percentage)
|
||||||
* });
|
* })
|
||||||
*/
|
*/
|
||||||
this.performWrite = (image, drive, onProgress) => {
|
exports.performWrite = (image, drive, onProgress) => {
|
||||||
return $q((resolve, reject) => {
|
return new Bluebird((resolve, reject) => {
|
||||||
const child = childWriter.write(image, drive, {
|
const child = childWriter.write(image, drive, {
|
||||||
validateWriteOnSuccess: settings.get('validateWriteOnSuccess'),
|
validateWriteOnSuccess: settings.get('validateWriteOnSuccess'),
|
||||||
unmountOnSuccess: settings.get('unmountOnSuccess')
|
unmountOnSuccess: settings.get('unmountOnSuccess')
|
||||||
@ -73,22 +65,22 @@ imageWriter.service('ImageWriterService', function ($q, $rootScope) {
|
|||||||
* @public
|
* @public
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* This function will update `ImageWriterService.state` with the current writing state.
|
* This function will update `imageWriter.state` with the current writing state.
|
||||||
*
|
*
|
||||||
* @param {String} image - image path
|
* @param {String} image - image path
|
||||||
* @param {Object} drive - drive
|
* @param {Object} drive - drive
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ImageWriterService.flash('foo.img', {
|
* imageWriter.flash('foo.img', {
|
||||||
* device: '/dev/disk2'
|
* device: '/dev/disk2'
|
||||||
* }).then(() => {
|
* }).then(() => {
|
||||||
* console.log('Write completed!');
|
* console.log('Write completed!')
|
||||||
* });
|
* })
|
||||||
*/
|
*/
|
||||||
this.flash = (image, drive) => {
|
exports.flash = (image, drive) => {
|
||||||
if (flashState.isFlashing()) {
|
if (flashState.isFlashing()) {
|
||||||
return $q.reject(new Error('There is already a flash in progress'))
|
return Bluebird.reject(new Error('There is already a flash in progress'))
|
||||||
}
|
}
|
||||||
|
|
||||||
flashState.setFlashingFlag()
|
flashState.setFlashingFlag()
|
||||||
@ -103,14 +95,8 @@ imageWriter.service('ImageWriterService', function ($q, $rootScope) {
|
|||||||
|
|
||||||
analytics.logEvent('Flash', analyticsData)
|
analytics.logEvent('Flash', analyticsData)
|
||||||
|
|
||||||
return this.performWrite(image, drive, (state) => {
|
return exports.performWrite(image, drive, (state) => {
|
||||||
// Bring this value to the world of angular.
|
|
||||||
// If we don't trigger a digest loop,
|
|
||||||
// `.getFlashState()` will not return
|
|
||||||
// the latest updated progress state.
|
|
||||||
$rootScope.$apply(() => {
|
|
||||||
flashState.setProgressState(state)
|
flashState.setProgressState(state)
|
||||||
})
|
|
||||||
}).then(flashState.unsetFlashingFlag).then(() => {
|
}).then(flashState.unsetFlashingFlag).then(() => {
|
||||||
if (flashState.wasLastFlashCancelled()) {
|
if (flashState.wasLastFlashCancelled()) {
|
||||||
analytics.logEvent('Elevation cancelled', analyticsData)
|
analytics.logEvent('Elevation cancelled', analyticsData)
|
||||||
@ -136,11 +122,8 @@ imageWriter.service('ImageWriterService', function ($q, $rootScope) {
|
|||||||
}, analyticsData))
|
}, analyticsData))
|
||||||
}
|
}
|
||||||
|
|
||||||
return $q.reject(error)
|
return Bluebird.reject(error)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
windowProgress.clear()
|
windowProgress.clear()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = MODULE_NAME
|
|
||||||
|
@ -22,11 +22,13 @@ const driveScanner = require('../../../modules/drive-scanner')
|
|||||||
const progressStatus = require('../../../modules/progress-status')
|
const progressStatus = require('../../../modules/progress-status')
|
||||||
const notification = require('../../../os/notification')
|
const notification = require('../../../os/notification')
|
||||||
const exceptionReporter = require('../../../modules/exception-reporter')
|
const exceptionReporter = require('../../../modules/exception-reporter')
|
||||||
|
const imageWriter = require('../../../modules/image-writer')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const store = require('../../../../shared/store')
|
||||||
|
|
||||||
module.exports = function (
|
module.exports = function (
|
||||||
$state,
|
$state,
|
||||||
ImageWriterService,
|
$timeout,
|
||||||
FlashErrorModalService
|
FlashErrorModalService
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
@ -53,7 +55,7 @@ module.exports = function (
|
|||||||
* size: 99999,
|
* size: 99999,
|
||||||
* mountpoint: '/mnt/foo',
|
* mountpoint: '/mnt/foo',
|
||||||
* system: false
|
* system: false
|
||||||
* });
|
* })
|
||||||
*/
|
*/
|
||||||
this.flashImageToDrive = (image, drive) => {
|
this.flashImageToDrive = (image, drive) => {
|
||||||
if (flashState.isFlashing()) {
|
if (flashState.isFlashing()) {
|
||||||
@ -64,9 +66,13 @@ module.exports = function (
|
|||||||
// otherwise Windows throws EPERM
|
// otherwise Windows throws EPERM
|
||||||
driveScanner.stop()
|
driveScanner.stop()
|
||||||
|
|
||||||
|
// Trigger Angular digests along with store updates, as the flash state
|
||||||
|
// updates. Without this there is essentially no progress to watch.
|
||||||
|
const unsubscribe = store.subscribe($timeout)
|
||||||
|
|
||||||
const iconPath = '../../assets/icon.png'
|
const iconPath = '../../assets/icon.png'
|
||||||
|
|
||||||
ImageWriterService.flash(image.path, drive).then(() => {
|
imageWriter.flash(image.path, drive).then(() => {
|
||||||
if (!flashState.wasLastFlashCancelled()) {
|
if (!flashState.wasLastFlashCancelled()) {
|
||||||
notification.send('Success!', {
|
notification.send('Success!', {
|
||||||
body: messages.info.flashComplete({
|
body: messages.info.flashComplete({
|
||||||
@ -102,8 +108,10 @@ module.exports = function (
|
|||||||
FlashErrorModalService.show(messages.error.genericFlashError())
|
FlashErrorModalService.show(messages.error.genericFlashError())
|
||||||
exceptionReporter.report(error)
|
exceptionReporter.report(error)
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
})
|
||||||
|
.finally(() => {
|
||||||
driveScanner.start()
|
driveScanner.start()
|
||||||
|
unsubscribe()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +123,7 @@ module.exports = function (
|
|||||||
* @returns {String} progress button label
|
* @returns {String} progress button label
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const label = FlashController.getProgressButtonLabel();
|
* const label = FlashController.getProgressButtonLabel()
|
||||||
*/
|
*/
|
||||||
this.getProgressButtonLabel = () => {
|
this.getProgressButtonLabel = () => {
|
||||||
if (!flashState.isFlashing()) {
|
if (!flashState.isFlashing()) {
|
||||||
|
@ -42,8 +42,6 @@ const MainPage = angular.module(MODULE_NAME, [
|
|||||||
require('../../os/open-external/open-external'),
|
require('../../os/open-external/open-external'),
|
||||||
require('../../os/dropzone/dropzone'),
|
require('../../os/dropzone/dropzone'),
|
||||||
|
|
||||||
require('../../modules/image-writer'),
|
|
||||||
|
|
||||||
require('../../utils/byte-size/byte-size')
|
require('../../utils/byte-size/byte-size')
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -1,132 +1,99 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 resin.io
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const m = require('mochainon')
|
const m = require('mochainon')
|
||||||
const angular = require('angular')
|
const angular = require('angular')
|
||||||
|
const Bluebird = require('bluebird')
|
||||||
const flashState = require('../../../lib/shared/models/flash-state')
|
const flashState = require('../../../lib/shared/models/flash-state')
|
||||||
|
const imageWriter = require('../../../lib/gui/modules/image-writer')
|
||||||
require('angular-mocks')
|
require('angular-mocks')
|
||||||
|
|
||||||
describe('Browser: ImageWriter', function () {
|
describe('Browser: imageWriter', () => {
|
||||||
beforeEach(angular.mock.module(
|
describe('.flash()', () => {
|
||||||
require('../../../lib/gui/modules/image-writer')
|
describe('given a successful write', () => {
|
||||||
))
|
beforeEach(() => {
|
||||||
|
this.performWriteStub = m.sinon.stub(imageWriter, 'performWrite')
|
||||||
describe('ImageWriterService', function () {
|
this.performWriteStub.returns(Bluebird.resolve({
|
||||||
let $q
|
|
||||||
let $rootScope
|
|
||||||
let ImageWriterService
|
|
||||||
|
|
||||||
beforeEach(angular.mock.inject(function (_$q_, _$rootScope_, _ImageWriterService_) {
|
|
||||||
$q = _$q_
|
|
||||||
$rootScope = _$rootScope_
|
|
||||||
ImageWriterService = _ImageWriterService_
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('.flash()', function () {
|
|
||||||
describe('given a successful write', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
this.performWriteStub = m.sinon.stub(ImageWriterService, 'performWrite')
|
|
||||||
this.performWriteStub.returns($q.resolve({
|
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
sourceChecksum: '1234'
|
sourceChecksum: '1234'
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(() => {
|
||||||
this.performWriteStub.restore()
|
this.performWriteStub.restore()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should set flashing to false when done', function () {
|
it('should set flashing to false when done', () => {
|
||||||
flashState.unsetFlashingFlag({
|
flashState.unsetFlashingFlag({
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
sourceChecksum: '1234'
|
sourceChecksum: '1234'
|
||||||
})
|
})
|
||||||
|
|
||||||
ImageWriterService.flash('foo.img', '/dev/disk2')
|
imageWriter.flash('foo.img', '/dev/disk2').finally(() => {
|
||||||
$rootScope.$apply()
|
|
||||||
m.chai.expect(flashState.isFlashing()).to.be.false
|
m.chai.expect(flashState.isFlashing()).to.be.false
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should prevent writing more than once', function () {
|
it('should prevent writing more than once', () => {
|
||||||
flashState.unsetFlashingFlag({
|
flashState.unsetFlashingFlag({
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
sourceChecksum: '1234'
|
sourceChecksum: '1234'
|
||||||
})
|
})
|
||||||
|
|
||||||
ImageWriterService.flash('foo.img', '/dev/disk2')
|
const writing = imageWriter.flash('foo.img', '/dev/disk2')
|
||||||
ImageWriterService.flash('foo.img', '/dev/disk2').catch(angular.noop)
|
imageWriter.flash('foo.img', '/dev/disk2').catch(angular.noop)
|
||||||
$rootScope.$apply()
|
writing.finally(() => {
|
||||||
m.chai.expect(this.performWriteStub).to.have.been.calledOnce
|
m.chai.expect(this.performWriteStub).to.have.been.calledOnce
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should reject the second flash attempt', function () {
|
|
||||||
ImageWriterService.flash('foo.img', '/dev/disk2')
|
|
||||||
|
|
||||||
let rejectError = null
|
|
||||||
ImageWriterService.flash('foo.img', '/dev/disk2').catch(function (error) {
|
|
||||||
rejectError = error
|
|
||||||
})
|
})
|
||||||
|
|
||||||
$rootScope.$apply()
|
it('should reject the second flash attempt', () => {
|
||||||
|
imageWriter.flash('foo.img', '/dev/disk2')
|
||||||
|
|
||||||
|
let rejectError = null
|
||||||
|
imageWriter.flash('foo.img', '/dev/disk2').catch((error) => {
|
||||||
|
rejectError = error
|
||||||
|
}).finally(() => {
|
||||||
m.chai.expect(rejectError).to.be.an.instanceof(Error)
|
m.chai.expect(rejectError).to.be.an.instanceof(Error)
|
||||||
m.chai.expect(rejectError.message).to.equal('There is already a flash in progress')
|
m.chai.expect(rejectError.message).to.equal('There is already a flash in progress')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('given an unsuccessful write', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
this.performWriteStub = m.sinon.stub(ImageWriterService, 'performWrite')
|
|
||||||
this.error = new Error('write error')
|
|
||||||
this.error.code = 'FOO'
|
|
||||||
this.performWriteStub.returns($q.reject(this.error))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
describe('given an unsuccessful write', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
this.performWriteStub = m.sinon.stub(imageWriter, 'performWrite')
|
||||||
|
this.error = new Error('write error')
|
||||||
|
this.error.code = 'FOO'
|
||||||
|
this.performWriteStub.returns(Bluebird.reject(this.error))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
this.performWriteStub.restore()
|
this.performWriteStub.restore()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should set flashing to false when done', function () {
|
it('should set flashing to false when done', () => {
|
||||||
ImageWriterService.flash('foo.img', '/dev/disk2').catch(angular.noop)
|
imageWriter.flash('foo.img', '/dev/disk2').catch(angular.noop).finally(() => {
|
||||||
$rootScope.$apply()
|
|
||||||
m.chai.expect(flashState.isFlashing()).to.be.false
|
m.chai.expect(flashState.isFlashing()).to.be.false
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should set the error code in the flash results', function () {
|
it('should set the error code in the flash results', () => {
|
||||||
ImageWriterService.flash('foo.img', '/dev/disk2').catch(angular.noop)
|
imageWriter.flash('foo.img', '/dev/disk2').catch(angular.noop).finally(() => {
|
||||||
$rootScope.$apply()
|
|
||||||
const flashResults = flashState.getFlashResults()
|
const flashResults = flashState.getFlashResults()
|
||||||
m.chai.expect(flashResults.errorCode).to.equal('FOO')
|
m.chai.expect(flashResults.errorCode).to.equal('FOO')
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should be rejected with the error', function () {
|
it('should be rejected with the error', () => {
|
||||||
flashState.unsetFlashingFlag({
|
flashState.unsetFlashingFlag({
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
sourceChecksum: '1234'
|
sourceChecksum: '1234'
|
||||||
})
|
})
|
||||||
|
|
||||||
let rejection
|
let rejection
|
||||||
ImageWriterService.flash('foo.img', '/dev/disk2').catch(function (error) {
|
imageWriter.flash('foo.img', '/dev/disk2').catch((error) => {
|
||||||
rejection = error
|
rejection = error
|
||||||
})
|
}).finally(() => {
|
||||||
|
|
||||||
$rootScope.$apply()
|
|
||||||
|
|
||||||
m.chai.expect(rejection).to.be.an.instanceof(Error)
|
m.chai.expect(rejection).to.be.an.instanceof(Error)
|
||||||
m.chai.expect(rejection.message).to.equal('write error')
|
m.chai.expect(rejection.message).to.equal('write error')
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user