278 lines
8.1 KiB
JavaScript
278 lines
8.1 KiB
JavaScript
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
|
|
"use strict";
|
|
|
|
(function() {
|
|
|
|
let moto = self.moto = self.moto || {};
|
|
if (moto.IDB) return;
|
|
|
|
moto.IDB = moto.Storage = Storage;
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
|
|
|
|
let IDB, IRR, local = null;
|
|
|
|
try {
|
|
IDB = self.indexedDB || self.mozIndexedDB || self.webkitIndexedDB || self.msIndexedDB;
|
|
IRR = self.IDBKeyRange;
|
|
} catch (e) {
|
|
console.log("in private or restricted browsing mode. database storage blocked. application may not function.");
|
|
}
|
|
|
|
/**
|
|
* @param {String} dbname
|
|
* @param {number} [version]
|
|
* @constructor
|
|
*/
|
|
function Storage(dbname, options = {}) {
|
|
this.db = null;
|
|
this.name = dbname;
|
|
this.stores = options.stores || [ dbname ];
|
|
this.version = options.version || 1;
|
|
this.queue = [];
|
|
this.idle = [];
|
|
this.initCalled = false;
|
|
}
|
|
|
|
Storage.delete = function(dbname) {
|
|
IDB.deleteDatabase(dbname);
|
|
};
|
|
|
|
let SP = Storage.prototype;
|
|
|
|
/** ******************************************************************
|
|
* indexedDB implementation
|
|
******************************************************************* */
|
|
|
|
SP.onIdle = function(fn) {
|
|
if (this.queue.length) {
|
|
this.idle.push(fn);
|
|
} else {
|
|
fn();
|
|
}
|
|
return this;
|
|
};
|
|
|
|
SP.promise = function() {
|
|
let db = this;
|
|
return {
|
|
keys: (lower, upper) => new Promise(function(resolve, reject) {
|
|
db.keys(resolve,lower,upper);
|
|
}),
|
|
|
|
iterate: (lower, upper, nullterm) => new Promise(function(resolve, reject) {
|
|
db.iterate(resolve, lower, upper, nullterm);
|
|
}),
|
|
|
|
get: (key) => new Promise(function(resolve, reject) {
|
|
db.get(key, resolve);
|
|
}),
|
|
|
|
put: (key, value) => new Promise(function(resolve, reject) {
|
|
db.put(key, value, resolve);
|
|
}),
|
|
|
|
remove: (key) => new Promise(function(resolve, reject) {
|
|
db.remove(key, resolve);
|
|
})
|
|
};
|
|
}
|
|
|
|
SP.keys = function(callback, lower, upper) {
|
|
let out = [];
|
|
this.iterate(function(k,v) {
|
|
if (k === null) {
|
|
callback(out);
|
|
} else {
|
|
out.push(k);
|
|
}
|
|
}, lower, upper, true);
|
|
};
|
|
|
|
SP.iterate = function(callback, lower, upper, nullterm) {
|
|
if (!this.db) {
|
|
this.init();
|
|
return this.queue.push(["iterate", callback, lower, upper]);
|
|
}
|
|
let range = lower && upper ? IRR.bound(lower,upper) :
|
|
lower ? IRR.lowerBound(lower) :
|
|
upper ? IRR.upperBound(upper) : undefined;
|
|
// iterate over all db values for debugging
|
|
this.db
|
|
.transaction(this.name)
|
|
.objectStore(this.current)
|
|
.openCursor(range).onsuccess = function(event) {
|
|
let cursor = event.target.result;
|
|
if (cursor) {
|
|
callback(cursor.key,cursor.value);
|
|
cursor.continue();
|
|
} else if (nullterm) {
|
|
callback(null);
|
|
}
|
|
};
|
|
};
|
|
|
|
SP.deleteStore = function() {
|
|
if (this.initCalled) throw "cannot delete ObjectStore after init() called";
|
|
this.version = Number.MAX_SAFE_INTEGER || Number.MAX_VALUE;
|
|
return this.init(true);
|
|
};
|
|
|
|
SP.use = function(store) {
|
|
this.current = store;
|
|
return this;
|
|
}
|
|
|
|
SP.init = function(options = {}) {
|
|
if (this.initCalled) return;
|
|
|
|
let storage = this,
|
|
name = this.name,
|
|
request = null;
|
|
|
|
function fallback() {
|
|
console.log("in private browsing mode or browser lacks support for IndexedDB. unable to setup storage for '" + name + "'.");
|
|
local = {};
|
|
storage.runQueue();
|
|
}
|
|
|
|
try {
|
|
request = IDB.open(name, this.version);
|
|
|
|
request.onupgradeneeded = function(event) {
|
|
if (options.delete === true) {
|
|
storage.db.deleteObjectStore(name);
|
|
return;
|
|
}
|
|
let db = storage.db = request.result;
|
|
for (let store of storage.stores) {
|
|
storage.store = db.createObjectStore(store);
|
|
storage.current = store;
|
|
}
|
|
event.target.transaction.oncomplete = function(event) {
|
|
storage.runQueue();
|
|
};
|
|
};
|
|
|
|
request.onsuccess = function(event) {
|
|
storage.current = storage.stores[0];
|
|
storage.db = request.result;
|
|
storage.runQueue();
|
|
};
|
|
|
|
request.onerror = function(event) {
|
|
console.log({error:event});
|
|
fallback();
|
|
};
|
|
} catch (e) {
|
|
fallback();
|
|
return;
|
|
}
|
|
|
|
this.initCalled = true;
|
|
return this;
|
|
};
|
|
|
|
SP.runQueue = function() {
|
|
if (this.queue.length > 0) {
|
|
let i = 0, q = this.queue, e;
|
|
while (i < q.length) {
|
|
e = q[i++];
|
|
switch (e[0]) {
|
|
case 'iterate': this.iterate(e[1], e[2], e[3]); break;
|
|
case 'put': this.put(e[1], e[2], e[3]); break;
|
|
case 'get': this.get(e[1], e[2]); break;
|
|
case 'remove': this.remove(e[1], e[2]); break;
|
|
case 'clear': this.clear();
|
|
}
|
|
}
|
|
this.queue = [];
|
|
}
|
|
while (this.idle.length) {
|
|
this.idle.shift()();
|
|
}
|
|
};
|
|
|
|
SP.put = function(key, value, callback) {
|
|
if (local) {
|
|
local[key] = value;
|
|
if (callback) callback(true);
|
|
return;
|
|
}
|
|
if (!this.db) {
|
|
this.init();
|
|
return this.queue.push(['put', key, value, callback]);
|
|
}
|
|
try {
|
|
let req = this.db
|
|
.transaction(this.name, "readwrite")
|
|
.objectStore(this.current).put(value, key);
|
|
if (callback) {
|
|
req.onsuccess = function(event) { callback(true) };
|
|
req.onerror = function(event) { callback(false) };
|
|
}
|
|
} catch (e) {
|
|
console.log(e);
|
|
if (callback) callback(false);
|
|
}
|
|
};
|
|
|
|
SP.get = function(key, callback) {
|
|
if (local) {
|
|
if (callback) callback(local[key]);
|
|
return;
|
|
}
|
|
if (!this.db) {
|
|
this.init();
|
|
return this.queue.push(['get', key, callback]);
|
|
}
|
|
try {
|
|
let req = this.db
|
|
.transaction(this.name)
|
|
.objectStore(this.current).get(key);
|
|
if (callback) {
|
|
req.onsuccess = function(event) { callback(req.result) };
|
|
req.onerror = function(event) { callback(null) };
|
|
}
|
|
} catch (e) {
|
|
console.log(e);
|
|
if (callback) callback(null);
|
|
}
|
|
};
|
|
|
|
SP.remove = function(key, callback) {
|
|
if (local) {
|
|
delete local[key];
|
|
if (callback) callback(true);
|
|
return;
|
|
}
|
|
if (!this.db) {
|
|
this.init();
|
|
return this.queue.push(['remove', key, callback]);
|
|
}
|
|
try {
|
|
let req = this.db
|
|
.transaction(this.name, "readwrite")
|
|
.objectStore(this.current).delete(key);
|
|
if (callback) {
|
|
req.onsuccess = function(event) { callback(true) };
|
|
req.onerror = function(event) { callback(false) };
|
|
}
|
|
} catch (e) {
|
|
console.log(e);
|
|
if (callback) callback(false);
|
|
}
|
|
};
|
|
|
|
SP.clear = function(key) {
|
|
if (!this.db) {
|
|
this.init();
|
|
return this.queue.push(['clear']);
|
|
}
|
|
this.db
|
|
.transaction(this.name, "readwrite")
|
|
.objectStore(this.current).clear();
|
|
};
|
|
|
|
})();
|