pjs/mail/extensions/newsblog/js/newsblog.js

561 строка
21 KiB
JavaScript
Executable File

/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* ***** 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 the News&Blog Feed Downloader
*
* The Initial Developer of the Original Code is
* The Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2004
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Myk Melez <myk@mozilla.org) (Original Author)
* David Bienvenu <bienvenu@nventure.com>
*
* 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 ***** */
var gExternalScriptsLoaded = false;
var nsNewsBlogFeedDownloader =
{
downloadFeed: function(aUrl, aFolder, aQuickMode, aTitle, aUrlListener, aMsgWindow)
{
if (!gExternalScriptsLoaded)
loadScripts();
// we don't yet support the ability to check for new articles while we are in the middle of
// subscribing to a feed. For now, abort the check for new feeds.
if (progressNotifier.mSubscribeMode)
{
debug('Aborting RSS New Mail Check. Feed subscription in progress\n');
return;
}
// if folder seems to have lost its feeds, look in DS for feeds.
if (!aUrl.length)
{
var ds = getSubscriptionsDS(aFolder.server);
var enumerator = ds.GetSources(FZ_DESTFOLDER, aFolder, true);
var concatenatedUris = "";
while (enumerator.hasMoreElements())
{
var containerArc = enumerator.getNext();
var uri = containerArc.QueryInterface(Components.interfaces.nsIRDFResource).Value;
if (concatenatedUris.length > 0)
concatenatedUris += "|";
concatenatedUris += uri;
}
if (concatenatedUris.length > 0)
{
aUrl = concatenatedUris;
try
{
var msgdb = aFolder.getMsgDatabase(null);
var folderInfo = msgdb.dBFolderInfo;
folderInfo.setCharPtrProperty("feedUrl", concatenatedUris);
}
catch (ex) {dump(ex);}
}
}
// aUrl may be a delimited list of feeds for a particular folder. We need to kick off a download
// for each feed.
var feedUrlArray = aUrl.split("|");
// we might just pull all these args out of the aFolder DB, instead of passing them in...
var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"]
.getService(Components.interfaces.nsIRDFService);
progressNotifier.init(aMsgWindow, false);
for (url in feedUrlArray)
{
if (feedUrlArray[url])
{
id = rdf.GetResource(feedUrlArray[url]);
feed = new Feed(id, aFolder.server);
feed.folder = aFolder;
gNumPendingFeedDownloads++; // bump our pending feed download count
feed.download(true, progressNotifier);
}
}
},
subscribeToFeed: function(aUrl, aFolder, aMsgWindow)
{
if (!gExternalScriptsLoaded)
loadScripts();
// we don't support the ability to subscribe to several feeds at once yet...
// for now, abort the subscription if we are already in the middle of subscribing to a feed
// via drag and drop.
if (gNumPendingFeedDownloads)
{
debug('Aborting RSS subscription. Feed downloads already in progress\n');
return;
}
// if aFolder is null, then use the root folder for the first RSS account
if (!aFolder)
{
var accountManager = Components.classes["@mozilla.org/messenger/account-manager;1"]
.getService(Components.interfaces.nsIMsgAccountManager);
var allServers = accountManager.allServers;
for (var i=0; i< allServers.Count() && !aFolder; i++)
{
var currentServer = allServers.GetElementAt(i).QueryInterface(Components.interfaces.nsIMsgIncomingServer);
if (currentServer && currentServer.type == 'rss')
aFolder = currentServer.rootFolder;
}
}
// What do we do if the user hasn't created an RSS account yet?
// for now, fall out, would be nice if we could force RSS account creation
if (!aFolder)
return;
// if aUrl is a feed url, then it is of the form: feed:http://somesite/feed.xml or
// feed://http://somesite/feed.xml
// Strip off the feed: part so we can subscribe to the contained URL.
if (/^feed:/i.test(aUrl))
{
aUrl = aUrl.replace(/^feed:/i, '');
// Strip off the optional forward slashes if we were given feed://
aUrl = aUrl.replace(/^\x2f\x2f/, '');
}
// make sure we aren't already subscribed to this feed before we attempt to subscribe to it.
if (feedAlreadyExists(aUrl, aFolder.server))
{
aMsgWindow.statusFeedback.showStatusString(GetNewsBlogStringBundle().GetStringFromName('subscribe-feedAlreadySubscribed'));
return;
}
var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"]
.getService(Components.interfaces.nsIRDFService);
var itemResource = rdf.GetResource(aUrl);
var feed = new Feed(itemResource, aFolder.server);
feed.quickMode = feed.server.getBoolAttribute('quickMode');
if (!aFolder.isServer) // if the root server, create a new folder for the feed
feed.folder = aFolder; // user must want us to add this subscription url to an existing RSS folder.
progressNotifier.init(aMsgWindow, true);
gNumPendingFeedDownloads++;
feed.download(true, progressNotifier);
},
updateSubscriptionsDS: function(aFolder, aUnsubscribe)
{
if (!gExternalScriptsLoaded)
loadScripts();
// an rss folder was just renamed...we need to update our feed data source
var msgdb = aFolder.QueryInterface(Components.interfaces.nsIMsgFolder).getMsgDatabase(null);
var folderInfo = msgdb.dBFolderInfo;
var feedurls = folderInfo.getCharPtrProperty("feedUrl");
var feedUrlArray = feedurls.split("|");
var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
var ds = getSubscriptionsDS(aFolder.server);
for (url in feedUrlArray)
{
if (feedUrlArray[url])
{
id = rdf.GetResource(feedUrlArray[url]);
// get the node for the current folder URI
var node = ds.GetTarget(id, FZ_DESTFOLDER, true);
// we need to check and see if the folder is a child of the trash...if it is, then we can
// treat this as an unsubscribe action
if (aUnsubscribe)
{
var feeds = getSubscriptionsList(aFolder.server);
var index = feeds.IndexOf(id);
if (index != -1)
feeds.RemoveElementAt(index, false);
removeAssertions(ds, id);
}
else
ds.Change(id, FZ_DESTFOLDER, node, rdf.GetResource(aFolder.URI));
}
} // for each feed url in the folder property
ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource).Flush(); // flush any changes
},
QueryInterface: function(aIID)
{
if (aIID.equals(Components.interfaces.nsINewsBlogFeedDownloader) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
return null;
}
}
var nsNewsBlogAcctMgrExtension =
{
name: "newsblog",
chromePackageName: "messenger-newsblog",
showPanel: function (server)
{
return server.type == "rss";
},
QueryInterface: function(aIID)
{
if (aIID.equals(Components.interfaces.nsIMsgAccountManagerExtension) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
return null;
}
}
var nsFeedCommandLineHandler =
{
/* nsISupports */
QueryInterface : function(aIID)
{
if (!aIID.equals(Components.interfaces.nsISupports) &&
!aIID.equals(Components.interfaces.nsICommandLineHandler))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
},
/* nsICommandLineHandler */
handle : function(cmdLine)
{
// we only care about "-mail someurl" where someurl is a feed: url
// we also don't want to remove the parameter in case we don't end up handling it...
var mailPos = cmdLine.findFlag("mail", false);
if (mailPos != -1 && cmdLine.length >= mailPos )
{
var uriStr = cmdLine.getArgument(mailPos + 1);
if (/^feed:/i.test(uriStr))
{
var mailWindow = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService()
.QueryInterface(Components.interfaces.nsIWindowMediator).getMostRecentWindow("mail:3pane");
// if we don't have a 3 pane window visible already, then we can optimize and do nothing here,
// that will let the default command line handler create a 3pane window for us using the feed
// URL as an argument to that window when it gets constructed...so we only care about the
// case where we want to re-use an existing 3 pane to subscribe to the feed url
if (mailWindow)
{
cmdLine.handleFlagWithParam("mail", false); // eat up the arguments we are now handling
cmdLine.preventDefault = true; // prevent the default cmd line handler from doing anything
var feedHandler = Components.classes["@mozilla.org/newsblog-feed-downloader;1"].getService(Components.interfaces.nsINewsBlogFeedDownloader);
if (feedHandler)
feedHandler.subscribeToFeed(uriStr, null, mailWindow.msgWindow);
}
}
}
},
helpInfo : ""
};
var nsNewsBlogFeedDownloaderModule =
{
getClassObject: function(aCompMgr, aCID, aIID)
{
if (!aIID.equals(Components.interfaces.nsIFactory))
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
for (var key in this.mObjects)
if (aCID.equals(this.mObjects[key].CID))
return this.mObjects[key].factory;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
mObjects:
{
feedDownloader:
{
CID: Components.ID("{5c124537-adca-4456-b2b5-641ab687d1f6}"),
contractID: "@mozilla.org/newsblog-feed-downloader;1",
className: "News+Blog Feed Downloader",
factory:
{
createInstance: function (aOuter, aIID)
{
if (aOuter != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
if (!aIID.equals(Components.interfaces.nsINewsBlogFeedDownloader) &&
!aIID.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_INVALID_ARG;
// return the singleton
return nsNewsBlogFeedDownloader.QueryInterface(aIID);
}
} // factory
}, // feed downloader
nsNewsBlogAcctMgrExtension:
{
CID: Components.ID("{E109C05F-D304-4ca5-8C44-6DE1BFAF1F74}"),
contractID: "@mozilla.org/accountmanager/extension;1?name=newsblog",
className: "News+Blog Account Manager Extension",
factory:
{
createInstance: function (aOuter, aIID)
{
if (aOuter != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
if (!aIID.equals(Components.interfaces.nsIMsgAccountManagerExtension) &&
!aIID.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_INVALID_ARG;
// return the singleton
return nsNewsBlogAcctMgrExtension.QueryInterface(aIID);
}
} // factory
}, // account manager extension
nsFeedCommandLineHandler:
{
CID: Components.ID("{0E377BF7-E4FE-4c94-804C-0C33D49F883E}"),
contractID: "@mozilla.org/newsblog-feed-downloader/clh;1",
className: "Feed CommandLine Handler",
factory:
{
createInstance: function (aOuter, aIID)
{
if (aOuter != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
if (!aIID.equals(Components.interfaces.nsICommandLineHandler) &&
!aIID.equals(Components.interfaces.nsISupports))
throw Components.results.NS_ERROR_INVALID_ARG;
// return the singleton
return nsFeedCommandLineHandler.QueryInterface(aIID);
}
} // factory
}
},
registerSelf: function(aCompMgr, aFileSpec, aLocation, aType)
{
aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
for (var key in this.mObjects)
{
var obj = this.mObjects[key];
aCompMgr.registerFactoryLocation(obj.CID, obj.className, obj.contractID, aFileSpec, aLocation, aType);
}
// we also need to do special account extension registration
var catman = Components.classes["@mozilla.org/categorymanager;1"].getService(Components.interfaces.nsICategoryManager);
catman.addCategoryEntry("mailnews-accountmanager-extensions",
"newsblog account manager extension",
"@mozilla.org/accountmanager/extension;1?name=newsblog", true, true);
catman.addCategoryEntry("command-line-handler",
"l-feed",
"@mozilla.org/newsblog-feed-downloader/clh;1", true, true);
},
unregisterSelf: function(aCompMgr, aFileSpec, aLocation)
{
aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
for (var key in this.mObjects)
{
var obj = this.mObjects[key];
aCompMgr.unregisterFactoryLocation(obj.CID, aFileSpec);
}
// unregister the account manager extension
catman = Components.classes["@mozilla.org/categorymanager;1"].getService(Components.interfaces.nsICategoryManager);
catman.deleteCategoryEntry("mailnews-accountmanager-extensions",
"@mozilla.org/accountmanager/extension;1?name=newsblog", true);
catMan.addCategoryEntry("command-line-handler",
"@mozilla.org/newsblog-feed-downloader/clh;1", true);
},
canUnload: function(aCompMgr)
{
return true;
}
};
function NSGetModule(aCompMgr, aFileSpec)
{
return nsNewsBlogFeedDownloaderModule;
}
function loadScripts()
{
var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
if (scriptLoader)
{
scriptLoader.loadSubScript("chrome://messenger-newsblog/content/Feed.js");
scriptLoader.loadSubScript("chrome://messenger-newsblog/content/FeedItem.js");
scriptLoader.loadSubScript("chrome://messenger-newsblog/content/feed-parser.js");
scriptLoader.loadSubScript("chrome://messenger-newsblog/content/file-utils.js");
scriptLoader.loadSubScript("chrome://messenger-newsblog/content/utils.js");
}
gExternalScriptsLoaded = true;
}
// Progress glue code. Acts as a go between the RSS back end and the mail window front end
// determined by the aMsgWindow parameter passed into nsINewsBlogFeedDownloader.
// gNumPendingFeedDownloads: keeps track of the total number of feeds we have been asked to download
// this number may not reflect the # of entries in our mFeeds array because not all
// feeds may have reported in for the first time...
var gNumPendingFeedDownloads = 0;
var progressNotifier = {
mSubscribeMode: false,
mMsgWindow: null,
mStatusFeedback: null,
mFeeds: new Array,
init: function(aMsgWindow, aSubscribeMode)
{
if (!gNumPendingFeedDownloads) // if we aren't already in the middle of downloading feed items...
{
this.mStatusFeedback = aMsgWindow ? aMsgWindow.statusFeedback : null;
this.mSubscribeMode = aSubscribeMode;
this.mMsgWindow = aMsgWindow;
if (this.mStatusFeedback)
{
this.mStatusFeedback.startMeteors();
this.mStatusFeedback.showStatusString(aSubscribeMode ? GetNewsBlogStringBundle().GetStringFromName('subscribe-validating')
: GetNewsBlogStringBundle().GetStringFromName('newsblog-getNewMailCheck'));
}
}
},
downloaded: function(feed, aErrorCode)
{
if (this.mSubscribeMode && aErrorCode == kNewsBlogSuccess)
{
// if we get here...we should always have a folder by now...either
// in feed.folder or FeedItems created the folder for us....
updateFolderFeedUrl(feed.folder, feed.url, false);
addFeed(feed.url, feed.name, feed.folder); // add feed just adds the feed to the subscription UI and flushes the datasource
// Nice touch: select the folder that now contains the newly subscribed feed...this is particularly nice
// if we just finished subscribing to a feed URL that the operating system gave us.
this.mMsgWindow.windowCommands.selectFolder(feed.folder.URI);
}
else if (feed.folder)
feed.folder.setMsgDatabase(null);
if (this.mStatusFeedback)
{
var newsBlogBundle = GetNewsBlogStringBundle();
if (aErrorCode == kNewsBlogNoNewItems)
this.mStatusFeedback.showStatusString(newsBlogBundle.GetStringFromName("newsblog-noNewArticlesForFeed"));
else if (aErrorCode == kNewsBlogInvalidFeed)
this.mStatusFeedback.showStatusString(newsBlogBundle.formatStringFromName("newsblog-invalidFeed",
[feed.url], 1));
else if (aErrorCode == kNewsBlogRequestFailure)
this.mStatusFeedback.showStatusString(newsBlogBundle.formatStringFromName("newsblog-networkError",
[feed.url], 1));
this.mStatusFeedback.stopMeteors();
}
gNumPendingFeedDownloads--;
if (!gNumPendingFeedDownloads)
{
this.mFeeds = new Array;
this.mSubscribeMode = false;
// should we do this on a timer so the text sticks around for a little while?
// It doesnt look like we do it on a timer for newsgroups so we'll follow that model.
if (aErrorCode == kNewsBlogSuccess && this.mStatusFeedback) // don't clear the status text if we just dumped an error to the status bar!
this.mStatusFeedback.showStatusString("");
}
},
// this gets called after the RSS parser finishes storing a feed item to disk
// aCurrentFeedItems is an integer corresponding to how many feed items have been downloaded so far
// aMaxFeedItems is an integer corresponding to the total number of feed items to download
onFeedItemStored: function (feed, aCurrentFeedItems, aMaxFeedItems)
{
// we currently don't do anything here. Eventually we may add
// status text about the number of new feed articles received.
if (this.mSubscribeMode && this.mStatusFeedback) // if we are subscribing to a feed, show feed download progress
{
this.mStatusFeedback.showStatusString(GetNewsBlogStringBundle().formatStringFromName("subscribe-fetchingFeedItems", [aCurrentFeedItems, aMaxFeedItems], 2));
this.onProgress(feed, aCurrentFeedItems, aMaxFeedItems);
}
},
onProgress: function(feed, aProgress, aProgressMax)
{
if (feed.url in this.mFeeds) // have we already seen this feed?
this.mFeeds[feed.url].currentProgress = aProgress;
else
this.mFeeds[feed.url] = {currentProgress: aProgress, maxProgress: aProgressMax};
this.updateProgressBar();
},
updateProgressBar: function()
{
var currentProgress = 0;
var maxProgress = 0;
for (index in this.mFeeds)
{
currentProgress += this.mFeeds[index].currentProgress;
maxProgress += this.mFeeds[index].maxProgress;
}
// if we start seeing weird "jumping" behavior where the progress bar goes below a threshold then above it again,
// then we can factor a fudge factor here based on the number of feeds that have not reported yet and the avg
// progress we've already received for existing feeds. Fortunately the progressmeter is on a timer
// and only updates every so often. For the most part all of our request have initial progress
// before the UI actually picks up a progress value.
if (this.mStatusFeedback)
{
var progress = (currentProgress * 100) / maxProgress;
this.mStatusFeedback.showProgress(progress);
}
}
}
function GetNewsBlogStringBundle(name)
{
var strBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"].getService();
strBundleService = strBundleService.QueryInterface(Components.interfaces.nsIStringBundleService);
var strBundle = strBundleService.createBundle("chrome://messenger-newsblog/locale/newsblog.properties");
return strBundle;
}