zotero/chrome/content/zotero/xpcom/zotero.js

2604 lines
72 KiB
JavaScript
Raw Normal View History

/*
***** BEGIN LICENSE BLOCK *****
2009-12-28 09:47:49 +00:00
Copyright © 2009 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
2009-12-28 09:47:49 +00:00
This file is part of Zotero.
2009-12-28 09:47:49 +00:00
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
2009-12-28 09:47:49 +00:00
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
2009-12-28 09:47:49 +00:00
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.
2009-12-28 09:47:49 +00:00
You should have received a copy of the GNU Affero General Public License
2009-12-28 09:47:49 +00:00
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
const ZOTERO_CONFIG = {
GUID: 'zotero@chnm.gmu.edu',
2011-03-09 02:53:04 +00:00
REPOSITORY_URL: 'https://repo.zotero.org/repo',
REPOSITORY_CHECK_INTERVAL: 86400, // 24 hours
2008-06-17 20:41:31 +00:00
REPOSITORY_RETRY_INTERVAL: 3600, // 1 hour
2.0b3 megacommit - Support for group libraries - General support for multiple libraries of different types - Streamlined sync support - Using solely libraryID and key rather than itemID, and removed all itemID-changing code - Combined two requests for increased performance and decreased server load - Added warning on user account change - Provide explicit error message on SSL failure - Removed snapshot and link toolbar buttons and changed browser context menu options and drags to create parent items + snapshots - Closes #786, Add numPages field - Fixes #1063, Duplicate item with tags broken in Sync Preview - Added better purging of deleted tags - Added local user key before first sync - Add clientDateModified to all objects for more flexibility in syncing - Added new triples-based Relation object type, currently used to store links between items copied between local and group libraries - Updated zotero.org translator for groups - Additional trigger-based consistency checks - Fixed broken URL drag in Firefox 3.5 - Disabled zeroconf menu option (no longer functional) Developer-specific changes: - Overhauled data layer - Data object constructors no longer take arguments (return to 1.0-like API) - Existing objects can be retrieved by setting id or library/key properties - id/library/key must be set for new objects before other fields - New methods: - ZoteroPane.getSelectedLibraryID() - ZoteroPane.getSelectedGroup(asID) - ZoteroPane.addItemFromDocument(doc, itemType, saveSnapshot) - ZoteroPane.addItemFromURL(url, itemType) - ZoteroPane.canEdit() - Zotero.CollectionTreeView.selectLibrary(libraryID) - New Zotero.URI methods - Changed methods - Many data object methods now take a libraryID - ZoteroPane.addAttachmentFromPage(link, itemID) - Removed saveItem and saveAttachments parameters from Zotero.Translate constructor - translate() now takes a libraryID, null for local library, or false to not save items (previously on constructor) - saveAttachments is now a translate() parameter - Zotero.flattenArguments() better handles passed objects - Zotero.File.getFileHash() (not currently used)
2009-05-14 18:23:40 +00:00
BASE_URI: 'http://zotero.org/',
WWW_BASE_URL: 'http://www.zotero.org/',
PROXY_AUTH_URL: 'http://zotero.org.s3.amazonaws.com/proxy-auth',
Zotero File Storage megacommit - Group file sync via Zotero File Storage - Split file syncing into separate modules for ZFS and WebDAV - Dragging items between libraries copies child notes, snapshots/files, and links based on checkboxes for each (enabled by default) in the Zotero preferences - Sync errors now trigger an exclamation/error icon separate from the sync icon, with a popup window displaying the error and an option to report it - Various errors that could cause perpetual sync icon spinning now stop the sync properly - Zotero.Utilities.md5(str) is now md5(strOrFile, base64) - doPost(), doHead(), and retrieveSource() now takes a headers parameter instead of requestContentType - doHead() can now accept an nsIURI (with login credentials), is a background request, and isn't cached - When library access or file writing access is denied during sync, display a warning and then reset local group to server version - Perform additional steps (e.g., removing local groups) when switching sync users to prevent errors - Compare hash as well as mod time when checking for modified local files - Don't trigger notifications when removing groups from the client - Clear relation links to items in removed groups - Zotero.Item.attachmentHash property to get file MD5 - importFromFile() now takes libraryID as a third parameter - Zotero.Attachments.getNumFiles() returns the number of files in the attachment directory - Zotero.Attachments.copyAttachmentToLibrary() copies an attachment item, including files, to another library - Removed Zotero.File.getFileHash() in favor of updated Zotero.Utilities.md5() - Zotero.File.copyDirectory(dir, newDir) copies all files from dir into newDir - Preferences shuffling: OpenURL to Advanced, import/export character set options to Export, "Include URLs of paper articles in references" to Styles - Other stuff I don't remember Suffice it to say, this could use testing.
2009-09-13 07:23:29 +00:00
SYNC_URL: 'https://sync.zotero.org/',
API_URL: 'https://api.zotero.org/',
2013-03-12 23:57:47 +00:00
API_VERSION: 2,
2011-08-29 23:59:59 +00:00
PREF_BRANCH: 'extensions.zotero.',
BOOKMARKLET_ORIGIN : 'https://www.zotero.org',
HTTP_BOOKMARKLET_ORIGIN : 'http://www.zotero.org',
BOOKMARKLET_URL: 'https://www.zotero.org/bookmarklet/'
};
// Commonly used imports accessible anywhere
Components.utils.import("resource://zotero/q.js");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/osfile.jsm");
/*
* Core functions
*/
(function(){
// Privileged (public) methods
this.init = init;
2007-10-23 07:11:59 +00:00
this.stateCheck = stateCheck;
this.getProfileDirectory = getProfileDirectory;
this.getZoteroDirectory = getZoteroDirectory;
this.getStorageDirectory = getStorageDirectory;
this.getZoteroDatabase = getZoteroDatabase;
2007-10-23 07:11:59 +00:00
this.chooseZoteroDirectory = chooseZoteroDirectory;
this.debug = debug;
2007-10-23 07:11:59 +00:00
this.log = log;
this.logError = logError;
2007-10-23 07:11:59 +00:00
this.getErrors = getErrors;
this.getSystemInfo = getSystemInfo;
this.safeDebug = safeDebug;
this.getString = getString;
2007-10-23 07:11:59 +00:00
this.localeJoin = localeJoin;
this.getLocaleCollation = getLocaleCollation;
this.setFontSize = setFontSize;
this.flattenArguments = flattenArguments;
Closes #259, auto-complete of tags Addresses #260, Add auto-complete to search window - New XPCOM autocomplete component for Zotero data -- can be used by setting the autocompletesearch attribute of a textbox to 'zotero' and passing a search scope with the autocompletesearchparam attribute. Additional parameters can be passed by appending them to the autocompletesearchparam value with a '/', e.g. 'tag/2732' (to exclude tags that show up in item 2732) - Tag entry now uses more or less the same interface as metadata -- no more popup window -- note that tab isn't working properly yet, and there's no way to quickly enter multiple tags (though it's now considerably quicker than it was before) - Autocomplete for tags, excluding any tags already set for the current item - Standalone note windows now register with the Notifier (since tags needed item modification notifications to work properly), which will help with #282, "Notes opened in separate windows need item notification" - Tags are now retrieved in alphabetical order - Scholar.Item.replaceTag(oldTagID, newTag), with a single notify - Scholar.getAncestorByTagName(elem, tagName) -- walk up the DOM tree from an element until an element with the specified tag name is found (also checks with 'xul:' prefix, for use in XBL), or false if not found -- probably shouldn't be used too widely, since it's doing string comparisons, but better than specifying, say, nine '.parentNode' properties, and makes for more resilient code A few notes: - Autocomplete in Minefield seems to self-destruct after using it in the same field a few times, taking down saving of the field with it -- this may or may not be my fault, but it makes Zotero more or less unusable in 3.0 at the moment. Sorry. (I use 3.0 myself for development, so I'll work on it.) - This would have been much, much easier if having an autocomplete textbox (which uses an XBL-generated popup for the suggestions) within a popup (as it is in the independent note edit panes) didn't introduce all sorts of crazy bugs that had to be defeated with annoying hackery -- one side effect of this is that at the moment you can't close the tags popup with the Escape key - Independent note windows now need to pull in itemPane.js to function properly, which is a bit messy and not ideal, but less messy and more ideal than duplicating all the dual-state editor and tabindex logic would be - Hitting tab in a tag field not only doesn't work but also breaks things until the next window refresh. - There are undoubtedly other bugs.
2006-09-07 08:07:48 +00:00
this.getAncestorByTagName = getAncestorByTagName;
this.join = join;
this.randomString = randomString;
this.moveToUnique = moveToUnique;
// Public properties
2007-10-23 07:11:59 +00:00
this.initialized = false;
this.skipLoading = false;
this.startupError;
2007-10-23 07:11:59 +00:00
this.__defineGetter__("startupErrorHandler", function() { return _startupErrorHandler; });
this.version;
this.platform;
this.locale;
2007-10-23 07:11:59 +00:00
this.dir; // locale direction: 'ltr' or 'rtl'
this.isMac;
2007-10-23 07:11:59 +00:00
this.isWin;
this.initialURL; // used by Schema to show the changelog on upgrades
2.0b3 megacommit - Support for group libraries - General support for multiple libraries of different types - Streamlined sync support - Using solely libraryID and key rather than itemID, and removed all itemID-changing code - Combined two requests for increased performance and decreased server load - Added warning on user account change - Provide explicit error message on SSL failure - Removed snapshot and link toolbar buttons and changed browser context menu options and drags to create parent items + snapshots - Closes #786, Add numPages field - Fixes #1063, Duplicate item with tags broken in Sync Preview - Added better purging of deleted tags - Added local user key before first sync - Add clientDateModified to all objects for more flexibility in syncing - Added new triples-based Relation object type, currently used to store links between items copied between local and group libraries - Updated zotero.org translator for groups - Additional trigger-based consistency checks - Fixed broken URL drag in Firefox 3.5 - Disabled zeroconf menu option (no longer functional) Developer-specific changes: - Overhauled data layer - Data object constructors no longer take arguments (return to 1.0-like API) - Existing objects can be retrieved by setting id or library/key properties - id/library/key must be set for new objects before other fields - New methods: - ZoteroPane.getSelectedLibraryID() - ZoteroPane.getSelectedGroup(asID) - ZoteroPane.addItemFromDocument(doc, itemType, saveSnapshot) - ZoteroPane.addItemFromURL(url, itemType) - ZoteroPane.canEdit() - Zotero.CollectionTreeView.selectLibrary(libraryID) - New Zotero.URI methods - Changed methods - Many data object methods now take a libraryID - ZoteroPane.addAttachmentFromPage(link, itemID) - Removed saveItem and saveAttachments parameters from Zotero.Translate constructor - translate() now takes a libraryID, null for local library, or false to not save items (previously on constructor) - saveAttachments is now a translate() parameter - Zotero.flattenArguments() better handles passed objects - Zotero.File.getFileHash() (not currently used)
2009-05-14 18:23:40 +00:00
this.__defineGetter__('userID', function () {
var sql = "SELECT value FROM settings WHERE "
+ "setting='account' AND key='userID'";
return Zotero.DB.valueQuery(sql);
});
this.__defineSetter__('userID', function (val) {
var sql = "REPLACE INTO settings VALUES ('account', 'userID', ?)";
Zotero.DB.query(sql, parseInt(val));
});
this.__defineGetter__('libraryID', function () {
var sql = "SELECT value FROM settings WHERE "
+ "setting='account' AND key='libraryID'";
return Zotero.DB.valueQuery(sql);
});
this.__defineSetter__('libraryID', function (val) {
var sql = "REPLACE INTO settings VALUES ('account', 'libraryID', ?)";
Zotero.DB.query(sql, parseInt(val));
});
Zotero File Storage megacommit - Group file sync via Zotero File Storage - Split file syncing into separate modules for ZFS and WebDAV - Dragging items between libraries copies child notes, snapshots/files, and links based on checkboxes for each (enabled by default) in the Zotero preferences - Sync errors now trigger an exclamation/error icon separate from the sync icon, with a popup window displaying the error and an option to report it - Various errors that could cause perpetual sync icon spinning now stop the sync properly - Zotero.Utilities.md5(str) is now md5(strOrFile, base64) - doPost(), doHead(), and retrieveSource() now takes a headers parameter instead of requestContentType - doHead() can now accept an nsIURI (with login credentials), is a background request, and isn't cached - When library access or file writing access is denied during sync, display a warning and then reset local group to server version - Perform additional steps (e.g., removing local groups) when switching sync users to prevent errors - Compare hash as well as mod time when checking for modified local files - Don't trigger notifications when removing groups from the client - Clear relation links to items in removed groups - Zotero.Item.attachmentHash property to get file MD5 - importFromFile() now takes libraryID as a third parameter - Zotero.Attachments.getNumFiles() returns the number of files in the attachment directory - Zotero.Attachments.copyAttachmentToLibrary() copies an attachment item, including files, to another library - Removed Zotero.File.getFileHash() in favor of updated Zotero.Utilities.md5() - Zotero.File.copyDirectory(dir, newDir) copies all files from dir into newDir - Preferences shuffling: OpenURL to Advanced, import/export character set options to Export, "Include URLs of paper articles in references" to Styles - Other stuff I don't remember Suffice it to say, this could use testing.
2009-09-13 07:23:29 +00:00
this.__defineGetter__('username', function () {
var sql = "SELECT value FROM settings WHERE "
+ "setting='account' AND key='username'";
return Zotero.DB.valueQuery(sql);
});
this.__defineSetter__('username', function (val) {
var sql = "REPLACE INTO settings VALUES ('account', 'username', ?)";
Zotero.DB.query(sql, val);
});
this.getActiveZoteroPane = function() {
2013-06-06 06:37:19 +00:00
return Services.wm.getMostRecentWindow("navigator:browser").ZoteroPane;
};
2.0b3 megacommit - Support for group libraries - General support for multiple libraries of different types - Streamlined sync support - Using solely libraryID and key rather than itemID, and removed all itemID-changing code - Combined two requests for increased performance and decreased server load - Added warning on user account change - Provide explicit error message on SSL failure - Removed snapshot and link toolbar buttons and changed browser context menu options and drags to create parent items + snapshots - Closes #786, Add numPages field - Fixes #1063, Duplicate item with tags broken in Sync Preview - Added better purging of deleted tags - Added local user key before first sync - Add clientDateModified to all objects for more flexibility in syncing - Added new triples-based Relation object type, currently used to store links between items copied between local and group libraries - Updated zotero.org translator for groups - Additional trigger-based consistency checks - Fixed broken URL drag in Firefox 3.5 - Disabled zeroconf menu option (no longer functional) Developer-specific changes: - Overhauled data layer - Data object constructors no longer take arguments (return to 1.0-like API) - Existing objects can be retrieved by setting id or library/key properties - id/library/key must be set for new objects before other fields - New methods: - ZoteroPane.getSelectedLibraryID() - ZoteroPane.getSelectedGroup(asID) - ZoteroPane.addItemFromDocument(doc, itemType, saveSnapshot) - ZoteroPane.addItemFromURL(url, itemType) - ZoteroPane.canEdit() - Zotero.CollectionTreeView.selectLibrary(libraryID) - New Zotero.URI methods - Changed methods - Many data object methods now take a libraryID - ZoteroPane.addAttachmentFromPage(link, itemID) - Removed saveItem and saveAttachments parameters from Zotero.Translate constructor - translate() now takes a libraryID, null for local library, or false to not save items (previously on constructor) - saveAttachments is now a translate() parameter - Zotero.flattenArguments() better handles passed objects - Zotero.File.getFileHash() (not currently used)
2009-05-14 18:23:40 +00:00
this.getLocalUserKey = function (generate) {
if (_localUserKey) {
return _localUserKey;
}
var sql = "SELECT value FROM settings WHERE "
+ "setting='account' AND key='localUserKey'";
var key = Zotero.DB.valueQuery(sql);
// Generate a local user key if we don't have a global library id
if (!key && generate) {
key = Zotero.randomString(8);
var sql = "INSERT INTO settings VALUES ('account', 'localUserKey', ?)";
Zotero.DB.query(sql, key);
}
_localUserKey = key;
return key;
};
/**
* @property {Boolean} waiting Whether Zotero is waiting for other
* main thread events to be processed
*/
this.__defineGetter__('waiting', function () _waiting);
/**
* @property {Boolean} locked Whether all Zotero panes are locked
* with an overlay
*/
this.__defineGetter__('locked', function () _locked);
this.__defineSetter__('locked', function (lock) {
var wasLocked = _locked;
_locked = lock;
if (!wasLocked && lock) {
this.unlockDeferred = Q.defer();
this.unlockPromise = this.unlockDeferred.promise;
}
else if (wasLocked && !lock) {
Zotero.debug("Running unlock callbacks");
this.unlockDeferred.resolve();
}
});
/**
* @property {Boolean} suppressUIUpdates Don't update UI on Notifier triggers
*/
this.suppressUIUpdates = false;
/**
* @property {Boolean} closing True if the application is closing.
*/
this.closing = false;
this.initializationDeferred;
this.initializationPromise;
this.unlockDeferred;
this.unlockPromise;
2007-10-23 07:11:59 +00:00
var _startupErrorHandler;
var _zoteroDirectory = false;
var _localizedStringBundle;
2.0b3 megacommit - Support for group libraries - General support for multiple libraries of different types - Streamlined sync support - Using solely libraryID and key rather than itemID, and removed all itemID-changing code - Combined two requests for increased performance and decreased server load - Added warning on user account change - Provide explicit error message on SSL failure - Removed snapshot and link toolbar buttons and changed browser context menu options and drags to create parent items + snapshots - Closes #786, Add numPages field - Fixes #1063, Duplicate item with tags broken in Sync Preview - Added better purging of deleted tags - Added local user key before first sync - Add clientDateModified to all objects for more flexibility in syncing - Added new triples-based Relation object type, currently used to store links between items copied between local and group libraries - Updated zotero.org translator for groups - Additional trigger-based consistency checks - Fixed broken URL drag in Firefox 3.5 - Disabled zeroconf menu option (no longer functional) Developer-specific changes: - Overhauled data layer - Data object constructors no longer take arguments (return to 1.0-like API) - Existing objects can be retrieved by setting id or library/key properties - id/library/key must be set for new objects before other fields - New methods: - ZoteroPane.getSelectedLibraryID() - ZoteroPane.getSelectedGroup(asID) - ZoteroPane.addItemFromDocument(doc, itemType, saveSnapshot) - ZoteroPane.addItemFromURL(url, itemType) - ZoteroPane.canEdit() - Zotero.CollectionTreeView.selectLibrary(libraryID) - New Zotero.URI methods - Changed methods - Many data object methods now take a libraryID - ZoteroPane.addAttachmentFromPage(link, itemID) - Removed saveItem and saveAttachments parameters from Zotero.Translate constructor - translate() now takes a libraryID, null for local library, or false to not save items (previously on constructor) - saveAttachments is now a translate() parameter - Zotero.flattenArguments() better handles passed objects - Zotero.File.getFileHash() (not currently used)
2009-05-14 18:23:40 +00:00
var _localUserKey;
2011-08-30 00:49:04 +00:00
var _waiting = 0;
var _locked = false;
var _shutdownListeners = [];
var _progressMeters;
var _progressPopup;
var _lastPercentage;
// whether we are waiting for another Zotero process to release its DB lock
var _waitingForDBLock = false;
/**
* Maintains nsITimers to be used when Zotero.wait() completes (to reduce performance penalty
* of initializing new objects)
*/
var _waitTimers = [];
/**
* Maintains nsITimerCallbacks to be used when Zotero.wait() completes
*/
var _waitTimerCallbacks = [];
/**
* Maintains running nsITimers in global scope, so that they don't disappear randomly
*/
var _runningTimers = [];
// Errors that were in the console at startup
var _startupErrors = [];
// Number of errors to maintain in the recent errors buffer
const ERROR_BUFFER_SIZE = 25;
// A rolling buffer of the last ERROR_BUFFER_SIZE errors
var _recentErrors = [];
/**
* Initialize the extension
*
* @return {Boolean|Promise:Boolean}
*/
function init() {
2007-10-23 07:11:59 +00:00
if (this.initialized || this.skipLoading) {
return false;
}
this.initializationDeferred = Q.defer();
this.initializationPromise = this.initializationDeferred.promise;
this.locked = true;
// Load in the preferences branch for the extension
Zotero.Prefs.init();
Zotero.Debug.init();
2007-10-23 07:11:59 +00:00
2013-06-06 06:37:19 +00:00
this.mainThread = Services.tm.mainThread;
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
.getService(Components.interfaces.nsIXULAppInfo);
this.platformVersion = appInfo.platformVersion;
this.platformMajorVersion = parseInt(appInfo.platformVersion.match(/^[0-9]+/)[0]);
this.isFx = true;
2013-06-06 06:37:19 +00:00
this.isStandalone = Services.appinfo.ID == ZOTERO_CONFIG['GUID'];
return Q.fcall(function () {
if(Zotero.isStandalone) {
return Services.appinfo.version;
} else {
var deferred = Q.defer();
Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAddonByID(
ZOTERO_CONFIG.GUID,
function (addon) {
deferred.resolve(addon.version);
}
);
return deferred.promise;
}
})
.then(function (version) {
Zotero.version = version;
// OS platform
var win = Components.classes["@mozilla.org/appshell/appShellService;1"]
.getService(Components.interfaces.nsIAppShellService)
.hiddenDOMWindow;
this.platform = win.navigator.platform;
this.isMac = (this.platform.substr(0, 3) == "Mac");
this.isWin = (this.platform.substr(0, 3) == "Win");
this.isLinux = (this.platform.substr(0, 5) == "Linux");
this.oscpu = win.navigator.oscpu;
// Browser
Zotero.browser = "g";
// Locale
var uaPrefs = Services.prefs.getBranch("general.useragent.");
try {
this.locale = uaPrefs.getComplexValue("locale", Components.interfaces.nsIPrefLocalizedString);
} catch (e) {}
if(this.locale) {
this.locale = this.locale.toString();
} else {
this.locale = uaPrefs.getCharPref("locale");
}
if (this.locale.length == 2) {
this.locale = this.locale + '-' + this.locale.toUpperCase();
}
// Load in the localization stringbundle for use by getString(name)
var appLocale = Services.locale.getApplicationLocale();
_localizedStringBundle = Services.strings.createBundle(
"chrome://zotero/locale/zotero.properties", appLocale);
// Also load the brand as appName
var brandBundle = Services.strings.createBundle(
"chrome://branding/locale/brand.properties", appLocale);
this.appName = brandBundle.GetStringFromName("brandShortName");
// Set the locale direction to Zotero.dir
// DEBUG: is there a better way to get the entity from JS?
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance();
xmlhttp.open('GET', 'chrome://global/locale/global.dtd', false);
xmlhttp.overrideMimeType('text/plain');
xmlhttp.send(null);
var matches = xmlhttp.responseText.match(/(ltr|rtl)/);
if (matches && matches[0] == 'rtl') {
Zotero.dir = 'rtl';
}
else {
Zotero.dir = 'ltr';
}
// Make sure that Zotero Standalone is not running as root
if(Zotero.isStandalone && !Zotero.isWin) _checkRoot();
try {
var dataDir = Zotero.getZoteroDirectory();
}
catch (e) {
// Zotero dir not found
if (e.name == 'NS_ERROR_FILE_NOT_FOUND') {
Zotero.startupError = Zotero.getString('dataDir.notFound');
_startupErrorHandler = function() {
var win = Services.wm.getMostRecentWindow('navigator:browser');
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_OK)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING)
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING);
var index = ps.confirmEx(win,
Zotero.getString('general.error'),
Zotero.startupError + '\n\n' +
Zotero.getString('dataDir.previousDir') + ' '
+ Zotero.Prefs.get('lastDataDir'),
buttonFlags, null,
Zotero.getString('dataDir.useProfileDir', Zotero.appName),
Zotero.getString('general.locate'),
null, {});
// Revert to profile directory
if (index == 1) {
Zotero.chooseZoteroDirectory(false, true);
}
// Locate data directory
else if (index == 2) {
Zotero.chooseZoteroDirectory();
}
}
return;
} else if(e.name == "ZOTERO_DIR_MAY_EXIST") {
var app = Zotero.isStandalone ? Zotero.getString('app.standalone') : Zotero.getString('app.firefox');
var altApp = !Zotero.isStandalone ? Zotero.getString('app.standalone') : Zotero.getString('app.firefox');
var message = Zotero.getString("dataDir.standaloneMigration.description", [app, altApp]);
if(e.multipleProfiles) {
message += "\n\n"+Zotero.getString("dataDir.standaloneMigration.multipleProfiles", [app, altApp]);
}
2007-10-23 07:11:59 +00:00
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_YES)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_NO)
2007-10-23 07:11:59 +00:00
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING);
var index = ps.confirmEx(null, Zotero.getString("dataDir.standaloneMigration.title"), message,
buttonFlags, null, null,
Zotero.getString('dataDir.standaloneMigration.selectCustom'),
2007-10-23 07:11:59 +00:00
null, {});
// Migrate data directory
if (index == 0) {
// copy prefs
var prefsFile = e.profile.clone();
prefsFile.append("prefs.js");
if(prefsFile.exists()) {
// build sandbox
var sandbox = new Components.utils.Sandbox("http://www.example.com/");
Components.utils.evalInSandbox(
"var prefs = {};"+
"function user_pref(key, val) {"+
"prefs[key] = val;"+
"}"
, sandbox);
// remove comments
var prefsJs = Zotero.File.getContents(prefsFile);
prefsJs = prefsJs.replace(/^#[^\r\n]*$/mg, "");
// evaluate
Components.utils.evalInSandbox(prefsJs, sandbox);
var prefs = sandbox.prefs;
for(var key in prefs) {
if(key.substr(0, ZOTERO_CONFIG.PREF_BRANCH.length) === ZOTERO_CONFIG.PREF_BRANCH
&& key !== "extensions.zotero.firstRun2") {
Zotero.Prefs.set(key.substr(ZOTERO_CONFIG.PREF_BRANCH.length), prefs[key]);
}
}
}
// also set data dir if no custom data dir is now defined
if(!Zotero.Prefs.get("useDataDir")) {
var dir = e.dir.QueryInterface(Components.interfaces.nsILocalFile);
Zotero.Prefs.set('dataDir', dir.persistentDescriptor);
Zotero.Prefs.set('lastDataDir', dir.path);
Zotero.Prefs.set('useDataDir', true);
}
}
// Create new data directory
else if (index == 1) {
Zotero.File.createDirectoryIfMissing(e.curDir);
}
// Locate new data directory
else if (index == 2) {
Zotero.chooseZoteroDirectory(true);
}
}
// DEBUG: handle more startup errors
else {
throw (e);
return false;
}
2007-10-23 07:11:59 +00:00
}
// Register shutdown handler to call Zotero.shutdown()
var _shutdownObserver = {observe:function() { Zotero.shutdown().done() }};
Services.obs.addObserver(_shutdownObserver, "quit-application", false);
try {
Zotero.IPC.init();
}
catch (e) {
if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED') {
var msg = Zotero.localeJoin([
Zotero.getString('startupError.databaseCannotBeOpened'),
Zotero.getString('startupError.checkPermissions')
]);
Zotero.startupError = msg;
2013-08-12 07:10:09 +00:00
Zotero.debug(e, 1);
Components.utils.reportError(e);
return false;
}
2007-10-23 07:11:59 +00:00
throw (e);
}
// Get startup errors
try {
var messages = {};
Services.console.getMessageArray(messages, {});
_startupErrors = [msg for each(msg in messages.value) if(_shouldKeepError(msg))];
} catch(e) {
Zotero.logError(e);
2012-02-14 20:20:19 +00:00
}
// Register error observer
Services.console.registerListener(ConsoleListener);
// Add shutdown listener to remove quit-application observer and console listener
this.addShutdownListener(function() {
Services.obs.removeObserver(_shutdownObserver, "quit-application", false);
Services.console.unregisterListener(ConsoleListener);
});
// Load additional info for connector or not
if(Zotero.isConnector) {
Zotero.debug("Loading in connector mode");
Zotero.Connector_Types.init();
if(!Zotero.isFirstLoadThisSession) {
// We want to get a checkInitComplete message before initializing if we switched to
// connector mode because Standalone was launched
Zotero.IPC.broadcast("checkInitComplete");
} else {
Zotero.initComplete();
}
2011-09-20 21:59:28 +00:00
} else {
Zotero.debug("Loading in full mode");
return Q.fcall(_initFull)
.then(function (success) {
if(!success) return false;
if(Zotero.isStandalone) Zotero.Standalone.init();
Zotero.initComplete();
});
}
return true;
}.bind(this));
2011-09-20 21:59:28 +00:00
}
/**
* Triggers events when initialization finishes
*/
this.initComplete = function() {
if(Zotero.initialized) return;
Zotero.debug("Running initialization callbacks");
this.initialized = true;
this.initializationDeferred.resolve();
2011-09-20 21:59:28 +00:00
if(Zotero.isConnector) {
Zotero.Repo.init();
}
if(!Zotero.isFirstLoadThisSession) {
// trigger zotero-reloaded event
Zotero.debug('Triggering "zotero-reloaded" event');
Services.obs.notifyObservers(Zotero, "zotero-reloaded", null);
}
Zotero.debug('Triggering "zotero-loaded" event');
Services.obs.notifyObservers(Zotero, "zotero-loaded", null);
}
/**
* Initialization function to be called only if Zotero is in full mode
*
* @return {Promise:Boolean}
*/
function _initFull() {
Zotero.VersionHeader.init();
// Check for DB restore
var dataDir = Zotero.getZoteroDirectory();
var restoreFile = dataDir.clone();
restoreFile.append('restore-from-server');
if (restoreFile.exists()) {
try {
// TODO: better error handling
// TODO: prompt for location
// TODO: Back up database
restoreFile.remove(false);
var dbfile = Zotero.getZoteroDatabase();
dbfile.remove(false);
// Recreate database with no quick start guide
Zotero.Schema.skipDefaultData = true;
Zotero.Schema.updateSchema();
Zotero.restoreFromServer = true;
}
catch (e) {
// Restore from backup?
alert(e);
}
}
2007-10-23 07:11:59 +00:00
if(!_initDB()) return false;
Zotero.HTTP.triggerProxyAuth();
2007-10-23 07:11:59 +00:00
// Add notifier queue callbacks to the DB layer
Zotero.DB.addCallback('begin', Zotero.Notifier.begin);
Zotero.DB.addCallback('commit', Zotero.Notifier.commit);
2007-10-23 07:11:59 +00:00
Zotero.DB.addCallback('rollback', Zotero.Notifier.reset);
return Q.fcall(function () {
// Require >=2.1b3 database to ensure proper locking
if (!Zotero.isStandalone) {
return;
}
return Zotero.Schema.getDBVersion('system')
.then(function (dbSystemVersion) {
if (dbSystemVersion > 0 && dbSystemVersion < 31) {
var dir = Zotero.getProfileDirectory();
dir.append('zotero');
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.createInstance(Components.interfaces.nsIPromptService);
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING)
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING)
+ ps.BUTTON_POS_2_DEFAULT;
var index = ps.confirmEx(
null,
Zotero.getString('dataDir.incompatibleDbVersion.title'),
Zotero.getString('dataDir.incompatibleDbVersion.text'),
buttonFlags,
Zotero.getString('general.useDefault'),
Zotero.getString('dataDir.standaloneMigration.selectCustom'),
Zotero.getString('general.quit'),
null,
{}
);
var quit = false;
// Default location
if (index == 0) {
Zotero.File.createDirectoryIfMissing(dir);
Zotero.Prefs.set("useDataDir", false)
Services.startup.quit(
Components.interfaces.nsIAppStartup.eAttemptQuit
| Components.interfaces.nsIAppStartup.eRestart
);
}
// Select new data directory
else if (index == 1) {
var dir = Zotero.chooseZoteroDirectory(true);
if (!dir) {
quit = true;
}
}
else {
quit = true;
}
if (quit) {
Services.startup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
}
throw true;
}
});
})
.then(function () {
return Zotero.Schema.updateSchema()
.then(function (updated) {
Zotero.locked = false;
// Initialize various services
Zotero.Integration.init();
if(Zotero.Prefs.get("httpServer.enabled")) {
Zotero.Server.init();
}
2013-08-12 07:10:22 +00:00
Zotero.Fulltext.init();
Zotero.Notifier.registerObserver(Zotero.Tags, 'setting');
Zotero.Sync.init();
Zotero.Sync.Runner.init();
Zotero.MIMETypeHandler.init();
Zotero.Proxies.init();
// Initialize keyboard shortcuts
Zotero.Keys.init();
// Initialize Locate Manager
Zotero.LocateManager.init();
Zotero.Items.startEmptyTrashTimer();
})
.catch(function (e) {
Zotero.debug(e, 1);
Components.utils.reportError(e); // DEBUG: doesn't always work
Zotero.startupError = Zotero.getString('startupError.databaseUpgradeError') + "\n\n" + e;
throw true;
});
})
.then(function () {
return true;
})
.catch(function (e) {
Zotero.skipLoading = true;
return false;
});
}
/**
* Initializes the DB connection
*/
function _initDB(haveReleasedLock) {
try {
// Test read access
Zotero.DB.test();
var dbfile = Zotero.getZoteroDatabase();
// Test write access on Zotero data directory
if (!dbfile.parent.isWritable()) {
var msg = 'Cannot write to ' + dbfile.parent.path + '/';
}
// Test write access on Zotero database
else if (!dbfile.isWritable()) {
var msg = 'Cannot write to ' + dbfile.path;
}
else {
var msg = false;
}
if (msg) {
var e = {
name: 'NS_ERROR_FILE_ACCESS_DENIED',
message: msg,
2013-08-12 07:10:09 +00:00
toString: function () this.message
};
throw (e);
}
}
catch (e) {
if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED') {
var msg = Zotero.localeJoin([
Zotero.getString('startupError.databaseCannotBeOpened'),
Zotero.getString('startupError.checkPermissions')
]);
Zotero.startupError = msg;
} else if(e.name == "NS_ERROR_STORAGE_BUSY" || e.result == 2153971713) {
if(Zotero.isStandalone) {
// Standalone should force Fx to release lock
if(!haveReleasedLock && Zotero.IPC.broadcast("releaseLock")) {
_waitingForDBLock = true;
var timeout = Date.now() + 5000; // 5 second timeout
while(_waitingForDBLock && !Zotero.closing && Date.now() < timeout) {
// AMO Reviewer: This is used by Zotero Standalone, not Zotero for Firefox.
Zotero.mainThread.processNextEvent(true);
}
2011-07-20 03:12:31 +00:00
if(Zotero.closing) return false;
// Run a second init with haveReleasedLock = true, so that
// if we still can't acquire a DB lock, we will give up
return _initDB(true);
}
} else {
// Fx should start as connector if Standalone is running
var haveStandalone = Zotero.IPC.broadcast("test");
if(haveStandalone) {
throw "ZOTERO_SHOULD_START_AS_CONNECTOR";
}
}
var msg = Zotero.localeJoin([
Zotero.getString('startupError.databaseInUse'),
Zotero.getString(Zotero.isStandalone ? 'startupError.closeFirefox' : 'startupError.closeStandalone')
]);
Zotero.startupError = msg;
}
2013-08-12 07:10:09 +00:00
Zotero.debug(e.toString(), 1);
Components.utils.reportError(e); // DEBUG: doesn't always work
Zotero.skipLoading = true;
return false;
}
2007-10-23 07:11:59 +00:00
return true;
}
/**
* Called when the DB has been released by another Zotero process to perform necessary
* initialization steps
*/
this.onDBLockReleased = function() {
if(Zotero.isConnector) {
// if DB lock is released, switch out of connector mode
switchConnectorMode(false);
} else if(_waitingForDBLock) {
// if waiting for DB lock and we get it, continue init
_waitingForDBLock = false;
}
}
2007-10-23 07:11:59 +00:00
/*
* Check if a DB transaction is open and, if so, disable Zotero
*/
function stateCheck() {
if(!Zotero.isConnector && Zotero.DB.transactionInProgress()) {
Zotero.logError("State check failed due to transaction in progress");
2007-10-23 07:11:59 +00:00
this.initialized = false;
this.skipLoading = true;
return false;
}
return true;
}
this.shutdown = function() {
2008-11-14 13:43:01 +00:00
Zotero.debug("Shutting down Zotero");
try {
// set closing to true
Zotero.closing = true;
// run shutdown listener
for each(var listener in _shutdownListeners) {
try {
listener();
2011-08-24 06:29:16 +00:00
} catch(e) {
Zotero.logError(e);
}
}
// remove temp directory
Zotero.removeTempDirectory();
if(Zotero.initialized && Zotero.DB) {
Zotero.debug("Closing database");
// run GC to finalize open statements
// TODO remove this and finalize statements created with
// Zotero.DBConnection.getStatement() explicitly
Components.utils.forceGC();
// unlock DB
return Zotero.DB.closeDatabase().then(function() {
// broadcast that DB lock has been released
Zotero.IPC.broadcast("lockReleased");
});
}
return Q();
} catch(e) {
Zotero.debug(e);
return Q.reject(e);
}
}
function getProfileDirectory(){
2013-06-06 06:37:19 +00:00
return Services.dirsvc.get("ProfD", Components.interfaces.nsIFile);
}
function getDefaultProfile(prefDir) {
// find profiles.ini file
var profilesIni = prefDir.clone();
profilesIni.append("profiles.ini");
if(!profilesIni.exists()) return false;
var iniContents = Zotero.File.getContents(profilesIni);
// cheap and dirty ini parser
var curSection = null;
var defaultSection = null;
var nSections = 0;
for each(var line in iniContents.split(/(?:\r?\n|\r)/)) {
let tline = line.trim();
if(tline[0] == "[" && tline[tline.length-1] == "]") {
curSection = {};
if(tline != "[General]") nSections++;
} else if(curSection && tline != "") {
let equalsIndex = tline.indexOf("=");
let key = tline.substr(0, equalsIndex);
let val = tline.substr(equalsIndex+1);
curSection[key] = val;
if(key == "Default" && val == "1") {
defaultSection = curSection;
}
}
}
if(!defaultSection && curSection) defaultSection = curSection;
// parse out ini to reveal profile
if(!defaultSection || !defaultSection.Path) return false;
2011-08-24 06:07:48 +00:00
if(defaultSection.IsRelative === "1") {
var defaultProfile = prefDir.clone().QueryInterface(Components.interfaces.nsILocalFile);
2011-08-24 01:15:21 +00:00
try {
for each(var dir in defaultSection.Path.split("/")) defaultProfile.append(dir);
} catch(e) {
Zotero.logError("Could not find profile at "+defaultSection.Path);
throw e;
}
} else {
var defaultProfile = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
defaultProfile.initWithPath(defaultSection.Path);
}
if(!defaultProfile.exists()) return false;
return [defaultProfile, nSections > 1];
}
function getZoteroDirectory(){
2007-10-23 07:11:59 +00:00
if (_zoteroDirectory != false) {
// Return a clone of the file pointer so that callers can modify it
return _zoteroDirectory.clone();
}
2007-10-23 07:11:59 +00:00
if (Zotero.Prefs.get('useDataDir')) {
var file = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
try {
file.persistentDescriptor = Zotero.Prefs.get('dataDir');
}
catch (e) {
Zotero.debug("Persistent descriptor in extensions.zotero.dataDir did not resolve", 1);
e = { name: "NS_ERROR_FILE_NOT_FOUND" };
throw (e);
}
2007-10-23 07:11:59 +00:00
if (!file.exists()) {
var e = { name: "NS_ERROR_FILE_NOT_FOUND" };
throw (e);
}
}
else {
var file = Zotero.getProfileDirectory();
file.append('zotero');
// if standalone and no directory yet, check Firefox directory
// or if in Firefox and no directory yet, check standalone Zotero directory
if(!file.exists()) {
2013-06-06 06:37:19 +00:00
var prefDir = Services.dirsvc
.get("DefProfRt", Components.interfaces.nsILocalFile).parent.parent;
2010-09-20 02:50:24 +00:00
if(Zotero.isStandalone) {
if(Zotero.isWin) {
2010-09-25 16:05:15 +00:00
prefDir = prefDir.parent;
prefDir.append("Mozilla");
prefDir.append("Firefox");
2010-09-20 02:50:24 +00:00
} else if(Zotero.isMac) {
prefDir.append("Firefox");
2010-09-20 02:50:24 +00:00
} else {
prefDir.append(".mozilla");
prefDir.append("firefox");
2010-09-20 02:50:24 +00:00
}
} else {
if(Zotero.isWin) {
prefDir = prefDir.parent;
prefDir.append("Zotero");
2010-09-25 16:04:18 +00:00
prefDir.append("Zotero");
2010-09-20 02:50:24 +00:00
} else if(Zotero.isMac) {
prefDir.append("Zotero");
2010-09-20 02:50:24 +00:00
} else {
prefDir.append(".zotero");
2010-09-25 16:05:15 +00:00
prefDir.append("zotero");
2010-09-20 02:50:24 +00:00
}
}
Zotero.debug("Looking for existing profile in "+prefDir.path);
// get default profile
var defProfile;
try {
defProfile = getDefaultProfile(prefDir);
} catch(e) {
Zotero.debug("An error occurred locating the Firefox profile; not "+
"attempting to migrate from Zotero for Firefox");
Zotero.logError(e);
}
if(defProfile) {
// get Zotero directory
var zoteroDir = defProfile[0].clone();
zoteroDir.append("zotero");
if(zoteroDir.exists()) {
// if Zotero directory exists in default profile for alternative app, ask
// whether to use
var e = { name:"ZOTERO_DIR_MAY_EXIST", curDir:file, profile:defProfile[0], dir:zoteroDir, multipleProfiles:defProfile[1] };
throw (e);
}
}
}
Zotero.File.createDirectoryIfMissing(file);
}
2007-10-23 07:11:59 +00:00
Zotero.debug("Using data directory " + file.path);
_zoteroDirectory = file;
return file.clone();
}
function getStorageDirectory(){
var file = Zotero.getZoteroDirectory();
file.append('storage');
Zotero.File.createDirectoryIfMissing(file);
return file;
}
2007-10-23 07:11:59 +00:00
function getZoteroDatabase(name, ext){
name = name ? name + '.sqlite' : 'zotero.sqlite';
ext = ext ? '.' + ext : '';
var file = Zotero.getZoteroDirectory();
2007-10-23 07:11:59 +00:00
file.append(name + ext);
return file;
}
/**
* @return {nsIFile}
*/
2008-11-14 13:43:01 +00:00
this.getTempDirectory = function () {
var tmp = this.getZoteroDirectory();
tmp.append('tmp');
Zotero.File.createDirectoryIfMissing(tmp);
return tmp;
}
2008-11-14 13:43:01 +00:00
this.removeTempDirectory = function () {
var tmp = this.getZoteroDirectory();
tmp.append('tmp');
if (tmp.exists()) {
try {
tmp.remove(true);
}
catch (e) {}
}
}
this.getStylesDirectory = function () {
var dir = this.getZoteroDirectory();
dir.append('styles');
Zotero.File.createDirectoryIfMissing(dir);
return dir;
}
this.getTranslatorsDirectory = function () {
var dir = this.getZoteroDirectory();
dir.append('translators');
Zotero.File.createDirectoryIfMissing(dir);
return dir;
}
function chooseZoteroDirectory(forceQuitNow, useProfileDir, moreInfoCallback) {
2013-06-06 06:37:19 +00:00
var win = Services.wm.getMostRecentWindow('navigator:browser');
var ps = Services.prompt;
2007-10-23 07:11:59 +00:00
if (useProfileDir) {
Zotero.Prefs.set('useDataDir', false);
}
2007-10-23 07:11:59 +00:00
else {
var nsIFilePicker = Components.interfaces.nsIFilePicker;
while (true) {
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
fp.init(win, Zotero.getString('dataDir.selectDir'), nsIFilePicker.modeGetFolder);
fp.appendFilters(nsIFilePicker.filterAll);
if (fp.show() == nsIFilePicker.returnOK) {
var file = fp.file;
if (file.directoryEntries.hasMoreElements()) {
var dbfile = file.clone();
dbfile.append('zotero.sqlite');
2007-10-23 07:11:59 +00:00
// Warn if non-empty and no zotero.sqlite
if (!dbfile.exists()) {
var buttonFlags = ps.STD_YES_NO_BUTTONS;
if (moreInfoCallback) {
buttonFlags += ps.BUTTON_POS_2 * ps.BUTTON_TITLE_IS_STRING;
}
var index = ps.confirmEx(null,
2007-10-23 07:11:59 +00:00
Zotero.getString('dataDir.selectedDirNonEmpty.title'),
Zotero.getString('dataDir.selectedDirNonEmpty.text'),
buttonFlags,
null,
null,
moreInfoCallback ? Zotero.getString('general.help') : null,
null, {});
2007-10-23 07:11:59 +00:00
// Not OK -- return to file picker
if (index == 1) {
continue;
}
else if (index == 2) {
setTimeout(function () {
moreInfoCallback();
}, 1);
return false;
}
2007-10-23 07:11:59 +00:00
}
}
else {
var buttonFlags = ps.STD_YES_NO_BUTTONS;
if (moreInfoCallback) {
buttonFlags += ps.BUTTON_POS_2 * ps.BUTTON_TITLE_IS_STRING;
}
var index = ps.confirmEx(null,
2013-02-28 22:31:07 +00:00
Zotero.getString('dataDir.selectedDirEmpty.title'),
Zotero.getString('dataDir.selectedDirEmpty.text', Zotero.appName) + '\n\n'
+ Zotero.getString('dataDir.selectedDirEmpty.useNewDir'),
buttonFlags,
null,
null,
moreInfoCallback ? Zotero.getString('general.moreInformation') : null,
null, {});
// Not OK -- return to file picker
if (index == 1) {
continue;
}
else if (index == 2) {
setTimeout(function () {
moreInfoCallback();
}, 1);
return false;
}
}
2007-10-23 07:11:59 +00:00
// Set new data directory
Zotero.Prefs.set('dataDir', file.persistentDescriptor);
Zotero.Prefs.set('lastDataDir', file.path);
Zotero.Prefs.set('useDataDir', true);
2007-10-23 07:11:59 +00:00
break;
}
else {
return false;
}
}
}
2007-10-23 07:11:59 +00:00
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING);
if (!forceQuitNow) {
2007-10-23 07:11:59 +00:00
buttonFlags += (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
}
var app = Zotero.appName;
var index = ps.confirmEx(null,
2007-10-23 07:11:59 +00:00
Zotero.getString('general.restartRequired'),
Zotero.getString('general.restartRequiredForChange', app)
+ "\n\n" + Zotero.getString('dataDir.moveFilesToNewLocation', app),
2007-10-23 07:11:59 +00:00
buttonFlags,
Zotero.getString('general.quitApp', app),
forceQuitNow ? null : Zotero.getString('general.restartLater'),
2007-10-23 07:11:59 +00:00
null, null, {});
2007-10-23 07:11:59 +00:00
if (index == 0) {
2013-06-06 06:37:19 +00:00
Services.startup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
2007-10-23 07:11:59 +00:00
}
2007-10-23 07:11:59 +00:00
return useProfileDir ? true : file;
}
/**
* Launch a file, the best way we can
*/
this.launchFile = function (file) {
try {
file.launch();
}
catch (e) {
Zotero.debug("launch() not supported -- trying fallback executable");
try {
if (Zotero.isWin) {
var pref = "fallbackLauncher.windows";
}
else {
var pref = "fallbackLauncher.unix";
}
var path = Zotero.Prefs.get(pref);
var exec = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
exec.initWithPath(path);
if (!exec.exists()) {
throw (path + " does not exist");
}
var proc = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
proc.init(exec);
var args = [file.path];
proc.runw(true, args, args.length);
}
catch (e) {
Zotero.debug(e);
Zotero.debug("Launching via executable failed -- passing to loadUrl()");
// If nsILocalFile.launch() isn't available and the fallback
// executable doesn't exist, we just let the Firefox external
// helper app window handle it
var nsIFPH = Components.classes["@mozilla.org/network/protocol;1?name=file"]
.getService(Components.interfaces.nsIFileProtocolHandler);
var uri = nsIFPH.newFileURI(file);
var nsIEPS = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"].
getService(Components.interfaces.nsIExternalProtocolService);
nsIEPS.loadUrl(uri);
}
}
}
/*
* Debug logging function
*
2007-10-23 07:11:59 +00:00
* Uses prefs e.z.debug.log and e.z.debug.level (restart required)
*
* Defaults to log level 3 if level not provided
*/
function debug(message, level) {
Zotero.Debug.log(message, level);
2007-10-23 07:11:59 +00:00
}
/*
* Log a message to the Mozilla JS error console
*
* |type| is a string with one of the flag types in nsIScriptError:
* 'error', 'warning', 'exception', 'strict'
*/
Initial Zotero 1.5 Megacommit Apologies for the massive (and, due to data_access.js splitting, difficult-to-follow) commit. Please note that external code that accesses the data layer may need to be tweaked for compatibility. Here's a comprehensive-as-possible changelog: - Added server sync functionality (incomplete) - Overhaul of data layer - Split data_access.js into separate files (item.js, items.js, creator.js, etc.) - Made creators and collections first-class objects, similar to items - Constructors now take id as first parameter, e.g. new Zotero.Item(1234, 'book'), to allow explicit id setting and id changing - Made various data layer operations (including attachment fields) require a save() rather than making direct DB changes - Better handling of unsaved objects - Item.setCreator() now takes creator objects instead of creator ids, and Item.save() will auto-save unsaved creators - clone() now works on unsaved objects - Newly created object instances are now disabled after save() to force refetch of globally accessible instance using Zotero.(Items|Creators|etc.).get() - Added secondary lookup key to data objects - Deprecated getID() and getItemType() methods in favor of .id and .itemTypeID properties - toArray() deprecated in favor of serialize(), which has a somewhat modified format - Added support for multiple creators with identical data -- currently unimplemented in interface and most of data layer - Added Item.diff() for comparing item metadata - Database changes - Added SQLite triggers to enforce foreign key constraints - Added Zotero.DB.transactionVacuum flag to run a VACUUM after a transaction - Added Zotero.DB.transactionDate, .transactionDateTime, and transactionTimestamp to retrieve consistent timestamps for entire transaction - Properly store 64-bit integers - Set PRAGMA locking_mode=EXCLUSIVE on database - Set SQLite page size to 4096 on new databases - Set SQLite page cache to 8MB - Do some database cleanup and integrity checking on migration from 1.0 branch - Removed IF NOT EXISTS from userdata.sql CREATE statements -- userdata.sql is now processed only on DB initialization - Removed itemNoteTitles table and moved titles into itemNotes - Abstracted metadata edit box and note box into flexible XBL bindings with various modes, including read-only states - Massive speed-up of item tree view - Several fixes from 1.0 branch for Fx3 compatibility - Added Notifier observer to log delete events for syncing - Zotero.Utilities changes - New methods getSQLDataType() and md5() - Removed onError from Zotero.Utilities.HTTP.doGet() - Don't display more than 1024 characters in doPost() debug output - Don't display passwords in doPost() debug output - Added Zotero.Notifier.untrigger() -- currently unused - Added Zotero.reloadDataObjects() to reset all in-memory objects - Added |chars| parameter to Zotero.randomString(len, chars) - Added Zotero.Date.getUnixTimestamp() and Date.toUnixTimestamp(JSDate) - Adjusted zotero-service.js to simplify file inclusion Various things (such as tags) are temporarily broken.
2008-05-04 08:32:48 +00:00
function log(message, type, sourceName, sourceLine, lineNumber, columnNumber) {
2007-10-23 07:11:59 +00:00
var scriptError = Components.classes["@mozilla.org/scripterror;1"]
.createInstance(Components.interfaces.nsIScriptError);
if (!type) {
type = 'warning';
}
2007-10-23 07:11:59 +00:00
var flags = scriptError[type + 'Flag'];
scriptError.init(
message,
sourceName ? sourceName : null,
sourceLine != undefined ? sourceLine : null,
lineNumber != undefined ? lineNumber : null,
columnNumber != undefined ? columnNumber : null,
flags,
'component javascript'
2007-10-23 07:11:59 +00:00
);
2013-06-06 06:37:19 +00:00
Services.console.logMessage(scriptError);
2007-10-23 07:11:59 +00:00
}
/**
* Log a JS error to the Mozilla JS error console.
* @param {Exception} err
*/
function logError(err) {
log(err.message ? err.message : err.toString(), "error",
err.fileName ? err.fileName : (err.filename ? err.filename : null), null,
err.lineNumber ? err.lineNumber : null, null);
}
2007-10-23 07:11:59 +00:00
function getErrors(asStrings) {
var errors = [];
for each(var msg in _startupErrors.concat(_recentErrors)) {
// Remove password in malformed XML messages
if (msg.category == 'malformed-xml') {
try {
// msg.message is read-only, so store separately
var altMessage = msg.message.replace(/(file: "https?:\/\/[^:]+:)([^@]+)(@[^"]+")/, "$1********$3");
}
catch (e) {}
}
2007-10-23 07:11:59 +00:00
if (asStrings) {
errors.push(altMessage ? altMessage : msg.message)
2007-10-23 07:11:59 +00:00
}
else {
errors.push(msg);
}
}
2007-10-23 07:11:59 +00:00
return errors;
}
/**
* Get versions, platform, etc.
*
* Can be used synchronously or asynchronously; info on other add-ons
* is available only in async mode
*/
function getSystemInfo(callback) {
var info = {
version: Zotero.version,
platform: Zotero.platform,
oscpu: Zotero.oscpu,
locale: Zotero.locale,
2013-06-06 06:37:19 +00:00
appName: Services.appinfo.name,
appVersion: Services.appinfo.version
};
if (callback) {
Zotero.getInstalledExtensions(function(extensions) {
info.extensions = extensions.join(', ');
var str = '';
for (var key in info) {
str += key + ' => ' + info[key] + ', ';
}
str = str.substr(0, str.length - 2);
callback(str);
});
}
var str = '';
for (var key in info) {
str += key + ' => ' + info[key] + ', ';
}
str = str.substr(0, str.length - 2);
return str;
}
/**
* @return {String[]} Array of extension names and versions
*/
this.getInstalledExtensions = function(callback) {
function onHaveInstalledAddons(installed) {
installed.sort(function(a, b) {
return ((a.appDisabled || a.userDisabled) ? 1 : 0) -
((b.appDisabled || b.userDisabled) ? 1 : 0);
});
var addons = [];
for each(var addon in installed) {
switch (addon.id) {
case "zotero@chnm.gmu.edu":
case "{972ce4c6-7e08-4474-a285-3208198ce6fd}": // Default theme
continue;
}
addons.push(addon.name + " (" + addon.version
+ (addon.type != 2 ? ", " + addon.type : "")
+ ((addon.appDisabled || addon.userDisabled) ? ", disabled" : "")
+ ")");
2010-11-05 05:05:36 +00:00
}
callback(addons);
}
2012-02-14 20:20:19 +00:00
Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAllAddons(onHaveInstalledAddons);
}
2007-10-23 07:11:59 +00:00
function safeDebug(obj){
for (var i in obj){
try {
Zotero.debug(i + ': ' + obj[i]);
}
catch (e){
try {
Zotero.debug(i + ': ERROR');
}
catch (e){}
}
}
}
function getString(name, params){
try {
2007-10-23 07:11:59 +00:00
if (params != undefined){
if (typeof params != 'object'){
params = [params];
}
var l10n = _localizedStringBundle.formatStringFromName(name, params, params.length);
}
else {
var l10n = _localizedStringBundle.GetStringFromName(name);
}
}
catch (e){
throw ('Localized string not available for ' + name);
}
return l10n;
}
2007-10-23 07:11:59 +00:00
/*
2011-04-16 09:22:43 +00:00
* This function should be removed
2007-10-23 07:11:59 +00:00
*
* |separator| defaults to a space (not a comma like Array.join()) if
* not specified
*
* TODO: Substitute localized characters (e.g. Arabic comma and semicolon)
*/
function localeJoin(arr, separator) {
if (typeof separator == 'undefined') {
separator = ' ';
}
return arr.join(separator);
}
function getLocaleCollation() {
var collationFactory = Components.classes["@mozilla.org/intl/collation-factory;1"]
.getService(Components.interfaces.nsICollationFactory);
2013-06-06 06:37:19 +00:00
return collationFactory.CreateCollation(Services.locale.getApplicationLocale());
2007-10-23 07:11:59 +00:00
}
/*
* Sets font size based on prefs -- intended for use on root element
* (zotero-pane, note window, etc.)
*/
function setFontSize(rootElement) {
var size = Zotero.Prefs.get('fontSize');
rootElement.style.fontSize = size + 'em';
if (size <= 1) {
size = 'small';
}
else if (size <= 1.25) {
size = 'medium';
}
else {
size = 'large';
}
// Custom attribute -- allows for additional customizations in zotero.css
rootElement.setAttribute('zoteroFontSize', size);
}
/*
* Flattens mixed arrays/values in a passed _arguments_ object and returns
* an array of values -- allows for functions to accept both arrays of
* values and/or an arbitrary number of individual values
*/
function flattenArguments(args){
// Put passed scalar values into an array
2013-05-01 22:23:09 +00:00
if (args === null || typeof args == 'string' || typeof args.length == 'undefined') {
args = [args];
}
2.0b3 megacommit - Support for group libraries - General support for multiple libraries of different types - Streamlined sync support - Using solely libraryID and key rather than itemID, and removed all itemID-changing code - Combined two requests for increased performance and decreased server load - Added warning on user account change - Provide explicit error message on SSL failure - Removed snapshot and link toolbar buttons and changed browser context menu options and drags to create parent items + snapshots - Closes #786, Add numPages field - Fixes #1063, Duplicate item with tags broken in Sync Preview - Added better purging of deleted tags - Added local user key before first sync - Add clientDateModified to all objects for more flexibility in syncing - Added new triples-based Relation object type, currently used to store links between items copied between local and group libraries - Updated zotero.org translator for groups - Additional trigger-based consistency checks - Fixed broken URL drag in Firefox 3.5 - Disabled zeroconf menu option (no longer functional) Developer-specific changes: - Overhauled data layer - Data object constructors no longer take arguments (return to 1.0-like API) - Existing objects can be retrieved by setting id or library/key properties - id/library/key must be set for new objects before other fields - New methods: - ZoteroPane.getSelectedLibraryID() - ZoteroPane.getSelectedGroup(asID) - ZoteroPane.addItemFromDocument(doc, itemType, saveSnapshot) - ZoteroPane.addItemFromURL(url, itemType) - ZoteroPane.canEdit() - Zotero.CollectionTreeView.selectLibrary(libraryID) - New Zotero.URI methods - Changed methods - Many data object methods now take a libraryID - ZoteroPane.addAttachmentFromPage(link, itemID) - Removed saveItem and saveAttachments parameters from Zotero.Translate constructor - translate() now takes a libraryID, null for local library, or false to not save items (previously on constructor) - saveAttachments is now a translate() parameter - Zotero.flattenArguments() better handles passed objects - Zotero.File.getFileHash() (not currently used)
2009-05-14 18:23:40 +00:00
var returns = [];
for (var i=0; i<args.length; i++){
2013-05-01 22:23:09 +00:00
var arg = args[i];
if (!arg && arg !== 0) {
2.0b3 megacommit - Support for group libraries - General support for multiple libraries of different types - Streamlined sync support - Using solely libraryID and key rather than itemID, and removed all itemID-changing code - Combined two requests for increased performance and decreased server load - Added warning on user account change - Provide explicit error message on SSL failure - Removed snapshot and link toolbar buttons and changed browser context menu options and drags to create parent items + snapshots - Closes #786, Add numPages field - Fixes #1063, Duplicate item with tags broken in Sync Preview - Added better purging of deleted tags - Added local user key before first sync - Add clientDateModified to all objects for more flexibility in syncing - Added new triples-based Relation object type, currently used to store links between items copied between local and group libraries - Updated zotero.org translator for groups - Additional trigger-based consistency checks - Fixed broken URL drag in Firefox 3.5 - Disabled zeroconf menu option (no longer functional) Developer-specific changes: - Overhauled data layer - Data object constructors no longer take arguments (return to 1.0-like API) - Existing objects can be retrieved by setting id or library/key properties - id/library/key must be set for new objects before other fields - New methods: - ZoteroPane.getSelectedLibraryID() - ZoteroPane.getSelectedGroup(asID) - ZoteroPane.addItemFromDocument(doc, itemType, saveSnapshot) - ZoteroPane.addItemFromURL(url, itemType) - ZoteroPane.canEdit() - Zotero.CollectionTreeView.selectLibrary(libraryID) - New Zotero.URI methods - Changed methods - Many data object methods now take a libraryID - ZoteroPane.addAttachmentFromPage(link, itemID) - Removed saveItem and saveAttachments parameters from Zotero.Translate constructor - translate() now takes a libraryID, null for local library, or false to not save items (previously on constructor) - saveAttachments is now a translate() parameter - Zotero.flattenArguments() better handles passed objects - Zotero.File.getFileHash() (not currently used)
2009-05-14 18:23:40 +00:00
continue;
}
2013-05-01 22:23:09 +00:00
if (Array.isArray(arg)) {
for (var j=0; j<arg.length; j++){
returns.push(arg[j]);
}
}
else {
2013-05-01 22:23:09 +00:00
returns.push(arg);
}
}
return returns;
}
Closes #259, auto-complete of tags Addresses #260, Add auto-complete to search window - New XPCOM autocomplete component for Zotero data -- can be used by setting the autocompletesearch attribute of a textbox to 'zotero' and passing a search scope with the autocompletesearchparam attribute. Additional parameters can be passed by appending them to the autocompletesearchparam value with a '/', e.g. 'tag/2732' (to exclude tags that show up in item 2732) - Tag entry now uses more or less the same interface as metadata -- no more popup window -- note that tab isn't working properly yet, and there's no way to quickly enter multiple tags (though it's now considerably quicker than it was before) - Autocomplete for tags, excluding any tags already set for the current item - Standalone note windows now register with the Notifier (since tags needed item modification notifications to work properly), which will help with #282, "Notes opened in separate windows need item notification" - Tags are now retrieved in alphabetical order - Scholar.Item.replaceTag(oldTagID, newTag), with a single notify - Scholar.getAncestorByTagName(elem, tagName) -- walk up the DOM tree from an element until an element with the specified tag name is found (also checks with 'xul:' prefix, for use in XBL), or false if not found -- probably shouldn't be used too widely, since it's doing string comparisons, but better than specifying, say, nine '.parentNode' properties, and makes for more resilient code A few notes: - Autocomplete in Minefield seems to self-destruct after using it in the same field a few times, taking down saving of the field with it -- this may or may not be my fault, but it makes Zotero more or less unusable in 3.0 at the moment. Sorry. (I use 3.0 myself for development, so I'll work on it.) - This would have been much, much easier if having an autocomplete textbox (which uses an XBL-generated popup for the suggestions) within a popup (as it is in the independent note edit panes) didn't introduce all sorts of crazy bugs that had to be defeated with annoying hackery -- one side effect of this is that at the moment you can't close the tags popup with the Escape key - Independent note windows now need to pull in itemPane.js to function properly, which is a bit messy and not ideal, but less messy and more ideal than duplicating all the dual-state editor and tabindex logic would be - Hitting tab in a tag field not only doesn't work but also breaks things until the next window refresh. - There are undoubtedly other bugs.
2006-09-07 08:07:48 +00:00
function getAncestorByTagName(elem, tagName){
while (elem.parentNode){
elem = elem.parentNode;
2008-12-29 09:35:11 +00:00
if (elem.localName == tagName) {
Closes #259, auto-complete of tags Addresses #260, Add auto-complete to search window - New XPCOM autocomplete component for Zotero data -- can be used by setting the autocompletesearch attribute of a textbox to 'zotero' and passing a search scope with the autocompletesearchparam attribute. Additional parameters can be passed by appending them to the autocompletesearchparam value with a '/', e.g. 'tag/2732' (to exclude tags that show up in item 2732) - Tag entry now uses more or less the same interface as metadata -- no more popup window -- note that tab isn't working properly yet, and there's no way to quickly enter multiple tags (though it's now considerably quicker than it was before) - Autocomplete for tags, excluding any tags already set for the current item - Standalone note windows now register with the Notifier (since tags needed item modification notifications to work properly), which will help with #282, "Notes opened in separate windows need item notification" - Tags are now retrieved in alphabetical order - Scholar.Item.replaceTag(oldTagID, newTag), with a single notify - Scholar.getAncestorByTagName(elem, tagName) -- walk up the DOM tree from an element until an element with the specified tag name is found (also checks with 'xul:' prefix, for use in XBL), or false if not found -- probably shouldn't be used too widely, since it's doing string comparisons, but better than specifying, say, nine '.parentNode' properties, and makes for more resilient code A few notes: - Autocomplete in Minefield seems to self-destruct after using it in the same field a few times, taking down saving of the field with it -- this may or may not be my fault, but it makes Zotero more or less unusable in 3.0 at the moment. Sorry. (I use 3.0 myself for development, so I'll work on it.) - This would have been much, much easier if having an autocomplete textbox (which uses an XBL-generated popup for the suggestions) within a popup (as it is in the independent note edit panes) didn't introduce all sorts of crazy bugs that had to be defeated with annoying hackery -- one side effect of this is that at the moment you can't close the tags popup with the Escape key - Independent note windows now need to pull in itemPane.js to function properly, which is a bit messy and not ideal, but less messy and more ideal than duplicating all the dual-state editor and tabindex logic would be - Hitting tab in a tag field not only doesn't work but also breaks things until the next window refresh. - There are undoubtedly other bugs.
2006-09-07 08:07:48 +00:00
return elem;
}
}
return false;
}
/*
* A version of join() that operates externally for use on objects other
* than arrays (e.g. _arguments_)
*
* Note that this is safer than extending Object()
*/
function join(obj, delim){
var a = [];
for (var i=0, len=obj.length; i<len; i++){
a.push(obj[i]);
}
return a.join(delim);
}
/**
* Generate a random string of length 'len' (defaults to 8)
**/
Initial Zotero 1.5 Megacommit Apologies for the massive (and, due to data_access.js splitting, difficult-to-follow) commit. Please note that external code that accesses the data layer may need to be tweaked for compatibility. Here's a comprehensive-as-possible changelog: - Added server sync functionality (incomplete) - Overhaul of data layer - Split data_access.js into separate files (item.js, items.js, creator.js, etc.) - Made creators and collections first-class objects, similar to items - Constructors now take id as first parameter, e.g. new Zotero.Item(1234, 'book'), to allow explicit id setting and id changing - Made various data layer operations (including attachment fields) require a save() rather than making direct DB changes - Better handling of unsaved objects - Item.setCreator() now takes creator objects instead of creator ids, and Item.save() will auto-save unsaved creators - clone() now works on unsaved objects - Newly created object instances are now disabled after save() to force refetch of globally accessible instance using Zotero.(Items|Creators|etc.).get() - Added secondary lookup key to data objects - Deprecated getID() and getItemType() methods in favor of .id and .itemTypeID properties - toArray() deprecated in favor of serialize(), which has a somewhat modified format - Added support for multiple creators with identical data -- currently unimplemented in interface and most of data layer - Added Item.diff() for comparing item metadata - Database changes - Added SQLite triggers to enforce foreign key constraints - Added Zotero.DB.transactionVacuum flag to run a VACUUM after a transaction - Added Zotero.DB.transactionDate, .transactionDateTime, and transactionTimestamp to retrieve consistent timestamps for entire transaction - Properly store 64-bit integers - Set PRAGMA locking_mode=EXCLUSIVE on database - Set SQLite page size to 4096 on new databases - Set SQLite page cache to 8MB - Do some database cleanup and integrity checking on migration from 1.0 branch - Removed IF NOT EXISTS from userdata.sql CREATE statements -- userdata.sql is now processed only on DB initialization - Removed itemNoteTitles table and moved titles into itemNotes - Abstracted metadata edit box and note box into flexible XBL bindings with various modes, including read-only states - Massive speed-up of item tree view - Several fixes from 1.0 branch for Fx3 compatibility - Added Notifier observer to log delete events for syncing - Zotero.Utilities changes - New methods getSQLDataType() and md5() - Removed onError from Zotero.Utilities.HTTP.doGet() - Don't display more than 1024 characters in doPost() debug output - Don't display passwords in doPost() debug output - Added Zotero.Notifier.untrigger() -- currently unused - Added Zotero.reloadDataObjects() to reset all in-memory objects - Added |chars| parameter to Zotero.randomString(len, chars) - Added Zotero.Date.getUnixTimestamp() and Date.toUnixTimestamp(JSDate) - Adjusted zotero-service.js to simplify file inclusion Various things (such as tags) are temporarily broken.
2008-05-04 08:32:48 +00:00
function randomString(len, chars) {
return Zotero.Utilities.randomString(len, chars);
}
function moveToUnique(file, newFile){
newFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
var newName = newFile.leafName;
newFile.remove(null);
// Move file to unique name
file.moveTo(newFile.parent, newName);
return file;
}
Initial Zotero 1.5 Megacommit Apologies for the massive (and, due to data_access.js splitting, difficult-to-follow) commit. Please note that external code that accesses the data layer may need to be tweaked for compatibility. Here's a comprehensive-as-possible changelog: - Added server sync functionality (incomplete) - Overhaul of data layer - Split data_access.js into separate files (item.js, items.js, creator.js, etc.) - Made creators and collections first-class objects, similar to items - Constructors now take id as first parameter, e.g. new Zotero.Item(1234, 'book'), to allow explicit id setting and id changing - Made various data layer operations (including attachment fields) require a save() rather than making direct DB changes - Better handling of unsaved objects - Item.setCreator() now takes creator objects instead of creator ids, and Item.save() will auto-save unsaved creators - clone() now works on unsaved objects - Newly created object instances are now disabled after save() to force refetch of globally accessible instance using Zotero.(Items|Creators|etc.).get() - Added secondary lookup key to data objects - Deprecated getID() and getItemType() methods in favor of .id and .itemTypeID properties - toArray() deprecated in favor of serialize(), which has a somewhat modified format - Added support for multiple creators with identical data -- currently unimplemented in interface and most of data layer - Added Item.diff() for comparing item metadata - Database changes - Added SQLite triggers to enforce foreign key constraints - Added Zotero.DB.transactionVacuum flag to run a VACUUM after a transaction - Added Zotero.DB.transactionDate, .transactionDateTime, and transactionTimestamp to retrieve consistent timestamps for entire transaction - Properly store 64-bit integers - Set PRAGMA locking_mode=EXCLUSIVE on database - Set SQLite page size to 4096 on new databases - Set SQLite page cache to 8MB - Do some database cleanup and integrity checking on migration from 1.0 branch - Removed IF NOT EXISTS from userdata.sql CREATE statements -- userdata.sql is now processed only on DB initialization - Removed itemNoteTitles table and moved titles into itemNotes - Abstracted metadata edit box and note box into flexible XBL bindings with various modes, including read-only states - Massive speed-up of item tree view - Several fixes from 1.0 branch for Fx3 compatibility - Added Notifier observer to log delete events for syncing - Zotero.Utilities changes - New methods getSQLDataType() and md5() - Removed onError from Zotero.Utilities.HTTP.doGet() - Don't display more than 1024 characters in doPost() debug output - Don't display passwords in doPost() debug output - Added Zotero.Notifier.untrigger() -- currently unused - Added Zotero.reloadDataObjects() to reset all in-memory objects - Added |chars| parameter to Zotero.randomString(len, chars) - Added Zotero.Date.getUnixTimestamp() and Date.toUnixTimestamp(JSDate) - Adjusted zotero-service.js to simplify file inclusion Various things (such as tags) are temporarily broken.
2008-05-04 08:32:48 +00:00
/**
* Allow other events (e.g., UI updates) on main thread to be processed if necessary
*
* @param {Integer} [timeout=50] Maximum number of milliseconds to wait
*/
this.wait = function (timeout) {
2013-01-21 18:29:26 +00:00
if (timeout === undefined) {
timeout = 50;
}
var mainThread = Zotero.mainThread;
var endTime = Date.now() + timeout;
var more;
//var cycles = 0;
2011-08-30 00:49:04 +00:00
_waiting++;
2011-08-30 00:49:04 +00:00
Zotero.debug("Spinning event loop ("+_waiting+")", 5);
do {
more = mainThread.processNextEvent(false);
//cycles++;
} while (more && Date.now() < endTime);
2011-08-30 00:49:04 +00:00
_waiting--;
// requeue nsITimerCallbacks that came up during Zotero.wait() but couldn't execute
for(var i in _waitTimers) {
_waitTimers[i].initWithCallback(_waitTimerCallbacks[i], 0, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}
_waitTimers = [];
_waitTimerCallbacks = [];
//Zotero.debug("Waited " + cycles + " cycles");
return;
};
/**
* Generate a function that produces a static output
*
* Zotero.lazy(fn) returns a function. The first time this function
* is called, it calls fn() and returns its output. Subsequent
* calls return the same output as the first without calling fn()
* again.
*/
this.lazy = function(fn) {
var x, called = false;
return function() {
if(!called) {
x = fn.apply(this);
called = true;
}
return x;
};
};
/**
* Pumps a generator until it yields false. See itemTreeView.js for an example.
*
* If errorHandler is specified, exceptions in the generator will be caught
* and passed to the callback
*/
this.pumpGenerator = function(generator, ms, errorHandler, doneHandler) {
_waiting++;
var timer = Components.classes["@mozilla.org/timer;1"].
createInstance(Components.interfaces.nsITimer),
yielded,
useJIT = Components.utils.methodjit;
var timerCallback = {"notify":function() {
// XXX Remove when we drop support for Fx <24
if(useJIT !== undefined) Components.utils.methodjit = useJIT;
var err = false;
_waiting--;
try {
if((yielded = generator.next()) !== false) {
_waiting++;
return;
}
} catch(e if e.toString() === "[object StopIteration]") {
// There must be a better way to perform this check
} catch(e) {
err = e;
}
timer.cancel();
_runningTimers.splice(_runningTimers.indexOf(timer), 1);
// requeue nsITimerCallbacks that came up during generator pumping but couldn't execute
for(var i in _waitTimers) {
_waitTimers[i].initWithCallback(_waitTimerCallbacks[i], 0, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}
_waitTimers = [];
_waitTimerCallbacks = [];
if(err) {
if(errorHandler) {
errorHandler(err);
} else {
throw err;
}
} else if(doneHandler) {
doneHandler(yielded);
}
}}
timer.initWithCallback(timerCallback, ms ? ms : 0, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
// add timer to global scope so that it doesn't get garbage collected before it completes
_runningTimers.push(timer);
};
/**
* Pumps a generator until it yields false. Unlike the above, this returns a promise.
*/
this.promiseGenerator = function(generator, ms) {
var deferred = Q.defer();
this.pumpGenerator(generator, ms,
function(e) { deferred.reject(e); },
function(data) { deferred.resolve(data) });
return deferred.promise;
};
/**
* Emulates the behavior of window.setTimeout, but ensures that callbacks do not get called
* during Zotero.wait()
*
* @param {Function} func The function to be called
* @param {Integer} ms The number of milliseconds to wait before calling func
* @param {Boolean} runWhenWaiting True if the callback should be run even if Zotero.wait()
* is executing
*/
this.setTimeout = function(func, ms, runWhenWaiting) {
var timer = Components.classes["@mozilla.org/timer;1"].
createInstance(Components.interfaces.nsITimer),
useJIT = Components.utils.methodjit;
var timerCallback = {"notify":function() {
// XXX Remove when we drop support for Fx <24
if(useJIT !== undefined) Components.utils.methodjit = useJIT;
if(_waiting && !runWhenWaiting) {
// if our callback gets called during Zotero.wait(), queue it to be set again
// when Zotero.wait() completes
_waitTimers.push(timer);
_waitTimerCallbacks.push(timerCallback);
} else {
// execute callback function
func();
// remove timer from global scope, so it can be garbage collected
_runningTimers.splice(_runningTimers.indexOf(timer), 1);
}
}}
timer.initWithCallback(timerCallback, ms, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
// add timer to global scope so that it doesn't get garbage collected before it completes
_runningTimers.push(timer);
}
/**
* Show Zotero pane overlay and progress bar in all windows
*
* @param {String} msg
* @param {Boolean} [determinate=false]
* @return void
*/
this.showZoteroPaneProgressMeter = function (msg, determinate, icon) {
if (!msg) msg = "";
2013-06-06 06:37:19 +00:00
var currentWindow = Services.wm.getMostRecentWindow("navigator:browser");
var enumerator = Services.wm.getEnumerator("navigator:browser");
var progressMeters = [];
while (enumerator.hasMoreElements()) {
var win = enumerator.getNext();
if(!win.ZoteroPane) continue;
if(!win.ZoteroPane.isShowing()) {
if (win != currentWindow) {
continue;
}
// If Zotero is closed in the top-most window, show a popup instead
_progressPopup = new Zotero.ProgressWindow();
_progressPopup.changeHeadline("Zotero");
if (icon) {
_progressPopup.addLines([msg], [icon]);
}
else {
_progressPopup.addDescription(msg);
}
_progressPopup.show();
continue;
}
var label = win.ZoteroPane.document.getElementById('zotero-pane-progress-label');
if (msg) {
label.hidden = false;
label.value = msg;
}
else {
label.hidden = true;
}
var progressMeter = win.ZoteroPane.document.getElementById('zotero-pane-progressmeter')
if (determinate) {
progressMeter.mode = 'determined';
progressMeter.value = 0;
progressMeter.max = 1000;
}
else {
progressMeter.mode = 'undetermined';
}
_showWindowZoteroPaneOverlay(win.ZoteroPane.document);
win.ZoteroPane.document.getElementById('zotero-pane-overlay-deck').selectedIndex = 0;
progressMeters.push(progressMeter);
}
this.locked = true;
_progressMeters = progressMeters;
}
/**
* @param {Number} percentage Percentage complete as integer or float
*/
this.updateZoteroPaneProgressMeter = function (percentage) {
if(percentage !== null) {
if (percentage < 0 || percentage > 100) {
Zotero.debug("Invalid percentage value '" + percentage + "' in Zotero.updateZoteroPaneProgressMeter()");
return;
}
percentage = Math.round(percentage * 10);
}
if (percentage === _lastPercentage) {
return;
}
for each(var pm in _progressMeters) {
if (percentage !== null) {
if (pm.mode == 'undetermined') {
pm.max = 1000;
pm.mode = 'determined';
}
pm.value = percentage;
} else if(pm.mode === 'determined') {
pm.mode = 'undetermined';
}
}
_lastPercentage = percentage;
}
/**
* Hide Zotero pane overlay in all windows
*/
this.hideZoteroPaneOverlays = function () {
this.locked = false;
2013-06-06 06:37:19 +00:00
var enumerator = Services.wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
var win = enumerator.getNext();
if(win.ZoteroPane && win.ZoteroPane.document) {
_hideWindowZoteroPaneOverlay(win.ZoteroPane.document);
}
}
if (_progressPopup) {
_progressPopup.close();
}
_progressMeters = [];
_progressPopup = null;
_lastPercentage = null;
}
/**
* Adds a listener to be called when Zotero shuts down (even if Firefox is not shut down)
*/
this.addShutdownListener = function(listener) {
_shutdownListeners.push(listener);
}
function _showWindowZoteroPaneOverlay(doc) {
doc.getElementById('zotero-collections-tree').disabled = true;
doc.getElementById('zotero-items-tree').disabled = true;
doc.getElementById('zotero-pane-tab-catcher-top').hidden = false;
doc.getElementById('zotero-pane-tab-catcher-bottom').hidden = false;
doc.getElementById('zotero-pane-overlay').hidden = false;
}
function _hideWindowZoteroPaneOverlay(doc) {
doc.getElementById('zotero-collections-tree').disabled = false;
doc.getElementById('zotero-items-tree').disabled = false;
doc.getElementById('zotero-pane-tab-catcher-top').hidden = true;
doc.getElementById('zotero-pane-tab-catcher-bottom').hidden = true;
doc.getElementById('zotero-pane-overlay').hidden = true;
}
this.updateQuickSearchBox = function (document) {
var searchBox = document.getElementById('zotero-tb-search');
if(!searchBox) return;
var mode = Zotero.Prefs.get("search.quicksearch-mode");
var prefix = 'zotero-tb-search-mode-';
var prefixLen = prefix.length;
var modes = {
titleCreatorYear: {
2013-02-28 22:31:07 +00:00
label: Zotero.getString('quickSearch.mode.titleCreatorYear')
},
fields: {
2013-02-28 22:31:07 +00:00
label: Zotero.getString('quickSearch.mode.fieldsAndTags')
},
everything: {
2013-02-28 22:31:07 +00:00
label: Zotero.getString('quickSearch.mode.everything')
}
};
if (!modes[mode]) {
Zotero.Prefs.set("search.quicksearch-mode", "fields");
mode = 'fields';
}
// TEMP -- pre-3.0b3
else if (modes[mode] == 'titlesAndCreators') {
Zotero.Prefs.set("search.quicksearch-mode", "titleCreatorYear");
mode = 'titleCreatorYear'
}
var hbox = document.getAnonymousNodes(searchBox)[0];
var input = hbox.getElementsByAttribute('class', 'textbox-input')[0];
// Already initialized, so just update selection
var button = hbox.getElementsByAttribute('id', 'zotero-tb-search-menu-button');
if (button.length) {
Zotero.debug("already initialized search menu");
button = button[0];
var menupopup = button.firstChild;
for each(var menuitem in menupopup.childNodes) {
if (menuitem.id.substr(prefixLen) == mode) {
menuitem.setAttribute('checked', true);
2012-02-14 20:20:19 +00:00
searchBox.placeholder = modes[mode].label;
return;
}
}
return;
}
// Otherwise, build menu
button = document.createElement('button');
button.id = 'zotero-tb-search-menu-button';
button.setAttribute('type', 'menu');
var menupopup = document.createElement('menupopup');
for (var i in modes) {
var menuitem = document.createElement('menuitem');
menuitem.setAttribute('id', prefix + i);
menuitem.setAttribute('label', modes[i].label);
menuitem.setAttribute('name', 'searchMode');
menuitem.setAttribute('type', 'radio');
//menuitem.setAttribute("tooltiptext", "");
menupopup.appendChild(menuitem);
if (mode == i) {
menuitem.setAttribute('checked', true);
menupopup.selectedItem = menuitem;
}
}
menupopup.addEventListener("command", function(event) {
var mode = event.target.id.substr(22);
Zotero.Prefs.set("search.quicksearch-mode", mode);
if (document.getElementById("zotero-tb-search").value == "") {
event.stopPropagation();
}
}, false);
button.appendChild(menupopup);
hbox.insertBefore(button, input);
2012-02-14 20:20:19 +00:00
searchBox.placeholder = modes[mode].label;
// If Alt-Up/Down, show popup
searchBox.addEventListener("keypress", function(event) {
if (event.altKey && (event.keyCode == event.DOM_VK_UP || event.keyCode == event.DOM_VK_DOWN)) {
document.getElementById('zotero-tb-search-menu-button').open = true;
event.stopPropagation();
}
}, false);
}
/*
* Clear entries that no longer exist from various tables
*/
this.purgeDataObjects = function (skipStoragePurge) {
Zotero.Creators.purge();
Zotero.Tags.purge();
Zotero.Fulltext.purgeUnusedWords();
Zotero.Items.purge();
Zotero Commons updates: - Store one item per IA bucket, with attachments stored as objects - Use proper mediatype field based on Zotero item type - Commons list is now pulled dynamically based on RDF stored at IA, without need for corresponding local item (which may have been deleted, etc.) - Once available, OCRed PDFs can be pulled down by right-clicking on Commons and selecting Refresh - Downloaded OCRed PDFs are now named the same as the existing attachment, with "(OCR)" appended - The relations table is used to link downloaded OCRed PDFs to the IA file, so the downloaded file can be renamed without triggering another download - The Commons view is marked for automatic refresh after an item is uploaded - Added some progress notifications, though more are probably needed - Other things Also: - Added Zotero.File.getBinaryContents(file) - Erase an item's relations when the item is deleted, and purge orphaned ones - Zotero.URI.eraseByPathPrefix(prefix) no longer prepends 'http://zotero.org' (which has been moved to Zotero.URI.defaultPrefix) - New function Zotero.URI.eraseByURI(prefix) Known Issues: - Slow (some IA changes should be able to speed it up) - Identifier format is likely temporary - Sometimes it stops during setTimeout() calls for no apparent reason whatsoever - Didn't test items with multiple attachments - Not sure if Commons view will auto-refresh if you switch to it before the upload is done - IA translator not yet updated - Deleting items not supported by IA - Date Added/Date Modified don't show up properly in Zotero for Commons items
2010-04-27 08:03:08 +00:00
// DEBUG: this might not need to be permanent
Zotero.Relations.purge();
}
2.0b3 megacommit - Support for group libraries - General support for multiple libraries of different types - Streamlined sync support - Using solely libraryID and key rather than itemID, and removed all itemID-changing code - Combined two requests for increased performance and decreased server load - Added warning on user account change - Provide explicit error message on SSL failure - Removed snapshot and link toolbar buttons and changed browser context menu options and drags to create parent items + snapshots - Closes #786, Add numPages field - Fixes #1063, Duplicate item with tags broken in Sync Preview - Added better purging of deleted tags - Added local user key before first sync - Add clientDateModified to all objects for more flexibility in syncing - Added new triples-based Relation object type, currently used to store links between items copied between local and group libraries - Updated zotero.org translator for groups - Additional trigger-based consistency checks - Fixed broken URL drag in Firefox 3.5 - Disabled zeroconf menu option (no longer functional) Developer-specific changes: - Overhauled data layer - Data object constructors no longer take arguments (return to 1.0-like API) - Existing objects can be retrieved by setting id or library/key properties - id/library/key must be set for new objects before other fields - New methods: - ZoteroPane.getSelectedLibraryID() - ZoteroPane.getSelectedGroup(asID) - ZoteroPane.addItemFromDocument(doc, itemType, saveSnapshot) - ZoteroPane.addItemFromURL(url, itemType) - ZoteroPane.canEdit() - Zotero.CollectionTreeView.selectLibrary(libraryID) - New Zotero.URI methods - Changed methods - Many data object methods now take a libraryID - ZoteroPane.addAttachmentFromPage(link, itemID) - Removed saveItem and saveAttachments parameters from Zotero.Translate constructor - translate() now takes a libraryID, null for local library, or false to not save items (previously on constructor) - saveAttachments is now a translate() parameter - Zotero.flattenArguments() better handles passed objects - Zotero.File.getFileHash() (not currently used)
2009-05-14 18:23:40 +00:00
this.reloadDataObjects = function () {
Zotero.Tags.reloadAll();
Initial Zotero 1.5 Megacommit Apologies for the massive (and, due to data_access.js splitting, difficult-to-follow) commit. Please note that external code that accesses the data layer may need to be tweaked for compatibility. Here's a comprehensive-as-possible changelog: - Added server sync functionality (incomplete) - Overhaul of data layer - Split data_access.js into separate files (item.js, items.js, creator.js, etc.) - Made creators and collections first-class objects, similar to items - Constructors now take id as first parameter, e.g. new Zotero.Item(1234, 'book'), to allow explicit id setting and id changing - Made various data layer operations (including attachment fields) require a save() rather than making direct DB changes - Better handling of unsaved objects - Item.setCreator() now takes creator objects instead of creator ids, and Item.save() will auto-save unsaved creators - clone() now works on unsaved objects - Newly created object instances are now disabled after save() to force refetch of globally accessible instance using Zotero.(Items|Creators|etc.).get() - Added secondary lookup key to data objects - Deprecated getID() and getItemType() methods in favor of .id and .itemTypeID properties - toArray() deprecated in favor of serialize(), which has a somewhat modified format - Added support for multiple creators with identical data -- currently unimplemented in interface and most of data layer - Added Item.diff() for comparing item metadata - Database changes - Added SQLite triggers to enforce foreign key constraints - Added Zotero.DB.transactionVacuum flag to run a VACUUM after a transaction - Added Zotero.DB.transactionDate, .transactionDateTime, and transactionTimestamp to retrieve consistent timestamps for entire transaction - Properly store 64-bit integers - Set PRAGMA locking_mode=EXCLUSIVE on database - Set SQLite page size to 4096 on new databases - Set SQLite page cache to 8MB - Do some database cleanup and integrity checking on migration from 1.0 branch - Removed IF NOT EXISTS from userdata.sql CREATE statements -- userdata.sql is now processed only on DB initialization - Removed itemNoteTitles table and moved titles into itemNotes - Abstracted metadata edit box and note box into flexible XBL bindings with various modes, including read-only states - Massive speed-up of item tree view - Several fixes from 1.0 branch for Fx3 compatibility - Added Notifier observer to log delete events for syncing - Zotero.Utilities changes - New methods getSQLDataType() and md5() - Removed onError from Zotero.Utilities.HTTP.doGet() - Don't display more than 1024 characters in doPost() debug output - Don't display passwords in doPost() debug output - Added Zotero.Notifier.untrigger() -- currently unused - Added Zotero.reloadDataObjects() to reset all in-memory objects - Added |chars| parameter to Zotero.randomString(len, chars) - Added Zotero.Date.getUnixTimestamp() and Date.toUnixTimestamp(JSDate) - Adjusted zotero-service.js to simplify file inclusion Various things (such as tags) are temporarily broken.
2008-05-04 08:32:48 +00:00
Zotero.Collections.reloadAll();
Zotero.Creators.reloadAll();
Initial Zotero 1.5 Megacommit Apologies for the massive (and, due to data_access.js splitting, difficult-to-follow) commit. Please note that external code that accesses the data layer may need to be tweaked for compatibility. Here's a comprehensive-as-possible changelog: - Added server sync functionality (incomplete) - Overhaul of data layer - Split data_access.js into separate files (item.js, items.js, creator.js, etc.) - Made creators and collections first-class objects, similar to items - Constructors now take id as first parameter, e.g. new Zotero.Item(1234, 'book'), to allow explicit id setting and id changing - Made various data layer operations (including attachment fields) require a save() rather than making direct DB changes - Better handling of unsaved objects - Item.setCreator() now takes creator objects instead of creator ids, and Item.save() will auto-save unsaved creators - clone() now works on unsaved objects - Newly created object instances are now disabled after save() to force refetch of globally accessible instance using Zotero.(Items|Creators|etc.).get() - Added secondary lookup key to data objects - Deprecated getID() and getItemType() methods in favor of .id and .itemTypeID properties - toArray() deprecated in favor of serialize(), which has a somewhat modified format - Added support for multiple creators with identical data -- currently unimplemented in interface and most of data layer - Added Item.diff() for comparing item metadata - Database changes - Added SQLite triggers to enforce foreign key constraints - Added Zotero.DB.transactionVacuum flag to run a VACUUM after a transaction - Added Zotero.DB.transactionDate, .transactionDateTime, and transactionTimestamp to retrieve consistent timestamps for entire transaction - Properly store 64-bit integers - Set PRAGMA locking_mode=EXCLUSIVE on database - Set SQLite page size to 4096 on new databases - Set SQLite page cache to 8MB - Do some database cleanup and integrity checking on migration from 1.0 branch - Removed IF NOT EXISTS from userdata.sql CREATE statements -- userdata.sql is now processed only on DB initialization - Removed itemNoteTitles table and moved titles into itemNotes - Abstracted metadata edit box and note box into flexible XBL bindings with various modes, including read-only states - Massive speed-up of item tree view - Several fixes from 1.0 branch for Fx3 compatibility - Added Notifier observer to log delete events for syncing - Zotero.Utilities changes - New methods getSQLDataType() and md5() - Removed onError from Zotero.Utilities.HTTP.doGet() - Don't display more than 1024 characters in doPost() debug output - Don't display passwords in doPost() debug output - Added Zotero.Notifier.untrigger() -- currently unused - Added Zotero.reloadDataObjects() to reset all in-memory objects - Added |chars| parameter to Zotero.randomString(len, chars) - Added Zotero.Date.getUnixTimestamp() and Date.toUnixTimestamp(JSDate) - Adjusted zotero-service.js to simplify file inclusion Various things (such as tags) are temporarily broken.
2008-05-04 08:32:48 +00:00
Zotero.Items.reloadAll();
}
/**
* Brings Zotero Standalone to the foreground
*/
this.activateStandalone = function() {
2013-06-06 06:37:19 +00:00
var uri = Services.io.newURI('zotero://select', null, null);
var handler = Components.classes['@mozilla.org/uriloader/external-protocol-service;1']
.getService(Components.interfaces.nsIExternalProtocolService)
.getProtocolHandlerInfo('zotero');
handler.preferredAction = Components.interfaces.nsIHandlerInfo.useSystemDefault;
handler.launchWithURI(uri, null);
}
/**
* Determines whether to keep an error message so that it can (potentially) be reported later
*/
function _shouldKeepError(msg) {
const skip = ['CSS Parser', 'content javascript'];
//Zotero.debug(msg);
try {
msg.QueryInterface(Components.interfaces.nsIScriptError);
//Zotero.debug(msg);
if (skip.indexOf(msg.category) != -1 || msg.flags & msg.warningFlag) {
return false;
}
}
catch (e) { }
const blacklist = [
"No chrome package registered for chrome://communicator",
'[JavaScript Error: "Components is not defined" {file: "chrome://nightly/content/talkback/talkback.js',
'[JavaScript Error: "document.getElementById("sanitizeItem")',
'No chrome package registered for chrome://piggy-bank',
'[JavaScript Error: "[Exception... "\'Component is not available\' when calling method: [nsIHandlerService::getTypeFromExtension',
'[JavaScript Error: "this._uiElement is null',
'Error: a._updateVisibleText is not a function',
'[JavaScript Error: "Warning: unrecognized command line flag ',
'LibX:',
'function skype_',
'[JavaScript Error: "uncaught exception: Permission denied to call method Location.toString"]',
'CVE-2009-3555',
'OpenGL LayerManager',
2013-07-30 19:48:53 +00:00
'trying to re-register CID',
'Services.HealthReport'
];
for (var i=0; i<blacklist.length; i++) {
if (msg.message.indexOf(blacklist[i]) != -1) {
//Zotero.debug("Skipping blacklisted error: " + msg.message);
return false;
}
}
return true;
}
/**
* Warn if Zotero Standalone is running as root and clobber the cache directory if it is
*/
function _checkRoot() {
var env = Components.classes["@mozilla.org/process/environment;1"].
getService(Components.interfaces.nsIEnvironment);
var user = env.get("USER") || env.get("USERNAME");
if(user === "root") {
// Show warning
if(Services.prompt.confirmEx(null, "", Zotero.getString("standalone.rootWarning"),
Services.prompt.BUTTON_POS_0*Services.prompt.BUTTON_TITLE_IS_STRING |
Services.prompt.BUTTON_POS_1*Services.prompt.BUTTON_TITLE_IS_STRING,
Zotero.getString("standalone.rootWarning.exit"),
Zotero.getString("standalone.rootWarning.continue"),
null, null, {}) == 0) {
Components.utils.import("resource://gre/modules/ctypes.jsm");
var exit = Zotero.IPC.getLibc().declare("exit", ctypes.default_abi,
ctypes.void_t, ctypes.int);
// Zap cache files
try {
Services.dirsvc.get("ProfLD", Components.interfaces.nsIFile).remove(true);
} catch(e) {}
// Exit Zotero without giving XULRunner the opportunity to figure out the
// cache is missing. Otherwise XULRunner will zap the prefs
exit(0);
}
}
}
/**
* Observer for console messages
* @namespace
*/
var ConsoleListener = {
"QueryInterface":XPCOMUtils.generateQI([Components.interfaces.nsIConsoleMessage,
Components.interfaces.nsISupports]),
"observe":function(msg) {
if(!_shouldKeepError(msg)) return;
if(_recentErrors.length === ERROR_BUFFER_SIZE) _recentErrors.shift();
_recentErrors.push(msg);
}
};
}).call(Zotero);
Zotero.Prefs = new function(){
// Privileged methods
this.init = init;
this.get = get;
this.set = set;
this.register = register;
this.unregister = unregister;
this.observe = observe;
// Public properties
2007-10-23 07:11:59 +00:00
this.prefBranch;
function init(){
2013-06-06 06:37:19 +00:00
this.prefBranch = Services.prefs.getBranch(ZOTERO_CONFIG.PREF_BRANCH);
// Register observer to handle pref changes
this.register();
2012-02-14 20:20:19 +00:00
// Process pref version updates
var fromVersion = this.get('prefVersion');
if (!fromVersion) {
fromVersion = 0;
}
var toVersion = 1;
if (fromVersion < toVersion) {
for (var i = fromVersion + 1; i <= toVersion; i++) {
switch (i) {
case 1:
// If a sync username is entered and ZFS is enabled, turn
// on-demand downloading off to maintain current behavior
if (this.get('sync.server.username')) {
if (this.get('sync.storage.enabled')
&& this.get('sync.storage.protocol') == 'zotero') {
this.set('sync.storage.downloadMode.personal', 'on-sync');
}
if (this.get('sync.storage.groups.enabled')) {
this.set('sync.storage.downloadMode.groups', 'on-sync');
}
}
}
}
this.set('prefVersion', toVersion);
}
}
/**
* Retrieve a preference
**/
2007-10-23 07:11:59 +00:00
function get(pref, global){
try {
2007-10-23 07:11:59 +00:00
if (global) {
2013-06-06 06:37:19 +00:00
var branch = Services.prefs.getBranch("");
2007-10-23 07:11:59 +00:00
}
else {
2012-01-06 00:05:50 +00:00
var branch = this.prefBranch;
2007-10-23 07:11:59 +00:00
}
2012-01-06 00:05:50 +00:00
switch (branch.getPrefType(pref)){
case branch.PREF_BOOL:
return branch.getBoolPref(pref);
case branch.PREF_STRING:
return branch.getCharPref(pref);
case branch.PREF_INT:
return branch.getIntPref(pref);
}
}
catch (e){
throw ("Invalid preference '" + pref + "'");
}
}
/**
* Set a preference
**/
function set(pref, value) {
try {
switch (this.prefBranch.getPrefType(pref)){
case this.prefBranch.PREF_BOOL:
return this.prefBranch.setBoolPref(pref, value);
case this.prefBranch.PREF_STRING:
return this.prefBranch.setCharPref(pref, value);
case this.prefBranch.PREF_INT:
return this.prefBranch.setIntPref(pref, value);
// If not an existing pref, create appropriate type automatically
case 0:
if (typeof value == 'boolean') {
Zotero.debug("Creating boolean pref '" + pref + "'");
return this.prefBranch.setBoolPref(pref, value);
}
if (typeof value == 'string') {
Zotero.debug("Creating string pref '" + pref + "'");
return this.prefBranch.setCharPref(pref, value);
}
if (parseInt(value) == value) {
Zotero.debug("Creating integer pref '" + pref + "'");
return this.prefBranch.setIntPref(pref, value);
}
throw ("Invalid preference value '" + value + "' for pref '" + pref + "'");
}
}
catch (e){
throw ("Invalid preference '" + pref + "'");
}
}
this.clear = function (pref) {
try {
this.prefBranch.clearUserPref(pref);
}
catch (e) {
throw ("Invalid preference '" + pref + "'");
}
}
// Import settings bundles
this.importSettings = function (str, uri) {
2013-06-06 06:37:19 +00:00
var ps = Services.prompt;
if (!uri.match(/https:\/\/([^\.]+\.)?zotero.org\//)) {
Zotero.debug("Ignoring settings file not from https://zotero.org");
return;
}
str = Zotero.Utilities.trim(str.replace(/<\?xml.*\?>\s*/, ''));
Zotero.debug(str);
var confirm = ps.confirm(
null,
"",
"Apply settings from zotero.org?"
);
if (!confirm) {
return;
}
// TODO: parse settings XML
}
//
// Methods to register a preferences observer
//
function register(){
this.prefBranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
this.prefBranch.addObserver("", this, false);
}
function unregister(){
if (!this.prefBranch){
return;
}
this.prefBranch.removeObserver("", this);
}
function observe(subject, topic, data){
if(topic!="nsPref:changed"){
return;
}
2012-02-14 20:20:19 +00:00
try {
// subject is the nsIPrefBranch we're observing (after appropriate QI)
// data is the name of the pref that's been changed (relative to subject)
2012-02-14 20:20:19 +00:00
switch (data) {
case "statusBarIcon":
2013-06-06 06:37:19 +00:00
var doc = Services.wm.getMostRecentWindow("navigator:browser").document;
2012-02-14 20:20:19 +00:00
var addonBar = doc.getElementById("addon-bar");
var icon = doc.getElementById("zotero-toolbar-button");
// When the customize window is open, toolbar buttons seem to
// become wrapped in toolbarpaletteitems, which we need to remove
// manually if we change the pref to hidden or else the customize
// window doesn't close.
var wrapper = doc.getElementById("wrapper-zotero-toolbar-button");
var palette = doc.getElementById("navigator-toolbox").palette;
var inAddonBar = false;
if (icon) {
// Because of the potential wrapper, don't just use .parentNode
var toolbar = Zotero.getAncestorByTagName(icon, "toolbar");
inAddonBar = toolbar == addonBar;
}
var val = this.get("statusBarIcon");
if (val == 0) {
// If showing in add-on bar, hide
if (!icon || !inAddonBar) {
return;
}
palette.appendChild(icon);
if (wrapper) {
addonBar.removeChild(wrapper);
}
addonBar.setAttribute("currentset", addonBar.currentSet);
doc.persist(addonBar.id, "currentset");
}
else {
// If showing somewhere else, remove it from there
if (icon && !inAddonBar) {
palette.appendChild(icon);
if (wrapper) {
toolbar.removeChild(wrapper);
}
toolbar.setAttribute("currentset", toolbar.currentSet);
doc.persist(toolbar.id, "currentset");
}
// If not showing in add-on bar, add
if (!inAddonBar) {
var icon = addonBar.insertItem("zotero-toolbar-button");
addonBar.setAttribute("currentset", addonBar.currentSet);
doc.persist(addonBar.id, "currentset");
addonBar.setAttribute("collapsed", false);
doc.persist(addonBar.id, "collapsed");
}
// And make small
if (val == 1) {
icon.setAttribute("compact", true);
}
// Or large
else if (val == 2) {
icon.removeAttribute("compact");
}
}
break;
case "automaticScraperUpdates":
if (this.get('automaticScraperUpdates')){
Zotero.Schema.updateFromRepository();
}
else {
Zotero.Schema.stopRepositoryTimer();
}
break;
case "zoteroDotOrgVersionHeader":
if (this.get("zoteroDotOrgVersionHeader")) {
Zotero.VersionHeader.register();
}
else {
Zotero.VersionHeader.unregister();
}
break;
case "sync.autoSync":
if (this.get("sync.autoSync")) {
Zotero.Sync.Runner.IdleListener.register();
}
else {
Zotero.Sync.Runner.IdleListener.unregister();
}
break;
case "search.quicksearch-mode":
2013-06-06 06:37:19 +00:00
var enumerator = Services.wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
var win = enumerator.getNext();
if (!win.ZoteroPane) continue;
Zotero.updateQuickSearchBox(win.ZoteroPane.document);
}
var enumerator = wm.getEnumerator("zotero:item-selector");
while (enumerator.hasMoreElements()) {
var win = enumerator.getNext();
if (!win.Zotero) continue;
Zotero.updateQuickSearchBox(win.document);
}
break;
}
2012-02-14 20:20:19 +00:00
}
catch (e) {
Zotero.debug(e);
throw (e);
}
}
}
2007-10-23 07:11:59 +00:00
/*
* Handles keyboard shortcut initialization from preferences, optionally
* overriding existing global shortcuts
*
* Actions are configured in ZoteroPane.handleKeyPress()
*/
Zotero.Keys = new function() {
this.init = init;
this.windowInit = windowInit;
this.getCommand = getCommand;
var _keys = {};
2007-10-23 07:11:59 +00:00
/*
* Called by Zotero.init()
*/
function init() {
var actions = Zotero.Prefs.prefBranch.getChildList('keys', {}, {});
// Get the key=>command mappings from the prefs
for each(var action in actions) {
var action = action.substr(5); // strips 'keys.'
// Remove old pref
2007-10-23 07:11:59 +00:00
if (action == 'overrideGlobal') {
Zotero.Prefs.clear('keys.overrideGlobal');
2007-10-23 07:11:59 +00:00
continue;
}
_keys[Zotero.Prefs.get('keys.' + action)] = action;
}
}
/*
* Called by ZoteroPane.onLoad()
*/
function windowInit(document) {
var globalKeys = [
{
name: 'openZotero',
defaultKey: 'Z'
},
{
name: 'saveToZotero',
defaultKey: 'S'
2007-10-23 07:11:59 +00:00
}
];
globalKeys.forEach(function (x) {
let keyElem = document.getElementById('key_' + x.name);
if (keyElem) {
let prefKey = Zotero.Prefs.get('keys.' + x.name);
// Only override the default with the pref if the <key> hasn't
// been manually changed and the pref has been
if (keyElem.getAttribute('key') == x.defaultKey
&& keyElem.getAttribute('modifiers') == 'accel shift'
&& prefKey != x.defaultKey) {
keyElem.setAttribute('key', prefKey);
}
2007-10-23 07:11:59 +00:00
}
});
2007-10-23 07:11:59 +00:00
}
function getCommand(key) {
2013-07-26 19:34:39 +00:00
key = key.toUpperCase();
2007-10-23 07:11:59 +00:00
return _keys[key] ? _keys[key] : false;
}
}
/**
* Add X-Zotero-Version header to HTTP requests to zotero.org
*
* @namespace
*/
Zotero.VersionHeader = {
init: function () {
if (Zotero.Prefs.get("zoteroDotOrgVersionHeader")) {
this.register();
}
Zotero.addShutdownListener(this.unregister);
},
// Called from this.init() and Zotero.Prefs.observe()
register: function () {
Services.obs.addObserver(this, "http-on-modify-request", false);
},
observe: function (subject, topic, data) {
try {
var channel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
if (channel.URI.host.match(/zotero\.org$/)) {
channel.setRequestHeader("X-Zotero-Version", Zotero.version, false);
}
}
catch (e) {
Zotero.debug(e);
}
},
unregister: function () {
Services.obs.removeObserver(Zotero.VersionHeader, "http-on-modify-request");
}
}
2008-11-30 20:18:48 +00:00
Zotero.DragDrop = {
currentDragEvent: null,
currentTarget: null,
currentOrientation: 0,
2008-11-30 20:18:48 +00:00
getDataFromDataTransfer: function (dataTransfer, firstOnly) {
var dt = dataTransfer;
var dragData = {
dataType: '',
data: [],
dropEffect: dt.dropEffect
};
var len = firstOnly ? 1 : dt.mozItemCount;
if (dt.types.contains('zotero/collection')) {
dragData.dataType = 'zotero/collection';
var ids = dt.getData('zotero/collection').split(",");
dragData.data = ids;
}
else if (dt.types.contains('zotero/item')) {
dragData.dataType = 'zotero/item';
var ids = dt.getData('zotero/item').split(",");
dragData.data = ids;
}
else if (dt.types.contains('application/x-moz-file')) {
dragData.dataType = 'application/x-moz-file';
var files = [];
for (var i=0; i<len; i++) {
var file = dt.mozGetDataAt("application/x-moz-file", i);
file.QueryInterface(Components.interfaces.nsIFile);
// Don't allow folder drag
if (file.isDirectory()) {
continue;
2008-11-30 20:18:48 +00:00
}
files.push(file);
}
dragData.data = files;
}
else if (dt.types.contains('text/x-moz-url')) {
dragData.dataType = 'text/x-moz-url';
var urls = [];
for (var i=0; i<len; i++) {
var url = dt.getData("text/x-moz-url").split("\n")[0];
urls.push(url);
2008-11-30 20:18:48 +00:00
}
dragData.data = urls;
2008-11-30 20:18:48 +00:00
}
2008-11-30 20:18:48 +00:00
return dragData;
},
getDragSource: function () {
var dt = this.currentDragEvent.dataTransfer;
if (!dt) {
Zotero.debug("Drag data not available", 2);
return false;
}
// For items, the drag source is the ItemGroup of the parent window
// of the source tree
if (dt.types.contains("zotero/item")) {
var sourceNode = dt.mozSourceNode;
if (!sourceNode || sourceNode.tagName != 'treechildren'
|| sourceNode.parentElement.id != 'zotero-items-tree') {
return false;
}
var win = sourceNode.ownerDocument.defaultView;
return win.ZoteroPane.collectionsView.itemGroup;
}
else {
return false;
}
},
getDragTarget: function () {
var event = this.currentDragEvent;
var target = event.target;
if (target.tagName == 'treechildren') {
var tree = target.parentNode;
if (tree.id == 'zotero-collections-tree') {
let row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
let win = tree.ownerDocument.defaultView;
return win.ZoteroPane.collectionsView.getItemGroupAtRow(row.value);
}
}
return false;
2008-11-30 20:18:48 +00:00
}
}
2007-10-23 07:11:59 +00:00
/**
* Functions for creating and destroying hidden browser objects
**/
Zotero.Browser = new function() {
2012-11-07 22:17:16 +00:00
var nBrowsers = 0;
this.createHiddenBrowser = createHiddenBrowser;
this.deleteHiddenBrowser = deleteHiddenBrowser;
2007-10-23 07:11:59 +00:00
function createHiddenBrowser(win) {
if (!win) {
2013-06-06 06:37:19 +00:00
var win = Services.wm.getMostRecentWindow("navigator:browser");
if(!win) {
2013-06-06 06:37:19 +00:00
var win = Services.ww.activeWindow;
}
}
Fulltext search support There are currently two types of fulltext searching: an SQL-based word index and a file scanner. They each have their advantages and drawbacks. The word index is very fast to search and is currently used for the find-as-you-type quicksearch. However, indexing files takes some time, so we should probably offer a preference to turn it off ("Index attachment content for quicksearch" or something). There's also an issue with Chinese characters (which are indexed by character rather than word, since there are no spaces to go by, so a search for a word with common characters could produce erroneous results). The quicksearch doesn't use a left-bound index (since that would probably upset German speakers searching for "musik" in "nachtmusik," though I don't know for sure how they think of words) but still seems pretty fast. * Note: There will be a potentially long delay when you start Firefox with this revision as it builds a fulltext word index of your existing items. We obviously need a notification/option for this. * The file scanner, used in the Attachment Content condition of the search dialog, offers phrase searching as well as regex support (both case-sensitive and not, and defaulting to multiline). It doesn't require an index, though it should probably be optimized to use the word index, if available, for narrowing the results when not in regex mode. (It does only scan files that pass all the other search conditions, which speeds it up considerably for multi-condition searches, and skips non-text files unless instructed otherwise, but it's still relatively slow.) Both convert HTML to text before searching (with the exception of the binary file scanning mode). There are some issues with which files get indexed and which don't that we can't do much about and that will probably confuse users immensely. Dan C. suggested some sort of indicator (say, a green dot) to show which files are indexed. Also added (very ugly) charset detection (anybody want to figure out getCharsetFromString(str)?), a setTimeout() replacement in the XPCOM service, an arrayToHash() method, and a new header to timedtextarea.xml, since it's really not copyright CHNM (it's really just a few lines off from the toolkit timed-textbox binding--I tried to change it to extend timed-textbox and just ignore Return keypress events so that we didn't need to duplicate the Mozilla code, but timed-textbox's reliance on html:input instead of html:textarea made things rather difficult). To do: - Pref/buttons to disable/clear/rebuild fulltext index - Hidden prefs to set maximum file size to index/scan - Don't index words of fewer than 3 non-Asian characters - MRU cache for saved searches - Use word index if available to narrow search scope of fulltext scanner - Cache attachment info methods - Show content excerpt in search results (at least in advanced search window, when it exists) - Notification window (a la scraping) to show when indexing - Indicator of indexed status - Context menu option to index - Indicator that a file scanning search is in progress, if possible - Find other ways to make it index the NYT front page in under 10 seconds - Probably fix lots of bugs, which you will likely start telling me about...now.
2006-09-21 00:10:29 +00:00
// Create a hidden browser
2007-10-23 07:11:59 +00:00
var hiddenBrowser = win.document.createElement("browser");
hiddenBrowser.setAttribute('type', 'content');
hiddenBrowser.setAttribute('disablehistory', 'true');
2007-10-23 07:11:59 +00:00
win.document.documentElement.appendChild(hiddenBrowser);
// Disable some features
hiddenBrowser.docShell.allowAuth = false;
hiddenBrowser.docShell.allowDNSPrefetch = false;
hiddenBrowser.docShell.allowImages = false;
hiddenBrowser.docShell.allowJavascript = true;
hiddenBrowser.docShell.allowMetaRedirects = false;
hiddenBrowser.docShell.allowPlugins = false;
2012-11-07 22:17:16 +00:00
Zotero.debug("Created hidden browser (" + (nBrowsers++) + ")");
2007-10-23 07:11:59 +00:00
return hiddenBrowser;
}
function deleteHiddenBrowser(myBrowsers) {
2012-11-07 20:15:56 +00:00
if(!(myBrowsers instanceof Array)) myBrowsers = [myBrowsers];
for(var i=0; i<myBrowsers.length; i++) {
var myBrowser = myBrowsers[i];
myBrowser.stop();
myBrowser.destroy();
myBrowser.parentNode.removeChild(myBrowser);
myBrowser = null;
2012-11-07 22:17:16 +00:00
Zotero.debug("Deleted hidden browser (" + (--nBrowsers) + ")");
}
}
2007-10-23 07:11:59 +00:00
}
/**
* Functions for disabling and enabling the unresponsive script indicator
**/
Zotero.UnresponsiveScriptIndicator = new function() {
this.disable = disable;
this.enable = enable;
// stores the state of the unresponsive script preference prior to disabling
var _unresponsiveScriptPreference, _isDisabled;
/**
* disables the "unresponsive script" warning; necessary for import and
* export, which can take quite a while to execute
**/
function disable() {
// don't do anything if already disabled
if (_isDisabled) {
return false;
}
2007-10-23 07:11:59 +00:00
2013-06-06 06:37:19 +00:00
_unresponsiveScriptPreference = Services.prefs.getIntPref("dom.max_chrome_script_run_time");
Services.prefs.setIntPref("dom.max_chrome_script_run_time", 0);
2007-10-23 07:11:59 +00:00
_isDisabled = true;
return true;
2007-10-23 07:11:59 +00:00
}
/**
* restores the "unresponsive script" warning
**/
function enable() {
2013-06-06 06:37:19 +00:00
Services.prefs.setIntPref("dom.max_chrome_script_run_time", _unresponsiveScriptPreference);
2007-10-23 07:11:59 +00:00
_isDisabled = false;
}
}
/*
* Implements nsIWebProgressListener
*/
Zotero.WebProgressFinishListener = function(onFinish) {
this.onStateChange = function(wp, req, stateFlags, status) {
//Zotero.debug('onStageChange: ' + stateFlags);
if ((stateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
&& (stateFlags & Components.interfaces.nsIWebProgressListener.STATE_IS_NETWORK)) {
onFinish();
}
}
this.onProgressChange = function(wp, req, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {
//Zotero.debug('onProgressChange');
//Zotero.debug('Current: ' + curTotalProgress);
//Zotero.debug('Max: ' + maxTotalProgress);
}
this.onLocationChange = function(wp, req, location) {}
this.onSecurityChange = function(wp, req, stateFlags, status) {}
this.onStatusChange = function(wp, req, status, msg) {}
}
/*
* Saves or loads JSON objects.
2007-10-23 07:11:59 +00:00
*/
Zotero.JSON = new function() {
this.serialize = function(arg) {
Zotero.debug("WARNING: Zotero.JSON.serialize() is deprecated; use JSON.stringify()");
return JSON.stringify(arg);
2007-10-23 07:11:59 +00:00
}
this.unserialize = function(arg) {
Zotero.debug("WARNING: Zotero.JSON.unserialize() is deprecated; use JSON.parse()");
return JSON.parse(arg);
2007-10-23 07:11:59 +00:00
}
}