From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mailnews/news/src/nsNewsDownloader.cpp | 507 ++++++++++++++++++++++++++++ 1 file changed, 507 insertions(+) create mode 100644 comm/mailnews/news/src/nsNewsDownloader.cpp (limited to 'comm/mailnews/news/src/nsNewsDownloader.cpp') diff --git a/comm/mailnews/news/src/nsNewsDownloader.cpp b/comm/mailnews/news/src/nsNewsDownloader.cpp new file mode 100644 index 0000000000..945e1bd084 --- /dev/null +++ b/comm/mailnews/news/src/nsNewsDownloader.cpp @@ -0,0 +1,507 @@ +/* -*- Mode: C++; tab-width: 2; 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 "nsIMsgSearchSession.h" +#include "nsIMsgSearchTerm.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgMailSession.h" +#include "nsMsgMessageFlags.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" +#include "mozilla/Components.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* 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 nntpService = + do_GetService("@mozilla.org/messenger/nntpservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr uri; + return nntpService->FetchMessage(m_folder, m_keyToDownload, m_window, nullptr, + this, getter_AddRefs(uri)); +} + +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) { + rv = m_headerEnumerator->GetNext(getter_AddRefs(m_newsHeader)); + 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 bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, false); + nsCOMPtr 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 prettyName; + nsString statusString; + + m_folder->GetPrettyName(prettyName); + + AutoTArray formatStrings = {firstStr, totalStr, prettyName}; + rv = bundle->FormatStringFromName("downloadingArticlesForOffline", + formatStrings, 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); + m_downloadedHdrsForCurGroup = false; +} + +nsMsgDownloadAllNewsgroups::~nsMsgDownloadAllNewsgroups() {} + +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 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_folderQueue to hold a (reversed) list of all the folders + * to consider for the current server. + * If no servers found, returns false. + */ +bool nsMsgDownloadAllNewsgroups::AdvanceToNextServer() { + nsresult rv; + + if (m_allServers.IsEmpty()) { + nsCOMPtr accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ASSERTION(accountManager && NS_SUCCEEDED(rv), + "couldn't get account mgr"); + if (!accountManager || NS_FAILED(rv)) return false; + + rv = accountManager->GetAllServers(m_allServers); + NS_ENSURE_SUCCESS(rv, false); + } + size_t serverIndex = 0; + if (m_currentServer) { + serverIndex = m_allServers.IndexOf(m_currentServer); + if (serverIndex == m_allServers.NoIndex) { + serverIndex = 0; + } else { + ++serverIndex; + } + } + m_currentServer = nullptr; + uint32_t numServers = m_allServers.Length(); + nsCOMPtr rootFolder; + + while (serverIndex < numServers) { + nsCOMPtr server(m_allServers[serverIndex]); + serverIndex++; + + nsCOMPtr 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(m_folderQueue); + if (NS_SUCCEEDED(rv)) { + if (!m_folderQueue.IsEmpty()) { + // We'll be popping folders from the end of the queue as we go. + m_folderQueue.Reverse(); + 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 newsFolder = do_QueryInterface(m_currentFolder); + if (newsFolder) newsFolder->SetSaveArticleOffline(false); + + nsCOMPtr session = + do_GetService("@mozilla.org/messenger/services/session;1", &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) { + hasMore = !m_folderQueue.IsEmpty(); + } + if (!hasMore) { + hasMore = AdvanceToNextServer(); + } + + if (hasMore) { + m_currentFolder = m_folderQueue.PopLastElement(); + } + 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 db; + nsCOMPtr downloadSettings; + m_currentFolder->GetMsgDatabase(getter_AddRefs(db)); + nsresult rv = + m_currentFolder->GetDownloadSettings(getter_AddRefs(downloadSettings)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr newsFolder = do_QueryInterface(m_currentFolder); + if (newsFolder) newsFolder->SetSaveArticleOffline(true); + + nsCOMPtr searchSession = + do_CreateInstance("@mozilla.org/messenger/searchSession;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool downloadByDate, downloadUnreadOnly; + uint32_t ageLimitOfMsgsToDownload; + + downloadSettings->GetDownloadByDate(&downloadByDate); + downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly); + downloadSettings->GetAgeLimitOfMsgsToDownload(&ageLimitOfMsgsToDownload); + + nsCOMPtr term; + nsCOMPtr 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; +} -- cgit v1.2.3