Skip to content

Commit

Permalink
fix(app-shell,app): Send labware files and runtime parameters over USB (
Browse files Browse the repository at this point in the history
  • Loading branch information
SyntaxColoring authored and Carlos-fernandez committed May 20, 2024
1 parent 266f502 commit 4a13440
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 39 deletions.
4 changes: 3 additions & 1 deletion api-client/src/protocols/createProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export function createProtocol(
runTimeParameterValues?: RunTimeParameterCreateData
): ResponsePromise<Protocol> {
const formData = new FormData()
files.forEach(file => formData.append('files', file, file.name))
files.forEach(file => {
formData.append('files', file, file.name)
})
if (protocolKey != null) formData.append('key', protocolKey)
if (runTimeParameterValues != null)
formData.append(
Expand Down
13 changes: 0 additions & 13 deletions app-shell/src/protocol-storage/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import fse from 'fs-extra'
import path from 'path'
import { shell } from 'electron'
import first from 'lodash/first'

import {
ADD_PROTOCOL,
Expand Down Expand Up @@ -48,18 +47,6 @@ export const getParsedAnalysisFromPath = (
}
}

export const getProtocolSrcFilePaths = (
protocolKey: string
): Promise<string[]> => {
const protocolDir = `${FileSystem.PROTOCOLS_DIRECTORY_PATH}/${protocolKey}`
return ensureDir(protocolDir)
.then(() => FileSystem.parseProtocolDirs([protocolDir]))
.then(storedProtocols => {
const storedProtocol = first(storedProtocols)
return storedProtocol?.srcFilePaths ?? []
})
}

// Revert a v7.0.0 pre-parity stop-gap solution.
const migrateProtocolsFromTempDirectory = preParityMigrateProtocolsFrom(
FileSystem.PRE_V7_PARITY_DIRECTORY_PATH,
Expand Down
31 changes: 14 additions & 17 deletions app-shell/src/usb.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { ipcMain, IpcMainInvokeEvent } from 'electron'
import axios, { AxiosRequestConfig } from 'axios'
import FormData from 'form-data'
import fs from 'fs'
import path from 'path'

import {
fetchSerialPortList,
Expand All @@ -12,14 +10,14 @@ import {
} from '@opentrons/usb-bridge/node-client'

import { createLogger } from './log'
import { getProtocolSrcFilePaths } from './protocol-storage'
import { usbRequestsStart, usbRequestsStop } from './config/actions'
import {
SYSTEM_INFO_INITIALIZED,
USB_DEVICE_ADDED,
USB_DEVICE_REMOVED,
} from './constants'

import type { IPCSafeFormData } from '@opentrons/app/src/redux/shell/types'
import type { UsbDevice } from '@opentrons/app/src/redux/system-info/types'
import type { PortInfo } from '@opentrons/usb-bridge/node-client'
import type { Action, Dispatch } from './types'
Expand Down Expand Up @@ -83,6 +81,17 @@ function isUsbDeviceOt3(device: UsbDevice): boolean {
device.vendorId === parseInt(DEFAULT_VENDOR_ID, 16)
)
}

function reconstructFormData(ipcSafeFormData: IPCSafeFormData): FormData {
const result = new FormData()
ipcSafeFormData.forEach(entry => {
entry.type === 'file'
? result.append(entry.name, Buffer.from(entry.value), entry.filename)
: result.append(entry.name, entry.value)
})
return result
}

async function usbListener(
_event: IpcMainInvokeEvent,
config: AxiosRequestConfig
Expand All @@ -92,21 +101,9 @@ async function usbListener(
let formHeaders = {}

// check for formDataProxy
if (data?.formDataProxy != null) {
if (data?.proxiedFormData != null) {
// reconstruct FormData
const formData = new FormData()
const { protocolKey } = data.formDataProxy

const srcFilePaths: string[] = await getProtocolSrcFilePaths(protocolKey)

// create readable stream from file
srcFilePaths.forEach(srcFilePath => {
const readStream = fs.createReadStream(srcFilePath)
formData.append('files', readStream, path.basename(srcFilePath))
})

formData.append('key', protocolKey)

const formData = reconstructFormData(data.proxiedFormData)
formHeaders = formData.getHeaders()
data = formData
}
Expand Down
42 changes: 34 additions & 8 deletions app/src/redux/shell/remote.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// access main process remote modules via attachments to `global`
import type { AxiosRequestConfig } from 'axios'
import type { ResponsePromise } from '@opentrons/api-client'
import type { Remote, NotifyTopic, NotifyResponseData } from './types'
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import type {
Remote,
NotifyTopic,
NotifyResponseData,
IPCSafeFormData,
} from './types'

const emptyRemote: Remote = {} as any

Expand All @@ -20,18 +24,40 @@ export const remote: Remote = new Proxy(emptyRemote, {
},
})

export function appShellRequestor<Data>(
// FormData and File objects can't be sent through invoke().
// This converts them into simpler objects that can be.
// app-shell will convert them back.
async function proxyFormData(formData: FormData): Promise<IPCSafeFormData> {
const result: IPCSafeFormData = []
for (const [name, value] of formData.entries()) {
if (value instanceof File) {
result.push({
type: 'file',
name,
// todo(mm, 2024-04-24): Send just the (full) filename instead of the file
// contents, to avoid the IPC message ballooning into several MB.
value: await value.arrayBuffer(),
filename: value.name,
})
} else {
result.push({ type: 'string', name, value })
}
}

return result
}

export async function appShellRequestor<Data>(
config: AxiosRequestConfig
): ResponsePromise<Data> {
): Promise<AxiosResponse<Data>> {
const { data } = config
// special case: protocol files and form data cannot be sent through invoke. proxy by protocolKey and handle in app-shell
const formDataProxy =
data instanceof FormData
? { formDataProxy: { protocolKey: data.get('key') } }
? { proxiedFormData: await proxyFormData(data) }
: data
const configProxy = { ...config, data: formDataProxy }

return remote.ipcRenderer.invoke('usb:request', configProxy)
return await remote.ipcRenderer.invoke('usb:request', configProxy)
}

interface CallbackStore {
Expand Down
15 changes: 15 additions & 0 deletions app/src/redux/shell/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,18 @@ export type ShellAction =
| RobotMassStorageDeviceEnumerated
| RobotMassStorageDeviceRemoved
| NotifySubscribeAction

export type IPCSafeFormDataEntry =
| {
type: 'string'
name: string
value: string
}
| {
type: 'file'
name: string
value: ArrayBuffer
filename: string
}

export type IPCSafeFormData = IPCSafeFormDataEntry[]

0 comments on commit 4a13440

Please sign in to comment.