e5b54d9b6a
These may be indicative of a potentially fatal lack of disk space. // FREEBIE
662 lines
30 KiB
JavaScript
662 lines
30 KiB
JavaScript
(function (root, factory) {
|
||
if (typeof define === 'function' && define.amd) {
|
||
// AMD. Register as an anonymous module.
|
||
define(['backbone', 'underscore'], factory);
|
||
} else if (typeof exports === 'object') {
|
||
// Node. Does not work with strict CommonJS, but
|
||
// only CommonJS-like environments that support module.exports,
|
||
// like Node.
|
||
module.exports = factory(require('backbone'), require('underscore'));
|
||
} else {
|
||
// Browser globals (root is window)
|
||
root.returnExports = factory(root.Backbone, root._);
|
||
}
|
||
}(this, function (Backbone, _) {
|
||
|
||
// Generate four random hex digits.
|
||
function S4() {
|
||
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||
}
|
||
|
||
// Generate a pseudo-GUID by concatenating random hexadecimal.
|
||
function guid() {
|
||
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
|
||
}
|
||
|
||
if ( _(indexedDB).isUndefined() ) { return; }
|
||
|
||
// Driver object
|
||
// That's the interesting part.
|
||
// There is a driver for each schema provided. The schema is a te combination of name (for the database), a version as well as migrations to reach that
|
||
// version of the database.
|
||
function Driver(schema, ready, nolog, onerror) {
|
||
this.schema = schema;
|
||
this.ready = ready;
|
||
this.error = null;
|
||
this.transactions = []; // Used to list all transactions and keep track of active ones.
|
||
this.db = null;
|
||
this.nolog = nolog;
|
||
this.onerror = onerror;
|
||
var lastMigrationPathVersion = _.last(this.schema.migrations).version;
|
||
if (!this.nolog) debugLog("opening database " + this.schema.id + " in version #" + lastMigrationPathVersion);
|
||
this.dbRequest = indexedDB.open(this.schema.id,lastMigrationPathVersion); //schema version need to be an unsigned long
|
||
|
||
this.launchMigrationPath = function(dbVersion) {
|
||
var transaction = this.dbRequest.transaction;
|
||
var clonedMigrations = _.clone(schema.migrations);
|
||
this.migrate(transaction, clonedMigrations, dbVersion, {
|
||
error: function (event) {
|
||
this.error = "Database not up to date. " + dbVersion + " expected was " + lastMigrationPathVersion;
|
||
}.bind(this)
|
||
});
|
||
};
|
||
|
||
this.dbRequest.onblocked = function(event){
|
||
if (!this.nolog) debugLog("connection to database blocked");
|
||
}
|
||
|
||
this.dbRequest.onsuccess = function (e) {
|
||
this.db = e.target.result; // Attach the connection ot the queue.
|
||
var currentIntDBVersion = (parseInt(this.db.version) || 0); // we need convert beacuse chrome store in integer and ie10 DP4+ in int;
|
||
var lastMigrationInt = (parseInt(lastMigrationPathVersion) || 0); // And make sure we compare numbers with numbers.
|
||
|
||
if (currentIntDBVersion === lastMigrationInt) { //if support new event onupgradeneeded will trigger the ready function
|
||
// No migration to perform!
|
||
this.ready();
|
||
} else if (currentIntDBVersion < lastMigrationInt ) {
|
||
// We need to migrate up to the current migration defined in the database
|
||
this.launchMigrationPath(currentIntDBVersion);
|
||
} else {
|
||
// Looks like the IndexedDB is at a higher version than the current driver schema.
|
||
this.error = "Database version is greater than current code " + currentIntDBVersion + " expected was " + lastMigrationInt;
|
||
}
|
||
}.bind(this);
|
||
|
||
|
||
|
||
this.dbRequest.onerror = function (e) {
|
||
// Failed to open the database
|
||
this.error = "Couldn't not connect to the database"
|
||
if (!this.nolog) debugLog("Couldn't not connect to the database");
|
||
this.onerror();
|
||
}.bind(this);
|
||
|
||
this.dbRequest.onabort = function (e) {
|
||
// Failed to open the database
|
||
this.error = "Connection to the database aborted"
|
||
if (!this.nolog) debugLog("Connection to the database aborted");
|
||
this.onerror();
|
||
}.bind(this);
|
||
|
||
|
||
|
||
this.dbRequest.onupgradeneeded = function(iDBVersionChangeEvent){
|
||
this.db =iDBVersionChangeEvent.target.result;
|
||
|
||
var newVersion = iDBVersionChangeEvent.newVersion;
|
||
var oldVersion = iDBVersionChangeEvent.oldVersion;
|
||
|
||
// Fix Safari 8 and iOS 8 bug
|
||
// at the first connection oldVersion is equal to 9223372036854776000
|
||
// but the real value is 0
|
||
if (oldVersion > 99999999999)
|
||
oldVersion = 0;
|
||
|
||
if (!this.nolog) debugLog("onupgradeneeded = " + oldVersion + " => " + newVersion);
|
||
this.launchMigrationPath(oldVersion);
|
||
}.bind(this);
|
||
}
|
||
|
||
function debugLog(str) {
|
||
if (typeof window !== "undefined" && typeof window.console !== "undefined" && typeof window.console.log !== "undefined") {
|
||
window.console.log(str);
|
||
}
|
||
else if(console.log !== "undefined") {
|
||
console.log(str)
|
||
}
|
||
}
|
||
|
||
// Driver Prototype
|
||
Driver.prototype = {
|
||
|
||
// Tracks transactions. Mostly for debugging purposes. TO-IMPROVE
|
||
_track_transaction: function(transaction) {
|
||
this.transactions.push(transaction);
|
||
function removeIt() {
|
||
var idx = this.transactions.indexOf(transaction);
|
||
if (idx !== -1) {this.transactions.splice(idx); }
|
||
};
|
||
transaction.oncomplete = removeIt.bind(this);
|
||
transaction.onabort = removeIt.bind(this);
|
||
transaction.onerror = removeIt.bind(this);
|
||
},
|
||
|
||
// Performs all the migrations to reach the right version of the database.
|
||
migrate: function (transaction, migrations, version, options) {
|
||
transaction.onerror = options.error;
|
||
transaction.onabort = options.error;
|
||
|
||
if (!this.nolog) debugLog("migrate begin version from #" + version);
|
||
var that = this;
|
||
var migration = migrations.shift();
|
||
if (migration) {
|
||
if (!version || version < migration.version) {
|
||
// We need to apply this migration-
|
||
if (typeof migration.before == "undefined") {
|
||
migration.before = function (next) {
|
||
next();
|
||
};
|
||
}
|
||
if (typeof migration.after == "undefined") {
|
||
migration.after = function (next) {
|
||
next();
|
||
};
|
||
}
|
||
// First, let's run the before script
|
||
if (!this.nolog) debugLog("migrate begin before version #" + migration.version);
|
||
migration.before(function () {
|
||
if (!this.nolog) debugLog("migrate done before version #" + migration.version);
|
||
|
||
if (!this.nolog) debugLog("migrate begin migrate version #" + migration.version);
|
||
|
||
migration.migrate(transaction, function () {
|
||
if (!this.nolog) debugLog("migrate done migrate version #" + migration.version);
|
||
// Migration successfully appliedn let's go to the next one!
|
||
if (!this.nolog) debugLog("migrate begin after version #" + migration.version);
|
||
migration.after(function () {
|
||
if (!this.nolog) debugLog("migrate done after version #" + migration.version);
|
||
if (!this.nolog) debugLog("Migrated to " + migration.version);
|
||
|
||
//last modification occurred, need finish
|
||
if(migrations.length ==0) {
|
||
if (!this.nolog) {
|
||
debugLog("migrate setting transaction.oncomplete to finish version #" + migration.version);
|
||
transaction.oncomplete = function() {
|
||
debugLog("migrate done transaction.oncomplete version #" + migration.version);
|
||
debugLog("Done migrating");
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!this.nolog) debugLog("migrate end from version #" + version + " to " + migration.version);
|
||
that.migrate(transaction, migrations, version, options);
|
||
}
|
||
|
||
}.bind(this));
|
||
}.bind(this));
|
||
}.bind(this));
|
||
} else {
|
||
// No need to apply this migration
|
||
if (!this.nolog) debugLog("Skipping migration " + migration.version);
|
||
this.migrate(transaction, migrations, version, options);
|
||
}
|
||
}
|
||
},
|
||
|
||
// This is the main method, called by the ExecutionQueue when the driver is ready (database open and migration performed)
|
||
execute: function (storeName, method, object, options) {
|
||
if (!this.nolog) debugLog("execute : " + method + " on " + storeName + " for " + object.id);
|
||
switch (method) {
|
||
case "create":
|
||
this.create(storeName, object, options);
|
||
break;
|
||
case "read":
|
||
if (object.id || object.cid) {
|
||
this.read(storeName, object, options); // It's a model
|
||
} else {
|
||
this.query(storeName, object, options); // It's a collection
|
||
}
|
||
break;
|
||
case "update":
|
||
this.update(storeName, object, options); // We may want to check that this is not a collection. TOFIX
|
||
break;
|
||
case "delete":
|
||
if (object.id || object.cid) {
|
||
this.delete(storeName, object, options);
|
||
} else {
|
||
this.clear(storeName, object, options);
|
||
}
|
||
break;
|
||
default:
|
||
// Hum what?
|
||
}
|
||
},
|
||
|
||
// Writes the json to the storeName in db. It is a create operations, which means it will fail if the key already exists
|
||
// options are just success and error callbacks.
|
||
create: function (storeName, object, options) {
|
||
var writeTransaction = this.db.transaction([storeName], 'readwrite');
|
||
//this._track_transaction(writeTransaction);
|
||
var store = writeTransaction.objectStore(storeName);
|
||
var json = object.toJSON();
|
||
var idAttribute = _.result(object, 'idAttribute');
|
||
var writeRequest;
|
||
|
||
if (json[idAttribute] === undefined && !store.autoIncrement) json[idAttribute] = guid();
|
||
|
||
writeTransaction.onerror = function (e) {
|
||
options.error(e);
|
||
};
|
||
writeTransaction.oncomplete = function (e) {
|
||
options.success(json);
|
||
};
|
||
|
||
if (!store.keyPath)
|
||
writeRequest = store.add(json, json[idAttribute]);
|
||
else
|
||
writeRequest = store.add(json);
|
||
},
|
||
|
||
// Writes the json to the storeName in db. It is an update operation, which means it will overwrite the value if the key already exist
|
||
// options are just success and error callbacks.
|
||
update: function (storeName, object, options) {
|
||
var writeTransaction = this.db.transaction([storeName], 'readwrite');
|
||
//this._track_transaction(writeTransaction);
|
||
var store = writeTransaction.objectStore(storeName);
|
||
var json = object.toJSON();
|
||
var idAttribute = _.result(object, 'idAttribute');
|
||
var writeRequest;
|
||
|
||
if (!json[idAttribute]) json[idAttribute] = guid();
|
||
|
||
if (!store.keyPath)
|
||
writeRequest = store.put(json, json[idAttribute]);
|
||
else
|
||
writeRequest = store.put(json);
|
||
|
||
writeRequest.onerror = function (e) {
|
||
options.error(e);
|
||
};
|
||
writeTransaction.oncomplete = function (e) {
|
||
options.success(json);
|
||
};
|
||
},
|
||
|
||
// Reads from storeName in db with json.id if it's there of with any json.xxxx as long as xxx is an index in storeName
|
||
read: function (storeName, object, options) {
|
||
var readTransaction = this.db.transaction([storeName], "readonly");
|
||
this._track_transaction(readTransaction);
|
||
|
||
var store = readTransaction.objectStore(storeName);
|
||
var json = object.toJSON();
|
||
var idAttribute = _.result(object, 'idAttribute');
|
||
|
||
var getRequest = null;
|
||
if (json[idAttribute]) {
|
||
getRequest = store.get(json[idAttribute]);
|
||
} else if(options.index) {
|
||
var index = store.index(options.index.name);
|
||
getRequest = index.get(options.index.value);
|
||
} else {
|
||
// We need to find which index we have
|
||
var cardinality = 0; // try to fit the index with most matches
|
||
_.each(store.indexNames, function (key, index) {
|
||
index = store.index(key);
|
||
if(typeof index.keyPath === 'string' && 1 > cardinality) {
|
||
// simple index
|
||
if (json[index.keyPath] !== undefined) {
|
||
getRequest = index.get(json[index.keyPath]);
|
||
cardinality = 1;
|
||
}
|
||
} else if(typeof index.keyPath === 'object' && index.keyPath.length > cardinality) {
|
||
// compound index
|
||
var valid = true;
|
||
var keyValue = _.map(index.keyPath, function(keyPart) {
|
||
valid = valid && json[keyPart] !== undefined;
|
||
return json[keyPart];
|
||
});
|
||
if(valid) {
|
||
getRequest = index.get(keyValue);
|
||
cardinality = index.keyPath.length;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
if (getRequest) {
|
||
getRequest.onsuccess = function (event) {
|
||
if (event.target.result) {
|
||
options.success(event.target.result);
|
||
} else {
|
||
options.error("Not Found");
|
||
}
|
||
};
|
||
getRequest.onerror = function () {
|
||
options.error("Not Found"); // We couldn't find the record.
|
||
}
|
||
} else {
|
||
options.error("Not Found"); // We couldn't even look for it, as we don't have enough data.
|
||
}
|
||
},
|
||
|
||
// Deletes the json.id key and value in storeName from db.
|
||
delete: function (storeName, object, options) {
|
||
var deleteTransaction = this.db.transaction([storeName], 'readwrite');
|
||
//this._track_transaction(deleteTransaction);
|
||
|
||
var store = deleteTransaction.objectStore(storeName);
|
||
var json = object.toJSON();
|
||
var idAttribute = _.result(object, 'idAttribute');
|
||
|
||
var deleteRequest = store.delete(json[idAttribute]);
|
||
|
||
deleteTransaction.oncomplete = function (event) {
|
||
options.success(null);
|
||
};
|
||
deleteRequest.onerror = function (event) {
|
||
options.error("Not Deleted");
|
||
};
|
||
},
|
||
|
||
// Clears all records for storeName from db.
|
||
clear: function (storeName, object, options) {
|
||
var deleteTransaction = this.db.transaction([storeName], "readwrite");
|
||
//this._track_transaction(deleteTransaction);
|
||
|
||
var store = deleteTransaction.objectStore(storeName);
|
||
|
||
var deleteRequest = store.clear();
|
||
deleteRequest.onsuccess = function (event) {
|
||
options.success(null);
|
||
};
|
||
deleteRequest.onerror = function (event) {
|
||
options.error("Not Cleared");
|
||
};
|
||
},
|
||
|
||
// Performs a query on storeName in db.
|
||
// options may include :
|
||
// - conditions : value of an index, or range for an index
|
||
// - range : range for the primary key
|
||
// - limit : max number of elements to be yielded
|
||
// - offset : skipped items.
|
||
query: function (storeName, collection, options) {
|
||
var elements = [];
|
||
var skipped = 0, processed = 0;
|
||
var queryTransaction = this.db.transaction([storeName], "readonly");
|
||
//this._track_transaction(queryTransaction);
|
||
|
||
var idAttribute = _.result(collection.model.prototype, 'idAttribute');
|
||
var readCursor = null;
|
||
var store = queryTransaction.objectStore(storeName);
|
||
var index = null,
|
||
lower = null,
|
||
upper = null,
|
||
bounds = null;
|
||
|
||
if (options.conditions) {
|
||
// We have a condition, we need to use it for the cursor
|
||
_.each(store.indexNames, function (key) {
|
||
if (!readCursor) {
|
||
index = store.index(key);
|
||
if (options.conditions[index.keyPath] instanceof Array) {
|
||
lower = options.conditions[index.keyPath][0] > options.conditions[index.keyPath][1] ? options.conditions[index.keyPath][1] : options.conditions[index.keyPath][0];
|
||
upper = options.conditions[index.keyPath][0] > options.conditions[index.keyPath][1] ? options.conditions[index.keyPath][0] : options.conditions[index.keyPath][1];
|
||
bounds = IDBKeyRange.bound(lower, upper, true, true);
|
||
|
||
if (options.conditions[index.keyPath][0] > options.conditions[index.keyPath][1]) {
|
||
// Looks like we want the DESC order
|
||
readCursor = index.openCursor(bounds, window.IDBCursor.PREV || "prev");
|
||
} else {
|
||
// We want ASC order
|
||
readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
|
||
}
|
||
} else if (typeof options.conditions[index.keyPath] === 'object' && ('$gt' in options.conditions[index.keyPath] || '$gte' in options.conditions[index.keyPath])) {
|
||
if('$gt' in options.conditions[index.keyPath])
|
||
bounds = IDBKeyRange.lowerBound(options.conditions[index.keyPath]['$gt'], true);
|
||
else
|
||
bounds = IDBKeyRange.lowerBound(options.conditions[index.keyPath]['$gte']);
|
||
readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
|
||
} else if (typeof options.conditions[index.keyPath] === 'object' && ('$lt' in options.conditions[index.keyPath] || '$lte' in options.conditions[index.keyPath])) {
|
||
if('$lt' in options.conditions[index.keyPath])
|
||
bounds = IDBKeyRange.upperBound(options.conditions[index.keyPath]['$lt'], true);
|
||
else
|
||
bounds = IDBKeyRange.upperBound(options.conditions[index.keyPath]['$lte']);
|
||
readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
|
||
} else if (options.conditions[index.keyPath] != undefined) {
|
||
bounds = IDBKeyRange.only(options.conditions[index.keyPath]);
|
||
readCursor = index.openCursor(bounds);
|
||
}
|
||
}
|
||
});
|
||
} else if (options.index) {
|
||
index = store.index(options.index.name);
|
||
var excludeLower = !!options.index.excludeLower;
|
||
var excludeUpper = !!options.index.excludeUpper;
|
||
if (index) {
|
||
if (options.index.lower && options.index.upper) {
|
||
bounds = IDBKeyRange.bound(options.index.lower, options.index.upper, excludeLower, excludeUpper);
|
||
} else if (options.index.lower) {
|
||
bounds = IDBKeyRange.lowerBound(options.index.lower, excludeLower);
|
||
} else if (options.index.upper) {
|
||
bounds = IDBKeyRange.upperBound(options.index.upper, excludeUpper);
|
||
} else if (options.index.only) {
|
||
bounds = IDBKeyRange.only(options.index.only);
|
||
}
|
||
|
||
if (typeof options.index.order === 'string' && options.index.order.toLowerCase() === 'desc') {
|
||
readCursor = index.openCursor(bounds, window.IDBCursor.PREV || "prev");
|
||
} else {
|
||
readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
|
||
}
|
||
}
|
||
} else {
|
||
// No conditions, use the index
|
||
if (options.range) {
|
||
lower = options.range[0] > options.range[1] ? options.range[1] : options.range[0];
|
||
upper = options.range[0] > options.range[1] ? options.range[0] : options.range[1];
|
||
bounds = IDBKeyRange.bound(lower, upper);
|
||
if (options.range[0] > options.range[1]) {
|
||
readCursor = store.openCursor(bounds, window.IDBCursor.PREV || "prev");
|
||
} else {
|
||
readCursor = store.openCursor(bounds, window.IDBCursor.NEXT || "next");
|
||
}
|
||
} else {
|
||
readCursor = store.openCursor();
|
||
}
|
||
}
|
||
|
||
if (typeof (readCursor) == "undefined" || !readCursor) {
|
||
options.error("No Cursor");
|
||
} else {
|
||
readCursor.onerror = function(e){
|
||
options.error("readCursor error", e);
|
||
};
|
||
// Setup a handler for the cursor’s `success` event:
|
||
readCursor.onsuccess = function (e) {
|
||
var cursor = e.target.result;
|
||
if (!cursor) {
|
||
if (options.addIndividually || options.clear) {
|
||
// nothing!
|
||
// We need to indicate that we're done. But, how?
|
||
collection.trigger("reset");
|
||
} else {
|
||
options.success(elements); // We're done. No more elements.
|
||
}
|
||
}
|
||
else {
|
||
// Cursor is not over yet.
|
||
if (options.limit && processed >= options.limit) {
|
||
// Yet, we have processed enough elements. So, let's just skip.
|
||
if (bounds) {
|
||
if (options.conditions && options.conditions[index.keyPath]) {
|
||
cursor.continue(options.conditions[index.keyPath][1] + 1); /* We need to 'terminate' the cursor cleany, by moving to the end */
|
||
} else if (options.index && (options.index.upper || options.index.lower)) {
|
||
if (typeof options.index.order === 'string' && options.index.order.toLowerCase() === 'desc') {
|
||
cursor.continue(options.index.lower);
|
||
} else {
|
||
cursor.continue(options.index.upper);
|
||
}
|
||
}
|
||
} else {
|
||
cursor.continue(); /* We need to 'terminate' the cursor cleany, by moving to the end */
|
||
}
|
||
}
|
||
else if (options.offset && options.offset > skipped) {
|
||
skipped++;
|
||
cursor.continue(); /* We need to Moving the cursor forward */
|
||
} else {
|
||
// This time, it looks like it's good!
|
||
if (options.addIndividually) {
|
||
collection.add(cursor.value);
|
||
} else if (options.clear) {
|
||
var deleteRequest = store.delete(cursor.value[idAttribute]);
|
||
deleteRequest.onsuccess = function (event) {
|
||
elements.push(cursor.value);
|
||
};
|
||
deleteRequest.onerror = function (event) {
|
||
elements.push(cursor.value);
|
||
};
|
||
|
||
} else {
|
||
elements.push(cursor.value);
|
||
}
|
||
processed++;
|
||
cursor.continue();
|
||
}
|
||
}
|
||
};
|
||
}
|
||
},
|
||
close :function(){
|
||
if(this.db){
|
||
this.db.close();
|
||
}
|
||
}
|
||
};
|
||
|
||
// ExecutionQueue object
|
||
// The execution queue is an abstraction to buffer up requests to the database.
|
||
// It holds a "driver". When the driver is ready, it just fires up the queue and executes in sync.
|
||
function ExecutionQueue(schema,next,nolog) {
|
||
this.driver = new Driver(schema, this.ready.bind(this), nolog, this.error.bind(this));
|
||
this.started = false;
|
||
this.failed = false;
|
||
this.stack = [];
|
||
this.version = _.last(schema.migrations).version;
|
||
this.next = next;
|
||
}
|
||
|
||
// ExecutionQueue Prototype
|
||
ExecutionQueue.prototype = {
|
||
// Called when the driver is ready
|
||
// It just loops over the elements in the queue and executes them.
|
||
ready: function () {
|
||
this.started = true;
|
||
_.each(this.stack, function (message) {
|
||
this.execute(message);
|
||
}.bind(this));
|
||
this.stack = []; // fix memory leak
|
||
this.next();
|
||
},
|
||
|
||
error: function() {
|
||
this.failed = true;
|
||
_.each(this.stack, function (message) {
|
||
this.execute(message);
|
||
}.bind(this));
|
||
this.stack = [];
|
||
this.next();
|
||
},
|
||
|
||
// Executes a given command on the driver. If not started, just stacks up one more element.
|
||
execute: function (message) {
|
||
if (this.started) {
|
||
try {
|
||
this.driver.execute(message[2].storeName || message[1].storeName, message[0], message[1], message[2]); // Upon messages, we execute the query
|
||
} catch (e) {
|
||
if (e.name === 'InvalidStateError') {
|
||
var f = window.onInvalidStateError;
|
||
if (f) f(e);
|
||
}
|
||
throw e;
|
||
}
|
||
} else if (this.failed) {
|
||
message[2].error();
|
||
} else {
|
||
this.stack.push(message);
|
||
}
|
||
},
|
||
|
||
close : function(){
|
||
this.driver.close();
|
||
}
|
||
};
|
||
|
||
// Method used by Backbone for sync of data with data store. It was initially designed to work with "server side" APIs, This wrapper makes
|
||
// it work with the local indexedDB stuff. It uses the schema attribute provided by the object.
|
||
// The wrapper keeps an active Executuon Queue for each "schema", and executes querues agains it, based on the object type (collection or
|
||
// single model), but also the method... etc.
|
||
// Keeps track of the connections
|
||
var Databases = {};
|
||
|
||
function sync(method, object, options) {
|
||
|
||
if(method == "closeall"){
|
||
_.each(Databases,function(database){
|
||
database.close();
|
||
});
|
||
// Clean up active databases object.
|
||
Databases = {};
|
||
return Backbone.$.Deferred().resolve();
|
||
}
|
||
|
||
// If a model or a collection does not define a database, fall back on ajaxSync
|
||
if (!object || !_.isObject(object.database)) {
|
||
return Backbone.ajaxSync(method, object, options);
|
||
}
|
||
|
||
var schema = object.database;
|
||
if (Databases[schema.id]) {
|
||
if(Databases[schema.id].version != _.last(schema.migrations).version){
|
||
Databases[schema.id].close();
|
||
delete Databases[schema.id];
|
||
}
|
||
}
|
||
|
||
var promise;
|
||
|
||
if (typeof Backbone.$ === 'undefined' || typeof Backbone.$.Deferred === 'undefined') {
|
||
var noop = function() {};
|
||
var resolve = noop;
|
||
var reject = noop;
|
||
} else {
|
||
var dfd = Backbone.$.Deferred();
|
||
var resolve = dfd.resolve;
|
||
var reject = dfd.reject;
|
||
|
||
promise = dfd.promise();
|
||
}
|
||
|
||
var success = options.success;
|
||
options.success = function(resp) {
|
||
if (success) success(resp);
|
||
resolve();
|
||
object.trigger('sync', object, resp, options);
|
||
};
|
||
|
||
var error = options.error;
|
||
options.error = function(resp) {
|
||
if (error) error(resp);
|
||
reject();
|
||
object.trigger('error', object, resp, options);
|
||
};
|
||
|
||
var next = function(){
|
||
Databases[schema.id].execute([method, object, options]);
|
||
};
|
||
|
||
if (!Databases[schema.id]) {
|
||
Databases[schema.id] = new ExecutionQueue(schema,next,schema.nolog);
|
||
} else {
|
||
next();
|
||
}
|
||
|
||
return promise;
|
||
};
|
||
|
||
Backbone.ajaxSync = Backbone.sync;
|
||
Backbone.sync = sync;
|
||
|
||
return { sync: sync, debugLog: debugLog};
|
||
}));
|