Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CP-3170] Handle device detach during pre-terminal stages of reflashing #2122

Merged
merged 9 commits into from
Oct 17, 2024
16 changes: 12 additions & 4 deletions libs/core/core/hooks/use-abort-flashing-on-device-detached.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,32 @@
*/

import { useEffect } from "react"
import { useDispatch } from "react-redux"
import { useDispatch, useSelector } from "react-redux"
import { answerMain } from "shared/utils"
import {
DeviceBaseProperties,
DeviceProtocolMainEvent,
} from "device-protocol/models"
import { abortMscFlashing } from "msc-flash-harmony"
import {
abortMscFlashing,
FlashingProcessState,
selectIsFlashingInActivePhases,
} from "msc-flash-harmony"
import { Dispatch } from "Core/__deprecated__/renderer/store"

export const useAbortFlashingOnDeviceDetached = () => {
const dispatch = useDispatch<Dispatch>()
const flashingInActivePhases = useSelector(selectIsFlashingInActivePhases)

useEffect(() => {
return answerMain<DeviceBaseProperties>(
DeviceProtocolMainEvent.DeviceDetached,
() => {
dispatch(abortMscFlashing())
const reason = flashingInActivePhases
? FlashingProcessState.Failed
: undefined
dispatch(abortMscFlashing({ reason }))
}
)
}, [dispatch])
}, [dispatch, flashingInActivePhases])
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import React from "react"
import { useSelector } from "react-redux"
import { FlashingErrorModal } from "msc-flash-harmony"
import { FunctionComponent } from "Core/core/types/function-component.interface"
import ContactSupportFlow from "Core/contact-support/containers/contact-support-flow.container"
import { UpdateOsInterruptedFlowContainer } from "Core/update/components/update-os-interrupted-flow"
Expand All @@ -25,6 +26,7 @@ const ModalsManager: FunctionComponent = () => {
<UpdateOsInterruptedFlowContainer />
<ConnectingLoaderModal />
<DetachedDuringUploadErrorModal />
<FlashingErrorModal />
{appUpdateVisible && <AppUpdateFlow />}
</>
)
Expand Down
1 change: 1 addition & 0 deletions libs/msc-flash/msc-flash-harmony/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export * from "./lib/actions"
export * from "./lib/constants"
export * from "./lib/dto"
export * from "./lib/reducers"
export * from "./lib/selectors"
export * from "./lib/services"
export * from "./lib/ui"
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ReduxRootState } from "Core/__deprecated__/renderer/store"
import { FlashingProcessState } from "../constants"
import { selectFlashingAbortController } from "../selectors"
import { setFlashingProcessState } from "./set-flashing-process-state.action"
import { setMscFlashingAbort } from "./actions"

export const abortMscFlashing = createAsyncThunk<
void,
Expand All @@ -20,6 +21,7 @@ export const abortMscFlashing = createAsyncThunk<
const abortController = selectFlashingAbortController(getState())

abortController?.abort?.()
dispatch(setMscFlashingAbort(undefined))

if (reason) {
dispatch(setFlashingProcessState(reason))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@ import { MscFlashDetailsService } from "../services/msc-flash-details.service"

export const getMscFlashingFilesDetails = createAsyncThunk<
MscFlashDetails,
{ product: Product; environment: OsEnvironment; platform: SupportedPlatform },
{
product: Product
environment: OsEnvironment
platform: SupportedPlatform
signal?: AbortSignal
},
{ state: ReduxRootState }
>(
ActionName.MscFlashingGetFilesDetails,
async ({ product, environment, platform }, { rejectWithValue }) => {
async ({ product, environment, platform, signal }, { rejectWithValue }) => {
try {
const filesDetails = await MscFlashDetailsService.getMscFlashDetails(
product,
environment,
platform
platform,
signal
)

return filesDetails
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,37 @@
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/

import { ipcRenderer } from "electron-better-ipc"
import { Result, ResultObject } from "Core/core/builder"
import { AppError } from "Core/core/errors"
import { PureOsDownloadChannels } from "Core/__deprecated__/main/functions/register-pure-os-download-listener"
import {
DownloadFinished,
DownloadStatus,
} from "Core/__deprecated__/renderer/interfaces/file-download.interface"
import { ipcRenderer } from "electron-better-ipc"

// AUTO DISABLED - fix me if you like :)
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const cancelFileDownload = (interrupt = false) => {
ipcRenderer.send(PureOsDownloadChannels.cancel, interrupt)
}

export const downloadFlashingFileRequest = async (props: {
url: string
fileName: string
}): Promise<
export const downloadFlashingFileRequest = async (
props: {
url: string
fileName: string
},
signal: AbortSignal
): Promise<
| ResultObject<DownloadFinished>
| ResultObject<DownloadStatus.Cancelled | DownloadStatus.Interrupted>
> => {
const abortHandler = () => {
signal.removeEventListener("abort", abortHandler)
cancelFileDownload(true)
}
signal.addEventListener("abort", abortHandler)

const data: DownloadFinished = await ipcRenderer.callMain(
PureOsDownloadChannels.start,
props
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
export * from "./select-flashing-state.selector"
export * from "./select-flashing-abort-controller"
export * from "./select-flashing-process-state"
export * from "./select-is-flashing-in-active-phases"
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) Mudita sp. z o.o. All rights reserved.
* For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md
*/

import { createSelector } from "@reduxjs/toolkit"
import { FlashingProcessState } from "../constants"
import { selectFlashingProcessState } from "./select-flashing-process-state"

const flashingActivePhases: FlashingProcessState[] = [
FlashingProcessState.GettingFilesDetails,
FlashingProcessState.DownloadingFiles,
FlashingProcessState.UnpackingFiles,
FlashingProcessState.FlashingProcess,
FlashingProcessState.TerminalOpened,
]

export const selectIsFlashingInActivePhases = createSelector(
selectFlashingProcessState,
(flashingProcessState) => flashingActivePhases.includes(flashingProcessState)
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,54 @@ import { RELEASE_SPACE } from "Core/update/constants/release-space.constant"
import MacDeviceFlashService from "./device-flash/macos/macos-device-flash-service"
import { removeDownloadedMscFiles } from "./remove-downloaded-msc-files.service"
import { setMscFlashingAbort } from "../actions/actions"
import { selectFlashingProcessState } from "../selectors"
import { selectIsFlashingInActivePhases } from "../selectors"
import logger from "Core/__deprecated__/main/utils/logger"

export const flashMscDeviceService =
() => async (dispatch: Dispatch, getState: () => ReduxRootState) => {
try {
await getFlashingImageDetails(dispatch)
const abortController = new AbortController()
dispatch(setMscFlashingAbort(abortController))

await getFlashingImageDetails(dispatch, abortController.signal)

const mscFlashingFiles = getState().flashing.mscFlashDetails

if (mscFlashingFiles) {
await downloadFlashingFiles(dispatch, mscFlashingFiles)
await unpackFlashingImage(dispatch, mscFlashingFiles)
await startFlashingProcess(dispatch, mscFlashingFiles)
await downloadFlashingFiles(
dispatch,
mscFlashingFiles,
abortController.signal
)
await unpackFlashingImage(
dispatch,
mscFlashingFiles,
abortController.signal
)
await startFlashingProcess(
dispatch,
mscFlashingFiles,
abortController.signal
)
}
} catch (error) {
await removeDownloadedMscFiles()

const processState = selectFlashingProcessState(getState())
if (processState !== FlashingProcessState.Canceled) {
console.error("Error during flashing process:", error)
const flashingInActivePhases = selectIsFlashingInActivePhases(getState())
if (flashingInActivePhases) {
logger.error("Error during flashing process:", error)
dispatch(setFlashingProcessState(FlashingProcessState.Failed))
}
}
}

const getFlashingImageDetails = async (dispatch: Dispatch) => {
const getFlashingImageDetails = async (
dispatch: Dispatch,
signal: AbortSignal
) => {
if (signal.aborted) {
return
}
dispatch(setFlashingProcessState(FlashingProcessState.GettingFilesDetails))

let platform: SupportedPlatform
Expand All @@ -61,6 +83,7 @@ const getFlashingImageDetails = async (dispatch: Dispatch) => {

await dispatch(
getMscFlashingFilesDetails({
signal,
product: Product.MscHarmony,
environment: RELEASE_SPACE,
platform: platform,
Expand All @@ -70,15 +93,22 @@ const getFlashingImageDetails = async (dispatch: Dispatch) => {

const downloadFlashingFiles = async (
dispatch: Dispatch,
mscFlashingFiles: MscFlashDetails
mscFlashingFiles: MscFlashDetails,
signal: AbortSignal
) => {
if (signal.aborted) {
return
}
dispatch(setFlashingProcessState(FlashingProcessState.DownloadingFiles))

for (const file of [mscFlashingFiles.image, ...mscFlashingFiles.scripts]) {
const downloadResult = await downloadFlashingFileRequest({
url: file.url,
fileName: file.name,
})
const downloadResult = await downloadFlashingFileRequest(
{
url: file.url,
fileName: file.name,
},
signal
)
mkurczewski marked this conversation as resolved.
Show resolved Hide resolved
if (!downloadResult.ok) {
throw new Error(`Failed to download file: ${file.name}`)
}
Expand All @@ -87,8 +117,12 @@ const downloadFlashingFiles = async (

const unpackFlashingImage = async (
dispatch: Dispatch,
mscFlashingFiles: MscFlashDetails | undefined
mscFlashingFiles: MscFlashDetails | undefined,
signal: AbortSignal
) => {
if (signal.aborted) {
return
}
dispatch(setFlashingProcessState(FlashingProcessState.UnpackingFiles))

const flashingImageName = mscFlashingFiles ? mscFlashingFiles.image.name : ""
Expand All @@ -98,11 +132,19 @@ const unpackFlashingImage = async (

const startFlashingProcess = async (
dispatch: Dispatch,
flashingFiles: MscFlashDetails | undefined
flashingFiles: MscFlashDetails | undefined,
signal: AbortSignal
) => {
try {
if (signal.aborted) {
return
}
mkurczewski marked this conversation as resolved.
Show resolved Hide resolved

dispatch(setFlashingProcessState(FlashingProcessState.FlashingProcess))
const { osDownloadLocation } = await getAppSettingsMain()
if (signal.aborted) {
return
}

const deviceFlash =
DeviceFlashFactory.createDeviceFlashService(osDownloadLocation)
Expand All @@ -117,19 +159,19 @@ const startFlashingProcess = async (
const scriptFilePath = path.join(osDownloadLocation, flashingScriptName)

await deviceFlash.execute(device, imageFilePath, scriptFilePath)
if (signal.aborted) {
return
}

if (deviceFlash instanceof MacDeviceFlashService) {
dispatch(setFlashingProcessState(FlashingProcessState.TerminalOpened))

const abortController = new AbortController()

dispatch(setMscFlashingAbort(abortController))

await deviceFlash.waitForFlashCompletion({
signal: abortController.signal,
})
await deviceFlash.waitForFlashCompletion({ signal })
}

if (signal.aborted) {
return
}
dispatch(setFlashingProcessState(FlashingProcessState.Restarting))

await removeDownloadedMscFiles()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ export class MscFlashDetailsService {
static async getMscFlashDetails(
product: Product,
environment: OsEnvironment,
platform: SupportedPlatform
platform: SupportedPlatform,
signal?: AbortSignal
): Promise<MscFlashDetails> {
const response = await axios.get<MscFlashDetails>(
`${API_BASE_URL}/${MuditaCenterServerRoutes.GetMscFlashDetails}`,
{
params: { product, environment, platform },
signal,
}
)

Expand Down
Loading
Loading