From 8314fad50140ba39b15163ebb049b952439fc1a0 Mon Sep 17 00:00:00 2001 From: Daniel Karski Date: Tue, 15 Oct 2024 09:32:25 +0200 Subject: [PATCH 1/5] [CP-3185] Minor refactor of device-flash --- .../src/lib/selectors/index.ts | 2 +- .../select-flashing-abort-controller.ts | 2 +- .../select-flashing-process-state.ts | 2 +- ...e.selector.ts => select-flashing-state.ts} | 0 .../linux-device-flash.service.ts | 88 ------------------- .../linux/linux-device-flash.service.ts | 20 ++--- .../lib/services/flash-msc-device.service.ts | 2 +- .../restarting-device-modal.component.tsx | 14 +-- libs/shared/utils/src/index.ts | 1 + .../utils/src/lib/exec-command-with-sudo.ts | 2 +- .../split-path-to-dir-name-and-base-name.ts | 14 +++ 11 files changed, 37 insertions(+), 110 deletions(-) rename libs/msc-flash/msc-flash-harmony/src/lib/selectors/{select-flashing-state.selector.ts => select-flashing-state.ts} (100%) delete mode 100644 libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/linux-device-flash.service.ts create mode 100644 libs/shared/utils/src/lib/split-path-to-dir-name-and-base-name.ts diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/selectors/index.ts b/libs/msc-flash/msc-flash-harmony/src/lib/selectors/index.ts index 67ac48c574..313272bf24 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/selectors/index.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/selectors/index.ts @@ -3,7 +3,7 @@ * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md */ -export * from "./select-flashing-state.selector" +export * from "./select-flashing-state" export * from "./select-flashing-abort-controller" export * from "./select-flashing-process-state" export * from "./select-is-flashing-in-active-phases" diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-abort-controller.ts b/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-abort-controller.ts index ffd1307724..5b585cf88b 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-abort-controller.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-abort-controller.ts @@ -4,7 +4,7 @@ */ import { createSelector } from "@reduxjs/toolkit" -import { flashingState } from "./select-flashing-state.selector" +import { flashingState } from "./select-flashing-state" export const selectFlashingAbortController = createSelector( flashingState, diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-process-state.ts b/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-process-state.ts index 7bbba9d7f7..297389ddf5 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-process-state.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-process-state.ts @@ -4,7 +4,7 @@ */ import { createSelector } from "@reduxjs/toolkit" -import { flashingState } from "./select-flashing-state.selector" +import { flashingState } from "./select-flashing-state" export const selectFlashingProcessState = createSelector( flashingState, diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-state.selector.ts b/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-state.ts similarity index 100% rename from libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-state.selector.ts rename to libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-flashing-state.ts diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/linux-device-flash.service.ts b/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/linux-device-flash.service.ts deleted file mode 100644 index 6ab60d2401..0000000000 --- a/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/linux-device-flash.service.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) Mudita sp. z o.o. All rights reserved. - * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md - */ - -import path from "path" -import { execPromise, execCommandWithSudo } from "shared/utils" -import IDeviceFlash from "./device-flash.interface" -import LinuxPartitionParser from "./linux-partition-parser" - -class LinuxDeviceFlashService implements IDeviceFlash { - async findDeviceByDeviceName(deviceName: string): Promise { - console.log(`Searching for device with model name: ${deviceName}`) - const devices = await this.getDevices() - - for (const device of devices) { - if (device.includes(deviceName)) { - return device.split(" ")[0] - } - } - console.error(`${deviceName} device model not found`) - process.exit(1) - } - - async execute( - device: string, - imagePath: string, - scriptPath: string - ): Promise { - const unmountDeviceCommand = await this.getUnmountDeviceCommand(device) - const flashImageToDeviceCommand = await this.getFlashImageToDeviceCommand( - device, - imagePath, - scriptPath - ) - const ejectDeviceCommand = await this.getEjectDeviceCommand(device) - - const command = `${unmountDeviceCommand} && ${flashImageToDeviceCommand} && ${ejectDeviceCommand}` - await execCommandWithSudo(command, { name: "Mudita Auto Flash" }) - - console.log("Flash process completed successfully") - } - - private async getUnmountDeviceCommand(device: string): Promise { - const partitions = await this.getPartitions(device) - const partitionsString = partitions - .map((partition) => `/dev/${partition}`) - .join(" ") - - return `umount ${partitionsString} 2>/dev/null || true` - } - - private async getFlashImageToDeviceCommand( - device: string, - imagePath: string, - scriptPath: string - ): Promise { - const [path, scriptBasename] = - this.splitPathToDirnameAndBasename(scriptPath) - const [, imageBasename] = this.splitPathToDirnameAndBasename(imagePath) - return `chmod +x ${scriptPath} && cd ${path} && ./${scriptBasename} ${imageBasename} /dev/${device}` - } - - private async getEjectDeviceCommand(device: string): Promise { - return `eject /dev/${device} 2>/dev/null || true` - } - - private async getDevices(): Promise { - const devices = await execPromise("lsblk -o NAME,MODEL") - - return devices?.split("\n") ?? [] - } - - private async getPartitions(device: string): Promise { - const partitions = await execPromise( - `lsblk /dev/${device} -o NAME,MOUNTPOINT` - ) - return LinuxPartitionParser.parsePartitions(partitions ?? "") - } - - private splitPathToDirnameAndBasename(currentPath: string) { - const dirname = path.dirname(currentPath) - const basename = path.basename(currentPath) - return [dirname, basename] - } -} - -export default LinuxDeviceFlashService diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/linux/linux-device-flash.service.ts b/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/linux/linux-device-flash.service.ts index 029b04376c..fdc1609a28 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/linux/linux-device-flash.service.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/linux/linux-device-flash.service.ts @@ -3,8 +3,11 @@ * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md */ -import path from "path" -import { execPromise, execCommandWithSudo } from "shared/utils" +import { + execPromise, + execCommandWithSudo, + splitPathToDirNameAndBaseName, +} from "shared/utils" import IDeviceFlash from "../device-flash.interface" import LinuxPartitionParser from "./linux-partition-parser" @@ -36,7 +39,7 @@ class LinuxDeviceFlashService implements IDeviceFlash { const ejectDeviceCommand = await this.getEjectDeviceCommand(device) const command = `${unmountDeviceCommand} && ${flashImageToDeviceCommand} && ${ejectDeviceCommand}` - await execCommandWithSudo(command, { name: "Mudita Auto Flash" }) + await execCommandWithSudo(command) console.log("Flash process completed successfully") } @@ -55,9 +58,8 @@ class LinuxDeviceFlashService implements IDeviceFlash { imagePath: string, scriptPath: string ): Promise { - const [path, scriptBasename] = - this.splitPathToDirnameAndBasename(scriptPath) - const [, imageBasename] = this.splitPathToDirnameAndBasename(imagePath) + const [path, scriptBasename] = splitPathToDirNameAndBaseName(scriptPath) + const [, imageBasename] = splitPathToDirNameAndBaseName(imagePath) return `chmod +x ${scriptPath} && cd ${path} && ./${scriptBasename} ${imageBasename} /dev/${device}` } @@ -77,12 +79,6 @@ class LinuxDeviceFlashService implements IDeviceFlash { ) return LinuxPartitionParser.parsePartitions(partitions ?? "") } - - private splitPathToDirnameAndBasename(currentPath: string) { - const dirname = path.dirname(currentPath) - const basename = path.basename(currentPath) - return [dirname, basename] - } } export default LinuxDeviceFlashService diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/services/flash-msc-device.service.ts b/libs/msc-flash/msc-flash-harmony/src/lib/services/flash-msc-device.service.ts index e298bcd136..fbe6e7626a 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/services/flash-msc-device.service.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/services/flash-msc-device.service.ts @@ -84,9 +84,9 @@ const getFlashingImageDetails = async ( await dispatch( getMscFlashingFilesDetails({ signal, + platform, product: Product.MscHarmony, environment: RELEASE_SPACE, - platform: platform, }) ) } diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/ui/restarting-device-modal/restarting-device-modal.component.tsx b/libs/msc-flash/msc-flash-harmony/src/lib/ui/restarting-device-modal/restarting-device-modal.component.tsx index 6ba8b4c98d..76912ce1a4 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/ui/restarting-device-modal/restarting-device-modal.component.tsx +++ b/libs/msc-flash/msc-flash-harmony/src/lib/ui/restarting-device-modal/restarting-device-modal.component.tsx @@ -35,12 +35,16 @@ export const RestartingDeviceModal: FunctionComponent< const dispatch = useDispatch() useEffect(() => { - const timer = setTimeout(() => { - dispatch(setFlashingProcessState(FlashingProcessState.Failed)) - }, 2 * 60 * 1000) + let timeoutId: NodeJS.Timeout - return () => clearTimeout(timer) - }, [dispatch]) + if (open) { + timeoutId = setTimeout(() => { + dispatch(setFlashingProcessState(FlashingProcessState.Failed)) + }, 2 * 60 * 1000) + } + + return () => clearTimeout(timeoutId) + }, [dispatch, open]) return ( => { return new Promise((resolve, reject) => { sudoPrompt.exec(command, options, (error, stdout, stderr) => { diff --git a/libs/shared/utils/src/lib/split-path-to-dir-name-and-base-name.ts b/libs/shared/utils/src/lib/split-path-to-dir-name-and-base-name.ts new file mode 100644 index 0000000000..6c0a7df413 --- /dev/null +++ b/libs/shared/utils/src/lib/split-path-to-dir-name-and-base-name.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import path from "path" + +export const splitPathToDirNameAndBaseName = ( + currentPath: string +): [string, string] => { + const dirName = path.dirname(currentPath) + const baseName = path.basename(currentPath) + return [dirName, baseName] +} From ea43f5742cdd713339950888ffa25c669cca3bd9 Mon Sep 17 00:00:00 2001 From: Daniel Karski Date: Tue, 15 Oct 2024 09:41:53 +0200 Subject: [PATCH 2/5] [CP-3170] useAbortFlashingOnDeviceDetached -> useMscDeviceDetachedEffect (with debounce logic) --- .../apps/base-app/base-app.component.tsx | 4 +- .../use-abort-flashing-on-device-detached.ts | 35 ------------ .../hooks/use-msc-device-detached-effect.ts | 55 +++++++++++++++++++ 3 files changed, 57 insertions(+), 37 deletions(-) delete mode 100644 libs/core/core/hooks/use-abort-flashing-on-device-detached.ts create mode 100644 libs/core/core/hooks/use-msc-device-detached-effect.ts diff --git a/libs/core/core/components/apps/base-app/base-app.component.tsx b/libs/core/core/components/apps/base-app/base-app.component.tsx index 2d7468759f..84910a147f 100644 --- a/libs/core/core/components/apps/base-app/base-app.component.tsx +++ b/libs/core/core/components/apps/base-app/base-app.component.tsx @@ -16,7 +16,7 @@ import { useDeviceLockedEffect } from "Core/core/hooks/use-device-locked-effect" import { useDeviceDetachedEffect } from "Core/core/hooks/use-device-detached-effect" import { useDeviceConnectFailedEffect } from "Core/core/hooks/use-device-connect-failed-effect" import { useDiscoveryRedirectEffect } from "Core/core/hooks/use-discovery-redirect-effect" -import { useAbortFlashingOnDeviceDetached } from "Core/core/hooks/use-abort-flashing-on-device-detached" +import { useMscDeviceDetachedEffect } from "Core/core/hooks/use-msc-device-detached-effect" import { useRouterListener } from "Core/core/hooks" import { OutboxWrapper, @@ -48,7 +48,7 @@ const BaseApp: FunctionComponent = () => { useHelp() // MSC - useAbortFlashingOnDeviceDetached() + useMscDeviceDetachedEffect() return ( <> diff --git a/libs/core/core/hooks/use-abort-flashing-on-device-detached.ts b/libs/core/core/hooks/use-abort-flashing-on-device-detached.ts deleted file mode 100644 index 9acc54659a..0000000000 --- a/libs/core/core/hooks/use-abort-flashing-on-device-detached.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) Mudita sp. z o.o. All rights reserved. - * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md - */ - -import { useEffect } from "react" -import { useDispatch, useSelector } from "react-redux" -import { answerMain } from "shared/utils" -import { - DeviceBaseProperties, - DeviceProtocolMainEvent, -} from "device-protocol/models" -import { - abortMscFlashing, - FlashingProcessState, - selectIsFlashingInActivePhases, -} from "msc-flash-harmony" -import { Dispatch } from "Core/__deprecated__/renderer/store" - -export const useAbortFlashingOnDeviceDetached = () => { - const dispatch = useDispatch() - const flashingInActivePhases = useSelector(selectIsFlashingInActivePhases) - - useEffect(() => { - return answerMain( - DeviceProtocolMainEvent.DeviceDetached, - () => { - const reason = flashingInActivePhases - ? FlashingProcessState.Failed - : undefined - dispatch(abortMscFlashing({ reason })) - } - ) - }, [dispatch, flashingInActivePhases]) -} diff --git a/libs/core/core/hooks/use-msc-device-detached-effect.ts b/libs/core/core/hooks/use-msc-device-detached-effect.ts new file mode 100644 index 0000000000..0b9d38e3e6 --- /dev/null +++ b/libs/core/core/hooks/use-msc-device-detached-effect.ts @@ -0,0 +1,55 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import { useCallback, useEffect } from "react" +import { useDispatch, useSelector } from "react-redux" +import { answerMain, useDebouncedEventsHandler } from "shared/utils" +import { + DeviceBaseProperties, + DeviceProtocolMainEvent, + DeviceType, +} from "device-protocol/models" +import { + abortMscFlashing, + FlashingProcessState, + selectIsFlashingInActivePhases, +} from "msc-flash-harmony" +import { Dispatch } from "Core/__deprecated__/renderer/store" + +export const useMscDeviceDetachedEffect = () => { + const handleDevicesDetached = useHandleDevicesDetached() + + const batchDeviceDetachedEvents = + useDebouncedEventsHandler(handleDevicesDetached) + + useEffect(() => { + return answerMain( + DeviceProtocolMainEvent.DeviceDetached, + batchDeviceDetachedEvents + ) + }, [batchDeviceDetachedEvents]) +} + +const useHandleDevicesDetached = () => { + const dispatch = useDispatch() + const flashingInActivePhases = useSelector(selectIsFlashingInActivePhases) + return useCallback( + (deviceDetachedEvents: DeviceBaseProperties[]) => { + const mscEvents = deviceDetachedEvents.filter( + ({ deviceType }) => deviceType === DeviceType.MuditaHarmonyMsc + ) + + if (mscEvents.length === 0) { + return + } + + const reason = flashingInActivePhases + ? FlashingProcessState.Failed + : undefined + dispatch(abortMscFlashing({ reason })) + }, + [dispatch, flashingInActivePhases] + ) +} From 1fdedd403f507b2c5deaddb06b259fb068ba572c Mon Sep 17 00:00:00 2001 From: Daniel Karski Date: Tue, 15 Oct 2024 10:26:43 +0200 Subject: [PATCH 3/5] [CP-3184] Implement UI modal requesting the eject operation on Windows --- .../components/core/icon/icon-type.ts | 1 + .../components/core/icon/icon.config.ts | 4 + .../renderer/locales/default/en-US.json | 2 + .../renderer/svg/back-arrow-icon.svg | 4 + .../flashing-process-state.constant.ts | 1 + .../src/lib/ui/recovery-mode.tsx | 12 ++- ...aiting-for-back-button-modal.component.tsx | 82 +++++++++++++++++++ 7 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 libs/core/__deprecated__/renderer/svg/back-arrow-icon.svg create mode 100644 libs/msc-flash/msc-flash-harmony/src/lib/ui/waiting-for-back-button-modal.component.tsx diff --git a/libs/core/__deprecated__/renderer/components/core/icon/icon-type.ts b/libs/core/__deprecated__/renderer/components/core/icon/icon-type.ts index ddd8ac4cc1..f6c47920e3 100644 --- a/libs/core/__deprecated__/renderer/components/core/icon/icon-type.ts +++ b/libs/core/__deprecated__/renderer/components/core/icon/icon-type.ts @@ -8,6 +8,7 @@ export enum IconType { ArrowLongLeft, ArrowLongRight, AttachContact, + BackArrowIcon, BackupFolder, Battery, BorderCheckIcon, diff --git a/libs/core/__deprecated__/renderer/components/core/icon/icon.config.ts b/libs/core/__deprecated__/renderer/components/core/icon/icon.config.ts index 866faf42c2..1921070d38 100644 --- a/libs/core/__deprecated__/renderer/components/core/icon/icon.config.ts +++ b/libs/core/__deprecated__/renderer/components/core/icon/icon.config.ts @@ -8,6 +8,7 @@ import Arrow from "Core/__deprecated__/renderer/svg/arrow.svg" import ArrowLongLeft from "Core/__deprecated__/renderer/svg/arrow-long-left.svg" import ArrowLongRight from "Core/__deprecated__/renderer/svg/arrow-long-right.svg" import AttachContact from "Core/__deprecated__/renderer/svg/attach-contact.svg" +import BackArrowIcon from "Core/__deprecated__/renderer/svg/back-arrow-icon.svg" import BackupFolder from "Core/__deprecated__/renderer/svg/backup-folder.svg" import Battery from "Core/__deprecated__/renderer/svg/battery.svg" import BorderCheck from "Core/__deprecated__/renderer/svg/border-check-icon.svg" @@ -147,6 +148,9 @@ const typeToIcon: Partial> = { [IconType.AttachContact]: AttachContact, // AUTO DISABLED - fix me if you like :) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + [IconType.BackArrowIcon]: BackArrowIcon, + // AUTO DISABLED - fix me if you like :) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment [IconType.BackupFolder]: BackupFolder, // AUTO DISABLED - fix me if you like :) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment diff --git a/libs/core/__deprecated__/renderer/locales/default/en-US.json b/libs/core/__deprecated__/renderer/locales/default/en-US.json index a1bbcf73bd..133d1caf22 100644 --- a/libs/core/__deprecated__/renderer/locales/default/en-US.json +++ b/libs/core/__deprecated__/renderer/locales/default/en-US.json @@ -1018,6 +1018,8 @@ "module.recoveryMode.modal.restarting.message": "Please do not disconnect your Harmony.", "module.recoveryMode.modal.error.subtitle": "Recovery failed", "module.recoveryMode.modal.error.message": "The process was interrupted. Please try again.", + "module.recoveryMode.modal.waitingForBackButton.subtitle": "Almost finished...", + "module.recoveryMode.modal.waitingForBackButton.description": "Press the back button on your device to complete the recovery process.", "module.recoveryMode.mocal.terminalInfo.subtitle": "Just a few more steps...", "module.recoveryMode.mocal.terminalInfo.description": "The Terminal was opened in the background", "module.recoveryMode.mocal.terminalInfo.step1": "1. Switch to the open terminal", diff --git a/libs/core/__deprecated__/renderer/svg/back-arrow-icon.svg b/libs/core/__deprecated__/renderer/svg/back-arrow-icon.svg new file mode 100644 index 0000000000..f4d4d9c916 --- /dev/null +++ b/libs/core/__deprecated__/renderer/svg/back-arrow-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/constants/flashing-process-state.constant.ts b/libs/msc-flash/msc-flash-harmony/src/lib/constants/flashing-process-state.constant.ts index 89cc1bd73f..0b43614583 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/constants/flashing-process-state.constant.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/constants/flashing-process-state.constant.ts @@ -10,6 +10,7 @@ export enum FlashingProcessState { UnpackingFiles = "unpacking-files", FlashingProcess = "flashing-process", TerminalOpened = "terminal-opened", + WaitingForBackButton = "waiting-for-back-button", Restarting = "restarting-device", Completed = "completed", Canceled = "canceled", diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/ui/recovery-mode.tsx b/libs/msc-flash/msc-flash-harmony/src/lib/ui/recovery-mode.tsx index f433412100..6720b93204 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/ui/recovery-mode.tsx +++ b/libs/msc-flash/msc-flash-harmony/src/lib/ui/recovery-mode.tsx @@ -28,6 +28,7 @@ import theme from "Core/core/styles/theming/theme" import { RestartingDeviceModal } from "./restarting-device-modal/restarting-device-modal.component" import { MacTerminalInfoModal } from "./mac-terminal-info-modal/mac-terminal-info-modal.component" import { abortMscFlashing } from "../actions" +import { WaitingForBackButtonModal } from "./waiting-for-back-button-modal.component" const messages = defineMessages({ header: { @@ -129,12 +130,15 @@ const RecoveryModeUI: FunctionComponent = () => { const isRestartingModalVisible = (): boolean => { return flashingProcessState === FlashingProcessState.Restarting } + const isWaitingForBackButtonVisible = (): boolean => { + return flashingProcessState === FlashingProcessState.WaitingForBackButton + } const isMacTerminalInfoModalVisible = (): boolean => { return flashingProcessState === FlashingProcessState.TerminalOpened } - const macTerminalInfoCloseHandler = (): void => { + const cancelMscFlashing = (): void => { dispatch(abortMscFlashing({ reason: FlashingProcessState.Canceled })) } @@ -208,7 +212,11 @@ const RecoveryModeUI: FunctionComponent = () => { + diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/ui/waiting-for-back-button-modal.component.tsx b/libs/msc-flash/msc-flash-harmony/src/lib/ui/waiting-for-back-button-modal.component.tsx new file mode 100644 index 0000000000..284aa7ea3d --- /dev/null +++ b/libs/msc-flash/msc-flash-harmony/src/lib/ui/waiting-for-back-button-modal.component.tsx @@ -0,0 +1,82 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import React from "react" +import { defineMessages } from "react-intl" +import styled from "styled-components" +import { ModalContent, ModalDialog, RoundIconWrapper } from "Core/ui" +import Icon from "Core/__deprecated__/renderer/components/core/icon/icon.component" +import { FunctionComponent } from "Core/core/types/function-component.interface" +import { intl } from "Core/__deprecated__/renderer/utils/intl" +import { ModalSize } from "Core/__deprecated__/renderer/components/core/modal/modal.interface" +import { IconType } from "Core/__deprecated__/renderer/components/core/icon/icon-type" +import Text, { + TextDisplayStyle, +} from "Core/__deprecated__/renderer/components/core/text/text.component" + +interface CompletedInfoModalProps { + open: boolean + onClose: () => void +} + +const messages = defineMessages({ + waitingForBackButtonModalTitle: { + id: "module.recoveryMode.harmony.title", + }, + waitingForBackButtonModalSubtitle: { + id: "module.recoveryMode.modal.waitingForBackButton.subtitle", + }, + waitingForBackButtonModalDescription: { + id: "module.recoveryMode.modal.waitingForBackButton.description", + }, +}) + +export const WaitingForBackButtonModal: FunctionComponent< + CompletedInfoModalProps +> = ({ open, onClose }) => { + return ( + <> + + + + + + + + + + + + + + + + ) +} + +const Descrption = styled.div` + margin-top: 0.8rem; +` + +export const BackArrowIconWrapper = styled.div` + display: flex; + align-items: center; + justify-content: center; + margin-top: 1.4rem; +` From c90eaefefd8c09dccce3ba28c51e107d5cc060ca Mon Sep 17 00:00:00 2001 From: Daniel Karski Date: Wed, 16 Oct 2024 10:19:46 +0200 Subject: [PATCH 4/5] [CP-3185] Integrate the flashing mechanism with the application on Windows (#2126) --- .../hooks/use-msc-device-detached-effect.ts | 13 +++- .../store/src/lib/action-names.ts | 1 + .../src/lib/actions/actions.ts | 4 ++ .../src/lib/reducers/flashing.reducer.ts | 9 ++- .../src/lib/selectors/index.ts | 1 + .../selectors/select-is-flashing-in-state.ts | 14 ++++ .../device-flash/device-flash.factory.ts | 7 +- .../windows/windows-device-flash.service.ts | 71 +++++++++++++++++++ .../lib/services/flash-msc-device.service.ts | 71 +++++++++++++------ .../src/lib/services/unpack-flashing-image.ts | 4 ++ 10 files changed, 171 insertions(+), 24 deletions(-) create mode 100644 libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-is-flashing-in-state.ts create mode 100644 libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/windows/windows-device-flash.service.ts diff --git a/libs/core/core/hooks/use-msc-device-detached-effect.ts b/libs/core/core/hooks/use-msc-device-detached-effect.ts index 0b9d38e3e6..bbc452f0db 100644 --- a/libs/core/core/hooks/use-msc-device-detached-effect.ts +++ b/libs/core/core/hooks/use-msc-device-detached-effect.ts @@ -15,6 +15,8 @@ import { abortMscFlashing, FlashingProcessState, selectIsFlashingInActivePhases, + selectIsFlashingInState, + setMscFlashingInitialState, } from "msc-flash-harmony" import { Dispatch } from "Core/__deprecated__/renderer/store" @@ -35,6 +37,10 @@ export const useMscDeviceDetachedEffect = () => { const useHandleDevicesDetached = () => { const dispatch = useDispatch() const flashingInActivePhases = useSelector(selectIsFlashingInActivePhases) + const flashingInWaitingForBackButtonState = useSelector( + selectIsFlashingInState(FlashingProcessState.WaitingForBackButton) + ) + return useCallback( (deviceDetachedEvents: DeviceBaseProperties[]) => { const mscEvents = deviceDetachedEvents.filter( @@ -45,11 +51,16 @@ const useHandleDevicesDetached = () => { return } + if (flashingInWaitingForBackButtonState) { + dispatch(setMscFlashingInitialState()) + return + } + const reason = flashingInActivePhases ? FlashingProcessState.Failed : undefined dispatch(abortMscFlashing({ reason })) }, - [dispatch, flashingInActivePhases] + [dispatch, flashingInActivePhases, flashingInWaitingForBackButtonState] ) } diff --git a/libs/generic-view/store/src/lib/action-names.ts b/libs/generic-view/store/src/lib/action-names.ts index a7743e3a38..8c8d258cad 100644 --- a/libs/generic-view/store/src/lib/action-names.ts +++ b/libs/generic-view/store/src/lib/action-names.ts @@ -93,6 +93,7 @@ export enum ActionName { MscFlashingSetProcessState = "msc-flashing/set-process-state", MscFlashingSetAbort = "msc-flashing/set-abort", MscFlashingAbort = "msc-flashing/abort", + MscFlashingSetInitialState = "msc-flashing/set-initial-state", GetEntitiesConfig = "entities/get-entities-config", SetEntitiesConfig = "entities/set-entities-config", diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/actions/actions.ts b/libs/msc-flash/msc-flash-harmony/src/lib/actions/actions.ts index 92d03fd3a9..b703d50862 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/actions/actions.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/actions/actions.ts @@ -9,3 +9,7 @@ import { ActionName } from "generic-view/store" export const setMscFlashingAbort = createAction( ActionName.MscFlashingSetAbort ) + +export const setMscFlashingInitialState = createAction( + ActionName.MscFlashingSetInitialState +) diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/reducers/flashing.reducer.ts b/libs/msc-flash/msc-flash-harmony/src/lib/reducers/flashing.reducer.ts index f5ad29b599..8fb794cee7 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/reducers/flashing.reducer.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/reducers/flashing.reducer.ts @@ -5,7 +5,11 @@ import { createReducer } from "@reduxjs/toolkit" import { FlashingState } from "./flashing.interface" -import { getMscFlashingFilesDetails, setFlashingProcessState } from "../actions" +import { + getMscFlashingFilesDetails, + setFlashingProcessState, + setMscFlashingInitialState, +} from "../actions" import { FlashingProcessState } from "../constants" import { setMscFlashingAbort } from "../actions/actions" @@ -28,5 +32,8 @@ export const flashingReducer = createReducer( .addCase(setMscFlashingAbort, (state, action) => { state.abortController = action.payload }) + .addCase(setMscFlashingInitialState, () => { + return { ...initialState } + }) } ) diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/selectors/index.ts b/libs/msc-flash/msc-flash-harmony/src/lib/selectors/index.ts index 313272bf24..e523005240 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/selectors/index.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/selectors/index.ts @@ -7,3 +7,4 @@ export * from "./select-flashing-state" export * from "./select-flashing-abort-controller" export * from "./select-flashing-process-state" export * from "./select-is-flashing-in-active-phases" +export * from "./select-is-flashing-in-state" diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-is-flashing-in-state.ts b/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-is-flashing-in-state.ts new file mode 100644 index 0000000000..fb799f00c0 --- /dev/null +++ b/libs/msc-flash/msc-flash-harmony/src/lib/selectors/select-is-flashing-in-state.ts @@ -0,0 +1,14 @@ +/** + * 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" + +export const selectIsFlashingInState = (targetState: FlashingProcessState) => + createSelector( + selectFlashingProcessState, + (flashingProcessState) => flashingProcessState === targetState + ) diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/device-flash.factory.ts b/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/device-flash.factory.ts index d5d537beb2..c4f22c9f8f 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/device-flash.factory.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/device-flash.factory.ts @@ -6,15 +6,20 @@ import IDeviceFlash from "./device-flash.interface" import LinuxDeviceFlashService from "./linux/linux-device-flash.service" import MacDeviceFlashService from "./macos/macos-device-flash-service" +import WindowsDeviceFlashService from "./windows/windows-device-flash.service" class DeviceFlashFactory { - static createDeviceFlashService(temporaryDirectoryPath: string): IDeviceFlash { + static createDeviceFlashService( + temporaryDirectoryPath: string + ): IDeviceFlash { const platform = process.platform if (platform === "linux") { return new LinuxDeviceFlashService() } else if (platform === "darwin") { return new MacDeviceFlashService(temporaryDirectoryPath) + } else if (platform === "win32") { + return new WindowsDeviceFlashService() } else { throw new Error(`Unsupported platform: ${platform}`) } diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/windows/windows-device-flash.service.ts b/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/windows/windows-device-flash.service.ts new file mode 100644 index 0000000000..79bf4d0ac2 --- /dev/null +++ b/libs/msc-flash/msc-flash-harmony/src/lib/services/device-flash/windows/windows-device-flash.service.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import { + execCommandWithSudo, + execPromise, + splitPathToDirNameAndBaseName, +} from "shared/utils" +import IDeviceFlash from "../device-flash.interface" + +interface DiskInformation { + DiskNumber: number + FriendlyName: string + Size: string + OperationalStatus: string +} + +class WindowsDeviceFlashService implements IDeviceFlash { + async findDeviceByDeviceName(deviceName: string): Promise { + console.log( + `Searching for the device with the friendly name: ${deviceName}` + ) + + const getDiskResult = await execPromise( + `powershell.exe -Command "Get-Disk | Where-Object { $_.FriendlyName -eq '${deviceName}' }| ConvertTo-Json"` + ) + + if (!getDiskResult) { + throw new Error(`Disk not found for friendly name: ${deviceName}`) + } + + const diskInformation: DiskInformation = JSON.parse(getDiskResult) + + console.log( + `Disk information retrieved successfully: Disk Number - ${diskInformation.DiskNumber}, Size - ${diskInformation.Size} bytes, Status - ${diskInformation.OperationalStatus}` + ) + + return String(diskInformation.DiskNumber) + } + + async execute( + device: string, + imagePath: string, + scriptPath: string + ): Promise { + await this.flashDevice(device, imagePath, scriptPath) + console.log("Flash process completed successfully") + } + + private async flashDevice( + device: string, + imagePath: string, + scriptPath: string + ): Promise { + const [path, scriptBasename] = splitPathToDirNameAndBaseName(scriptPath) + const [, imageBasename] = splitPathToDirNameAndBaseName(imagePath) + const command = `cd ${path} && powershell.exe -ExecutionPolicy Bypass -File ${scriptBasename} -file ${imageBasename} -diskid ${device} -force` + + try { + await execCommandWithSudo(command) + } catch (error) { + throw new Error( + `An error occurred during flashing: ${JSON.stringify(error)}` + ) + } + } +} + +export default WindowsDeviceFlashService diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/services/flash-msc-device.service.ts b/libs/msc-flash/msc-flash-harmony/src/lib/services/flash-msc-device.service.ts index fbe6e7626a..9d701f4798 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/services/flash-msc-device.service.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/services/flash-msc-device.service.ts @@ -5,22 +5,23 @@ import path from "path" import { Dispatch, ReduxRootState } from "Core/__deprecated__/renderer/store" +import logger from "Core/__deprecated__/main/utils/logger" +import getAppSettingsMain from "Core/__deprecated__/main/functions/get-app-settings" +import { RELEASE_SPACE } from "Core/update/constants/release-space.constant" import { FlashingProcessState, SupportedPlatform, Product } from "../constants" import { setFlashingProcessState } from "../actions/set-flashing-process-state.action" import { getMscFlashingFilesDetails } from "../actions/get-msc-flashing-files-details.action" import { MscFlashDetails } from "../dto" import { downloadFlashingFileRequest } from "../requests" +import { setMscFlashingAbort } from "../actions/actions" +import { selectIsFlashingInActivePhases } from "../selectors" import { unpackFlashingImageService } from "./unpack-flashing-image" import DeviceFlashFactory from "./device-flash/device-flash.factory" -import getAppSettingsMain from "Core/__deprecated__/main/functions/get-app-settings" - -const IMAGE_FILE_NAME = "BellHybrid.img" -import { RELEASE_SPACE } from "Core/update/constants/release-space.constant" import MacDeviceFlashService from "./device-flash/macos/macos-device-flash-service" +import IDeviceFlash from "./device-flash/device-flash.interface" import { removeDownloadedMscFiles } from "./remove-downloaded-msc-files.service" -import { setMscFlashingAbort } from "../actions/actions" -import { selectIsFlashingInActivePhases } from "../selectors" -import logger from "Core/__deprecated__/main/utils/logger" + +const IMAGE_FILE_NAME = "BellHybrid.img" export const flashMscDeviceService = () => async (dispatch: Dispatch, getState: () => ReduxRootState) => { @@ -148,34 +149,62 @@ const startFlashingProcess = async ( const deviceFlash = DeviceFlashFactory.createDeviceFlashService(osDownloadLocation) + const deviceName = getDeviceName() + const device = await deviceFlash.findDeviceByDeviceName(deviceName) - const device = await deviceFlash.findDeviceByDeviceName("HARMONY") - - const flashingScriptName = flashingFiles - ? flashingFiles.scripts[0].name - : "" - - const imageFilePath = path.join(osDownloadLocation, IMAGE_FILE_NAME) - const scriptFilePath = path.join(osDownloadLocation, flashingScriptName) + const { imageFilePath, scriptFilePath } = buildFlashingFilePaths( + flashingFiles, + osDownloadLocation + ) await deviceFlash.execute(device, imageFilePath, scriptFilePath) + if (signal.aborted) { return } - if (deviceFlash instanceof MacDeviceFlashService) { - dispatch(setFlashingProcessState(FlashingProcessState.TerminalOpened)) - - await deviceFlash.waitForFlashCompletion({ signal }) - } + await handlePostExecutingTasks(deviceFlash, dispatch, signal) if (signal.aborted) { return } - dispatch(setFlashingProcessState(FlashingProcessState.Restarting)) await removeDownloadedMscFiles() } catch (error) { throw new Error(`Flash process failed with error: ${JSON.stringify(error)}`) } } + +const getDeviceName = () => { + return process.platform === "win32" ? "MUDITA HARMONY MSC" : "HARMONY" +} + +const buildFlashingFilePaths = ( + flashingFiles: MscFlashDetails | undefined, + osDownloadLocation: string +) => { + const flashingFilesScripts = + process.platform === "win32" + ? flashingFiles?.scripts[1] + : flashingFiles?.scripts[0] + const flashingScriptName = flashingFilesScripts?.name ?? "" + const imageFilePath = path.join(osDownloadLocation, IMAGE_FILE_NAME) + const scriptFilePath = path.join(osDownloadLocation, flashingScriptName) + + return { imageFilePath, scriptFilePath } +} + +const handlePostExecutingTasks = async ( + deviceFlash: IDeviceFlash, + dispatch: Dispatch, + signal: AbortSignal +) => { + if (deviceFlash instanceof MacDeviceFlashService) { + dispatch(setFlashingProcessState(FlashingProcessState.TerminalOpened)) + await deviceFlash.waitForFlashCompletion({ signal }) + } else if (process.platform === "win32") { + dispatch(setFlashingProcessState(FlashingProcessState.WaitingForBackButton)) + } else { + dispatch(setFlashingProcessState(FlashingProcessState.Restarting)) + } +} diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/services/unpack-flashing-image.ts b/libs/msc-flash/msc-flash-harmony/src/lib/services/unpack-flashing-image.ts index 084b51e70f..75f6e9a5a1 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/services/unpack-flashing-image.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/services/unpack-flashing-image.ts @@ -18,6 +18,10 @@ export const unpackFlashingImageService = async ( const imageFilePath = path.join(osDownloadLocation, fileName) command = `tar -xf "${imageFilePath}" -C "${osDownloadLocation}"` } + if (process.platform === "win32") { + const imageFilePath = path.join(osDownloadLocation, fileName) + command = `tar -xzvf "${imageFilePath}" -C "${osDownloadLocation}"` + } try { await execPromise(command) From 26e21f10a837741cb28b3db6c10a09c6150c9b60 Mon Sep 17 00:00:00 2001 From: Daniel Karski Date: Wed, 16 Oct 2024 10:57:28 +0200 Subject: [PATCH 5/5] [CP-3184] Unpacking method fixed --- .../src/lib/services/unpack-flashing-image.ts | 6 ++++-- libs/shared/utils/src/lib/exec-command-with-sudo.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libs/msc-flash/msc-flash-harmony/src/lib/services/unpack-flashing-image.ts b/libs/msc-flash/msc-flash-harmony/src/lib/services/unpack-flashing-image.ts index 75f6e9a5a1..2cba1c2e47 100644 --- a/libs/msc-flash/msc-flash-harmony/src/lib/services/unpack-flashing-image.ts +++ b/libs/msc-flash/msc-flash-harmony/src/lib/services/unpack-flashing-image.ts @@ -19,8 +19,10 @@ export const unpackFlashingImageService = async ( command = `tar -xf "${imageFilePath}" -C "${osDownloadLocation}"` } if (process.platform === "win32") { - const imageFilePath = path.join(osDownloadLocation, fileName) - command = `tar -xzvf "${imageFilePath}" -C "${osDownloadLocation}"` + const imageFilePathTarGz = path.join(osDownloadLocation, fileName) + const baseName = path.basename(fileName, ".tar.gz"); + const imageFilePathTar = path.join(osDownloadLocation, `${baseName}.tar`, `${baseName}.tar`) + command = `tar -xzvf "${imageFilePathTarGz}" -C "${osDownloadLocation}" && tar -xvf "${imageFilePathTar}" -C "${osDownloadLocation}" ` } try { diff --git a/libs/shared/utils/src/lib/exec-command-with-sudo.ts b/libs/shared/utils/src/lib/exec-command-with-sudo.ts index 00bf4538d8..d61e17b1b9 100644 --- a/libs/shared/utils/src/lib/exec-command-with-sudo.ts +++ b/libs/shared/utils/src/lib/exec-command-with-sudo.ts @@ -7,7 +7,7 @@ import sudoPrompt from "@vscode/sudo-prompt" export const execCommandWithSudo = ( command: string, - options?: { name?: string; icns?: string; env?: { [key: string]: string } } + options: { name?: string; icns?: string; env?: { [key: string]: string } } = {} ): Promise => { return new Promise((resolve, reject) => { sudoPrompt.exec(command, options, (error, stdout, stderr) => {