@speckle/objectloader2
Version:
This is an updated objectloader for the Speckle viewer written in typescript
218 lines (193 loc) • 6.15 kB
text/typescript
import BatchedPool from '../../helpers/batchedPool.js'
import Queue from '../../helpers/queue.js'
import { ObjectLoaderRuntimeError } from '../../types/errors.js'
import { Fetcher, isBase, Item, take } from '../../types/types.js'
import { Downloader } from '../interfaces.js'
export interface ServerDownloaderOptions {
serverUrl: string
streamId: string
objectId: string
token?: string
headers?: Headers
fetch?: Fetcher
}
export default class ServerDownloader implements Downloader {
constructor(options: ServerDownloaderOptions) {
this.
this.
options.fetch ?? ((...args): Promise<Response> => globalThis.fetch(...args))
this.
if (options.headers) {
for (const header of options.headers.entries()) {
this.
}
}
this.
if (this.
this.
}
this.
this.
}`
this.
this.
}/${this.
}
if (total <= 50) {
return [total]
}
return [10000, 25000, 10000, 1000]
}
initializePool(params: {
results: Queue<Item>
total: number
maxDownloadBatchWait?: number
}): void {
const { results, total } = params
this.
this.
concurrencyAndSizes: this.
maxWaitTime: params.maxDownloadBatchWait,
processFunction: (batch: string[]): Promise<void> =>
this.downloadBatch({
batch,
url: this.
headers: this.
})
})
}
if (this.
return this.
}
throw new Error('Download pool is not initialized')
}
add(id: string): void {
this.
}
async disposeAsync(): Promise<void> {
await this.
}
let base: unknown
try {
base = JSON.parse(unparsedBase)
} catch (e: unknown) {
throw new Error(`Error parsing object ${baseId}: ${(e as Error).message}`)
}
if (isBase(base)) {
return { baseId, base }
} else {
throw new ObjectLoaderRuntimeError(`${baseId} is not a base`)
}
}
async downloadBatch(params: {
batch: string[]
url: string
headers: HeadersInit
}): Promise<void> {
const { batch, url, headers } = params
const keys = new Set<string>(batch)
const response = await this.
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify({ objects: JSON.stringify(batch) })
})
this.
if (!response.body) {
throw new Error('ReadableStream not supported or response has no body.')
}
const reader = response.body.getReader()
let leftover = new Uint8Array(0)
let count = 0
while (true) {
const { done, value } = await reader.read()
if (done) break
leftover = await this.processArray(leftover, value, keys, async () => {
count++
if (count % 1000 === 0) {
await new Promise((resolve) => setTimeout(resolve, 100)) //allow other stuff to happen
}
})
}
if (keys.size > 0) {
throw new Error(
'Items requested were not downloaded: ' + take(keys.values(), 10).join(',')
)
}
}
async processArray(
leftover: Uint8Array,
value: Uint8Array,
keys: Set<string>,
callback: () => Promise<void>
): Promise<Uint8Array> {
//this concat will allocate a new array
const combined = this.concatUint8Arrays(leftover, value)
let start = 0
//subarray doesn't allocate
for (let i = 0; i < combined.length; i++) {
if (combined[i] === 0x0a) {
const line = combined.subarray(start, i) // line without \n
//strings are allocated here
const item = this.processLine(line)
this.
start = i + 1
await callback()
keys.delete(item.baseId)
}
}
return combined.subarray(start) // carry over remainder
}
processLine(line: Uint8Array): Item {
for (let i = 0; i < line.length; i++) {
if (line[i] === 0x09) {
//this is a tab
const baseId = this.
const json = line.subarray(i + 1)
const base = this.
const item = this.
item.size = json.length
return item
}
}
throw new ObjectLoaderRuntimeError(
'Invalid line format: ' + this.
)
}
concatUint8Arrays(a: Uint8Array, b: Uint8Array): Uint8Array {
const c = new Uint8Array(a.length + b.length)
c.set(a, 0)
c.set(b, a.length)
return c
}
async downloadSingle(): Promise<Item> {
const response = await this.
headers: this.
})
this.
const responseText = await response.text()
const item = this.
item.size = 0
return item
}
if (!response.ok) {
if ([401, 403].includes(response.status)) {
throw new ObjectLoaderRuntimeError('You do not have access!')
}
throw new ObjectLoaderRuntimeError(
`Failed to fetch objects: ${response.status} ${response.statusText})`
)
}
}
}