feat(GUI): warn when atempting to burn Windows images (#1043)

Change-Type: minor
Changelog-Entry: Show warning when user tries to flash a Windows image
Closes: https://github.com/resin-io/etcher/issues/1035
This commit is contained in:
Ștefan Daniel Mihăilă 2017-02-24 21:12:03 +02:00 committed by Juan Cruz Viotti
parent d845cccff5
commit 5b9104e8aa
7 changed files with 115 additions and 13 deletions

View File

@ -18,7 +18,7 @@
const _ = require('lodash');
module.exports = function(ModalService) {
module.exports = function($sce, ModalService) {
/**
* @summary Display the warning modal
@ -39,6 +39,7 @@ module.exports = function(ModalService) {
* });
*/
this.display = (options = {}) => {
options.description = $sce.trustAsHtml(options.description);
return ModalService.open({
template: './components/warning-modal/templates/warning-modal.tpl.html',
controller: 'WarningModalController as modal',

View File

@ -7,7 +7,7 @@
</div>
<div class="modal-body">
<p>{{ ::modal.options.description }}</p>
<p ng-bind-html="modal.options.description"></p>
</div>
<div class="modal-footer">

View File

@ -107,15 +107,15 @@ SupportedFormats.service('SupportedFormatsModel', function() {
* @function
* @public
*
* @param {String} image - image path
* @param {String} imagePath - image path
* @returns {Boolean} whether the image is supported
*
* if (SupportedFormatsModel.isSupportedImage('foo.iso.bz2')) {
* console.log('The image is supported!');
* }
*/
this.isSupportedImage = (image) => {
const extension = path.extname(image).slice(1).toLowerCase();
this.isSupportedImage = (imagePath) => {
const extension = path.extname(imagePath).slice(1).toLowerCase();
if (_.some([
_.includes(this.getNonCompressedExtensions(), extension),
@ -128,7 +128,25 @@ SupportedFormats.service('SupportedFormatsModel', function() {
return false;
}
return this.isSupportedImage(path.basename(image, `.${extension}`));
return this.isSupportedImage(path.basename(imagePath, `.${extension}`));
};
/**
* @summary Check if an image seems to be a Windows image
* @function
* @public
*
* @param {String} imagePath - image path
* @returns {Boolean} whether the image seems to be a Windows image
*
* @example
* if (SupportedFormatsModel.looksLikeWindowsImage('path/to/en_windows_7_ultimate_with_sp1_x86_dvd_u_677460.iso')) {
* console.log('Looks like a Windows image');
* }
*/
this.looksLikeWindowsImage = (imagePath) => {
const regex = /windows|win7|win8|win10|winxp/i;
return regex.test(path.basename(imagePath));
};
});

View File

@ -21,9 +21,25 @@
*/
const angular = require('angular');
const url = require('url');
const MODULE_NAME = 'Etcher.OS.OpenExternal';
const OSOpenExternal = angular.module(MODULE_NAME, []);
OSOpenExternal.service('OSOpenExternalService', require('./services/open-external'));
OSOpenExternal.directive('osOpenExternal', require('./directives/open-external'));
OSOpenExternal.run(function(OSOpenExternalService) {
document.addEventListener('click', (event) => {
const target = event.target;
if (target.tagName === 'A' && angular.isDefined(target.href)) {
// Electron interprets relative URLs as being relative to the current loaded URL (with `webContents.loadURL`) and expands
// them to the corresponding absolute URL. If it's a `file://` URL, we don't want it opened in an external browser.
if (url.parse(target.href).protocol !== 'file:') {
OSOpenExternalService.open(target.href);
}
}
});
});
module.exports = MODULE_NAME;

View File

@ -17,9 +17,17 @@
'use strict';
const _ = require('lodash');
const Bluebird = require('bluebird');
const messages = require('../../../../shared/messages');
module.exports = function(SupportedFormatsModel, SelectionStateModel, AnalyticsService, ErrorService, OSDialogService) {
module.exports = function(
SupportedFormatsModel,
SelectionStateModel,
AnalyticsService,
ErrorService,
OSDialogService,
WarningModalService
) {
/**
* @summary Main supported extensions
@ -65,14 +73,36 @@ module.exports = function(SupportedFormatsModel, SelectionStateModel, AnalyticsS
return;
}
SelectionStateModel.setImage(image);
Bluebird.try(() => {
if (!SupportedFormatsModel.looksLikeWindowsImage(image.path)) {
return false;
}
// An easy way so we can quickly identify if we're making use of
// certain features without printing pages of text to DevTools.
image.logo = Boolean(image.logo);
image.bmap = Boolean(image.bmap);
AnalyticsService.logEvent('Possibly Windows image', image);
// TODO: `Continue` should be on a red background (dangerous action) instead of `Change`.
// We want `X` to act as `Continue`, that's why `Continue` is the `rejectionLabel`
return WarningModalService.display({
confirmationLabel: 'Change',
rejectionLabel: 'Continue',
description: messages.warning.looksLikeWindowsImage()
});
}).then((shouldChange) => {
if (shouldChange) {
return this.reselectImage();
}
SelectionStateModel.setImage(image);
// An easy way so we can quickly identify if we're making use of
// certain features without printing pages of text to DevTools.
image.logo = Boolean(image.logo);
image.bmap = Boolean(image.bmap);
AnalyticsService.logEvent('Select image', image);
}).catch(ErrorService.reportException);
AnalyticsService.logEvent('Select image', image);
};
/**

View File

@ -51,6 +51,13 @@ module.exports = {
exitWhileFlashing: _.template([
'You are currently flashing a drive.',
'Closing Etcher may leave your drive in an unusable state.'
].join(' ')),
looksLikeWindowsImage: _.template([
'It looks like you are trying to burn a Windows image.\n\n',
'Unlike other images, Windows images require special processing to be made bootable.',
'We suggest you use a tool specially designed for this purpose, such as',
'<a href="https://rufus.akeo.ie">Rufus</a>.'
].join(' '))
},

View File

@ -123,6 +123,36 @@ describe('Browser: SupportedFormats', function() {
});
describe('.looksLikeWindowsImage()', function() {
_.each([
'C:\\path\\to\\en_windows_10_multiple_editions_version_1607_updated_jan_2017_x64_dvd_9714399.iso',
'/path/to/en_windows_10_multiple_editions_version_1607_updated_jan_2017_x64_dvd_9714399.iso',
'/path/to/Win10_1607_SingleLang_English_x32.iso',
'/path/to/en_winxp_pro_x86_build2600_iso.img'
], (imagePath) => {
it(`should return true if filename is ${imagePath}`, function() {
const looksLikeWindowsImage = SupportedFormatsModel.looksLikeWindowsImage(imagePath);
m.chai.expect(looksLikeWindowsImage).to.be.true;
});
});
_.each([
'C:\\path\\to\\2017-01-11-raspbian-jessie.img',
'/path/to/2017-01-11-raspbian-jessie.img'
], (imagePath) => {
it(`should return false if filename is ${imagePath}`, function() {
const looksLikeWindowsImage = SupportedFormatsModel.looksLikeWindowsImage(imagePath);
m.chai.expect(looksLikeWindowsImage).to.be.false;
});
});
});
});
});