- Closes #1832, Connectors should be able to retrieve translator data and code from server in the absence of Zotero Standalone
- Closes #1831, Connectors should be able to save via API in the absence of Zotero Standalone - Fixes Zotero.Utilities.deepCopy() for arrays - Fixes some circumstances where an error would not be saved for future error reporting - Fixes connector status checking
This commit is contained in:
parent
2bee3ecf1e
commit
b114266fb3
13 changed files with 508 additions and 307 deletions
|
@ -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<schemaTypes.length; i++) {
|
||||
var schemaType = schemaTypes[i];
|
||||
this[schemaType] = typeSchema[schemaType];
|
||||
this[schemaType] = Zotero.Utilities.deepCopy(Zotero.Connector_Types.schema[schemaType]);
|
||||
for(var id in this[schemaType]) {
|
||||
var entry = this[schemaType][id];
|
||||
entry.id = id;
|
||||
entry.id = parseInt(id);
|
||||
this[schemaType][entry.name] = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes schema to a callback
|
||||
* @param {Function} callback
|
||||
*/
|
||||
this.getSchema = function(callback) {
|
||||
callback(Zotero.Connector_Types.schema);
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.CachedTypes = function() {
|
||||
this.getID = function(idOrName) {
|
||||
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
|
||||
return Zotero.Connector.Types[this.schemaType][idOrName].id;
|
||||
if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false;
|
||||
return Zotero.Connector_Types[this.schemaType][idOrName].id;
|
||||
}
|
||||
|
||||
this.getName = function(idOrName) {
|
||||
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
|
||||
return Zotero.Connector.Types[this.schemaType][idOrName].name;
|
||||
if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false;
|
||||
return Zotero.Connector_Types[this.schemaType][idOrName].name;
|
||||
}
|
||||
|
||||
this.getLocalizedString = function(idOrName) {
|
||||
if(!Zotero.Connector.Types[this.schemaType][idOrName]) return false;
|
||||
return Zotero.Connector.Types[this.schemaType][idOrName].localizedString;
|
||||
if(!Zotero.Connector_Types[this.schemaType][idOrName]) return false;
|
||||
return Zotero.Connector_Types[this.schemaType][idOrName].localizedString;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,14 +81,14 @@ Zotero.ItemTypes = new function() {
|
|||
Zotero.CachedTypes.call(this);
|
||||
|
||||
this.getImageSrc = function(idOrName) {
|
||||
if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false;
|
||||
if(!Zotero.Connector_Types["itemTypes"][idOrName]) return false;
|
||||
|
||||
if(Zotero.isFx) {
|
||||
return "chrome://zotero/skin/"+Zotero.Connector.Types["itemTypes"][idOrName].icon;
|
||||
return "chrome://zotero/skin/"+Zotero.Connector_Types["itemTypes"][idOrName].icon;
|
||||
} else if(Zotero.isChrome) {
|
||||
return chrome.extension.getURL("images/"+Zotero.Connector.Types["itemTypes"][idOrName].icon);
|
||||
return chrome.extension.getURL("images/"+Zotero.Connector_Types["itemTypes"][idOrName].icon);
|
||||
} else if(Zotero.isSafari) {
|
||||
return safari.extension.baseURI+"images/itemTypes/"+Zotero.Connector.Types["itemTypes"][idOrName].icon;
|
||||
return safari.extension.baseURI+"images/itemTypes/"+Zotero.Connector_Types["itemTypes"][idOrName].icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,11 +98,11 @@ Zotero.CreatorTypes = new function() {
|
|||
Zotero.CachedTypes.call(this);
|
||||
|
||||
this.getTypesForItemType = function(idOrName) {
|
||||
if(!Zotero.Connector.Types["itemTypes"][idOrName]) return false;
|
||||
var itemType = Zotero.Connector.Types["itemTypes"][idOrName];
|
||||
if(!Zotero.Connector_Types["itemTypes"][idOrName]) return false;
|
||||
var itemType = Zotero.Connector_Types["itemTypes"][idOrName];
|
||||
var creatorTypes = [];
|
||||
for(var i=0; i<itemType.creatorTypes.length; i++) {
|
||||
creatorTypes.push(Zotero.Connector.Types["creatorTypes"][itemType.creatorTypes[i]]);
|
||||
creatorTypes.push(Zotero.Connector_Types["creatorTypes"][itemType.creatorTypes[i]]);
|
||||
}
|
||||
return creatorTypes;
|
||||
}
|
||||
|
@ -107,10 +114,29 @@ Zotero.ItemFields = new function() {
|
|||
|
||||
this.isValidForType = function(fieldIdOrName, typeIdOrName) {
|
||||
// mimics itemFields.js
|
||||
if(!Zotero.Connector.Types["fields"][fieldIdOrName]
|
||||
|| !Zotero.Connector.Types["itemTypes"][typeIdOrName]) throw "Invalid field or type ID";
|
||||
if(!Zotero.Connector_Types["fields"][fieldIdOrName]
|
||||
|| !Zotero.Connector_Types["itemTypes"][typeIdOrName]) throw "Invalid field or type ID";
|
||||
|
||||
return Zotero.Connector.Types["itemTypes"][typeIdOrName].fields.indexOf(
|
||||
Zotero.Connector.Types["fields"][fieldIdOrName].id) !== -1;
|
||||
return Zotero.Connector_Types["itemTypes"][typeIdOrName].fields.indexOf(
|
||||
Zotero.Connector_Types["fields"][fieldIdOrName].id) !== -1;
|
||||
}
|
||||
|
||||
this.getFieldIDFromTypeAndBase = function(itemType, baseField) {
|
||||
if(!Zotero.Connector_Types["fields"][baseField]
|
||||
|| !Zotero.Connector_Types["itemTypes"][itemType]) throw "Invalid field or type ID";
|
||||
|
||||
// get as ID
|
||||
baseField = Zotero.Connector_Types["fields"][baseField].id;
|
||||
|
||||
|
||||
// loop through base fields for item type
|
||||
var baseFields = Zotero.Connector_Types["itemTypes"][itemType]["baseFields"];
|
||||
for(var i in baseFields) {
|
||||
if(baseFields[i] === baseField) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -27,63 +27,16 @@ Zotero.Connector = new function() {
|
|||
const CONNECTOR_URI = "http://127.0.0.1:23119/";
|
||||
const CONNECTOR_SERVER_API_VERSION = 1;
|
||||
|
||||
this.isOnline = true;
|
||||
this.haveRefreshedData = false;
|
||||
this.data = null;
|
||||
|
||||
/**
|
||||
* Called to initialize Zotero
|
||||
*/
|
||||
this.init = function() {
|
||||
Zotero.Connector.getData();
|
||||
}
|
||||
this.isOnline = null;
|
||||
|
||||
/**
|
||||
* Checks if Zotero is online and passes current status to callback
|
||||
* @param {Function} callback
|
||||
*/
|
||||
this.checkIsOnline = function(callback) {
|
||||
if(Zotero.Connector.isOnline) {
|
||||
callback(true);
|
||||
} else {
|
||||
Zotero.Connector.getData(callback);
|
||||
}
|
||||
}
|
||||
|
||||
function _getDataFile() {
|
||||
var dataFile = Zotero.getZoteroDirectory();
|
||||
dataFile.append("connector.json");
|
||||
return dataFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the Zotero.Connector.data object to localStorage/preferences
|
||||
* @param {String} [json] The
|
||||
*/
|
||||
this.serializeData = function(json) {
|
||||
if(!json) json = JSON.stringify(Zotero.Connector.data);
|
||||
|
||||
if(Zotero.isFx) {
|
||||
Zotero.File.putContents(_getDataFile(), json);
|
||||
} else {
|
||||
localStorage.data = json;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserializes the Zotero.Connector.data object from localStorage/preferences
|
||||
*/
|
||||
this.unserializeData = function() {
|
||||
var data = null;
|
||||
|
||||
if(Zotero.isFx) {
|
||||
var dataFile = _getDataFile();
|
||||
if(dataFile.exists()) data = Zotero.File.getContents(dataFile);
|
||||
} else {
|
||||
if(localStorage.data) data = localStorage.data;
|
||||
}
|
||||
|
||||
if(data) Zotero.Connector.data = JSON.parse(data);
|
||||
Zotero.Connector.callMethod("ping", {}, function(status) {
|
||||
callback(status !== false);
|
||||
});
|
||||
}
|
||||
|
||||
// saner descriptions of some HTTP error codes
|
||||
|
@ -96,25 +49,17 @@ Zotero.Connector = new function() {
|
|||
this.EXCEPTION_CODES = [0, 400, 404, 412, 500, 501];
|
||||
|
||||
/**
|
||||
* Updates Zotero's status depending on the success or failure of a request
|
||||
* Sends the XHR to execute an RPC call.
|
||||
*
|
||||
* @param {Boolean} isOnline Whether or not Zotero was online
|
||||
* @param {Function} successCallback Function to be called after loading new data if
|
||||
* Zotero is online
|
||||
* @param {Function} failureCallback Function to be called if Zotero is offline
|
||||
*
|
||||
* Calls Zotero.Connector.Browser.onStateChange(isOnline, method, context) if status has changed
|
||||
* @param {String} method RPC method. See documentation above.
|
||||
* @param {Object} data RPC data. See documentation above.
|
||||
* @param {Function} callback Function to be called when requests complete.
|
||||
*/
|
||||
function _checkState(isOnline, callback) {
|
||||
if(isOnline) {
|
||||
if(Zotero.Connector.haveRefreshedData) {
|
||||
if(callback) callback(true);
|
||||
} else {
|
||||
Zotero.Connector.getData(callback);
|
||||
}
|
||||
} else {
|
||||
if(callback) callback(false, this.EXCEPTION_NOT_AVAILABLE);
|
||||
}
|
||||
this.callMethod = function(method, data, callback) {
|
||||
var newCallback = function(req) {
|
||||
try {
|
||||
var isOnline = req.status !== Zotero.Connector.EXCEPTION_NOT_AVAILABLE
|
||||
&& req.status !== Zotero.Connector.EXCEPTION_INCOMPATIBLE_VERSION;
|
||||
|
||||
if(Zotero.Connector.isOnline !== isOnline) {
|
||||
Zotero.Connector.isOnline = isOnline;
|
||||
|
@ -123,77 +68,11 @@ Zotero.Connector = new function() {
|
|||
}
|
||||
}
|
||||
|
||||
return isOnline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads list of translators and other relevant data from local Zotero instance
|
||||
*
|
||||
* @param {Function} successCallback Function to be called after loading new data if
|
||||
* Zotero is online
|
||||
* @param {Function} failureCallback Function to be called if Zotero is offline
|
||||
*/
|
||||
this.getData = function(callback) {
|
||||
Zotero.HTTP.doPost(CONNECTOR_URI+"connector/getData",
|
||||
JSON.stringify({"browser":Zotero.browser, "apiVersion":CONNECTOR_SERVER_API_VERSION}),
|
||||
function(req) {
|
||||
var isOnline = req.status !== 0 && req.status !== 412;
|
||||
|
||||
if(isOnline) {
|
||||
// if request succeded, update data
|
||||
Zotero.Connector.haveRefreshedData = true;
|
||||
Zotero.Connector.serializeData(req.responseText);
|
||||
Zotero.Connector.data = JSON.parse(req.responseText);
|
||||
} else {
|
||||
// if request failed, unserialize saved data
|
||||
Zotero.Connector.unserializeData();
|
||||
}
|
||||
Zotero.Connector.Types.init(Zotero.Connector.data.schema);
|
||||
|
||||
// update online state. this shouldn't loop, since haveRefreshedData should
|
||||
// be true if isOnline is true.
|
||||
_checkState(isOnline, callback);
|
||||
}, {"Content-Type":"application/json"});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives callback an object containing schema and preferences from Zotero.Connector.data
|
||||
*/
|
||||
this.getSchemaAndPreferences = function(callback) {
|
||||
if(Zotero.Connector.data) {
|
||||
callback({"schema":Zotero.Connector.data["schema"],
|
||||
"preferences":Zotero.Connector.data["preferences"]});
|
||||
return;
|
||||
}
|
||||
|
||||
this.getData(function(success) {
|
||||
if(success) {
|
||||
callback({"schema":Zotero.Connector.data["schema"],
|
||||
"preferences":Zotero.Connector.data["preferences"]});
|
||||
return;
|
||||
}
|
||||
callback(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the XHR to execute an RPC call.
|
||||
*
|
||||
* @param {String} method RPC method. See documentation above.
|
||||
* @param {Object} data RPC data. See documentation above.
|
||||
* @param {Function} successCallback Function to be called if request succeeded.
|
||||
* @param {Function} failureCallback Function to be called if request failed.
|
||||
*/
|
||||
this.callMethod = function(method, data, callback) {
|
||||
Zotero.HTTP.doPost(CONNECTOR_URI+"connector/"+method, JSON.stringify(data),
|
||||
function(req) {
|
||||
_checkState(req.status !== this.EXCEPTION_NOT_AVAILABLE
|
||||
&& req.status !== this.EXCEPTION_INCOMPATIBLE_VERSION, function() {
|
||||
if(!callback) return;
|
||||
|
||||
if(Zotero.Connector.EXCEPTION_CODES.indexOf(req.status) !== -1) {
|
||||
callback(false, req.status);
|
||||
Zotero.debug("Connector: Method "+method+" failed");
|
||||
if(callback) callback(false, req.status);
|
||||
} else {
|
||||
Zotero.debug("Connector: Method "+method+" succeeded");
|
||||
var val = null;
|
||||
if(req.responseText) {
|
||||
if(req.getResponseHeader("Content-Type") === "application/json") {
|
||||
|
@ -202,9 +81,16 @@ Zotero.Connector = new function() {
|
|||
val = req.responseText;
|
||||
}
|
||||
}
|
||||
callback(val, req.status);
|
||||
if(callback) callback(val, req.status);
|
||||
}
|
||||
});
|
||||
}, {"Content-Type":"application/json"});
|
||||
} catch(e) {
|
||||
Zotero.logError(e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
var uri = CONNECTOR_URI+"connector/"+method;
|
||||
|
||||
Zotero.HTTP.doPost(uri, JSON.stringify(data),
|
||||
newCallback, {"Content-Type":"application/json"});
|
||||
}
|
||||
}
|
|
@ -81,6 +81,6 @@ Zotero.Connector_Debug = new function() {
|
|||
|
||||
var reportID = reported[0].getAttribute('reportID');
|
||||
callback(true, reportID);
|
||||
}, {"Content-Type":"application/octet-stream"});
|
||||
}, {"Content-Type":"text/plain"});
|
||||
}
|
||||
}
|
170
chrome/content/zotero/xpcom/connector/repo.js
Normal file
170
chrome/content/zotero/xpcom/connector/repo.js
Normal file
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2011 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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<translators.length; i++) {
|
||||
for(var i in translators) {
|
||||
var translator = new Zotero.Translator(translators[i]);
|
||||
_translators[translator.translatorID] = translator;
|
||||
|
||||
|
@ -185,24 +198,104 @@ Zotero.Translators = new function() {
|
|||
/**
|
||||
* Converts translators to JSON-serializable objects
|
||||
*/
|
||||
this.serialize = function(translator) {
|
||||
this.serialize = function(translator, properties) {
|
||||
// handle translator arrays
|
||||
if(translator.length !== undefined) {
|
||||
var newTranslators = new Array(translator.length);
|
||||
for(var i in translator) {
|
||||
newTranslators[i] = Zotero.Translators.serialize(translator[i]);
|
||||
newTranslators[i] = Zotero.Translators.serialize(translator[i], properties);
|
||||
}
|
||||
return newTranslators;
|
||||
}
|
||||
|
||||
// handle individual translator
|
||||
var newTranslator = {};
|
||||
for(var i in PRESERVE_PROPERTIES) {
|
||||
var property = PRESERVE_PROPERTIES[i];
|
||||
for(var i in properties) {
|
||||
var property = properties[i];
|
||||
newTranslator[property] = translator[property];
|
||||
}
|
||||
return newTranslator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all translator data to localStorage
|
||||
*/
|
||||
this.update = function(newMetadata, reset) {
|
||||
if(!_initialized) Zotero.Translators.init();
|
||||
if(!newMetadata.length) return;
|
||||
|
||||
if(reset) {
|
||||
var serializedTranslators = newMetadata;
|
||||
if(!Zotero.isFx) {
|
||||
// clear cached translatorCode
|
||||
Zotero.debug("Translators: Resetting translators");
|
||||
for(var i in localStorage) {
|
||||
if(i.substr(0, TRANSLATOR_CODE_PREFIX.length) === TRANSLATOR_CODE_PREFIX) {
|
||||
delete localStorage[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var serializedTranslators = [];
|
||||
var hasChanged = false;
|
||||
|
||||
// Update translators with new metadata
|
||||
for(var i in newMetadata) {
|
||||
var newTranslator = new Zotero.Translator(newMetadata[i]);
|
||||
|
||||
if(_translators.hasOwnProperty(newTranslator.translatorID)) {
|
||||
if(_translators[newTranslator.translatorID].lastUpdated !== newTranslator.lastUpdated) {
|
||||
if(!Zotero.isFx) {
|
||||
// if lastUpdated does not match between old and new translator
|
||||
// invalidate translator code cache
|
||||
delete localStorage["translatorCode-"+newTranslator.translatorID];
|
||||
}
|
||||
|
||||
Zotero.debug("Translators: Updating "+newTranslator.label);
|
||||
hasChanged = true;
|
||||
}
|
||||
} else {
|
||||
Zotero.debug("Translators: Adding "+newTranslator.label);
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
_translators[newTranslator.translatorID] = newTranslator;
|
||||
}
|
||||
|
||||
if(!hasChanged) return;
|
||||
|
||||
// Serialize translators
|
||||
for(var i in _translators) {
|
||||
var serializedTranslator = this.serialize(_translators[i], TRANSLATOR_SAVE_PROPERTIES);
|
||||
|
||||
// don't save run mode
|
||||
delete serializedTranslator.runMode;
|
||||
|
||||
serializedTranslators.push(serializedTranslator);
|
||||
}
|
||||
}
|
||||
|
||||
// Store
|
||||
if(!Zotero.isFx) {
|
||||
localStorage["translatorMetadata"] = JSON.stringify(serializedTranslators);
|
||||
}
|
||||
|
||||
// Reinitialize
|
||||
Zotero.Translators.init(serializedTranslators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocesses code for a translator
|
||||
*/
|
||||
this.preprocessCode = function(code) {
|
||||
if(!Zotero.isFx) {
|
||||
const foreach = /^(\s*)for each\s*\((var )?([^ ]+) in (.*?)\)(\s*){/gm;
|
||||
code = code.replace(foreach, "$1var $3_zForEachSubject = $4; "+
|
||||
"for(var $3_zForEachIndex in $3_zForEachSubject)$5{ "+
|
||||
"$2$3 = $3_zForEachSubject[$3_zForEachIndex];", code);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -242,10 +335,11 @@ Zotero.Translators.CodeGetter.prototype.getCodeFor = function(i) {
|
|||
}
|
||||
}
|
||||
|
||||
const TRANSLATOR_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target",
|
||||
"priority", "browserSupport"];
|
||||
var PRESERVE_PROPERTIES = TRANSLATOR_PROPERTIES.concat(["displayOptions", "configOptions",
|
||||
"code", "runMode"]);
|
||||
const TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target",
|
||||
"priority"];
|
||||
var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["displayOptions", "configOptions",
|
||||
"browserSupport", "code", "runMode"]);
|
||||
var TRANSLATOR_SAVE_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["browserSupport"]);
|
||||
/**
|
||||
* @class Represents an individual translator
|
||||
* @constructor
|
||||
|
@ -270,8 +364,8 @@ var PRESERVE_PROPERTIES = TRANSLATOR_PROPERTIES.concat(["displayOptions", "confi
|
|||
*/
|
||||
Zotero.Translator = function(info) {
|
||||
// make sure we have all the properties
|
||||
for(var i in TRANSLATOR_PROPERTIES) {
|
||||
var property = TRANSLATOR_PROPERTIES[i];
|
||||
for(var i in TRANSLATOR_REQUIRED_PROPERTIES) {
|
||||
var property = TRANSLATOR_REQUIRED_PROPERTIES[i];
|
||||
if(info[property] === undefined) {
|
||||
this.logError('Missing property "'+property+'" in translator metadata JSON object in ' + info.label);
|
||||
haveMetadata = false;
|
||||
|
@ -281,7 +375,9 @@ Zotero.Translator = function(info) {
|
|||
}
|
||||
}
|
||||
|
||||
if(info["browserSupport"].indexOf(Zotero.browser) !== -1) {
|
||||
this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g";
|
||||
|
||||
if(this.browserSupport.indexOf(Zotero.browser) !== -1) {
|
||||
this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
|
||||
} else {
|
||||
this.runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE;
|
||||
|
@ -313,12 +409,13 @@ Zotero.Translator.prototype.getCode = function(callback) {
|
|||
callback(true);
|
||||
} else {
|
||||
var me = this;
|
||||
Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":this.translatorID},
|
||||
Zotero.Repo.getTranslatorCode(this.translatorID,
|
||||
function(code) {
|
||||
if(!code) {
|
||||
callback(false);
|
||||
} else {
|
||||
me.code = me.preprocessCode(code);
|
||||
// cache code for session only (we have standalone anyway)
|
||||
me.code = code;
|
||||
callback(true);
|
||||
}
|
||||
}
|
||||
|
@ -326,20 +423,6 @@ Zotero.Translator.prototype.getCode = function(callback) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocesses code for this translator
|
||||
*/
|
||||
Zotero.Translator.prototype.preprocessCode = function(code) {
|
||||
if(!Zotero.isFx) {
|
||||
const foreach = /^(\s*)for each\s*\((var )?([^ ]+) in (.*?)\)(\s*){/gm;
|
||||
code = code.replace(foreach, "$1var $3_zForEachSubject = $4; "+
|
||||
"for(var $3_zForEachIndex in $3_zForEachSubject)$5{ "+
|
||||
"$2$3 = $3_zForEachSubject[$3_zForEachIndex];", code);
|
||||
Zotero.debug(code);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
Zotero.Translator.prototype.__defineGetter__("displayOptions", function() {
|
||||
return Zotero.Utilities.deepCopy(this._displayOptions);
|
||||
});
|
||||
|
@ -356,7 +439,7 @@ Zotero.Translator.prototype.__defineGetter__("configOptions", function() {
|
|||
* @param {Integer} colNumber
|
||||
*/
|
||||
Zotero.Translator.prototype.logError = function(message, type, line, lineNumber, colNumber) {
|
||||
Zotero.log(message, type ? type : "error", this.label);
|
||||
Zotero.logError(message);
|
||||
}
|
||||
|
||||
Zotero.Translator.RUN_MODE_IN_BROWSER = 1;
|
||||
|
|
1
chrome/content/zotero/xpcom/connector/typeSchemaData.js
Normal file
1
chrome/content/zotero/xpcom/connector/typeSchemaData.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -23,7 +23,7 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
const CONNECTOR_SERVER_API_VERSION = 1;
|
||||
const CONNECTOR_SERVER_API_VERSION = 2;
|
||||
|
||||
Zotero.Server.Connector = function() {};
|
||||
Zotero.Server.Connector._waitingForSelection = {};
|
||||
|
@ -196,30 +196,13 @@ Zotero.Server.Connector.CookieManager.prototype = {
|
|||
* Lists all available translators, including code for translators that should be run on every page
|
||||
*
|
||||
* Accepts:
|
||||
* browser - one-letter code of the current browser
|
||||
* g = Gecko (Firefox)
|
||||
* c = Google Chrome (WebKit & V8)
|
||||
* s = Safari (WebKit & Nitro/Squirrelfish Extreme)
|
||||
* i = Internet Explorer
|
||||
* Nothing
|
||||
* Returns:
|
||||
* translators - Zotero.Translator objects
|
||||
* schema - Some information about the database. Currently includes:
|
||||
* itemTypes
|
||||
* name
|
||||
* localizedString
|
||||
* creatorTypes
|
||||
* fields
|
||||
* baseFields
|
||||
* creatorTypes
|
||||
* name
|
||||
* localizedString
|
||||
* fields
|
||||
* name
|
||||
* localizedString
|
||||
* Array of Zotero.Translator objects
|
||||
*/
|
||||
Zotero.Server.Connector.GetData = function() {};
|
||||
Zotero.Server.Endpoints["/connector/getData"] = Zotero.Server.Connector.GetData;
|
||||
Zotero.Server.Connector.GetData.prototype = {
|
||||
Zotero.Server.Connector.GetTranslators = function() {};
|
||||
Zotero.Server.Endpoints["/connector/getTranslators"] = Zotero.Server.Connector.GetTranslators;
|
||||
Zotero.Server.Connector.GetTranslators.prototype = {
|
||||
"supportedMethods":["POST"],
|
||||
"supportedDataTypes":["application/json"],
|
||||
|
||||
|
@ -229,12 +212,8 @@ Zotero.Server.Connector.GetData.prototype = {
|
|||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(data, sendResponseCallback) {
|
||||
if(data.apiVersion !== CONNECTOR_SERVER_API_VERSION) {
|
||||
sendResponseCallback(412);
|
||||
}
|
||||
|
||||
// Translator data
|
||||
var responseData = {"preferences":{}, "translators":[]};
|
||||
var responseData = [];
|
||||
|
||||
// TODO only send necessary translators
|
||||
var translators = Zotero.Translators.getAll();
|
||||
|
@ -247,68 +226,10 @@ Zotero.Server.Connector.GetData.prototype = {
|
|||
|
||||
// Do not pass targetless translators that do not support this browser (since that
|
||||
// would mean passing each page back to Zotero)
|
||||
responseData.translators.push(serializableTranslator);
|
||||
}
|
||||
|
||||
// Various DB data (only sending what is required at the moment)
|
||||
var systemVersion = Zotero.Schema.getDBVersion("system");
|
||||
if(systemVersion != data.systemVersion) {
|
||||
responseData.schema = this._generateTypeSchema();
|
||||
}
|
||||
|
||||
// Preferences
|
||||
var prefs = Zotero.Prefs.prefBranch.getChildList("", {}, {});
|
||||
for each(var pref in prefs) {
|
||||
responseData.preferences[pref] = Zotero.Prefs.get(pref);
|
||||
responseData.push(serializableTranslator);
|
||||
}
|
||||
|
||||
sendResponseCallback(200, "application/json", JSON.stringify(responseData));
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates a type schema. This is used by connector/type.js to handle types without DB access.
|
||||
*/
|
||||
"_generateTypeSchema":function() {
|
||||
var schema = {"itemTypes":{}, "creatorTypes":{}, "fields":{}};
|
||||
var types = Zotero.ItemTypes.getTypes();
|
||||
|
||||
var fieldIDs = Zotero.DB.columnQuery("SELECT fieldID FROM fieldsCombined");
|
||||
var baseMappedFields = Zotero.ItemFields.getBaseMappedFields();
|
||||
for each(var fieldID in fieldIDs) {
|
||||
var fieldObj = {"name":Zotero.ItemFields.getName(fieldID)};
|
||||
try {
|
||||
fieldObj.localizedString = Zotero.getString("itemFields." + fieldObj.name)
|
||||
} catch(e) {}
|
||||
schema.fields[fieldID] = fieldObj;
|
||||
}
|
||||
|
||||
// names, localizedStrings, creatorTypes, and fields for each item type
|
||||
for each(var type in types) {
|
||||
var fieldIDs = Zotero.ItemFields.getItemTypeFields(type.id);
|
||||
var baseFields = {};
|
||||
for each(var fieldID in fieldIDs) {
|
||||
if(baseMappedFields.indexOf(fieldID) !== -1) {
|
||||
baseFields[fieldID] = Zotero.ItemFields.getFieldIDFromTypeAndBase(type.id, fieldID);
|
||||
}
|
||||
}
|
||||
|
||||
var icon = Zotero.ItemTypes.getImageSrc(type.name);
|
||||
icon = icon.substr(icon.lastIndexOf("/")+1);
|
||||
|
||||
schema.itemTypes[type.id] = {"name":type.name,
|
||||
"localizedString":Zotero.ItemTypes.getLocalizedString(type.name),
|
||||
"creatorTypes":[creatorType.id for each(creatorType in Zotero.CreatorTypes.getTypesForItemType(type.id))],
|
||||
"fields":fieldIDs, "baseFields":baseFields, "icon":icon};
|
||||
|
||||
}
|
||||
|
||||
var types = Zotero.CreatorTypes.getTypes();
|
||||
for each(var type in types) {
|
||||
schema.creatorTypes[type.id] = {"name":type.name,
|
||||
"localizedString":Zotero.CreatorTypes.getLocalizedString(type.name)};
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -611,3 +532,28 @@ Zotero.Server.Connector.GetTranslatorCode.prototype = {
|
|||
sendResponseCallback(200, "application/javascript", translator.code);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test connection
|
||||
*
|
||||
* Accepts:
|
||||
* Nothing
|
||||
* Returns:
|
||||
* Nothing (200 OK response)
|
||||
*/
|
||||
Zotero.Server.Connector.Ping = function() {};
|
||||
Zotero.Server.Endpoints["/connector/ping"] = Zotero.Server.Connector.Ping;
|
||||
Zotero.Server.Connector.Ping.prototype = {
|
||||
"supportedMethods":["POST"],
|
||||
"supportedDataTypes":["application/json"],
|
||||
|
||||
/**
|
||||
* Finishes up translation when item selection is complete
|
||||
* @param {String} data POST data or GET query string
|
||||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(postData, sendResponseCallback) {
|
||||
sendResponseCallback(200);
|
||||
}
|
||||
}
|
|
@ -1633,7 +1633,7 @@ Zotero.Translate.Search.prototype.setTranslator = function(translator) {
|
|||
* translation fails
|
||||
*/
|
||||
Zotero.Translate.Search.prototype.complete = function(returnValue, error) {
|
||||
if(this._currentState == "translate" && !this.newItems.length) {
|
||||
if(this._currentState == "translate" && (!this.newItems || !this.newItems.length)) {
|
||||
Zotero.debug("Translate: Could not find a result using "+this.translator[0].label+": \n"
|
||||
+this._generateErrorString(error), 3);
|
||||
if(this.translator.length > 1) {
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -153,3 +153,7 @@ pref("extensions.zotero.pane.persist", '');
|
|||
|
||||
// Domains allowed to import, separated by a semicolon
|
||||
pref("extensions.zotero.ingester.allowedSites", "");
|
||||
|
||||
// Connector
|
||||
pref("extensions.zotero.connector.repo.lastCheck.localTime", 0);
|
||||
pref("extensions.zotero.connector.repo.lastCheck.repoTime", 0);
|
Loading…
Reference in a new issue