diff options
Diffstat (limited to 'mailnews/news/src/nsNewsDownloader.cpp')
-rw-r--r-- | mailnews/news/src/nsNewsDownloader.cpp | 586 |
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; +} |