k-l-lambda's picture
feat: add Python ML services (CPU mode) with model download
2b7aae2
import { Cache } from './Cache.js';
import { Loader } from './Loader.js';
const loading = {};
class FileLoader extends Loader {
constructor(manager) {
super(manager);
}
load(url, onLoad, onProgress, onError) {
if (url === undefined) url = '';
if (this.path !== undefined) url = this.path + url;
url = this.manager.resolveURL(url);
const cached = Cache.get(url);
if (cached !== undefined) {
this.manager.itemStart(url);
setTimeout(() => {
if (onLoad) onLoad(cached);
this.manager.itemEnd(url);
}, 0);
return cached;
}
// Check if request is duplicate
if (loading[url] !== undefined) {
loading[url].push({
onLoad: onLoad,
onProgress: onProgress,
onError: onError,
});
return;
}
// Initialise array for duplicate requests
loading[url] = [];
loading[url].push({
onLoad: onLoad,
onProgress: onProgress,
onError: onError,
});
// create request
const req = new Request(url, {
headers: new Headers(this.requestHeader),
credentials: this.withCredentials ? 'include' : 'same-origin',
// An abort controller could be added within a future PR
});
// start the fetch
fetch(req)
.then((response) => {
if (response.status === 200 || response.status === 0) {
// Some browsers return HTTP Status 0 when using non-http protocol
// e.g. 'file://' or 'data://'. Handle as success.
if (response.status === 0) {
console.warn('THREE.FileLoader: HTTP Status 0 received.');
}
if (typeof ReadableStream === 'undefined' || response.body.getReader === undefined) {
return response;
}
const callbacks = loading[url];
const reader = response.body.getReader();
const contentLength = response.headers.get('Content-Length');
const total = contentLength ? parseInt(contentLength) : 0;
const lengthComputable = total !== 0;
let loaded = 0;
// periodically read data into the new stream tracking while download progress
const stream = new ReadableStream({
start(controller) {
readData();
function readData() {
reader.read().then(({ done, value }) => {
if (done) {
controller.close();
} else {
loaded += value.byteLength;
const event = new ProgressEvent('progress', { lengthComputable, loaded, total });
for (let i = 0, il = callbacks.length; i < il; i++) {
const callback = callbacks[i];
if (callback.onProgress) callback.onProgress(event);
}
controller.enqueue(value);
readData();
}
});
}
},
});
return new Response(stream);
} else {
throw Error(`fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`);
}
})
.then((response) => {
switch (this.responseType) {
case 'arraybuffer':
return response.arrayBuffer();
case 'blob':
return response.blob();
case 'document':
return response.text().then((text) => {
const parser = new DOMParser();
return parser.parseFromString(text, this.mimeType);
});
case 'json':
return response.json();
default:
return response.text();
}
})
.then((data) => {
// Add to cache only on HTTP success, so that we do not cache
// error response bodies as proper responses to requests.
Cache.add(url, data);
const callbacks = loading[url];
delete loading[url];
for (let i = 0, il = callbacks.length; i < il; i++) {
const callback = callbacks[i];
if (callback.onLoad) callback.onLoad(data);
}
})
.catch((err) => {
// Abort errors and other errors are handled the same
const callbacks = loading[url];
if (callbacks === undefined) {
// When onLoad was called and url was deleted in `loading`
this.manager.itemError(url);
throw err;
}
delete loading[url];
for (let i = 0, il = callbacks.length; i < il; i++) {
const callback = callbacks[i];
if (callback.onError) callback.onError(err);
}
this.manager.itemError(url);
})
.finally(() => {
this.manager.itemEnd(url);
});
this.manager.itemStart(url);
}
setResponseType(value) {
this.responseType = value;
return this;
}
setMimeType(value) {
this.mimeType = value;
return this;
}
}
export { FileLoader };