Files
2026-07-13 12:11:06 +08:00

187 lines
4.8 KiB
TypeScript

/*
* Copyright 2016 balena.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.
*/
import type { Drive as DrivelistDrive } from 'drivelist';
import type * as sdk from 'etcher-sdk';
import type { Dictionary } from 'lodash';
import * as errors from '../../../shared/errors';
import type { SourceMetadata } from '../../../shared/typings/source-selector';
import * as flashState from '../models/flash-state';
import * as settings from '../models/settings';
import * as windowProgress from '../os/window-progress';
import { spawnChildAndConnect } from './api';
let cancelEmitter: (type: string) => void | undefined;
interface FlashResults {
skip?: boolean;
cancelled?: boolean;
results?: {
bytesWritten: number;
devices: {
failed: number;
successful: number;
};
errors: Error[];
};
}
async function performWrite(
image: SourceMetadata,
drives: DrivelistDrive[],
onProgress: sdk.multiWrite.OnProgressFunction,
): Promise<{ cancelled?: boolean }> {
const { autoBlockmapping, decompressFirst } = await settings.getAll();
// Spawn the child process with privileges and wait for the connection to be made
const { emit, registerHandler } = await spawnChildAndConnect({
withPrivileges: true,
});
return await new Promise((resolve, reject) => {
// if the connection failed, reject the promise
const flashResults: FlashResults = {};
const onFail = ({ device, error }: { device: any; error: any }) => {
console.log('fail event');
console.log(device);
console.log(error);
if (device.devicePath) {
flashState.addFailedDeviceError({ device, error });
}
finish();
};
const onDone = (payload: any) => {
console.log('CHILD: flash done', payload);
payload.results.errors = payload.results.errors.map(
(data: Dictionary<any> & { message: string }) => {
return errors.fromJSON(data);
},
);
flashResults.results = payload.results;
finish();
};
const onAbort = () => {
console.log('CHILD: flash aborted');
flashResults.cancelled = true;
finish();
};
const onSkip = () => {
console.log('CHILD: validation skipped');
flashResults.skip = true;
finish();
};
const finish = () => {
console.log('Flash results', flashResults);
// The flash wasn't cancelled and we didn't get a 'done' event
// Catch unexpected situation
if (
!flashResults.cancelled &&
!flashResults.skip &&
flashResults.results === undefined
) {
console.log(flashResults);
reject(
errors.createUserError({
title: 'The writer process ended unexpectedly',
description:
'Please try again, and contact the Etcher team if the problem persists',
}),
);
}
resolve(flashResults);
};
registerHandler('state', onProgress);
registerHandler('fail', onFail);
registerHandler('done', onDone);
registerHandler('abort', onAbort);
registerHandler('skip', onSkip);
cancelEmitter = (cancelStatus: string) => emit('cancel', cancelStatus);
// Now that we know we're connected we can instruct the child process to start the write
const parameters = {
image,
destinations: drives,
SourceType: image.SourceType,
autoBlockmapping,
decompressFirst,
};
console.log('params', parameters);
emit('write', parameters);
});
// The process continue in the event handler
}
/**
* @summary Flash an image to drives
*/
export async function flash(
image: SourceMetadata,
drives: DrivelistDrive[],
// This function is a parameter so it can be mocked in tests
write = performWrite,
): Promise<void> {
if (flashState.isFlashing()) {
throw new Error('There is already a flash in progress');
}
await flashState.setFlashingFlag();
flashState.setDevicePaths(
drives.map((d) => d.devicePath).filter((p) => p != null) as string[],
);
// start api and call the flasher
try {
const result = await write(image, drives, flashState.setProgressState);
console.log('got results', result);
await flashState.unsetFlashingFlag(result);
console.log('removed flashing flag');
} catch (error: any) {
await flashState.unsetFlashingFlag({
cancelled: false,
errorCode: error.code,
});
windowProgress.clear();
throw error;
}
windowProgress.clear();
}
/**
* @summary Cancel write operation
* //TODO: find a better solution to handle cancellation
*/
export async function cancel(type: string) {
const status = type.toLowerCase();
if (cancelEmitter) {
cancelEmitter(status);
}
}