From 1a90dbe179c030c27af02f03d0fa471cad190af4 Mon Sep 17 00:00:00 2001 From: foudfou Date: Mon, 11 Mar 2013 16:03:49 +0100 Subject: [PATCH] add support for Zotero app --- src/chrome.manifest | 1 + .../icons/hicolor/22x22/apps/zotero22.png | 1 + src/chrome/skin/zotero22.png | Bin 0 -> 1391 bytes src/install.rdf | 8 + src/modules/FiretrayHandler.jsm | 27 +- src/modules/commons.js | 5 + src/modules/linux/FiretrayStatusIcon.jsm | 10 +- src/modules/log4moz.js | 604 ++++++++++++++++++ src/modules/logging.jsm | 3 +- 9 files changed, 651 insertions(+), 8 deletions(-) create mode 120000 src/chrome/skin/linux/icons/hicolor/22x22/apps/zotero22.png create mode 100644 src/chrome/skin/zotero22.png create mode 100644 src/modules/log4moz.js diff --git a/src/chrome.manifest b/src/chrome.manifest index 92a9329..2ce3ab7 100644 --- a/src/chrome.manifest +++ b/src/chrome.manifest @@ -10,6 +10,7 @@ overlay chrome://songbird/content/xul/mainScriptsOverlay.xul chrome://firetray/c overlay chrome://sunbird/content/calendar.xul chrome://firetray/content/overlay.xul overlay chrome://navigator/content/navigator.xul chrome://firetray/content/overlay.xul overlay chrome://chatzilla/content/chatzilla.xul chrome://firetray/content/overlay.xul +overlay chrome://zotero/content/standalone/standalone.xul chrome://firetray/content/overlay.xul component {a9c9cc52-4d6c-45c2-a73f-0be1bd60aaa6} components/firetray-clhandler.js contract @mozilla.org/commandlinehandler/general-startup;1?type=firetray {a9c9cc52-4d6c-45c2-a73f-0be1bd60aaa6} diff --git a/src/chrome/skin/linux/icons/hicolor/22x22/apps/zotero22.png b/src/chrome/skin/linux/icons/hicolor/22x22/apps/zotero22.png new file mode 120000 index 0000000..395e145 --- /dev/null +++ b/src/chrome/skin/linux/icons/hicolor/22x22/apps/zotero22.png @@ -0,0 +1 @@ +../../../../../zotero22.png \ No newline at end of file diff --git a/src/chrome/skin/zotero22.png b/src/chrome/skin/zotero22.png new file mode 100644 index 0000000000000000000000000000000000000000..4d11372028e9b7c62de12492f226897f424357ab GIT binary patch literal 1391 zcmV-#1(5oQP)tR{*$p@gl`yk*im)_Rk7{W5@18~;&8q2a!N)ZGBI+>(d zuM<`(h(rRj4mN->=8#;JVm=G6tl?bh}vHF11|x(^@HI(LqI5KwJ200{DV^y{zVrc&5J zfa5rXVc5@qA8d?4YfT)-$SCU1qk5g%xpQRNZ7TIT1_%-fq@XIx>+Ua$SUNlkN4eEDOhR z9)qoQu#6(2UJtDlt&cy(DwU`;n@FVyjlmn-%4n;NxPBcRm)5pnYCCsgJdbc)u`%Yq zt3pN*VW)#?wTM<$XjH4j8__rp+Z>zZQVGYh5LzQ@HP#Xdf}x?lpeIrh$FYSm#Boe7 z2%uJ@)ojw0vfp_`5sS;qk2_Fnyum^IUlg%ZDID7dpc4dkA6zb%@myElym`~zRwyu7 zC{PC0!m!_AX7fJ_7_C7m3WWlUjIg-8Oto57^YimgAH2G{T9;D(wp=d1F+Mi->z{4i zno3MgGU$0kQew2m0Ajs*gb>(5U}Z8awcC`x{kHRu`T0*e-R_6W%gg`TEC7IKi^bum zMn<0BJ~Fa@IGcTI$hNbig#zhBg0R=a7=vruw1R-;dY#ocZr^XW|NcXza`~U7(ybp# zrIn5HeFD5;FV4>927LdS;e7thfqee=&+Obe@ItYOB?Mo6^9>87Qlr*r{Ov)t`hKm~ zoB!^{jmCd6eiXc6FTe6ia;?$$$;9aBTlswc55Di4J9qB + + + zotero@chnm.gmu.edu + 2.1a1 + 3.0.* + + + Jared Forsyth Glen Winters diff --git a/src/modules/FiretrayHandler.jsm b/src/modules/FiretrayHandler.jsm index 210ee6c..56eea32 100644 --- a/src/modules/FiretrayHandler.jsm +++ b/src/modules/FiretrayHandler.jsm @@ -472,11 +472,23 @@ firetray.VersionChangeHandler = { log.info("appId="+firetray.Handler.appId); if (firetray.Handler.appId === FIRETRAY_APP_DB['thunderbird']['id']) this.openMailTab(url); + else if (firetray.Handler.appId === FIRETRAY_APP_DB['firefox']['id'] || firetray.Handler.appId === FIRETRAY_APP_DB['seamonkey']['id']) this.openBrowserTab(url); - else - log.error("unsupported application"); + + else if (firetray.Handler.appId === FIRETRAY_APP_DB['zotero']['id']) { + let win = Services.wm.getMostRecentWindow("navigator:browser"); + if (!win) + log.error("Zotero main-window not found"); + else + win.openDialog("chrome://zotero/content/standalone/basicViewer.xul", + "basicViewer", + "chrome,resizable,centerscreen,menubar,scrollbars", url); + + } else { + this.openSystemBrowser(url); + } }, openMailTab: function(url) { @@ -514,6 +526,17 @@ firetray.VersionChangeHandler = { } }, + openSystemBrowser: function(url) { + try { + var uri = Services.io.newURI(url, null, null); + var handler = Cc['@mozilla.org/uriloader/external-protocol-service;1'] + .getService(Ci.nsIExternalProtocolService) + .getProtocolHandlerInfo('http'); + handler.preferredAction = Ci.nsIHandlerInfo.useSystemDefault; + handler.launchWithURI(uri, null); + } catch (e) {log.error(e);} + }, + tryEraseOldOptions: function() { let v03Options = [ "close_to_tray", "minimize_to_tray", "start_minimized", "confirm_exit", diff --git a/src/modules/commons.js b/src/modules/commons.js index b5bc865..7406848 100644 --- a/src/modules/commons.js +++ b/src/modules/commons.js @@ -80,6 +80,11 @@ const FIRETRAY_APP_DB = { chatzilla: { id: "{59c81df5-4b7a-477b-912d-4e0fdf64e5f2}", mainXUL: null + }, + + zotero: { + id: "zotero@chnm.gmu.edu", + mainXUL: null } }; diff --git a/src/modules/linux/FiretrayStatusIcon.jsm b/src/modules/linux/FiretrayStatusIcon.jsm index 05a745a..5378b5f 100644 --- a/src/modules/linux/FiretrayStatusIcon.jsm +++ b/src/modules/linux/FiretrayStatusIcon.jsm @@ -157,21 +157,21 @@ firetray.StatusIcon = { let direction = gdkEventScroll.contents.direction; switch(direction) { case gdk.GDK_SCROLL_UP: - log.debug("SCROLL UP"); + log.debug("SCROLL UP"); if (scroll_mode === "down_hides") firetray.Handler.showAllWindows(); else if (scroll_mode === "up_hides") firetray.Handler.hideAllWindows(); - break; + break; case gdk.GDK_SCROLL_DOWN: - log.debug("SCROLL DOWN"); + log.debug("SCROLL DOWN"); if (scroll_mode === "down_hides") firetray.Handler.hideAllWindows(); else if (scroll_mode === "up_hides") firetray.Handler.showAllWindows(); - break; + break; default: - log.error("SCROLL UNKNOWN"); + log.error("SCROLL UNKNOWN"); } }, diff --git a/src/modules/log4moz.js b/src/modules/log4moz.js new file mode 100644 index 0000000..5512cb5 --- /dev/null +++ b/src/modules/log4moz.js @@ -0,0 +1,604 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +this.EXPORTED_SYMBOLS = ['Log4Moz']; + +const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; + +const ONE_BYTE = 1; +const ONE_KILOBYTE = 1024 * ONE_BYTE; +const ONE_MEGABYTE = 1024 * ONE_KILOBYTE; + +const STREAM_SEGMENT_SIZE = 4096; +const PR_UINT32_MAX = 0xffffffff; + +Cu.import("resource://gre/modules/NetUtil.jsm"); +Cu.import("resource://gre/modules/FileUtils.jsm"); + +this.Log4Moz = { + Level: { + Fatal: 70, + Error: 60, + Warn: 50, + Info: 40, + Config: 30, + Debug: 20, + Trace: 10, + All: 0, + Desc: { + 70: "FATAL", + 60: "ERROR", + 50: "WARN", + 40: "INFO", + 30: "CONFIG", + 20: "DEBUG", + 10: "TRACE", + 0: "ALL" + } + }, + + get repository() { + delete Log4Moz.repository; + Log4Moz.repository = new LoggerRepository(); + return Log4Moz.repository; + }, + set repository(value) { + delete Log4Moz.repository; + Log4Moz.repository = value; + }, + + LogMessage: LogMessage, + Logger: Logger, + LoggerRepository: LoggerRepository, + + Formatter: Formatter, + BasicFormatter: BasicFormatter, + + Appender: Appender, + DumpAppender: DumpAppender, + ConsoleAppender: ConsoleAppender, + BlockingStreamAppender: BlockingStreamAppender, + StorageStreamAppender: StorageStreamAppender, + + // Discouraged due to blocking I/O. + FileAppender: FileAppender, + RotatingFileAppender: RotatingFileAppender, + + // Logging helper: + // let logger = Log4Moz.repository.getLogger("foo"); + // logger.info(Log4Moz.enumerateInterfaces(someObject).join(",")); + enumerateInterfaces: function Log4Moz_enumerateInterfaces(aObject) { + let interfaces = []; + + for (i in Ci) { + try { + aObject.QueryInterface(Ci[i]); + interfaces.push(i); + } + catch(ex) {} + } + + return interfaces; + }, + + // Logging helper: + // let logger = Log4Moz.repository.getLogger("foo"); + // logger.info(Log4Moz.enumerateProperties(someObject).join(",")); + enumerateProperties: function Log4Moz_enumerateProps(aObject, + aExcludeComplexTypes) { + let properties = []; + + for (p in aObject) { + try { + if (aExcludeComplexTypes && + (typeof aObject[p] == "object" || typeof aObject[p] == "function")) + continue; + properties.push(p + " = " + aObject[p]); + } + catch(ex) { + properties.push(p + " = " + ex); + } + } + + return properties; + } +}; + + +/* + * LogMessage + * Encapsulates a single log event's data + */ +function LogMessage(loggerName, level, message){ + this.loggerName = loggerName; + this.message = message; + this.level = level; + this.time = Date.now(); +} +LogMessage.prototype = { + get levelDesc() { + if (this.level in Log4Moz.Level.Desc) + return Log4Moz.Level.Desc[this.level]; + return "UNKNOWN"; + }, + + toString: function LogMsg_toString(){ + return "LogMessage [" + this.time + " " + this.level + " " + + this.message + "]"; + } +}; + +/* + * Logger + * Hierarchical version. Logs to all appenders, assigned or inherited + */ + +function Logger(name, repository) { + if (!repository) + repository = Log4Moz.repository; + this._name = name; + this.children = []; + this.ownAppenders = []; + this.appenders = []; + this._repository = repository; +} +Logger.prototype = { + get name() { + return this._name; + }, + + _level: null, + get level() { + if (this._level != null) + return this._level; + if (this.parent) + return this.parent.level; + dump("log4moz warning: root logger configuration error: no level defined\n"); + return Log4Moz.Level.All; + }, + set level(level) { + this._level = level; + }, + + _parent: null, + get parent() this._parent, + set parent(parent) { + if (this._parent == parent) { + return; + } + // Remove ourselves from parent's children + if (this._parent) { + let index = this._parent.children.indexOf(this); + if (index != -1) { + this._parent.children.splice(index, 1); + } + } + this._parent = parent; + parent.children.push(this); + this.updateAppenders(); + }, + + updateAppenders: function updateAppenders() { + if (this._parent) { + let notOwnAppenders = this._parent.appenders.filter(function(appender) { + return this.ownAppenders.indexOf(appender) == -1; + }, this); + this.appenders = notOwnAppenders.concat(this.ownAppenders); + } else { + this.appenders = this.ownAppenders.slice(); + } + + // Update children's appenders. + for (let i = 0; i < this.children.length; i++) { + this.children[i].updateAppenders(); + } + }, + + addAppender: function Logger_addAppender(appender) { + if (this.ownAppenders.indexOf(appender) != -1) { + return; + } + this.ownAppenders.push(appender); + this.updateAppenders(); + }, + + removeAppender: function Logger_removeAppender(appender) { + let index = this.ownAppenders.indexOf(appender); + if (index == -1) { + return; + } + this.ownAppenders.splice(index, 1); + this.updateAppenders(); + }, + + log: function Logger_log(level, string) { + if (this.level > level) + return; + + // Hold off on creating the message object until we actually have + // an appender that's responsible. + let message; + let appenders = this.appenders; + for (let i = 0; i < appenders.length; i++){ + let appender = appenders[i]; + if (appender.level > level) + continue; + + if (!message) + message = new LogMessage(this._name, level, string); + + appender.append(message); + } + }, + + fatal: function Logger_fatal(string) { + this.log(Log4Moz.Level.Fatal, string); + }, + error: function Logger_error(string) { + this.log(Log4Moz.Level.Error, string); + }, + warn: function Logger_warn(string) { + this.log(Log4Moz.Level.Warn, string); + }, + info: function Logger_info(string) { + this.log(Log4Moz.Level.Info, string); + }, + config: function Logger_config(string) { + this.log(Log4Moz.Level.Config, string); + }, + debug: function Logger_debug(string) { + this.log(Log4Moz.Level.Debug, string); + }, + trace: function Logger_trace(string) { + this.log(Log4Moz.Level.Trace, string); + } +}; + +/* + * LoggerRepository + * Implements a hierarchy of Loggers + */ + +function LoggerRepository() {} +LoggerRepository.prototype = { + _loggers: {}, + + _rootLogger: null, + get rootLogger() { + if (!this._rootLogger) { + this._rootLogger = new Logger("root", this); + this._rootLogger.level = Log4Moz.Level.All; + } + return this._rootLogger; + }, + set rootLogger(logger) { + throw "Cannot change the root logger"; + }, + + _updateParents: function LogRep__updateParents(name) { + let pieces = name.split('.'); + let cur, parent; + + // find the closest parent + // don't test for the logger name itself, as there's a chance it's already + // there in this._loggers + for (let i = 0; i < pieces.length - 1; i++) { + if (cur) + cur += '.' + pieces[i]; + else + cur = pieces[i]; + if (cur in this._loggers) + parent = cur; + } + + // if we didn't assign a parent above, there is no parent + if (!parent) + this._loggers[name].parent = this.rootLogger; + else + this._loggers[name].parent = this._loggers[parent]; + + // trigger updates for any possible descendants of this logger + for (let logger in this._loggers) { + if (logger != name && logger.indexOf(name) == 0) + this._updateParents(logger); + } + }, + + getLogger: function LogRep_getLogger(name) { + if (name in this._loggers) + return this._loggers[name]; + this._loggers[name] = new Logger(name, this); + this._updateParents(name); + return this._loggers[name]; + } +}; + +/* + * Formatters + * These massage a LogMessage into whatever output is desired + * Only the BasicFormatter is currently implemented + */ + +// Abstract formatter +function Formatter() {} +Formatter.prototype = { + format: function Formatter_format(message) {} +}; + +// Basic formatter that doesn't do anything fancy +function BasicFormatter(dateFormat) { + if (dateFormat) + this.dateFormat = dateFormat; +} +BasicFormatter.prototype = { + __proto__: Formatter.prototype, + + format: function BF_format(message) { + return message.time + "\t" + message.loggerName + "\t" + message.levelDesc + + "\t" + message.message + "\n"; + } +}; + +/* + * Appenders + * These can be attached to Loggers to log to different places + * Simply subclass and override doAppend to implement a new one + */ + +function Appender(formatter) { + this._name = "Appender"; + this._formatter = formatter? formatter : new BasicFormatter(); +} +Appender.prototype = { + level: Log4Moz.Level.All, + + append: function App_append(message) { + this.doAppend(this._formatter.format(message)); + }, + toString: function App_toString() { + return this._name + " [level=" + this._level + + ", formatter=" + this._formatter + "]"; + }, + doAppend: function App_doAppend(message) {} +}; + +/* + * DumpAppender + * Logs to standard out + */ + +function DumpAppender(formatter) { + this._name = "DumpAppender"; + Appender.call(this, formatter); +} +DumpAppender.prototype = { + __proto__: Appender.prototype, + + doAppend: function DApp_doAppend(message) { + dump(message); + } +}; + +/* + * ConsoleAppender + * Logs to the javascript console + */ + +function ConsoleAppender(formatter) { + this._name = "ConsoleAppender"; + Appender.call(this, formatter); +} +ConsoleAppender.prototype = { + __proto__: Appender.prototype, + + doAppend: function CApp_doAppend(message) { + if (message.level > Log4Moz.Level.Warn) { + Cu.reportError(message); + return; + } + Cc["@mozilla.org/consoleservice;1"]. + getService(Ci.nsIConsoleService).logStringMessage(message); + } +}; + +/** + * Base implementation for stream based appenders. + * + * Caution: This writes to the output stream synchronously, thus logging calls + * block as the data is written to the stream. This can have negligible impact + * for in-memory streams, but should be taken into account for I/O streams + * (files, network, etc.) + */ +function BlockingStreamAppender(formatter) { + this._name = "BlockingStreamAppender"; + Appender.call(this, formatter); +} +BlockingStreamAppender.prototype = { + __proto__: Appender.prototype, + + _converterStream: null, // holds the nsIConverterOutputStream + _outputStream: null, // holds the underlying nsIOutputStream + + /** + * Output stream to write to. + * + * This will automatically open the stream if it doesn't exist yet by + * calling newOutputStream. The resulting raw stream is wrapped in a + * nsIConverterOutputStream to ensure text is written as UTF-8. + */ + get outputStream() { + if (!this._outputStream) { + // First create a raw stream. We can bail out early if that fails. + this._outputStream = this.newOutputStream(); + if (!this._outputStream) { + return null; + } + + // Wrap the raw stream in an nsIConverterOutputStream. We can reuse + // the instance if we already have one. + if (!this._converterStream) { + this._converterStream = Cc["@mozilla.org/intl/converter-output-stream;1"] + .createInstance(Ci.nsIConverterOutputStream); + } + this._converterStream.init( + this._outputStream, "UTF-8", STREAM_SEGMENT_SIZE, + Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + } + return this._converterStream; + }, + + newOutputStream: function newOutputStream() { + throw "Stream-based appenders need to implement newOutputStream()!"; + }, + + reset: function reset() { + if (!this._outputStream) { + return; + } + this.outputStream.close(); + this._outputStream = null; + }, + + doAppend: function doAppend(message) { + if (!message) { + return; + } + try { + this.outputStream.writeString(message); + } catch(ex) { + if (ex.result == Cr.NS_BASE_STREAM_CLOSED) { + // The underlying output stream is closed, so let's open a new one + // and try again. + this._outputStream = null; + try { + this.outputStream.writeString(message); + } catch (ex) { + // Ah well, we tried, but something seems to be hosed permanently. + } + } + } + } +}; + +/** + * Append to an nsIStorageStream + * + * This writes logging output to an in-memory stream which can later be read + * back as an nsIInputStream. It can be used to avoid expensive I/O operations + * during logging. Instead, one can periodically consume the input stream and + * e.g. write it to disk asynchronously. + */ +function StorageStreamAppender(formatter) { + this._name = "StorageStreamAppender"; + BlockingStreamAppender.call(this, formatter); +} +StorageStreamAppender.prototype = { + __proto__: BlockingStreamAppender.prototype, + + _ss: null, + newOutputStream: function newOutputStream() { + let ss = this._ss = Cc["@mozilla.org/storagestream;1"] + .createInstance(Ci.nsIStorageStream); + ss.init(STREAM_SEGMENT_SIZE, PR_UINT32_MAX, null); + return ss.getOutputStream(0); + }, + + getInputStream: function getInputStream() { + if (!this._ss) { + return null; + } + return this._ss.newInputStream(0); + }, + + reset: function reset() { + BlockingStreamAppender.prototype.reset.call(this); + this._ss = null; + } +}; + +/** + * File appender (discouraged) + * + * Writes otuput to a file using a regular nsIFileOutputStream (as opposed + * to nsISafeFileOutputStream, since immediate durability is typically not + * needed for logs.) Note that I/O operations block the logging caller. + */ +function FileAppender(file, formatter) { + this._name = "FileAppender"; + this._file = file; // nsIFile + BlockingStreamAppender.call(this, formatter); +} +FileAppender.prototype = { + __proto__: BlockingStreamAppender.prototype, + + newOutputStream: function newOutputStream() { + try { + return FileUtils.openFileOutputStream(this._file); + } catch(e) { + return null; + } + }, + + reset: function reset() { + BlockingStreamAppender.prototype.reset.call(this); + try { + this._file.remove(false); + } catch (e) { + // File didn't exist in the first place, or we're on Windows. Meh. + } + } +}; + +/** + * Rotating file appender (discouraged) + * + * Similar to FileAppender, but rotates logs when they become too large. + */ +function RotatingFileAppender(file, formatter, maxSize, maxBackups) { + if (maxSize === undefined) + maxSize = ONE_MEGABYTE * 2; + + if (maxBackups === undefined) + maxBackups = 0; + + this._name = "RotatingFileAppender"; + FileAppender.call(this, file, formatter); + this._maxSize = maxSize; + this._maxBackups = maxBackups; +} +RotatingFileAppender.prototype = { + __proto__: FileAppender.prototype, + + doAppend: function doAppend(message) { + FileAppender.prototype.doAppend.call(this, message); + try { + this.rotateLogs(); + } catch(e) { + dump("Error writing file:" + e + "\n"); + } + }, + + rotateLogs: function rotateLogs() { + if (this._file.exists() && this._file.fileSize < this._maxSize) { + return; + } + + BlockingStreamAppender.prototype.reset.call(this); + + for (let i = this.maxBackups - 1; i > 0; i--) { + let backup = this._file.parent.clone(); + backup.append(this._file.leafName + "." + i); + if (backup.exists()) { + backup.moveTo(this._file.parent, this._file.leafName + "." + (i + 1)); + } + } + + let cur = this._file.clone(); + if (cur.exists()) { + cur.moveTo(cur.parent, cur.leafName + ".1"); + } + + // Note: this._file still points to the same file + } +}; diff --git a/src/modules/logging.jsm b/src/modules/logging.jsm index ed111ce..63b02ec 100644 --- a/src/modules/logging.jsm +++ b/src/modules/logging.jsm @@ -54,7 +54,8 @@ firetray.Logging = { if (this.initialized) return; ["resource://services-common/log4moz.js", // FF - "resource:///modules/gloda/log4moz.js"] // TB + "resource:///modules/gloda/log4moz.js", // TB + "resource://firetray/log4moz.js"] // default .forEach(function(file){ try {Cu.import(file);} catch(x) {} }, this);