diff --git a/.jscsrc b/.jscsrc
index c7d6cc49..e777638e 100644
--- a/.jscsrc
+++ b/.jscsrc
@@ -131,7 +131,7 @@
"!=="
],
"maximumLineLength": 130,
- "maximumNumberOfLines": 500,
+ "maximumNumberOfLines": 1000,
"requireAlignedMultilineParams": true,
"requireAlignedObjectValues": false,
"requireAnonymousFunctions": true,
diff --git a/lib/gui/components/drive-selector/templates/drive-selector-modal.tpl.html b/lib/gui/components/drive-selector/templates/drive-selector-modal.tpl.html
index 4dd7f164..4d79629e 100644
--- a/lib/gui/components/drive-selector/templates/drive-selector-modal.tpl.html
+++ b/lib/gui/components/drive-selector/templates/drive-selector-modal.tpl.html
@@ -6,20 +6,32 @@
-
+ ng-disabled="!modal.state.isDriveValid(drive)"
+ ng-click="modal.state.isDriveValid(drive) && modal.state.toggleSetDrive(drive)">
{{ drive.description }} - {{ drive.size | gigabyte | number:1 }} GB
{{ drive.name }}
diff --git a/lib/gui/models/selection-state.js b/lib/gui/models/selection-state.js
index 6ef0b8d7..e39eda0b 100644
--- a/lib/gui/models/selection-state.js
+++ b/lib/gui/models/selection-state.js
@@ -83,6 +83,10 @@ SelectionStateModel.service('SelectionStateModel', function() {
throw new Error(`Invalid drive size: ${drive.size}`);
}
+ if (!_.isBoolean(drive.protected)) {
+ throw new Error(`Invalid drive protected state: ${drive.protected}`);
+ }
+
if (!self.isDriveLargeEnough(drive)) {
throw new Error('The drive is not large enough');
}
@@ -124,6 +128,62 @@ SelectionStateModel.service('SelectionStateModel', function() {
return (self.getImageSize() || 0) <= drive.size;
};
+ /**
+ * @summary Check if a drive is locked
+ * @function
+ * @public
+ *
+ * @description
+ * This usually points out a locked SD Card.
+ *
+ * @param {Object} drive - drive
+ * @returns {Boolean} whether the drive is locked
+ *
+ * @example
+ * if (SelectionStateModel.isDriveLocked({
+ * device: '/dev/disk2',
+ * name: 'My Drive',
+ * size: 123456789,
+ * protected: true
+ * })) {
+ * console.log('This drive is locked (e.g: write-protected)');
+ * }
+ */
+ this.isDriveLocked = function(drive) {
+ return _.get(drive, 'protected', false);
+ };
+
+ /**
+ * @summary Check if a drive is valid
+ * @function
+ * @public
+ *
+ * @description
+ * This function is a facade to:
+ *
+ * - `SelectionStateModel.isDriveLargeEnough()`
+ * - `SelectionStateModel.isDriveLocked()`
+ *
+ * @param {Object} drive - drive
+ * @returns {Boolean} whether the drive is valid
+ *
+ * @example
+ * if (SelectionStateModel.isDriveValid({
+ * device: '/dev/disk2',
+ * name: 'My Drive',
+ * size: 123456789,
+ * protected: true
+ * })) {
+ * console.log('This drive is valid!');
+ * }
+ */
+ this.isDriveValid = function(drive) {
+ return _.every([
+ self.isDriveLargeEnough(drive),
+ !self.isDriveLocked(drive)
+ ]);
+ };
+
/**
* @summary Toggle set drive
* @function
diff --git a/package.json b/package.json
index 95989f0d..b5f1eeb7 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
"bluebird": "^3.0.5",
"bootstrap-sass": "^3.3.5",
"chalk": "^1.1.3",
- "drivelist": "^3.1.2",
+ "drivelist": "^3.2.0",
"electron-is-running-in-asar": "^1.0.0",
"etcher-image-stream": "^2.0.0",
"etcher-image-write": "^5.0.0",
diff --git a/tests/gui/models/selection-state.spec.js b/tests/gui/models/selection-state.spec.js
index 8d165be4..13e92e14 100644
--- a/tests/gui/models/selection-state.spec.js
+++ b/tests/gui/models/selection-state.spec.js
@@ -49,13 +49,76 @@ describe('Browser: SelectionState', function() {
});
+ describe('.isDriveLocked()', function() {
+
+ it('should return true if the drive is protected', function() {
+ const result = SelectionStateModel.isDriveLocked({
+ device: '/dev/disk2',
+ name: 'USB Drive',
+ size: 999999999,
+ protected: true
+ });
+
+ m.chai.expect(result).to.be.true;
+ });
+
+ it('should return false if the drive is not protected', function() {
+ const result = SelectionStateModel.isDriveLocked({
+ device: '/dev/disk2',
+ name: 'USB Drive',
+ size: 999999999,
+ protected: false
+ });
+
+ m.chai.expect(result).to.be.false;
+ });
+
+ it('should return false if we don\'t know if the drive is protected', function() {
+ const result = SelectionStateModel.isDriveLocked({
+ device: '/dev/disk2',
+ name: 'USB Drive',
+ size: 999999999
+ });
+
+ m.chai.expect(result).to.be.false;
+ });
+
+ });
+
+ describe('.isDriveValid()', function() {
+
+ it('should return true if the drive is not locked', function() {
+ const result = SelectionStateModel.isDriveValid({
+ device: '/dev/disk2',
+ name: 'USB Drive',
+ size: 999999999,
+ protected: false
+ });
+
+ m.chai.expect(result).to.be.true;
+ });
+
+ it('should return false if the drive is locked', function() {
+ const result = SelectionStateModel.isDriveValid({
+ device: '/dev/disk2',
+ name: 'USB Drive',
+ size: 999999999,
+ protected: true
+ });
+
+ m.chai.expect(result).to.be.false;
+ });
+
+ });
+
describe('given a drive', function() {
beforeEach(function() {
SelectionStateModel.setDrive({
device: '/dev/disk2',
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
});
});
@@ -66,7 +129,8 @@ describe('Browser: SelectionState', function() {
m.chai.expect(drive).to.deep.equal({
device: '/dev/disk2',
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
});
});
@@ -87,14 +151,16 @@ describe('Browser: SelectionState', function() {
SelectionStateModel.setDrive({
device: '/dev/disk5',
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
});
const drive = SelectionStateModel.getDrive();
m.chai.expect(drive).to.deep.equal({
device: '/dev/disk5',
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
});
});
@@ -120,14 +186,16 @@ describe('Browser: SelectionState', function() {
SelectionStateModel.setDrive({
device: '/dev/disk5',
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
});
const drive = SelectionStateModel.getDrive();
m.chai.expect(drive).to.deep.equal({
device: '/dev/disk5',
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
});
});
@@ -135,7 +203,8 @@ describe('Browser: SelectionState', function() {
m.chai.expect(function() {
SelectionStateModel.setDrive({
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
});
}).to.throw('Missing drive device');
});
@@ -145,7 +214,8 @@ describe('Browser: SelectionState', function() {
SelectionStateModel.setDrive({
device: 123,
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
});
}).to.throw('Invalid drive device: 123');
});
@@ -154,7 +224,8 @@ describe('Browser: SelectionState', function() {
m.chai.expect(function() {
SelectionStateModel.setDrive({
device: '/dev/disk2',
- size: 999999999
+ size: 999999999,
+ protected: false
});
}).to.throw('Missing drive name');
});
@@ -164,7 +235,8 @@ describe('Browser: SelectionState', function() {
SelectionStateModel.setDrive({
device: '/dev/disk2',
name: 123,
- size: 999999999
+ size: 999999999,
+ protected: false
});
}).to.throw('Invalid drive name: 123');
});
@@ -173,7 +245,8 @@ describe('Browser: SelectionState', function() {
m.chai.expect(function() {
SelectionStateModel.setDrive({
device: '/dev/disk2',
- name: 'USB Drive'
+ name: 'USB Drive',
+ protected: false
});
}).to.throw('Missing drive size');
});
@@ -183,11 +256,33 @@ describe('Browser: SelectionState', function() {
SelectionStateModel.setDrive({
device: '/dev/disk2',
name: 'USB Drive',
- size: '999999999'
+ size: '999999999',
+ protected: false
});
}).to.throw('Invalid drive size: 999999999');
});
+ it('should throw if no protected property', function() {
+ m.chai.expect(function() {
+ SelectionStateModel.setDrive({
+ device: '/dev/disk2',
+ name: 'USB Drive',
+ size: 999999999
+ });
+ }).to.throw('Invalid drive protected state: undefined');
+ });
+
+ it('should throw if the protected is not boolean', function() {
+ m.chai.expect(function() {
+ SelectionStateModel.setDrive({
+ device: '/dev/disk2',
+ name: 'USB Drive',
+ size: 999999999,
+ protected: 'foo'
+ });
+ }).to.throw('Invalid drive protected state: foo');
+ });
+
});
});
@@ -207,7 +302,8 @@ describe('Browser: SelectionState', function() {
const result = SelectionStateModel.isDriveLargeEnough({
device: '/dev/disk1',
name: 'USB Drive',
- size: 99999999999999
+ size: 99999999999999,
+ protected: false
});
m.chai.expect(result).to.be.true;
@@ -217,7 +313,8 @@ describe('Browser: SelectionState', function() {
const result = SelectionStateModel.isDriveLargeEnough({
device: '/dev/disk1',
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
});
m.chai.expect(result).to.be.true;
@@ -227,7 +324,56 @@ describe('Browser: SelectionState', function() {
const result = SelectionStateModel.isDriveLargeEnough({
device: '/dev/disk1',
name: 'USB Drive',
- size: 999999998
+ size: 999999998,
+ protected: false
+ });
+
+ m.chai.expect(result).to.be.false;
+ });
+
+ });
+
+ describe('.isDriveValid()', function() {
+
+ it('should return true if the drive is large enough and it is not locked', function() {
+ const result = SelectionStateModel.isDriveValid({
+ device: '/dev/disk1',
+ name: 'USB Drive',
+ size: 99999999999999,
+ protected: false
+ });
+
+ m.chai.expect(result).to.be.true;
+ });
+
+ it('should return false if the drive is large enough but it is locked', function() {
+ const result = SelectionStateModel.isDriveValid({
+ device: '/dev/disk1',
+ name: 'USB Drive',
+ size: 99999999999999,
+ protected: true
+ });
+
+ m.chai.expect(result).to.be.false;
+ });
+
+ it('should return false if the drive is not large enough and it is not locked', function() {
+ const result = SelectionStateModel.isDriveValid({
+ device: '/dev/disk1',
+ name: 'USB Drive',
+ size: 1,
+ protected: false
+ });
+
+ m.chai.expect(result).to.be.false;
+ });
+
+ it('should return false if the drive is not large enough and it is locked', function() {
+ const result = SelectionStateModel.isDriveValid({
+ device: '/dev/disk1',
+ name: 'USB Drive',
+ size: 1,
+ protected: true
});
m.chai.expect(result).to.be.false;
@@ -242,7 +388,8 @@ describe('Browser: SelectionState', function() {
SelectionStateModel.setDrive({
device: '/dev/disk1',
name: 'USB Drive',
- size: 999999998
+ size: 999999998,
+ protected: false
});
}).to.throw('The drive is not large enough');
});
@@ -381,7 +528,8 @@ describe('Browser: SelectionState', function() {
SelectionStateModel.setDrive({
device: '/dev/disk1',
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
});
SelectionStateModel.setImage({
@@ -452,7 +600,8 @@ describe('Browser: SelectionState', function() {
size: 999999999,
mountpoint: '/media/UNTITLED',
name: '/dev/sdb',
- system: false
+ system: false,
+ protected: false
});
});
@@ -471,7 +620,8 @@ describe('Browser: SelectionState', function() {
size: 999999999,
mountpoint: '/media/UNTITLED',
name: '/dev/sdb',
- system: false
+ system: false,
+ protected: false
})).to.be.true;
});
@@ -483,7 +633,8 @@ describe('Browser: SelectionState', function() {
mountpoint: '/media/UNTITLED',
name: '/dev/sdb',
system: false,
- $$hashKey: 1234
+ $$hashKey: 1234,
+ protected: false
})).to.be.true;
});
@@ -494,7 +645,8 @@ describe('Browser: SelectionState', function() {
size: 999999999,
mountpoint: '/media/UNTITLED',
name: '/dev/sdb',
- system: false
+ system: false,
+ protected: false
})).to.be.false;
});
@@ -505,7 +657,8 @@ describe('Browser: SelectionState', function() {
size: 999999999,
mountpoint: '/media/UNTITLED',
name: '/dev/sdb',
- system: false
+ system: false,
+ protected: false
})).to.be.true;
});
@@ -533,7 +686,8 @@ describe('Browser: SelectionState', function() {
size: 999999999,
mountpoint: '/media/UNTITLED',
name: '/dev/sdb',
- system: false
+ system: false,
+ protected: false
})).to.be.false;
});
@@ -553,7 +707,8 @@ describe('Browser: SelectionState', function() {
size: 999999999,
mountpoint: '/media/UNTITLED',
name: '/dev/sdb',
- system: false
+ system: false,
+ protected: false
};
SelectionStateModel.setDrive(this.drive);
@@ -569,7 +724,8 @@ describe('Browser: SelectionState', function() {
const drive = {
device: '/dev/disk2',
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
};
m.chai.expect(SelectionStateModel.getDrive()).to.deep.equal(this.drive);
@@ -590,7 +746,8 @@ describe('Browser: SelectionState', function() {
const drive = {
device: '/dev/disk2',
name: 'USB Drive',
- size: 999999999
+ size: 999999999,
+ protected: false
};
m.chai.expect(SelectionStateModel.hasDrive()).to.be.false;