mirror of
https://github.com/balena-io/etcher.git
synced 2025-11-16 13:49:27 +00:00
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.
213 lines
5.5 KiB
JavaScript
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', {})
|
|
})
|
|
})
|