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-3184] Implement UI modal requesting the eject operation on Windows #2129

Merged
merged 6 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading