src/viewer/scene/utils/FileLoader.js
import {Cache} from './Cache.js';
import {Loader} from './Loader.js';
import {core} from "../core.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);
core.scheduleTask(() => {
if (onLoad) {
onLoad(cached);
}
this.manager.itemEnd(url);
}, 0);
return cached;
}
if (loading[url] !== undefined) {
loading[url].push({onLoad, onProgress, onError});
return;
}
loading[url] = [];
loading[url].push({onLoad, onProgress, onError});
const req = new Request(url, {
headers: new Headers(this.requestHeader),
credentials: this.withCredentials ? 'include' : 'same-origin'
});
const mimeType = this.mimeType;
const responseType = this.responseType;
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('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;
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 (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, mimeType);
});
case 'json':
return response.json();
default:
if (mimeType === undefined) {
return response.text();
} else {
// sniff encoding
const re = /charset="?([^;"\s]*)"?/i;
const exec = re.exec(mimeType);
const label = exec && exec[1] ? exec[1].toLowerCase() : undefined;
const decoder = new TextDecoder(label);
return response.arrayBuffer().then(ab => decoder.decode(ab));
}
}
}).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};