Support async file conversions (#326)

* Support async file conversions
Fixes #325

* Add loop

* Add exit condition

* More tweaks

* Clean up

* Fix large binary STL

* Add error messages

* Reduce timeout
This commit is contained in:
Pierre Jacquier
2024-01-29 16:27:46 +01:00
committed by GitHub
parent ffbc846ca1
commit b2cf16b400
4 changed files with 57 additions and 17 deletions

View File

@ -22,7 +22,7 @@ describe('Function downloadFile', () => {
'example.obj' 'example.obj'
) )
// TODO: add hash validation or something like that // TODO: add hash validation or something like that
expect(response).toHaveLength(37077) expect(await response.text()).toHaveLength(37077)
}) })
it('downloads a public LFS github file', async () => { it('downloads a public LFS github file', async () => {
@ -36,6 +36,6 @@ describe('Function downloadFile', () => {
'Part1.SLDPRT' 'Part1.SLDPRT'
) )
// TODO: add hash validation or something like that // TODO: add hash validation or something like that
expect(response).toHaveLength(70702) expect(await response.text()).toHaveLength(70702)
}) })
}) })

View File

@ -1,5 +1,5 @@
import { Octokit } from '@octokit/rest' import { Octokit } from '@octokit/rest'
import { Client, file } from '@kittycad/lib' import { api_calls, Client, file } from '@kittycad/lib'
import { ContentFile, DiffEntry, FileBlob, FileDiff } from './types' import { ContentFile, DiffEntry, FileBlob, FileDiff } from './types'
import { import {
FileExportFormat_type, FileExportFormat_type,
@ -35,7 +35,7 @@ export async function downloadFile(
repo: string, repo: string,
ref: string, ref: string,
path: string path: string
): Promise<string> { ): Promise<Blob> {
// First get some info on the blob with the Contents api // First get some info on the blob with the Contents api
const content = await octokit.rest.repos.getContent({ const content = await octokit.rest.repos.getContent({
owner, owner,
@ -54,15 +54,16 @@ export async function downloadFile(
console.log(`Downloading ${contentFile.download_url}...`) console.log(`Downloading ${contentFile.download_url}...`)
const response = await fetch(contentFile.download_url) const response = await fetch(contentFile.download_url)
if (!response.ok) throw response if (!response.ok) throw response
return await response.text() return await response.blob()
} }
async function convert( async function convert(
client: Client, client: Client,
body: string, blob: Blob,
extension: string, extension: string,
outputFormat = 'obj' outputFormat = 'obj'
) { ) {
const body = await blob.arrayBuffer()
if (extension === outputFormat) { if (extension === outputFormat) {
console.log( console.log(
'Skipping conversion, as extension is equal to outputFormat' 'Skipping conversion, as extension is equal to outputFormat'
@ -76,9 +77,26 @@ async function convert(
output_format: outputFormat as FileExportFormat_type, output_format: outputFormat as FileExportFormat_type,
}) })
const key = `source.${outputFormat}` const key = `source.${outputFormat}`
if ('error_code' in response || !response.outputs[key]) throw response if ('error_code' in response) throw response
const { status, id, outputs } = response const { id } = response
let { status, outputs } = response
console.log(`File conversion: ${id}, ${status}`) console.log(`File conversion: ${id}, ${status}`)
let retries = 0
while (status !== 'completed' && status !== 'failed') {
if (retries >= 60) {
console.log('Async conversion took too long, aborting.')
break
}
retries++
await new Promise(resolve => setTimeout(resolve, 1000))
const response = await api_calls.get_async_operation({ client, id })
if ('error_code' in response) throw response
status = response.status
console.log(`File conversion: ${id}, ${status} (retry #${retries})`)
if ('outputs' in response) {
outputs = response.outputs
}
}
return outputs[key] return outputs[key]
} }

View File

@ -6,6 +6,7 @@ import { createPortal } from 'react-dom'
import { Loading } from '../Loading' import { Loading } from '../Loading'
import { CadBlob } from './CadBlob' import { CadBlob } from './CadBlob'
import { ColorModeWithAuto } from '@primer/react/lib/ThemeProvider' import { ColorModeWithAuto } from '@primer/react/lib/ThemeProvider'
import { ErrorMessage } from './ErrorMessage'
function CadBlobPortal({ function CadBlobPortal({
element, element,
@ -20,6 +21,7 @@ function CadBlobPortal({
sha: string sha: string
filename: string filename: string
}): React.ReactElement { }): React.ReactElement {
const [loading, setLoading] = useState(true)
const [richBlob, setRichBlob] = useState<FileBlob>() const [richBlob, setRichBlob] = useState<FileBlob>()
const [richSelected, setRichSelected] = useState(true) const [richSelected, setRichSelected] = useState(true)
const [toolbarContainer, setToolbarContainer] = useState<HTMLElement>() const [toolbarContainer, setToolbarContainer] = useState<HTMLElement>()
@ -65,14 +67,17 @@ function CadBlobPortal({
useEffect(() => { useEffect(() => {
;(async () => { ;(async () => {
setLoading(true)
const response = await chrome.runtime.sendMessage({ const response = await chrome.runtime.sendMessage({
id: MessageIds.GetFileBlob, id: MessageIds.GetFileBlob,
data: { owner, repo, sha, filename }, data: { owner, repo, sha, filename },
}) })
if ('error' in response) { if ('error' in response) {
console.log(response.error) console.log(response.error)
setLoading(false)
} else { } else {
setRichBlob(response as FileBlob) setRichBlob(response as FileBlob)
setLoading(false)
} }
})() })()
}, [owner, repo, sha, filename]) }, [owner, repo, sha, filename])
@ -115,10 +120,16 @@ function CadBlobPortal({
width: '100%', width: '100%',
}} }}
> >
{richBlob ? ( {loading ? (
<CadBlob blob={richBlob.blob} />
) : (
<Loading /> <Loading />
) : (
<>
{richBlob ? (
<CadBlob blob={richBlob.blob} />
) : (
<ErrorMessage />
)}
</>
)} )}
</Box>, </Box>,
blobContainer blobContainer

View File

@ -7,6 +7,7 @@ import { Loading } from '../Loading'
import { CadDiff } from './CadDiff' import { CadDiff } from './CadDiff'
import { SourceRichToggle } from './SourceRichToggle' import { SourceRichToggle } from './SourceRichToggle'
import { ColorModeWithAuto } from '@primer/react/lib/ThemeProvider' import { ColorModeWithAuto } from '@primer/react/lib/ThemeProvider'
import { ErrorMessage } from './ErrorMessage'
function CadDiffPortal({ function CadDiffPortal({
element, element,
@ -23,6 +24,7 @@ function CadDiffPortal({
sha: string sha: string
parentSha: string parentSha: string
}): React.ReactElement { }): React.ReactElement {
const [loading, setLoading] = useState(true)
const [richDiff, setRichDiff] = useState<FileDiff>() const [richDiff, setRichDiff] = useState<FileDiff>()
const [richSelected, setRichSelected] = useState(true) const [richSelected, setRichSelected] = useState(true)
const [toolbarContainer, setToolbarContainer] = useState<HTMLElement>() const [toolbarContainer, setToolbarContainer] = useState<HTMLElement>()
@ -54,14 +56,17 @@ function CadDiffPortal({
useEffect(() => { useEffect(() => {
;(async () => { ;(async () => {
setLoading(true)
const response = await chrome.runtime.sendMessage({ const response = await chrome.runtime.sendMessage({
id: MessageIds.GetFileDiff, id: MessageIds.GetFileDiff,
data: { owner, repo, sha, parentSha, file }, data: { owner, repo, sha, parentSha, file },
}) })
if ('error' in response) { if ('error' in response) {
console.log(response.error) console.log(response.error)
setLoading(false)
} else { } else {
setRichDiff(response as FileDiff) setRichDiff(response as FileDiff)
setLoading(false)
} }
})() })()
}, [file, owner, repo, sha, parentSha]) }, [file, owner, repo, sha, parentSha])
@ -87,13 +92,19 @@ function CadDiffPortal({
{diffContainer && {diffContainer &&
createPortal( createPortal(
<Box sx={{ display: richSelected ? 'block' : 'none' }}> <Box sx={{ display: richSelected ? 'block' : 'none' }}>
{richDiff ? ( {loading ? (
<CadDiff
before={richDiff.before}
after={richDiff.after}
/>
) : (
<Loading /> <Loading />
) : (
<>
{richDiff ? (
<CadDiff
before={richDiff.before}
after={richDiff.after}
/>
) : (
<ErrorMessage />
)}
</>
)} )}
</Box>, </Box>,
diffContainer diffContainer