1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "msgCore.h"
7 #include "nntpCore.h"
8 #include "netCore.h"
9 #include "nsIMsgNewsFolder.h"
10 #include "nsIStringBundle.h"
11 #include "nsNewsDownloader.h"
12 #include "nsINntpService.h"
13 #include "nsMsgNewsCID.h"
14 #include "nsIMsgSearchSession.h"
15 #include "nsIMsgSearchTerm.h"
16 #include "nsIMsgAccountManager.h"
17 #include "nsMsgFolderFlags.h"
18 #include "nsIMsgMailSession.h"
19 #include "nsMsgMessageFlags.h"
20 #include "nsServiceManagerUtils.h"
21 #include "nsComponentManagerUtils.h"
22 #include "nsMsgUtils.h"
23 #include "mozilla/Services.h"
24
25 // This file contains the news article download state machine.
26
27 // if pIds is not null, download the articles whose id's are passed in.
28 // Otherwise, which articles to download is determined by nsNewsDownloader
29 // object, or subclasses thereof. News can download marked objects, for example.
DownloadArticles(nsIMsgWindow * window,nsIMsgFolder * folder,nsTArray<nsMsgKey> * pIds)30 nsresult nsNewsDownloader::DownloadArticles(nsIMsgWindow* window,
31 nsIMsgFolder* folder,
32 nsTArray<nsMsgKey>* pIds) {
33 if (pIds != nullptr)
34 m_keysToDownload.InsertElementsAt(0, pIds->Elements(), pIds->Length());
35
36 if (!m_keysToDownload.IsEmpty()) m_downloadFromKeys = true;
37
38 m_folder = folder;
39 m_window = window;
40 m_numwrote = 0;
41
42 bool headersToDownload = GetNextHdrToRetrieve();
43 // should we have a special error code for failure here?
44 return (headersToDownload) ? DownloadNext(true) : NS_ERROR_FAILURE;
45 }
46
47 /* Saving news messages
48 */
49
NS_IMPL_ISUPPORTS(nsNewsDownloader,nsIUrlListener,nsIMsgSearchNotify)50 NS_IMPL_ISUPPORTS(nsNewsDownloader, nsIUrlListener, nsIMsgSearchNotify)
51
52 nsNewsDownloader::nsNewsDownloader(nsIMsgWindow* window, nsIMsgDatabase* msgDB,
53 nsIUrlListener* listener) {
54 m_numwrote = 0;
55 m_downloadFromKeys = false;
56 m_newsDB = msgDB;
57 m_abort = false;
58 m_listener = listener;
59 m_window = window;
60 m_lastPercent = -1;
61 m_lastProgressTime = 0;
62 // not the perfect place for this, but I think it will work.
63 if (m_window) m_window->SetStopped(false);
64 }
65
~nsNewsDownloader()66 nsNewsDownloader::~nsNewsDownloader() {
67 if (m_listener)
68 m_listener->OnStopRunningUrl(/* don't have a url */ nullptr, m_status);
69 if (m_newsDB) {
70 m_newsDB->Commit(nsMsgDBCommitType::kLargeCommit);
71 m_newsDB = nullptr;
72 }
73 }
74
OnStartRunningUrl(nsIURI * url)75 NS_IMETHODIMP nsNewsDownloader::OnStartRunningUrl(nsIURI* url) { return NS_OK; }
76
OnStopRunningUrl(nsIURI * url,nsresult exitCode)77 NS_IMETHODIMP nsNewsDownloader::OnStopRunningUrl(nsIURI* url,
78 nsresult exitCode) {
79 bool stopped = false;
80 if (m_window) m_window->GetStopped(&stopped);
81 if (stopped) exitCode = NS_BINDING_ABORTED;
82
83 nsresult rv = exitCode;
84 if (NS_SUCCEEDED(exitCode) || exitCode == NS_MSG_NEWS_ARTICLE_NOT_FOUND)
85 rv = DownloadNext(false);
86
87 return rv;
88 }
89
DownloadNext(bool firstTimeP)90 nsresult nsNewsDownloader::DownloadNext(bool firstTimeP) {
91 nsresult rv;
92 if (!firstTimeP) {
93 bool moreHeaders = GetNextHdrToRetrieve();
94 if (!moreHeaders) {
95 if (m_listener) m_listener->OnStopRunningUrl(nullptr, NS_OK);
96 return NS_OK;
97 }
98 }
99 StartDownload();
100 m_wroteAnyP = false;
101 nsCOMPtr<nsINntpService> nntpService =
102 do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
103 NS_ENSURE_SUCCESS(rv, rv);
104
105 return nntpService->FetchMessage(m_folder, m_keyToDownload, m_window, nullptr,
106 this, nullptr);
107 }
108
GetNextHdrToRetrieve()109 bool DownloadNewsArticlesToOfflineStore::GetNextHdrToRetrieve() {
110 nsresult rv;
111
112 if (m_downloadFromKeys) return nsNewsDownloader::GetNextHdrToRetrieve();
113
114 if (m_headerEnumerator == nullptr)
115 rv = m_newsDB->EnumerateMessages(getter_AddRefs(m_headerEnumerator));
116
117 bool hasMore = false;
118
119 while (NS_SUCCEEDED(rv = m_headerEnumerator->HasMoreElements(&hasMore)) &&
120 hasMore) {
121 rv = m_headerEnumerator->GetNext(getter_AddRefs(m_newsHeader));
122 NS_ENSURE_SUCCESS(rv, false);
123 uint32_t hdrFlags;
124 m_newsHeader->GetFlags(&hdrFlags);
125 if (hdrFlags & nsMsgMessageFlags::Marked) {
126 m_newsHeader->GetMessageKey(&m_keyToDownload);
127 break;
128 } else {
129 m_newsHeader = nullptr;
130 }
131 }
132 return hasMore;
133 }
134
Abort()135 void nsNewsDownloader::Abort() {}
Complete()136 void nsNewsDownloader::Complete() {}
137
GetNextHdrToRetrieve()138 bool nsNewsDownloader::GetNextHdrToRetrieve() {
139 nsresult rv;
140 if (m_downloadFromKeys) {
141 if (m_numwrote >= (int32_t)m_keysToDownload.Length()) return false;
142
143 m_keyToDownload = m_keysToDownload[m_numwrote++];
144 int32_t percent;
145 percent = (100 * m_numwrote) / (int32_t)m_keysToDownload.Length();
146
147 int64_t nowMS = 0;
148 if (percent < 100) // always need to do 100%
149 {
150 nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
151 if (nowMS - m_lastProgressTime < 750) return true;
152 }
153
154 m_lastProgressTime = nowMS;
155 nsCOMPtr<nsIStringBundleService> bundleService =
156 mozilla::services::GetStringBundleService();
157 NS_ENSURE_TRUE(bundleService, false);
158 nsCOMPtr<nsIStringBundle> bundle;
159 rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
160 NS_ENSURE_SUCCESS(rv, false);
161
162 nsAutoString firstStr;
163 firstStr.AppendInt(m_numwrote);
164 nsAutoString totalStr;
165 totalStr.AppendInt(int(m_keysToDownload.Length()));
166 nsString prettyName;
167 nsString statusString;
168
169 m_folder->GetPrettyName(prettyName);
170
171 AutoTArray<nsString, 3> formatStrings = {firstStr, totalStr, prettyName};
172 rv = bundle->FormatStringFromName("downloadingArticlesForOffline",
173 formatStrings, statusString);
174 NS_ENSURE_SUCCESS(rv, false);
175 ShowProgress(statusString.get(), percent);
176 return true;
177 }
178 NS_ASSERTION(false, "shouldn't get here if we're not downloading from keys.");
179 return false; // shouldn't get here if we're not downloading from keys.
180 }
181
ShowProgress(const char16_t * progressString,int32_t percent)182 nsresult nsNewsDownloader::ShowProgress(const char16_t* progressString,
183 int32_t percent) {
184 if (!m_statusFeedback) {
185 if (m_window) m_window->GetStatusFeedback(getter_AddRefs(m_statusFeedback));
186 }
187 if (m_statusFeedback) {
188 m_statusFeedback->ShowStatusString(nsDependentString(progressString));
189 if (percent != m_lastPercent) {
190 m_statusFeedback->ShowProgress(percent);
191 m_lastPercent = percent;
192 }
193 }
194 return NS_OK;
195 }
196
OnStartRunningUrl(nsIURI * url)197 NS_IMETHODIMP DownloadNewsArticlesToOfflineStore::OnStartRunningUrl(
198 nsIURI* url) {
199 return NS_OK;
200 }
201
OnStopRunningUrl(nsIURI * url,nsresult exitCode)202 NS_IMETHODIMP DownloadNewsArticlesToOfflineStore::OnStopRunningUrl(
203 nsIURI* url, nsresult exitCode) {
204 m_status = exitCode;
205 if (m_newsHeader != nullptr) {
206 #ifdef DEBUG_bienvenu
207 // XP_Trace("finished retrieving %ld\n", m_newsHeader->GetMessageKey());
208 #endif
209 if (m_newsDB) {
210 nsMsgKey msgKey;
211 m_newsHeader->GetMessageKey(&msgKey);
212 m_newsDB->MarkMarked(msgKey, false, nullptr);
213 }
214 }
215 m_newsHeader = nullptr;
216 return nsNewsDownloader::OnStopRunningUrl(url, exitCode);
217 }
218
FinishDownload()219 int DownloadNewsArticlesToOfflineStore::FinishDownload() { return 0; }
220
OnSearchHit(nsIMsgDBHdr * header,nsIMsgFolder * folder)221 NS_IMETHODIMP nsNewsDownloader::OnSearchHit(nsIMsgDBHdr* header,
222 nsIMsgFolder* folder) {
223 NS_ENSURE_ARG(header);
224
225 uint32_t msgFlags;
226 header->GetFlags(&msgFlags);
227 // only need to download articles we don't already have...
228 if (!(msgFlags & nsMsgMessageFlags::Offline)) {
229 nsMsgKey key;
230 header->GetMessageKey(&key);
231 m_keysToDownload.AppendElement(key);
232 }
233 return NS_OK;
234 }
235
OnSearchDone(nsresult status)236 NS_IMETHODIMP nsNewsDownloader::OnSearchDone(nsresult status) {
237 if (m_keysToDownload.IsEmpty()) {
238 if (m_listener) return m_listener->OnStopRunningUrl(nullptr, NS_OK);
239 }
240 nsresult rv = DownloadArticles(
241 m_window, m_folder,
242 /* we've already set m_keysToDownload, so don't pass it in */ nullptr);
243 if (NS_FAILED(rv))
244 if (m_listener) m_listener->OnStopRunningUrl(nullptr, rv);
245
246 return rv;
247 }
OnNewSearch()248 NS_IMETHODIMP nsNewsDownloader::OnNewSearch() { return NS_OK; }
249
StartDownload()250 int DownloadNewsArticlesToOfflineStore::StartDownload() {
251 m_newsDB->GetMsgHdrForKey(m_keyToDownload, getter_AddRefs(m_newsHeader));
252 return 0;
253 }
254
DownloadNewsArticlesToOfflineStore(nsIMsgWindow * window,nsIMsgDatabase * db,nsIUrlListener * listener)255 DownloadNewsArticlesToOfflineStore::DownloadNewsArticlesToOfflineStore(
256 nsIMsgWindow* window, nsIMsgDatabase* db, nsIUrlListener* listener)
257 : nsNewsDownloader(window, db, listener) {
258 m_newsDB = db;
259 }
260
~DownloadNewsArticlesToOfflineStore()261 DownloadNewsArticlesToOfflineStore::~DownloadNewsArticlesToOfflineStore() {}
262
DownloadMatchingNewsArticlesToNewsDB(nsIMsgWindow * window,nsIMsgFolder * folder,nsIMsgDatabase * newsDB,nsIUrlListener * listener)263 DownloadMatchingNewsArticlesToNewsDB::DownloadMatchingNewsArticlesToNewsDB(
264 nsIMsgWindow* window, nsIMsgFolder* folder, nsIMsgDatabase* newsDB,
265 nsIUrlListener* listener)
266 : DownloadNewsArticlesToOfflineStore(window, newsDB, listener) {
267 m_window = window;
268 m_folder = folder;
269 m_newsDB = newsDB;
270 m_downloadFromKeys = true; // search term matching means downloadFromKeys.
271 }
272
~DownloadMatchingNewsArticlesToNewsDB()273 DownloadMatchingNewsArticlesToNewsDB::~DownloadMatchingNewsArticlesToNewsDB() {}
274
NS_IMPL_ISUPPORTS(nsMsgDownloadAllNewsgroups,nsIUrlListener)275 NS_IMPL_ISUPPORTS(nsMsgDownloadAllNewsgroups, nsIUrlListener)
276
277 nsMsgDownloadAllNewsgroups::nsMsgDownloadAllNewsgroups(
278 nsIMsgWindow* window, nsIUrlListener* listener) {
279 m_window = window;
280 m_listener = listener;
281 m_downloaderForGroup =
282 new DownloadMatchingNewsArticlesToNewsDB(window, nullptr, nullptr, this);
283 m_downloadedHdrsForCurGroup = false;
284 }
285
~nsMsgDownloadAllNewsgroups()286 nsMsgDownloadAllNewsgroups::~nsMsgDownloadAllNewsgroups() {}
287
OnStartRunningUrl(nsIURI * url)288 NS_IMETHODIMP nsMsgDownloadAllNewsgroups::OnStartRunningUrl(nsIURI* url) {
289 return NS_OK;
290 }
291
292 NS_IMETHODIMP
OnStopRunningUrl(nsIURI * url,nsresult exitCode)293 nsMsgDownloadAllNewsgroups::OnStopRunningUrl(nsIURI* url, nsresult exitCode) {
294 nsresult rv = exitCode;
295 if (NS_SUCCEEDED(exitCode) || exitCode == NS_MSG_NEWS_ARTICLE_NOT_FOUND) {
296 if (m_downloadedHdrsForCurGroup) {
297 bool savingArticlesOffline = false;
298 nsCOMPtr<nsIMsgNewsFolder> newsFolder =
299 do_QueryInterface(m_currentFolder);
300 if (newsFolder) newsFolder->GetSaveArticleOffline(&savingArticlesOffline);
301
302 m_downloadedHdrsForCurGroup = false;
303 if (savingArticlesOffline) // skip this group - we're saving to it
304 // already
305 rv = ProcessNextGroup();
306 else
307 rv = DownloadMsgsForCurrentGroup();
308 } else {
309 rv = ProcessNextGroup();
310 }
311 } else if (m_listener) // notify main observer.
312 m_listener->OnStopRunningUrl(url, exitCode);
313
314 return rv;
315 }
316
317 /**
318 * Leaves m_currentServer at the next nntp "server" that
319 * might have folders to download for offline use. If no more servers,
320 * m_currentServer will be left at nullptr and the function returns false.
321 * Also, sets up m_folderQueue to hold a (reversed) list of all the folders
322 * to consider for the current server.
323 * If no servers found, returns false.
324 */
AdvanceToNextServer()325 bool nsMsgDownloadAllNewsgroups::AdvanceToNextServer() {
326 nsresult rv;
327
328 if (m_allServers.IsEmpty()) {
329 nsCOMPtr<nsIMsgAccountManager> accountManager =
330 do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
331 NS_ASSERTION(accountManager && NS_SUCCEEDED(rv),
332 "couldn't get account mgr");
333 if (!accountManager || NS_FAILED(rv)) return false;
334
335 rv = accountManager->GetAllServers(m_allServers);
336 NS_ENSURE_SUCCESS(rv, false);
337 }
338 size_t serverIndex = 0;
339 if (m_currentServer) {
340 serverIndex = m_allServers.IndexOf(m_currentServer);
341 if (serverIndex == m_allServers.NoIndex) {
342 serverIndex = 0;
343 } else {
344 ++serverIndex;
345 }
346 }
347 m_currentServer = nullptr;
348 uint32_t numServers = m_allServers.Length();
349 nsCOMPtr<nsIMsgFolder> rootFolder;
350
351 while (serverIndex < numServers) {
352 nsCOMPtr<nsIMsgIncomingServer> server(m_allServers[serverIndex]);
353 serverIndex++;
354
355 nsCOMPtr<nsINntpIncomingServer> newsServer = do_QueryInterface(server);
356 if (!newsServer) // we're only looking for news servers
357 continue;
358
359 if (server) {
360 m_currentServer = server;
361 server->GetRootFolder(getter_AddRefs(rootFolder));
362 if (rootFolder) {
363 rv = rootFolder->GetDescendants(m_folderQueue);
364 if (NS_SUCCEEDED(rv)) {
365 if (!m_folderQueue.IsEmpty()) {
366 // We'll be popping folders from the end of the queue as we go.
367 m_folderQueue.Reverse();
368 return true;
369 }
370 }
371 }
372 }
373 }
374 return false;
375 }
376
377 /**
378 * Sets m_currentFolder to the next usable folder.
379 *
380 * @return False if no more folders found, otherwise true.
381 */
AdvanceToNextGroup()382 bool nsMsgDownloadAllNewsgroups::AdvanceToNextGroup() {
383 nsresult rv = NS_OK;
384
385 if (m_currentFolder) {
386 nsCOMPtr<nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_currentFolder);
387 if (newsFolder) newsFolder->SetSaveArticleOffline(false);
388
389 nsCOMPtr<nsIMsgMailSession> session =
390 do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
391 if (NS_SUCCEEDED(rv) && session) {
392 bool folderOpen;
393 uint32_t folderFlags;
394 m_currentFolder->GetFlags(&folderFlags);
395 session->IsFolderOpenInWindow(m_currentFolder, &folderOpen);
396 if (!folderOpen &&
397 !(folderFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
398 m_currentFolder->SetMsgDatabase(nullptr);
399 }
400 m_currentFolder = nullptr;
401 }
402
403 bool hasMore = false;
404 if (m_currentServer) {
405 hasMore = !m_folderQueue.IsEmpty();
406 }
407 if (!hasMore) {
408 hasMore = AdvanceToNextServer();
409 }
410
411 if (hasMore) {
412 m_currentFolder = m_folderQueue.PopLastElement();
413 }
414 return m_currentFolder;
415 }
416
RunSearch(nsIMsgFolder * folder,nsIMsgDatabase * newsDB,nsIMsgSearchSession * searchSession)417 nsresult DownloadMatchingNewsArticlesToNewsDB::RunSearch(
418 nsIMsgFolder* folder, nsIMsgDatabase* newsDB,
419 nsIMsgSearchSession* searchSession) {
420 m_folder = folder;
421 m_newsDB = newsDB;
422 m_searchSession = searchSession;
423
424 m_keysToDownload.Clear();
425
426 NS_ENSURE_ARG(searchSession);
427 NS_ENSURE_ARG(folder);
428
429 searchSession->RegisterListener(this, nsIMsgSearchSession::allNotifications);
430 nsresult rv =
431 searchSession->AddScopeTerm(nsMsgSearchScope::localNews, folder);
432 NS_ENSURE_SUCCESS(rv, rv);
433
434 return searchSession->Search(m_window);
435 }
436
ProcessNextGroup()437 nsresult nsMsgDownloadAllNewsgroups::ProcessNextGroup() {
438 bool done = false;
439
440 while (!done) {
441 done = !AdvanceToNextGroup();
442 if (!done && m_currentFolder) {
443 uint32_t folderFlags;
444 m_currentFolder->GetFlags(&folderFlags);
445 if (folderFlags & nsMsgFolderFlags::Offline) break;
446 }
447 }
448 if (done) {
449 if (m_listener) return m_listener->OnStopRunningUrl(nullptr, NS_OK);
450 }
451 m_downloadedHdrsForCurGroup = true;
452 return m_currentFolder ? m_currentFolder->GetNewMessages(m_window, this)
453 : NS_ERROR_NOT_INITIALIZED;
454 }
455
DownloadMsgsForCurrentGroup()456 nsresult nsMsgDownloadAllNewsgroups::DownloadMsgsForCurrentGroup() {
457 NS_ENSURE_TRUE(m_downloaderForGroup, NS_ERROR_OUT_OF_MEMORY);
458 nsCOMPtr<nsIMsgDatabase> db;
459 nsCOMPtr<nsIMsgDownloadSettings> downloadSettings;
460 m_currentFolder->GetMsgDatabase(getter_AddRefs(db));
461 nsresult rv =
462 m_currentFolder->GetDownloadSettings(getter_AddRefs(downloadSettings));
463 NS_ENSURE_SUCCESS(rv, rv);
464
465 nsCOMPtr<nsIMsgNewsFolder> newsFolder = do_QueryInterface(m_currentFolder);
466 if (newsFolder) newsFolder->SetSaveArticleOffline(true);
467
468 nsCOMPtr<nsIMsgSearchSession> searchSession =
469 do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
470 NS_ENSURE_SUCCESS(rv, rv);
471
472 bool downloadByDate, downloadUnreadOnly;
473 uint32_t ageLimitOfMsgsToDownload;
474
475 downloadSettings->GetDownloadByDate(&downloadByDate);
476 downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly);
477 downloadSettings->GetAgeLimitOfMsgsToDownload(&ageLimitOfMsgsToDownload);
478
479 nsCOMPtr<nsIMsgSearchTerm> term;
480 nsCOMPtr<nsIMsgSearchValue> value;
481
482 rv = searchSession->CreateTerm(getter_AddRefs(term));
483 NS_ENSURE_SUCCESS(rv, rv);
484 term->GetValue(getter_AddRefs(value));
485
486 if (downloadUnreadOnly) {
487 value->SetAttrib(nsMsgSearchAttrib::MsgStatus);
488 value->SetStatus(nsMsgMessageFlags::Read);
489 searchSession->AddSearchTerm(nsMsgSearchAttrib::MsgStatus,
490 nsMsgSearchOp::Isnt, value, true, nullptr);
491 }
492 if (downloadByDate) {
493 value->SetAttrib(nsMsgSearchAttrib::AgeInDays);
494 value->SetAge(ageLimitOfMsgsToDownload);
495 searchSession->AddSearchTerm(nsMsgSearchAttrib::AgeInDays,
496 nsMsgSearchOp::IsLessThan, value,
497 nsMsgSearchBooleanOp::BooleanAND, nullptr);
498 }
499 value->SetAttrib(nsMsgSearchAttrib::MsgStatus);
500 value->SetStatus(nsMsgMessageFlags::Offline);
501 searchSession->AddSearchTerm(nsMsgSearchAttrib::MsgStatus,
502 nsMsgSearchOp::Isnt, value,
503 nsMsgSearchBooleanOp::BooleanAND, nullptr);
504
505 m_downloaderForGroup->RunSearch(m_currentFolder, db, searchSession);
506 return rv;
507 }
508