home *** CD-ROM | disk | FTP | other *** search
- /* -*- 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.errors.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.SelectFolder(feed.folder.URI);
- }
-
- 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;
- }
-