Files
etcher/lib/browser/modules/drive-scanner.js
Juan Cruz Viotti fda6f8a6a5 Fix Cannot read property 'length' of undefined
This error was reported by TrackJS various times:

```
TypeError: Cannot read property 'length' of undefined
  at EventEmitter.<anonymous> (file:///Users/jviotti/Projects/resin/etcher/lib/browser/app.js:104:15)
  at emitOne (events.js:77:13)
  at EventEmitter.emit (events.js:169:7)
  at /Users/jviotti/Projects/resin/etcher/lib/browser/modules/drive-scanner.js:131:17
  at processQueue (/Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:15616:28)
  at /Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:15632:27
  at Scope.$eval (/Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:16884:28)
  at Scope.$digest (/Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:16700:31)
  at /Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:16923:26
  at completeOutstandingRequest (/Users/jviotti/Projects/resin/etcher/node_modules/angular/angular.js:5825:10),
```

The error refers to the following line in `app.js`:

```js
if (drives.length === 1 && self.selection.hasImage()) {
```

Which indicates that the array of detected drives returned to the main
controller is `undefined` for some reason.

The problem resides in the `.scan()` method of `DriveScannerService`:

```js
this.scan = function() {
  return $q.when(drives.listRemovable()).catch(dialog.showError);
};
```

When an error is thrown when scanning the drives, the `.catch()` block
is called. This means that the error is not propagated to the outer code
and the promise resolves with `undefined`.

The solution is to move `.catch()` to the place `.scan()` is called,
instead of making use of it in the low-level parts of the process.
2016-03-17 09:36:43 -04:00

164 lines
3.9 KiB
JavaScript

/*
* Copyright 2016 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';
/**
* @module Etcher.drive-scanner
*/
const angular = require('angular');
const _ = require('lodash');
const EventEmitter = require('events').EventEmitter;
const electron = require('electron');
if (window.mocha) {
var path = require('path');
var srcPath = path.join(__dirname, '..', '..', 'src');
var drives = electron.remote.require(path.join(srcPath, 'drives'));
var dialog = electron.remote.require(path.join(srcPath, 'dialog'));
} else {
var drives = electron.remote.require('./src/drives');
var dialog = electron.remote.require('./src/dialog');
}
const driveScanner = angular.module('Etcher.drive-scanner', []);
driveScanner.service('DriveScannerService', function($q, $interval, $timeout) {
let self = this;
let interval = null;
/**
* @summary List of available drives
* @type {Object[]}
* @public
*/
this.drives = [];
/**
* @summary Check if there are available drives
* @function
* @public
*
* @returns {Boolean} whether there are available drives
*
* @example
* if (DriveScannerService.hasAvailableDrives()) {
* console.log('There are available drives!');
* }
*/
this.hasAvailableDrives = function() {
return !_.isEmpty(self.drives);
};
/**
* @summary Set the list of drives
* @function
* @public
*
* @param {Object[]} drives - drives
*
* @example
* DriveScannerService.scan().then(function(drives) {
* DriveScannerService.setDrives(drives);
* });
*/
this.setDrives = function(drives) {
// Only update if something has changed
// to avoid unnecessary DOM manipulations
// angular.equals ignores $$hashKey by default
if (!angular.equals(self.drives, drives)) {
self.drives = drives;
}
};
/**
* @summary Get available drives
* @function
* @public
*
* @fulfil {Object[]} - drives
* @returns {Promise}
*
* @example
* DriveScannerService.scan().then(function(drives) {
* console.log(drives);
* });
*/
this.scan = function() {
return $q.when(drives.listRemovable());
};
/**
* @summary Scan drives and populate `.drives`
* @function
* @public
*
* @description
* This function returns an event emitter instance
* that emits a `scan` event everything it scans
* the drives successfully.
*
* @param {Number} ms - interval milliseconds
* @returns {EventEmitter} event emitter instance
*
* @example
* const emitter = DriveScannerService.start(2000);
*
* emitter.on('scan', function(drives) {
* console.log(drives);
* });
*/
this.start = function(ms) {
let emitter = new EventEmitter();
const fn = function() {
return self.scan().then(function(drives) {
emitter.emit('scan', drives);
self.setDrives(drives);
}).catch(dialog.showError);
};
// Make sure any pending interval is cancelled
// to avoid potential memory leaks.
self.stop();
// Call fn after in the next process tick
// to be able to capture the first run
// in unit tests.
$timeout(function() {
fn();
interval = $interval(fn, ms);
});
return emitter;
};
/**
* @summary Stop scanning drives
* @function
* @public
*
* @example
* DriveScannerService.stop();
*/
this.stop = function() {
$interval.cancel(interval);
};
});