const SCHOLAR_CONFIG = { GUID: 'zotero@chnm.gmu.edu', DB_FILE: 'zotero.sqlite', DB_REBUILD: false, // erase DB and recreate from schema DEBUG_LOGGING: true, DEBUG_TO_CONSOLE: true, // dump debug messages to console rather than (much slower) Debug Logger REPOSITORY_URL: 'http://www.zotero.org/repo', REPOSITORY_CHECK_INTERVAL: 86400, // 24 hours REPOSITORY_RETRY_INTERVAL: 3600 // 1 hour }; /* * Core functions */ var Scholar = new function(){ var _initialized = false; var _shutdown = false; var _localizedStringBundle; // Privileged (public) methods this.init = init; this.shutdown = shutdown; this.getProfileDirectory = getProfileDirectory; this.getScholarDirectory = getScholarDirectory; this.getStorageDirectory = getStorageDirectory; this.getScholarDatabase = getScholarDatabase; this.backupDatabase = backupDatabase; this.debug = debug; this.varDump = varDump; this.getString = getString; this.flattenArguments = flattenArguments; this.getAncestorByTagName = getAncestorByTagName; this.join = join; this.inArray = inArray; this.arraySearch = arraySearch; this.randomString = randomString; this.getRandomID = getRandomID; this.moveToUnique = moveToUnique; // Public properties this.version; this.platform; this.locale; this.isMac; /* * Initialize the extension */ function init(){ if (_initialized){ return false; } // Register shutdown handler to call Scholar.shutdown() var observerService = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); observerService.addObserver({ observe: function(subject, topic, data){ Scholar.shutdown(subject, topic, data) } }, "xpcom-shutdown", false); // Load in the preferences branch for the extension Scholar.Prefs.init(); // Load in the extension version from the extension manager var nsIUpdateItem = Components.interfaces.nsIUpdateItem; var gExtensionManager = Components.classes["@mozilla.org/extensions/manager;1"] .getService(Components.interfaces.nsIExtensionManager); var itemType = nsIUpdateItem.TYPE_EXTENSION; this.version = gExtensionManager.getItemForID(SCHOLAR_CONFIG['GUID']).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"); // Locale var localeService = Components.classes['@mozilla.org/intl/nslocaleservice;1']. getService(Components.interfaces.nsILocaleService); this.locale = localeService.getLocaleComponentForUserAgent(); // Load in the localization stringbundle for use by getString(name) var src = 'chrome://scholar/locale/scholar.properties'; var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"] .getService(Components.interfaces.nsILocaleService); var appLocale = localeService.getApplicationLocale(); var stringBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"] .getService(Components.interfaces.nsIStringBundleService); _localizedStringBundle = stringBundleService.createBundle(src, appLocale); // Trigger updating of schema and scrapers Scholar.Schema.updateSchema(); Scholar.Schema.updateScrapersRemote(); // Initialize integration web server Scholar.Integration.SOAP.init(); Scholar.Integration.init(); _initialized = true; return true; } function shutdown(subject, topic, data){ // Called twice otherwise, for some reason if (_shutdown){ return false; } _shutdown = true; Scholar.backupDatabase(); return true; } function getProfileDirectory(){ return Components.classes["@mozilla.org/file/directory_service;1"] .getService(Components.interfaces.nsIProperties) .get("ProfD", Components.interfaces.nsIFile); } function getScholarDirectory(){ var file = Scholar.getProfileDirectory(); file.append('zotero'); // If it doesn't exist, create if (!file.exists() || !file.isDirectory()){ file.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755); } return file; } function getStorageDirectory(){ var file = Scholar.getScholarDirectory(); file.append('storage'); // If it doesn't exist, create if (!file.exists() || !file.isDirectory()){ file.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755); } return file; } function getScholarDatabase(ext){ ext = ext ? '.' + ext : ''; var file = Scholar.getScholarDirectory(); file.append(SCHOLAR_CONFIG['DB_FILE'] + ext); return file; } /* * Back up the main database file * * This could probably create a corrupt file fairly easily if all changes * haven't been flushed to disk -- proceed with caution */ function backupDatabase(){ if (Scholar.DB.transactionInProgress()){ Scholar.debug('Transaction in progress--skipping DB backup', 2); return false; } Scholar.debug('Backing up database'); var file = Scholar.getScholarDatabase(); var backupFile = Scholar.getScholarDatabase('bak'); // Copy via a temporary file so we don't run into disk space issues // after deleting the old backup file var tmpFile = Scholar.getScholarDatabase('tmp'); if (tmpFile.exists()){ tmpFile.remove(null); } try { file.copyTo(file.parent, tmpFile.leafName); } catch (e){ // TODO: deal with low disk space throw (e); } // Remove old backup file if (backupFile.exists()){ backupFile.remove(null); } tmpFile.moveTo(tmpFile.parent, backupFile.leafName); return true; } /* * Debug logging function * * Uses DebugLogger extension available from http://mozmonkey.com/debuglogger/ * if available, otherwise the console (in which case boolean browser.dom.window.dump.enabled * must be created and set to true in about:config) * * Defaults to log level 3 if level not provided */ function debug(message, level) { if (!SCHOLAR_CONFIG['DEBUG_LOGGING']){ return false; } if (typeof message!='string'){ message = Scholar.varDump(message); } if (!level){ level = 3; } if (!SCHOLAR_CONFIG['DEBUG_TO_CONSOLE']){ try { var logManager = Components.classes["@mozmonkey.com/debuglogger/manager;1"] .getService(Components.interfaces.nsIDebugLoggerManager); var logger = logManager.registerLogger("Zotero"); } catch (e){} } if (logger){ logger.log(level, message); } else { dump('zotero(' + level + '): ' + message + "\n\n"); } return true; } /** * PHP var_dump equivalent for JS * * Adapted from http://binnyva.blogspot.com/2005/10/dump-function-javascript-equivalent-of.html */ function varDump(arr,level) { var dumped_text = ""; if (!level){ level = 0; } // The padding given at the beginning of the line. var level_padding = ""; for (var j=0;j function(...){...} \n"; } else { dumped_text += level_padding + "'" + item + "' => \"" + value + "\"\n"; } } } } else { // Stings/Chars/Numbers etc. dumped_text = "===>"+arr+"<===("+typeof(arr)+")"; } return dumped_text; } function getString(name){ try { var l10n = _localizedStringBundle.GetStringFromName(name); } catch (e){ throw ('Localized string not available for ' + name); } return l10n; } /* * 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 if (typeof args!='object' || args===null){ args = [args]; } var returns = new Array(); for (var i=0; i 12) { // swap day and month var tmp = date.day; date.day = date.month date.month = tmp; } if(date.month <= 12) { if(date.year < 100) { // for two digit years, determine proper // four digit year var today = new Date(); var year = today.getFullYear(); var twoDigitYear = year % 100; var century = year - twoDigitYear; if(date.year <= twoDigitYear) { // assume this date is from our century date.year = century + date.year; } else { // assume this date is from the previous century date.year = century - 100 + date.year; } } date.month--; // subtract one for JS style Scholar.debug("DATE: retrieved with algorithms: "+date.toSource()); return date; } } // couldn't use algorithms; use regexp // first, see if we have anything resembling a year var m = _yearRe.exec(string); if(m) { date.year = m[2]; date.part = m[1]+m[3]; Scholar.debug("DATE: got year ("+date.year+", "+date.part+")"); // get short month strings from CSL interpreter if(!months) { var months = Scholar.CSL.getMonthStrings("short"); } if(!_monthRe) { // then, see if have anything resembling a month anywhere _monthRe = new RegExp("^(.*)\\b("+months.join("|")+")[^ ]* (.*)$", "i"); } var m = _monthRe.exec(date.part); if(m) { date.month = months.indexOf(m[2][0].toUpperCase()+m[2].substr(1).toLowerCase()); date.part = m[1]+m[3]; Scholar.debug("DATE: got month ("+date.month+", "+date.part+")"); // then, see if there's a day if(!_dayRe) { var daySuffixes = Scholar.getString("date.daySuffixes").replace(/, ?/g, "|"); _dayRe = new RegExp("\\b([0-9]{1,2})(?:"+daySuffixes+")?\\b(.*)", "i"); } var m = _dayRe.exec(date.part); if(m) { date.day = parseInt(m[1], 10); if(m.index > 0) { date.part = date.part.substr(0, m.index); if(m[2]) { date.part += " "+m[2];; } } else { date.part = m[2]; } Scholar.debug("DATE: got day ("+date.day+", "+date.part+")"); } } } if(date.part) { date.part = date.part.replace(/^[^A-Za-z0-9]+/, "").replace(/[^A-Za-z0-9]+$/, ""); if(!date.part.length) { date.part = undefined; } } return date; } /* * does pretty formatting of a date object returned by strToDate() */ function formatDate(date) { var string = ""; if(date.part) { string += date.part+" "; } if(!months) { var months = Scholar.CSL.getMonthStrings("short"); } if(date.month != undefined && months[date.month]) { // get short month strings from CSL interpreter var months = Scholar.CSL.getMonthStrings("long"); string += months[date.month]; if(date.day) { string += " "+date.day+", "; } else { string += " "; } } if(date.year) { string += date.year; } return string; } function getFileDateString(file){ var date = new Date(); date.setTime(file.lastModifiedTime); return date.toLocaleDateString(); } function getFileTimeString(file){ var date = new Date(); date.setTime(file.lastModifiedTime); return date.toLocaleTimeString(); } } Scholar.Browser = new function() { this.createHiddenBrowser = createHiddenBrowser; this.deleteHiddenBrowser = deleteHiddenBrowser; function createHiddenBrowser(myWindow) { if(!myWindow) { var myWindow = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator) .getMostRecentWindow("navigator:browser"); } // Create a hidden browser var newHiddenBrowser = myWindow.document.createElement("browser"); myWindow.document.documentElement.appendChild(newHiddenBrowser); Scholar.debug("created hidden browser"); return newHiddenBrowser; } function deleteHiddenBrowser(myBrowser) { // Delete a hidden browser myBrowser.parentNode.removeChild(myBrowser); delete myBrowser; Scholar.debug("deleted hidden browser"); } }