/* -*- Mode: C++; tab-width: 4; 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 mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2001 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of 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 ***** */ #include "msgCore.h" #include "nntpCore.h" #include "netCore.h" #include "nsXPIDLString.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" // 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, nsMsgKeyArray *pIds) { if (pIds != nsnull) m_keysToDownload.InsertAt(0, pIds); if (m_keysToDownload.GetSize() > 0) m_downloadFromKeys = PR_TRUE; m_folder = folder; m_window = window; m_numwrote = 0; PRBool headersToDownload = GetNextHdrToRetrieve(); // should we have a special error code for failure here? return (headersToDownload) ? DownloadNext(PR_TRUE) : NS_ERROR_FAILURE; } /* Saving news messages */ NS_IMPL_ISUPPORTS2(nsNewsDownloader, nsIUrlListener, nsIMsgSearchNotify) nsNewsDownloader::nsNewsDownloader(nsIMsgWindow *window, nsIMsgDatabase *msgDB, nsIUrlListener *listener) { m_numwrote = 0; m_downloadFromKeys = PR_FALSE; m_newsDB = msgDB; m_abort = PR_FALSE; m_listener = listener; m_window = window; m_lastPercent = -1; LL_I2L(m_lastProgressTime, 0); // not the perfect place for this, but I think it will work. if (m_window) m_window->SetStopped(PR_FALSE); } nsNewsDownloader::~nsNewsDownloader() { if (m_listener) m_listener->OnStopRunningUrl(/* don't have a url */nsnull, m_status); if (m_newsDB) { m_newsDB->Commit(nsMsgDBCommitType::kLargeCommit); m_newsDB = nsnull; } } NS_IMETHODIMP nsNewsDownloader::OnStartRunningUrl(nsIURI* url) { return NS_OK; } NS_IMETHODIMP nsNewsDownloader::OnStopRunningUrl(nsIURI* url, nsresult exitCode) { PRBool stopped = PR_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(PR_FALSE); return rv; } nsresult nsNewsDownloader::DownloadNext(PRBool firstTimeP) { nsresult rv; if (!firstTimeP) { PRBool moreHeaders = GetNextHdrToRetrieve(); if (!moreHeaders) { if (m_listener) m_listener->OnStopRunningUrl(nsnull, NS_OK); return NS_OK; } } StartDownload(); m_wroteAnyP = PR_FALSE; nsCOMPtr nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID,&rv); NS_ENSURE_SUCCESS(rv, rv); return nntpService->FetchMessage(m_folder, m_keyToDownload, m_window, nsnull, this, nsnull); } PRBool DownloadNewsArticlesToOfflineStore::GetNextHdrToRetrieve() { nsresult rv; if (m_downloadFromKeys) return nsNewsDownloader::GetNextHdrToRetrieve(); if (m_headerEnumerator == nsnull) rv = m_newsDB->EnumerateMessages(getter_AddRefs(m_headerEnumerator)); PRBool hasMore = PR_FALSE; while (NS_SUCCEEDED(rv = m_headerEnumerator->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr supports; rv = m_headerEnumerator->GetNext(getter_AddRefs(supports)); m_newsHeader = do_QueryInterface(supports); NS_ENSURE_SUCCESS(rv,rv); PRUint32 hdrFlags; m_newsHeader->GetFlags(&hdrFlags); if (hdrFlags & MSG_FLAG_MARKED) { m_newsHeader->GetMessageKey(&m_keyToDownload); break; } else { m_newsHeader = nsnull; } } return hasMore; } void nsNewsDownloader::Abort() {} void nsNewsDownloader::Complete() {} PRBool nsNewsDownloader::GetNextHdrToRetrieve() { nsresult rv; if (m_downloadFromKeys) { if (m_numwrote >= (PRInt32) m_keysToDownload.GetSize()) return PR_FALSE; m_keyToDownload = m_keysToDownload.GetAt(m_numwrote++); PRInt32 percent; percent = (100 * m_numwrote) / (PRInt32) m_keysToDownload.GetSize(); PRInt64 nowMS = LL_ZERO; if (percent < 100) // always need to do 100% { int64 minIntervalBetweenProgress; LL_I2L(minIntervalBetweenProgress, 750); int64 diffSinceLastProgress; LL_I2L(nowMS, PR_IntervalToMilliseconds(PR_IntervalNow())); LL_SUB(diffSinceLastProgress, nowMS, m_lastProgressTime); // r = a - b LL_SUB(diffSinceLastProgress, diffSinceLastProgress, minIntervalBetweenProgress); // r = a - b if (!LL_GE_ZERO(diffSinceLastProgress)) return PR_TRUE; } m_lastProgressTime = nowMS; nsCOMPtr newsFolder = do_QueryInterface(m_folder); nsCOMPtr bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr bundle; rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); nsAutoString firstStr; firstStr.AppendInt(m_numwrote); nsAutoString totalStr; totalStr.AppendInt(m_keysToDownload.GetSize()); nsXPIDLString prettiestName; nsXPIDLString statusString; m_folder->GetPrettiestName(getter_Copies(prettiestName)); const PRUnichar *formatStrings[3] = { firstStr.get(), totalStr.get(), (const PRUnichar *) prettiestName }; rv = bundle->FormatStringFromName(NS_LITERAL_STRING("downloadingArticlesForOffline").get(), formatStrings, 3, getter_Copies(statusString)); NS_ENSURE_SUCCESS(rv, rv); ShowProgress(statusString, percent); return PR_TRUE; } NS_ASSERTION(PR_FALSE, "shouldn't get here if we're not downloading from keys."); return PR_FALSE; // shouldn't get here if we're not downloading from keys. } nsresult nsNewsDownloader::ShowProgress(const PRUnichar *progressString, PRInt32 percent) { if (!m_statusFeedback) { if (m_window) m_window->GetStatusFeedback(getter_AddRefs(m_statusFeedback)); } if (m_statusFeedback) { m_statusFeedback->ShowStatusString(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 != nsnull) { #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, PR_FALSE, nsnull); } } m_newsHeader = nsnull; return nsNewsDownloader::OnStopRunningUrl(url, exitCode); } int DownloadNewsArticlesToOfflineStore::FinishDownload() { return 0; } NS_IMETHODIMP nsNewsDownloader::OnSearchHit(nsIMsgDBHdr *header, nsIMsgFolder *folder) { NS_ENSURE_ARG(header); PRUint32 msgFlags; header->GetFlags(&msgFlags); // only need to download articles we don't already have... if (! (msgFlags & MSG_FLAG_OFFLINE)) { nsMsgKey key; header->GetMessageKey(&key); m_keysToDownload.Add(key); } return NS_OK; } NS_IMETHODIMP nsNewsDownloader::OnSearchDone(nsresult status) { if (m_keysToDownload.GetSize() == 0) { if (m_listener) return m_listener->OnStopRunningUrl(nsnull, NS_OK); } nsresult rv = DownloadArticles(m_window, m_folder, /* we've already set m_keysToDownload, so don't pass it in */ nsnull); if (NS_FAILED(rv)) if (m_listener) m_listener->OnStopRunningUrl(nsnull, 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 = PR_TRUE; // search term matching means downloadFromKeys. } DownloadMatchingNewsArticlesToNewsDB::~DownloadMatchingNewsArticlesToNewsDB() { } NS_IMPL_ISUPPORTS1(nsMsgDownloadAllNewsgroups, nsIUrlListener) nsMsgDownloadAllNewsgroups::nsMsgDownloadAllNewsgroups(nsIMsgWindow *window, nsIUrlListener *listener) { m_window = window; m_listener = listener; m_downloaderForGroup = new DownloadMatchingNewsArticlesToNewsDB(window, nsnull, nsnull, this); NS_IF_ADDREF(m_downloaderForGroup); m_downloadedHdrsForCurGroup = PR_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) { PRBool savingArticlesOffline = PR_FALSE; nsCOMPtr newsFolder = do_QueryInterface(m_currentFolder); if (newsFolder) newsFolder->GetSaveArticleOffline(&savingArticlesOffline); m_downloadedHdrsForCurGroup = PR_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 nsnull. // Also, sets up m_serverEnumerator to enumerate over the server // If no servers found, m_serverEnumerator will be left at null, nsresult nsMsgDownloadAllNewsgroups::AdvanceToNextServer(PRBool *done) { nsresult rv; NS_ENSURE_ARG(done); *done = PR_TRUE; if (!m_allServers) { nsCOMPtr accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); NS_ASSERTION(accountManager && NS_SUCCEEDED(rv), "couldn't get account mgr"); if (!accountManager || NS_FAILED(rv)) return rv; rv = accountManager->GetAllServers(getter_AddRefs(m_allServers)); NS_ENSURE_SUCCESS(rv, rv); } PRUint32 serverIndex = (m_currentServer) ? m_allServers->IndexOf(m_currentServer) + 1 : 0; m_currentServer = nsnull; PRUint32 numServers; m_allServers->Count(&numServers); nsCOMPtr rootFolder; while (serverIndex < numServers) { nsCOMPtr server = do_QueryElementAt(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) { NS_NewISupportsArray(getter_AddRefs(m_allFolders)); rv = rootFolder->ListDescendents(m_allFolders); if (NS_SUCCEEDED(rv)) m_allFolders->Enumerate(getter_AddRefs(m_serverEnumerator)); if (NS_SUCCEEDED(rv) && m_serverEnumerator) { rv = m_serverEnumerator->First(); if (NS_SUCCEEDED(rv)) { *done = PR_FALSE; break; } } } } } return rv; } nsresult nsMsgDownloadAllNewsgroups::AdvanceToNextGroup(PRBool *done) { nsresult rv; NS_ENSURE_ARG(done); *done = PR_TRUE; if (m_currentFolder) { nsCOMPtr newsFolder = do_QueryInterface(m_currentFolder); if (newsFolder) newsFolder->SetSaveArticleOffline(PR_FALSE); nsCOMPtr session = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); if (NS_SUCCEEDED(rv) && session) { PRBool folderOpen; PRUint32 folderFlags; m_currentFolder->GetFlags(&folderFlags); session->IsFolderOpenInWindow(m_currentFolder, &folderOpen); if (!folderOpen && ! (folderFlags & (MSG_FOLDER_FLAG_TRASH | MSG_FOLDER_FLAG_INBOX))) m_currentFolder->SetMsgDatabase(nsnull); } m_currentFolder = nsnull; } *done = PR_FALSE; if (!m_currentServer) rv = AdvanceToNextServer(done); else rv = m_serverEnumerator->Next(); if (NS_FAILED(rv)) rv = AdvanceToNextServer(done); if (NS_SUCCEEDED(rv) && !*done && m_serverEnumerator) { nsCOMPtr supports; rv = m_serverEnumerator->CurrentItem(getter_AddRefs(supports)); m_currentFolder = do_QueryInterface(supports); *done = PR_FALSE; } return rv; } nsresult DownloadMatchingNewsArticlesToNewsDB::RunSearch(nsIMsgFolder *folder, nsIMsgDatabase *newsDB, nsIMsgSearchSession *searchSession) { m_folder = folder; m_newsDB = newsDB; m_searchSession = searchSession; m_keysToDownload.RemoveAll(); nsresult rv; NS_ENSURE_ARG(searchSession); NS_ENSURE_ARG(folder); searchSession->RegisterListener(this); rv = searchSession->AddScopeTerm(nsMsgSearchScope::localNews, folder); return searchSession->Search(m_window); } nsresult nsMsgDownloadAllNewsgroups::ProcessNextGroup() { nsresult rv = NS_OK; PRBool done = PR_FALSE; while (NS_SUCCEEDED(rv) && !done) { rv = AdvanceToNextGroup(&done); if (m_currentFolder) { PRUint32 folderFlags; m_currentFolder->GetFlags(&folderFlags); if (folderFlags & MSG_FOLDER_FLAG_OFFLINE) break; } } if (NS_FAILED(rv) || done) { if (m_listener) return m_listener->OnStopRunningUrl(nsnull, NS_OK); } m_downloadedHdrsForCurGroup = PR_TRUE; return m_currentFolder->GetNewMessages(m_window, this); } nsresult nsMsgDownloadAllNewsgroups::DownloadMsgsForCurrentGroup() { NS_ENSURE_TRUE(m_downloaderForGroup, NS_ERROR_OUT_OF_MEMORY); nsCOMPtr db; nsCOMPtr termList; nsCOMPtr downloadSettings; m_currentFolder->GetMsgDatabase(m_window, 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(PR_TRUE); if (!m_termList) { rv = NS_NewISupportsArray(getter_AddRefs(m_termList)); NS_ENSURE_SUCCESS(rv, rv); } nsCOMPtr searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); PRBool downloadByDate, downloadUnreadOnly; PRUint32 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(MSG_FLAG_READ); searchSession->AddSearchTerm(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, value, PR_TRUE, nsnull); } if (downloadByDate) { value->SetAttrib(nsMsgSearchAttrib::AgeInDays); value->SetAge(ageLimitOfMsgsToDownload); searchSession->AddSearchTerm(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, value, nsMsgSearchBooleanOp::BooleanAND, nsnull); } value->SetAttrib(nsMsgSearchAttrib::MsgStatus); value->SetStatus(MSG_FLAG_OFFLINE); searchSession->AddSearchTerm(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, value, nsMsgSearchBooleanOp::BooleanAND, nsnull); m_downloaderForGroup->RunSearch(m_currentFolder, db, searchSession); return rv; }