зеркало из https://github.com/mozilla/snowl.git
340 строки
11 KiB
JavaScript
340 строки
11 KiB
JavaScript
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Snowl.
|
|
*
|
|
* The Initial Developer of the Original Code is Mozilla.
|
|
* Portions created by the Initial Developer are Copyright (C) 2008
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Myk Melez <myk@mozilla.org>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
const EXPORTED_SYMBOLS = ["SnowlService"];
|
|
|
|
const Cc = Components.classes;
|
|
const Ci = Components.interfaces;
|
|
const Cr = Components.results;
|
|
const Cu = Components.utils;
|
|
|
|
// modules that come with Firefox
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
// modules that are generic
|
|
Cu.import("resource://snowl/modules/log4moz.js");
|
|
Cu.import("resource://snowl/modules/Observers.js");
|
|
Cu.import("resource://snowl/modules/Preferences.js");
|
|
Cu.import("resource://snowl/modules/URI.js");
|
|
|
|
// modules that are Snowl-specific
|
|
Cu.import("resource://snowl/modules/datastore.js");
|
|
Cu.import("resource://snowl/modules/source.js");
|
|
Cu.import("resource://snowl/modules/target.js");
|
|
Cu.import("resource://snowl/modules/utils.js");
|
|
|
|
const PERMS_FILE = 0644;
|
|
const PERMS_DIRECTORY = 0755;
|
|
|
|
const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
|
|
const SNOWL_HANDLER_URI = "chrome://snowl/content/subscribe.xul?feed=%s";
|
|
const SNOWL_HANDLER_TITLE = "Snowl";
|
|
|
|
// How often to check if sources need refreshing, in milliseconds.
|
|
const REFRESH_CHECK_INTERVAL = 60 * 1000; // 60 seconds
|
|
|
|
let SnowlService = {
|
|
get gBrowserWindow() {
|
|
let wm = Cc["@mozilla.org/appshell/window-mediator;1"].
|
|
getService(Ci.nsIWindowMediator);
|
|
delete this._gBrowserWindow;
|
|
return this._gBrowserWindow = wm.getMostRecentWindow("navigator:browser");
|
|
},
|
|
|
|
get _prefs() {
|
|
delete this._prefs;
|
|
return this._prefs = new Preferences("extensions.snowl.");
|
|
},
|
|
|
|
get _dirSvc() {
|
|
let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
|
|
getService(Ci.nsIProperties);
|
|
this.__defineGetter__("_dirSvc", function() { return dirSvc });
|
|
return this._dirSvc;
|
|
},
|
|
|
|
get _converterSvc() {
|
|
let converterSvc =
|
|
Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
|
|
getService(Ci.nsIWebContentConverterService);
|
|
this.__defineGetter__("_converterSvc", function() { return converterSvc });
|
|
return this._converterSvc;
|
|
},
|
|
|
|
get _promptSvc() {
|
|
let promptSvc =
|
|
Cc["@mozilla.org/embedcomp/prompt-service;1"].
|
|
getService(Ci.nsIPromptService);
|
|
this.__defineGetter__("_promptSvc", function() { return promptSvc });
|
|
return this._promptSvc;
|
|
},
|
|
|
|
_log: null,
|
|
|
|
_init: function() {
|
|
this._initLogging();
|
|
this._registerFeedHandler();
|
|
this._initTimer();
|
|
|
|
Observers.add("snowl:source:added", this.onSourcesChanged, this);
|
|
Observers.add("snowl:source:removed", this.onSourcesChanged, this);
|
|
|
|
// FIXME: refresh stale sources on startup in a way that doesn't hang
|
|
// the UI thread.
|
|
//this.refreshStaleSources();
|
|
},
|
|
|
|
_timer: null,
|
|
_initTimer: function() {
|
|
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
let callback = {
|
|
_svc: this,
|
|
notify: function(aTimer) { this._svc.refreshStaleSources() }
|
|
};
|
|
this._timer.initWithCallback(callback,
|
|
REFRESH_CHECK_INTERVAL,
|
|
Ci.nsITimer.TYPE_REPEATING_SLACK);
|
|
},
|
|
|
|
_initLogging: function() {
|
|
let root = Log4Moz.repository.rootLogger;
|
|
root.level = Log4Moz.Level[this._prefs.get("log.logger.root.level")];
|
|
|
|
let formatter = new Log4Moz.BasicFormatter();
|
|
|
|
let capp = new Log4Moz.ConsoleAppender(formatter);
|
|
capp.level = Log4Moz.Level[this._prefs.get("log.appender.console.level")];
|
|
root.addAppender(capp);
|
|
|
|
let dapp = new Log4Moz.DumpAppender(formatter);
|
|
dapp.level = Log4Moz.Level[this._prefs.get("log.appender.dump.level")];
|
|
root.addAppender(dapp);
|
|
|
|
this._log = Log4Moz.repository.getLogger("Snowl.Service");
|
|
this._log.info("initialized logging");
|
|
},
|
|
|
|
_registerFeedHandler: function() {
|
|
if (this._converterSvc.getWebContentHandlerByURI(TYPE_MAYBE_FEED, SNOWL_HANDLER_URI))
|
|
return;
|
|
|
|
this._converterSvc.registerContentHandler(TYPE_MAYBE_FEED,
|
|
SNOWL_HANDLER_URI,
|
|
SNOWL_HANDLER_TITLE,
|
|
null);
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// Event & Notification Handlers
|
|
|
|
onSourcesChanged: function() {
|
|
// Invalidate the cache of sources indexed by ID.
|
|
this._sourcesByID = null;
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// Accounts, Sources, Targets
|
|
|
|
_accountTypeConstructors: {},
|
|
addAccountType: function(constructor) {
|
|
if (constructor in this._accountTypeConstructors)
|
|
this._log.warn("constructor for " + constructor.name +
|
|
"already exists");
|
|
this._accountTypeConstructors[constructor.name] = constructor;
|
|
},
|
|
|
|
_constructAccount: function(row) {
|
|
let constructor = this._accountTypeConstructors[row.type];
|
|
if (!constructor)
|
|
throw "no constructor for type " + row.type;
|
|
|
|
return new constructor(row.id,
|
|
row.name,
|
|
URI.get(row.machineURI),
|
|
URI.get(row.humanURI),
|
|
row.username,
|
|
SnowlDateUtils.julianToJSDate(row.lastRefreshed),
|
|
row.importance,
|
|
row.placeID);
|
|
},
|
|
|
|
get _getAccountStatement() {
|
|
delete this._getAccountStatement;
|
|
return this._getAccountStatement = SnowlDatastore.createStatement(
|
|
"SELECT id, type, name, machineURI, humanURI, username, lastRefreshed, importance, placeID " +
|
|
"FROM sources WHERE id = :id"
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Get the account with the given ID.
|
|
*
|
|
* @param id {integer} the ID of the account to retrieve
|
|
*/
|
|
getAccount: function(id) {
|
|
let account = null;
|
|
|
|
try {
|
|
this._getAccountStatement.params.id = id;
|
|
if (this._getAccountStatement.step())
|
|
account = this._constructAccount(this._getAccountStatement.row);
|
|
}
|
|
finally {
|
|
this._getAccountStatement.reset();
|
|
}
|
|
|
|
return account;
|
|
},
|
|
|
|
get _accountsStatement() {
|
|
delete this._accountsStatement;
|
|
return this._accountsStatement = SnowlDatastore.createStatement(
|
|
"SELECT id, type, name, machineURI, humanURI, username, lastRefreshed, importance, placeID " +
|
|
"FROM sources"
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Get all accounts.
|
|
*/
|
|
get accounts() {
|
|
let accounts = [];
|
|
|
|
try {
|
|
while (this._accountsStatement.step()) {
|
|
try {
|
|
accounts.push(this._constructAccount(this._accountsStatement.row));
|
|
}
|
|
catch(ex) {
|
|
this._log.error(ex);
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
this._accountsStatement.reset();
|
|
}
|
|
|
|
return accounts;
|
|
},
|
|
|
|
get sources() {
|
|
return this.accounts.filter(function(acct) acct.implements(SnowlSource));
|
|
},
|
|
|
|
_sourcesByID: null,
|
|
get sourcesByID() {
|
|
if (!this._sourcesByID) {
|
|
this._sourcesByID = {};
|
|
for each (let source in this.sources)
|
|
this._sourcesByID[source.id] = source;
|
|
}
|
|
|
|
return this._sourcesByID;
|
|
},
|
|
|
|
get targets() {
|
|
return this.accounts.filter(function(acct) acct.implements(SnowlTarget));
|
|
},
|
|
|
|
refreshStaleSources: function() {
|
|
this._log.info("refreshing stale sources");
|
|
|
|
let now = new Date();
|
|
let staleSources = [];
|
|
for each (let source in this.sources)
|
|
if (now - source.lastRefreshed > source.refreshInterval)
|
|
staleSources.push(source);
|
|
this.refreshAllSources(staleSources);
|
|
},
|
|
|
|
refreshAllSources: function(sources) {
|
|
let allSources = sources ? sources : this.sources;
|
|
// We specify the same refresh time when refreshing sources so that all
|
|
// new messages have the same received time, which makes messages sorted by
|
|
// received, then published times look better (more mixed together by
|
|
// publication time, not clumped up by source based on the received time)
|
|
// when retrieved in the same refresh (f.e. when the user starts their
|
|
// browser in the morning after leaving it off overnight).
|
|
let refreshTime = new Date();
|
|
for each (let source in allSources) {
|
|
this._log.info("refreshing source " + source.name);
|
|
source.refresh(refreshTime);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Determine whether or not the datastore contains the message with the given ID.
|
|
*
|
|
* @param aExternalID {string} the external ID of the message
|
|
*
|
|
* @returns {boolean} whether or not the datastore contains the message
|
|
*/
|
|
hasMessage: function(aExternalID) {
|
|
return SnowlDatastore.selectHasMessage(aExternalID);
|
|
},
|
|
|
|
/**
|
|
* Determine whether or not the datastore contains a source with the given URI.
|
|
*
|
|
* @param aMachineURI {string} the URI to check
|
|
*
|
|
* @returns {string} the name of the source if found otherwise null
|
|
*/
|
|
hasSource: function(aMachineURI) {
|
|
return SnowlDatastore.selectHasSource(aMachineURI);
|
|
},
|
|
|
|
/**
|
|
* Determine whether or not the datastore contains a source with the given URI
|
|
* also username for multiple subscriptions per URI. Use hasSource() for URI
|
|
* only check.
|
|
*
|
|
* @param aMachineURI {string} the URI to check
|
|
* @param aUsername {string} the username to check
|
|
*
|
|
* @returns {object} the [name, username] of the source and username if found
|
|
* otherwise nulls
|
|
*/
|
|
hasSourceUsername: function(aMachineURI, aUsername) {
|
|
return SnowlDatastore.selectHasSourceUsername(aMachineURI, aUsername);
|
|
}
|
|
|
|
};
|
|
|
|
SnowlService._init();
|