diff --git a/mozilla/mail/extensions/newsblog/content/Feed.js b/mozilla/mail/extensions/newsblog/content/Feed.js index c0aabcadfa3..89ff844be50 100755 --- a/mozilla/mail/extensions/newsblog/content/Feed.js +++ b/mozilla/mail/extensions/newsblog/content/Feed.js @@ -14,6 +14,12 @@ var serializer = .classes["@mozilla.org/xmlextras/xmlserializer;1"] .createInstance(Components.interfaces.nsIDOMSerializer); +// error codes used to inform the consumer about attempts to download a feed + +const kNewsBlogSuccess = 0; +const kNewsBlogInvalidFeed = 1; // usually means there was an error trying to parse the feed... +const kNewsBlogRequestFailure = 2; // generic networking failure when trying to download the feed. + // Hash of feeds being downloaded, indexed by URL, so the load event listener // can access the Feed objects after it finishes downloading the feed files. var gFzFeedCache = new Object(); @@ -57,19 +63,27 @@ Feed.prototype.name getter = function() { } Feed.prototype.download = function(parseItems, aCallback) { + this.downloadCallback = aCallback; // may be null // Whether or not to parse items when downloading and parsing the feed. // Defaults to true, but setting to false is useful for obtaining // just the title of the feed when the user subscribes to it. this.parseItems = parseItems == null ? true : parseItems ? true : false; + // Before we do anything...make sure the url is an http url. This is just a sanity check + // so we don't try opening mailto urls, imap urls, etc. that the user may have tried to subscribe to + // as an rss feed.. + var uri = Components.classes["@mozilla.org/network/standard-url;1"]. + createInstance(Components.interfaces.nsIURI); + uri.spec = this.url; + if (!uri.schemeIs("http")) + return this.onParseError(this); // simulate an invalid feed error + this.request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] .createInstance(Components.interfaces.nsIXMLHttpRequest); this.request.onprogress = Feed.onProgress; // must be set before calling .open this.request.open("GET", this.url, true); - this.downloadCallback = aCallback; // may be null - this.request.overrideMimeType("text/xml"); this.request.onload = Feed.onDownloaded; this.request.onerror = Feed.onDownloadError; @@ -100,17 +114,13 @@ Feed.onProgress = function(event) { } Feed.onDownloadError = function(event) { - // XXX add error message if available and notify the user? - var request = event.target; - var url = request.channel.originalURI.spec; - var feed = gFzFeedCache[url]; - if (feed) - { - debug(feed.title + " download failed"); - if (feed.downloadCallback) - feed.downloaded(feed, false); - } - throw("error downloading feed " + url); + if (feed.downloadCallback) + feed.downloadCallback.downloaded(feed, kNewsBlogRequestFailure); +} + +Feed.prototype.onParseError = function(feed) { + if (feed && feed.downloadCallback) + feed.downloadCallback.downloaded(feed, kNewsBlogInvalidFeed); } Feed.prototype.url getter = function() { @@ -159,8 +169,7 @@ Feed.prototype.parse = function() { debug("parsing feed " + this.url); if (!this.request.responseText) { - throw("error parsing feed " + this.url + ": no data"); - return; + return this.onParseError(this); } else if (this.request.responseText.search(/="http:\/\/purl\.org\/rss\/1\.0\/"/) != -1) { debug(this.url + " is an RSS 1.x (RDF-based) feed"); @@ -192,12 +201,12 @@ Feed.prototype.parse = function() { Feed.prototype.parseAsRSS2 = function() { if (!this.request.responseXML || !(this.request.responseXML instanceof Components.interfaces.nsIDOMXMLDocument)) - throw("error parsing RSS 2.0 feed " + this.url + ": data not parsed into XMLDocument object"); + return this.onParseError(this); // Get the first channel (assuming there is only one per RSS File). var channel = this.request.responseXML.getElementsByTagName("channel")[0]; if (!channel) - throw("error parsing RSS 2.0 feed " + this.url + ": channel element missing"); + return this.onParseError(this); this.title = this.title || getNodeValue(channel.getElementsByTagName("title")[0]); this.description = getNodeValue(channel.getElementsByTagName("description")[0]); @@ -311,12 +320,12 @@ Feed.prototype.parseAsRSS1 = function() { Feed.prototype.parseAsAtom = function() { if (!this.request.responseXML || !(this.request.responseXML instanceof Components.interfaces.nsIDOMXMLDocument)) - throw("error parsing Atom feed " + this.url + ": data not parsed into XMLDocument object"); + return this.onParseError(this); // Get the first channel (assuming there is only one per Atom File). var channel = this.request.responseXML.getElementsByTagName("feed")[0]; if (!channel) - throw("channel missing from Atom feed " + request.channel.name); + return this.onParseError(this); this.title = this.title || getNodeValue(channel.getElementsByTagName("title")[0]); this.description = getNodeValue(channel.getElementsByTagName("tagline")[0]); @@ -443,6 +452,7 @@ Feed.prototype.removeInvalidItems = function() { var gItemsToStore; var gItemsToStoreIndex = 0; +var gStoreItemsTimer; // gets the next item from gItemsToStore and forces that item to be stored // to the folder. If more items are left to be stored, fires a timer for the next one. @@ -463,20 +473,16 @@ function storeNextItem() if (gItemsToStoreIndex < gItemsToStore.length) { - if ('setTimeout' in this) - setTimeout(storeNextItem, 50); // fire off a timer for the next item to store - else - { - debug('set timeout is not defined if this call originated from newsblog.js\n'); - storeNextItem(); - } + if (!gStoreItemsTimer) + gStoreItemsTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); + gStoreItemsTimer.initWithCallback(storeNextItemTimerCallback, 50, Components.interfaces.nsITimer.TYPE_ONE_SHOT); } else { item.feed.removeInvalidItems(); if (item.feed.downloadCallback) - item.feed.downloadCallback.downloaded(item.feed, true); + item.feed.downloadCallback.downloaded(item.feed, kNewsBlogSuccess); item.feed.request = null; // force the xml http request to go away. This helps reduce some // nasty assertions on shut down of all things. @@ -485,3 +491,17 @@ function storeNextItem() gItemsToStoreIndex = 0; } } + +var storeNextItemTimerCallback = { + notify: function(aTimer) { + storeNextItem(); + }, + + QueryInterface: function(aIID) { + if (aIID.equals(Components.interfaces.nsITimerCallback) || aIID.equals(Components.interfaces.nsISupports)) + return this; + + Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE; + return null; + } +} diff --git a/mozilla/mail/extensions/newsblog/content/subscriptions.js b/mozilla/mail/extensions/newsblog/content/subscriptions.js index f8889cf43e6..ca235b03aa4 100755 --- a/mozilla/mail/extensions/newsblog/content/subscriptions.js +++ b/mozilla/mail/extensions/newsblog/content/subscriptions.js @@ -34,7 +34,6 @@ * * ***** END LICENSE BLOCK ***** */ -var kFeedUrlDelimiter = '|'; // the delimiter used to delimit feed urls in the msg folder database "feedUrl" property var gRSSServer = null; function doLoad() { @@ -74,10 +73,10 @@ function clearStatusInfo() } var feedDownloadCallback = { - downloaded: function(feed, aSuccess) + downloaded: function(feed, aErrorCode) { // feed is null if our attempt to parse the feed failed - if (aSuccess) + if (aErrorCode == kNewsBlogSuccess) { updateStatusItem('progressMeter', 100); @@ -91,10 +90,10 @@ var feedDownloadCallback = { // it also flushes the subscription datasource addFeed(feed.url, feed.name, null, folder); } - else - { - // Add some code to alert the user that the feed was not something we could understand... - } + else if (aErrorCode == kNewsBlogInvalidFeed) // the feed was bad... + window.alert(document.getElementById('bundle_newsblog').getFormattedString('newsblog-invalidFeed', [feed.url])); + else // we never even downloaded the feed...(kNewsBlogRequestFailure) + window.alert(document.getElementById('bundle_newsblog').getFormattedString('newsblog-networkError', [feed.url])); // our operation is done...clear out the status text and progressmeter setTimeout(clearStatusInfo, 1000); @@ -116,23 +115,6 @@ var feedDownloadCallback = { }, } -// updates the "feedUrl" property in the message database for the folder in question. -function updateFolderFeedUrl(aFolder, aFeedUrl, aRemoveUrl) -{ - var msgdb = aFolder.QueryInterface(Components.interfaces.nsIMsgFolder).getMsgDatabase(null); - var folderInfo = msgdb.dBFolderInfo; - var oldFeedUrl = folderInfo.getCharPtrProperty("feedUrl"); - - if (aRemoveUrl) - { - // remove our feed url string from the list of feed urls - var newFeedUrl = oldFeedUrl.replace(kFeedUrlDelimiter + aFeedUrl, ""); - folderInfo.setCharPtrProperty("feedUrl", newFeedUrl); - } - else - folderInfo.setCharPtrProperty("feedUrl", oldFeedUrl + kFeedUrlDelimiter + aFeedUrl); -} - function doAdd() { var userAddedFeed = false; var feedProperties = { feedName: "", feedLocation: "", serverURI: gRSSServer.serverURI, folderURI: "", result: userAddedFeed}; diff --git a/mozilla/mail/extensions/newsblog/content/utils.js b/mozilla/mail/extensions/newsblog/content/utils.js index f086dd2f5ab..20929a1355b 100755 --- a/mozilla/mail/extensions/newsblog/content/utils.js +++ b/mozilla/mail/extensions/newsblog/content/utils.js @@ -88,6 +88,25 @@ function addFeed(url, title, quickMode, destFolder) { ds.Flush(); } +// updates the "feedUrl" property in the message database for the folder in question. + +var kFeedUrlDelimiter = '|'; // the delimiter used to delimit feed urls in the msg folder database "feedUrl" property + +function updateFolderFeedUrl(aFolder, aFeedUrl, aRemoveUrl) +{ + var msgdb = aFolder.QueryInterface(Components.interfaces.nsIMsgFolder).getMsgDatabase(null); + var folderInfo = msgdb.dBFolderInfo; + var oldFeedUrl = folderInfo.getCharPtrProperty("feedUrl"); + + if (aRemoveUrl) + { + // remove our feed url string from the list of feed urls + var newFeedUrl = oldFeedUrl.replace(kFeedUrlDelimiter + aFeedUrl, ""); + folderInfo.setCharPtrProperty("feedUrl", newFeedUrl); + } + else + folderInfo.setCharPtrProperty("feedUrl", oldFeedUrl + kFeedUrlDelimiter + aFeedUrl); +} function getNodeValue(node) { if (node && node.textContent) diff --git a/mozilla/mail/extensions/newsblog/js/newsblog.js b/mozilla/mail/extensions/newsblog/js/newsblog.js index 5db125170a9..7f1c114d019 100755 --- a/mozilla/mail/extensions/newsblog/js/newsblog.js +++ b/mozilla/mail/extensions/newsblog/js/newsblog.js @@ -51,7 +51,7 @@ var nsNewsBlogFeedDownloader = var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"] .getService(Components.interfaces.nsIRDFService); - progressNotifier.init(aMsgWindow.statusFeedback); + progressNotifier.init(aMsgWindow.statusFeedback, false); var index = 0; for (url in feedUrlArray) @@ -68,6 +68,23 @@ var nsNewsBlogFeedDownloader = } }, + subscribeToFeed: function(aUrl, aFolder, aMsgWindow) + { + if (!gExternalScriptsLoaded) + loadScripts(); + var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"] + .getService(Components.interfaces.nsIRDFService); + + var itemResource = rdf.GetResource(aUrl); + var feed = new Feed(itemResource); + feed.server = aFolder.server; + if (!aFolder.server.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.statusFeedback, true); + feed.download(true, progressNotifier); + }, + QueryInterface: function(aIID) { if (aIID.equals(Components.interfaces.nsINewsBlogFeedDownloader) || @@ -222,33 +239,52 @@ function loadScripts() var gNumPendingFeedDownloads = 0; var progressNotifier = { - + mSubscribeMode: false, mStatusFeedback: null, mFeeds: new Array, - init: function(aStatusFeedback) + init: function(aStatusFeedback, aSubscribeMode) { if (!gNumPendingFeedDownloads) // if we aren't already in the middle of downloading feed items... { this.mStatusFeedback = aStatusFeedback; + this.mSubscribeMode = aSubscribeMode; this.mStatusFeedback.startMeteors(); - this.mStatusFeedback.showStatusString(GetString('newsblog-getNewMailCheck')); + + this.mStatusFeedback.showStatusString(aSubscribeMode ? GetNewsBlogStringBundle().GetStringFromName('subscribe-validating') + : GetNewsBlogStringBundle().GetStringFromName('newsblog-getNewMailCheck')); } }, - downloaded: function(feed) + 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.... + var folder = feed.folder ? feed.folder : feed.server.rootMsgFolder.getChildNamed(feed.name); + updateFolderFeedUrl(folder, feed.url, false); + addFeed(feed.url, feed.name, null, folder); // add feed just adds the feed to the subscription UI and flushes the datasource + } + else if (aErrorCode == kNewsBlogInvalidFeed) + this.mStatusFeedback.showStatusString(GetNewsBlogStringBundle().formatStringFromName("newsblog-invalidFeed", + [feed.url], 1)); + else if (aErrorCode == kNewsBlogRequestFailure) + this.mStatusFeedback.showStatusString(GetNewsBlogStringBundle().formatStringFromName("newsblog-networkError", + [feed.url], 1)); + this.mStatusFeedback.stopMeteors(); gNumPendingFeedDownloads--; if (!gNumPendingFeedDownloads) { this.mFeeds = new Array; - // no more pending actions...clear the status bar text...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. + this.mSubscribeMode = false; - this.mStatusFeedback.showStatusString(""); + // 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) // don't clear the status text if we just dumped an error to the status bar! + this.mStatusFeedback.showStatusString(""); } }, @@ -259,6 +295,12 @@ var progressNotifier = { { // we currently don't do anything here. Eventually we may add // status text about the number of new feed articles received. + + if (this.mSubscribeMode) // 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) @@ -292,10 +334,10 @@ var progressNotifier = { } } -function GetString(name) +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.GetStringFromName(name); + return strBundle; } diff --git a/mozilla/mail/extensions/newsblog/locale/newsblog.properties b/mozilla/mail/extensions/newsblog/locale/newsblog.properties index 18fd34ad09b..79f5fbb0196 100644 --- a/mozilla/mail/extensions/newsblog/locale/newsblog.properties +++ b/mozilla/mail/extensions/newsblog/locale/newsblog.properties @@ -1,6 +1,6 @@ # Status strings used in the subscribe dialog -subscribe-validating=Verifying the feed... +subscribe-validating=Verifying the RSS feed... # when downloading new feed items from the subscribe dialog. # LOCALIZATION NOTE: Do not translate %d in the following line. @@ -8,4 +8,6 @@ subscribe-validating=Verifying the feed... # the second %S will receive the total number of messages subscribe-fetchingFeedItems=Downloading feed articles (%S of %S) +newsblog-invalidFeed=%S is not a valid RSS feed. +newsblog-networkError=%S could not be found. Please check the name and try again. newsblog-getNewMailCheck=Checking RSS feeds for new items diff --git a/mozilla/mailnews/base/resources/content/messengerdnd.js b/mozilla/mailnews/base/resources/content/messengerdnd.js index a16df448a8f..74e0c86886b 100644 --- a/mozilla/mailnews/base/resources/content/messengerdnd.js +++ b/mozilla/mailnews/base/resources/content/messengerdnd.js @@ -69,6 +69,7 @@ function CanDropOnFolderTree(index, orientation) trans.addDataFlavor("text/x-moz-message"); trans.addDataFlavor("text/x-moz-folder"); + trans.addDataFlavor("text/x-moz-url"); var folderTree = GetFolderTree(); var targetResource = GetFolderResource(folderTree, index); @@ -121,33 +122,43 @@ function CanDropOnFolderTree(index, orientation) if (hdr.folder == targetFolder) return false; break; + } else if (dataFlavor.value == "text/x-moz-folder") { + + // we should only get here if we are dragging and dropping folders + dragFolder = true; + sourceResource = RDF.GetResource(sourceUri); + var sourceFolder = sourceResource.QueryInterface(Components.interfaces.nsIMsgFolder); + sourceServer = sourceFolder.server; + + if (targetUri == sourceUri) + return false; + + //don't allow drop on different imap servers. + if (sourceServer != targetServer && targetServer.type == "imap") + return false; + + //don't allow immediate child to be dropped to it's parent + if (targetFolder.URI == sourceFolder.parent.URI) + { + debugDump(targetFolder.URI + "\n"); + debugDump(sourceFolder.parent.URI + "\n"); + return false; + } + + var isAncestor = sourceFolder.isAncestorOf(targetFolder); + // don't allow parent to be dropped on its ancestors + if (isAncestor) + return false; + } else if (dataFlavor.value == "text/x-moz-url") { + // eventually check to make sure this is an http url before doing anything else... + var uri = Components.classes["@mozilla.org/network/standard-url;1"]. + createInstance(Components.interfaces.nsIURI); + var url = sourceUri.split("\n")[0]; + uri.spec = url; + + if (uri.schemeIs("http") && targetServer && targetServer.type == 'rss') + return true; } - - // we should only get here if we are dragging and dropping folders - dragFolder = true; - sourceResource = RDF.GetResource(sourceUri); - var sourceFolder = sourceResource.QueryInterface(Components.interfaces.nsIMsgFolder); - sourceServer = sourceFolder.server; - - if (targetUri == sourceUri) - return false; - - //don't allow drop on different imap servers. - if (sourceServer != targetServer && targetServer.type == "imap") - return false; - - //don't allow immediate child to be dropped to it's parent - if (targetFolder.URI == sourceFolder.parent.URI) - { - debugDump(targetFolder.URI + "\n"); - debugDump(sourceFolder.parent.URI + "\n"); - return false; - } - - var isAncestor = sourceFolder.isAncestorOf(targetFolder); - // don't allow parent to be dropped on its ancestors - if (isAncestor) - return false; } if (dragFolder) @@ -198,6 +209,8 @@ function DropOnFolderTree(row, orientation) var folderTree = GetFolderTree(); var targetResource = GetFolderResource(folderTree, row); + var targetFolder = targetResource.QueryInterface(Components.interfaces.nsIMsgFolder); + var targetServer = targetFolder.server; var targetUri = targetResource.Value; debugDump("***targetUri = " + targetUri + "\n"); @@ -209,6 +222,7 @@ function DropOnFolderTree(row, orientation) var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable); trans.addDataFlavor("text/x-moz-message"); trans.addDataFlavor("text/x-moz-folder"); + trans.addDataFlavor("text/x-moz-url"); var list = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray); @@ -248,6 +262,24 @@ function DropOnFolderTree(row, orientation) } else if (flavor.value == "text/x-moz-message") dropMessage = true; + else if (flavor.value == "text/x-moz-url") + { + var uri = Components.classes["@mozilla.org/network/standard-url;1"]. + createInstance(Components.interfaces.nsIURI); + var url = sourceUri.split("\n")[0]; + uri.spec = url; + + if (uri.schemeIs("http") && targetServer && targetServer.type == 'rss') + { + var rssService = Components.classes["@mozilla.org/newsblog-feed-downloader;1"].getService(). + QueryInterface(Components.interfaces.nsINewsBlogFeedDownloader); + if (rssService) + rssService.subscribeToFeed(url, targetFolder, msgWindow); + return true; + } + else + return false; + } } else { if (!dropMessage) @@ -273,9 +305,6 @@ function DropOnFolderTree(row, orientation) var isSourceNews = false; isSourceNews = isNewsURI(sourceUri); - - var targetFolder = targetResource.QueryInterface(Components.interfaces.nsIMsgFolder); - var targetServer = targetFolder.server; if (dropMessage) { var sourceMsgHdr = list.GetElementAt(0).QueryInterface(Components.interfaces.nsIMsgDBHdr); diff --git a/mozilla/mailnews/local/public/nsINewsBlogFeedDownloader.idl b/mozilla/mailnews/local/public/nsINewsBlogFeedDownloader.idl index 873a1ee12f4..a12241fc52b 100755 --- a/mozilla/mailnews/local/public/nsINewsBlogFeedDownloader.idl +++ b/mozilla/mailnews/local/public/nsINewsBlogFeedDownloader.idl @@ -46,5 +46,9 @@ interface nsINewsBlogFeedDownloader : nsISupports void downloadFeed(in string aUrl, in nsIMsgFolder aFolder, in boolean aQuickMode, in wstring aTitle, in nsIUrlListener aUrlListener, in nsIMsgWindow aMsgWindow); + + /* A convient method to subscribe to feeds without going through the subscribe UI + used by drag and drop */ + void subscribeToFeed(in string aUrl, in nsIMsgFolder aFolder, in nsIMsgWindow aMsgWindow); };