diff --git a/chrome/content/zotero/xpcom/connector/cachedTypes.js b/chrome/content/zotero/xpcom/connector/cachedTypes.js
index 182f6caccf..ee81017d1d 100644
--- a/chrome/content/zotero/xpcom/connector/cachedTypes.js
+++ b/chrome/content/zotero/xpcom/connector/cachedTypes.js
@@ -30,42 +30,49 @@
/**
* @namespace
*/
-if(!Zotero.Connector) Zotero.Connector = {};
-Zotero.Connector.Types = new function() {
+Zotero.Connector_Types = new function() {
/**
* Initializes types
* @param {Object} typeSchema typeSchema generated by Zotero.Connector.GetData#_generateTypeSchema
*/
- this.init = function(typeSchema) {
+ this.init = function() {
const schemaTypes = ["itemTypes", "creatorTypes", "fields"];
// attach IDs and make referenceable by either ID or name
for(var i=0; i.
+
+ ***** END LICENSE BLOCK *****
+*/
+
+const TRANSLATOR_CODE_PREFIX = "translatorCode-";
+Zotero.Repo = new function() {
+ var _nextCheck;
+ var _timeoutID;
+
+ /**
+ * Try to retrieve translator metadata from Zotero Standalone and initialize repository check
+ * timer
+ */
+ this.init = function() {
+ // get time of next check
+ _nextCheck = Zotero.Prefs.get("connector.repo.lastCheck.localTime")
+ +ZOTERO_CONFIG.REPOSITORY_CHECK_INTERVAL*1000;
+
+ // update from standalone, but only cascade to repo if we are overdue
+ _updateFromStandalone(_nextCheck <= Date.now());
+ };
+
+ /**
+ * Reset all translators and code
+ */
+ this.reset = function(callback) {
+ Zotero.Prefs.set("connector.repo.lastCheck.repoTime", 0);
+ this.update(true);
+ };
+
+ /**
+ * Force updating translators
+ */
+ var update = this.update = function(reset, callback) {
+ _updateFromStandalone(true, reset);
+ };
+
+ /**
+ * Get translator code from repository
+ * @param {String} translatorID ID of the translator to retrieve code for
+ * @param {Function} callback Callback to pass code when retreived
+ */
+ this.getTranslatorCode = function(translatorID, callback) {
+ // we might have code in localstorage
+ if(!Zotero.isFx) {
+ var localCode = localStorage[TRANSLATOR_CODE_PREFIX+translatorID];
+ if(localCode) {
+ callback(localCode);
+ return;
+ }
+ }
+
+ // otherwise, try standalone
+ Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":translatorID}, function(result) {
+ if(result) {
+ _haveCode(result, translatorID, callback);
+ return;
+ }
+
+ // then try repo
+ Zotero.HTTP.doGet(ZOTERO_CONFIG.REPOSITORY_URL+"/code/"+translatorID, function(xmlhttp) {
+ _haveCode(xmlhttp.status === 200 ? xmlhttp.responseText : false, translatorID, callback);
+ });
+ });
+ };
+
+ /**
+ * Called when code has been retrieved from standalone or repo
+ */
+ function _haveCode(code, translatorID, callback) {
+ if(!code) {
+ callback(false);
+ return;
+ }
+
+ if(!Zotero.isFx) {
+ localStorage["translatorCode-"+translatorID] = Zotero.Translators.preprocessCode(code);
+ }
+ callback(code);
+ }
+
+ /**
+ * Retrieve translator metadata from Zotero Standalone
+ * @param {Boolean} [tryRepoOnFailure] If true, run _updateFromRepo() if standalone cannot be
+ * contacted
+ */
+ function _updateFromStandalone(tryRepoOnFailure, reset, callback) {
+ Zotero.Connector.callMethod("getTranslators", {}, function(result) {
+ if(!result && tryRepoOnFailure) {
+ _updateFromRepo(reset, callback);
+ } else {
+ _handleResponse(result, reset);
+ if(callback) callback(!!result);
+ }
+ });
+ }
+
+ /**
+ * Retrieve metadata from repository
+ */
+ function _updateFromRepo(reset, callback) {
+ var url = ZOTERO_CONFIG.REPOSITORY_URL+"/metadata?last="+
+ Zotero.Prefs.get("connector.repo.lastCheck.repoTime");
+
+ Zotero.HTTP.doGet(url, function(xmlhttp) {
+ var success = xmlhttp.status === 200;
+ _handleResponse(success ? JSON.parse(xmlhttp.responseText) : false, reset);
+
+ if(success) {
+ var date = xmlhttp.getResponseHeader("Date");
+ Zotero.Prefs.set("connector.repo.lastCheck.repoTime",
+ Math.floor(Date.parse(date)/1000));
+ }
+ if(callback) callback(!!result);
+ });
+ }
+
+ /**
+ * Handle response from Zotero Standalone or repository and set timer for next update
+ */
+ function _handleResponse(result, reset) {
+ // set up timer
+ var now = Date.now();
+
+ if(result) {
+ Zotero.Translators.update(result, reset);
+ Zotero.Prefs.set("connector.repo.lastCheck.localTime", now);
+ Zotero.debug("Repo: Check succeeded");
+ } else {
+ Zotero.debug("Repo: Check failed");
+ }
+
+ if(result || _nextCheck <= now) {
+ // if we failed a scheduled check, then use retry interval
+ _nextCheck = now+(result
+ ? ZOTERO_CONFIG.REPOSITORY_CHECK_INTERVAL
+ : ZOTERO_CONFIG.REPOSITORY_RETRY_INTERVAL)*1000;
+ } else if(_timeoutID) {
+ // if we didn't fail a scheduled check and another is already scheduled, leave it
+ return;
+ }
+
+ // remove old timeout and create a new one
+ if(_timeoutID) clearTimeout(_timeoutID);
+ var nextCheckIn = (_nextCheck-now+2000);
+ _timeoutID = setTimeout(update, nextCheckIn);
+ Zotero.debug("Repo: Next check in "+nextCheckIn);
+ }
+}
\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/connector/translate_item.js b/chrome/content/zotero/xpcom/connector/translate_item.js
index bced5f3f60..ab3cd0e5af 100644
--- a/chrome/content/zotero/xpcom/connector/translate_item.js
+++ b/chrome/content/zotero/xpcom/connector/translate_item.js
@@ -25,6 +25,9 @@
Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType) {
this.newItems = [];
+
+ this._itemsToSaveToServer = [];
+ this._timeoutID = null;
}
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0;
@@ -43,6 +46,84 @@ Zotero.Translate.ItemSaver.prototype = {
// save items
this.newItems.push(item);
- Zotero.Connector.callMethod("saveItems", {"items":[item]}, function(success) {});
+ var me = this;
+ Zotero.Connector.callMethod("saveItems", {"items":[item]}, function(success) {
+ if(success === false && !Zotero.isFx) {
+ // attempt to save to server on a timer
+ if(me._timeoutID) clearTimeout(me._timeoutID);
+ me._itemsToSaveToServer.push(item);
+ setTimeout(function() { me._saveToServer() }, 2000);
+ }
+ });
+ },
+
+ "_saveToServer":function() {
+ const IGNORE_FIELDS = ["seeAlso", "attachments", "complete"];
+
+ // clear timeout, since saving has begin
+ this._timeoutID = null;
+
+ var newItems = new Array(this._itemsToSaveToServer.length);
+ for(var i in this._itemsToSaveToServer) {
+ var item = this._itemsToSaveToServer[i];
+ var newItem = newItems[i] = {};
+
+ var typeID = Zotero.ItemTypes.getID(item.itemType);
+ var fieldID;
+ for(var field in item) {
+ if(IGNORE_FIELDS.indexOf(field) !== -1) continue;
+
+ var val = item[field];
+
+ if(field === "itemType") {
+ newItem[field] = val;
+ } else if(field === "creators") {
+ // TODO normalize
+ newItem[field] = val;
+ } else if(field === "tags") {
+ // TODO normalize
+ newItem[field] = val;
+ } else if(field === "notes") {
+ // TODO normalize
+ newItem[field] = val;
+ } else if(fieldID = Zotero.ItemFields.getID(field)) {
+ // if content is not a string, either stringify it or delete it
+ if(typeof val !== "string") {
+ if(val || val === 0) {
+ val = val.toString();
+ } else {
+ continue;
+ }
+ }
+
+ // map from base field if possible
+ var itemFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, fieldID);
+ if(itemFieldID) {
+ newItem[Zotero.ItemFields.getName(itemFieldID)] = val;
+ continue; // already know this is valid
+ }
+
+ // if field is valid for this type, set field
+ if(Zotero.ItemFields.isValidForType(fieldID, typeID)) {
+ newItem[field] = val;
+ } else {
+ Zotero.debug("Translate: Discarded field "+field+": field not valid for type "+item.itemType, 3);
+ }
+ } else if(field !== "complete") {
+ Zotero.debug("Translate: Discarded unknown field "+field, 3);
+ }
+ }
+ }
+
+ var url = 'users/%%USERID%%/items?key=%%APIKEY%%';
+ var payload = JSON.stringify({"items":newItems});
+ this._itemsToSaveToServer = [];
+
+ Zotero.OAuth.doAuthenticatedPost(url, payload, function(status, message) {
+ if(!status) {
+ Zotero.Messaging.sendMessage("saveDialog_error", status);
+ throw new Error("Translate: Save to server failed: "+message);
+ }
+ }, true);
}
};
\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/connector/translator.js b/chrome/content/zotero/xpcom/connector/translator.js
index 236ea15803..ff834230a0 100644
--- a/chrome/content/zotero/xpcom/connector/translator.js
+++ b/chrome/content/zotero/xpcom/connector/translator.js
@@ -36,15 +36,28 @@ Zotero.Translators = new function() {
/**
* Initializes translator cache, loading all relevant translators into memory
+ * @param {Zotero.Translate[]} [translators] List of translators. If not specified, it will be
+ * retrieved from storage.
*/
- this.init = function() {
+ this.init = function(translators) {
+ if(!translators) {
+ translators = [];
+ if(!Zotero.isFx && localStorage["translatorMetadata"]) {
+ try {
+ translators = JSON.parse(localStorage["translatorMetadata"]);
+ if(typeof translators !== "object") {
+ translators = [];
+ }
+ } catch(e) {}
+ }
+ }
+
_cache = {"import":[], "export":[], "web":[], "search":[]};
_translators = {};
_initialized = true;
// Build caches
- var translators = Zotero.Connector.data.translators;
- for(var i=0; i 1) {
diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js
index e899d9f9ce..bb5ee8cd39 100644
--- a/chrome/content/zotero/xpcom/utilities.js
+++ b/chrome/content/zotero/xpcom/utilities.js
@@ -538,7 +538,7 @@ Zotero.Utilities = {
* @return {Object}
*/
"deepCopy":function(obj) {
- var obj2 = {};
+ var obj2 = (obj instanceof Array ? [] : {});
for(var i in obj) {
if(typeof obj[i] === "object") {
obj2[i] = Zotero.Utilities.deepCopy(obj[i]);
diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js
index 20b18d1e00..7afc1a0f7a 100644
--- a/chrome/content/zotero/xpcom/zotero.js
+++ b/chrome/content/zotero/xpcom/zotero.js
@@ -424,7 +424,16 @@ if(appInfo.platformVersion[0] >= 2) {
// Load additional info for connector or not
if(Zotero.isConnector) {
Zotero.debug("Loading in connector mode");
- Zotero.Connector.init();
+ Zotero.Connector_Types.init();
+
+ if(!Zotero.isFirstLoadThisSession) {
+ // wait for initComplete message if we switched to connector because standalone was
+ // started
+ _waitingForInitComplete = true;
+ while(_waitingForInitComplete) Zotero.mainThread.processNextEvent(true);
+ }
+
+ Zotero.Repo.init();
} else {
Zotero.debug("Loading in full mode");
_initFull();
@@ -444,13 +453,6 @@ if(appInfo.platformVersion[0] >= 2) {
Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");
if(!Zotero.isFirstLoadThisSession) {
- if(Zotero.isConnector) {
- // wait for initComplete message if we switched to connector because standalone was
- // started
- _waitingForInitComplete = true;
- while(_waitingForInitComplete) Zotero.mainThread.processNextEvent(true);
- }
-
// trigger zotero-reloaded event
Zotero.debug('Triggering "zotero-reloaded" event');
observerService.notifyObservers(Zotero, "zotero-reloaded", null);
diff --git a/components/zotero-service.js b/components/zotero-service.js
index b8a5aa4428..c7bd615fff 100644
--- a/components/zotero-service.js
+++ b/components/zotero-service.js
@@ -114,7 +114,9 @@ const xpcomFilesConnector = [
'connector/translate_item',
'connector/translator',
'connector/connector',
- 'connector/cachedTypes'
+ 'connector/cachedTypes',
+ 'connector/repo',
+ 'connector/typeSchemaData'
];
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js
index 832baccd8b..7fb63053c1 100644
--- a/defaults/preferences/zotero.js
+++ b/defaults/preferences/zotero.js
@@ -152,4 +152,8 @@ pref("extensions.zotero.purge.tags", false);
pref("extensions.zotero.pane.persist", '');
// Domains allowed to import, separated by a semicolon
-pref("extensions.zotero.ingester.allowedSites", "");
\ No newline at end of file
+pref("extensions.zotero.ingester.allowedSites", "");
+
+// Connector
+pref("extensions.zotero.connector.repo.lastCheck.localTime", 0);
+pref("extensions.zotero.connector.repo.lastCheck.repoTime", 0);
\ No newline at end of file