From 8b986b5ccd4613acb45501b0b35054024d8fc7dd Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Wed, 26 Jan 2022 20:26:03 +1100 Subject: [PATCH 1/2] Integrate minimal IDBFS copied from emscripten directly into interop_web.js --- src/interop_web.js | 179 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 167 insertions(+), 12 deletions(-) diff --git a/src/interop_web.js b/src/interop_web.js index 093b89203..129c6635c 100644 --- a/src/interop_web.js +++ b/src/interop_web.js @@ -253,11 +253,13 @@ mergeInto(LibraryManager.library, { //######################################################################################################################## //--------------------------------------------------------Filesystem------------------------------------------------------ //######################################################################################################################## + interop_InitFilesystem__deps: ['interop_SaveNode'], interop_InitFilesystem: function(buffer) { if (!window.cc_idbErr) return; var msg = 'Error preloading IndexedDB:' + window.cc_idbErr + '\n\nPreviously saved settings/maps will be lost'; ccall('Platform_LogError', 'void', ['string'], [msg]); }, + interop_LoadIndexedDB__deps: ['IDBFS_loadFS'], interop_LoadIndexedDB: function() { try { FS.lookupPath('/classicube'); @@ -267,13 +269,12 @@ mergeInto(LibraryManager.library, { addRunDependency('load-idb'); FS.mkdir('/classicube'); - FS.mount(IDBFS, {}, '/classicube'); - FS.syncfs(true, function(err) { + _IDBFS_loadFS(function(err) { if (err) window.cc_idbErr = err; removeRunDependency('load-idb'); }); }, - interop_InitFilesystem__deps: ['interop_SaveNode'], + interop_SaveNode__deps: ['IDBFS_getDB', 'IDBFS_storeRemoteEntry'], interop_SaveNode: function(path) { var callback = function(err) { if (!err) return; @@ -301,14 +302,14 @@ mergeInto(LibraryManager.library, { return callback(err); } - IDBFS.getDB('/classicube', function(err, db) { + _IDBFS_getDB(function(err, db) { if (err) return callback(err); var transaction, store; // can still throw errors here try { - transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readwrite'); - store = transaction.objectStore(IDBFS.DB_STORE_NAME); + transaction = db.transaction([IDBFS_DB_STORE_NAME], 'readwrite'); + store = transaction.objectStore(IDBFS_DB_STORE_NAME); } catch (err) { return callback(err); } @@ -318,15 +319,169 @@ mergeInto(LibraryManager.library, { e.preventDefault(); }; - var req = store.put(entry, path); - req.onsuccess = function() { callback(null); }; - req.onerror = function(e) { - callback(this.error); - e.preventDefault(); - }; + _IDBFS_storeRemoteEntry(store, path, entry, callback); }); }, +//######################################################################################################################## +//--------------------------------------------------------IndexedDB------------------------------------------------------- +//######################################################################################################################## + IDBFS_loadFS__deps: ['IDBFS_getRemoteSet', 'IDBFS_reconcile'], + IDBFS_loadFS: function(callback) { + _IDBFS_getRemoteSet(function(err, remote) { + if (err) return callback(err); + _IDBFS_reconcile(remote, callback); + }); + }, + IDBFS_getDB: function(callback) { + var db = window.IDBFS_db; + if (db) return callback(null, db); + + IDBFS_DB_VERSION = 21; + IDBFS_DB_STORE_NAME = "FILE_DATA"; + var idb = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; + if (!idb) return callback("IndexedDB unsupported"); + + var req; + try { + req = idb.open('/classicube', IDBFS_DB_VERSION); + } catch (e) { + return callback(e); + } + if (!req) return callback("Unable to connect to IndexedDB"); + + req.onupgradeneeded = function(e) { + var db = e.target.result; + var transaction = e.target.transaction; + var fileStore; + + if (db.objectStoreNames.contains(IDBFS_DB_STORE_NAME)) { + fileStore = transaction.objectStore(IDBFS_DB_STORE_NAME); + } else { + fileStore = db.createObjectStore(IDBFS_DB_STORE_NAME); + } + + if (!fileStore.indexNames.contains('timestamp')) { + fileStore.createIndex('timestamp', 'timestamp', { unique: false }); + } + }; + req.onsuccess = function() { + db = req.result; + window.IDBFS_db = db; + callback(null, db); + }; + req.onerror = function(e) { + callback(this.error); + e.preventDefault(); + }; + }, + IDBFS_getRemoteSet__deps: ['IDBFS_getDB'], + IDBFS_getRemoteSet: function(callback) { + var entries = {}; + + _IDBFS_getDB(function(err, db) { + if (err) return callback(err); + + try { + var transaction = db.transaction([IDBFS_DB_STORE_NAME], 'readonly'); + transaction.onerror = function(e) { + callback(this.error); + e.preventDefault(); + }; + + var store = transaction.objectStore(IDBFS_DB_STORE_NAME); + var index = store.index('timestamp'); + + index.openKeyCursor().onsuccess = function(event) { + var cursor = event.target.result; + + if (!cursor) { + return callback(null, { type: 'remote', db: db, entries: entries }); + } + + entries[cursor.primaryKey] = { timestamp: cursor.key }; + cursor.continue(); + }; + } catch (e) { + return callback(e); + } + }); + }, + IDBFS_loadRemoteEntry: function(store, path, callback) { + var req = store.get(path); + req.onsuccess = function(event) { callback(null, event.target.result); }; + req.onerror = function(e) { + callback(this.error); + e.preventDefault(); + }; + }, + IDBFS_storeRemoteEntry: function(store, path, entry, callback) { + var req = store.put(entry, path); + req.onsuccess = function() { callback(null); }; + req.onerror = function(e) { + callback(this.error); + e.preventDefault(); + }; + }, + IDBFS_storeLocalEntry: function(path, entry, callback) { + try { + if (FS.isDir(entry.mode)) { + FS.mkdir(path, entry.mode); + } else { + FS.writeFile(path, entry.contents, { canOwn: true }); + } + + FS.chmod(path, entry.mode); + FS.utime(path, entry.timestamp, entry.timestamp); + } catch (e) { + return callback(e); + } + + callback(null); + }, + IDBFS_reconcile__deps: ['IDBFS_loadRemoteEntry', 'IDBFS_storeLocalEntry'], + IDBFS_reconcile: function(src, callback) { + var total = 0; + var create = []; + + Object.keys(src.entries).forEach(function (key) { + create.push(key); + total++; + }); + if (!total) return callback(null); + + var errored = false; + var completed = 0; + var transaction = src.db.transaction([IDBFS_DB_STORE_NAME], 'readwrite'); + var store = transaction.objectStore(IDBFS_DB_STORE_NAME); + + function done(err) { + if (err) { + if (!done.errored) { + done.errored = true; + return callback(err); + } + return; + } + if (++completed >= total) { + return callback(null); + } + }; + + transaction.onerror = function(e) { + done(this.error); + e.preventDefault(); + }; + + // sort paths in ascending order so directory entries are created + // before the files inside them + create.sort().forEach(function (path) { + _IDBFS_loadRemoteEntry(store, path, function (err, entry) { + if (err) return done(err); + _IDBFS_storeLocalEntry(path, entry, done); + }); + }); + }, //######################################################################################################################## //---------------------------------------------------------Sockets-------------------------------------------------------- From 87bcdd1e434027d31595fae94e542184b1dbae12 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Wed, 26 Jan 2022 20:52:50 +1100 Subject: [PATCH 2/2] Try to fix 'The database connection is closing' issues Browsers may rarely randomly close the IndexedDB connection, which would prevent the game from being able to save options or maps anymore until the user restarted the game. With this commit, the game now listens for the 'onclose' event on the IDBConnection for when the connection is unexpectedly closed - and when the 'onclose' event is raised, clears the cached IndexedDB connection global so that the next call to getDB opens a fresh IndexedDB connection. I tested this by deleting the IndexedDB database in developer tools (which triggered 'onclose' event) and seemed to work fine --- src/interop_web.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/interop_web.js b/src/interop_web.js index 129c6635c..efb7cb7ab 100644 --- a/src/interop_web.js +++ b/src/interop_web.js @@ -368,6 +368,11 @@ mergeInto(LibraryManager.library, { req.onsuccess = function() { db = req.result; window.IDBFS_db = db; + // browser will sometimes close connection behind the scenes + db.onclose = function(ev) { + console.log('IndexedDB connection closed unexpectedly!'); + window.IDBFS_db = null; + } callback(null, db); }; req.onerror = function(e) {