diff --git a/lib/gui/app.js b/lib/gui/app.js index ae881240..c8a4c697 100644 --- a/lib/gui/app.js +++ b/lib/gui/app.js @@ -41,6 +41,7 @@ const analytics = require('./modules/analytics'); const updateNotifier = require('./components/update-notifier'); const availableDrives = require('./models/available-drives'); const selectionState = require('./models/selection-state'); +const driveScanner = require('./modules/drive-scanner'); const Store = require('./models/store'); @@ -51,7 +52,6 @@ const app = angular.module('Etcher', [ // Etcher modules require('./modules/error'), - require('./modules/drive-scanner'), // Components require('./components/svg-icon'), @@ -176,8 +176,8 @@ app.run(() => { }); }); -app.run(($timeout, DriveScannerService, ErrorService) => { - DriveScannerService.on('drives', (drives) => { +app.run(($timeout, ErrorService) => { + driveScanner.on('drives', (drives) => { // Safely trigger a digest cycle. // In some cases, AngularJS doesn't acknowledge that the @@ -188,18 +188,18 @@ app.run(($timeout, DriveScannerService, ErrorService) => { }); }); - DriveScannerService.on('error', (error) => { + driveScanner.on('error', (error) => { // Stop the drive scanning loop in case of errors, // otherwise we risk presenting the same error over // and over again to the user, while also heavily // spamming our error reporting service. - DriveScannerService.stop(); + driveScanner.stop(); return ErrorService.reportException(error); }); - DriveScannerService.start(); + driveScanner.start(); }); app.run(($window, WarningModalService, ErrorService, OSDialogService) => { diff --git a/lib/gui/modules/drive-scanner.js b/lib/gui/modules/drive-scanner.js index 88398c71..ca215397 100644 --- a/lib/gui/modules/drive-scanner.js +++ b/lib/gui/modules/drive-scanner.js @@ -16,112 +16,100 @@ 'use strict'; -/** - * @module Etcher.Modules.DriveScanner - */ - const Rx = require('rx'); const os = require('os'); const _ = require('lodash'); -const angular = require('angular'); const EventEmitter = require('events').EventEmitter; const drivelist = require('drivelist'); const settings = require('../models/settings'); -const MODULE_NAME = 'Etcher.Modules.DriveScanner'; -const driveScanner = angular.module(MODULE_NAME, []); +const DRIVE_SCANNER_INTERVAL_MS = 2000; +const DRIVE_SCANNER_FIRST_SCAN_DELAY_MS = 0; +const emitter = new EventEmitter(); -driveScanner.factory('DriveScannerService', () => { - const DRIVE_SCANNER_INTERVAL_MS = 2000; - const DRIVE_SCANNER_FIRST_SCAN_DELAY_MS = 0; - const emitter = new EventEmitter(); +/* eslint-disable lodash/prefer-lodash-method */ - /* eslint-disable lodash/prefer-lodash-method */ +const availableDrives = Rx.Observable.timer( + DRIVE_SCANNER_FIRST_SCAN_DELAY_MS, + DRIVE_SCANNER_INTERVAL_MS +) - const availableDrives = Rx.Observable.timer( - DRIVE_SCANNER_FIRST_SCAN_DELAY_MS, - DRIVE_SCANNER_INTERVAL_MS - ) +/* eslint-enable lodash/prefer-lodash-method */ - /* eslint-enable lodash/prefer-lodash-method */ + .flatMap(() => { + return Rx.Observable.fromNodeCallback(drivelist.list)(); + }) - .flatMap(() => { - return Rx.Observable.fromNodeCallback(drivelist.list)(); - }) + // Build human friendly "description" + .map((drives) => { + return _.map(drives, (drive) => { + drive.name = drive.device; - // Build human friendly "description" - .map((drives) => { - return _.map(drives, (drive) => { - drive.name = drive.device; - - if (os.platform() === 'win32' && !_.isEmpty(drive.mountpoints)) { - drive.name = _.join(_.map(drive.mountpoints, 'path'), ', '); - } - - return drive; - }); - }) - - .map((drives) => { - if (settings.get('unsafeMode')) { - return drives; + if (os.platform() === 'win32' && !_.isEmpty(drive.mountpoints)) { + drive.name = _.join(_.map(drive.mountpoints, 'path'), ', '); } - return _.reject(drives, { - system: true - }); - }) - .pausable(new Rx.Subject()); + return drive; + }); + }) - /* - * This service emits the following events: - * - * - `drives (Object[])` - * - `error (Error)` - * - * For example: - * - * ``` - * DriveScannerService.on('drives', (drives) => { - * console.log(drives); - * }); - * - * DriveScannerService.on('error', (error) => { - * throw error; - * }); - * ``` - */ - availableDrives.subscribe((drives) => { - emitter.emit('drives', drives); - }, (error) => { - emitter.emit('error', error); - }); + .map((drives) => { + if (settings.get('unsafeMode')) { + return drives; + } - /** - * @summary Start scanning drives - * @function - * @public - * - * @example - * DriveScannerService.start(); - */ - emitter.start = () => { - availableDrives.resume(); - }; + return _.reject(drives, { + system: true + }); + }) + .pausable(new Rx.Subject()); - /** - * @summary Stop scanning drives - * @function - * @public - * - * @example - * DriveScannerService.stop(); - */ - emitter.stop = () => { - availableDrives.pause(); - }; - - return emitter; +/* + * This service emits the following events: + * + * - `drives (Object[])` + * - `error (Error)` + * + * For example: + * + * ``` + * driveScanner.on('drives', (drives) => { + * console.log(drives); + * }); + * + * driveScanner.on('error', (error) => { + * throw error; + * }); + * ``` + */ +availableDrives.subscribe((drives) => { + emitter.emit('drives', drives); +}, (error) => { + emitter.emit('error', error); }); -module.exports = MODULE_NAME; +/** + * @summary Start scanning drives + * @function + * @public + * + * @example + * driveScanner.start(); + */ +emitter.start = () => { + availableDrives.resume(); +}; + +/** + * @summary Stop scanning drives + * @function + * @public + * + * @example + * driveScanner.stop(); + */ +emitter.stop = () => { + availableDrives.pause(); +}; + +module.exports = emitter; diff --git a/lib/gui/pages/main/controllers/flash.js b/lib/gui/pages/main/controllers/flash.js index f10e4eb8..56da4eec 100644 --- a/lib/gui/pages/main/controllers/flash.js +++ b/lib/gui/pages/main/controllers/flash.js @@ -19,13 +19,13 @@ const messages = require('../../../../shared/messages'); const settings = require('../../../models/settings'); const flashState = require('../../../models/flash-state'); +const driveScanner = require('../../../modules/drive-scanner'); const utils = require('../../../../shared/utils'); const notification = require('../../../os/notification'); const path = require('path'); module.exports = function( $state, - DriveScannerService, ImageWriterService, FlashErrorModalService, ErrorService @@ -58,7 +58,7 @@ module.exports = function( // Stop scanning drives when flashing // otherwise Windows throws EPERM - DriveScannerService.stop(); + driveScanner.stop(); const iconPath = '../../assets/icon.png'; @@ -101,7 +101,7 @@ module.exports = function( }) .finally(() => { - DriveScannerService.start(); + driveScanner.start(); }); }; diff --git a/lib/gui/pages/main/main.js b/lib/gui/pages/main/main.js index fe75faea..cf583c39 100644 --- a/lib/gui/pages/main/main.js +++ b/lib/gui/pages/main/main.js @@ -43,7 +43,6 @@ const MainPage = angular.module(MODULE_NAME, [ require('../../os/open-external/open-external'), require('../../os/dropzone/dropzone'), - require('../../modules/drive-scanner'), require('../../modules/image-writer'), require('../../modules/error'), diff --git a/tests/gui/modules/drive-scanner.spec.js b/tests/gui/modules/drive-scanner.spec.js index 7a087d00..74b2bd4d 100644 --- a/tests/gui/modules/drive-scanner.spec.js +++ b/tests/gui/modules/drive-scanner.spec.js @@ -2,52 +2,83 @@ const m = require('mochainon'); const os = require('os'); -const angular = require('angular'); const drivelist = require('drivelist'); -require('angular-mocks'); +const driveScanner = require('../../../lib/gui/modules/drive-scanner'); -describe('Browser: DriveScanner', function() { +describe('Browser: driveScanner', function() { - beforeEach(angular.mock.module( - require('../../../lib/gui/modules/drive-scanner') - )); - - describe('DriveScannerService', function() { - - let DriveScannerService; - - beforeEach(angular.mock.inject(function(_DriveScannerService_) { - DriveScannerService = _DriveScannerService_; - })); - - describe('given no available drives', function() { - - beforeEach(function() { - this.drivesListStub = m.sinon.stub(drivelist, 'list'); - this.drivesListStub.yields(null, []); - }); - - afterEach(function() { - this.drivesListStub.restore(); - }); - - it('should emit an empty array', function(done) { - DriveScannerService.on('drives', function(drives) { - m.chai.expect(drives).to.deep.equal([]); - DriveScannerService.stop(); - done(); - }); - - DriveScannerService.start(); - }); + describe('given no available drives', function() { + beforeEach(function() { + this.drivelistStub = m.sinon.stub(drivelist, 'list'); + this.drivelistStub.yields(null, []); }); - describe('given only system available drives', function() { + afterEach(function() { + this.drivelistStub.restore(); + }); + + it('should emit an empty array', function(done) { + driveScanner.once('drives', function(drives) { + m.chai.expect(drives).to.deep.equal([]); + driveScanner.stop(); + done(); + }); + + driveScanner.start(); + }); + + }); + + describe('given only system available drives', function() { + + beforeEach(function() { + this.drivelistStub = m.sinon.stub(drivelist, 'list'); + this.drivelistStub.yields(null, [ { + device: '/dev/sda', + description: 'WDC WD10JPVX-75J', + size: '931.5G', + mountpoints: [ + { + path: '/' + } + ], + system: true + } ]); + }); + + afterEach(function() { + this.drivelistStub.restore(); + }); + + it('should emit an empty array', function(done) { + driveScanner.once('drives', function(drives) { + m.chai.expect(drives).to.deep.equal([]); + driveScanner.stop(); + done(); + }); + + driveScanner.start(); + }); + + }); + + describe('given linux', function() { + + beforeEach(function() { + this.osPlatformStub = m.sinon.stub(os, 'platform'); + this.osPlatformStub.returns('linux'); + }); + + afterEach(function() { + this.osPlatformStub.restore(); + }); + + describe('given available drives', function() { beforeEach(function() { - this.drivesListStub = m.sinon.stub(drivelist, 'list'); - this.drivesListStub.yields(null, [ + this.drivelistStub = m.sinon.stub(drivelist, 'list'); + this.drivelistStub.yields(null, [ { device: '/dev/sda', description: 'WDC WD10JPVX-75J', @@ -58,55 +89,42 @@ describe('Browser: DriveScanner', function() { } ], system: true + }, + { + device: '/dev/sdb', + description: 'Foo', + size: '14G', + mountpoints: [ + { + path: '/mnt/foo' + } + ], + system: false + }, + { + device: '/dev/sdc', + description: 'Bar', + size: '14G', + mountpoints: [ + { + path: '/mnt/bar' + } + ], + system: false } ]); }); afterEach(function() { - this.drivesListStub.restore(); + this.drivelistStub.restore(); }); - it('should emit an empty array', function(done) { - DriveScannerService.on('drives', function(drives) { - m.chai.expect(drives).to.deep.equal([]); - DriveScannerService.stop(); - done(); - }); - - DriveScannerService.start(); - }); - - }); - - describe('given linux', function() { - - beforeEach(function() { - this.osPlatformStub = m.sinon.stub(os, 'platform'); - this.osPlatformStub.returns('linux'); - }); - - afterEach(function() { - this.osPlatformStub.restore(); - }); - - describe('given available drives', function() { - - beforeEach(function() { - this.drivesListStub = m.sinon.stub(drivelist, 'list'); - this.drivesListStub.yields(null, [ - { - device: '/dev/sda', - description: 'WDC WD10JPVX-75J', - size: '931.5G', - mountpoints: [ - { - path: '/' - } - ], - system: true - }, + it('should emit the non removable drives', function(done) { + driveScanner.once('drives', function(drives) { + m.chai.expect(drives).to.deep.equal([ { device: '/dev/sdb', + name: '/dev/sdb', description: 'Foo', size: '14G', mountpoints: [ @@ -118,6 +136,7 @@ describe('Browser: DriveScanner', function() { }, { device: '/dev/sdc', + name: '/dev/sdc', description: 'Bar', size: '14G', mountpoints: [ @@ -128,81 +147,76 @@ describe('Browser: DriveScanner', function() { system: false } ]); + + driveScanner.stop(); + done(); }); - afterEach(function() { - this.drivesListStub.restore(); - }); - - it('should emit the non removable drives', function(done) { - DriveScannerService.on('drives', function(drives) { - m.chai.expect(drives).to.deep.equal([ - { - device: '/dev/sdb', - name: '/dev/sdb', - description: 'Foo', - size: '14G', - mountpoints: [ - { - path: '/mnt/foo' - } - ], - system: false - }, - { - device: '/dev/sdc', - name: '/dev/sdc', - description: 'Bar', - size: '14G', - mountpoints: [ - { - path: '/mnt/bar' - } - ], - system: false - } - ]); - - DriveScannerService.stop(); - done(); - }); - - DriveScannerService.start(); - }); - + driveScanner.start(); }); }); - describe('given windows', function() { + }); + + describe('given windows', function() { + + beforeEach(function() { + this.osPlatformStub = m.sinon.stub(os, 'platform'); + this.osPlatformStub.returns('win32'); + }); + + afterEach(function() { + this.osPlatformStub.restore(); + }); + + describe('given available drives', function() { beforeEach(function() { - this.osPlatformStub = m.sinon.stub(os, 'platform'); - this.osPlatformStub.returns('win32'); + this.drivelistStub = m.sinon.stub(drivelist, 'list'); + this.drivelistStub.yields(null, [ + { + device: '\\\\.\\PHYSICALDRIVE1', + description: 'WDC WD10JPVX-75J', + size: '931.5G', + mountpoints: [ + { + path: 'C:' + } + ], + system: true + }, + { + device: '\\\\.\\PHYSICALDRIVE2', + description: 'Foo', + size: '14G', + mountpoints: [], + system: false + }, + { + device: '\\\\.\\PHYSICALDRIVE3', + description: 'Bar', + size: '14G', + mountpoints: [ + { + path: 'F:' + } + ], + system: false + } + ]); }); afterEach(function() { - this.osPlatformStub.restore(); + this.drivelistStub.restore(); }); - describe('given available drives', function() { - - beforeEach(function() { - this.drivesListStub = m.sinon.stub(drivelist, 'list'); - this.drivesListStub.yields(null, [ - { - device: '\\\\.\\PHYSICALDRIVE1', - description: 'WDC WD10JPVX-75J', - size: '931.5G', - mountpoints: [ - { - path: 'C:' - } - ], - system: true - }, + it('should emit the non removable drives', function(done) { + driveScanner.once('drives', function(drives) { + m.chai.expect(drives).to.deep.equal([ { device: '\\\\.\\PHYSICALDRIVE2', + name: '\\\\.\\PHYSICALDRIVE2', description: 'Foo', size: '14G', mountpoints: [], @@ -210,6 +224,7 @@ describe('Browser: DriveScanner', function() { }, { device: '\\\\.\\PHYSICALDRIVE3', + name: 'F:', description: 'Bar', size: '14G', mountpoints: [ @@ -220,149 +235,118 @@ describe('Browser: DriveScanner', function() { system: false } ]); + + driveScanner.stop(); + done(); }); - afterEach(function() { - this.drivesListStub.restore(); - }); - - it('should emit the non removable drives', function(done) { - DriveScannerService.on('drives', function(drives) { - m.chai.expect(drives).to.deep.equal([ - { - device: '\\\\.\\PHYSICALDRIVE2', - name: '\\\\.\\PHYSICALDRIVE2', - description: 'Foo', - size: '14G', - mountpoints: [], - system: false - }, - { - device: '\\\\.\\PHYSICALDRIVE3', - name: 'F:', - description: 'Bar', - size: '14G', - mountpoints: [ - { - path: 'F:' - } - ], - system: false - } - ]); - - DriveScannerService.stop(); - done(); - }); - - DriveScannerService.start(); - }); - - }); - - describe('given a drive with a single drive letters', function() { - - beforeEach(function() { - this.drivesListStub = m.sinon.stub(drivelist, 'list'); - this.drivesListStub.yields(null, [ - { - device: '\\\\.\\PHYSICALDRIVE3', - description: 'Bar', - size: '14G', - mountpoints: [ - { - path: 'F:' - } - ], - system: false - } - ]); - }); - - afterEach(function() { - this.drivesListStub.restore(); - }); - - it('should use the drive letter as the name', function(done) { - DriveScannerService.on('drives', function(drives) { - m.chai.expect(drives).to.have.length(1); - m.chai.expect(drives[0].name).to.equal('F:'); - DriveScannerService.stop(); - done(); - }); - - DriveScannerService.start(); - }); - - }); - - describe('given a drive with multiple drive letters', function() { - - beforeEach(function() { - this.drivesListStub = m.sinon.stub(drivelist, 'list'); - this.drivesListStub.yields(null, [ - { - device: '\\\\.\\PHYSICALDRIVE3', - description: 'Bar', - size: '14G', - mountpoints: [ - { - path: 'F:' - }, - { - path: 'G:' - }, - { - path: 'H:' - } - ], - system: false - } - ]); - }); - - afterEach(function() { - this.drivesListStub.restore(); - }); - - it('should join all the mountpoints in `name`', function(done) { - DriveScannerService.on('drives', function(drives) { - m.chai.expect(drives).to.have.length(1); - m.chai.expect(drives[0].name).to.equal('F:, G:, H:'); - DriveScannerService.stop(); - done(); - }); - - DriveScannerService.start(); - }); - + driveScanner.start(); }); }); - describe('given an error when listing the drives', function() { + describe('given a drive with a single drive letters', function() { + + beforeEach(function() { + this.drivelistStub = m.sinon.stub(drivelist, 'list'); + this.drivelistStub.yields(null, [ + { + device: '\\\\.\\PHYSICALDRIVE3', + description: 'Bar', + size: '14G', + mountpoints: [ + { + path: 'F:' + } + ], + system: false + } + ]); + }); + + afterEach(function() { + this.drivelistStub.restore(); + }); + + it('should use the drive letter as the name', function(done) { + driveScanner.once('drives', function(drives) { + m.chai.expect(drives).to.have.length(1); + m.chai.expect(drives[0].name).to.equal('F:'); + driveScanner.stop(); + done(); + }); + + driveScanner.start(); + }); + + }); + + describe('given a drive with multiple drive letters', function() { beforeEach(function() { this.drivesListStub = m.sinon.stub(drivelist, 'list'); - this.drivesListStub.yields(new Error('scan error')); + this.drivesListStub.yields(null, [ + { + device: '\\\\.\\PHYSICALDRIVE3', + description: 'Bar', + size: '14G', + mountpoints: [ + { + path: 'F:' + }, + { + path: 'G:' + }, + { + path: 'H:' + } + ], + system: false + } + ]); }); afterEach(function() { this.drivesListStub.restore(); }); - it('should emit the error', function(done) { - DriveScannerService.on('error', function(error) { - m.chai.expect(error).to.be.an.instanceof(Error); - m.chai.expect(error.message).to.equal('scan error'); - DriveScannerService.stop(); + it('should join all the mountpoints in `name`', function(done) { + driveScanner.once('drives', function(drives) { + m.chai.expect(drives).to.have.length(1); + m.chai.expect(drives[0].name).to.equal('F:, G:, H:'); + driveScanner.stop(); done(); }); - DriveScannerService.start(); + driveScanner.start(); }); }); }); + + describe('given an error when listing the drives', function() { + + beforeEach(function() { + this.drivesListStub = m.sinon.stub(drivelist, 'list'); + this.drivesListStub.yields(new Error('scan error')); + }); + + afterEach(function() { + this.drivesListStub.restore(); + }); + + it('should emit the error', function(done) { + driveScanner.on('error', function(error) { + m.chai.expect(error).to.be.an.instanceof(Error); + m.chai.expect(error.message).to.equal('scan error'); + driveScanner.stop(); + done(); + }); + + driveScanner.start(); + }); + + }); + });