var BABYLON = BABYLON || {}; (function () { // Handling various flavors of prefixed version of IndexedDB window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction; window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange; BABYLON.Database = function (urlToScene) { this.currentSceneUrl = BABYLON.Database.ReturnFullUrlLocation(urlToScene); this.db = null; this.enableSceneOffline = false; this.enableTexturesOffline = false; this.manifestVersionFound = 0; this.mustUpdateRessources = false; this.hasReachedQuota = false; this.checkManifestFile(); }; BABYLON.Database.isUASupportingBlobStorage = true; function parseURL(url) { var a = document.createElement('a'); a.href = url; var fileName = url.substring(url.lastIndexOf("/") + 1, url.length); var absLocation = url.substring(0, url.indexOf(fileName, 0)); return absLocation; }; BABYLON.Database.ReturnFullUrlLocation = function (url) { if (url.indexOf("http:/") === -1) { return (parseURL(window.location.href) + url); } else { return url; } }; BABYLON.Database.prototype.checkManifestFile = function () { var that = this; var manifestURL = this.currentSceneUrl + ".manifest"; var xhr = new XMLHttpRequest(); xhr.open("GET", manifestURL, false); xhr.addEventListener("load", function () { if (xhr.status === 200) { try { manifestFile = JSON.parse(xhr.response); that.enableSceneOffline = manifestFile.enableSceneOffline; that.enableTexturesOffline = manifestFile.enableTexturesOffline; if (manifestFile.version && !isNaN(parseInt(manifestFile.version))) { that.manifestVersionFound = manifestFile.version; } } catch (ex) { that.enableSceneOffline = false; that.enableTexturesOffline = false; } } else { that.enableSceneOffline = false; that.enableTexturesOffline = false; } }, false); xhr.addEventListener("error", function (event) { that.enableSceneOffline = false; that.enableTexturesOffline = false; }, false); xhr.send(); }; BABYLON.Database.prototype.openAsync = function (successCallback, errorCallback) { var that = this; if (!window.indexedDB || !(this.enableSceneOffline || this.enableTexturesOffline)) { // Your browser doesn't support IndexedDB this.isSupported = false; if (errorCallback) errorCallback(); } else { // If the DB hasn't been opened or created yet if (!this.db) { this.hasReachedQuota = false; this.isSupported = true; var request = window.indexedDB.open("babylonjs", 1.0); // Could occur if user is blocking the quota for the DB and/or doesn't grant access to IndexedDB request.onerror = function (event) { that.isSupported = false; if (errorCallback) errorCallback(); }; // executes when a version change transaction cannot complete due to other active transactions request.onblocked = function (event) { console.log("IDB request blocked. Please reload the page."); if (errorCallback) errorCallback(); }; // DB has been opened successfully request.onsuccess = function (event) { that.db = request.result; successCallback(); }; // Initialization of the DB. Creating Scenes & Textures stores request.onupgradeneeded = function (event) { that.db = event.target.result; var scenesStore = that.db.createObjectStore("scenes", { keyPath: "sceneUrl" }); var versionsStore = that.db.createObjectStore("versions", { keyPath: "sceneUrl" }); var texturesStore = that.db.createObjectStore("textures", { keyPath: "textureUrl" }); }; } // DB has already been created and opened else { if (successCallback) successCallback(); } } }; BABYLON.Database.prototype.loadImageFromDB = function (url, image) { var that = this; var completeURL = BABYLON.Database.ReturnFullUrlLocation(url); var saveAndLoadImage = function (event) { if (!that.hasReachedQuota && that.db !== null) { // the texture is not yet in the DB, let's try to save it that._saveImageIntoDBAsync(completeURL, image); } // If the texture is not in the DB and we've reached the DB quota limit // let's load it directly from the web else { image.src = url; } }; if (!this.mustUpdateRessources) { this._loadImageFromDBAsync(completeURL, image, saveAndLoadImage); } // First time we're download the images or update requested in the manifest file by a version change else { saveAndLoadImage(); } }; BABYLON.Database.prototype._loadImageFromDBAsync = function (url, image, notInDBCallback) { if (this.isSupported && this.db !== null) { var texture; var transaction = this.db.transaction(["textures"]); transaction.onabort = function (event) { image.src = url; }; transaction.oncomplete = function (event) { var blobTextureURL; if (texture) { var URL = window.URL || window.webkitURL; blobTextureURL = URL.createObjectURL(texture.data, { oneTimeOnly: true }); image.src = blobTextureURL; } else { notInDBCallback(); } }; var getRequest = transaction.objectStore("textures").get(url); getRequest.onsuccess = function (event) { texture = event.target.result; }; getRequest.onerror = function (event) { console.log("Error loading texture " + url + " from DB."); image.src = url; }; } else { console.log("Error: IndexedDB not supported by your browser or BabylonJS Database is not open."); image.src = url; } }; BABYLON.Database.prototype._saveImageIntoDBAsync = function (url, image) { if (this.isSupported) { // In case of error (type not supported or quota exceeded), we're at least sending back XHR data to allow texture loading later on var generateBlobUrl = function () { var blobTextureURL; if (blob) { var URL = window.URL || window.webkitURL; try { blobTextureURL = URL.createObjectURL(blob, { oneTimeOnly: true }); } // Chrome is raising a type error if we're setting the oneTimeOnly parameter catch (ex) { blobTextureURL = URL.createObjectURL(blob); } } image.src = blobTextureURL; }; if (BABYLON.Database.isUASupportingBlobStorage) { var that = this; // Create XHR var xhr = new XMLHttpRequest(), blob; xhr.open("GET", url, true); xhr.responseType = "blob"; xhr.addEventListener("load", function () { if (xhr.status === 200) { // Blob as response (XHR2) blob = xhr.response; var transaction = that.db.transaction(["textures"], "readwrite"); // the transaction could abort because of a QuotaExceededError error transaction.onabort = function (event) { try { if (event.srcElement.error.name === "QuotaExceededError") { that.hasReachedQuota = true; } } catch (ex) { } generateBlobUrl(); }; transaction.oncomplete = function (event) { generateBlobUrl(); }; var newTexture = {}; newTexture.textureUrl = url; newTexture.data = blob; try { // Put the blob into the dabase var addRequest = transaction.objectStore("textures").put(newTexture); addRequest.onsuccess = function (event) { console.log(""); }; addRequest.onerror = function (event) { generateBlobUrl(); }; } catch (ex) { // "DataCloneError" generated by Chrome when you try to inject blob into IndexedDB if (ex.code === 25) { BABYLON.Database.isUASupportingBlobStorage = false; } image.src = url; } } else { image.src = url; } }, false); xhr.addEventListener("error", function (event) { console.log("Error in XHR request in BABYLON.Database."); image.src = url; }, false); xhr.send(); } else { image.src = url; } } else { console.log("Error: IndexedDB not supported by your browser or BabylonJS Database is not open."); image.src = url; } }; BABYLON.Database.prototype._checkVersionFromDB = function (url, versionLoaded) { var that = this; var updateVersion = function (event) { // the version is not yet in the DB or we need to update it that._saveVersionIntoDBAsync(url, versionLoaded); }; this._loadVersionFromDBAsync(url, versionLoaded, updateVersion); }; BABYLON.Database.prototype._loadVersionFromDBAsync = function (url, callback, updateInDBCallback) { if (this.isSupported) { var version; var that = this; var transaction = this.db.transaction(["versions"]); transaction.oncomplete = function (event) { if (version) { // If the version in the JSON file is > than the version in DB if (that.manifestVersionFound > version.data) { that.mustUpdateRessources = true; updateInDBCallback(); } else { callback(version.data); } } // version was not found in DB else { that.mustUpdateRessources = true; updateInDBCallback(); } }; transaction.onabort = function (event) { callback(-1); }; var getRequest = transaction.objectStore("versions").get(url); getRequest.onsuccess = function (event) { version = event.target.result; }; getRequest.onerror = function (event) { console.log("Error loading version for scene " + url + " from DB."); callback(-1); }; } else { console.log("Error: IndexedDB not supported by your browser or BabylonJS Database is not open."); callback(-1); } }; BABYLON.Database.prototype._saveVersionIntoDBAsync = function (url, callback) { if (this.isSupported && !this.hasReachedQuota) { var that = this; // Open a transaction to the database var transaction = this.db.transaction(["versions"], "readwrite"); // the transaction could abort because of a QuotaExceededError error transaction.onabort = function (event) { try { if (event.srcElement.error.name === "QuotaExceededError") { that.hasReachedQuota = true; } } catch (ex) { } callback(-1); }; transaction.oncomplete = function (event) { callback(that.manifestVersionFound); }; var newVersion = {}; newVersion.sceneUrl = url; newVersion.data = this.manifestVersionFound; try { // Put the scene into the database var addRequest = transaction.objectStore("versions").put(newVersion); addRequest.onsuccess = function (event) { console.log(""); }; addRequest.onerror = function (event) { console.log("Error in DB add version request in BABYLON.Database."); }; } catch (ex) { callback(-1); } } else { callback(-1); } }; BABYLON.Database.prototype.loadSceneFromDB = function (url, sceneLoaded, progressCallBack) { var that = this; var completeUrl = BABYLON.Database.ReturnFullUrlLocation(url); var saveAndLoadScene = function (event) { // the scene is not yet in the DB, let's try to save it that._saveSceneIntoDBAsync(completeUrl, sceneLoaded, progressCallBack); }; this._checkVersionFromDB(completeUrl, function (version) { if (!that.mustUpdateRessources) { that._loadSceneFromDBAsync(completeUrl, sceneLoaded, saveAndLoadScene); } else { that._saveSceneIntoDBAsync(completeUrl, sceneLoaded, progressCallBack); } }); }; BABYLON.Database.prototype._loadSceneFromDBAsync = function (url, callback, notInDBCallback) { if (this.isSupported) { var scene; var transaction = this.db.transaction(["scenes"]); transaction.oncomplete = function (event) { if (scene) { callback(scene.data); } // scene was not found in DB else { notInDBCallback(); } }; transaction.onabort = function (event) { notInDBCallback(); }; var getRequest = transaction.objectStore("scenes").get(url); getRequest.onsuccess = function (event) { scene = event.target.result; }; getRequest.onerror = function (event) { console.log("Error loading scene " + url + " from DB."); notInDBCallback(); }; } else { console.log("Error: IndexedDB not supported by your browser or BabylonJS Database is not open."); callback(); } }; BABYLON.Database.prototype._saveSceneIntoDBAsync = function (url, callback, progressCallback) { if (this.isSupported) { // Create XHR var xhr = new XMLHttpRequest(), sceneText; var that = this; xhr.open("GET", url, true); xhr.onprogress = progressCallback; xhr.addEventListener("load", function () { if (xhr.status === 200) { // Blob as response (XHR2) sceneText = xhr.responseText; if (!that.hasReachedQuota) { // Open a transaction to the database var transaction = that.db.transaction(["scenes"], "readwrite"); // the transaction could abort because of a QuotaExceededError error transaction.onabort = function (event) { try { if (event.srcElement.error.name === "QuotaExceededError") { that.hasReachedQuota = true; } } catch (ex) { } callback(sceneText); }; transaction.oncomplete = function (event) { callback(sceneText); }; var newScene = {}; newScene.sceneUrl = url; newScene.data = sceneText; newScene.version = that.manifestVersionFound; try { // Put the scene into the database var addRequest = transaction.objectStore("scenes").put(newScene); addRequest.onsuccess = function (event) { console.log(""); }; addRequest.onerror = function (event) { console.log("Error in DB add scene request in BABYLON.Database."); }; } catch (ex) { callback(sceneText); } } else { callback(sceneText); } } else { callback(); } }, false); xhr.addEventListener("error", function (event) { console.log("error on XHR request."); callback(); }, false); xhr.send(); } else { console.log("Error: IndexedDB not supported by your browser or BabylonJS Database is not open."); callback(); } }; })();