summaryrefslogtreecommitdiffstats
path: root/mailnews/news/src/nsNewsDownloader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/news/src/nsNewsDownloader.cpp')
-rw-r--r--mailnews/news/src/nsNewsDownloader.cpp586
1 files changed, 586 insertions, 0 deletions
diff --git a/mailnews/news/src/nsNewsDownloader.cpp b/mailnews/news/src/nsNewsDownloader.cpp
new file mode 100644
index 000000000..0794e30e7
--- /dev/null
+++ b/mailnews/news/src/nsNewsDownloader.cpp
@@ -0,0 +1,586 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "msgCore.h"
+#include "nntpCore.h"
+#include "netCore.h"
+#include "nsIMsgNewsFolder.h"
+#include "nsIStringBundle.h"
+#include "nsNewsDownloader.h"
+#include "nsINntpService.h"
+#include "nsMsgNewsCID.h"
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMsgSearchValidityManager.h"
+#include "nsRDFCID.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIRequestObserver.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+#include "nsIArray.h"
+#include "nsArrayUtils.h"
+
+// This file contains the news article download state machine.
+
+// if pIds is not null, download the articles whose id's are passed in. Otherwise,
+// which articles to download is determined by nsNewsDownloader object,
+// or subclasses thereof. News can download marked objects, for example.
+nsresult nsNewsDownloader::DownloadArticles(nsIMsgWindow *window, nsIMsgFolder *folder, nsTArray<nsMsgKey> *pIds)
+{
+ if (pIds != nullptr)
+ m_keysToDownload.InsertElementsAt(0, pIds->Elements(), pIds->Length());
+
+ if (!m_keysToDownload.IsEmpty())
+ m_downloadFromKeys = true;
+
+ m_folder = folder;
+ m_window = window;
+ m_numwrote = 0;
+
+ bool headersToDownload = GetNextHdrToRetrieve();
+ // should we have a special error code for failure here?
+ return (headersToDownload) ? DownloadNext(true) : NS_ERROR_FAILURE;
+}
+
+/* Saving news messages
+ */
+
+NS_IMPL_ISUPPORTS(nsNewsDownloader, nsIUrlListener, nsIMsgSearchNotify)
+
+nsNewsDownloader::nsNewsDownloader(nsIMsgWindow *window, nsIMsgDatabase *msgDB, nsIUrlListener *listener)
+{
+ m_numwrote = 0;
+ m_downloadFromKeys = false;
+ m_newsDB = msgDB;
+ m_abort = false;
+ m_listener = listener;
+ m_window = window;
+ m_lastPercent = -1;
+ m_lastProgressTime = 0;
+ // not the perfect place for this, but I think it will work.
+ if (m_window)
+ m_window->SetStopped(false);
+}
+
+nsNewsDownloader::~nsNewsDownloader()
+{
+ if (m_listener)
+ m_listener->OnStopRunningUrl(/* don't have a url */nullptr, m_status);
+ if (m_newsDB)
+ {
+ m_newsDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ m_newsDB = nullptr;
+ }
+}
+
+NS_IMETHODIMP nsNewsDownloader::OnStartRunningUrl(nsIURI* url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNewsDownloader::OnStopRunningUrl(nsIURI* url, nsresult exitCode)
+{
+ bool stopped = false;
+ if (m_window)
+ m_window->GetStopped(&stopped);
+ if (stopped)
+ exitCode = NS_BINDING_ABORTED;
+
+ nsresult rv = exitCode;
+ if (NS_SUCCEEDED(exitCode) || exitCode == NS_MSG_NEWS_ARTICLE_NOT_FOUND)
+ rv = DownloadNext(false);
+
+ return rv;
+}
+
+nsresult nsNewsDownloader::DownloadNext(bool firstTimeP)
+{
+ nsresult rv;
+ if (!firstTimeP)
+ {
+ bool moreHeaders = GetNextHdrToRetrieve();
+ if (!moreHeaders)
+ {
+ if (m_listener)
+ m_listener->OnStopRunningUrl(nullptr, NS_OK);
+ return NS_OK;
+ }
+ }
+ StartDownload();
+ m_wroteAnyP = false;
+ nsCOMPtr <nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nntpService->FetchMessage(m_folder, m_keyToDownload, m_window, nullptr, this, nullptr);
+}
+
+bool DownloadNewsArticlesToOfflineStore::GetNextHdrToRetrieve()
+{
+ nsresult rv;
+
+ if (m_downloadFromKeys)
+ return nsNewsDownloader::GetNextHdrToRetrieve();
+
+ if (m_headerEnumerator == nullptr)
+ rv = m_newsDB->EnumerateMessages(getter_AddRefs(m_headerEnumerator));
+
+ bool hasMore = false;
+
+ while (NS_SUCCEEDED(rv = m_headerEnumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = m_headerEnumerator->GetNext(getter_AddRefs(supports));
+ m_newsHeader = do_QueryInterface(supports);
+ NS_ENSURE_SUCCESS(rv, false);
+ uint32_t hdrFlags;
+ m_newsHeader->GetFlags(&hdrFlags);
+ if (hdrFlags & nsMsgMessageFlags::Marked)
+ {
+ m_newsHeader->GetMessageKey(&m_keyToDownload);
+ break;
+ }
+ else
+ {
+ m_newsHeader = nullptr;
+ }
+ }
+ return hasMore;
+}
+
+void nsNewsDownloader::Abort() {}
+void nsNewsDownloader::Complete() {}
+
+bool nsNewsDownloader::GetNextHdrToRetrieve()
+{
+ nsresult rv;
+ if (m_downloadFromKeys)
+ {
+ if (m_numwrote >= (int32_t) m_keysToDownload.Length())
+ return false;
+
+ m_keyToDownload = m_keysToDownload[m_numwrote++];
+ int32_t percent;
+ percent = (100 * m_numwrote) / (int32_t) m_keysToDownload.Length();
+
+ int64_t nowMS = 0;
+ if (percent < 100) // always need to do 100%
+ {
+ nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
+ if (nowMS - m_lastProgressTime < 750)
+ return true;
+ }
+
+ m_lastProgressTime = nowMS;
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_folder);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, false);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoString firstStr;
+ firstStr.AppendInt(m_numwrote);
+ nsAutoString totalStr;
+ totalStr.AppendInt(int(m_keysToDownload.Length()));
+ nsString prettiestName;
+ nsString statusString;
+
+ m_folder->GetPrettiestName(prettiestName);
+
+ const char16_t *formatStrings[3] = { firstStr.get(), totalStr.get(), prettiestName.get() };
+ rv = bundle->FormatStringFromName(u"downloadingArticlesForOffline",
+ formatStrings, 3, getter_Copies(statusString));
+ NS_ENSURE_SUCCESS(rv, false);
+ ShowProgress(statusString.get(), percent);
+ return true;
+ }
+ NS_ASSERTION(false, "shouldn't get here if we're not downloading from keys.");
+ return false; // shouldn't get here if we're not downloading from keys.
+}
+
+nsresult nsNewsDownloader::ShowProgress(const char16_t *progressString, int32_t percent)
+{
+ if (!m_statusFeedback)
+ {
+ if (m_window)
+ m_window->GetStatusFeedback(getter_AddRefs(m_statusFeedback));
+ }
+ if (m_statusFeedback)
+ {
+ m_statusFeedback->ShowStatusString(nsDependentString(progressString));
+ if (percent != m_lastPercent)
+ {
+ m_statusFeedback->ShowProgress(percent);
+ m_lastPercent = percent;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP DownloadNewsArticlesToOfflineStore::OnStartRunningUrl(nsIURI* url)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP DownloadNewsArticlesToOfflineStore::OnStopRunningUrl(nsIURI* url, nsresult exitCode)
+{
+ m_status = exitCode;
+ if (m_newsHeader != nullptr)
+ {
+#ifdef DEBUG_bienvenu
+ // XP_Trace("finished retrieving %ld\n", m_newsHeader->GetMessageKey());
+#endif
+ if (m_newsDB)
+ {
+ nsMsgKey msgKey;
+ m_newsHeader->GetMessageKey(&msgKey);
+ m_newsDB->MarkMarked(msgKey, false, nullptr);
+ }
+ }
+ m_newsHeader = nullptr;
+ return nsNewsDownloader::OnStopRunningUrl(url, exitCode);
+}
+
+int DownloadNewsArticlesToOfflineStore::FinishDownload()
+{
+ return 0;
+}
+
+
+NS_IMETHODIMP nsNewsDownloader::OnSearchHit(nsIMsgDBHdr *header, nsIMsgFolder *folder)
+{
+ NS_ENSURE_ARG(header);
+
+
+ uint32_t msgFlags;
+ header->GetFlags(&msgFlags);
+ // only need to download articles we don't already have...
+ if (! (msgFlags & nsMsgMessageFlags::Offline))
+ {
+ nsMsgKey key;
+ header->GetMessageKey(&key);
+ m_keysToDownload.AppendElement(key);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNewsDownloader::OnSearchDone(nsresult status)
+{
+ if (m_keysToDownload.IsEmpty())
+ {
+ if (m_listener)
+ return m_listener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ nsresult rv = DownloadArticles(m_window, m_folder,
+ /* we've already set m_keysToDownload, so don't pass it in */ nullptr);
+ if (NS_FAILED(rv))
+ if (m_listener)
+ m_listener->OnStopRunningUrl(nullptr, rv);
+
+ return rv;
+}
+NS_IMETHODIMP nsNewsDownloader::OnNewSearch()
+{
+ return NS_OK;
+}
+
+int DownloadNewsArticlesToOfflineStore::StartDownload()
+{
+ m_newsDB->GetMsgHdrForKey(m_keyToDownload, getter_AddRefs(m_newsHeader));
+ return 0;
+}
+
+DownloadNewsArticlesToOfflineStore::DownloadNewsArticlesToOfflineStore(nsIMsgWindow *window, nsIMsgDatabase *db, nsIUrlListener *listener)
+ : nsNewsDownloader(window, db, listener)
+{
+ m_newsDB = db;
+}
+
+DownloadNewsArticlesToOfflineStore::~DownloadNewsArticlesToOfflineStore()
+{
+}
+
+DownloadMatchingNewsArticlesToNewsDB::DownloadMatchingNewsArticlesToNewsDB
+ (nsIMsgWindow *window, nsIMsgFolder *folder, nsIMsgDatabase *newsDB,
+ nsIUrlListener *listener) :
+ DownloadNewsArticlesToOfflineStore(window, newsDB, listener)
+{
+ m_window = window;
+ m_folder = folder;
+ m_newsDB = newsDB;
+ m_downloadFromKeys = true; // search term matching means downloadFromKeys.
+}
+
+DownloadMatchingNewsArticlesToNewsDB::~DownloadMatchingNewsArticlesToNewsDB()
+{
+}
+
+
+NS_IMPL_ISUPPORTS(nsMsgDownloadAllNewsgroups, nsIUrlListener)
+
+
+nsMsgDownloadAllNewsgroups::nsMsgDownloadAllNewsgroups(nsIMsgWindow *window, nsIUrlListener *listener)
+{
+ m_window = window;
+ m_listener = listener;
+ m_downloaderForGroup = new DownloadMatchingNewsArticlesToNewsDB(window, nullptr, nullptr, this);
+ NS_IF_ADDREF(m_downloaderForGroup);
+ m_downloadedHdrsForCurGroup = false;
+}
+
+nsMsgDownloadAllNewsgroups::~nsMsgDownloadAllNewsgroups()
+{
+ NS_IF_RELEASE(m_downloaderForGroup);
+}
+
+NS_IMETHODIMP nsMsgDownloadAllNewsgroups::OnStartRunningUrl(nsIURI* url)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDownloadAllNewsgroups::OnStopRunningUrl(nsIURI* url, nsresult exitCode)
+{
+ nsresult rv = exitCode;
+ if (NS_SUCCEEDED(exitCode) || exitCode == NS_MSG_NEWS_ARTICLE_NOT_FOUND)
+ {
+ if (m_downloadedHdrsForCurGroup)
+ {
+ bool savingArticlesOffline = false;
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_currentFolder);
+ if (newsFolder)
+ newsFolder->GetSaveArticleOffline(&savingArticlesOffline);
+
+ m_downloadedHdrsForCurGroup = false;
+ if (savingArticlesOffline) // skip this group - we're saving to it already
+ rv = ProcessNextGroup();
+ else
+ rv = DownloadMsgsForCurrentGroup();
+ }
+ else
+ {
+ rv = ProcessNextGroup();
+ }
+ }
+ else if (m_listener) // notify main observer.
+ m_listener->OnStopRunningUrl(url, exitCode);
+
+ return rv;
+}
+
+/**
+ * Leaves m_currentServer at the next nntp "server" that
+ * might have folders to download for offline use. If no more servers,
+ * m_currentServer will be left at nullptr and the function returns false.
+ * Also, sets up m_serverEnumerator to enumerate over the server.
+ * If no servers found, m_serverEnumerator will be left at null.
+ */
+bool nsMsgDownloadAllNewsgroups::AdvanceToNextServer()
+{
+ nsresult rv;
+
+ if (!m_allServers)
+ {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ASSERTION(accountManager && NS_SUCCEEDED(rv), "couldn't get account mgr");
+ if (!accountManager || NS_FAILED(rv))
+ return false;
+
+ rv = accountManager->GetAllServers(getter_AddRefs(m_allServers));
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+ uint32_t serverIndex = 0;
+ if (m_currentServer)
+ {
+ rv = m_allServers->IndexOf(0, m_currentServer, &serverIndex);
+ if (NS_FAILED(rv))
+ serverIndex = -1;
+
+ ++serverIndex;
+ }
+ m_currentServer = nullptr;
+ uint32_t numServers;
+ m_allServers->GetLength(&numServers);
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+
+ while (serverIndex < numServers)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(m_allServers, serverIndex);
+ serverIndex++;
+
+ nsCOMPtr <nsINntpIncomingServer> newsServer = do_QueryInterface(server);
+ if (!newsServer) // we're only looking for news servers
+ continue;
+
+ if (server)
+ {
+ m_currentServer = server;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ {
+ rv = rootFolder->GetDescendants(getter_AddRefs(m_allFolders));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = m_allFolders->Enumerate(getter_AddRefs(m_serverEnumerator));
+ if (NS_SUCCEEDED(rv) && m_serverEnumerator)
+ {
+ bool hasMore = false;
+ rv = m_serverEnumerator->HasMoreElements(&hasMore);
+ if (NS_SUCCEEDED(rv) && hasMore)
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Sets m_currentFolder to the next usable folder.
+ *
+ * @return False if no more folders found, otherwise true.
+ */
+bool nsMsgDownloadAllNewsgroups::AdvanceToNextGroup()
+{
+ nsresult rv = NS_OK;
+
+ if (m_currentFolder)
+ {
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_currentFolder);
+ if (newsFolder)
+ newsFolder->SetSaveArticleOffline(false);
+
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && session)
+ {
+ bool folderOpen;
+ uint32_t folderFlags;
+ m_currentFolder->GetFlags(&folderFlags);
+ session->IsFolderOpenInWindow(m_currentFolder, &folderOpen);
+ if (!folderOpen && ! (folderFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
+ m_currentFolder->SetMsgDatabase(nullptr);
+ }
+ m_currentFolder = nullptr;
+ }
+
+ bool hasMore = false;
+ if (m_currentServer)
+ m_serverEnumerator->HasMoreElements(&hasMore);
+ if (!hasMore)
+ hasMore = AdvanceToNextServer();
+
+ if (hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ rv = m_serverEnumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv))
+ m_currentFolder = do_QueryInterface(supports);
+ }
+ return m_currentFolder;
+}
+
+nsresult DownloadMatchingNewsArticlesToNewsDB::RunSearch(nsIMsgFolder *folder, nsIMsgDatabase *newsDB, nsIMsgSearchSession *searchSession)
+{
+ m_folder = folder;
+ m_newsDB = newsDB;
+ m_searchSession = searchSession;
+
+ m_keysToDownload.Clear();
+
+ NS_ENSURE_ARG(searchSession);
+ NS_ENSURE_ARG(folder);
+
+ searchSession->RegisterListener(this,
+ nsIMsgSearchSession::allNotifications);
+ nsresult rv = searchSession->AddScopeTerm(nsMsgSearchScope::localNews, folder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return searchSession->Search(m_window);
+}
+
+nsresult nsMsgDownloadAllNewsgroups::ProcessNextGroup()
+{
+ bool done = false;
+
+ while (!done)
+ {
+ done = !AdvanceToNextGroup();
+ if (!done && m_currentFolder)
+ {
+ uint32_t folderFlags;
+ m_currentFolder->GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Offline)
+ break;
+ }
+ }
+ if (done)
+ {
+ if (m_listener)
+ return m_listener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ m_downloadedHdrsForCurGroup = true;
+ return m_currentFolder ? m_currentFolder->GetNewMessages(m_window, this) : NS_ERROR_NOT_INITIALIZED;
+}
+
+nsresult nsMsgDownloadAllNewsgroups::DownloadMsgsForCurrentGroup()
+{
+ NS_ENSURE_TRUE(m_downloaderForGroup, NS_ERROR_OUT_OF_MEMORY);
+ nsCOMPtr <nsIMsgDatabase> db;
+ nsCOMPtr <nsIMsgDownloadSettings> downloadSettings;
+ m_currentFolder->GetMsgDatabase(getter_AddRefs(db));
+ nsresult rv = m_currentFolder->GetDownloadSettings(getter_AddRefs(downloadSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_currentFolder);
+ if (newsFolder)
+ newsFolder->SetSaveArticleOffline(true);
+
+ nsCOMPtr <nsIMsgSearchSession> searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool downloadByDate, downloadUnreadOnly;
+ uint32_t ageLimitOfMsgsToDownload;
+
+ downloadSettings->GetDownloadByDate(&downloadByDate);
+ downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly);
+ downloadSettings->GetAgeLimitOfMsgsToDownload(&ageLimitOfMsgsToDownload);
+
+ nsCOMPtr <nsIMsgSearchTerm> term;
+ nsCOMPtr <nsIMsgSearchValue> value;
+
+ rv = searchSession->CreateTerm(getter_AddRefs(term));
+ NS_ENSURE_SUCCESS(rv, rv);
+ term->GetValue(getter_AddRefs(value));
+
+ if (downloadUnreadOnly)
+ {
+ value->SetAttrib(nsMsgSearchAttrib::MsgStatus);
+ value->SetStatus(nsMsgMessageFlags::Read);
+ searchSession->AddSearchTerm(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, value, true, nullptr);
+ }
+ if (downloadByDate)
+ {
+ value->SetAttrib(nsMsgSearchAttrib::AgeInDays);
+ value->SetAge(ageLimitOfMsgsToDownload);
+ searchSession->AddSearchTerm(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, value, nsMsgSearchBooleanOp::BooleanAND, nullptr);
+ }
+ value->SetAttrib(nsMsgSearchAttrib::MsgStatus);
+ value->SetStatus(nsMsgMessageFlags::Offline);
+ searchSession->AddSearchTerm(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, value, nsMsgSearchBooleanOp::BooleanAND, nullptr);
+
+ m_downloaderForGroup->RunSearch(m_currentFolder, db, searchSession);
+ return rv;
+}