libzotero
Version:
javascript libZotero
605 lines (532 loc) • 18.6 kB
JavaScript
;
var log = require('./Log.js').Logger('libZotero:Idb');
module.exports = {};
//Initialize an indexedDB for the specified library user or group + id
//returns a promise that is resolved with a Zotero.Idb.Library instance when successful
//and rejected onerror
module.exports.Library = function (libraryString) {
log.debug('Zotero.Idb.Library', 3);
log.debug('Initializing Zotero IDB', 3);
this.libraryString = libraryString;
this.owningLibrary = null;
this.initialized = false;
};
module.exports.Library.prototype.init = function () {
var idbLibrary = this;
return new Promise(function (resolve, reject) {
//Don't bother with the prefixed names because they should all be irrelevant by now
var indexedDB = window.indexedDB;
idbLibrary.indexedDB = indexedDB;
// Now we can open our database
log.debug('requesting indexedDb from browser', 3);
var request = indexedDB.open('Zotero_' + idbLibrary.libraryString, 4);
request.onerror = function (e) {
log.error('ERROR OPENING INDEXED DB');
reject();
};
var upgradeCallback = function upgradeCallback(event) {
log.debug('Zotero.Idb onupgradeneeded or onsuccess', 3);
var oldVersion = event.oldVersion;
log.debug('oldVersion: ' + event.oldVersion, 3);
var db = event.target.result;
idbLibrary.db = db;
if (oldVersion < 4) {
//delete old versions of object stores
log.debug('Existing object store names:', 3);
log.debug(JSON.stringify(db.objectStoreNames), 3);
log.debug('Deleting old object stores', 3);
if (db.objectStoreNames['items']) {
db.deleteObjectStore('items');
}
if (db.objectStoreNames['tags']) {
db.deleteObjectStore('tags');
}
if (db.objectStoreNames['collections']) {
db.deleteObjectStore('collections');
}
if (db.objectStoreNames['files']) {
db.deleteObjectStore('files');
}
if (db.objectStoreNames['versions']) {
db.deleteObjectStore('versions');
}
log.debug('Existing object store names:', 3);
log.debug(JSON.stringify(db.objectStoreNames), 3);
// Create object stores to hold items, collections, and tags.
// IDB keys are just the zotero object keys
var itemStore = db.createObjectStore('items', { keyPath: 'key' });
var collectionStore = db.createObjectStore('collections', { keyPath: 'key' });
var tagStore = db.createObjectStore('tags', { keyPath: 'tag' });
var fileStore = db.createObjectStore('files');
var versionStore = db.createObjectStore('versions');
log.debug('itemStore index names:', 3);
log.debug(JSON.stringify(itemStore.indexNames), 3);
log.debug('collectionStore index names:', 3);
log.debug(JSON.stringify(collectionStore.indexNames), 3);
log.debug('tagStore index names:', 3);
log.debug(JSON.stringify(tagStore.indexNames), 3);
// Create index to search/sort items by each attribute
Object.keys(Zotero.Item.prototype.fieldMap).forEach(function (key) {
log.debug('Creating index on ' + key, 3);
itemStore.createIndex(key, 'data.' + key, { unique: false });
});
//itemKey index was created above with all other item fields
//itemStore.createIndex("itemKey", "itemKey", { unique: false });
//create multiEntry indices on item collectionKeys and tags
itemStore.createIndex('collectionKeys', 'data.collections', { unique: false, multiEntry: true });
//index on extra tagstrings array since tags are objects and we can't index them directly
itemStore.createIndex('itemTagStrings', '_supplement.tagstrings', { unique: false, multiEntry: true });
//example filter for tag: Zotero.Idb.filterItems("itemTagStrings", "Unread");
//example filter collection: Zotero.Idb.filterItems("collectionKeys", "<collectionKey>");
//itemStore.createIndex("itemType", "itemType", { unique: false });
itemStore.createIndex('parentItemKey', 'data.parentItem', { unique: false });
itemStore.createIndex('libraryKey', 'libraryKey', { unique: false });
itemStore.createIndex('deleted', 'data.deleted', { unique: false });
collectionStore.createIndex('name', 'data.name', { unique: false });
collectionStore.createIndex('key', 'key', { unique: false });
collectionStore.createIndex('parentCollection', 'data.parentCollection', { unique: false });
//collectionStore.createIndex("libraryKey", "libraryKey", { unique: false });
tagStore.createIndex('tag', 'tag', { unique: false });
//tagStore.createIndex("libraryKey", "libraryKey", { unique: false });
}
};
request.onupgradeneeded = upgradeCallback;
request.onsuccess = function () {
log.debug('IDB success', 3);
idbLibrary.db = request.result;
idbLibrary.initialized = true;
resolve(idbLibrary);
};
});
};
module.exports.Library.prototype.deleteDB = function () {
var idbLibrary = this;
idbLibrary.db.close();
return new Promise(function (resolve, reject) {
var deleteRequest = idbLibrary.indexedDB.deleteDatabase('Zotero_' + idbLibrary.libraryString);
deleteRequest.onerror = function () {
log.error('Error deleting indexedDB');
reject();
};
deleteRequest.onsuccess = function () {
log.debug('Successfully deleted indexedDB', 2);
resolve();
};
});
};
/**
* @param {string} store_name
* @param {string} mode either "readonly" or "readwrite"
*/
module.exports.Library.prototype.getObjectStore = function (store_name, mode) {
var idbLibrary = this;
var tx = idbLibrary.db.transaction(store_name, mode);
return tx.objectStore(store_name);
};
module.exports.Library.prototype.clearObjectStore = function (store_name) {
var idbLibrary = this;
var store = idbLibrary.getObjectStore(store_name, 'readwrite');
return new Promise(function (resolve, reject) {
var req = store.clear();
req.onsuccess = function (evt) {
log.debug('Store cleared', 3);
resolve();
};
req.onerror = function (evt) {
log.error('clearObjectStore:', evt.target.errorCode);
reject();
};
});
};
/**
* Add array of items to indexedDB
* @param {array} items
*/
module.exports.Library.prototype.addItems = function (items) {
return this.addObjects(items, 'item');
};
/**
* Update/add array of items to indexedDB
* @param {array} items
*/
module.exports.Library.prototype.updateItems = function (items) {
return this.updateObjects(items, 'item');
};
/**
* Remove array of items to indexedDB. Just references itemKey and does no other checks that items match
* @param {array} items
*/
module.exports.Library.prototype.removeItems = function (items) {
return this.removeObjects(items, 'item');
};
/**
* Get item from indexedDB that has given itemKey
* @param {string} itemKey
*/
module.exports.Library.prototype.getItem = function (itemKey) {
var idbLibrary = this;
return new Promise(function (resolve, reject) {
var success = function success(event) {
resolve(event.target.result);
};
idbLibrary.db.transaction('items').objectStore(['items'], 'readonly').get(itemKey).onsuccess = success;
});
};
/**
* Get all the items in this indexedDB
* @param {array} items
*/
module.exports.Library.prototype.getAllItems = function () {
return this.getAllObjects('item');
};
module.exports.Library.prototype.getOrderedItemKeys = function (field, order) {
var idbLibrary = this;
log.debug('Zotero.Idb.getOrderedItemKeys', 3);
log.debug('' + field + ' ' + order, 3);
return new Promise(function (resolve, reject) {
var objectStore = idbLibrary.db.transaction(['items'], 'readonly').objectStore('items');
var index = objectStore.index(field);
if (!index) {
throw new Error("Index for requested field '" + field + "'' not found");
}
var cursorDirection = 'next';
if (order == 'desc') {
cursorDirection = 'prev';
}
var cursorRequest = index.openKeyCursor(null, cursorDirection);
var itemKeys = [];
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
itemKeys.push(cursor.primaryKey);
cursor.continue();
} else {
log.debug('No more cursor: done. Resolving deferred.', 3);
resolve(itemKeys);
}
}.bind(this);
cursorRequest.onfailure = function (event) {
reject();
}.bind(this);
});
};
//filter the items in indexedDB by value in field
module.exports.Library.prototype.filterItems = function (field, value) {
var idbLibrary = this;
log.debug('Zotero.Idb.filterItems ' + field + ' - ' + value, 3);
return new Promise(function (resolve, reject) {
var itemKeys = [];
var objectStore = idbLibrary.db.transaction(['items'], 'readonly').objectStore('items');
var index = objectStore.index(field);
if (!index) {
throw new Error("Index for requested field '" + field + "'' not found");
}
var cursorDirection = 'next';
/*if(order == "desc"){
cursorDirection = "prev";
}*/
var range = IDBKeyRange.only(value);
var cursorRequest = index.openKeyCursor(range, cursorDirection);
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
itemKeys.push(cursor.primaryKey);
cursor.continue();
} else {
log.debug('No more cursor: done. Resolving deferred.', 3);
resolve(itemKeys);
}
}.bind(this);
cursorRequest.onfailure = function (event) {
reject();
}.bind(this);
});
};
module.exports.Library.prototype.inferType = function (object) {
if (!object) {
return false;
}
if (!object.instance) {
return false;
}
switch (object.instance) {
case 'Zotero.Item':
return 'item';
case 'Zotero.Collection':
return 'collection';
case 'Zotero.Tag':
return 'tag';
default:
return false;
}
};
module.exports.Library.prototype.getTransactionAndStore = function (type, access) {
var idbLibrary = this;
var transaction;
var objectStore;
switch (type) {
case 'item':
transaction = idbLibrary.db.transaction(['items'], access);
objectStore = transaction.objectStore('items');
break;
case 'collection':
transaction = idbLibrary.db.transaction(['collections'], access);
objectStore = transaction.objectStore('collections');
break;
case 'tag':
transaction = idbLibrary.db.transaction(['tags'], access);
objectStore = transaction.objectStore('tags');
break;
default:
return Promise.reject();
}
return [transaction, objectStore];
};
module.exports.Library.prototype.addObjects = function (objects, type) {
log.debug('Zotero.Idb.Library.addObjects', 3);
var idbLibrary = this;
if (!type) {
type = idbLibrary.inferType(objects[0]);
}
var TS = idbLibrary.getTransactionAndStore(type, 'readwrite');
var transaction = TS[0];
var objectStore = TS[1];
return new Promise(function (resolve, reject) {
transaction.oncomplete = function (event) {
log.debug('Add Objects transaction completed.', 3);
resolve();
};
transaction.onerror = function (event) {
log.error('Add Objects transaction failed.');
reject();
};
var reqSuccess = function reqSuccess(event) {
log.debug('Added Object ' + event.target.result, 4);
};
for (var i in objects) {
var request = objectStore.add(objects[i].apiObj);
request.onsuccess = reqSuccess;
}
});
};
module.exports.Library.prototype.updateObjects = function (objects, type) {
log.debug('Zotero.Idb.Library.updateObjects', 3);
var idbLibrary = this;
if (!type) {
type = idbLibrary.inferType(objects[0]);
}
var TS = idbLibrary.getTransactionAndStore(type, 'readwrite');
var transaction = TS[0];
var objectStore = TS[1];
return new Promise(function (resolve, reject) {
transaction.oncomplete = function (event) {
log.debug('Update Objects transaction completed.', 3);
resolve();
};
transaction.onerror = function (event) {
log.error('Update Objects transaction failed.');
reject();
};
var reqSuccess = function reqSuccess(event) {
log.debug('Updated Object ' + event.target.result, 4);
};
for (var i in objects) {
var request = objectStore.put(objects[i].apiObj);
request.onsuccess = reqSuccess;
}
});
};
module.exports.Library.prototype.removeObjects = function (objects, type) {
var idbLibrary = this;
if (!type) {
type = idbLibrary.inferType(objects[0]);
}
var TS = idbLibrary.getTransactionAndStore(type, 'readwrite');
var transaction = TS[0];
var objectStore = TS[1];
return new Promise(function (resolve, reject) {
transaction.oncomplete = function (event) {
log.debug('Remove Objects transaction completed.', 3);
resolve();
};
transaction.onerror = function (event) {
log.error('Remove Objects transaction failed.');
reject();
};
var reqSuccess = function reqSuccess(event) {
log.debug('Removed Object ' + event.target.result, 4);
};
for (var i in objects) {
var request = objectStore.delete(objects[i].key);
request.onsuccess = reqSuccess;
}
});
};
module.exports.Library.prototype.getAllObjects = function (type) {
var idbLibrary = this;
return new Promise(function (resolve, reject) {
var objects = [];
var objectStore = idbLibrary.db.transaction(type + 's').objectStore(type + 's');
objectStore.openCursor().onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
objects.push(cursor.value);
cursor.continue();
} else {
resolve(objects);
}
};
});
};
module.exports.Library.prototype.addCollections = function (collections) {
return this.addObjects(collections, 'collection');
};
module.exports.Library.prototype.updateCollections = function (collections) {
log.debug('Zotero.Idb.Library.updateCollections', 3);
return this.updateObjects(collections, 'collection');
};
/**
* Get collection from indexedDB that has given collectionKey
* @param {string} collectionKey
*/
module.exports.Library.prototype.getCollection = function (collectionKey) {
var idbLibrary = this;
return new Promise(function (resolve, reject) {
var success = function success(event) {
resolve(event.target.result);
};
idbLibrary.db.transaction('collections').objectStore(['collections'], 'readonly').get(collectionKey).onsuccess = success;
});
};
module.exports.Library.prototype.removeCollections = function (collections) {
log.debug('Zotero.Idb.Library.removeCollections', 3);
return this.removeObjects(collections, 'collection');
};
module.exports.Library.prototype.getAllCollections = function () {
log.debug('Zotero.Idb.Library.getAllCollections', 3);
return this.getAllObjects('collection');
};
module.exports.Library.prototype.addTags = function (tags) {
return this.addObjects(tags, 'tag');
};
module.exports.Library.prototype.updateTags = function (tags) {
log.debug('Zotero.Idb.Library.updateTags', 3);
return this.updateObjects(tags, 'tag');
};
module.exports.Library.prototype.getAllTags = function () {
log.debug('getAllTags', 3);
return this.getAllObjects('tag');
};
module.exports.Library.prototype.setVersion = function (type, version) {
log.debug('Zotero.Idb.Library.setVersion', 3);
var idbLibrary = this;
return new Promise(function (resolve, reject) {
var transaction = idbLibrary.db.transaction(['versions'], 'readwrite');
transaction.oncomplete = function (event) {
log.debug('set version transaction completed.', 3);
resolve();
};
transaction.onerror = function (event) {
log.error('set version transaction failed.');
reject();
};
var versionStore = transaction.objectStore('versions');
var reqSuccess = function reqSuccess(event) {
log.debug('Set Version' + event.target.result, 3);
};
var request = versionStore.put(version, type);
request.onsuccess = reqSuccess;
});
};
/**
* Get version data from indexedDB
* @param {string} type
*/
module.exports.Library.prototype.getVersion = function (type) {
log.debug('Zotero.Idb.Library.getVersion', 3);
var idbLibrary = this;
return new Promise(function (resolve, reject) {
var success = function success(event) {
log.debug('done getting version');
resolve(event.target.result);
};
idbLibrary.db.transaction(['versions'], 'readonly').objectStore('versions').get(type).onsuccess = success;
});
};
module.exports.Library.prototype.setFile = function (itemKey, fileData) {
log.debug('Zotero.Idb.Library.setFile', 3);
var idbLibrary = this;
return new Promise(function (resolve, reject) {
var transaction = idbLibrary.db.transaction(['files'], 'readwrite');
transaction.oncomplete = function (event) {
log.debug('set file transaction completed.', 3);
resolve();
};
transaction.onerror = function (event) {
log.error('set file transaction failed.');
reject();
};
var fileStore = transaction.objectStore('files');
var reqSuccess = function reqSuccess(event) {
log.debug('Set File' + event.target.result, 3);
};
var request = fileStore.put(fileData, itemKey);
request.onsuccess = reqSuccess;
});
};
/**
* Get item from indexedDB that has given itemKey
* @param {string} itemKey
*/
module.exports.Library.prototype.getFile = function (itemKey) {
log.debug('Zotero.Idb.Library.getFile', 3);
var idbLibrary = this;
return new Promise(function (resolve, reject) {
var success = function success(event) {
log.debug('done getting file');
resolve(event.target.result);
};
idbLibrary.db.transaction(['files'], 'readonly').objectStore('files').get(itemKey).onsuccess = success;
});
};
module.exports.Library.prototype.deleteFile = function (itemKey) {
log.debug('Zotero.Idb.Library.deleteFile', 3);
var idbLibrary = this;
return new Promise(function (resolve, reject) {
var transaction = idbLibrary.db.transaction(['files'], 'readwrite');
transaction.oncomplete = function (event) {
log.debug('delete file transaction completed.', 3);
resolve();
};
transaction.onerror = function (event) {
log.error('delete file transaction failed.');
reject();
};
var fileStore = transaction.objectStore('files');
var reqSuccess = function reqSuccess(event) {
log.debug('Deleted File' + event.target.result, 4);
};
var request = fileStore.delete(itemKey);
request.onsuccess = reqSuccess;
});
};
//intersect two arrays of strings as an AND condition on index results
module.exports.Library.prototype.intersect = function (ar1, ar2) {
var idbLibrary = this;
var result = [];
for (var i = 0; i < ar1.length; i++) {
if (ar2.indexOf(ar1[i]) !== -1) {
result.push(ar1[i]);
}
}
return result;
};
//intersect an array of arrays of strings as an AND condition on index results
module.exports.Library.prototype.intersectAll = function (arrs) {
var idbLibrary = this;
var result = arrs[0];
for (var i = 0; i < arrs.length - 1; i++) {
result = idbLibrary.intersect(result, arrs[i + 1]);
}
return result;
};