From 91719435d9c63f667dae11bf9fafe61025b99e74 Mon Sep 17 00:00:00 2001 From: Benedict Aas Date: Mon, 19 Feb 2018 19:12:48 +0000 Subject: [PATCH] feat(GUI): warn the user on large drive selection (#2045) We warn the user when they select a large drive to confirm they want to flash in case the device is important. Fixes: https://github.com/resin-io/etcher/issues/1916 Change-Type: patch Changelog-Entry: Warn the user on selection of large drives. --- .../controllers/drive-selector.js | 25 +++++---- lib/shared/drive-constraints.js | 41 +++++++++++++- lib/shared/messages.js | 7 +++ lib/shared/store.js | 3 ++ tests/shared/drive-constraints.spec.js | 53 +++++++++++++++++++ tests/shared/models/available-drives.spec.js | 22 ++++++++ 6 files changed, 141 insertions(+), 10 deletions(-) diff --git a/lib/gui/app/components/drive-selector/controllers/drive-selector.js b/lib/gui/app/components/drive-selector/controllers/drive-selector.js index 5983b767..8b1141ec 100644 --- a/lib/gui/app/components/drive-selector/controllers/drive-selector.js +++ b/lib/gui/app/components/drive-selector/controllers/drive-selector.js @@ -75,17 +75,24 @@ module.exports = function ( return $q.resolve(false) } - if (constraints.isDriveSizeRecommended(drive, selectionState.getImage())) { - return $q.resolve(true) + if (!constraints.isDriveSizeRecommended(drive, selectionState.getImage())) { + return WarningModalService.display({ + confirmationLabel: 'Yes, continue', + description: [ + messages.warning.unrecommendedDriveSize(selectionState.getImage(), drive), + 'Are you sure you want to continue?' + ].join(' ') + }) } - return WarningModalService.display({ - confirmationLabel: 'Yes, continue', - description: [ - messages.warning.unrecommendedDriveSize(selectionState.getImage(), drive), - 'Are you sure you want to continue?' - ].join(' ') - }) + if (constraints.isDriveSizeLarge(drive)) { + return WarningModalService.display({ + confirmationLabel: 'Yes, continue', + description: messages.warning.largeDriveSize(drive) + }) + } + + return $q.resolve(true) } /** diff --git a/lib/shared/drive-constraints.js b/lib/shared/drive-constraints.js index eb6f562b..98314d0f 100644 --- a/lib/shared/drive-constraints.js +++ b/lib/shared/drive-constraints.js @@ -276,6 +276,29 @@ exports.isDriveSizeRecommended = (drive, image) => { return _.get(drive, [ 'size' ], UNKNOWN_SIZE) >= _.get(image, [ 'recommendedDriveSize' ], UNKNOWN_SIZE) } +/** + * @summary 64GB + * @private + * @constant + */ +exports.LARGE_DRIVE_SIZE = 64e9 + +/** + * @summary Check whether a drive's size is 'large' + * @public + * + * @param {Object} drive - drive + * @returns {Boolean} whether drive size is large + * + * @example + * if (constraints.isDriveSizeLarge(drive)) { + * console.log('Impressive') + * } + */ +exports.isDriveSizeLarge = (drive) => { + return _.get(drive, [ 'size' ], UNKNOWN_SIZE) > exports.LARGE_DRIVE_SIZE +} + /** * @summary Drive/image compatibility status messages. * @public @@ -330,7 +353,16 @@ exports.COMPATIBILITY_STATUS_MESSAGES = { * @description * The drive contains the image and therefore cannot be written to. */ - CONTAINS_IMAGE: 'Drive Contains Image' + CONTAINS_IMAGE: 'Drive Contains Image', + + /** + * @property {String} LARGE_DRIVE + * @memberof COMPATIBILITY_STATUS_MESSAGES + * + * @description + * The drive is large and therefore likely not a medium you want to write to. + */ + LARGE_DRIVE: 'Large Drive' } /** @@ -416,6 +448,13 @@ exports.getDriveImageCompatibilityStatuses = (drive, image) => { }) } + if (exports.isDriveSizeLarge(drive)) { + statusList.push({ + type: exports.COMPATIBILITY_STATUS_TYPES.WARNING, + message: exports.COMPATIBILITY_STATUS_MESSAGES.LARGE_DRIVE + }) + } + if (!_.isNil(drive) && !exports.isDriveSizeRecommended(drive, image)) { statusList.push({ type: exports.COMPATIBILITY_STATUS_TYPES.WARNING, diff --git a/lib/shared/messages.js b/lib/shared/messages.js index c5dbb1f7..309ae260 100644 --- a/lib/shared/messages.js +++ b/lib/shared/messages.js @@ -78,6 +78,13 @@ module.exports = { 'The image does not appear to contain a partition table,', 'and might not be recognized or bootable by your device.' ].join(' ') + }, + + largeDriveSize: (drive) => { + return [ + `Drive ${drive.description} (${drive.device}) is unusually large for an SD card or USB stick.`, + '\n\nAre you sure you want to flash this drive?' + ].join(' ') } }, diff --git a/lib/shared/store.js b/lib/shared/store.js index 6883f466..6b4d9e1e 100644 --- a/lib/shared/store.js +++ b/lib/shared/store.js @@ -144,6 +144,9 @@ const storeReducer = (state = DEFAULT_STATE, action) => { constraints.isDriveValid(drive, image), constraints.isDriveSizeRecommended(drive, image), + // We don't want to auto-select large drives + !constraints.isDriveSizeLarge(drive), + // We don't want to auto-select system drives, // even when "unsafe mode" is enabled !constraints.isSystemDrive(drive) diff --git a/tests/shared/drive-constraints.spec.js b/tests/shared/drive-constraints.spec.js index ef198029..1425ca63 100644 --- a/tests/shared/drive-constraints.spec.js +++ b/tests/shared/drive-constraints.spec.js @@ -946,6 +946,48 @@ describe('Shared: DriveConstraints', function () { }) }) + describe('.isDriveSizeLarge()', function () { + beforeEach(function () { + this.drive = { + device: '/dev/disk2', + name: 'My Drive', + isReadonly: false, + isSystem: false, + disabled: false, + mountpoints: [ + { + path: this.mountpoint + } + ], + size: constraints.LARGE_DRIVE_SIZE + 1 + } + + this.image = { + path: path.join(__dirname, 'rpi.img'), + size: { + original: this.drive.size - 1, + final: { + estimation: false, + value: this.drive.size - 1 + } + } + } + }) + + describe('given a drive bigger than the unusually large drive size', function () { + it('should return true', function () { + m.chai.expect(constraints.isDriveSizeLarge(this.drive)).to.be.true + }) + }) + + describe('given a drive smaller than the unusually large drive size', function () { + it('should return false', function () { + this.drive.size = constraints.LARGE_DRIVE_SIZE - 1 + m.chai.expect(constraints.isDriveSizeLarge(this.drive)).to.be.false + }) + }) + }) + describe('.getDriveImageCompatibilityStatuses', function () { beforeEach(function () { if (process.platform === 'win32') { @@ -1080,6 +1122,17 @@ describe('Shared: DriveConstraints', function () { }) }) + describe('given the drive is unusually large', function () { + it('should return the large drive size warning', function () { + this.drive.size = constraints.LARGE_DRIVE_SIZE + 1 + + const result = constraints.getDriveImageCompatibilityStatuses(this.drive, this.image) + const expectedTuples = [ [ 'WARNING', 'LARGE_DRIVE' ] ] + + expectStatusTypesAndMessagesToBe(result, expectedTuples) + }) + }) + describe('given the image is null', () => { it('should return an empty list', function () { const result = constraints.getDriveImageCompatibilityStatuses(this.drive, null) diff --git a/tests/shared/models/available-drives.spec.js b/tests/shared/models/available-drives.spec.js index 3ca06585..f0d9a2c4 100644 --- a/tests/shared/models/available-drives.spec.js +++ b/tests/shared/models/available-drives.spec.js @@ -20,6 +20,7 @@ const m = require('mochainon') const path = require('path') const availableDrives = require('../../../lib/shared/models/available-drives') const selectionState = require('../../../lib/shared/models/selection-state') +const constraints = require('../../../lib/shared/drive-constraints') describe('Model: availableDrives', function () { describe('availableDrives', function () { @@ -347,6 +348,27 @@ describe('Model: availableDrives', function () { m.chai.expect(selectionState.hasDrive()).to.be.false }) + + it('should not auto-select a single large size drive', function () { + m.chai.expect(selectionState.hasDrive()).to.be.false + + availableDrives.setDrives([ + { + device: '/dev/sdb', + name: 'Foo', + size: constraints.LARGE_DRIVE_SIZE + 1, + mountpoints: [ + { + path: '/mnt/foo' + } + ], + system: false, + protected: false + } + ]) + + m.chai.expect(selectionState.hasDrive()).to.be.false + }) }) }) })