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:
@ -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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user