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

Optimized Model Loading #1256

Merged
merged 10 commits into from
Sep 13, 2024
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bldrs",
"version": "1.0.1103",
"version": "1.0.1101",
"main": "src/index.jsx",
"license": "AGPL-3.0",
"homepage": "https://github.com/bldrs-ai/Share",
Expand All @@ -12,7 +12,7 @@
"build-conway": "yarn clean && yarn build-share-conway && yarn build-cosmos",
"build-webifc": "yarn clean && yarn build-share-webifc && yarn build-cosmos",
"build-cosmos": "shx mkdir -p docs ; shx rm -rf docs/cosmos; cosmos-export --config .cosmos.config.json && shx mv cosmos-export docs/cosmos",
"build-share": "yarn update-version && node tools/esbuild/build.js && shx mkdir -p docs/static/js && shx cp src/OPFS/OPFS.worker.js docs/",
"build-share": "yarn update-version && node tools/esbuild/build.js && shx mkdir -p docs/static/js && shx cp src/OPFS/OPFS.worker.js docs/ && shx cp src/net/github/Cache.js docs/",
"build-share-conway": "run-script-os",
"build-share-conway:win32": "set USE_WEBIFC_SHIM=true&& yarn build-share",
"build-share-conway:linux:darwin": "USE_WEBIFC_SHIM=true yarn build-share",
Expand Down
118 changes: 61 additions & 57 deletions src/Components/Open/SaveModelControl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,63 +232,67 @@ function SaveModelDialog({isDialogDisplayed, setIsDialogDisplayed, navigate, org
justifyContent='center'
alignItems='center'
>
{!isAuthenticated ?

<PleaseLogin/> :

<Stack>
<Typography variant='overline' sx={{marginBottom: '6px'}}>Projects</Typography>
<Selector label='Organization' list={orgNamesArrWithAt} selected={selectedOrgName} setSelected={selectOrg}
data-testid='saveOrganization'
/>
<Selector
label='Repository'
list={repoNamesArr}
selected={selectedRepoName}
setSelected={selectRepo}
data-testid='saveRepository'
/>
<SelectorSeparator
label={(currentPath === '') ? 'Folder' :
`Folder: ${currentPath}`}
list={foldersArr}
selected={selectedFolderName}
setSelected={selectFolder}
data-testid='saveFolder'
/>
{requestCreateFolder && (
<div style={{display: 'flex', alignItems: 'center', marginBottom: '.5em'}}>
<TextField
label='Enter folder name'
variant='outlined'
size='small'
onChange={(e) => setCreateFolderName(e.target.value)}
data-testid='CreateFolderId'
sx={{flexGrow: 1}}
onKeyDown={(e) => {
// Stops the event from propagating up to parent elements
e.stopPropagation()
}}
/>
<IconButton
onClick={() => setRequestCreateFolder(false)}
size='small'
>
<ClearIcon className='icon-share'/>
</IconButton>
</div>
)}
<TextField
label='Enter file name'
variant='outlined'
size='small'
onChange={(e) => setSelectedFileName(e.target.value)}
onKeyDown={(e) => e.stopPropagation()}
sx={{marginBottom: '.5em'}}
data-testid='CreateFileId'
/>
</Stack>
}
{!isAuthenticated ? (
<PleaseLogin/>
) : (
file instanceof File && (
<Stack>
<Typography variant='overline' sx={{marginBottom: '6px'}}>Projects</Typography>
<Selector
label='Organization'
list={orgNamesArrWithAt}
selected={selectedOrgName}
setSelected={selectOrg}
data-testid='saveOrganization'
/>
<Selector
label='Repository'
list={repoNamesArr}
selected={selectedRepoName}
setSelected={selectRepo}
data-testid='saveRepository'
/>
<SelectorSeparator
label={currentPath === '' ? 'Folder' : `Folder: ${currentPath}`}
list={foldersArr}
selected={selectedFolderName}
setSelected={selectFolder}
data-testid='saveFolder'
/>
{requestCreateFolder && (
<div style={{display: 'flex', alignItems: 'center', marginBottom: '.5em'}}>
<TextField
label='Enter folder name'
variant='outlined'
size='small'
onChange={(e) => setCreateFolderName(e.target.value)}
data-testid='CreateFolderId'
sx={{flexGrow: 1}}
onKeyDown={(e) => {
// Stops the event from propagating up to parent elements
e.stopPropagation()
}}
/>
<IconButton
onClick={() => setRequestCreateFolder(false)}
size='small'
>
<ClearIcon className='icon-share'/>
</IconButton>
</div>
)}
<TextField
label='Enter file name'
variant='outlined'
size='small'
onChange={(e) => setSelectedFileName(e.target.value)}
onKeyDown={(e) => e.stopPropagation()}
sx={{marginBottom: '.5em'}}
data-testid='CreateFileId'
/>
</Stack>
)
)}
</Stack>
</Dialog>
)
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Open/SaveModelControl.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('SaveModelControl', () => {
expect(loginText).toBeInTheDocument()
})

it('Renders file selector if the user is logged in', async () => {
it('Renders file selector if the user is logged in', async () => {
mockedUseAuth0.mockReturnValue(mockedUserLoggedIn)
const {getByTestId} = render(<SaveModelControlFixture/>)
const saveControlButton = getByTestId('control-button-save')
Expand Down
4 changes: 2 additions & 2 deletions src/Components/Versions/VersionsPanel.fixture.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const RAW_GIT_PROXY_URL = process.env.RAW_GIT_PROXY_URL
const RAW_GIT_PROXY_URL_NEW = process.env.RAW_GIT_PROXY_URL_NEW


export const MOCK_MODEL_PATH_GIT = {
Expand All @@ -7,5 +7,5 @@ export const MOCK_MODEL_PATH_GIT = {
branch: 'main',
filepath: '/ZGRAGGEN.ifc',
eltPath: '',
gitpath: `${RAW_GIT_PROXY_URL}/user2/Schneestock-Public/main/ZGRAGGEN.ifc`,
gitpath: `${RAW_GIT_PROXY_URL_NEW}/user2/Schneestock-Public/main/ZGRAGGEN.ifc`,
}
58 changes: 26 additions & 32 deletions src/Containers/CadView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import ElementGroup from '../Components/ElementGroup'
import HelpControl from '../Components/Help/HelpControl'
import {useIsMobile} from '../Components/Hooks'
import LoadingBackdrop from '../Components/LoadingBackdrop'
import {getModelFromOPFS, downloadToOPFS} from '../OPFS/utils'
import {getModelFromOPFS, downloadToOPFS, downloadModel} from '../OPFS/utils'
import usePlaceMark from '../hooks/usePlaceMark'
import * as Analytics from '../privacy/analytics'
import useStore from '../store/useStore'
import {getLatestCommitHash} from '../net/github/Commits'
// TODO(pablo): use ^^ instead of this
import {parseGitHubPath} from '../utils/location'
import {getParentPathIdsForElement, setupLookupAndParentLinks} from '../utils/TreeUtils'
Expand All @@ -32,7 +31,7 @@ import ControlsGroupAndDrawer from './ControlsGroupAndDrawer'
import OperationsGroupAndDrawer from './OperationsGroupAndDrawer'
import ViewerContainer from './ViewerContainer'
import {elementSelection} from './selection'
import {getFinalUrl} from './urls'
import {getFinalDownloadData} from './urls'
import {initViewer} from './viewer'


Expand Down Expand Up @@ -252,8 +251,15 @@ export default function CadView({

// NB: for LFS targets, this will now be media.githubusercontent.com, so
// don't use for further API endpoint construction.
const ifcURL = (uploadedFile || filepath.indexOf('/') === 0) ?
filepath : await getFinalUrl(filepath, accessToken)
let ifcURL; let shaHash

if (uploadedFile || filepath.indexOf('/') === 0) {
ifcURL = filepath
shaHash = ''
} else {
[ifcURL, shaHash] = await getFinalDownloadData(filepath, accessToken, isOpfsAvailable)
}


const isCamHashSet = onHash(location, viewer.IFC.context.ifcCamera.cameraControls)

Expand Down Expand Up @@ -332,29 +338,21 @@ export default function CadView({
} else {
// TODO(pablo): probably already available in this scope, or use
// parseGitHubRepositoryURL instead.
// eslint-disable-next-line no-unused-vars
const {isPublic, owner, repo, branch, filePath} = parseGitHubPath(new URL(gitpath).pathname)
const commitHash = isPublic ?
await getLatestCommitHash(owner, repo, filePath, '', branch) :
await getLatestCommitHash(owner, repo, filePath, accessToken, branch)

if (commitHash === null) {
// downloadToOpfs below will error out as well.
debug().error(
`Error obtaining commit hash for: ` +
`owner:${owner}, repo:${repo}, filePath:${filePath}, branch:${branch} ` +
`accessToken (present?):${accessToken ? 'true' : 'false'}`)
}

const file = await downloadToOPFS(
const file = await downloadModel(
navigate,
appPrefix,
handleBeforeUnload,
ifcURL,
shaHash,
filePath,
commitHash,
accessToken,
owner,
repo,
branch,
setOpfsFile,
(progressEvent) => {
if (Number.isFinite(progressEvent.receivedLength)) {
const loadedBytes = progressEvent.receivedLength
Expand All @@ -365,21 +363,17 @@ export default function CadView({
}
})

if (file instanceof File) {
setOpfsFile(file)
} else {
debug().error('Retrieved object is not of type File.')
if (file) {
loadedModel = await viewer.loadIfcFile(
file,
!isCamHashSet,
(error) => {
debug().log('CadView#loadIfc$onError: ', error)
// TODO(pablo): error modal.
setIsModelLoading(false)
setErrorPath(filePath)
}, customViewSettings)
}

loadedModel = await viewer.loadIfcFile(
file,
!isCamHashSet,
(error) => {
debug().log('CadView#loadIfc$onError: ', error)
// TODO(pablo): error modal.
setIsModelLoading(false)
setErrorPath(filePath)
}, customViewSettings)
}

if (loadedModel) {
Expand Down
14 changes: 13 additions & 1 deletion src/Containers/CadView.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,19 @@ jest.mock('../OPFS/utils', () => {
return {
...actualUtils, // Preserve other exports from the module
downloadToOPFS: jest.fn().mockImplementation(() => {
// Read the file content from disk (consider using async read in real use-cases)
// Read the file content from disk
const fileContent = fs.readFileSync(path.join(__dirname, './index.ifc'), 'utf8')

const uint8Array = new Uint8Array(fileContent)
const blob = new Blob([uint8Array])

// The lastModified property is optional, and can be omitted or set to Date.now() if needed
const file = new FileMock([blob], 'index.ifc', {type: 'text/plain', lastModified: Date.now()})
// Return the mocked File in a promise if it's an async function
return Promise.resolve(file)
}),
downloadModel: jest.fn().mockImplementation(() => {
// Read the file content from disk
const fileContent = fs.readFileSync(path.join(__dirname, './index.ifc'), 'utf8')

const uint8Array = new Uint8Array(fileContent)
Expand Down
57 changes: 55 additions & 2 deletions src/Containers/urls.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {getDownloadUrl} from '../net/github/Files'
import {getDownloadUrl, getPathContents} from '../net/github/Files'
import {parseGitHubRepositoryUrl} from '../net/github/utils'


Expand All @@ -12,7 +12,7 @@ export async function getFinalUrl(url, accessToken) {
switch (u.host.toLowerCase()) {
case 'github.com':
if (!accessToken) {
const proxyUrl = new URL(process.env.RAW_GIT_PROXY_URL)
const proxyUrl = new URL(process.env.RAW_GIT_PROXY_URL_NEW)

// Replace the protocol, host, and hostname in the target
u.protocol = proxyUrl.protocol
Expand All @@ -39,6 +39,42 @@ export async function getFinalUrl(url, accessToken) {
}
}

/**
*
*/
export async function getFinalDownloadData(url, accessToken, isOpfsAvailable) {
const u = new URL(url)

switch (u.host.toLowerCase()) {
case 'github.com':
if (!accessToken) {
const proxyUrl = new URL(isOpfsAvailable ? process.env.RAW_GIT_PROXY_URL_NEW : process.env.RAW_GIT_PROXY_URL)

// Replace the protocol, host, and hostname in the target
u.protocol = proxyUrl.protocol
u.host = proxyUrl.host
u.hostname = proxyUrl.hostname

// If the port is specified, replace it in the target URL
if (proxyUrl.port) {
u.port = proxyUrl.port
}

// If there's a path, *and* it's not just the root, then prepend it to the target URL
if (proxyUrl.pathname && proxyUrl.pathname !== '/') {
u.pathname = proxyUrl.pathname + u.pathname
}

return [u.toString(), '']
}

return await getGitHubPathContents(url, accessToken)

default:
return [url, '']
}
}


/**
* @param {string} url
Expand All @@ -58,3 +94,20 @@ async function getGitHubDownloadUrl(url, accessToken) {
)
return downloadUrl
}

/**
*
*/
async function getGitHubPathContents(url, accessToken) {
const repo = parseGitHubRepositoryUrl(url)
const [downloadUrl, sha] = await getPathContents(
{
orgName: repo.owner,
name: repo.repository,
},
repo.path,
repo.ref,
accessToken,
)
return [downloadUrl, sha]
}
6 changes: 3 additions & 3 deletions src/Containers/urls.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ describe.only('With environment variables', () => {


it.only('getFinalUrl', async () => {
expect(await getFinalUrl('https://github.com/')).toStrictEqual(`${testEnvVars.RAW_GIT_PROXY_URL}/`)
expect(await getFinalUrl('https://github.com/')).toStrictEqual(`${testEnvVars.RAW_GIT_PROXY_URL_NEW}/`)

process.env.RAW_GIT_PROXY_URL = 'https://rawgit.bldrs.dev'
expect(await getFinalUrl('https://github.com/')).toStrictEqual('https://rawgit.bldrs.dev/')
process.env.RAW_GIT_PROXY_URL = 'https://rawgit.bldrs.dev.jest/model'
expect(await getFinalUrl('https://github.com/')).toStrictEqual('https://rawgit.bldrs.dev.jest/model/')
})
})
Loading
Loading