Files
etcher/lib/gui/modules/child-writer.js
Benedict Aas 674019ea75 feat(GUI): add button to cancel flash process (#2301)
We add a cancel button next to the flash progress bar that gracefully
aborts the flash process.

Closes: https://github.com/resin-io/etcher/issues/1791
Closes: https://github.com/resin-io/etcher/issues/2234
Closes: https://github.com/resin-io/etcher/issues/2245
Change-Type: patch
Changelog-Entry: Add a button to cancel the flash process.
2018-05-03 10:58:15 +01:00

213 lines
5.5 KiB
JavaScript

/*
* Copyright 2017 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'
const _ = require('lodash')
const ipc = require('node-ipc')
const EXIT_CODES = require('../../shared/exit-codes')
const errors = require('../../shared/errors')
const ImageWriter = require('../../sdk/writer')
ipc.config.id = process.env.IPC_CLIENT_ID
ipc.config.socketRoot = process.env.IPC_SOCKET_ROOT
// NOTE: Ensure this isn't disabled, as it will cause
// the stdout maxBuffer size to be exceeded when flashing
ipc.config.silent = true
// > If set to 0, the client will NOT try to reconnect.
// See https://github.com/RIAEvangelist/node-ipc/
//
// The purpose behind this change is for this process
// to emit a "disconnect" event as soon as the GUI
// process is closed, so we can kill this process as well.
ipc.config.stopRetrying = 0
const IPC_SERVER_ID = process.env.IPC_SERVER_ID
/**
* @summary Send a log debug message to the IPC server
* @function
* @private
*
* @param {String} message - message
*
* @example
* log('Hello world!')
*/
const log = (message) => {
ipc.of[IPC_SERVER_ID].emit('log', message)
}
/**
* @summary Terminate the child writer process
* @function
* @private
*
* @param {Number} [code=0] - exit code
*
* @example
* terminate(1)
*/
const terminate = (code) => {
ipc.disconnect(IPC_SERVER_ID)
process.nextTick(() => {
process.exit(code || EXIT_CODES.SUCCESS)
})
}
/**
* @summary Handle a child writer error
* @function
* @private
*
* @param {Error} error - error
*
* @example
* handleError(new Error('Something bad happened!'))
*/
const handleError = (error) => {
ipc.of[IPC_SERVER_ID].emit('error', errors.toJSON(error))
terminate(EXIT_CODES.GENERAL_ERROR)
}
ipc.connectTo(IPC_SERVER_ID, () => {
process.once('uncaughtException', handleError)
// Gracefully exit on the following cases. If the parent
// process detects that child exit successfully but
// no flashing information is available, then it will
// assume that the child died halfway through.
process.once('SIGINT', () => {
terminate(EXIT_CODES.SUCCESS)
})
process.once('SIGTERM', () => {
terminate(EXIT_CODES.SUCCESS)
})
// The IPC server failed. Abort.
ipc.of[IPC_SERVER_ID].on('error', () => {
terminate(EXIT_CODES.SUCCESS)
})
// The IPC server was disconnected. Abort.
ipc.of[IPC_SERVER_ID].on('disconnect', () => {
terminate(EXIT_CODES.SUCCESS)
})
let writer = null
ipc.of[IPC_SERVER_ID].on('write', (options) => {
const destinations = [].concat(options.destinations)
log(`Image: ${options.imagePath}`)
log(`Devices: ${destinations.join(', ')}`)
log(`Umount on success: ${options.unmountOnSuccess}`)
log(`Validate on success: ${options.validateWriteOnSuccess}`)
let exitCode = EXIT_CODES.SUCCESS
/**
* @summary Progress handler
* @param {Object} state - progress state
* @example
* writer.on('progress', onProgress)
*/
const onProgress = (state) => {
ipc.of[IPC_SERVER_ID].emit('state', state)
}
/**
* @summary Finish handler
* @param {Object} results - Flash results
* @example
* writer.on('finish', onFinish)
*/
const onFinish = (results) => {
log(`Finish: ${results.bytesWritten}`)
results.errors = _.map(results.errors, (error) => {
return errors.toJSON(error)
})
ipc.of[IPC_SERVER_ID].emit('done', { results })
terminate(exitCode)
}
/**
* @summary Abort handler
* @example
* writer.on('abort', onAbort)
*/
const onAbort = () => {
log('Abort')
ipc.of[IPC_SERVER_ID].emit('abort')
terminate(exitCode)
}
/**
* @summary Error handler
* @param {Error} error - error
* @example
* writer.on('error', onError)
*/
const onError = (error) => {
log(`Error: ${error.message}`)
exitCode = EXIT_CODES.GENERAL_ERROR
ipc.of[IPC_SERVER_ID].emit('error', errors.toJSON(error))
}
/**
* @summary Failure handler (non-fatal errors)
* @param {Object} event - event data (error & device)
* @example
* writer.on('fail', onFail)
*/
const onFail = (event) => {
ipc.of[IPC_SERVER_ID].emit('fail', {
device: event.device,
error: errors.toJSON(event.error)
})
}
writer = new ImageWriter({
verify: options.validateWriteOnSuccess,
unmountOnSuccess: options.unmountOnSuccess,
checksumAlgorithms: options.checksumAlgorithms || []
})
writer.on('error', onError)
writer.on('fail', onFail)
writer.on('progress', onProgress)
writer.on('finish', onFinish)
writer.on('abort', onAbort)
writer.write(options.imagePath, destinations)
})
ipc.of[IPC_SERVER_ID].on('cancel', () => {
if (writer) {
writer.abort()
}
})
ipc.of[IPC_SERVER_ID].on('connect', () => {
log(`Successfully connected to IPC server: ${IPC_SERVER_ID}, socket root ${ipc.config.socketRoot}`)
ipc.of[IPC_SERVER_ID].emit('ready', {})
})
})