refactor(GUI): remove angular dependency from drive scanner (#1512)

Remove the Angular dependency from DriveScanner and with it the service,
exposing it through the module directly.
This commit is contained in:
Benedict Aas 2017-06-15 16:26:03 +01:00 committed by Juan Cruz Viotti
parent 9c1f9a54b1
commit 0f600c3cc2
5 changed files with 330 additions and 359 deletions

View File

@ -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) => {

View File

@ -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;

View File

@ -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();
});
};

View File

@ -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'),

View File

@ -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();
});
});
});