mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-24 07:17:18 +00:00
Implement writing by spawning the CLI as a child process (#400)
After this change, the CLI becomes the only entity actually performing I/O with the devices, and the GUI is just a wrapper around it. When you click "Flash", the GUI spawns the CLI with all the appropriate options, including `--ipc`, which uses an IPC communication channel to report status back to the parent process. Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
This commit is contained in:
parent
f365f2fa57
commit
662c589ab9
@ -80,6 +80,14 @@ module.exports = yargs
|
||||
return true;
|
||||
})
|
||||
|
||||
.check(function(argv) {
|
||||
if (argv.ipc && !process.send) {
|
||||
throw new Error('Can\'t establish an IPC channel with the parent process');
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
|
||||
.options({
|
||||
help: {
|
||||
describe: 'show help',
|
||||
@ -107,6 +115,11 @@ module.exports = yargs
|
||||
boolean: true,
|
||||
alias: 'r'
|
||||
},
|
||||
ipc: {
|
||||
describe: 'communicate through a fork IPC channel',
|
||||
boolean: true,
|
||||
alias: 'i'
|
||||
},
|
||||
yes: {
|
||||
describe: 'confirm non-interactively',
|
||||
boolean: true,
|
||||
@ -132,7 +145,11 @@ module.exports = yargs
|
||||
// like *.exe and *.cmd in Windows systems.
|
||||
const executable = path.basename(argv[0], path.extname(argv[0])).toLowerCase();
|
||||
|
||||
if (executable === 'node' || executable === 'electron') {
|
||||
if (_.includes([
|
||||
'node',
|
||||
'electron',
|
||||
'electron helper'
|
||||
], executable)) {
|
||||
|
||||
// In this case, the second argument (e.g: index 1)
|
||||
// equals `lib/cli/etcher.js`, so the real arguments
|
||||
|
@ -42,7 +42,7 @@ form.run([
|
||||
// If `options.yes` is `false`, pass `undefined`,
|
||||
// otherwise the question will not be asked because
|
||||
// `false` is a defined value.
|
||||
yes: options.robot || options.yes || undefined
|
||||
yes: options.robot || options.ipc || options.yes || undefined
|
||||
|
||||
}
|
||||
}).then(function(answers) {
|
||||
@ -70,6 +70,16 @@ form.run([
|
||||
state.eta + 's',
|
||||
Math.floor(state.speed)
|
||||
].join(' '));
|
||||
} else if (options.ipc) {
|
||||
process.send({
|
||||
command: 'progress',
|
||||
data: {
|
||||
type: state.type,
|
||||
percentage: Math.floor(state.percentage),
|
||||
eta: state.eta,
|
||||
speed: Math.floor(state.speed)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
progressBars[state.type].update(state);
|
||||
}
|
||||
@ -79,6 +89,13 @@ form.run([
|
||||
|
||||
if (options.robot) {
|
||||
console.log(`done ${success}`);
|
||||
} else if (options.ipc) {
|
||||
process.send({
|
||||
command: 'done',
|
||||
data: {
|
||||
success: success
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (success) {
|
||||
console.log('Your flash is complete!');
|
||||
|
@ -60,6 +60,13 @@ analytics.config(function($mixpanelProvider) {
|
||||
// http://docs.trackjs.com/tracker/framework-integrations
|
||||
|
||||
analytics.run(function($window) {
|
||||
|
||||
// Don't configure TrackJS when
|
||||
// running inside the test suite
|
||||
if (window.mocha) {
|
||||
return;
|
||||
}
|
||||
|
||||
$window.trackJs.configure({
|
||||
userId: username.sync(),
|
||||
version: packageJSON.version
|
||||
|
@ -21,21 +21,20 @@
|
||||
*/
|
||||
|
||||
const angular = require('angular');
|
||||
const path = require('path');
|
||||
const isRunningInAsar = require('electron-is-running-in-asar');
|
||||
const electron = require('electron');
|
||||
|
||||
if (window.mocha) {
|
||||
var writer = electron.remote.require(require('path').join(__dirname, '..', '..', 'src', 'writer'));
|
||||
} else {
|
||||
var writer = electron.remote.require('./src/writer');
|
||||
}
|
||||
const childProcess = require('child_process');
|
||||
const EXIT_CODES = require('../../src/exit-codes');
|
||||
|
||||
const MODULE_NAME = 'Etcher.image-writer';
|
||||
const imageWriter = angular.module(MODULE_NAME, [
|
||||
require('../models/settings'),
|
||||
require('../modules/analytics'),
|
||||
require('../utils/notifier/notifier')
|
||||
]);
|
||||
|
||||
imageWriter.service('ImageWriterService', function($q, $timeout, SettingsModel, NotifierService) {
|
||||
imageWriter.service('ImageWriterService', function($q, $timeout, SettingsModel, NotifierService, AnalyticsService) {
|
||||
let self = this;
|
||||
let flashing = false;
|
||||
|
||||
@ -107,6 +106,7 @@ imageWriter.service('ImageWriterService', function($q, $timeout, SettingsModel,
|
||||
* @param {Object} drive - drive
|
||||
* @param {Function} onProgress - in progress callback (state)
|
||||
*
|
||||
* @fulfil {Boolean} - whether the operation succeeded
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
@ -117,7 +117,73 @@ imageWriter.service('ImageWriterService', function($q, $timeout, SettingsModel,
|
||||
* });
|
||||
*/
|
||||
this.performWrite = function(image, drive, onProgress) {
|
||||
return $q.when(writer.writeImage(image, drive, SettingsModel.data, onProgress));
|
||||
const argv = [ image ];
|
||||
argv.push('--drive', drive.device);
|
||||
|
||||
// Make use of `child_process.fork()` facility to send
|
||||
// messages to the parent through an IPC channel.
|
||||
// This allows us to receive the progress state by
|
||||
// listening to the `message` event of the
|
||||
// `ChildProcess` object.
|
||||
argv.push('--ipc');
|
||||
|
||||
// Explicitly set the boolen flag in positive
|
||||
// or negative way in order to be on the safe
|
||||
// side in case the Etcher CLI changes the
|
||||
// default value of these options.
|
||||
|
||||
if (SettingsModel.data.unmountOnSuccess) {
|
||||
argv.push('--unmount');
|
||||
} else {
|
||||
argv.push('--no-unmount');
|
||||
}
|
||||
|
||||
if (SettingsModel.data.validateWriteOnSuccess) {
|
||||
argv.push('--check');
|
||||
} else {
|
||||
argv.push('--no-check');
|
||||
}
|
||||
|
||||
return $q(function(resolve, reject) {
|
||||
|
||||
let executable;
|
||||
|
||||
if (isRunningInAsar()) {
|
||||
executable = path.join(process.resourcesPath, 'app.asar');
|
||||
} else {
|
||||
executable = electron.remote.process.argv[1];
|
||||
}
|
||||
|
||||
AnalyticsService.log(`Forking: ${executable} ${argv.join(' ')}`);
|
||||
const child = childProcess.fork(executable, argv, {
|
||||
|
||||
// Pipe stdout/stderr to the parent
|
||||
// We're not using it directly but its
|
||||
// handy for debugging reasons.
|
||||
silent: true
|
||||
|
||||
});
|
||||
|
||||
child.on('message', function(message) {
|
||||
if (message.command === 'progress') {
|
||||
return onProgress(message.data);
|
||||
}
|
||||
});
|
||||
|
||||
child.on('error', reject);
|
||||
|
||||
child.on('close', function(code) {
|
||||
if (code === EXIT_CODES.SUCCESS) {
|
||||
return resolve(true);
|
||||
}
|
||||
|
||||
if (code === EXIT_CODES.VALIDATION_ERROR) {
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
return reject(new Error(`Child process exitted with error code: ${code}`));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -154,7 +220,7 @@ imageWriter.service('ImageWriterService', function($q, $timeout, SettingsModel,
|
||||
|
||||
self.state = {
|
||||
type: state.type,
|
||||
progress: Math.floor(state.percentage),
|
||||
progress: state.percentage,
|
||||
|
||||
// Transform bytes to megabytes preserving only two decimal places
|
||||
speed: Math.floor(state.speed / 1e+6 * 100) / 100 || 0
|
||||
|
@ -22,7 +22,11 @@
|
||||
// *won't* attempt to load the `app.asar` application by default, therefore
|
||||
// if passing `ELECTRON_RUN_AS_NODE`, you have to pass the path to the asar
|
||||
// or the entry point file (this file) manually as an argument.
|
||||
if (process.env.ELECTRON_RUN_AS_NODE) {
|
||||
//
|
||||
// We also consider `ATOM_SHELL_INTERNAL_RUN_AS_NODE`, which is basically
|
||||
// an older equivalent of `ELECTRON_RUN_AS_NODE` that still gets set when
|
||||
// using `child_process.fork()`.
|
||||
if (process.env.ELECTRON_RUN_AS_NODE || process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE) {
|
||||
require('./cli/etcher');
|
||||
|
||||
} else {
|
||||
|
@ -54,6 +54,7 @@
|
||||
"bootstrap-sass": "^3.3.5",
|
||||
"chalk": "^1.1.3",
|
||||
"drivelist": "^3.0.0",
|
||||
"electron-is-running-in-asar": "^1.0.0",
|
||||
"etcher-image-stream": "^1.2.0",
|
||||
"etcher-image-write": "^4.0.2",
|
||||
"flexboxgrid": "^6.3.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user