babylon.database.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. var BABYLON = BABYLON || {};
  2. (function () {
  3. BABYLON.Database = {};
  4. var db = null;
  5. BABYLON.Database.enableSceneOffline = false;
  6. BABYLON.Database.enableTexturesOffline = false;
  7. BABYLON.Database.sceneToLoad = "";
  8. BABYLON.Database.currentSceneVersion = 0;
  9. BABYLON.Database.isUASupportingBlobStorage = true;
  10. BABYLON.Database.mustUpdateRessources = false;
  11. // Handling various flavors of prefixed version of IndexedDB
  12. window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB ||
  13. window.msIndexedDB;
  14. window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction ||
  15. window.msIDBTransaction;
  16. window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
  17. function parseURL(url) {
  18. var a = document.createElement('a');
  19. a.href = url;
  20. var fileName = url.substring(url.lastIndexOf("/") + 1, url.length);
  21. var absLocation = url.substring(0, url.indexOf(fileName, 0));
  22. return absLocation;
  23. }
  24. BABYLON.Database.CheckManifestFile = function (rootUrl, sceneFilename) {
  25. var absLocation = parseURL(window.location.href);
  26. BABYLON.Database.sceneToLoad = absLocation + rootUrl + sceneFilename;
  27. var manifestURL = BABYLON.Database.sceneToLoad + ".manifest";
  28. var xhr = new XMLHttpRequest();
  29. xhr.open("GET", manifestURL, false);
  30. xhr.addEventListener("load", function () {
  31. if (xhr.status === 200) {
  32. try {
  33. manifestFile = JSON.parse(xhr.response);
  34. BABYLON.Database.enableSceneOffline = manifestFile.enableSceneOffline;
  35. BABYLON.Database.enableTexturesOffline = manifestFile.enableTexturesOffline;
  36. if (manifestFile.version && !isNaN(parseInt(manifestFile.version))) {
  37. BABYLON.Database.currentSceneVersion = manifestFile.version;
  38. }
  39. }
  40. catch (ex) {
  41. BABYLON.Database.enableSceneOffline = false;
  42. BABYLON.Database.enableTexturesOffline = false;
  43. }
  44. }
  45. else {
  46. BABYLON.Database.enableSceneOffline = false;
  47. BABYLON.Database.enableTexturesOffline = false;
  48. }
  49. }, false);
  50. xhr.addEventListener("error", function (event) {
  51. BABYLON.Database.enableSceneOffline = false;
  52. BABYLON.Database.enableTexturesOffline = false;
  53. }, false);
  54. xhr.send();
  55. };
  56. BABYLON.Database.OpenAsync = function (successCallback, errorCallback) {
  57. if (!window.indexedDB || !(BABYLON.Database.enableSceneOffline || BABYLON.Database.enableTexturesOffline)) {
  58. // Your browser doesn't support IndexedDB
  59. BABYLON.Database.isSupported = false;
  60. if (errorCallback) errorCallback();
  61. }
  62. else {
  63. // If the DB hasn't been opened or created yet
  64. if (!db) {
  65. BABYLON.Database.hasReachedQuota = false;
  66. BABYLON.Database.isSupported = true;
  67. var request = window.indexedDB.open("babylonjs", 1.0);
  68. // Could occur if user is blocking the quota for the DB and/or doesn't grant access to IndexedDB
  69. request.onerror = function (event) {
  70. BABYLON.Database.isSupported = false;
  71. if (errorCallback) errorCallback();
  72. };
  73. // executes when a version change transaction cannot complete due to other active transactions
  74. request.onblocked = function (event) {
  75. console.log("IDB request blocked. Please reload the page.");
  76. if (errorCallback) errorCallback();
  77. };
  78. // DB has been opened successfully
  79. request.onsuccess = function (event) {
  80. db = request.result;
  81. isOpeningDB = false;
  82. console.log("DB opened.");
  83. successCallback();
  84. };
  85. // Initialization of the DB. Creating Scenes & Textures stores
  86. request.onupgradeneeded = function (event) {
  87. db = event.target.result;
  88. var scenesStore = db.createObjectStore("scenes", { keyPath: "sceneUrl" });
  89. var scenesStore = db.createObjectStore("versions", { keyPath: "sceneUrl" });
  90. var texturesStore = db.createObjectStore("textures", { keyPath: "textureUrl" });
  91. };
  92. }
  93. // DB has already been created and opened
  94. else {
  95. if (successCallback) successCallback();
  96. }
  97. }
  98. };
  99. BABYLON.Database.LoadImageFromDB = function (url, image) {
  100. var saveAndLoadImage = function (event) {
  101. if (!BABYLON.Database.hasReachedQuota && db !== null) {
  102. console.log("Saving into DB: " + url);
  103. // the texture is not yet in the DB, let's try to save it
  104. BABYLON.Database._saveImageIntoDBAsync(url, image);
  105. }
  106. // If the texture is not in the DB and we've reached the DB quota limit
  107. // let's load it directly from the web
  108. else {
  109. console.log("Image loaded directly from the web: " + url);
  110. image.src = url;
  111. }
  112. };
  113. console.log("Currently working on: " + url);
  114. if (!BABYLON.Database.mustUpdateRessources) {
  115. BABYLON.Database._loadImageFromDBAsync(url, image, saveAndLoadImage);
  116. }
  117. else {
  118. saveAndLoadImage();
  119. }
  120. };
  121. BABYLON.Database._loadImageFromDBAsync = function (url, image, notInDBCallback) {
  122. if (BABYLON.Database.isSupported && db !== null) {
  123. var indexeddbUrl = BABYLON.Database.sceneToLoad + "/" + url;
  124. var texture;
  125. var transaction = db.transaction(["textures"]);
  126. transaction.onabort = function (event) {
  127. image.src = url;
  128. };
  129. transaction.oncomplete = function (event) {
  130. var blobTextureURL;
  131. if (texture) {
  132. var URL = window.URL || window.webkitURL;
  133. blobTextureURL = URL.createObjectURL(texture.data, { oneTimeOnly: true });
  134. image.src = blobTextureURL;
  135. }
  136. else {
  137. notInDBCallback();
  138. }
  139. };
  140. var getRequest = transaction.objectStore("textures").get(indexeddbUrl);
  141. getRequest.onsuccess = function (event) {
  142. texture = event.target.result;
  143. };
  144. getRequest.onerror = function (event) {
  145. console.log("error loading texture " + indexeddbUrl + " from DB.");
  146. image.src = url;
  147. };
  148. }
  149. else {
  150. console.log("Error: IndexedDB not supported by your browser or BabylonJS Database is not open.");
  151. image.src = url;
  152. }
  153. };
  154. BABYLON.Database._saveImageIntoDBAsync = function (url, image) {
  155. if (BABYLON.Database.isSupported) {
  156. var indexeddbUrl = BABYLON.Database.sceneToLoad + "/" + url;
  157. // In case of error (type not supported or quota exceeded), we're at least sending back XHR data to allow texture loading later on
  158. var generateBlobUrl = function () {
  159. var blobTextureURL;
  160. if (blob) {
  161. var URL = window.URL || window.webkitURL;
  162. try {
  163. blobTextureURL = URL.createObjectURL(blob, { oneTimeOnly: true });
  164. }
  165. // Chrome is raising a type error if we're setting the oneTimeOnly parameter
  166. catch (ex) {
  167. blobTextureURL = URL.createObjectURL(blob);
  168. }
  169. }
  170. image.src = blobTextureURL;
  171. };
  172. if (BABYLON.Database.isUASupportingBlobStorage) {
  173. // Create XHR
  174. var xhr = new XMLHttpRequest(),
  175. blob;
  176. xhr.open("GET", url, true);
  177. xhr.responseType = "blob";
  178. xhr.addEventListener("load", function () {
  179. if (xhr.status === 200) {
  180. // Blob as response (XHR2)
  181. blob = xhr.response;
  182. // Open a transaction to the database
  183. var transaction = db.transaction(["textures"], "readwrite");
  184. // the transaction could abort because of a QuotaExceededError error
  185. transaction.onabort = function (event) {
  186. try {
  187. if (event.srcElement.error.name === "QuotaExceededError") {
  188. console.log("QUOTA EXCEEDED ERROR.");
  189. BABYLON.Database.hasReachedQuota = true;
  190. }
  191. }
  192. catch (ex) { }
  193. generateBlobUrl();
  194. };
  195. transaction.oncomplete = function (event) {
  196. generateBlobUrl();
  197. console.log("Saved into DB successfully.");
  198. };
  199. var newTexture = {};
  200. newTexture.textureUrl = indexeddbUrl;
  201. newTexture.data = blob;
  202. try {
  203. // Put the blob into the dabase
  204. var addRequest = transaction.objectStore("textures").put(newTexture);
  205. addRequest.onsuccess = function (event) {
  206. };
  207. addRequest.onerror = function (event) {
  208. generateBlobUrl();
  209. };
  210. }
  211. catch (ex) {
  212. // "DataCloneError" generated by Chrome when you try to inject blob into IndexedDB
  213. if (ex.code === 25) {
  214. BABYLON.Database.isUASupportingBlobStorage = false;
  215. console.log("Exception. Returning URL because UA doesn't support Blob in IDB.");
  216. }
  217. image.src = url;
  218. }
  219. }
  220. else {
  221. image.src = url;
  222. }
  223. }, false);
  224. xhr.addEventListener("error", function (event) {
  225. console.log("error on XHR request.");
  226. image.src = url;
  227. }, false);
  228. xhr.send();
  229. }
  230. else {
  231. console.log("Directly returning URL because UA doesn't support Blob in IDB.");
  232. image.src = url;
  233. }
  234. }
  235. else {
  236. console.log("Error: IndexedDB not supported by your browser or BabylonJS Database is not open.");
  237. image.src = url;
  238. }
  239. };
  240. BABYLON.Database._checkVersionFromDB = function (versionLoaded) {
  241. var updateVersion = function (event) {
  242. // the version is not yet in the DB or we need to update it
  243. BABYLON.Database._saveVersionIntoDBAsync(versionLoaded);
  244. };
  245. BABYLON.Database._loadVersionFromDBAsync(versionLoaded, updateVersion);
  246. };
  247. BABYLON.Database._loadVersionFromDBAsync = function (callback, updateInDBCallback) {
  248. if (BABYLON.Database.isSupported) {
  249. var version;
  250. var transaction = db.transaction(["versions"]);
  251. transaction.oncomplete = function (event) {
  252. if (version) {
  253. // If the version in the JSON file is > than the version in DB
  254. if (BABYLON.Database.currentSceneVersion > version.data) {
  255. console.log("Version change detected. Need to update DB with new ressources.");
  256. BABYLON.Database.mustUpdateRessources = true;
  257. updateInDBCallback();
  258. }
  259. else {
  260. callback(version.data);
  261. }
  262. }
  263. // version was not found in DB
  264. else {
  265. BABYLON.Database.mustUpdateRessources = true;
  266. updateInDBCallback();
  267. }
  268. };
  269. transaction.onabort = function (event) {
  270. callback(-1);
  271. };
  272. var getRequest = transaction.objectStore("versions").get(BABYLON.Database.sceneToLoad);
  273. getRequest.onsuccess = function (event) {
  274. version = event.target.result;
  275. };
  276. getRequest.onerror = function (event) {
  277. console.log("error loading version for scene " + BABYLON.Database.sceneToLoad + " from DB.");
  278. callback(-1);
  279. };
  280. }
  281. else {
  282. console.log("Error: IndexedDB not supported by your browser or BabylonJS Database is not open.");
  283. callback(-1);
  284. }
  285. };
  286. BABYLON.Database._saveVersionIntoDBAsync = function (callback) {
  287. if (BABYLON.Database.isSupported && !BABYLON.Database.hasReachedQuota) {
  288. // Open a transaction to the database
  289. var transaction = db.transaction(["versions"], "readwrite");
  290. // the transaction could abort because of a QuotaExceededError error
  291. transaction.onabort = function (event) {
  292. try {
  293. if (event.srcElement.error.name === "QuotaExceededError") {
  294. BABYLON.Database.hasReachedQuota = true;
  295. }
  296. }
  297. catch (ex) { }
  298. callback(-1);
  299. };
  300. transaction.oncomplete = function (event) {
  301. callback(BABYLON.Database.currentSceneVersion);
  302. };
  303. var newVersion = {};
  304. newVersion.sceneUrl = BABYLON.Database.sceneToLoad;
  305. newVersion.data = BABYLON.Database.currentSceneVersion;
  306. try {
  307. // Put the scene into the database
  308. var addRequest = transaction.objectStore("versions").put(newVersion);
  309. addRequest.onsuccess = function (event) {
  310. };
  311. addRequest.onerror = function (event) {
  312. console.log("error add request");
  313. };
  314. }
  315. catch (ex) {
  316. callback(-1);
  317. }
  318. }
  319. else {
  320. callback(-1);
  321. }
  322. };
  323. BABYLON.Database.LoadSceneFromDB = function (sceneLoaded, progressCallBack) {
  324. var saveAndLoadScene = function (event) {
  325. // the scene is not yet in the DB, let's try to save it
  326. BABYLON.Database._saveSceneIntoDBAsync(sceneLoaded, progressCallBack);
  327. };
  328. BABYLON.Database._checkVersionFromDB(function (version) {
  329. console.log("Version: " + version);
  330. if (!BABYLON.Database.mustUpdateRessources) {
  331. BABYLON.Database._loadSceneFromDBAsync(sceneLoaded, saveAndLoadScene);
  332. }
  333. else {
  334. BABYLON.Database._saveSceneIntoDBAsync(sceneLoaded, progressCallBack);
  335. }
  336. });
  337. };
  338. BABYLON.Database._loadSceneFromDBAsync = function (callback, notInDBCallback) {
  339. if (BABYLON.Database.isSupported) {
  340. var scene;
  341. var transaction = db.transaction(["scenes"]);
  342. transaction.oncomplete = function (event) {
  343. if (scene) {
  344. callback(scene.data);
  345. }
  346. // scene was not found in DB
  347. else {
  348. notInDBCallback();
  349. }
  350. };
  351. transaction.onabort = function (event) {
  352. notInDBCallback();
  353. };
  354. var getRequest = transaction.objectStore("scenes").get(BABYLON.Database.sceneToLoad);
  355. getRequest.onsuccess = function (event) {
  356. scene = event.target.result;
  357. };
  358. getRequest.onerror = function (event) {
  359. console.log("error loading scene " + BABYLON.Database.sceneToLoad + " from DB.");
  360. notInDBCallback();
  361. };
  362. }
  363. else {
  364. console.log("Error: IndexedDB not supported by your browser or BabylonJS Database is not open.");
  365. callback();
  366. }
  367. };
  368. BABYLON.Database._saveSceneIntoDBAsync = function (callback, progressCallback) {
  369. if (BABYLON.Database.isSupported) {
  370. // Create XHR
  371. var xhr = new XMLHttpRequest(), sceneText;
  372. xhr.open("GET", BABYLON.Database.sceneToLoad, true);
  373. xhr.onprogress = progressCallback;
  374. xhr.addEventListener("load", function () {
  375. if (xhr.status === 200) {
  376. // Blob as response (XHR2)
  377. sceneText = xhr.responseText;
  378. if (!BABYLON.Database.hasReachedQuota) {
  379. // Open a transaction to the database
  380. var transaction = db.transaction(["scenes"], "readwrite");
  381. // the transaction could abort because of a QuotaExceededError error
  382. transaction.onabort = function (event) {
  383. try {
  384. if (event.srcElement.error.name === "QuotaExceededError") {
  385. BABYLON.Database.hasReachedQuota = true;
  386. }
  387. }
  388. catch (ex) { }
  389. callback(sceneText);
  390. };
  391. transaction.oncomplete = function (event) {
  392. callback(sceneText);
  393. };
  394. var newScene = {};
  395. newScene.sceneUrl = BABYLON.Database.sceneToLoad;
  396. newScene.data = sceneText;
  397. newScene.version = BABYLON.Database.currentSceneVersion;
  398. try {
  399. // Put the scene into the database
  400. var addRequest = transaction.objectStore("scenes").put(newScene);
  401. addRequest.onsuccess = function (event) {
  402. };
  403. addRequest.onerror = function (event) {
  404. console.log("error add request");
  405. };
  406. }
  407. catch (ex) {
  408. callback(sceneText);
  409. }
  410. }
  411. else {
  412. callback(sceneText);
  413. }
  414. }
  415. else {
  416. callback();
  417. }
  418. }, false);
  419. xhr.addEventListener("error", function (event) {
  420. console.log("error on XHR request.");
  421. callback();
  422. }, false);
  423. xhr.send();
  424. }
  425. else {
  426. console.log("Error: IndexedDB not supported by your browser or BabylonJS Database is not open.");
  427. callback();
  428. }
  429. };
  430. // Called to close the db and reset the objects
  431. BABYLON.Database.Release = function () {
  432. if (db) {
  433. db.close();
  434. db = null;
  435. console.log("DB closed.");
  436. BABYLON.Database.hasReachedQuota = false;
  437. BABYLON.Database.mustUpdateRessources = false;
  438. BABYLON.Database.enableSceneOffline = false;
  439. BABYLON.Database.enableTexturesOffline = false;
  440. BABYLON.Database.sceneToLoad = "";
  441. BABYLON.Database.currentSceneVersion = 0;
  442. }
  443. };
  444. })();