Skip to content

Commit

Permalink
[CP-3184] Implement UI modal requesting the eject operation on Windows (
Browse files Browse the repository at this point in the history
  • Loading branch information
dkarski authored Oct 17, 2024
1 parent 3bfe9ac commit fb69532
Show file tree
Hide file tree
Showing 18 changed files with 278 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export enum IconType {
ArrowLongLeft,
ArrowLongRight,
AttachContact,
BackArrowIcon,
BackupFolder,
Battery,
BorderCheckIcon,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -147,6 +148,9 @@ const typeToIcon: Partial<Record<IconType, typeof Arrow>> = {
[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
Expand Down
2 changes: 2 additions & 0 deletions libs/core/__deprecated__/renderer/locales/default/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions libs/core/__deprecated__/renderer/svg/back-arrow-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 12 additions & 1 deletion libs/core/core/hooks/use-msc-device-detached-effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
abortMscFlashing,
FlashingProcessState,
selectIsFlashingInActivePhases,
selectIsFlashingInState,
setMscFlashingInitialState,
} from "msc-flash-harmony"
import { Dispatch } from "Core/__deprecated__/renderer/store"

Expand All @@ -35,6 +37,10 @@ export const useMscDeviceDetachedEffect = () => {
const useHandleDevicesDetached = () => {
const dispatch = useDispatch<Dispatch>()
const flashingInActivePhases = useSelector(selectIsFlashingInActivePhases)
const flashingInWaitingForBackButtonState = useSelector(
selectIsFlashingInState(FlashingProcessState.WaitingForBackButton)
)

return useCallback(
(deviceDetachedEvents: DeviceBaseProperties[]) => {
const mscEvents = deviceDetachedEvents.filter(
Expand All @@ -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]
)
}
1 change: 1 addition & 0 deletions libs/generic-view/store/src/lib/action-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions libs/msc-flash/msc-flash-harmony/src/lib/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ import { ActionName } from "generic-view/store"
export const setMscFlashingAbort = createAction<AbortController | undefined>(
ActionName.MscFlashingSetAbort
)

export const setMscFlashingInitialState = createAction(
ActionName.MscFlashingSetInitialState
)
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -28,5 +32,8 @@ export const flashingReducer = createReducer<FlashingState>(
.addCase(setMscFlashingAbort, (state, action) => {
state.abortController = action.payload
})
.addCase(setMscFlashingInitialState, () => {
return { ...initialState }
})
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string> {
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<void> {
await this.flashDevice(device, imagePath, scriptPath)
console.log("Flash process completed successfully")
}

private async flashDevice(
device: string,
imagePath: string,
scriptPath: string
): Promise<void> {
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
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export const unpackFlashingImageService = async (
const imageFilePath = path.join(osDownloadLocation, fileName)
command = `tar -xf "${imageFilePath}" -C "${osDownloadLocation}"`
}
if (process.platform === "win32") {
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 {
await execPromise(command)
Expand Down
12 changes: 10 additions & 2 deletions libs/msc-flash/msc-flash-harmony/src/lib/ui/recovery-mode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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 }))
}

Expand Down Expand Up @@ -208,7 +212,11 @@ const RecoveryModeUI: FunctionComponent = () => {
<RestartingDeviceModal open={isRestartingModalVisible()} />
<MacTerminalInfoModal
open={isMacTerminalInfoModalVisible()}
onClose={macTerminalInfoCloseHandler}
onClose={cancelMscFlashing}
/>
<WaitingForBackButtonModal
open={isWaitingForBackButtonVisible()}
onClose={cancelMscFlashing}
/>
</ThemeProvider>
</>
Expand Down
Loading

0 comments on commit fb69532

Please sign in to comment.