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 // this file implements the nsMsgDatabase interface using the MDB Interface.
7 
8 #include "nscore.h"
9 #include "msgCore.h"
10 #include "nsIFile.h"
11 #include "nsMailDatabase.h"
12 #include "nsDBFolderInfo.h"
13 #include "nsIMsgNewsFolder.h"
14 #include "nsMsgThread.h"
15 #include "nsIMsgSearchTerm.h"
16 #include "nsMsgBaseCID.h"
17 #include "nsMorkCID.h"
18 #include "nsIMdbFactoryFactory.h"
19 #include "mozilla/Logging.h"
20 #include "mozilla/Telemetry.h"
21 #include "prprf.h"
22 #include "nsMsgDBCID.h"
23 #include "nsMsgMimeCID.h"
24 #include "nsMsgFolderFlags.h"
25 #include "nsIMsgAccountManager.h"
26 #include "nsIMsgDBView.h"
27 #include "nsIMsgFolderCache.h"
28 #include "nsIMsgFolderCacheElement.h"
29 #include "MailNewsTypes2.h"
30 #include "nsMsgUtils.h"
31 #include "nsComponentManagerUtils.h"
32 #include "nsServiceManagerUtils.h"
33 #include "nsMemory.h"
34 #include "nsICollation.h"
35 #include "nsCollationCID.h"
36 #include "nsIPrefService.h"
37 #include "nsIPrefBranch.h"
38 #include "nsMsgDatabaseEnumerators.h"
39 #include "nsIMemoryReporter.h"
40 #include "nsIWeakReferenceUtils.h"
41 #include "mozilla/mailnews/MimeHeaderParser.h"
42 #include "mozilla/mailnews/Services.h"
43 
44 using namespace mozilla::mailnews;
45 using namespace mozilla;
46 
47 #if defined(DEBUG_sspitzer_) || defined(DEBUG_seth_)
48 #  define DEBUG_MSGKEYSET 1
49 #endif
50 
51 #define MSG_HASH_SIZE 512
52 
53 // This will be used on discovery, since we don't know total.
54 const int32_t kMaxHdrsInCache = 512;
55 
56 // special keys
57 static const nsMsgKey kAllMsgHdrsTableKey = 1;
58 static const nsMsgKey kTableKeyForThreadOne = 0xfffffffe;
59 static const nsMsgKey kAllThreadsTableKey = 0xfffffffd;
60 static const nsMsgKey kFirstPseudoKey = 0xfffffff0;
61 static const nsMsgKey kIdStartOfFake = 0xffffff80;
62 static const nsMsgKey kForceReparseKey = 0xfffffff0;
63 
64 LazyLogModule DBLog("MsgDB");
65 
66 PRTime nsMsgDatabase::gLastUseTime;
67 
NS_IMPL_ISUPPORTS(nsMsgDBService,nsIMsgDBService)68 NS_IMPL_ISUPPORTS(nsMsgDBService, nsIMsgDBService)
69 
70 nsMsgDBService::nsMsgDBService() {}
71 
~nsMsgDBService()72 nsMsgDBService::~nsMsgDBService() {
73 #ifdef DEBUG
74   // If you hit this warning, it means that some code is holding onto
75   // a db at shutdown.
76   NS_WARNING_ASSERTION(!m_dbCache.Length(), "some msg dbs left open");
77 #  ifndef MOZILLA_OFFICIAL
78   // Only print this on local builds since it causes crashes,
79   // see bug 1468691, bug 1377692 and bug 1342858.
80   for (uint32_t i = 0; i < m_dbCache.Length(); i++) {
81     nsMsgDatabase* pMessageDB = m_dbCache.ElementAt(i);
82     if (pMessageDB)
83       printf("db left open %s\n",
84              pMessageDB->m_dbFile->HumanReadablePath().get());
85   }
86 #  endif
87 #endif
88 }
89 
OpenFolderDB(nsIMsgFolder * aFolder,bool aLeaveInvalidDB,nsIMsgDatabase ** _retval)90 NS_IMETHODIMP nsMsgDBService::OpenFolderDB(nsIMsgFolder* aFolder,
91                                            bool aLeaveInvalidDB,
92                                            nsIMsgDatabase** _retval) {
93   NS_ENSURE_ARG(aFolder);
94   nsCOMPtr<nsIMsgIncomingServer> incomingServer;
95   nsresult rv = aFolder->GetServer(getter_AddRefs(incomingServer));
96   NS_ENSURE_SUCCESS(rv, rv);
97 
98   nsCOMPtr<nsIFile> summaryFilePath;
99   rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
100   NS_ENSURE_SUCCESS(rv, rv);
101 
102   nsMsgDatabase* cacheDB = FindInCache(summaryFilePath);
103   if (cacheDB) {
104     // this db could have ended up in the folder cache w/o an m_folder pointer
105     // via OpenMailDBFromFile. If so, take this chance to fix the folder.
106     if (!cacheDB->m_folder) cacheDB->m_folder = aFolder;
107     cacheDB->RememberLastUseTime();
108     *_retval = cacheDB;  // FindInCache already addRefed.
109     // if m_thumb is set, someone is asynchronously opening the db. But our
110     // caller wants to synchronously open it, so just do it.
111     if (cacheDB->m_thumb)
112       return cacheDB->Open(this, summaryFilePath, false, aLeaveInvalidDB);
113     return NS_OK;
114   }
115 
116   nsCString localDatabaseType;
117   incomingServer->GetLocalDatabaseType(localDatabaseType);
118   nsAutoCString dbContractID(NS_MSGDB_CONTRACTID);
119   dbContractID.Append(localDatabaseType.get());
120   nsCOMPtr<nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv);
121   NS_ENSURE_SUCCESS(rv, rv);
122 
123   // Don't try to create the database yet--let the createNewDB call do that.
124   nsMsgDatabase* msgDatabase = static_cast<nsMsgDatabase*>(msgDB.get());
125   msgDatabase->m_folder = aFolder;
126   rv = msgDatabase->Open(this, summaryFilePath, false, aLeaveInvalidDB);
127   if (NS_FAILED(rv) && rv != NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) return rv;
128 
129   NS_ADDREF(*_retval = msgDB);
130 
131   if (NS_FAILED(rv)) {
132 #ifdef DEBUG
133     // Doing these checks for debug only as we don't want to report certain
134     // errors in debug mode, but in release mode we wouldn't report them either
135 
136     // These errors are expected.
137     if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
138         rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
139       return rv;
140 
141     // If it isn't one of the expected errors, throw a warning.
142     NS_ENSURE_SUCCESS(rv, rv);
143 #endif
144     return rv;
145   }
146 
147   FinishDBOpen(aFolder, msgDatabase);
148   return rv;
149 }
150 
AsyncOpenFolderDB(nsIMsgFolder * aFolder,bool aLeaveInvalidDB,nsIMsgDatabase ** _retval)151 NS_IMETHODIMP nsMsgDBService::AsyncOpenFolderDB(nsIMsgFolder* aFolder,
152                                                 bool aLeaveInvalidDB,
153                                                 nsIMsgDatabase** _retval) {
154   NS_ENSURE_ARG(aFolder);
155 
156   nsCOMPtr<nsIFile> summaryFilePath;
157   nsresult rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
158   NS_ENSURE_SUCCESS(rv, rv);
159 
160   nsMsgDatabase* cacheDB = FindInCache(summaryFilePath);
161   if (cacheDB) {
162     // this db could have ended up in the folder cache w/o an m_folder pointer
163     // via OpenMailDBFromFile. If so, take this chance to fix the folder.
164     if (!cacheDB->m_folder) cacheDB->m_folder = aFolder;
165     *_retval = cacheDB;  // FindInCache already addRefed.
166     // We don't care if an other consumer is thumbing the store. In that
167     // case, they'll both thumb the store.
168     return NS_OK;
169   }
170 
171   nsCOMPtr<nsIMsgIncomingServer> incomingServer;
172   rv = aFolder->GetServer(getter_AddRefs(incomingServer));
173   NS_ENSURE_SUCCESS(rv, rv);
174   nsCString localDatabaseType;
175   incomingServer->GetLocalDatabaseType(localDatabaseType);
176   nsAutoCString dbContractID(NS_MSGDB_CONTRACTID);
177   dbContractID.Append(localDatabaseType.get());
178   nsCOMPtr<nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv);
179   NS_ENSURE_SUCCESS(rv, rv);
180 
181   nsMsgDatabase* msgDatabase = static_cast<nsMsgDatabase*>(msgDB.get());
182   rv = msgDatabase->OpenInternal(this, summaryFilePath, false, aLeaveInvalidDB,
183                                  false /* open asynchronously */);
184 
185   NS_ADDREF(*_retval = msgDB);
186   msgDatabase->m_folder = aFolder;
187 
188   if (NS_FAILED(rv)) {
189 #ifdef DEBUG
190     // Doing these checks for debug only as we don't want to report certain
191     // errors in debug mode, but in release mode we wouldn't report them either
192 
193     // These errors are expected.
194     if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
195         rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
196       return rv;
197 
198     // If it isn't one of the expected errors, throw a warning.
199     NS_ENSURE_SUCCESS(rv, rv);
200 #endif
201     return rv;
202   }
203 
204   FinishDBOpen(aFolder, msgDatabase);
205   return rv;
206 }
207 
OpenMore(nsIMsgDatabase * aDB,uint32_t aTimeHint,bool * _retval)208 NS_IMETHODIMP nsMsgDBService::OpenMore(nsIMsgDatabase* aDB, uint32_t aTimeHint,
209                                        bool* _retval) {
210   NS_ENSURE_ARG_POINTER(_retval);
211   nsMsgDatabase* msgDatabase = static_cast<nsMsgDatabase*>(aDB);
212   NS_ENSURE_TRUE(msgDatabase, NS_ERROR_INVALID_ARG);
213   // Check if this db has been opened.
214   if (!msgDatabase->m_thumb) {
215     *_retval = true;
216     return NS_OK;
217   }
218   nsresult rv;
219   *_retval = false;
220   PRIntervalTime startTime = PR_IntervalNow();
221   do {
222     mdb_count outTotal;        // total somethings to do in operation
223     mdb_count outCurrent;      // subportion of total completed so far
224     mdb_bool outDone = false;  // is operation finished?
225     mdb_bool outBroken;        // is operation irreparably dead and broken?
226     rv = msgDatabase->m_thumb->DoMore(msgDatabase->m_mdbEnv, &outTotal,
227                                       &outCurrent, &outDone, &outBroken);
228     if (NS_FAILED(rv)) break;
229     if (outDone) {
230       nsCOMPtr<nsIMdbFactory> mdbFactory;
231       rv = msgDatabase->GetMDBFactory(getter_AddRefs(mdbFactory));
232       NS_ENSURE_SUCCESS(rv, rv);
233       rv = mdbFactory->ThumbToOpenStore(msgDatabase->m_mdbEnv,
234                                         msgDatabase->m_thumb,
235                                         &msgDatabase->m_mdbStore);
236       msgDatabase->m_thumb = nullptr;
237       nsCOMPtr<nsIFile> folderPath;
238       (void)msgDatabase->m_folder->GetFilePath(getter_AddRefs(folderPath));
239       nsCOMPtr<nsIFile> summaryFile;
240       (void)GetSummaryFileLocation(folderPath, getter_AddRefs(summaryFile));
241 
242       if (NS_SUCCEEDED(rv))
243         rv = (msgDatabase->m_mdbStore) ? msgDatabase->InitExistingDB()
244                                        : NS_ERROR_FAILURE;
245       if (NS_SUCCEEDED(rv))
246         rv = msgDatabase->CheckForErrors(rv, false, this, summaryFile);
247 
248       FinishDBOpen(msgDatabase->m_folder, msgDatabase);
249       break;
250     }
251   } while (PR_IntervalToMilliseconds(PR_IntervalNow() - startTime) <=
252            aTimeHint);
253   *_retval = !msgDatabase->m_thumb;
254   return rv;
255 }
256 
257 /**
258  * When a db is opened, we need to hook up any pending listeners for
259  * that db, and notify them.
260  */
HookupPendingListeners(nsIMsgDatabase * db,nsIMsgFolder * folder)261 void nsMsgDBService::HookupPendingListeners(nsIMsgDatabase* db,
262                                             nsIMsgFolder* folder) {
263   for (int32_t listenerIndex = 0;
264        listenerIndex < m_foldersPendingListeners.Count(); listenerIndex++) {
265     //  check if we have a pending listener on this db, and if so, add it.
266     if (m_foldersPendingListeners[listenerIndex] == folder) {
267       db->AddListener(m_pendingListeners.ObjectAt(listenerIndex));
268       m_pendingListeners.ObjectAt(listenerIndex)->OnEvent(db, "DBOpened");
269     }
270   }
271 }
272 
FinishDBOpen(nsIMsgFolder * aFolder,nsMsgDatabase * aMsgDB)273 void nsMsgDBService::FinishDBOpen(nsIMsgFolder* aFolder,
274                                   nsMsgDatabase* aMsgDB) {
275   uint32_t folderFlags;
276   aFolder->GetFlags(&folderFlags);
277 
278   if (!(folderFlags & nsMsgFolderFlags::Virtual) &&
279       aMsgDB->m_mdbAllMsgHeadersTable) {
280     mdb_count numHdrsInTable = 0;
281     int32_t numMessages;
282     aMsgDB->m_mdbAllMsgHeadersTable->GetCount(aMsgDB->GetEnv(),
283                                               &numHdrsInTable);
284     aMsgDB->m_dbFolderInfo->GetNumMessages(&numMessages);
285     if (numMessages != (int32_t)numHdrsInTable) aMsgDB->SyncCounts();
286   }
287   HookupPendingListeners(aMsgDB, aFolder);
288   aMsgDB->RememberLastUseTime();
289 }
290 
291 //----------------------------------------------------------------------
292 // FindInCache - this addrefs the db it finds.
293 //----------------------------------------------------------------------
FindInCache(nsIFile * dbName)294 nsMsgDatabase* nsMsgDBService::FindInCache(nsIFile* dbName) {
295   for (uint32_t i = 0; i < m_dbCache.Length(); i++) {
296     nsMsgDatabase* pMessageDB = m_dbCache[i];
297     if (pMessageDB->MatchDbName(dbName)) {
298       if (pMessageDB->m_mdbStore)  // don't return db without store
299       {
300         NS_ADDREF(pMessageDB);
301         return pMessageDB;
302       }
303     }
304   }
305   return nullptr;
306 }
307 
308 // This method is called when the caller is trying to create a db without
309 // having a corresponding nsIMsgFolder object.  This happens in a few
310 // situations, including imap folder discovery, compacting local folders,
311 // and copying local folders.
OpenMailDBFromFile(nsIFile * aFolderName,nsIMsgFolder * aFolder,bool aCreate,bool aLeaveInvalidDB,nsIMsgDatabase ** pMessageDB)312 NS_IMETHODIMP nsMsgDBService::OpenMailDBFromFile(nsIFile* aFolderName,
313                                                  nsIMsgFolder* aFolder,
314                                                  bool aCreate,
315                                                  bool aLeaveInvalidDB,
316                                                  nsIMsgDatabase** pMessageDB) {
317   if (!aFolderName) return NS_ERROR_NULL_POINTER;
318 
319   nsCOMPtr<nsIFile> dbPath;
320   nsresult rv = GetSummaryFileLocation(aFolderName, getter_AddRefs(dbPath));
321   NS_ENSURE_SUCCESS(rv, rv);
322 
323   *pMessageDB = FindInCache(dbPath);
324   if (*pMessageDB) return NS_OK;
325 
326   RefPtr<nsMailDatabase> msgDB = new nsMailDatabase;
327   NS_ENSURE_TRUE(msgDB, NS_ERROR_OUT_OF_MEMORY);
328   rv = msgDB->Open(this, dbPath, aCreate, aLeaveInvalidDB);
329   if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) return rv;
330   NS_IF_ADDREF(*pMessageDB = msgDB);
331   if (aCreate && msgDB && rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) rv = NS_OK;
332   if (NS_SUCCEEDED(rv)) msgDB->m_folder = aFolder;
333   return rv;
334 }
335 
CreateNewDB(nsIMsgFolder * aFolder,nsIMsgDatabase ** _retval)336 NS_IMETHODIMP nsMsgDBService::CreateNewDB(nsIMsgFolder* aFolder,
337                                           nsIMsgDatabase** _retval) {
338   NS_ENSURE_ARG(aFolder);
339 
340   nsCOMPtr<nsIMsgIncomingServer> incomingServer;
341   nsresult rv = aFolder->GetServer(getter_AddRefs(incomingServer));
342   NS_ENSURE_SUCCESS(rv, rv);
343 
344   nsCOMPtr<nsIFile> summaryFilePath;
345   rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
346   NS_ENSURE_SUCCESS(rv, rv);
347 
348   nsCString localDatabaseType;
349   incomingServer->GetLocalDatabaseType(localDatabaseType);
350   nsAutoCString dbContractID(NS_MSGDB_CONTRACTID);
351   dbContractID.Append(localDatabaseType.get());
352 
353   nsCOMPtr<nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv);
354   NS_ENSURE_SUCCESS(rv, rv);
355 
356   nsMsgDatabase* msgDatabase = static_cast<nsMsgDatabase*>(msgDB.get());
357 
358   msgDatabase->m_folder = aFolder;
359   rv = msgDatabase->Open(this, summaryFilePath, true, true);
360 
361   // We are trying to create a new database, but that implies that it did not
362   // already exist. Open returns NS_MSG_ERROR_FOLDER_SUMMARY_MISSING for the
363   // successful creation of a new database. But if it existed for some
364   // reason, then we would get rv = NS_OK instead. That is a "failure"
365   // from our perspective, so we want to return a failure since we are not
366   // returning a valid database object.
367   NS_ENSURE_TRUE(rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING,
368                  NS_SUCCEEDED(rv) ? NS_ERROR_FILE_ALREADY_EXISTS : rv);
369 
370   NS_ADDREF(*_retval = msgDB);
371 
372   HookupPendingListeners(msgDB, aFolder);
373 
374   msgDatabase->RememberLastUseTime();
375 
376   return NS_OK;
377 }
378 
379 /* void registerPendingListener (in nsIMsgFolder aFolder, in nsIDBChangeListener
380  * aListener); */
RegisterPendingListener(nsIMsgFolder * aFolder,nsIDBChangeListener * aListener)381 NS_IMETHODIMP nsMsgDBService::RegisterPendingListener(
382     nsIMsgFolder* aFolder, nsIDBChangeListener* aListener) {
383   // need to make sure we don't hold onto these forever. Maybe a shutdown
384   // listener? if there is a db open on this folder already, we should register
385   // the listener.
386   m_foldersPendingListeners.AppendObject(aFolder);
387   m_pendingListeners.AppendObject(aListener);
388   nsCOMPtr<nsIMsgDatabase> openDB;
389   CachedDBForFolder(aFolder, getter_AddRefs(openDB));
390   if (openDB) openDB->AddListener(aListener);
391   return NS_OK;
392 }
393 
394 /* void unregisterPendingListener (in nsIDBChangeListener aListener); */
UnregisterPendingListener(nsIDBChangeListener * aListener)395 NS_IMETHODIMP nsMsgDBService::UnregisterPendingListener(
396     nsIDBChangeListener* aListener) {
397   int32_t listenerIndex = m_pendingListeners.IndexOfObject(aListener);
398   if (listenerIndex != -1) {
399     nsCOMPtr<nsIMsgDatabase> msgDB;
400     CachedDBForFolder(m_foldersPendingListeners[listenerIndex],
401                       getter_AddRefs(msgDB));
402     if (msgDB) msgDB->RemoveListener(aListener);
403     m_foldersPendingListeners.RemoveObjectAt(listenerIndex);
404     m_pendingListeners.RemoveObjectAt(listenerIndex);
405     return NS_OK;
406   }
407   return NS_ERROR_FAILURE;
408 }
409 
CachedDBForFolder(nsIMsgFolder * aFolder,nsIMsgDatabase ** aRetDB)410 NS_IMETHODIMP nsMsgDBService::CachedDBForFolder(nsIMsgFolder* aFolder,
411                                                 nsIMsgDatabase** aRetDB) {
412   NS_ENSURE_ARG_POINTER(aFolder);
413   NS_ENSURE_ARG_POINTER(aRetDB);
414 
415   nsCOMPtr<nsIFile> summaryFilePath;
416   nsresult rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
417   NS_ENSURE_SUCCESS(rv, rv);
418 
419   *aRetDB = FindInCache(summaryFilePath);
420   return NS_OK;
421 }
422 
ForceFolderDBClosed(nsIMsgFolder * aFolder)423 NS_IMETHODIMP nsMsgDBService::ForceFolderDBClosed(nsIMsgFolder* aFolder) {
424   nsCOMPtr<nsIMsgDatabase> mailDB;
425   nsresult rv = CachedDBForFolder(aFolder, getter_AddRefs(mailDB));
426   if (mailDB) {
427     mailDB->ForceClosed();
428   }
429   return rv;
430 }
431 
GetOpenDBs(nsTArray<RefPtr<nsIMsgDatabase>> & aOpenDBs)432 NS_IMETHODIMP nsMsgDBService::GetOpenDBs(
433     nsTArray<RefPtr<nsIMsgDatabase>>& aOpenDBs) {
434   aOpenDBs.Clear();
435   aOpenDBs.SetCapacity(m_dbCache.Length());
436   for (auto db : m_dbCache) {
437     aOpenDBs.AppendElement(db);
438   }
439   return NS_OK;
440 }
441 
442 static bool gGotGlobalPrefs = false;
443 static bool gThreadWithoutRe = true;
444 static bool gStrictThreading = false;
445 static bool gCorrectThreading = false;
446 
GetGlobalPrefs()447 void nsMsgDatabase::GetGlobalPrefs() {
448   if (!gGotGlobalPrefs) {
449     GetBoolPref("mail.thread_without_re", &gThreadWithoutRe);
450     GetBoolPref("mail.strict_threading", &gStrictThreading);
451     GetBoolPref("mail.correct_threading", &gCorrectThreading);
452     gGotGlobalPrefs = true;
453   }
454 }
455 
AddHdrToCache(nsIMsgDBHdr * hdr,nsMsgKey key)456 nsresult nsMsgDatabase::AddHdrToCache(
457     nsIMsgDBHdr* hdr, nsMsgKey key)  // do we want key? We could get it from hdr
458 {
459   if (m_bCacheHeaders) {
460     if (!m_cachedHeaders)
461       m_cachedHeaders = new PLDHashTable(
462           &gMsgDBHashTableOps, sizeof(struct MsgHdrHashElement), m_cacheSize);
463     if (m_cachedHeaders) {
464       if (key == nsMsgKey_None) hdr->GetMessageKey(&key);
465       if (m_cachedHeaders->EntryCount() > m_cacheSize) ClearHdrCache(true);
466       PLDHashEntryHdr* entry =
467           m_cachedHeaders->Add((void*)(uintptr_t)key, mozilla::fallible);
468       if (!entry) return NS_ERROR_OUT_OF_MEMORY;  // XXX out of memory
469 
470       MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry);
471       element->mHdr = hdr;
472       element->mKey = key;
473       NS_ADDREF(hdr);  // make the cache hold onto the header
474       return NS_OK;
475     }
476   }
477   return NS_ERROR_FAILURE;
478 }
479 
SetMsgHdrCacheSize(uint32_t aSize)480 NS_IMETHODIMP nsMsgDatabase::SetMsgHdrCacheSize(uint32_t aSize) {
481   m_cacheSize = aSize;
482   return NS_OK;
483 }
484 
GetMsgHdrCacheSize(uint32_t * aSize)485 NS_IMETHODIMP nsMsgDatabase::GetMsgHdrCacheSize(uint32_t* aSize) {
486   NS_ENSURE_ARG_POINTER(aSize);
487   *aSize = m_cacheSize;
488   return NS_OK;
489 }
490 
GetLastUseTime(PRTime * aTime)491 NS_IMETHODIMP nsMsgDatabase::GetLastUseTime(PRTime* aTime) {
492   NS_ENSURE_ARG_POINTER(aTime);
493   *aTime = m_lastUseTime;
494   return NS_OK;
495 }
496 
SetLastUseTime(PRTime aTime)497 NS_IMETHODIMP nsMsgDatabase::SetLastUseTime(PRTime aTime) {
498   gLastUseTime = m_lastUseTime = aTime;
499   return NS_OK;
500 }
501 
GetDatabaseSize(int64_t * _retval)502 NS_IMETHODIMP nsMsgDatabase::GetDatabaseSize(int64_t* _retval) {
503   NS_ENSURE_ARG_POINTER(_retval);
504 
505   nsresult rv;
506   bool exists;
507   NS_ENSURE_TRUE(m_dbFile, NS_ERROR_NULL_POINTER);
508   rv = m_dbFile->Exists(&exists);
509   if (NS_SUCCEEDED(rv)) {
510     if (exists)
511       rv = m_dbFile->GetFileSize(_retval);
512     else
513       *_retval = 0;
514   }
515 
516   return rv;
517 }
518 
ClearCachedHdrs()519 NS_IMETHODIMP nsMsgDatabase::ClearCachedHdrs() {
520   ClearCachedObjects(false);
521 #ifdef DEBUG_bienvenu1
522   if (mRefCnt > 1) {
523     NS_ASSERTION(false, "");
524     printf("someone's holding onto db - refs = %ld\n", mRefCnt);
525   }
526 #endif
527   return NS_OK;
528 }
529 
530 // Invalidate any outstanding message enumerators using this db.
InvalidateEnumerators()531 void nsMsgDatabase::InvalidateEnumerators() {
532   RefPtr<nsMsgDatabase> kungFuDeathGrip(this);
533   // Work in reverse, as the enumerators remove themselves from the list.
534   {
535     auto n = m_msgEnumerators.Length();
536     for (auto i = n; i > 0; --i) {
537       m_msgEnumerators[i - 1]->Invalidate();
538     }
539   }
540   // And again for thread enumerators.
541   {
542     auto n = m_threadEnumerators.Length();
543     for (auto i = n; i > 0; --i) {
544       m_threadEnumerators[i - 1]->Invalidate();
545     }
546   }
547 }
548 
FindExistingThread(nsMsgKey threadId)549 nsMsgThread* nsMsgDatabase::FindExistingThread(nsMsgKey threadId) {
550   uint32_t numThreads = m_threads.Length();
551   for (uint32_t i = 0; i < numThreads; i++)
552     if (m_threads[i]->m_threadKey == threadId) return m_threads[i];
553 
554   return nullptr;
555 }
556 
ClearThreads()557 void nsMsgDatabase::ClearThreads() {
558   // clear out existing threads
559   nsTArray<nsMsgThread*> copyThreads;
560   copyThreads.SwapElements(m_threads);
561 
562   uint32_t numThreads = copyThreads.Length();
563   for (uint32_t i = 0; i < numThreads; i++) copyThreads[i]->Clear();
564 }
565 
ClearCachedObjects(bool dbGoingAway)566 void nsMsgDatabase::ClearCachedObjects(bool dbGoingAway) {
567   ClearHdrCache(false);
568 #ifdef DEBUG_DavidBienvenu
569   if (m_headersInUse && m_headersInUse->EntryCount() > 0) {
570     NS_ASSERTION(false, "leaking headers");
571     printf("leaking %d headers in %s\n", m_headersInUse->EntryCount(),
572            m_dbFile->HumanReadablePath().get());
573   }
574 #endif
575   m_cachedThread = nullptr;
576   m_cachedThreadId = nsMsgKey_None;
577   // We should only clear the use hdr cache when the db is going away, or we
578   // could end up with multiple copies of the same logical msg hdr, which will
579   // lead to ref-counting problems.
580   if (dbGoingAway) {
581     ClearUseHdrCache();
582     ClearThreads();
583   }
584   m_thumb = nullptr;
585 }
586 
ClearHdrCache(bool reInit)587 nsresult nsMsgDatabase::ClearHdrCache(bool reInit) {
588   if (m_cachedHeaders) {
589     // save this away in case we renter this code.
590     PLDHashTable* saveCachedHeaders = m_cachedHeaders;
591     m_cachedHeaders = nullptr;
592     for (auto iter = saveCachedHeaders->Iter(); !iter.Done(); iter.Next()) {
593       auto element = static_cast<MsgHdrHashElement*>(iter.Get());
594       if (element) NS_IF_RELEASE(element->mHdr);
595     }
596 
597     if (reInit) {
598       saveCachedHeaders->ClearAndPrepareForLength(m_cacheSize);
599       m_cachedHeaders = saveCachedHeaders;
600     } else {
601       delete saveCachedHeaders;
602     }
603   }
604   return NS_OK;
605 }
606 
RemoveHdrFromCache(nsIMsgDBHdr * hdr,nsMsgKey key)607 nsresult nsMsgDatabase::RemoveHdrFromCache(nsIMsgDBHdr* hdr, nsMsgKey key) {
608   if (m_cachedHeaders) {
609     if (key == nsMsgKey_None) hdr->GetMessageKey(&key);
610 
611     PLDHashEntryHdr* entry =
612         m_cachedHeaders->Search((const void*)(uintptr_t)key);
613     if (entry) {
614       m_cachedHeaders->Remove((void*)(uintptr_t)key);
615       NS_RELEASE(hdr);  // get rid of extra ref the cache was holding.
616     }
617   }
618   return NS_OK;
619 }
620 
GetHdrFromUseCache(nsMsgKey key,nsIMsgDBHdr ** result)621 nsresult nsMsgDatabase::GetHdrFromUseCache(nsMsgKey key, nsIMsgDBHdr** result) {
622   if (!result) return NS_ERROR_NULL_POINTER;
623 
624   nsresult rv = NS_ERROR_FAILURE;
625 
626   *result = nullptr;
627 
628   if (m_headersInUse) {
629     PLDHashEntryHdr* entry =
630         m_headersInUse->Search((const void*)(uintptr_t)key);
631     if (entry) {
632       MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry);
633       *result = element->mHdr;
634     }
635     if (*result) {
636       NS_ADDREF(*result);
637       rv = NS_OK;
638     }
639   }
640   return rv;
641 }
642 
643 PLDHashTableOps nsMsgDatabase::gMsgDBHashTableOps = {
644     HashKey, MatchEntry, MoveEntry, ClearEntry, nullptr};
645 
646 // HashKey is supposed to maximize entropy in the low order bits, and the key
647 // as is, should do that.
HashKey(const void * aKey)648 PLDHashNumber nsMsgDatabase::HashKey(const void* aKey) {
649   return PLDHashNumber(NS_PTR_TO_INT32(aKey));
650 }
651 
MatchEntry(const PLDHashEntryHdr * aEntry,const void * aKey)652 bool nsMsgDatabase::MatchEntry(const PLDHashEntryHdr* aEntry,
653                                const void* aKey) {
654   const MsgHdrHashElement* hdr = static_cast<const MsgHdrHashElement*>(aEntry);
655   return aKey == (const void*)(uintptr_t)
656                      hdr->mKey;  // ### or get the key from the hdr...
657 }
658 
MoveEntry(PLDHashTable * aTable,const PLDHashEntryHdr * aFrom,PLDHashEntryHdr * aTo)659 void nsMsgDatabase::MoveEntry(PLDHashTable* aTable,
660                               const PLDHashEntryHdr* aFrom,
661                               PLDHashEntryHdr* aTo) {
662   new (KnownNotNull, aTo)
663       MsgHdrHashElement(std::move(*((MsgHdrHashElement*)aFrom)));
664 }
665 
ClearEntry(PLDHashTable * aTable,PLDHashEntryHdr * aEntry)666 void nsMsgDatabase::ClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry) {
667   MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(aEntry);
668   element->mHdr = nullptr;        // eh? Need to release this or not?
669   element->mKey = nsMsgKey_None;  // eh?
670 }
671 
AddHdrToUseCache(nsIMsgDBHdr * hdr,nsMsgKey key)672 nsresult nsMsgDatabase::AddHdrToUseCache(nsIMsgDBHdr* hdr, nsMsgKey key) {
673   if (!m_headersInUse) {
674     mdb_count numHdrs = MSG_HASH_SIZE;
675     if (m_mdbAllMsgHeadersTable)
676       m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrs);
677     m_headersInUse =
678         new PLDHashTable(&gMsgDBHashTableOps, sizeof(struct MsgHdrHashElement),
679                          std::max((mdb_count)MSG_HASH_SIZE, numHdrs));
680   }
681   if (m_headersInUse) {
682     if (key == nsMsgKey_None) hdr->GetMessageKey(&key);
683     PLDHashEntryHdr* entry =
684         m_headersInUse->Add((void*)(uintptr_t)key, mozilla::fallible);
685     if (!entry) return NS_ERROR_OUT_OF_MEMORY;  // XXX out of memory
686 
687     MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry);
688     element->mHdr = hdr;
689     element->mKey = key;
690     // the hash table won't add ref, we'll do it ourselves
691     // stand for the addref that CreateMsgHdr normally does.
692     NS_ADDREF(hdr);
693     return NS_OK;
694   }
695 
696   return NS_ERROR_OUT_OF_MEMORY;
697 }
698 
ClearUseHdrCache()699 nsresult nsMsgDatabase::ClearUseHdrCache() {
700   if (m_headersInUse) {
701     // clear mdb row pointers of any headers still in use, because the
702     // underlying db is going away.
703     for (auto iter = m_headersInUse->Iter(); !iter.Done(); iter.Next()) {
704       auto element = static_cast<const MsgHdrHashElement*>(iter.Get());
705       if (element && element->mHdr) {
706         nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(
707             element->mHdr);  // closed system, so this is ok
708         // clear out m_mdbRow member variable - the db is going away, which
709         // means that this member variable might very well point to a mork db
710         // that is gone.
711         NS_IF_RELEASE(msgHdr->m_mdbRow);
712         //    NS_IF_RELEASE(msgHdr->m_mdb);
713       }
714     }
715     delete m_headersInUse;
716     m_headersInUse = nullptr;
717   }
718   return NS_OK;
719 }
720 
RemoveHdrFromUseCache(nsIMsgDBHdr * hdr,nsMsgKey key)721 nsresult nsMsgDatabase::RemoveHdrFromUseCache(nsIMsgDBHdr* hdr, nsMsgKey key) {
722   if (m_headersInUse) {
723     if (key == nsMsgKey_None) hdr->GetMessageKey(&key);
724 
725     m_headersInUse->Remove((void*)(uintptr_t)key);
726   }
727   return NS_OK;
728 }
729 
CreateMsgHdr(nsIMdbRow * hdrRow,nsMsgKey key,nsIMsgDBHdr ** result)730 nsresult nsMsgDatabase::CreateMsgHdr(nsIMdbRow* hdrRow, nsMsgKey key,
731                                      nsIMsgDBHdr** result) {
732   NS_ENSURE_ARG_POINTER(hdrRow);
733   NS_ENSURE_ARG_POINTER(result);
734 
735   nsresult rv = GetHdrFromUseCache(key, result);
736   if (NS_SUCCEEDED(rv) && *result) {
737     hdrRow->Release();
738     return rv;
739   }
740 
741   nsMsgHdr* msgHdr = new nsMsgHdr(this, hdrRow);
742   if (!msgHdr) return NS_ERROR_OUT_OF_MEMORY;
743   msgHdr->SetMessageKey(key);
744   // don't need to addref here; GetHdrFromUseCache addrefs.
745   *result = msgHdr;
746 
747   AddHdrToCache(msgHdr, key);
748 
749   return NS_OK;
750 }
751 
AddListener(nsIDBChangeListener * aListener)752 NS_IMETHODIMP nsMsgDatabase::AddListener(nsIDBChangeListener* aListener) {
753   NS_ENSURE_ARG_POINTER(aListener);
754   m_ChangeListeners.AppendElementUnlessExists(aListener);
755   return NS_OK;
756 }
757 
RemoveListener(nsIDBChangeListener * aListener)758 NS_IMETHODIMP nsMsgDatabase::RemoveListener(nsIDBChangeListener* aListener) {
759   NS_ENSURE_ARG_POINTER(aListener);
760   m_ChangeListeners.RemoveElement(aListener);
761   return NS_OK;
762 }
763 
764 // XXX should we return rv for listener->propertyfunc_?
765 #define NOTIFY_LISTENERS(propertyfunc_, params_)                         \
766   PR_BEGIN_MACRO                                                         \
767   nsTObserverArray<nsCOMPtr<nsIDBChangeListener>>::ForwardIterator iter( \
768       m_ChangeListeners);                                                \
769   nsCOMPtr<nsIDBChangeListener> listener;                                \
770   while (iter.HasMore()) {                                               \
771     listener = iter.GetNext();                                           \
772     listener->propertyfunc_ params_;                                     \
773   }                                                                      \
774   PR_END_MACRO
775 
776 // change announcer methods - just broadcast to all listeners.
NotifyHdrChangeAll(nsIMsgDBHdr * aHdrChanged,uint32_t aOldFlags,uint32_t aNewFlags,nsIDBChangeListener * aInstigator)777 NS_IMETHODIMP nsMsgDatabase::NotifyHdrChangeAll(
778     nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags,
779     nsIDBChangeListener* aInstigator) {
780   // We will only notify the change if the header exists in the database.
781   // This allows database functions to be usable in both the case where the
782   // header is in the db, or the header is not so no notifications should be
783   // given.
784   nsMsgKey key;
785   bool inDb = false;
786   if (aHdrChanged) {
787     aHdrChanged->GetMessageKey(&key);
788     ContainsKey(key, &inDb);
789   }
790   if (inDb)
791     NOTIFY_LISTENERS(OnHdrFlagsChanged,
792                      (aHdrChanged, aOldFlags, aNewFlags, aInstigator));
793   return NS_OK;
794 }
795 
NotifyReadChanged(nsIDBChangeListener * aInstigator)796 NS_IMETHODIMP nsMsgDatabase::NotifyReadChanged(
797     nsIDBChangeListener* aInstigator) {
798   NOTIFY_LISTENERS(OnReadChanged, (aInstigator));
799   return NS_OK;
800 }
801 
NotifyJunkScoreChanged(nsIDBChangeListener * aInstigator)802 NS_IMETHODIMP nsMsgDatabase::NotifyJunkScoreChanged(
803     nsIDBChangeListener* aInstigator) {
804   NOTIFY_LISTENERS(OnJunkScoreChanged, (aInstigator));
805   return NS_OK;
806 }
807 
NotifyHdrDeletedAll(nsIMsgDBHdr * aHdrDeleted,nsMsgKey aParentKey,int32_t aFlags,nsIDBChangeListener * aInstigator)808 NS_IMETHODIMP nsMsgDatabase::NotifyHdrDeletedAll(
809     nsIMsgDBHdr* aHdrDeleted, nsMsgKey aParentKey, int32_t aFlags,
810     nsIDBChangeListener* aInstigator) {
811   NOTIFY_LISTENERS(OnHdrDeleted,
812                    (aHdrDeleted, aParentKey, aFlags, aInstigator));
813   return NS_OK;
814 }
815 
NotifyHdrAddedAll(nsIMsgDBHdr * aHdrAdded,nsMsgKey aParentKey,int32_t aFlags,nsIDBChangeListener * aInstigator)816 NS_IMETHODIMP nsMsgDatabase::NotifyHdrAddedAll(
817     nsIMsgDBHdr* aHdrAdded, nsMsgKey aParentKey, int32_t aFlags,
818     nsIDBChangeListener* aInstigator) {
819 #ifdef DEBUG_bienvenu1
820   printf("notifying add of %ld parent %ld\n", keyAdded, parentKey);
821 #endif
822   NOTIFY_LISTENERS(OnHdrAdded, (aHdrAdded, aParentKey, aFlags, aInstigator));
823   return NS_OK;
824 }
825 
NotifyParentChangedAll(nsMsgKey aKeyReparented,nsMsgKey aOldParent,nsMsgKey aNewParent,nsIDBChangeListener * aInstigator)826 NS_IMETHODIMP nsMsgDatabase::NotifyParentChangedAll(
827     nsMsgKey aKeyReparented, nsMsgKey aOldParent, nsMsgKey aNewParent,
828     nsIDBChangeListener* aInstigator) {
829   NOTIFY_LISTENERS(OnParentChanged,
830                    (aKeyReparented, aOldParent, aNewParent, aInstigator));
831   return NS_OK;
832 }
833 
NotifyAnnouncerGoingAway(void)834 NS_IMETHODIMP nsMsgDatabase::NotifyAnnouncerGoingAway(void) {
835   NOTIFY_LISTENERS(OnAnnouncerGoingAway, (this));
836   return NS_OK;
837 }
838 
MatchDbName(nsIFile * dbFile)839 bool nsMsgDatabase::MatchDbName(nsIFile* dbFile)  // returns true if they match
840 {
841   NS_ENSURE_TRUE(m_dbFile, false);
842   return dbFile->NativePath().Equals(m_dbFile->NativePath());
843 }
844 
AddToCache(nsMsgDatabase * pMessageDB)845 void nsMsgDBService::AddToCache(nsMsgDatabase* pMessageDB) {
846 #ifdef DEBUG_David_Bienvenu
847   NS_ASSERTION(m_dbCache.Length() < 50, "50 or more open db's");
848 #endif
849 #ifdef DEBUG
850   if (pMessageDB->m_folder) {
851     nsCOMPtr<nsIMsgDatabase> msgDB;
852     CachedDBForFolder(pMessageDB->m_folder, getter_AddRefs(msgDB));
853     NS_ASSERTION(!msgDB, "shouldn't have db in cache");
854   }
855 #endif
856   m_dbCache.AppendElement(pMessageDB);
857 }
858 
859 /**
860  * Log the open db's, and how many headers are in memory.
861  */
DumpCache()862 void nsMsgDBService::DumpCache() {
863   nsMsgDatabase* db = nullptr;
864   MOZ_LOG(DBLog, LogLevel::Info, ("%zu open DBs", m_dbCache.Length()));
865   for (uint32_t i = 0; i < m_dbCache.Length(); i++) {
866     db = m_dbCache.ElementAt(i);
867     MOZ_LOG(DBLog, LogLevel::Info,
868             ("%s - %" PRIu32 " hdrs in use",
869              db->m_dbFile->HumanReadablePath().get(),
870              db->m_headersInUse ? db->m_headersInUse->EntryCount() : 0));
871   }
872 }
873 
874 // Memory Reporting implementations
875 
SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const876 size_t nsMsgDatabase::SizeOfExcludingThis(
877     mozilla::MallocSizeOf aMallocSizeOf) const {
878   size_t totalSize = 0;
879   if (m_dbFolderInfo)
880     totalSize += m_dbFolderInfo->SizeOfExcludingThis(aMallocSizeOf);
881   if (m_mdbEnv) {
882     nsIMdbHeap* morkHeap = nullptr;
883     m_mdbEnv->GetHeap(&morkHeap);
884     if (morkHeap) totalSize += morkHeap->GetUsedSize();
885   }
886   totalSize += m_newSet.ShallowSizeOfExcludingThis(aMallocSizeOf);
887   totalSize += m_ChangeListeners.ShallowSizeOfExcludingThis(aMallocSizeOf);
888   totalSize += m_threads.ShallowSizeOfExcludingThis(aMallocSizeOf);
889   // We have two tables of header objects, but every header in m_cachedHeaders
890   // should be in m_headersInUse.
891   // double-counting...
892   size_t headerSize = 0;
893   if (m_headersInUse) {
894     headerSize = m_headersInUse->ShallowSizeOfIncludingThis(aMallocSizeOf);
895     for (auto iter = m_headersInUse->Iter(); !iter.Done(); iter.Next()) {
896       auto entry = static_cast<MsgHdrHashElement*>(iter.Get());
897       // Sigh, this is dangerous, but so long as this is a closed system, this
898       // is safe.
899       headerSize += static_cast<nsMsgHdr*>(entry->mHdr)
900                         ->SizeOfIncludingThis(aMallocSizeOf);
901     }
902   }
903   totalSize += headerSize;
904   if (m_msgReferences)
905     totalSize += m_msgReferences->ShallowSizeOfIncludingThis(aMallocSizeOf);
906   return totalSize;
907 }
908 
909 namespace mozilla {
910 namespace mailnews {
911 
912 MOZ_DEFINE_MALLOC_SIZE_OF(GetMallocSize)
913 
914 class MsgDBReporter final : public nsIMemoryReporter {
915   nsWeakPtr mDatabase;
916 
917  public:
MsgDBReporter(nsMsgDatabase * db)918   explicit MsgDBReporter(nsMsgDatabase* db)
919       : mDatabase(do_GetWeakReference(db)) {}
920 
921   NS_DECL_ISUPPORTS
GetName(nsACString & aName)922   NS_IMETHOD GetName(nsACString& aName) {
923     aName.AssignLiteral("msg-database-objects");
924     return NS_OK;
925   }
926 
CollectReports(nsIHandleReportCallback * aCb,nsISupports * aClosure,bool aAnonymize)927   NS_IMETHOD CollectReports(nsIHandleReportCallback* aCb, nsISupports* aClosure,
928                             bool aAnonymize) override {
929     nsCString path;
930     GetPath(path, aAnonymize);
931     nsCOMPtr<nsIMsgDatabase> database = do_QueryReferent(mDatabase);
932     nsMsgDatabase* db =
933         database ? static_cast<nsMsgDatabase*>(database.get()) : nullptr;
934     return aCb->Callback(EmptyCString(), path, nsIMemoryReporter::KIND_HEAP,
935                          nsIMemoryReporter::UNITS_BYTES,
936                          db ? db->SizeOfIncludingThis(GetMallocSize) : 0,
937                          "Memory used for the folder database."_ns, aClosure);
938   }
939 
GetPath(nsACString & memoryPath,bool aAnonymize)940   void GetPath(nsACString& memoryPath, bool aAnonymize) {
941     memoryPath.AssignLiteral("explicit/maildb/database(");
942     nsCOMPtr<nsIMsgDatabase> database = do_QueryReferent(mDatabase);
943     nsCOMPtr<nsIMsgFolder> folder;
944     if (database) database->GetFolder(getter_AddRefs(folder));
945     if (folder) {
946       if (aAnonymize)
947         memoryPath.AppendLiteral("<anonymized>");
948       else {
949         nsAutoCString folderURL;
950         folder->GetFolderURL(folderURL);
951         folderURL.ReplaceChar('/', '\\');
952         memoryPath += folderURL;
953       }
954     } else {
955       memoryPath.AppendLiteral("UNKNOWN-FOLDER");
956     }
957     memoryPath.Append(')');
958   }
959 
960  private:
~MsgDBReporter()961   ~MsgDBReporter() {}
962 };
963 
964 NS_IMPL_ISUPPORTS(MsgDBReporter, nsIMemoryReporter)
965 }  // namespace mailnews
966 }  // namespace mozilla
967 
nsMsgDatabase()968 nsMsgDatabase::nsMsgDatabase()
969     : m_dbFolderInfo(nullptr),
970       m_nextPseudoMsgKey(kFirstPseudoKey),
971       m_mdbEnv(nullptr),
972       m_mdbStore(nullptr),
973       m_mdbAllMsgHeadersTable(nullptr),
974       m_mdbAllThreadsTable(nullptr),
975       m_create(false),
976       m_leaveInvalidDB(false),
977       m_mdbTokensInitialized(false),
978       m_hdrRowScopeToken(0),
979       m_hdrTableKindToken(0),
980       m_threadTableKindToken(0),
981       m_subjectColumnToken(0),
982       m_senderColumnToken(0),
983       m_messageIdColumnToken(0),
984       m_referencesColumnToken(0),
985       m_recipientsColumnToken(0),
986       m_dateColumnToken(0),
987       m_messageSizeColumnToken(0),
988       m_flagsColumnToken(0),
989       m_priorityColumnToken(0),
990       m_labelColumnToken(0),
991       m_statusOffsetColumnToken(0),
992       m_numLinesColumnToken(0),
993       m_ccListColumnToken(0),
994       m_bccListColumnToken(0),
995       m_threadFlagsColumnToken(0),
996       m_threadIdColumnToken(0),
997       m_threadChildrenColumnToken(0),
998       m_threadUnreadChildrenColumnToken(0),
999       m_messageThreadIdColumnToken(0),
1000       m_threadSubjectColumnToken(0),
1001       m_messageCharSetColumnToken(0),
1002       m_threadParentColumnToken(0),
1003       m_threadRootKeyColumnToken(0),
1004       m_threadNewestMsgDateColumnToken(0),
1005       m_offlineMsgOffsetColumnToken(0),
1006       m_offlineMessageSizeColumnToken(0),
1007       m_headersInUse(nullptr),
1008       m_cachedHeaders(nullptr),
1009       m_bCacheHeaders(true),
1010       m_cachedThreadId(nsMsgKey_None),
1011       m_msgReferences(nullptr),
1012       m_cacheSize(kMaxHdrsInCache) {
1013   mMemReporter = new mozilla::mailnews::MsgDBReporter(this);
1014   mozilla::RegisterWeakMemoryReporter(mMemReporter);
1015 }
1016 
~nsMsgDatabase()1017 nsMsgDatabase::~nsMsgDatabase() {
1018   mozilla::UnregisterWeakMemoryReporter(mMemReporter);
1019   mMemReporter = nullptr;
1020   //  Close(FALSE);  // better have already been closed.
1021   ClearCachedObjects(true);
1022   InvalidateEnumerators();
1023   delete m_cachedHeaders;
1024   delete m_headersInUse;
1025 
1026   if (m_msgReferences) {
1027     delete m_msgReferences;
1028     m_msgReferences = nullptr;
1029   }
1030 
1031   MOZ_LOG(DBLog, LogLevel::Info,
1032           ("closing database    %s", m_dbFile->HumanReadablePath().get()));
1033 
1034   nsCOMPtr<nsIMsgDBService> serv(do_GetService(NS_MSGDB_SERVICE_CONTRACTID));
1035   if (serv) static_cast<nsMsgDBService*>(serv.get())->RemoveFromCache(this);
1036 
1037   // if the db folder info refers to the mdb db, we must clear it because
1038   // the reference will be a dangling one soon.
1039   if (m_dbFolderInfo) m_dbFolderInfo->ReleaseExternalReferences();
1040   m_dbFolderInfo = nullptr;
1041 
1042   if (m_mdbAllMsgHeadersTable) m_mdbAllMsgHeadersTable->Release();
1043 
1044   if (m_mdbAllThreadsTable) m_mdbAllThreadsTable->Release();
1045 
1046   if (m_mdbStore) m_mdbStore->Release();
1047 
1048   if (m_mdbEnv) {
1049     m_mdbEnv->Release();  //??? is this right?
1050     m_mdbEnv = nullptr;
1051   }
1052   m_ChangeListeners.Clear();
1053 }
1054 
NS_IMPL_ISUPPORTS(nsMsgDatabase,nsIMsgDatabase,nsIDBChangeAnnouncer)1055 NS_IMPL_ISUPPORTS(nsMsgDatabase, nsIMsgDatabase, nsIDBChangeAnnouncer)
1056 
1057 nsresult nsMsgDatabase::GetMDBFactory(nsIMdbFactory** aMdbFactory) {
1058   if (!mMdbFactory) {
1059     nsresult rv;
1060     nsCOMPtr<nsIMdbFactoryService> mdbFactoryService =
1061         do_GetService(NS_MORK_CONTRACTID, &rv);
1062     if (NS_SUCCEEDED(rv) && mdbFactoryService) {
1063       rv = mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory));
1064       NS_ENSURE_SUCCESS(rv, rv);
1065       if (!mMdbFactory) return NS_ERROR_FAILURE;
1066     }
1067   }
1068   NS_ADDREF(*aMdbFactory = mMdbFactory);
1069   return NS_OK;
1070 }
1071 
1072 // aLeaveInvalidDB: true if caller wants back a db even out of date.
1073 // If so, they'll extract out the interesting info from the db, close it,
1074 // delete it, and then try to open the db again, prior to reparsing.
Open(nsMsgDBService * aDBService,nsIFile * aFolderName,bool aCreate,bool aLeaveInvalidDB)1075 nsresult nsMsgDatabase::Open(nsMsgDBService* aDBService, nsIFile* aFolderName,
1076                              bool aCreate, bool aLeaveInvalidDB) {
1077   return nsMsgDatabase::OpenInternal(aDBService, aFolderName, aCreate,
1078                                      aLeaveInvalidDB,
1079                                      true /* open synchronously */);
1080 }
1081 
OpenInternal(nsMsgDBService * aDBService,nsIFile * summaryFile,bool aCreate,bool aLeaveInvalidDB,bool sync)1082 nsresult nsMsgDatabase::OpenInternal(nsMsgDBService* aDBService,
1083                                      nsIFile* summaryFile, bool aCreate,
1084                                      bool aLeaveInvalidDB, bool sync) {
1085   MOZ_LOG(DBLog, LogLevel::Info,
1086           ("nsMsgDatabase::Open(%s, %s, %p, %s)",
1087            summaryFile->HumanReadablePath().get(), aCreate ? "TRUE" : "FALSE",
1088            this, aLeaveInvalidDB ? "TRUE" : "FALSE"));
1089 
1090   nsresult rv = OpenMDB(summaryFile, aCreate, sync);
1091   if (NS_FAILED(rv))
1092     MOZ_LOG(DBLog, LogLevel::Info,
1093             ("error opening db %" PRIx32, static_cast<uint32_t>(rv)));
1094 
1095   if (MOZ_LOG_TEST(DBLog, LogLevel::Debug)) aDBService->DumpCache();
1096 
1097   if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) return rv;
1098 
1099   m_create = aCreate;
1100   m_leaveInvalidDB = aLeaveInvalidDB;
1101   if (!sync && NS_SUCCEEDED(rv)) {
1102     aDBService->AddToCache(this);
1103     // remember open options for when the parsing is complete.
1104     return rv;
1105   }
1106   return CheckForErrors(rv, true, aDBService, summaryFile);
1107 }
1108 
CheckForErrors(nsresult err,bool sync,nsMsgDBService * aDBService,nsIFile * summaryFile)1109 nsresult nsMsgDatabase::CheckForErrors(nsresult err, bool sync,
1110                                        nsMsgDBService* aDBService,
1111                                        nsIFile* summaryFile) {
1112   nsCOMPtr<nsIDBFolderInfo> folderInfo;
1113   bool summaryFileExists;
1114   bool newFile = false;
1115   bool deleteInvalidDB = false;
1116 
1117   bool exists;
1118   int64_t fileSize;
1119   summaryFile->Exists(&exists);
1120   summaryFile->GetFileSize(&fileSize);
1121   // if the old summary doesn't exist, we're creating a new one.
1122   if ((!exists || !fileSize) && m_create) newFile = true;
1123 
1124   summaryFileExists = exists && fileSize > 0;
1125 
1126   if (NS_SUCCEEDED(err)) {
1127     if (!m_dbFolderInfo) {
1128       err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
1129     } else {
1130       if (!newFile && summaryFileExists) {
1131         bool valid = false;
1132         nsresult rv = GetSummaryValid(&valid);
1133         if (NS_FAILED(rv) || !valid)
1134           err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
1135       }
1136       // compare current version of db versus filed out version info.
1137       uint32_t version;
1138       m_dbFolderInfo->GetVersion(&version);
1139       if (GetCurVersion() != version)
1140         err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
1141 
1142       // Check if we should force a reparse because, for example, we have
1143       // reached the key limit.
1144       bool forceReparse;
1145       m_dbFolderInfo->GetBooleanProperty("forceReparse", false, &forceReparse);
1146       if (forceReparse) {
1147         NS_WARNING("Forcing a reparse presumably because key limit reached");
1148         err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
1149       }
1150     }
1151     if (NS_FAILED(err) && !m_leaveInvalidDB) deleteInvalidDB = true;
1152   } else {
1153     err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
1154     deleteInvalidDB = true;
1155   }
1156 
1157   if (deleteInvalidDB) {
1158     // this will make the db folder info release its ref to the mail db...
1159     m_dbFolderInfo = nullptr;
1160     ForceClosed();
1161     if (err == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
1162       summaryFile->Remove(false);
1163   }
1164   if (NS_FAILED(err) || newFile) {
1165     // if we couldn't open file, or we have a blank one, and we're supposed
1166     // to upgrade, upgrade it.
1167     if (newFile && !m_leaveInvalidDB)  // caller is upgrading, and we have empty
1168                                        // summary file,
1169     {  // leave db around and open so caller can upgrade it.
1170       err = NS_MSG_ERROR_FOLDER_SUMMARY_MISSING;
1171     } else if (NS_FAILED(err) &&
1172                err != NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) {
1173       Close(false);
1174       summaryFile->Remove(false);  // blow away the db if it's corrupt.
1175     }
1176   }
1177   if (sync && (NS_SUCCEEDED(err) || err == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING))
1178     aDBService->AddToCache(this);
1179   return (summaryFileExists) ? err : NS_MSG_ERROR_FOLDER_SUMMARY_MISSING;
1180 }
1181 
1182 /**
1183  * Open the MDB database synchronously or async based on sync argument.
1184  * If successful, this routine will set up the m_mdbStore and m_mdbEnv of
1185  * the database object so other database calls can work.
1186  */
OpenMDB(nsIFile * dbFile,bool create,bool sync)1187 nsresult nsMsgDatabase::OpenMDB(nsIFile* dbFile, bool create, bool sync) {
1188   nsCOMPtr<nsIMdbFactory> mdbFactory;
1189   nsresult ret = GetMDBFactory(getter_AddRefs(mdbFactory));
1190   NS_ENSURE_SUCCESS(ret, ret);
1191 
1192   ret = mdbFactory->MakeEnv(NULL, &m_mdbEnv);
1193   if (NS_SUCCEEDED(ret)) {
1194     nsIMdbHeap* dbHeap = nullptr;
1195 
1196     if (m_mdbEnv) m_mdbEnv->SetAutoClear(true);
1197     PathString dbName = dbFile->NativePath();
1198     ret = dbFile->Clone(getter_AddRefs(m_dbFile));
1199     NS_ENSURE_SUCCESS(ret, ret);
1200     bool exists = false;
1201     ret = dbFile->Exists(&exists);
1202     if (!exists) {
1203       ret = NS_MSG_ERROR_FOLDER_SUMMARY_MISSING;
1204     }
1205     // If m_thumb is set, we're asynchronously opening the db already.
1206     else if (!m_thumb) {
1207       mdbOpenPolicy inOpenPolicy;
1208       mdb_bool canOpen;
1209       mdbYarn outFormatVersion;
1210 
1211       nsIMdbFile* oldFile = nullptr;
1212       ret = mdbFactory->OpenOldFile(
1213           m_mdbEnv, dbHeap, dbName.get(),
1214           mdbBool_kFalse,  // not readonly, we want modifiable
1215           &oldFile);
1216       if (oldFile) {
1217         if (NS_SUCCEEDED(ret)) {
1218           ret = mdbFactory->CanOpenFilePort(m_mdbEnv,
1219                                             oldFile,  // the file to investigate
1220                                             &canOpen, &outFormatVersion);
1221           if (NS_SUCCEEDED(ret) && canOpen) {
1222             inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
1223             inOpenPolicy.mOpenPolicy_MinMemory = 0;
1224             inOpenPolicy.mOpenPolicy_MaxLazy = 0;
1225 
1226             ret = mdbFactory->OpenFileStore(m_mdbEnv, dbHeap, oldFile,
1227                                             &inOpenPolicy,
1228                                             getter_AddRefs(m_thumb));
1229           } else
1230             ret = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
1231         }
1232         NS_RELEASE(oldFile);  // always release our file ref, store has own
1233       }
1234     }
1235     if (NS_SUCCEEDED(ret) && m_thumb && sync) {
1236       mdb_count outTotal;        // total somethings to do in operation
1237       mdb_count outCurrent;      // subportion of total completed so far
1238       mdb_bool outDone = false;  // is operation finished?
1239       mdb_bool outBroken;        // is operation irreparably dead and broken?
1240       do {
1241         ret = m_thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone,
1242                               &outBroken);
1243         if (NS_FAILED(ret)) {  // mork isn't really doing NS errors yet.
1244           outDone = true;
1245           break;
1246         }
1247       } while (NS_SUCCEEDED(ret) && !outBroken && !outDone);
1248       //        m_mdbEnv->ClearErrors(); // ### temporary...
1249       // only 0 is a non-error return.
1250       if (NS_SUCCEEDED(ret) && outDone) {
1251         ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, m_thumb, &m_mdbStore);
1252         if (NS_SUCCEEDED(ret))
1253           ret = (m_mdbStore) ? InitExistingDB() : NS_ERROR_FAILURE;
1254       }
1255 #ifdef DEBUG_bienvenu1
1256       DumpContents();
1257 #endif
1258       m_thumb = nullptr;
1259     } else if (create)  // ### need error code saying why open file store failed
1260     {
1261       nsIMdbFile* newFile = 0;
1262       ret = mdbFactory->CreateNewFile(m_mdbEnv, dbHeap, dbName.get(), &newFile);
1263       if (NS_FAILED(ret)) ret = NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
1264       if (newFile) {
1265         if (NS_SUCCEEDED(ret)) {
1266           mdbOpenPolicy inOpenPolicy;
1267 
1268           inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
1269           inOpenPolicy.mOpenPolicy_MinMemory = 0;
1270           inOpenPolicy.mOpenPolicy_MaxLazy = 0;
1271 
1272           ret = mdbFactory->CreateNewFileStore(m_mdbEnv, dbHeap, newFile,
1273                                                &inOpenPolicy, &m_mdbStore);
1274           if (NS_SUCCEEDED(ret))
1275             ret = (m_mdbStore) ? InitNewDB() : NS_ERROR_FAILURE;
1276         }
1277         NS_RELEASE(newFile);  // always release our file ref, store has own
1278       }
1279     }
1280   }
1281 
1282   return ret;
1283 }
1284 
CloseMDB(bool commit)1285 nsresult nsMsgDatabase::CloseMDB(bool commit) {
1286   if (commit) Commit(nsMsgDBCommitType::kSessionCommit);
1287   return (NS_OK);
1288 }
1289 
1290 // force the database to close - this'll flush out anybody holding onto
1291 // a database without having a listener!
1292 // This is evil in the com world, but there are times we need to delete the
1293 // file.
ForceClosed()1294 NS_IMETHODIMP nsMsgDatabase::ForceClosed() {
1295   nsresult err = NS_OK;
1296 
1297   // make sure someone has a reference so object won't get deleted out from
1298   // under us.
1299   NS_ADDREF_THIS();
1300   NotifyAnnouncerGoingAway();
1301   // make sure dbFolderInfo isn't holding onto mork stuff because mork db is
1302   // going away
1303   if (m_dbFolderInfo) m_dbFolderInfo->ReleaseExternalReferences();
1304   m_dbFolderInfo = nullptr;
1305 
1306   err = CloseMDB(true);  // Backup DB will try to recover info, so commit
1307   ClearCachedObjects(true);
1308   InvalidateEnumerators();
1309   if (m_mdbAllMsgHeadersTable) {
1310     m_mdbAllMsgHeadersTable->Release();
1311     m_mdbAllMsgHeadersTable = nullptr;
1312   }
1313   if (m_mdbAllThreadsTable) {
1314     m_mdbAllThreadsTable->Release();
1315     m_mdbAllThreadsTable = nullptr;
1316   }
1317   if (m_mdbStore) {
1318     m_mdbStore->Release();
1319     m_mdbStore = nullptr;
1320   }
1321 
1322   // better not be any listeners, because we're going away.
1323   NS_ASSERTION(m_ChangeListeners.IsEmpty(),
1324                "shouldn't have any listeners left");
1325 
1326   NS_RELEASE_THIS();
1327   return err;
1328 }
1329 
GetDBFolderInfo(nsIDBFolderInfo ** result)1330 NS_IMETHODIMP nsMsgDatabase::GetDBFolderInfo(nsIDBFolderInfo** result) {
1331   if (!m_dbFolderInfo) {
1332     NS_ERROR("db must be corrupt");
1333     return NS_ERROR_NULL_POINTER;
1334   }
1335   NS_ADDREF(*result = m_dbFolderInfo);
1336   return NS_OK;
1337 }
1338 
GetFolder(nsIMsgFolder ** aFolder)1339 NS_IMETHODIMP nsMsgDatabase::GetFolder(nsIMsgFolder** aFolder) {
1340   NS_ENSURE_ARG_POINTER(aFolder);
1341   NS_IF_ADDREF(*aFolder = m_folder);
1342   return NS_OK;
1343 }
1344 
Commit(nsMsgDBCommit commitType)1345 NS_IMETHODIMP nsMsgDatabase::Commit(nsMsgDBCommit commitType) {
1346   nsresult err = NS_OK;
1347   nsCOMPtr<nsIMdbThumb> commitThumb;
1348 
1349   RememberLastUseTime();
1350   if (commitType == nsMsgDBCommitType::kLargeCommit ||
1351       commitType == nsMsgDBCommitType::kSessionCommit) {
1352     mdb_percent outActualWaste = 0;
1353     mdb_bool outShould;
1354     if (m_mdbStore) {
1355       err =
1356           m_mdbStore->ShouldCompress(GetEnv(), 30, &outActualWaste, &outShould);
1357       if (NS_SUCCEEDED(err) && outShould)
1358         commitType = nsMsgDBCommitType::kCompressCommit;
1359     }
1360   }
1361   //  commitType = nsMsgDBCommitType::kCompressCommit;  // ### until incremental
1362   //  writing works.
1363 
1364   if (m_mdbStore) {
1365     switch (commitType) {
1366       case nsMsgDBCommitType::kLargeCommit:
1367         err = m_mdbStore->LargeCommit(GetEnv(), getter_AddRefs(commitThumb));
1368         break;
1369       case nsMsgDBCommitType::kSessionCommit:
1370         err = m_mdbStore->SessionCommit(GetEnv(), getter_AddRefs(commitThumb));
1371         break;
1372       case nsMsgDBCommitType::kCompressCommit:
1373         err = m_mdbStore->CompressCommit(GetEnv(), getter_AddRefs(commitThumb));
1374         break;
1375     }
1376   }
1377   if (commitThumb) {
1378     mdb_count outTotal = 0;      // total somethings to do in operation
1379     mdb_count outCurrent = 0;    // subportion of total completed so far
1380     mdb_bool outDone = false;    // is operation finished?
1381     mdb_bool outBroken = false;  // is operation irreparably dead and broken?
1382     while (!outDone && !outBroken && NS_SUCCEEDED(err)) {
1383       err = commitThumb->DoMore(GetEnv(), &outTotal, &outCurrent, &outDone,
1384                                 &outBroken);
1385     }
1386   }
1387   // ### do something with error, but clear it now because mork errors out on
1388   // commits.
1389   if (GetEnv()) GetEnv()->ClearErrors();
1390 
1391   nsresult rv;
1392   nsCOMPtr<nsIMsgAccountManager> accountManager =
1393       do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
1394   if (NS_SUCCEEDED(rv) && accountManager) {
1395     nsCOMPtr<nsIMsgFolderCache> folderCache;
1396 
1397     rv = accountManager->GetFolderCache(getter_AddRefs(folderCache));
1398     if (NS_SUCCEEDED(rv) && folderCache) {
1399       nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
1400       nsCString persistentPath;
1401       NS_ENSURE_TRUE(m_dbFile, NS_ERROR_NULL_POINTER);
1402       rv = m_dbFile->GetPersistentDescriptor(persistentPath);
1403       NS_ENSURE_SUCCESS(rv, err);
1404       rv = folderCache->GetCacheElement(persistentPath, false,
1405                                         getter_AddRefs(cacheElement));
1406       if (NS_SUCCEEDED(rv) && cacheElement && m_dbFolderInfo) {
1407         int32_t totalMessages, unreadMessages, pendingMessages,
1408             pendingUnreadMessages;
1409 
1410         m_dbFolderInfo->GetNumMessages(&totalMessages);
1411         m_dbFolderInfo->GetNumUnreadMessages(&unreadMessages);
1412         m_dbFolderInfo->GetImapUnreadPendingMessages(&pendingUnreadMessages);
1413         m_dbFolderInfo->GetImapTotalPendingMessages(&pendingMessages);
1414         cacheElement->SetInt32Property("totalMsgs", totalMessages);
1415         cacheElement->SetInt32Property("totalUnreadMsgs", unreadMessages);
1416         cacheElement->SetInt32Property("pendingMsgs", pendingMessages);
1417         cacheElement->SetInt32Property("pendingUnreadMsgs",
1418                                        pendingUnreadMessages);
1419         folderCache->Commit(false);
1420       }
1421     }
1422   }
1423 
1424   return err;
1425 }
1426 
Close(bool forceCommit)1427 NS_IMETHODIMP nsMsgDatabase::Close(bool forceCommit /* = TRUE */) {
1428   InvalidateEnumerators();
1429   return CloseMDB(forceCommit);
1430 }
1431 
1432 const char* kMsgHdrsScope =
1433     "ns:msg:db:row:scope:msgs:all";  // scope for all headers table
1434 const char* kMsgHdrsTableKind = "ns:msg:db:table:kind:msgs";
1435 const char* kThreadTableKind = "ns:msg:db:table:kind:thread";
1436 const char* kThreadHdrsScope =
1437     "ns:msg:db:row:scope:threads:all";  // scope for all threads table
1438 const char* kAllThreadsTableKind =
1439     "ns:msg:db:table:kind:allthreads";  // kind for table of all threads
1440 const char* kSubjectColumnName = "subject";
1441 const char* kSenderColumnName = "sender";
1442 const char* kMessageIdColumnName = "message-id";
1443 const char* kReferencesColumnName = "references";
1444 const char* kRecipientsColumnName = "recipients";
1445 const char* kDateColumnName = "date";
1446 const char* kMessageSizeColumnName = "size";
1447 const char* kFlagsColumnName = "flags";
1448 const char* kPriorityColumnName = "priority";
1449 const char* kLabelColumnName = "label";
1450 const char* kStatusOffsetColumnName = "statusOfset";
1451 const char* kNumLinesColumnName = "numLines";
1452 const char* kCCListColumnName = "ccList";
1453 const char* kBCCListColumnName = "bccList";
1454 const char* kMessageThreadIdColumnName = "msgThreadId";
1455 const char* kThreadFlagsColumnName = "threadFlags";
1456 const char* kThreadIdColumnName = "threadId";
1457 const char* kThreadChildrenColumnName = "children";
1458 const char* kThreadUnreadChildrenColumnName = "unreadChildren";
1459 const char* kThreadSubjectColumnName = "threadSubject";
1460 const char* kMessageCharSetColumnName = "msgCharSet";
1461 const char* kThreadParentColumnName = "threadParent";
1462 const char* kThreadRootColumnName = "threadRoot";
1463 const char* kThreadNewestMsgDateColumnName = "threadNewestMsgDate";
1464 const char* kOfflineMsgOffsetColumnName = "msgOffset";
1465 const char* kOfflineMsgSizeColumnName = "offlineMsgSize";
1466 struct mdbOid gAllMsgHdrsTableOID;
1467 struct mdbOid gAllThreadsTableOID;
1468 const char* kFixedBadRefThreadingProp = "fixedBadRefThreading";
1469 
1470 // set up empty tables, dbFolderInfo, etc.
InitNewDB()1471 nsresult nsMsgDatabase::InitNewDB() {
1472   nsresult err = NS_OK;
1473 
1474   err = InitMDBInfo();
1475   if (NS_SUCCEEDED(err)) {
1476     nsDBFolderInfo* dbFolderInfo = new nsDBFolderInfo(this);
1477     if (dbFolderInfo) {
1478       err = dbFolderInfo->AddToNewMDB();
1479       dbFolderInfo->SetVersion(GetCurVersion());
1480       dbFolderInfo->SetBooleanProperty("forceReparse", false);
1481       dbFolderInfo->SetBooleanProperty(kFixedBadRefThreadingProp, true);
1482       nsIMdbStore* store = GetStore();
1483       // create the unique table for the dbFolderInfo.
1484       struct mdbOid allMsgHdrsTableOID;
1485       struct mdbOid allThreadsTableOID;
1486       if (!store) return NS_ERROR_NULL_POINTER;
1487 
1488       allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
1489       allMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey;
1490       allThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
1491       allThreadsTableOID.mOid_Id = kAllThreadsTableKey;
1492 
1493       // TODO: check this error value?
1494       (void)store->NewTableWithOid(GetEnv(), &allMsgHdrsTableOID,
1495                                    m_hdrTableKindToken, false, nullptr,
1496                                    &m_mdbAllMsgHeadersTable);
1497 
1498       // error here is not fatal.
1499       (void)store->NewTableWithOid(GetEnv(), &allThreadsTableOID,
1500                                    m_allThreadsTableKindToken, false, nullptr,
1501                                    &m_mdbAllThreadsTable);
1502 
1503       m_dbFolderInfo = dbFolderInfo;
1504 
1505     } else
1506       err = NS_ERROR_OUT_OF_MEMORY;
1507   }
1508   return err;
1509 }
1510 
GetTableCreateIfMissing(const char * scope,const char * kind,nsIMdbTable ** table,mdb_token & scopeToken,mdb_token & kindToken)1511 nsresult nsMsgDatabase::GetTableCreateIfMissing(const char* scope,
1512                                                 const char* kind,
1513                                                 nsIMdbTable** table,
1514                                                 mdb_token& scopeToken,
1515                                                 mdb_token& kindToken) {
1516   struct mdbOid tableOID;
1517 
1518   if (!m_mdbStore) return NS_ERROR_FAILURE;
1519   (void)m_mdbStore->StringToToken(GetEnv(), scope, &scopeToken);
1520   (void)m_mdbStore->StringToToken(GetEnv(), kind, &kindToken);
1521   tableOID.mOid_Scope = scopeToken;
1522   tableOID.mOid_Id = 1;
1523 
1524   nsresult rv = m_mdbStore->GetTable(GetEnv(), &tableOID, table);
1525   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
1526 
1527   // create new all all offline ops table, if it doesn't exist.
1528   if (NS_SUCCEEDED(rv) && !*table) {
1529     rv = m_mdbStore->NewTable(GetEnv(), scopeToken, kindToken, false, nullptr,
1530                               table);
1531     if (NS_FAILED(rv) || !*table) rv = NS_ERROR_FAILURE;
1532   }
1533   NS_ASSERTION(NS_SUCCEEDED(rv), "couldn't create offline ops table");
1534   return rv;
1535 }
1536 
InitExistingDB()1537 nsresult nsMsgDatabase::InitExistingDB() {
1538   nsresult err = NS_OK;
1539 
1540   err = InitMDBInfo();
1541   if (NS_SUCCEEDED(err)) {
1542     err = GetStore()->GetTable(GetEnv(), &gAllMsgHdrsTableOID,
1543                                &m_mdbAllMsgHeadersTable);
1544     if (NS_SUCCEEDED(err)) {
1545       m_dbFolderInfo = new nsDBFolderInfo(this);
1546       if (m_dbFolderInfo) {
1547         err = m_dbFolderInfo->InitFromExistingDB();
1548       }
1549     } else
1550       err = NS_ERROR_FAILURE;
1551 
1552     NS_ASSERTION(NS_SUCCEEDED(err), "failed initing existing db");
1553     NS_ENSURE_SUCCESS(err, err);
1554     // create new all msg hdrs table, if it doesn't exist.
1555     if (NS_SUCCEEDED(err) && !m_mdbAllMsgHeadersTable) {
1556       struct mdbOid allMsgHdrsTableOID;
1557       allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
1558       allMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey;
1559 
1560       nsresult mdberr = GetStore()->NewTableWithOid(
1561           GetEnv(), &allMsgHdrsTableOID, m_hdrTableKindToken, false, nullptr,
1562           &m_mdbAllMsgHeadersTable);
1563       if (NS_FAILED(mdberr) || !m_mdbAllMsgHeadersTable) err = NS_ERROR_FAILURE;
1564     }
1565     struct mdbOid allThreadsTableOID;
1566     allThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
1567     allThreadsTableOID.mOid_Id = kAllThreadsTableKey;
1568     err = GetStore()->GetTable(GetEnv(), &gAllThreadsTableOID,
1569                                &m_mdbAllThreadsTable);
1570     if (!m_mdbAllThreadsTable) {
1571       nsresult mdberr = GetStore()->NewTableWithOid(
1572           GetEnv(), &allThreadsTableOID, m_allThreadsTableKindToken, false,
1573           nullptr, &m_mdbAllThreadsTable);
1574       if (NS_FAILED(mdberr) || !m_mdbAllThreadsTable) err = NS_ERROR_FAILURE;
1575     }
1576   }
1577   if (NS_SUCCEEDED(err) && m_dbFolderInfo) {
1578     bool fixedBadRefThreading;
1579     m_dbFolderInfo->GetBooleanProperty(kFixedBadRefThreadingProp, false,
1580                                        &fixedBadRefThreading);
1581     if (!fixedBadRefThreading) {
1582       nsCOMPtr<nsIMsgEnumerator> enumerator;
1583       err = EnumerateMessages(getter_AddRefs(enumerator));
1584       if (NS_SUCCEEDED(err) && enumerator) {
1585         bool hasMore;
1586 
1587         while (NS_SUCCEEDED(err = enumerator->HasMoreElements(&hasMore)) &&
1588                hasMore) {
1589           nsCOMPtr<nsIMsgDBHdr> msgHdr;
1590           err = enumerator->GetNext(getter_AddRefs(msgHdr));
1591           if (msgHdr && NS_SUCCEEDED(err)) {
1592             nsCString messageId;
1593             nsAutoCString firstReference;
1594             msgHdr->GetMessageId(getter_Copies(messageId));
1595             msgHdr->GetStringReference(0, firstReference);
1596             if (messageId.Equals(firstReference)) {
1597               err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
1598               break;
1599             }
1600           }
1601         }
1602       }
1603 
1604       m_dbFolderInfo->SetBooleanProperty(kFixedBadRefThreadingProp, true);
1605     }
1606   }
1607   return err;
1608 }
1609 
1610 // initialize the various tokens and tables in our db's env
InitMDBInfo()1611 nsresult nsMsgDatabase::InitMDBInfo() {
1612   nsresult err = NS_OK;
1613 
1614   if (!m_mdbTokensInitialized && GetStore()) {
1615     m_mdbTokensInitialized = true;
1616     err =
1617         GetStore()->StringToToken(GetEnv(), kMsgHdrsScope, &m_hdrRowScopeToken);
1618     if (NS_SUCCEEDED(err)) {
1619       GetStore()->StringToToken(GetEnv(), kSubjectColumnName,
1620                                 &m_subjectColumnToken);
1621       GetStore()->StringToToken(GetEnv(), kSenderColumnName,
1622                                 &m_senderColumnToken);
1623       GetStore()->StringToToken(GetEnv(), kMessageIdColumnName,
1624                                 &m_messageIdColumnToken);
1625       // if we just store references as a string, we won't get any savings from
1626       // the fact there's a lot of duplication. So we may want to break them up
1627       // into multiple columns, r1, r2, etc.
1628       GetStore()->StringToToken(GetEnv(), kReferencesColumnName,
1629                                 &m_referencesColumnToken);
1630       // similarly, recipients could be tokenized properties
1631       GetStore()->StringToToken(GetEnv(), kRecipientsColumnName,
1632                                 &m_recipientsColumnToken);
1633       GetStore()->StringToToken(GetEnv(), kDateColumnName, &m_dateColumnToken);
1634       GetStore()->StringToToken(GetEnv(), kMessageSizeColumnName,
1635                                 &m_messageSizeColumnToken);
1636       GetStore()->StringToToken(GetEnv(), kFlagsColumnName,
1637                                 &m_flagsColumnToken);
1638       GetStore()->StringToToken(GetEnv(), kPriorityColumnName,
1639                                 &m_priorityColumnToken);
1640       GetStore()->StringToToken(GetEnv(), kLabelColumnName,
1641                                 &m_labelColumnToken);
1642       GetStore()->StringToToken(GetEnv(), kStatusOffsetColumnName,
1643                                 &m_statusOffsetColumnToken);
1644       GetStore()->StringToToken(GetEnv(), kNumLinesColumnName,
1645                                 &m_numLinesColumnToken);
1646       GetStore()->StringToToken(GetEnv(), kCCListColumnName,
1647                                 &m_ccListColumnToken);
1648       GetStore()->StringToToken(GetEnv(), kBCCListColumnName,
1649                                 &m_bccListColumnToken);
1650       GetStore()->StringToToken(GetEnv(), kMessageThreadIdColumnName,
1651                                 &m_messageThreadIdColumnToken);
1652       GetStore()->StringToToken(GetEnv(), kThreadIdColumnName,
1653                                 &m_threadIdColumnToken);
1654       GetStore()->StringToToken(GetEnv(), kThreadFlagsColumnName,
1655                                 &m_threadFlagsColumnToken);
1656       GetStore()->StringToToken(GetEnv(), kThreadNewestMsgDateColumnName,
1657                                 &m_threadNewestMsgDateColumnToken);
1658       GetStore()->StringToToken(GetEnv(), kThreadChildrenColumnName,
1659                                 &m_threadChildrenColumnToken);
1660       GetStore()->StringToToken(GetEnv(), kThreadUnreadChildrenColumnName,
1661                                 &m_threadUnreadChildrenColumnToken);
1662       GetStore()->StringToToken(GetEnv(), kThreadSubjectColumnName,
1663                                 &m_threadSubjectColumnToken);
1664       GetStore()->StringToToken(GetEnv(), kMessageCharSetColumnName,
1665                                 &m_messageCharSetColumnToken);
1666       err = GetStore()->StringToToken(GetEnv(), kMsgHdrsTableKind,
1667                                       &m_hdrTableKindToken);
1668       if (NS_SUCCEEDED(err))
1669         err = GetStore()->StringToToken(GetEnv(), kThreadTableKind,
1670                                         &m_threadTableKindToken);
1671       err = GetStore()->StringToToken(GetEnv(), kAllThreadsTableKind,
1672                                       &m_allThreadsTableKindToken);
1673       err = GetStore()->StringToToken(GetEnv(), kThreadHdrsScope,
1674                                       &m_threadRowScopeToken);
1675       err = GetStore()->StringToToken(GetEnv(), kThreadParentColumnName,
1676                                       &m_threadParentColumnToken);
1677       err = GetStore()->StringToToken(GetEnv(), kThreadRootColumnName,
1678                                       &m_threadRootKeyColumnToken);
1679       err = GetStore()->StringToToken(GetEnv(), kOfflineMsgOffsetColumnName,
1680                                       &m_offlineMsgOffsetColumnToken);
1681       err = GetStore()->StringToToken(GetEnv(), kOfflineMsgSizeColumnName,
1682                                       &m_offlineMessageSizeColumnToken);
1683 
1684       if (NS_SUCCEEDED(err)) {
1685         // The table of all message hdrs will have table id 1.
1686         gAllMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
1687         gAllMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey;
1688         gAllThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
1689         gAllThreadsTableOID.mOid_Id = kAllThreadsTableKey;
1690       }
1691     }
1692   }
1693   return err;
1694 }
1695 
1696 // Returns if the db contains this key
ContainsKey(nsMsgKey key,bool * containsKey)1697 NS_IMETHODIMP nsMsgDatabase::ContainsKey(nsMsgKey key, bool* containsKey) {
1698   nsresult err = NS_OK;
1699   mdb_bool hasOid;
1700   mdbOid rowObjectId;
1701 
1702   if (!containsKey || !m_mdbAllMsgHeadersTable) return NS_ERROR_NULL_POINTER;
1703   *containsKey = false;
1704 
1705   rowObjectId.mOid_Id = key;
1706   rowObjectId.mOid_Scope = m_hdrRowScopeToken;
1707   err = m_mdbAllMsgHeadersTable->HasOid(GetEnv(), &rowObjectId, &hasOid);
1708   if (NS_SUCCEEDED(err)) *containsKey = hasOid;
1709 
1710   return err;
1711 }
1712 
1713 // get a message header for the given key. Caller must release()!
GetMsgHdrForKey(nsMsgKey key,nsIMsgDBHdr ** pmsgHdr)1714 NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForKey(nsMsgKey key,
1715                                              nsIMsgDBHdr** pmsgHdr) {
1716   nsresult err = NS_OK;
1717   mdb_bool hasOid;
1718   mdbOid rowObjectId;
1719 
1720   // Because this may be called a lot, and we don't want gettimeofday() to show
1721   // up in trace logs, we just remember the most recent time any db was used,
1722   // which should be close enough for our purposes.
1723   m_lastUseTime = gLastUseTime;
1724 
1725 #ifdef DEBUG_bienvenu1
1726   NS_ASSERTION(m_folder, "folder should be set");
1727 #endif
1728 
1729   if (!pmsgHdr || !m_mdbAllMsgHeadersTable || !m_mdbStore)
1730     return NS_ERROR_NULL_POINTER;
1731 
1732   *pmsgHdr = NULL;
1733   err = GetHdrFromUseCache(key, pmsgHdr);
1734   if (NS_SUCCEEDED(err) && *pmsgHdr) return err;
1735 
1736   rowObjectId.mOid_Id = key;
1737   rowObjectId.mOid_Scope = m_hdrRowScopeToken;
1738   err = m_mdbAllMsgHeadersTable->HasOid(GetEnv(), &rowObjectId, &hasOid);
1739   if (NS_SUCCEEDED(err) /* && hasOid */) {
1740     nsIMdbRow* hdrRow;
1741     err = m_mdbStore->GetRow(GetEnv(), &rowObjectId, &hdrRow);
1742 
1743     if (NS_SUCCEEDED(err)) {
1744       if (!hdrRow) {
1745         err = NS_ERROR_NULL_POINTER;
1746       } else {
1747         //        NS_ASSERTION(hasOid, "we had oid, right?");
1748         err = CreateMsgHdr(hdrRow, key, pmsgHdr);
1749       }
1750     }
1751   }
1752 
1753   return err;
1754 }
1755 
DeleteMessage(nsMsgKey key,nsIDBChangeListener * instigator,bool commit)1756 NS_IMETHODIMP nsMsgDatabase::DeleteMessage(nsMsgKey key,
1757                                            nsIDBChangeListener* instigator,
1758                                            bool commit) {
1759   nsCOMPtr<nsIMsgDBHdr> msgHdr;
1760 
1761   nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
1762   if (!msgHdr) return NS_MSG_MESSAGE_NOT_FOUND;
1763 
1764   rv = DeleteHeader(msgHdr, instigator, commit, true);
1765   return rv;
1766 }
1767 
DeleteMessages(nsTArray<nsMsgKey> const & nsMsgKeys,nsIDBChangeListener * instigator)1768 NS_IMETHODIMP nsMsgDatabase::DeleteMessages(nsTArray<nsMsgKey> const& nsMsgKeys,
1769                                             nsIDBChangeListener* instigator) {
1770   nsresult err = NS_OK;
1771 
1772   uint32_t kindex;
1773   for (kindex = 0; kindex < nsMsgKeys.Length(); kindex++) {
1774     nsMsgKey key = nsMsgKeys[kindex];
1775     nsCOMPtr<nsIMsgDBHdr> msgHdr;
1776 
1777     bool hasKey;
1778 
1779     if (NS_SUCCEEDED(ContainsKey(key, &hasKey)) && hasKey) {
1780       err = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
1781       if (NS_FAILED(err)) {
1782         err = NS_MSG_MESSAGE_NOT_FOUND;
1783         break;
1784       }
1785       if (msgHdr)
1786         err = DeleteHeader(msgHdr, instigator, kindex % 300 == 0, true);
1787       if (NS_FAILED(err)) break;
1788     }
1789   }
1790   return err;
1791 }
1792 
AdjustExpungedBytesOnDelete(nsIMsgDBHdr * msgHdr)1793 nsresult nsMsgDatabase::AdjustExpungedBytesOnDelete(nsIMsgDBHdr* msgHdr) {
1794   uint32_t size = 0;
1795   (void)msgHdr->GetMessageSize(&size);
1796   return m_dbFolderInfo->ChangeExpungedBytes(size);
1797 }
1798 
DeleteHeader(nsIMsgDBHdr * msg,nsIDBChangeListener * instigator,bool commit,bool notify)1799 NS_IMETHODIMP nsMsgDatabase::DeleteHeader(nsIMsgDBHdr* msg,
1800                                           nsIDBChangeListener* instigator,
1801                                           bool commit, bool notify) {
1802   if (!msg) return NS_ERROR_NULL_POINTER;
1803 
1804   nsMsgHdr* msgHdr =
1805       static_cast<nsMsgHdr*>(msg);  // closed system, so this is ok
1806   nsMsgKey key;
1807   (void)msg->GetMessageKey(&key);
1808   // only need to do this for mail - will this speed up news expiration?
1809   SetHdrFlag(msg, true, nsMsgMessageFlags::Expunged);  // tell mailbox (mail)
1810 
1811   bool hdrWasNew = m_newSet.BinaryIndexOf(key) != m_newSet.NoIndex;
1812   m_newSet.RemoveElement(key);
1813 
1814   if (m_dbFolderInfo) {
1815     bool isRead;
1816     m_dbFolderInfo->ChangeNumMessages(-1);
1817     IsRead(key, &isRead);
1818     if (!isRead) m_dbFolderInfo->ChangeNumUnreadMessages(-1);
1819     AdjustExpungedBytesOnDelete(msg);
1820   }
1821 
1822   uint32_t flags;
1823   nsMsgKey threadParent;
1824 
1825   // Save off flags and threadparent since they will no longer exist after we
1826   // remove the header from the db.
1827   if (notify) {
1828     (void)msg->GetFlags(&flags);
1829     msg->GetThreadParent(&threadParent);
1830   }
1831 
1832   RemoveHeaderFromThread(msgHdr);
1833   if (notify) {
1834     // If deleted hdr was new, restore the new flag on flags
1835     // so saved searches will know to reduce their new msg count.
1836     if (hdrWasNew) flags |= nsMsgMessageFlags::New;
1837     NotifyHdrDeletedAll(msg, threadParent, flags,
1838                         instigator);  // tell listeners
1839   }
1840   //  if (!onlyRemoveFromThread)  // to speed up expiration, try this. But
1841   //  really need to do this in RemoveHeaderFromDB
1842   nsresult ret = RemoveHeaderFromDB(msgHdr);
1843 
1844   if (commit)
1845     Commit(nsMsgDBCommitType::kLargeCommit);  // ### dmb is this a good time to
1846                                               // commit?
1847   return ret;
1848 }
1849 
1850 NS_IMETHODIMP
UndoDelete(nsIMsgDBHdr * aMsgHdr)1851 nsMsgDatabase::UndoDelete(nsIMsgDBHdr* aMsgHdr) {
1852   if (aMsgHdr) {
1853     // Force deleted flag, so SetHdrFlag won't bail out because deleted flag
1854     // isn't set.
1855     uint32_t result;
1856     aMsgHdr->OrFlags(nsMsgMessageFlags::Expunged, &result);
1857     SetHdrFlag(aMsgHdr, false,
1858                nsMsgMessageFlags::Expunged);  // Clear deleted flag in db.
1859   }
1860   return NS_OK;
1861 }
1862 
RemoveHeaderFromThread(nsMsgHdr * msgHdr)1863 nsresult nsMsgDatabase::RemoveHeaderFromThread(nsMsgHdr* msgHdr) {
1864   if (!msgHdr) return NS_ERROR_NULL_POINTER;
1865   nsresult ret = NS_OK;
1866   nsCOMPtr<nsIMsgThread> thread;
1867   ret = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
1868   if (NS_SUCCEEDED(ret) && thread) {
1869     ret = thread->RemoveChildHdr(msgHdr, this);
1870   }
1871   return ret;
1872 }
1873 
RemoveHeaderMdbRow(nsIMsgDBHdr * msg)1874 NS_IMETHODIMP nsMsgDatabase::RemoveHeaderMdbRow(nsIMsgDBHdr* msg) {
1875   NS_ENSURE_ARG_POINTER(msg);
1876   nsMsgHdr* msgHdr =
1877       static_cast<nsMsgHdr*>(msg);  // closed system, so this is ok
1878   return RemoveHeaderFromDB(msgHdr);
1879 }
1880 
1881 // This is a lower level routine which doesn't send notifications or
1882 // update folder info. One use is when a rule fires moving a header
1883 // from one db to another, to remove it from the first db.
1884 
RemoveHeaderFromDB(nsMsgHdr * msgHdr)1885 nsresult nsMsgDatabase::RemoveHeaderFromDB(nsMsgHdr* msgHdr) {
1886   if (!msgHdr) return NS_ERROR_NULL_POINTER;
1887   nsresult ret = NS_OK;
1888 
1889   RemoveHdrFromCache(msgHdr, nsMsgKey_None);
1890   if (UseCorrectThreading()) RemoveMsgRefsFromHash(msgHdr);
1891   nsIMdbRow* row = msgHdr->GetMDBRow();
1892   if (row) {
1893     ret = m_mdbAllMsgHeadersTable->CutRow(GetEnv(), row);
1894     row->CutAllColumns(GetEnv());
1895   }
1896   msgHdr->ClearCachedValues();
1897   return ret;
1898 }
1899 
IsRead(nsMsgKey key,bool * pRead)1900 nsresult nsMsgDatabase::IsRead(nsMsgKey key, bool* pRead) {
1901   nsCOMPtr<nsIMsgDBHdr> msgHdr;
1902 
1903   nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
1904   if (NS_FAILED(rv) || !msgHdr)
1905     return NS_MSG_MESSAGE_NOT_FOUND;  // XXX return rv?
1906   rv = IsHeaderRead(msgHdr, pRead);
1907   return rv;
1908 }
1909 
GetStatusFlags(nsIMsgDBHdr * msgHdr,nsMsgMessageFlagType origFlags)1910 uint32_t nsMsgDatabase::GetStatusFlags(nsIMsgDBHdr* msgHdr,
1911                                        nsMsgMessageFlagType origFlags) {
1912   uint32_t statusFlags = origFlags;
1913   bool isRead = true;
1914 
1915   nsMsgKey key;
1916   (void)msgHdr->GetMessageKey(&key);
1917   if ((!m_newSet.IsEmpty() && m_newSet[m_newSet.Length() - 1] == key) ||
1918       (m_newSet.BinaryIndexOf(key) != m_newSet.NoIndex))
1919     statusFlags |= nsMsgMessageFlags::New;
1920   if (NS_SUCCEEDED(IsHeaderRead(msgHdr, &isRead)) && isRead)
1921     statusFlags |= nsMsgMessageFlags::Read;
1922   return statusFlags;
1923 }
1924 
IsHeaderRead(nsIMsgDBHdr * msgHdr,bool * pRead)1925 nsresult nsMsgDatabase::IsHeaderRead(nsIMsgDBHdr* msgHdr, bool* pRead) {
1926   if (!msgHdr) return NS_MSG_MESSAGE_NOT_FOUND;
1927 
1928   nsMsgHdr* hdr = static_cast<nsMsgHdr*>(msgHdr);  // closed system, cast ok
1929   // can't call GetFlags, because it will be recursive.
1930   uint32_t flags;
1931   hdr->GetRawFlags(&flags);
1932   *pRead = !!(flags & nsMsgMessageFlags::Read);
1933   return NS_OK;
1934 }
1935 
IsMarked(nsMsgKey key,bool * pMarked)1936 NS_IMETHODIMP nsMsgDatabase::IsMarked(nsMsgKey key, bool* pMarked) {
1937   nsCOMPtr<nsIMsgDBHdr> msgHdr;
1938 
1939   nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
1940   if (NS_FAILED(rv)) return NS_MSG_MESSAGE_NOT_FOUND;  // XXX return rv?
1941 
1942   uint32_t flags;
1943   (void)msgHdr->GetFlags(&flags);
1944   *pMarked = !!(flags & nsMsgMessageFlags::Marked);
1945   return rv;
1946 }
1947 
IsIgnored(nsMsgKey key,bool * pIgnored)1948 NS_IMETHODIMP nsMsgDatabase::IsIgnored(nsMsgKey key, bool* pIgnored) {
1949   NS_ENSURE_ARG_POINTER(pIgnored);
1950 
1951   nsCOMPtr<nsIMsgThread> threadHdr;
1952 
1953   nsresult rv = GetThreadForMsgKey(key, getter_AddRefs(threadHdr));
1954   // This should be very surprising, but we leave that up to the caller
1955   // to determine for now.
1956   if (!threadHdr) return NS_MSG_MESSAGE_NOT_FOUND;
1957 
1958   uint32_t threadFlags;
1959   threadHdr->GetFlags(&threadFlags);
1960   *pIgnored = !!(threadFlags & nsMsgMessageFlags::Ignored);
1961   return rv;
1962 }
1963 
IsWatched(nsMsgKey key,bool * pWatched)1964 NS_IMETHODIMP nsMsgDatabase::IsWatched(nsMsgKey key, bool* pWatched) {
1965   NS_ENSURE_ARG_POINTER(pWatched);
1966 
1967   nsCOMPtr<nsIMsgThread> threadHdr;
1968 
1969   nsresult rv = GetThreadForMsgKey(key, getter_AddRefs(threadHdr));
1970   // This should be very surprising, but we leave that up to the caller
1971   // to determine for now.
1972   if (!threadHdr) return NS_MSG_MESSAGE_NOT_FOUND;
1973 
1974   uint32_t threadFlags;
1975   threadHdr->GetFlags(&threadFlags);
1976   *pWatched = !!(threadFlags & nsMsgMessageFlags::Watched);
1977   return rv;
1978 }
1979 
HasAttachments(nsMsgKey key,bool * pHasThem)1980 nsresult nsMsgDatabase::HasAttachments(nsMsgKey key, bool* pHasThem) {
1981   NS_ENSURE_ARG_POINTER(pHasThem);
1982 
1983   nsCOMPtr<nsIMsgDBHdr> msgHdr;
1984 
1985   nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
1986   if (NS_FAILED(rv)) return rv;
1987 
1988   uint32_t flags;
1989   (void)msgHdr->GetFlags(&flags);
1990   *pHasThem = !!(flags & nsMsgMessageFlags::Attachment);
1991   return rv;
1992 }
1993 
SetHdrReadFlag(nsIMsgDBHdr * msgHdr,bool bRead)1994 bool nsMsgDatabase::SetHdrReadFlag(nsIMsgDBHdr* msgHdr, bool bRead) {
1995   return SetHdrFlag(msgHdr, bRead, nsMsgMessageFlags::Read);
1996 }
1997 
MarkHdrReadInDB(nsIMsgDBHdr * msgHdr,bool bRead,nsIDBChangeListener * instigator)1998 nsresult nsMsgDatabase::MarkHdrReadInDB(nsIMsgDBHdr* msgHdr, bool bRead,
1999                                         nsIDBChangeListener* instigator) {
2000   nsresult rv;
2001   nsMsgKey key;
2002   uint32_t oldFlags;
2003   bool hdrInDB;
2004   (void)msgHdr->GetMessageKey(&key);
2005   msgHdr->GetFlags(&oldFlags);
2006 
2007   m_newSet.RemoveElement(key);
2008   (void)ContainsKey(key, &hdrInDB);
2009   if (hdrInDB && m_dbFolderInfo) {
2010     if (bRead)
2011       m_dbFolderInfo->ChangeNumUnreadMessages(-1);
2012     else
2013       m_dbFolderInfo->ChangeNumUnreadMessages(1);
2014   }
2015 
2016   SetHdrReadFlag(msgHdr, bRead);  // this will cause a commit, at least for
2017                                   // local mail, so do it after we change
2018   // the folder counts above, so they will get committed too.
2019   uint32_t flags;
2020   rv = msgHdr->GetFlags(&flags);
2021   flags &= ~nsMsgMessageFlags::New;
2022   msgHdr->SetFlags(flags);
2023   if (NS_FAILED(rv)) return rv;
2024 
2025   if (oldFlags == flags) return NS_OK;
2026 
2027   return NotifyHdrChangeAll(msgHdr, oldFlags, flags, instigator);
2028 }
2029 
MarkRead(nsMsgKey key,bool bRead,nsIDBChangeListener * instigator)2030 NS_IMETHODIMP nsMsgDatabase::MarkRead(nsMsgKey key, bool bRead,
2031                                       nsIDBChangeListener* instigator) {
2032   nsresult rv;
2033   nsCOMPtr<nsIMsgDBHdr> msgHdr;
2034 
2035   rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
2036   if (NS_FAILED(rv) || !msgHdr)
2037     return NS_MSG_MESSAGE_NOT_FOUND;  // XXX return rv?
2038 
2039   rv = MarkHdrRead(msgHdr, bRead, instigator);
2040   return rv;
2041 }
2042 
MarkReplied(nsMsgKey key,bool bReplied,nsIDBChangeListener * instigator)2043 NS_IMETHODIMP nsMsgDatabase::MarkReplied(
2044     nsMsgKey key, bool bReplied, nsIDBChangeListener* instigator /* = NULL */) {
2045   return SetKeyFlag(key, bReplied, nsMsgMessageFlags::Replied, instigator);
2046 }
2047 
MarkForwarded(nsMsgKey key,bool bForwarded,nsIDBChangeListener * instigator)2048 NS_IMETHODIMP nsMsgDatabase::MarkForwarded(
2049     nsMsgKey key, bool bForwarded,
2050     nsIDBChangeListener* instigator /* = NULL */) {
2051   return SetKeyFlag(key, bForwarded, nsMsgMessageFlags::Forwarded, instigator);
2052 }
2053 
MarkRedirected(nsMsgKey key,bool bRedirected,nsIDBChangeListener * instigator)2054 NS_IMETHODIMP nsMsgDatabase::MarkRedirected(
2055     nsMsgKey key, bool bRedirected,
2056     nsIDBChangeListener* instigator /* = NULL */) {
2057   return SetKeyFlag(key, bRedirected, nsMsgMessageFlags::Redirected,
2058                     instigator);
2059 }
2060 
MarkHasAttachments(nsMsgKey key,bool bHasAttachments,nsIDBChangeListener * instigator)2061 NS_IMETHODIMP nsMsgDatabase::MarkHasAttachments(
2062     nsMsgKey key, bool bHasAttachments, nsIDBChangeListener* instigator) {
2063   return SetKeyFlag(key, bHasAttachments, nsMsgMessageFlags::Attachment,
2064                     instigator);
2065 }
2066 
2067 NS_IMETHODIMP
MarkThreadRead(nsIMsgThread * thread,nsIDBChangeListener * instigator,nsTArray<nsMsgKey> & aThoseMarkedRead)2068 nsMsgDatabase::MarkThreadRead(nsIMsgThread* thread,
2069                               nsIDBChangeListener* instigator,
2070                               nsTArray<nsMsgKey>& aThoseMarkedRead) {
2071   NS_ENSURE_ARG_POINTER(thread);
2072   aThoseMarkedRead.ClearAndRetainStorage();
2073   nsresult rv = NS_OK;
2074 
2075   uint32_t numChildren;
2076   thread->GetNumChildren(&numChildren);
2077   aThoseMarkedRead.SetCapacity(numChildren);
2078   for (uint32_t curChildIndex = 0; curChildIndex < numChildren;
2079        curChildIndex++) {
2080     nsCOMPtr<nsIMsgDBHdr> child;
2081 
2082     rv = thread->GetChildHdrAt(curChildIndex, getter_AddRefs(child));
2083     if (NS_SUCCEEDED(rv) && child) {
2084       bool isRead = true;
2085       IsHeaderRead(child, &isRead);
2086       if (!isRead) {
2087         nsMsgKey key;
2088         if (NS_SUCCEEDED(child->GetMessageKey(&key)))
2089           aThoseMarkedRead.AppendElement(key);
2090         MarkHdrRead(child, true, instigator);
2091       }
2092     }
2093   }
2094   return rv;
2095 }
2096 
2097 NS_IMETHODIMP
MarkThreadIgnored(nsIMsgThread * thread,nsMsgKey threadKey,bool bIgnored,nsIDBChangeListener * instigator)2098 nsMsgDatabase::MarkThreadIgnored(nsIMsgThread* thread, nsMsgKey threadKey,
2099                                  bool bIgnored,
2100                                  nsIDBChangeListener* instigator) {
2101   NS_ENSURE_ARG(thread);
2102   uint32_t threadFlags;
2103   thread->GetFlags(&threadFlags);
2104   uint32_t oldThreadFlags =
2105       threadFlags;  // not quite right, since we probably want msg hdr flags.
2106   if (bIgnored) {
2107     threadFlags |= nsMsgMessageFlags::Ignored;
2108     threadFlags &= ~nsMsgMessageFlags::Watched;  // ignore is implicit un-watch
2109   } else
2110     threadFlags &= ~nsMsgMessageFlags::Ignored;
2111   thread->SetFlags(threadFlags);
2112 
2113   nsCOMPtr<nsIMsgDBHdr> msg;
2114   nsresult rv = GetMsgHdrForKey(threadKey, getter_AddRefs(msg));
2115   NS_ENSURE_SUCCESS(rv, rv);
2116   return NotifyHdrChangeAll(msg, oldThreadFlags, threadFlags, instigator);
2117 }
2118 
2119 NS_IMETHODIMP
MarkHeaderKilled(nsIMsgDBHdr * msg,bool bIgnored,nsIDBChangeListener * instigator)2120 nsMsgDatabase::MarkHeaderKilled(nsIMsgDBHdr* msg, bool bIgnored,
2121                                 nsIDBChangeListener* instigator) {
2122   uint32_t msgFlags;
2123   msg->GetFlags(&msgFlags);
2124   uint32_t oldFlags = msgFlags;
2125   if (bIgnored)
2126     msgFlags |= nsMsgMessageFlags::Ignored;
2127   else
2128     msgFlags &= ~nsMsgMessageFlags::Ignored;
2129   msg->SetFlags(msgFlags);
2130 
2131   return NotifyHdrChangeAll(msg, oldFlags, msgFlags, instigator);
2132 }
2133 
2134 NS_IMETHODIMP
MarkThreadWatched(nsIMsgThread * thread,nsMsgKey threadKey,bool bWatched,nsIDBChangeListener * instigator)2135 nsMsgDatabase::MarkThreadWatched(nsIMsgThread* thread, nsMsgKey threadKey,
2136                                  bool bWatched,
2137                                  nsIDBChangeListener* instigator) {
2138   NS_ENSURE_ARG(thread);
2139   uint32_t threadFlags;
2140   thread->GetFlags(&threadFlags);
2141   uint32_t oldThreadFlags =
2142       threadFlags;  // not quite right, since we probably want msg hdr flags.
2143   if (bWatched) {
2144     threadFlags |= nsMsgMessageFlags::Watched;
2145     threadFlags &= ~nsMsgMessageFlags::Ignored;  // watch is implicit un-ignore
2146   } else
2147     threadFlags &= ~nsMsgMessageFlags::Watched;
2148 
2149   nsCOMPtr<nsIMsgDBHdr> msg;
2150   GetMsgHdrForKey(threadKey, getter_AddRefs(msg));
2151 
2152   nsresult rv =
2153       NotifyHdrChangeAll(msg, oldThreadFlags, threadFlags, instigator);
2154   thread->SetFlags(threadFlags);
2155   return rv;
2156 }
2157 
MarkMarked(nsMsgKey key,bool mark,nsIDBChangeListener * instigator)2158 NS_IMETHODIMP nsMsgDatabase::MarkMarked(nsMsgKey key, bool mark,
2159                                         nsIDBChangeListener* instigator) {
2160   return SetKeyFlag(key, mark, nsMsgMessageFlags::Marked, instigator);
2161 }
2162 
MarkOffline(nsMsgKey key,bool offline,nsIDBChangeListener * instigator)2163 NS_IMETHODIMP nsMsgDatabase::MarkOffline(nsMsgKey key, bool offline,
2164                                          nsIDBChangeListener* instigator) {
2165   return SetKeyFlag(key, offline, nsMsgMessageFlags::Offline, instigator);
2166 }
2167 
SetStringProperty(nsMsgKey aKey,const char * aProperty,const char * aValue)2168 NS_IMETHODIMP nsMsgDatabase::SetStringProperty(nsMsgKey aKey,
2169                                                const char* aProperty,
2170                                                const char* aValue) {
2171   nsCOMPtr<nsIMsgDBHdr> msgHdr;
2172   nsresult rv = GetMsgHdrForKey(aKey, getter_AddRefs(msgHdr));
2173   if (NS_FAILED(rv) || !msgHdr)
2174     return NS_MSG_MESSAGE_NOT_FOUND;  // XXX return rv?
2175   return SetStringPropertyByHdr(msgHdr, aProperty, aValue);
2176 }
2177 
SetStringPropertyByHdr(nsIMsgDBHdr * msgHdr,const char * aProperty,const char * aValue)2178 NS_IMETHODIMP nsMsgDatabase::SetStringPropertyByHdr(nsIMsgDBHdr* msgHdr,
2179                                                     const char* aProperty,
2180                                                     const char* aValue) {
2181   // don't do notifications if message not yet added to database.
2182   // Ignore errors (consequences of failure are minor).
2183   bool notify = true;
2184   nsMsgKey key = nsMsgKey_None;
2185   msgHdr->GetMessageKey(&key);
2186   ContainsKey(key, &notify);
2187 
2188   nsCString oldValue;
2189   nsresult rv = msgHdr->GetStringProperty(aProperty, getter_Copies(oldValue));
2190   NS_ENSURE_SUCCESS(rv, rv);
2191 
2192   // if no change to this string property, bail out
2193   if (oldValue.Equals(aValue)) return NS_OK;
2194 
2195   // Precall OnHdrPropertyChanged to store prechange status
2196   nsTArray<uint32_t> statusArray(m_ChangeListeners.Length());
2197   nsCOMPtr<nsIDBChangeListener> listener;
2198   if (notify) {
2199     nsTObserverArray<nsCOMPtr<nsIDBChangeListener>>::ForwardIterator listeners(
2200         m_ChangeListeners);
2201     while (listeners.HasMore()) {
2202       listener = listeners.GetNext();
2203       // initialize |status| because some implementations of
2204       // OnHdrPropertyChanged does not set the value.
2205       uint32_t status = 0;
2206       (void)listener->OnHdrPropertyChanged(msgHdr, true, &status, nullptr);
2207       // ignore errors, but append element to keep arrays in sync
2208       statusArray.AppendElement(status);
2209     }
2210   }
2211 
2212   rv = msgHdr->SetStringProperty(aProperty, aValue);
2213   NS_ENSURE_SUCCESS(rv, rv);
2214 
2215   // Postcall OnHdrPropertyChanged to process the change
2216   if (notify) {
2217     // if this is the junk score property notify, as long as we're not going
2218     // from no value to non junk
2219     if (!strcmp(aProperty, "junkscore") &&
2220         !(oldValue.IsEmpty() && !strcmp(aValue, "0")))
2221       NotifyJunkScoreChanged(nullptr);
2222 
2223     nsTObserverArray<nsCOMPtr<nsIDBChangeListener>>::ForwardIterator listeners(
2224         m_ChangeListeners);
2225     for (uint32_t i = 0; listeners.HasMore(); i++) {
2226       listener = listeners.GetNext();
2227       uint32_t status = statusArray[i];
2228       (void)listener->OnHdrPropertyChanged(msgHdr, false, &status, nullptr);
2229       // ignore errors
2230     }
2231   }
2232 
2233   return NS_OK;
2234 }
2235 
2236 NS_IMETHODIMP
SetUint32PropertyByHdr(nsIMsgDBHdr * aMsgHdr,const char * aProperty,uint32_t aValue)2237 nsMsgDatabase::SetUint32PropertyByHdr(nsIMsgDBHdr* aMsgHdr,
2238                                       const char* aProperty, uint32_t aValue) {
2239   // If no change to this property, bail out.
2240   uint32_t oldValue;
2241   nsresult rv = aMsgHdr->GetUint32Property(aProperty, &oldValue);
2242   NS_ENSURE_SUCCESS(rv, rv);
2243   if (oldValue == aValue) return NS_OK;
2244 
2245   // Don't do notifications if message not yet added to database.
2246   bool notify = true;
2247   nsMsgKey key = nsMsgKey_None;
2248   aMsgHdr->GetMessageKey(&key);
2249   ContainsKey(key, &notify);
2250 
2251   // Precall OnHdrPropertyChanged to store prechange status.
2252   nsTArray<uint32_t> statusArray(m_ChangeListeners.Length());
2253   nsCOMPtr<nsIDBChangeListener> listener;
2254   if (notify) {
2255     nsTObserverArray<nsCOMPtr<nsIDBChangeListener>>::ForwardIterator listeners(
2256         m_ChangeListeners);
2257     while (listeners.HasMore()) {
2258       listener = listeners.GetNext();
2259       // initialize |status| because some implementations of
2260       // OnHdrPropertyChanged does not set the value.
2261       uint32_t status = 0;
2262       (void)listener->OnHdrPropertyChanged(aMsgHdr, true, &status, nullptr);
2263       // Ignore errors, but append element to keep arrays in sync.
2264       statusArray.AppendElement(status);
2265     }
2266   }
2267 
2268   rv = aMsgHdr->SetUint32Property(aProperty, aValue);
2269   NS_ENSURE_SUCCESS(rv, rv);
2270 
2271   // Postcall OnHdrPropertyChanged to process the change.
2272   if (notify) {
2273     nsTObserverArray<nsCOMPtr<nsIDBChangeListener>>::ForwardIterator listeners(
2274         m_ChangeListeners);
2275     for (uint32_t i = 0; listeners.HasMore(); i++) {
2276       listener = listeners.GetNext();
2277       uint32_t status = statusArray[i];
2278       (void)listener->OnHdrPropertyChanged(aMsgHdr, false, &status, nullptr);
2279       // Ignore errors.
2280     }
2281   }
2282 
2283   return NS_OK;
2284 }
2285 
SetLabel(nsMsgKey key,nsMsgLabelValue label)2286 NS_IMETHODIMP nsMsgDatabase::SetLabel(nsMsgKey key, nsMsgLabelValue label) {
2287   nsresult rv;
2288   nsCOMPtr<nsIMsgDBHdr> msgHdr;
2289 
2290   rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
2291   if (NS_FAILED(rv) || !msgHdr) return NS_MSG_MESSAGE_NOT_FOUND;
2292   nsMsgLabelValue oldLabel;
2293   msgHdr->GetLabel(&oldLabel);
2294 
2295   msgHdr->SetLabel(label);
2296   // clear old label
2297   if (oldLabel != label) {
2298     if (oldLabel != 0) rv = SetKeyFlag(key, false, oldLabel << 25, nullptr);
2299     // set the flag in the x-mozilla-status2 line.
2300     rv = SetKeyFlag(key, true, label << 25, nullptr);
2301   }
2302   return rv;
2303 }
2304 
MarkImapDeleted(nsMsgKey key,bool deleted,nsIDBChangeListener * instigator)2305 NS_IMETHODIMP nsMsgDatabase::MarkImapDeleted(nsMsgKey key, bool deleted,
2306                                              nsIDBChangeListener* instigator) {
2307   return SetKeyFlag(key, deleted, nsMsgMessageFlags::IMAPDeleted, instigator);
2308 }
2309 
MarkMDNNeeded(nsMsgKey key,bool bNeeded,nsIDBChangeListener * instigator)2310 NS_IMETHODIMP nsMsgDatabase::MarkMDNNeeded(
2311     nsMsgKey key, bool bNeeded, nsIDBChangeListener* instigator /* = NULL */) {
2312   return SetKeyFlag(key, bNeeded, nsMsgMessageFlags::MDNReportNeeded,
2313                     instigator);
2314 }
2315 
IsMDNNeeded(nsMsgKey key,bool * pNeeded)2316 NS_IMETHODIMP nsMsgDatabase::IsMDNNeeded(nsMsgKey key, bool* pNeeded) {
2317   nsCOMPtr<nsIMsgDBHdr> msgHdr;
2318 
2319   nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
2320   if (NS_FAILED(rv) || !msgHdr)
2321     return NS_MSG_MESSAGE_NOT_FOUND;  // XXX return rv?
2322 
2323   uint32_t flags;
2324   (void)msgHdr->GetFlags(&flags);
2325   *pNeeded = !!(flags & nsMsgMessageFlags::MDNReportNeeded);
2326   return rv;
2327 }
2328 
MarkMDNSent(nsMsgKey key,bool bSent,nsIDBChangeListener * instigator)2329 nsresult nsMsgDatabase::MarkMDNSent(
2330     nsMsgKey key, bool bSent, nsIDBChangeListener* instigator /* = NULL */) {
2331   return SetKeyFlag(key, bSent, nsMsgMessageFlags::MDNReportSent, instigator);
2332 }
2333 
IsMDNSent(nsMsgKey key,bool * pSent)2334 nsresult nsMsgDatabase::IsMDNSent(nsMsgKey key, bool* pSent) {
2335   nsCOMPtr<nsIMsgDBHdr> msgHdr;
2336 
2337   nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
2338   if (NS_FAILED(rv) || !msgHdr)
2339     return NS_MSG_MESSAGE_NOT_FOUND;  // XXX return rv?
2340 
2341   uint32_t flags;
2342   (void)msgHdr->GetFlags(&flags);
2343   *pSent = !!(flags & nsMsgMessageFlags::MDNReportSent);
2344   return rv;
2345 }
2346 
SetKeyFlag(nsMsgKey key,bool set,nsMsgMessageFlagType flag,nsIDBChangeListener * instigator)2347 nsresult nsMsgDatabase::SetKeyFlag(nsMsgKey key, bool set,
2348                                    nsMsgMessageFlagType flag,
2349                                    nsIDBChangeListener* instigator) {
2350   nsresult rv;
2351   nsCOMPtr<nsIMsgDBHdr> msgHdr;
2352 
2353   rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
2354   if (NS_FAILED(rv) || !msgHdr)
2355     return NS_MSG_MESSAGE_NOT_FOUND;  // XXX return rv?
2356 
2357   return SetMsgHdrFlag(msgHdr, set, flag, instigator);
2358 }
2359 
SetMsgHdrFlag(nsIMsgDBHdr * msgHdr,bool set,nsMsgMessageFlagType flag,nsIDBChangeListener * instigator)2360 nsresult nsMsgDatabase::SetMsgHdrFlag(nsIMsgDBHdr* msgHdr, bool set,
2361                                       nsMsgMessageFlagType flag,
2362                                       nsIDBChangeListener* instigator) {
2363   uint32_t oldFlags;
2364   (void)msgHdr->GetFlags(&oldFlags);
2365 
2366   if (!SetHdrFlag(msgHdr, set, flag)) return NS_OK;
2367 
2368   uint32_t flags;
2369   (void)msgHdr->GetFlags(&flags);
2370 
2371   return NotifyHdrChangeAll(msgHdr, oldFlags, flags, instigator);
2372 }
2373 
2374 // Helper routine - lowest level of flag setting - returns true if flags change,
2375 // false otherwise.
SetHdrFlag(nsIMsgDBHdr * msgHdr,bool bSet,nsMsgMessageFlagType flag)2376 bool nsMsgDatabase::SetHdrFlag(nsIMsgDBHdr* msgHdr, bool bSet,
2377                                nsMsgMessageFlagType flag) {
2378   uint32_t statusFlags;
2379   (void)msgHdr->GetFlags(&statusFlags);
2380   uint32_t currentStatusFlags = GetStatusFlags(msgHdr, statusFlags);
2381   bool flagAlreadySet = (currentStatusFlags & flag) != 0;
2382 
2383   if ((flagAlreadySet && !bSet) || (!flagAlreadySet && bSet)) {
2384     uint32_t resultFlags;
2385     if (bSet)
2386       msgHdr->OrFlags(flag, &resultFlags);
2387     else
2388       msgHdr->AndFlags(~flag, &resultFlags);
2389     return true;
2390   }
2391   return false;
2392 }
2393 
MarkHdrRead(nsIMsgDBHdr * msgHdr,bool bRead,nsIDBChangeListener * instigator)2394 NS_IMETHODIMP nsMsgDatabase::MarkHdrRead(nsIMsgDBHdr* msgHdr, bool bRead,
2395                                          nsIDBChangeListener* instigator) {
2396   bool isReadInDB = true;
2397   nsresult rv = nsMsgDatabase::IsHeaderRead(msgHdr, &isReadInDB);
2398   NS_ENSURE_SUCCESS(rv, rv);
2399 
2400   bool isRead = true;
2401   rv = IsHeaderRead(msgHdr, &isRead);
2402   NS_ENSURE_SUCCESS(rv, rv);
2403 
2404   // if the flag is already correct in the db, don't change it.
2405   // Check msg flags as well as IsHeaderRead in case it's a newsgroup
2406   // and the msghdr flags are out of sync with the newsrc settings.
2407   // (we could override this method for news db's, but it's a trivial fix here.
2408   if (bRead != isRead || isRead != isReadInDB) {
2409     nsMsgKey msgKey;
2410     msgHdr->GetMessageKey(&msgKey);
2411 
2412     bool inDB = false;
2413     (void)ContainsKey(msgKey, &inDB);
2414 
2415     if (inDB) {
2416       nsCOMPtr<nsIMsgThread> threadHdr;
2417       rv = GetThreadForMsgKey(msgKey, getter_AddRefs(threadHdr));
2418       if (threadHdr) threadHdr->MarkChildRead(bRead);
2419     }
2420 
2421 #ifndef MOZ_SUITE
2422     if (bRead) {
2423       Telemetry::ScalarAdd(Telemetry::ScalarID::TB_MAILS_READ, 1);
2424     }
2425 #endif
2426 
2427     return MarkHdrReadInDB(msgHdr, bRead, instigator);
2428   }
2429   return NS_OK;
2430 }
2431 
MarkHdrReplied(nsIMsgDBHdr * msgHdr,bool bReplied,nsIDBChangeListener * instigator)2432 NS_IMETHODIMP nsMsgDatabase::MarkHdrReplied(nsIMsgDBHdr* msgHdr, bool bReplied,
2433                                             nsIDBChangeListener* instigator) {
2434   return SetMsgHdrFlag(msgHdr, bReplied, nsMsgMessageFlags::Replied,
2435                        instigator);
2436 }
2437 
MarkHdrMarked(nsIMsgDBHdr * msgHdr,bool mark,nsIDBChangeListener * instigator)2438 NS_IMETHODIMP nsMsgDatabase::MarkHdrMarked(nsIMsgDBHdr* msgHdr, bool mark,
2439                                            nsIDBChangeListener* instigator) {
2440   return SetMsgHdrFlag(msgHdr, mark, nsMsgMessageFlags::Marked, instigator);
2441 }
2442 
2443 NS_IMETHODIMP
MarkHdrNotNew(nsIMsgDBHdr * aMsgHdr,nsIDBChangeListener * aInstigator)2444 nsMsgDatabase::MarkHdrNotNew(nsIMsgDBHdr* aMsgHdr,
2445                              nsIDBChangeListener* aInstigator) {
2446   NS_ENSURE_ARG_POINTER(aMsgHdr);
2447   nsMsgKey msgKey;
2448   aMsgHdr->GetMessageKey(&msgKey);
2449   m_newSet.RemoveElement(msgKey);
2450   return SetMsgHdrFlag(aMsgHdr, false, nsMsgMessageFlags::New, aInstigator);
2451 }
2452 
MarkAllRead(nsTArray<nsMsgKey> & aThoseMarked)2453 NS_IMETHODIMP nsMsgDatabase::MarkAllRead(nsTArray<nsMsgKey>& aThoseMarked) {
2454   aThoseMarked.ClearAndRetainStorage();
2455 
2456   nsCOMPtr<nsIMsgEnumerator> hdrs;
2457   nsresult rv = EnumerateMessages(getter_AddRefs(hdrs));
2458   NS_ENSURE_SUCCESS(rv, rv);
2459   bool hasMore = false;
2460 
2461   while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
2462     nsCOMPtr<nsIMsgDBHdr> msg;
2463     rv = hdrs->GetNext(getter_AddRefs(msg));
2464     if (NS_FAILED(rv)) break;
2465 
2466     bool isRead;
2467     IsHeaderRead(msg, &isRead);
2468 
2469     if (!isRead) {
2470       nsMsgKey key;
2471       (void)msg->GetMessageKey(&key);
2472       aThoseMarked.AppendElement(key);
2473       rv = MarkHdrRead(msg, true, nullptr);  // ### dmb - blow off error?
2474     }
2475   }
2476 
2477   // force num new to 0.
2478   int32_t numUnreadMessages;
2479 
2480   rv = m_dbFolderInfo->GetNumUnreadMessages(&numUnreadMessages);
2481   if (NS_SUCCEEDED(rv))
2482     m_dbFolderInfo->ChangeNumUnreadMessages(-numUnreadMessages);
2483   // caller will Commit the db, so no need to do it here.
2484   return rv;
2485 }
2486 
AddToNewList(nsMsgKey key)2487 NS_IMETHODIMP nsMsgDatabase::AddToNewList(nsMsgKey key) {
2488   // we add new keys in increasing order...
2489   if (m_newSet.IsEmpty() || (m_newSet[m_newSet.Length() - 1] < key))
2490     m_newSet.AppendElement(key);
2491   return NS_OK;
2492 }
2493 
ClearNewList(bool notify)2494 NS_IMETHODIMP nsMsgDatabase::ClearNewList(bool notify /* = FALSE */) {
2495   nsresult err = NS_OK;
2496   if (notify && !m_newSet.IsEmpty())  // need to update view
2497   {
2498     nsTArray<nsMsgKey> saveNewSet;
2499     // clear m_newSet so that the code that's listening to the key change
2500     // doesn't think we have new messages and send notifications all over
2501     // that we have new messages.
2502     saveNewSet.SwapElements(m_newSet);
2503     for (uint32_t elementIndex = saveNewSet.Length() - 1;; elementIndex--) {
2504       nsMsgKey lastNewKey = saveNewSet.ElementAt(elementIndex);
2505       nsCOMPtr<nsIMsgDBHdr> msgHdr;
2506       err = GetMsgHdrForKey(lastNewKey, getter_AddRefs(msgHdr));
2507       if (NS_SUCCEEDED(err)) {
2508         uint32_t flags;
2509         (void)msgHdr->GetFlags(&flags);
2510 
2511         if ((flags | nsMsgMessageFlags::New) != flags) {
2512           msgHdr->AndFlags(~nsMsgMessageFlags::New, &flags);
2513           NotifyHdrChangeAll(msgHdr, flags | nsMsgMessageFlags::New, flags,
2514                              nullptr);
2515         }
2516       }
2517       if (elementIndex == 0) break;
2518     }
2519   }
2520   return err;
2521 }
2522 
HasNew(bool * _retval)2523 NS_IMETHODIMP nsMsgDatabase::HasNew(bool* _retval) {
2524   if (!_retval) return NS_ERROR_NULL_POINTER;
2525 
2526   *_retval = (m_newSet.Length() > 0);
2527   return NS_OK;
2528 }
2529 
GetFirstNew(nsMsgKey * result)2530 NS_IMETHODIMP nsMsgDatabase::GetFirstNew(nsMsgKey* result) {
2531   bool hasnew;
2532   nsresult rv = HasNew(&hasnew);
2533   if (NS_FAILED(rv)) return rv;
2534   *result = (hasnew) ? m_newSet.ElementAt(0) : nsMsgKey_None;
2535   return NS_OK;
2536 }
2537 
2538 NS_IMETHODIMP
EnumerateMessages(nsIMsgEnumerator ** result)2539 nsMsgDatabase::EnumerateMessages(nsIMsgEnumerator** result) {
2540   RememberLastUseTime();
2541   NS_ENSURE_ARG_POINTER(result);
2542   NS_ADDREF(*result = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
2543                                             nullptr, nullptr));
2544   return NS_OK;
2545 }
2546 
2547 NS_IMETHODIMP
ReverseEnumerateMessages(nsIMsgEnumerator ** result)2548 nsMsgDatabase::ReverseEnumerateMessages(nsIMsgEnumerator** result) {
2549   NS_ENSURE_ARG_POINTER(result);
2550   NS_ADDREF(*result = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
2551                                             nullptr, nullptr, false));
2552   return NS_OK;
2553 }
2554 
2555 NS_IMETHODIMP
GetFilterEnumerator(const nsTArray<RefPtr<nsIMsgSearchTerm>> & searchTerms,bool aReverse,nsIMsgEnumerator ** aResult)2556 nsMsgDatabase::GetFilterEnumerator(
2557     const nsTArray<RefPtr<nsIMsgSearchTerm>>& searchTerms, bool aReverse,
2558     nsIMsgEnumerator** aResult) {
2559   NS_ENSURE_ARG_POINTER(aResult);
2560   RefPtr<nsMsgFilteredDBEnumerator> e =
2561       new nsMsgFilteredDBEnumerator(this, m_mdbAllMsgHeadersTable, aReverse);
2562 
2563   NS_ENSURE_TRUE(e, NS_ERROR_OUT_OF_MEMORY);
2564   nsresult rv = e->InitSearchSession(searchTerms, m_folder);
2565   NS_ENSURE_SUCCESS(rv, rv);
2566 
2567   e.forget(aResult);
2568   return NS_OK;
2569 }
2570 
2571 NS_IMETHODIMP
SyncCounts()2572 nsMsgDatabase::SyncCounts() {
2573   nsCOMPtr<nsIMsgEnumerator> hdrs;
2574   nsresult rv = EnumerateMessages(getter_AddRefs(hdrs));
2575   if (NS_FAILED(rv)) return rv;
2576   bool hasMore = false;
2577 
2578   mdb_count numHdrsInTable = 0;
2579   int32_t numUnread = 0;
2580   int32_t numHdrs = 0;
2581 
2582   if (m_mdbAllMsgHeadersTable)
2583     m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrsInTable);
2584   else
2585     return NS_ERROR_NULL_POINTER;
2586 
2587   while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
2588     nsCOMPtr<nsIMsgDBHdr> header;
2589     rv = hdrs->GetNext(getter_AddRefs(header));
2590     NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
2591     if (NS_FAILED(rv)) break;
2592 
2593     bool isRead;
2594     IsHeaderRead(header, &isRead);
2595     if (!isRead) numUnread++;
2596     numHdrs++;
2597   }
2598 
2599   int32_t oldTotal, oldUnread;
2600   (void)m_dbFolderInfo->GetNumUnreadMessages(&oldUnread);
2601   (void)m_dbFolderInfo->GetNumMessages(&oldTotal);
2602   if (oldUnread != numUnread)
2603     m_dbFolderInfo->ChangeNumUnreadMessages(numUnread - oldUnread);
2604   if (oldTotal != numHdrs)
2605     m_dbFolderInfo->ChangeNumMessages(numHdrs - oldTotal);
2606   return NS_OK;
2607 }
2608 
ListAllKeys(nsTArray<nsMsgKey> & keys)2609 NS_IMETHODIMP nsMsgDatabase::ListAllKeys(nsTArray<nsMsgKey>& keys) {
2610   nsresult rv = NS_OK;
2611   nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
2612   RememberLastUseTime();
2613   keys.Clear();
2614 
2615   if (m_mdbAllMsgHeadersTable) {
2616     uint32_t numMsgs = 0;
2617     m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numMsgs);
2618     keys.SetCapacity(numMsgs);
2619     rv = m_mdbAllMsgHeadersTable->GetTableRowCursor(GetEnv(), -1,
2620                                                     getter_AddRefs(rowCursor));
2621     while (NS_SUCCEEDED(rv) && rowCursor) {
2622       mdbOid outOid;
2623       mdb_pos outPos;
2624 
2625       rv = rowCursor->NextRowOid(GetEnv(), &outOid, &outPos);
2626       // is this right? Mork is returning a 0 id, but that should valid.
2627       if (outPos < 0 || outOid.mOid_Id == (mdb_id)-1) break;
2628       if (NS_SUCCEEDED(rv)) keys.AppendElement(outOid.mOid_Id);
2629     }
2630   }
2631   return rv;
2632 }
2633 
2634 NS_IMETHODIMP
EnumerateThreads(nsIMsgThreadEnumerator ** result)2635 nsMsgDatabase::EnumerateThreads(nsIMsgThreadEnumerator** result) {
2636   RememberLastUseTime();
2637   NS_ADDREF(*result = new nsMsgDBThreadEnumerator(this, nullptr));
2638   return NS_OK;
2639 }
2640 
2641 // only return headers with a particular flag set
nsMsgFlagSetFilter(nsIMsgDBHdr * msg,void * closure)2642 static nsresult nsMsgFlagSetFilter(nsIMsgDBHdr* msg, void* closure) {
2643   uint32_t msgFlags, desiredFlags;
2644   desiredFlags = *(uint32_t*)closure;
2645   msg->GetFlags(&msgFlags);
2646   return (msgFlags & desiredFlags) ? NS_OK : NS_ERROR_FAILURE;
2647 }
2648 
EnumerateMessagesWithFlag(nsIMsgEnumerator ** result,uint32_t * pFlag)2649 nsresult nsMsgDatabase::EnumerateMessagesWithFlag(nsIMsgEnumerator** result,
2650                                                   uint32_t* pFlag) {
2651   RememberLastUseTime();
2652   NS_ADDREF(*result = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
2653                                             nsMsgFlagSetFilter, pFlag));
2654   return NS_OK;
2655 }
2656 
CreateNewHdr(nsMsgKey key,nsIMsgDBHdr ** pnewHdr)2657 NS_IMETHODIMP nsMsgDatabase::CreateNewHdr(nsMsgKey key, nsIMsgDBHdr** pnewHdr) {
2658   nsresult err = NS_OK;
2659   nsIMdbRow* hdrRow = nullptr;
2660   struct mdbOid allMsgHdrsTableOID;
2661 
2662   if (!pnewHdr || !m_mdbAllMsgHeadersTable || !m_mdbStore)
2663     return NS_ERROR_NULL_POINTER;
2664 
2665   if (key != nsMsgKey_None) {
2666     allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
2667     allMsgHdrsTableOID.mOid_Id = key;  // presumes 0 is valid key value
2668 
2669     err = m_mdbStore->GetRow(GetEnv(), &allMsgHdrsTableOID, &hdrRow);
2670     if (!hdrRow)
2671       err = m_mdbStore->NewRowWithOid(GetEnv(), &allMsgHdrsTableOID, &hdrRow);
2672   } else {
2673     // Mork will assign an ID to the new row, generally the next available ID.
2674     err = m_mdbStore->NewRow(GetEnv(), m_hdrRowScopeToken, &hdrRow);
2675     if (hdrRow) {
2676       struct mdbOid oid;
2677       hdrRow->GetOid(GetEnv(), &oid);
2678       key = oid.mOid_Id;
2679     } else {
2680       // We failed to create a new row. That can happen if we run out of keys,
2681       // which will force a reparse.
2682       nsTArray<nsMsgKey> keys;
2683       if (NS_SUCCEEDED(ListAllKeys(keys))) {
2684         for (nsMsgKey key : keys) {
2685           if (key >= kForceReparseKey) {
2686             // Force a reparse.
2687             if (m_dbFolderInfo)
2688               m_dbFolderInfo->SetBooleanProperty("forceReparse", true);
2689             break;
2690           }
2691         }
2692       }
2693       err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
2694     }
2695   }
2696   if (NS_FAILED(err)) return err;
2697   err = CreateMsgHdr(hdrRow, key, pnewHdr);
2698   return err;
2699 }
2700 
AddNewHdrToDB(nsIMsgDBHdr * newHdr,bool notify)2701 NS_IMETHODIMP nsMsgDatabase::AddNewHdrToDB(nsIMsgDBHdr* newHdr, bool notify) {
2702   NS_ENSURE_ARG_POINTER(newHdr);
2703   nsMsgHdr* hdr = static_cast<nsMsgHdr*>(newHdr);  // closed system, cast ok
2704   bool newThread;
2705   bool hasKey = false;
2706   nsMsgKey msgKey = nsMsgKey_None;
2707   (void)hdr->GetMessageKey(&msgKey);
2708   (void)ContainsKey(msgKey, &hasKey);
2709   if (hasKey) {
2710     NS_ERROR("adding hdr that already exists");
2711     return NS_ERROR_FAILURE;
2712   }
2713   nsresult err = ThreadNewHdr(hdr, newThread);
2714   // we thread header before we add it to the all headers table
2715   // so that subject and reference threading will work (otherwise,
2716   // when we try to find the first header with the same subject or
2717   // reference, we get the new header!)
2718   if (NS_SUCCEEDED(err)) {
2719     nsMsgKey key;
2720     uint32_t flags;
2721 
2722     newHdr->GetMessageKey(&key);
2723     hdr->GetRawFlags(&flags);
2724     // use raw flags instead of GetFlags, because GetFlags will
2725     // pay attention to what's in m_newSet, and this new hdr isn't
2726     // in m_newSet yet.
2727     if (flags & nsMsgMessageFlags::New) {
2728       uint32_t newFlags;
2729       newHdr->AndFlags(~nsMsgMessageFlags::New,
2730                        &newFlags);  // make sure not filed out
2731       AddToNewList(key);
2732     }
2733     if (m_dbFolderInfo) {
2734       m_dbFolderInfo->ChangeNumMessages(1);
2735       bool isRead = true;
2736       IsHeaderRead(newHdr, &isRead);
2737       if (!isRead) m_dbFolderInfo->ChangeNumUnreadMessages(1);
2738       m_dbFolderInfo->OnKeyAdded(key);
2739     }
2740 
2741     err = m_mdbAllMsgHeadersTable->AddRow(GetEnv(), hdr->GetMDBRow());
2742     if (notify) {
2743       nsMsgKey threadParent;
2744 
2745       newHdr->GetThreadParent(&threadParent);
2746       NotifyHdrAddedAll(newHdr, threadParent, flags, NULL);
2747     }
2748 
2749     if (UseCorrectThreading()) err = AddMsgRefsToHash(newHdr);
2750   }
2751   NS_ASSERTION(NS_SUCCEEDED(err), "error creating thread");
2752   return err;
2753 }
2754 
CopyHdrFromExistingHdr(nsMsgKey key,nsIMsgDBHdr * existingHdr,bool addHdrToDB,nsIMsgDBHdr ** newHdr)2755 NS_IMETHODIMP nsMsgDatabase::CopyHdrFromExistingHdr(nsMsgKey key,
2756                                                     nsIMsgDBHdr* existingHdr,
2757                                                     bool addHdrToDB,
2758                                                     nsIMsgDBHdr** newHdr) {
2759   nsresult err = NS_OK;
2760 
2761   if (existingHdr) {
2762     nsMsgHdr* sourceMsgHdr =
2763         static_cast<nsMsgHdr*>(existingHdr);  // closed system, cast ok
2764     nsMsgHdr* destMsgHdr = nullptr;
2765     CreateNewHdr(key, (nsIMsgDBHdr**)&destMsgHdr);
2766     nsIMdbRow* sourceRow = sourceMsgHdr->GetMDBRow();
2767     if (!destMsgHdr || !sourceRow) return NS_MSG_MESSAGE_NOT_FOUND;
2768 
2769     nsIMdbRow* destRow = destMsgHdr->GetMDBRow();
2770     if (!destRow) return NS_ERROR_UNEXPECTED;
2771 
2772     err = destRow->SetRow(GetEnv(), sourceRow);
2773     if (NS_SUCCEEDED(err)) {
2774       // we may have gotten the header from a cache - calling SetRow
2775       // basically invalidates any cached values, so invalidate them.
2776       destMsgHdr->ClearCachedValues();
2777       if (addHdrToDB) err = AddNewHdrToDB(destMsgHdr, true);
2778       if (NS_SUCCEEDED(err) && newHdr) *newHdr = destMsgHdr;
2779     }
2780   }
2781   return err;
2782 }
2783 
RowCellColumnTonsString(nsIMdbRow * hdrRow,mdb_token columnToken,nsAString & resultStr)2784 nsresult nsMsgDatabase::RowCellColumnTonsString(nsIMdbRow* hdrRow,
2785                                                 mdb_token columnToken,
2786                                                 nsAString& resultStr) {
2787   NS_ENSURE_ARG_POINTER(hdrRow);
2788 
2789   struct mdbYarn yarn;
2790   nsresult rv = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
2791   NS_ENSURE_SUCCESS(rv, rv);
2792   YarnTonsString(&yarn, resultStr);
2793   return NS_OK;
2794 }
2795 
2796 // as long as the row still exists, and isn't changed, the returned const char
2797 // ** will be valid. But be very careful using this data - the caller should
2798 // never return it in turn to another caller.
RowCellColumnToConstCharPtr(nsIMdbRow * hdrRow,mdb_token columnToken,const char ** ptr)2799 nsresult nsMsgDatabase::RowCellColumnToConstCharPtr(nsIMdbRow* hdrRow,
2800                                                     mdb_token columnToken,
2801                                                     const char** ptr) {
2802   NS_ENSURE_ARG_POINTER(hdrRow);
2803 
2804   struct mdbYarn yarn;
2805   nsresult rv = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
2806   NS_ENSURE_SUCCESS(rv, rv);
2807   *ptr = (const char*)yarn.mYarn_Buf;
2808   return NS_OK;
2809 }
2810 
GetMimeConverter()2811 nsIMimeConverter* nsMsgDatabase::GetMimeConverter() {
2812   if (!m_mimeConverter) {
2813     // apply mime decode
2814     m_mimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID);
2815   }
2816   return m_mimeConverter;
2817 }
2818 
GetEffectiveCharset(nsIMdbRow * row,nsACString & resultCharset)2819 nsresult nsMsgDatabase::GetEffectiveCharset(nsIMdbRow* row,
2820                                             nsACString& resultCharset) {
2821   resultCharset.Truncate();
2822   nsresult rv = RowCellColumnToCharPtr(row, m_messageCharSetColumnToken,
2823                                        getter_Copies(resultCharset));
2824   if (NS_FAILED(rv) || resultCharset.IsEmpty() ||
2825       resultCharset.EqualsLiteral("us-ascii")) {
2826     resultCharset.AssignLiteral("UTF-8");
2827     nsCOMPtr<nsIMsgNewsFolder> newsfolder(do_QueryInterface(m_folder));
2828     if (newsfolder) newsfolder->GetCharset(resultCharset);
2829   }
2830   return rv;
2831 }
2832 
RowCellColumnToMime2DecodedString(nsIMdbRow * row,mdb_token columnToken,nsAString & resultStr)2833 nsresult nsMsgDatabase::RowCellColumnToMime2DecodedString(
2834     nsIMdbRow* row, mdb_token columnToken, nsAString& resultStr) {
2835   nsresult err = NS_OK;
2836   const char* nakedString = nullptr;
2837   err = RowCellColumnToConstCharPtr(row, columnToken, &nakedString);
2838   if (NS_SUCCEEDED(err) && nakedString && strlen(nakedString)) {
2839     GetMimeConverter();
2840     if (m_mimeConverter) {
2841       nsAutoString decodedStr;
2842       nsCString charSet;
2843       GetEffectiveCharset(row, charSet);
2844 
2845       err = m_mimeConverter->DecodeMimeHeader(nakedString, charSet.get(), false,
2846                                               true, resultStr);
2847     }
2848   }
2849   return err;
2850 }
2851 
RowCellColumnToAddressCollationKey(nsIMdbRow * row,mdb_token colToken,nsTArray<uint8_t> & result)2852 nsresult nsMsgDatabase::RowCellColumnToAddressCollationKey(
2853     nsIMdbRow* row, mdb_token colToken, nsTArray<uint8_t>& result) {
2854   nsString sender;
2855   nsresult rv = RowCellColumnToMime2DecodedString(row, colToken, sender);
2856   NS_ENSURE_SUCCESS(rv, rv);
2857 
2858   nsString name;
2859   ExtractName(DecodedHeader(sender), name);
2860   return CreateCollationKey(name, result);
2861 }
2862 
GetCollationKeyGenerator()2863 nsresult nsMsgDatabase::GetCollationKeyGenerator() {
2864   nsresult err = NS_OK;
2865   if (!m_collationKeyGenerator) {
2866     nsCOMPtr<nsICollationFactory> f =
2867         do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &err);
2868     if (NS_SUCCEEDED(err) && f) {
2869       // get a collation interface instance
2870       err = f->CreateCollation(getter_AddRefs(m_collationKeyGenerator));
2871     }
2872   }
2873   return err;
2874 }
2875 
RowCellColumnToCollationKey(nsIMdbRow * row,mdb_token columnToken,nsTArray<uint8_t> & result)2876 nsresult nsMsgDatabase::RowCellColumnToCollationKey(nsIMdbRow* row,
2877                                                     mdb_token columnToken,
2878                                                     nsTArray<uint8_t>& result) {
2879   const char* nakedString = nullptr;
2880   nsresult err;
2881 
2882   err = RowCellColumnToConstCharPtr(row, columnToken, &nakedString);
2883   if (!nakedString) nakedString = "";
2884   if (NS_SUCCEEDED(err)) {
2885     GetMimeConverter();
2886     if (m_mimeConverter) {
2887       nsCString decodedStr;
2888       nsCString charSet;
2889       GetEffectiveCharset(row, charSet);
2890 
2891       err = m_mimeConverter->DecodeMimeHeaderToUTF8(
2892           nsDependentCString(nakedString), charSet.get(), false, true,
2893           decodedStr);
2894       if (NS_SUCCEEDED(err))
2895         err = CreateCollationKey(NS_ConvertUTF8toUTF16(decodedStr), result);
2896     }
2897   }
2898   return err;
2899 }
2900 
2901 NS_IMETHODIMP
CompareCollationKeys(const nsTArray<uint8_t> & key1,const nsTArray<uint8_t> & key2,int32_t * result)2902 nsMsgDatabase::CompareCollationKeys(const nsTArray<uint8_t>& key1,
2903                                     const nsTArray<uint8_t>& key2,
2904                                     int32_t* result) {
2905   nsresult rv = GetCollationKeyGenerator();
2906   NS_ENSURE_SUCCESS(rv, rv);
2907   if (!m_collationKeyGenerator) return NS_ERROR_FAILURE;
2908 
2909   rv = m_collationKeyGenerator->CompareRawSortKey(key1, key2, result);
2910   NS_ENSURE_SUCCESS(rv, rv);
2911   return rv;
2912 }
2913 
2914 NS_IMETHODIMP
CreateCollationKey(const nsAString & sourceString,nsTArray<uint8_t> & result)2915 nsMsgDatabase::CreateCollationKey(const nsAString& sourceString,
2916                                   nsTArray<uint8_t>& result) {
2917   nsresult err = GetCollationKeyGenerator();
2918   NS_ENSURE_SUCCESS(err, err);
2919   if (!m_collationKeyGenerator) return NS_ERROR_FAILURE;
2920 
2921   err = m_collationKeyGenerator->AllocateRawSortKey(
2922       nsICollation::kCollationCaseInSensitive, sourceString, result);
2923   NS_ENSURE_SUCCESS(err, err);
2924   return err;
2925 }
2926 
RowCellColumnToUInt32(nsIMdbRow * hdrRow,mdb_token columnToken,uint32_t & uint32Result,uint32_t defaultValue)2927 nsresult nsMsgDatabase::RowCellColumnToUInt32(nsIMdbRow* hdrRow,
2928                                               mdb_token columnToken,
2929                                               uint32_t& uint32Result,
2930                                               uint32_t defaultValue) {
2931   return RowCellColumnToUInt32(hdrRow, columnToken, &uint32Result,
2932                                defaultValue);
2933 }
2934 
RowCellColumnToUInt32(nsIMdbRow * hdrRow,mdb_token columnToken,uint32_t * uint32Result,uint32_t defaultValue)2935 nsresult nsMsgDatabase::RowCellColumnToUInt32(nsIMdbRow* hdrRow,
2936                                               mdb_token columnToken,
2937                                               uint32_t* uint32Result,
2938                                               uint32_t defaultValue) {
2939   nsresult err = NS_OK;
2940 
2941   if (uint32Result) *uint32Result = defaultValue;
2942   if (hdrRow)  // ### probably should be an error if hdrRow is NULL...
2943   {
2944     struct mdbYarn yarn;
2945     err = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
2946     if (NS_SUCCEEDED(err)) YarnToUInt32(&yarn, uint32Result);
2947   }
2948   return err;
2949 }
2950 
UInt32ToRowCellColumn(nsIMdbRow * row,mdb_token columnToken,uint32_t value)2951 nsresult nsMsgDatabase::UInt32ToRowCellColumn(nsIMdbRow* row,
2952                                               mdb_token columnToken,
2953                                               uint32_t value) {
2954   struct mdbYarn yarn;
2955   char yarnBuf[100];
2956 
2957   if (!row) return NS_ERROR_NULL_POINTER;
2958 
2959   yarn.mYarn_Buf = (void*)yarnBuf;
2960   yarn.mYarn_Size = sizeof(yarnBuf);
2961   yarn.mYarn_Fill = yarn.mYarn_Size;
2962   yarn.mYarn_Form = 0;
2963   yarn.mYarn_Grow = NULL;
2964   return row->AddColumn(GetEnv(), columnToken, UInt32ToYarn(&yarn, value));
2965 }
2966 
UInt64ToRowCellColumn(nsIMdbRow * row,mdb_token columnToken,uint64_t value)2967 nsresult nsMsgDatabase::UInt64ToRowCellColumn(nsIMdbRow* row,
2968                                               mdb_token columnToken,
2969                                               uint64_t value) {
2970   NS_ENSURE_ARG_POINTER(row);
2971   struct mdbYarn yarn;
2972   char yarnBuf[17];  // max string is 16 bytes, + 1 for null.
2973 
2974   yarn.mYarn_Buf = (void*)yarnBuf;
2975   yarn.mYarn_Size = sizeof(yarnBuf);
2976   yarn.mYarn_Form = 0;
2977   yarn.mYarn_Grow = NULL;
2978   PR_snprintf((char*)yarn.mYarn_Buf, yarn.mYarn_Size, "%llx", value);
2979   yarn.mYarn_Fill = PL_strlen((const char*)yarn.mYarn_Buf);
2980   return row->AddColumn(GetEnv(), columnToken, &yarn);
2981 }
2982 
RowCellColumnToUInt64(nsIMdbRow * hdrRow,mdb_token columnToken,uint64_t * uint64Result,uint64_t defaultValue)2983 nsresult nsMsgDatabase::RowCellColumnToUInt64(nsIMdbRow* hdrRow,
2984                                               mdb_token columnToken,
2985                                               uint64_t* uint64Result,
2986                                               uint64_t defaultValue) {
2987   nsresult err = NS_OK;
2988 
2989   if (uint64Result) *uint64Result = defaultValue;
2990   if (hdrRow)  // ### probably should be an error if hdrRow is NULL...
2991   {
2992     struct mdbYarn yarn;
2993     err = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
2994     if (NS_SUCCEEDED(err)) YarnToUInt64(&yarn, uint64Result);
2995   }
2996   return err;
2997 }
2998 
CharPtrToRowCellColumn(nsIMdbRow * row,mdb_token columnToken,const char * charPtr)2999 nsresult nsMsgDatabase::CharPtrToRowCellColumn(nsIMdbRow* row,
3000                                                mdb_token columnToken,
3001                                                const char* charPtr) {
3002   if (!row) return NS_ERROR_NULL_POINTER;
3003 
3004   struct mdbYarn yarn;
3005   yarn.mYarn_Buf = (void*)charPtr;
3006   yarn.mYarn_Size = PL_strlen((const char*)yarn.mYarn_Buf) + 1;
3007   yarn.mYarn_Fill = yarn.mYarn_Size - 1;
3008   yarn.mYarn_Form =
3009       0;  // what to do with this? we're storing csid in the msg hdr...
3010 
3011   return row->AddColumn(GetEnv(), columnToken, &yarn);
3012 }
3013 
3014 // caller must free result
RowCellColumnToCharPtr(nsIMdbRow * row,mdb_token columnToken,char ** result)3015 nsresult nsMsgDatabase::RowCellColumnToCharPtr(nsIMdbRow* row,
3016                                                mdb_token columnToken,
3017                                                char** result) {
3018   nsresult err = NS_ERROR_NULL_POINTER;
3019 
3020   if (row && result) {
3021     struct mdbYarn yarn;
3022     err = row->AliasCellYarn(GetEnv(), columnToken, &yarn);
3023     if (NS_SUCCEEDED(err)) {
3024       *result = (char*)moz_xmalloc(yarn.mYarn_Fill + 1);
3025       if (*result) {
3026         if (yarn.mYarn_Fill > 0)
3027           memcpy(*result, yarn.mYarn_Buf, yarn.mYarn_Fill);
3028         (*result)[yarn.mYarn_Fill] = '\0';
3029       } else
3030         err = NS_ERROR_OUT_OF_MEMORY;
3031     }
3032   }
3033   return err;
3034 }
3035 
nsStringToYarn(struct mdbYarn * yarn,const nsAString & str)3036 /* static */ struct mdbYarn* nsMsgDatabase::nsStringToYarn(
3037     struct mdbYarn* yarn, const nsAString& str) {
3038   yarn->mYarn_Buf = ToNewCString(NS_ConvertUTF16toUTF8(str));
3039   yarn->mYarn_Size = strlen((const char*)yarn->mYarn_Buf) + 1;
3040   yarn->mYarn_Fill = yarn->mYarn_Size - 1;
3041   yarn->mYarn_Form =
3042       0;  // what to do with this? we're storing csid in the msg hdr...
3043   return yarn;
3044 }
3045 
UInt32ToYarn(struct mdbYarn * yarn,uint32_t i)3046 /* static */ struct mdbYarn* nsMsgDatabase::UInt32ToYarn(struct mdbYarn* yarn,
3047                                                          uint32_t i) {
3048   PR_snprintf((char*)yarn->mYarn_Buf, yarn->mYarn_Size, "%lx", i);
3049   yarn->mYarn_Fill = PL_strlen((const char*)yarn->mYarn_Buf);
3050   yarn->mYarn_Form =
3051       0;  // what to do with this? Should be parsed out of the mime2 header?
3052   return yarn;
3053 }
3054 
UInt64ToYarn(struct mdbYarn * yarn,uint64_t i)3055 /* static */ struct mdbYarn* nsMsgDatabase::UInt64ToYarn(struct mdbYarn* yarn,
3056                                                          uint64_t i) {
3057   PR_snprintf((char*)yarn->mYarn_Buf, yarn->mYarn_Size, "%llx", i);
3058   yarn->mYarn_Fill = PL_strlen((const char*)yarn->mYarn_Buf);
3059   yarn->mYarn_Form = 0;
3060   return yarn;
3061 }
3062 
YarnTonsString(struct mdbYarn * yarn,nsAString & str)3063 /* static */ void nsMsgDatabase::YarnTonsString(struct mdbYarn* yarn,
3064                                                 nsAString& str) {
3065   const char* buf = (const char*)yarn->mYarn_Buf;
3066   if (buf)
3067     CopyUTF8toUTF16(Substring(buf, buf + yarn->mYarn_Fill), str);
3068   else
3069     str.Truncate();
3070 }
3071 
YarnTonsCString(struct mdbYarn * yarn,nsACString & str)3072 /* static */ void nsMsgDatabase::YarnTonsCString(struct mdbYarn* yarn,
3073                                                  nsACString& str) {
3074   const char* buf = (const char*)yarn->mYarn_Buf;
3075   if (buf)
3076     str.Assign(buf, yarn->mYarn_Fill);
3077   else
3078     str.Truncate();
3079 }
3080 
3081 // WARNING - if yarn is empty, *pResult will not be changed!!!!
3082 // this is so we can leave default values as they were.
YarnToUInt32(struct mdbYarn * yarn,uint32_t * pResult)3083 /* static */ void nsMsgDatabase::YarnToUInt32(struct mdbYarn* yarn,
3084                                               uint32_t* pResult) {
3085   uint8_t numChars = std::min<mdb_fill>(8, yarn->mYarn_Fill);
3086 
3087   if (numChars == 0) return;
3088 
3089   *pResult = MsgUnhex((char*)yarn->mYarn_Buf, numChars);
3090 }
3091 
3092 // WARNING - if yarn is empty, *pResult will not be changed!!!!
3093 // this is so we can leave default values as they were.
YarnToUInt64(struct mdbYarn * yarn,uint64_t * pResult)3094 /* static */ void nsMsgDatabase::YarnToUInt64(struct mdbYarn* yarn,
3095                                               uint64_t* pResult) {
3096   uint8_t numChars = std::min<mdb_fill>(16, yarn->mYarn_Fill);
3097 
3098   if (numChars == 0) return;
3099 
3100   *pResult = MsgUnhex((char*)yarn->mYarn_Buf, numChars);
3101 }
3102 
GetProperty(nsIMdbRow * row,const char * propertyName,char ** result)3103 nsresult nsMsgDatabase::GetProperty(nsIMdbRow* row, const char* propertyName,
3104                                     char** result) {
3105   nsresult err = NS_OK;
3106   mdb_token property_token;
3107 
3108   if (m_mdbStore)
3109     err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
3110   else
3111     err = NS_ERROR_NULL_POINTER;
3112   if (NS_SUCCEEDED(err))
3113     err = RowCellColumnToCharPtr(row, property_token, result);
3114 
3115   return err;
3116 }
3117 
SetProperty(nsIMdbRow * row,const char * propertyName,const char * propertyVal)3118 nsresult nsMsgDatabase::SetProperty(nsIMdbRow* row, const char* propertyName,
3119                                     const char* propertyVal) {
3120   nsresult err = NS_OK;
3121   mdb_token property_token;
3122 
3123   NS_ENSURE_STATE(m_mdbStore);  // db might have been closed out from under us.
3124   if (!row) return NS_ERROR_NULL_POINTER;
3125 
3126   err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
3127   if (NS_SUCCEEDED(err))
3128     CharPtrToRowCellColumn(row, property_token, propertyVal);
3129   return err;
3130 }
3131 
GetPropertyAsNSString(nsIMdbRow * row,const char * propertyName,nsAString & result)3132 nsresult nsMsgDatabase::GetPropertyAsNSString(nsIMdbRow* row,
3133                                               const char* propertyName,
3134                                               nsAString& result) {
3135   nsresult err = NS_OK;
3136   mdb_token property_token;
3137 
3138   NS_ENSURE_STATE(m_mdbStore);  // db might have been closed out from under us.
3139   if (!row) return NS_ERROR_NULL_POINTER;
3140 
3141   err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
3142   if (NS_SUCCEEDED(err))
3143     err = RowCellColumnTonsString(row, property_token, result);
3144 
3145   return err;
3146 }
3147 
SetPropertyFromNSString(nsIMdbRow * row,const char * propertyName,const nsAString & propertyVal)3148 nsresult nsMsgDatabase::SetPropertyFromNSString(nsIMdbRow* row,
3149                                                 const char* propertyName,
3150                                                 const nsAString& propertyVal) {
3151   nsresult err = NS_OK;
3152   mdb_token property_token;
3153 
3154   NS_ENSURE_STATE(m_mdbStore);  // db might have been closed out from under us.
3155   if (!row) return NS_ERROR_NULL_POINTER;
3156 
3157   err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
3158   if (NS_SUCCEEDED(err))
3159     return SetNSStringPropertyWithToken(row, property_token, propertyVal);
3160 
3161   return err;
3162 }
3163 
GetUint32Property(nsIMdbRow * row,const char * propertyName,uint32_t * result,uint32_t defaultValue)3164 nsresult nsMsgDatabase::GetUint32Property(nsIMdbRow* row,
3165                                           const char* propertyName,
3166                                           uint32_t* result,
3167                                           uint32_t defaultValue) {
3168   nsresult err = NS_OK;
3169   mdb_token property_token;
3170 
3171   NS_ENSURE_STATE(m_mdbStore);  // db might have been closed out from under us.
3172   if (!row) return NS_ERROR_NULL_POINTER;
3173 
3174   err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
3175   if (NS_SUCCEEDED(err))
3176     err = RowCellColumnToUInt32(row, property_token, result, defaultValue);
3177 
3178   return err;
3179 }
3180 
GetUint64Property(nsIMdbRow * row,const char * propertyName,uint64_t * result,uint64_t defaultValue)3181 nsresult nsMsgDatabase::GetUint64Property(nsIMdbRow* row,
3182                                           const char* propertyName,
3183                                           uint64_t* result,
3184                                           uint64_t defaultValue) {
3185   nsresult err = NS_OK;
3186   mdb_token property_token;
3187 
3188   NS_ENSURE_STATE(m_mdbStore);  // db might have been closed out from under us.
3189   if (!row) return NS_ERROR_NULL_POINTER;
3190 
3191   err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
3192   if (NS_SUCCEEDED(err))
3193     err = RowCellColumnToUInt64(row, property_token, result, defaultValue);
3194 
3195   return err;
3196 }
3197 
SetUint32Property(nsIMdbRow * row,const char * propertyName,uint32_t propertyVal)3198 nsresult nsMsgDatabase::SetUint32Property(nsIMdbRow* row,
3199                                           const char* propertyName,
3200                                           uint32_t propertyVal) {
3201   struct mdbYarn yarn;
3202   char int32StrBuf[20];
3203   yarn.mYarn_Buf = int32StrBuf;
3204   yarn.mYarn_Size = sizeof(int32StrBuf);
3205   yarn.mYarn_Fill = sizeof(int32StrBuf);
3206 
3207   NS_ENSURE_STATE(m_mdbStore);  // db might have been closed out from under us.
3208   if (!row) return NS_ERROR_NULL_POINTER;
3209 
3210   mdb_token property_token;
3211 
3212   nsresult err =
3213       m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
3214   if (NS_SUCCEEDED(err)) {
3215     UInt32ToYarn(&yarn, propertyVal);
3216     err = row->AddColumn(GetEnv(), property_token, &yarn);
3217   }
3218   return err;
3219 }
3220 
SetUint64Property(nsIMdbRow * row,const char * propertyName,uint64_t propertyVal)3221 nsresult nsMsgDatabase::SetUint64Property(nsIMdbRow* row,
3222                                           const char* propertyName,
3223                                           uint64_t propertyVal) {
3224   struct mdbYarn yarn;
3225   char int64StrBuf[100];
3226   yarn.mYarn_Buf = int64StrBuf;
3227   yarn.mYarn_Size = sizeof(int64StrBuf);
3228   yarn.mYarn_Fill = sizeof(int64StrBuf);
3229 
3230   NS_ENSURE_STATE(m_mdbStore);  // db might have been closed out from under us.
3231   if (!row) return NS_ERROR_NULL_POINTER;
3232 
3233   mdb_token property_token;
3234 
3235   nsresult err =
3236       m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
3237   if (NS_SUCCEEDED(err)) {
3238     UInt64ToYarn(&yarn, propertyVal);
3239     err = row->AddColumn(GetEnv(), property_token, &yarn);
3240   }
3241   return err;
3242 }
3243 
GetBooleanProperty(nsIMdbRow * row,const char * propertyName,bool * result,bool defaultValue)3244 nsresult nsMsgDatabase::GetBooleanProperty(nsIMdbRow* row,
3245                                            const char* propertyName,
3246                                            bool* result,
3247                                            bool defaultValue /* = false */) {
3248   uint32_t res;
3249   nsresult rv =
3250       GetUint32Property(row, propertyName, &res, (uint32_t)defaultValue);
3251   *result = !!res;
3252   return rv;
3253 }
3254 
SetBooleanProperty(nsIMdbRow * row,const char * propertyName,bool propertyVal)3255 nsresult nsMsgDatabase::SetBooleanProperty(nsIMdbRow* row,
3256                                            const char* propertyName,
3257                                            bool propertyVal) {
3258   return SetUint32Property(row, propertyName, (uint32_t)propertyVal);
3259 }
3260 
SetNSStringPropertyWithToken(nsIMdbRow * row,mdb_token aProperty,const nsAString & propertyStr)3261 nsresult nsMsgDatabase::SetNSStringPropertyWithToken(
3262     nsIMdbRow* row, mdb_token aProperty, const nsAString& propertyStr) {
3263   NS_ENSURE_ARG(row);
3264   struct mdbYarn yarn;
3265 
3266   yarn.mYarn_Grow = NULL;
3267   nsresult err =
3268       row->AddColumn(GetEnv(), aProperty, nsStringToYarn(&yarn, propertyStr));
3269   free((char*)yarn.mYarn_Buf);  // won't need this when we have nsCString
3270   return err;
3271 }
3272 
GetCurVersion()3273 uint32_t nsMsgDatabase::GetCurVersion() { return kMsgDBVersion; }
3274 
SetSummaryValid(bool valid)3275 NS_IMETHODIMP nsMsgDatabase::SetSummaryValid(bool valid /* = true */) {
3276   // If the file was invalid when opened (for example in folder compact), then
3277   // it may
3278   //  not have been added to the cache. Add it now if missing.
3279   if (valid) {
3280     nsCOMPtr<nsIMsgDBService> serv(mozilla::services::GetDBService());
3281     static_cast<nsMsgDBService*>(serv.get())->EnsureCached(this);
3282   }
3283   // setting the version to 0 ought to make it pretty invalid.
3284   if (m_dbFolderInfo) m_dbFolderInfo->SetVersion(valid ? GetCurVersion() : 0);
3285 
3286   // for default db (and news), there's no nothing to set to make it it valid
3287   return NS_OK;
3288 }
3289 
GetSummaryValid(bool * aResult)3290 NS_IMETHODIMP nsMsgDatabase::GetSummaryValid(bool* aResult) {
3291   NS_ENSURE_ARG_POINTER(aResult);
3292   *aResult = true;
3293   return NS_OK;
3294 }
3295 
3296 // protected routines
3297 
3298 // should we thread messages with common subjects that don't start with Re:
3299 // together? I imagine we might have separate preferences for mail and news, so
3300 // this is a virtual method.
ThreadBySubjectWithoutRe()3301 bool nsMsgDatabase::ThreadBySubjectWithoutRe() {
3302   GetGlobalPrefs();
3303   return gThreadWithoutRe;
3304 }
3305 
UseStrictThreading()3306 bool nsMsgDatabase::UseStrictThreading() {
3307   GetGlobalPrefs();
3308   return gStrictThreading;
3309 }
3310 
3311 // Should we make sure messages are always threaded correctly (see bug 181446)
UseCorrectThreading()3312 bool nsMsgDatabase::UseCorrectThreading() {
3313   GetGlobalPrefs();
3314   return gCorrectThreading;
3315 }
3316 
3317 // adapted from removed PL_DHashFreeStringKey
msg_DHashFreeStringKey(PLDHashTable * aTable,PLDHashEntryHdr * aEntry)3318 static void msg_DHashFreeStringKey(PLDHashTable* aTable,
3319                                    PLDHashEntryHdr* aEntry) {
3320   const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry;
3321   free((void*)stub->key);
3322   PLDHashTable::ClearEntryStub(aTable, aEntry);
3323 }
3324 
3325 PLDHashTableOps nsMsgDatabase::gRefHashTableOps = {
3326     PLDHashTable::HashStringKey, PLDHashTable::MatchStringKey,
3327     PLDHashTable::MoveEntryStub, msg_DHashFreeStringKey, nullptr};
3328 
GetRefFromHash(nsCString & reference,nsMsgKey * threadId)3329 nsresult nsMsgDatabase::GetRefFromHash(nsCString& reference,
3330                                        nsMsgKey* threadId) {
3331   // Initialize the reference hash
3332   if (!m_msgReferences) {
3333     nsresult rv = InitRefHash();
3334     if (NS_FAILED(rv)) return rv;
3335   }
3336 
3337   // Find reference from the hash
3338   PLDHashEntryHdr* entry =
3339       m_msgReferences->Search((const void*)reference.get());
3340   if (entry) {
3341     RefHashElement* element = static_cast<RefHashElement*>(entry);
3342     *threadId = element->mThreadId;
3343     return NS_OK;
3344   }
3345 
3346   return NS_ERROR_FAILURE;
3347 }
3348 
AddRefToHash(nsCString & reference,nsMsgKey threadId)3349 nsresult nsMsgDatabase::AddRefToHash(nsCString& reference, nsMsgKey threadId) {
3350   if (m_msgReferences) {
3351     PLDHashEntryHdr* entry =
3352         m_msgReferences->Add((void*)reference.get(), mozilla::fallible);
3353     if (!entry) return NS_ERROR_OUT_OF_MEMORY;  // XXX out of memory
3354 
3355     RefHashElement* element = static_cast<RefHashElement*>(entry);
3356     if (!element->mRef) {
3357       element->mRef =
3358           ToNewCString(reference);  // Will be freed in msg_DHashFreeStringKey()
3359       element->mThreadId = threadId;
3360       element->mCount = 1;
3361     } else
3362       element->mCount++;
3363   }
3364 
3365   return NS_OK;
3366 }
3367 
AddMsgRefsToHash(nsIMsgDBHdr * msgHdr)3368 nsresult nsMsgDatabase::AddMsgRefsToHash(nsIMsgDBHdr* msgHdr) {
3369   uint16_t numReferences = 0;
3370   nsMsgKey threadId;
3371   nsresult rv = NS_OK;
3372 
3373   msgHdr->GetThreadId(&threadId);
3374   msgHdr->GetNumReferences(&numReferences);
3375 
3376   for (int32_t i = 0; i < numReferences; i++) {
3377     nsAutoCString reference;
3378 
3379     msgHdr->GetStringReference(i, reference);
3380     if (reference.IsEmpty()) break;
3381 
3382     rv = AddRefToHash(reference, threadId);
3383     if (NS_FAILED(rv)) break;
3384   }
3385 
3386   return rv;
3387 }
3388 
RemoveRefFromHash(nsCString & reference)3389 nsresult nsMsgDatabase::RemoveRefFromHash(nsCString& reference) {
3390   if (m_msgReferences) {
3391     PLDHashEntryHdr* entry =
3392         m_msgReferences->Search((const void*)reference.get());
3393     if (entry) {
3394       RefHashElement* element = static_cast<RefHashElement*>(entry);
3395       if (--element->mCount == 0)
3396         m_msgReferences->Remove((void*)reference.get());
3397     }
3398   }
3399   return NS_OK;
3400 }
3401 
3402 // Filter only messages with one or more references
RemoveMsgRefsFromHash(nsIMsgDBHdr * msgHdr)3403 nsresult nsMsgDatabase::RemoveMsgRefsFromHash(nsIMsgDBHdr* msgHdr) {
3404   uint16_t numReferences = 0;
3405   nsresult rv = NS_OK;
3406 
3407   msgHdr->GetNumReferences(&numReferences);
3408 
3409   for (int32_t i = 0; i < numReferences; i++) {
3410     nsAutoCString reference;
3411 
3412     msgHdr->GetStringReference(i, reference);
3413     if (reference.IsEmpty()) break;
3414 
3415     rv = RemoveRefFromHash(reference);
3416     if (NS_FAILED(rv)) break;
3417   }
3418 
3419   return rv;
3420 }
3421 
nsReferencesOnlyFilter(nsIMsgDBHdr * msg,void * closure)3422 static nsresult nsReferencesOnlyFilter(nsIMsgDBHdr* msg, void* closure) {
3423   uint16_t numReferences = 0;
3424   msg->GetNumReferences(&numReferences);
3425   return (numReferences) ? NS_OK : NS_ERROR_FAILURE;
3426 }
3427 
InitRefHash()3428 nsresult nsMsgDatabase::InitRefHash() {
3429   // Delete an existing table just in case
3430   if (m_msgReferences) delete m_msgReferences;
3431 
3432   // Create new table
3433   m_msgReferences = new PLDHashTable(
3434       &gRefHashTableOps, sizeof(struct RefHashElement), MSG_HASH_SIZE);
3435   if (!m_msgReferences) return NS_ERROR_OUT_OF_MEMORY;
3436 
3437   // Create enumerator to go through all messages with references
3438   nsCOMPtr<nsIMsgEnumerator> enumerator;
3439   enumerator = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
3440                                      nsReferencesOnlyFilter, nullptr);
3441   if (enumerator == nullptr) return NS_ERROR_OUT_OF_MEMORY;
3442 
3443   // Populate table with references of existing messages
3444   bool hasMore;
3445   nsresult rv = NS_OK;
3446   while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore) {
3447     nsCOMPtr<nsIMsgDBHdr> msgHdr;
3448     rv = enumerator->GetNext(getter_AddRefs(msgHdr));
3449     if (msgHdr && NS_SUCCEEDED(rv)) rv = AddMsgRefsToHash(msgHdr);
3450     if (NS_FAILED(rv)) break;
3451   }
3452 
3453   return rv;
3454 }
3455 
CreateNewThread(nsMsgKey threadId,const char * subject,nsMsgThread ** pnewThread)3456 nsresult nsMsgDatabase::CreateNewThread(nsMsgKey threadId, const char* subject,
3457                                         nsMsgThread** pnewThread) {
3458   nsresult err = NS_OK;
3459   nsCOMPtr<nsIMdbTable> threadTable;
3460   struct mdbOid threadTableOID;
3461   struct mdbOid allThreadsTableOID;
3462 
3463   if (!pnewThread || !m_mdbStore) return NS_ERROR_NULL_POINTER;
3464 
3465   threadTableOID.mOid_Scope = m_hdrRowScopeToken;
3466   threadTableOID.mOid_Id = threadId;
3467 
3468   // Under some circumstances, mork seems to reuse an old table when we create
3469   // one. Prevent problems from that by finding any old table first, and
3470   // deleting its rows.
3471   nsresult res = GetStore()->GetTable(GetEnv(), &threadTableOID,
3472                                       getter_AddRefs(threadTable));
3473   if (NS_SUCCEEDED(res) && threadTable) threadTable->CutAllRows(GetEnv());
3474 
3475   err = GetStore()->NewTableWithOid(GetEnv(), &threadTableOID,
3476                                     m_threadTableKindToken, false, nullptr,
3477                                     getter_AddRefs(threadTable));
3478   if (NS_FAILED(err)) return err;
3479 
3480   allThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
3481   allThreadsTableOID.mOid_Id = threadId;
3482 
3483   // add a row for this thread in the table of all threads that we'll use
3484   // to do our mapping between subject strings and threads.
3485   nsCOMPtr<nsIMdbRow> threadRow;
3486 
3487   err = m_mdbStore->GetRow(GetEnv(), &allThreadsTableOID,
3488                            getter_AddRefs(threadRow));
3489   if (!threadRow) {
3490     err = m_mdbStore->NewRowWithOid(GetEnv(), &allThreadsTableOID,
3491                                     getter_AddRefs(threadRow));
3492     if (NS_SUCCEEDED(err) && threadRow) {
3493       if (m_mdbAllThreadsTable)
3494         m_mdbAllThreadsTable->AddRow(GetEnv(), threadRow);
3495       err = CharPtrToRowCellColumn(threadRow, m_threadSubjectColumnToken,
3496                                    subject);
3497     }
3498   } else {
3499 #ifdef DEBUG_David_Bienvenu
3500     NS_WARNING("odd that thread row already exists");
3501 #endif
3502     threadRow->CutAllColumns(GetEnv());
3503     nsCOMPtr<nsIMdbRow> metaRow;
3504     threadTable->GetMetaRow(GetEnv(), nullptr, nullptr,
3505                             getter_AddRefs(metaRow));
3506     if (metaRow) metaRow->CutAllColumns(GetEnv());
3507 
3508     CharPtrToRowCellColumn(threadRow, m_threadSubjectColumnToken, subject);
3509   }
3510 
3511   *pnewThread = new nsMsgThread(this, threadTable);
3512   if (*pnewThread) {
3513     (*pnewThread)->SetThreadKey(threadId);
3514     m_cachedThread = *pnewThread;
3515     m_cachedThreadId = threadId;
3516   }
3517   return err;
3518 }
3519 
GetThreadForReference(nsCString & msgID,nsIMsgDBHdr ** pMsgHdr)3520 nsIMsgThread* nsMsgDatabase::GetThreadForReference(nsCString& msgID,
3521                                                    nsIMsgDBHdr** pMsgHdr) {
3522   nsMsgKey threadId;
3523   nsIMsgDBHdr* msgHdr = nullptr;
3524   GetMsgHdrForMessageID(msgID.get(), &msgHdr);
3525   nsIMsgThread* thread = NULL;
3526 
3527   if (msgHdr != NULL) {
3528     if (NS_SUCCEEDED(msgHdr->GetThreadId(&threadId))) {
3529       // find thread header for header whose message id we matched.
3530       thread = GetThreadForThreadId(threadId);
3531     }
3532     if (pMsgHdr)
3533       *pMsgHdr = msgHdr;
3534     else
3535       msgHdr->Release();
3536   }
3537   // Referenced message not found, check if there are messages that reference
3538   // same message
3539   else if (UseCorrectThreading()) {
3540     if (NS_SUCCEEDED(GetRefFromHash(msgID, &threadId)))
3541       thread = GetThreadForThreadId(threadId);
3542   }
3543 
3544   return thread;
3545 }
3546 
GetThreadForSubject(nsCString & subject)3547 nsIMsgThread* nsMsgDatabase::GetThreadForSubject(nsCString& subject) {
3548   nsIMsgThread* thread = nullptr;
3549 
3550   mdbYarn subjectYarn;
3551 
3552   subjectYarn.mYarn_Buf = (void*)subject.get();
3553   subjectYarn.mYarn_Fill = PL_strlen(subject.get());
3554   subjectYarn.mYarn_Form = 0;
3555   subjectYarn.mYarn_Size = subjectYarn.mYarn_Fill;
3556 
3557   nsCOMPtr<nsIMdbRow> threadRow;
3558   mdbOid outRowId;
3559   if (m_mdbStore) {
3560     nsresult result = m_mdbStore->FindRow(
3561         GetEnv(), m_threadRowScopeToken, m_threadSubjectColumnToken,
3562         &subjectYarn, &outRowId, getter_AddRefs(threadRow));
3563     if (NS_SUCCEEDED(result) && threadRow) {
3564       // Get key from row
3565       mdbOid outOid;
3566       nsMsgKey key = nsMsgKey_None;
3567       if (NS_SUCCEEDED(threadRow->GetOid(GetEnv(), &outOid)))
3568         key = outOid.mOid_Id;
3569       // find thread header for header whose message id we matched.
3570       // It is fine if key was not found,
3571       // GetThreadForThreadId(nsMsgKey_None) returns nullptr.
3572       thread = GetThreadForThreadId(key);
3573     }
3574 #ifdef DEBUG_bienvenu1
3575     else {
3576       nsresult rv;
3577       RefPtr<nsMsgThread> pThread;
3578 
3579       nsCOMPtr<nsIMdbPortTableCursor> tableCursor;
3580       m_mdbStore->GetPortTableCursor(GetEnv(), m_hdrRowScopeToken,
3581                                      m_threadTableKindToken,
3582                                      getter_AddRefs(tableCursor));
3583 
3584       nsCOMPtr<nsIMdbTable> table;
3585 
3586       while (true) {
3587         rv = tableCursor->NextTable(GetEnv(), getter_AddRefs(table));
3588         if (!table) break;
3589         if (NS_FAILED(rv)) break;
3590 
3591         pThread = new nsMsgThread(this, table);
3592         if (pThread) {
3593           nsCString curSubject;
3594           pThread->GetSubject(curSubject);
3595           if (subject.Equals(curSubject)) {
3596             NS_ERROR("thread with subject exists, but FindRow didn't find it");
3597             break;
3598           }
3599         } else
3600           break;
3601       }
3602     }
3603 #endif
3604   }
3605   return thread;
3606 }
3607 
3608 // Returns thread that contains a message that references the passed message ID
GetThreadForMessageId(nsCString & msgId)3609 nsIMsgThread* nsMsgDatabase::GetThreadForMessageId(nsCString& msgId) {
3610   nsIMsgThread* thread = NULL;
3611   nsMsgKey threadId;
3612 
3613   if (NS_SUCCEEDED(GetRefFromHash(msgId, &threadId)))
3614     thread = GetThreadForThreadId(threadId);
3615 
3616   return thread;
3617 }
3618 
ThreadNewHdr(nsMsgHdr * newHdr,bool & newThread)3619 nsresult nsMsgDatabase::ThreadNewHdr(nsMsgHdr* newHdr, bool& newThread) {
3620   nsresult result = NS_ERROR_UNEXPECTED;
3621   nsCOMPtr<nsIMsgThread> thread;
3622   nsCOMPtr<nsIMsgDBHdr> replyToHdr;
3623   nsMsgKey threadId = nsMsgKey_None, newHdrKey;
3624 
3625   if (!newHdr) return NS_ERROR_NULL_POINTER;
3626 
3627   newHdr->SetThreadParent(
3628       nsMsgKey_None);  // if we're undoing, could have a thread parent
3629   uint16_t numReferences = 0;
3630   uint32_t newHdrFlags = 0;
3631 
3632   // use raw flags instead of GetFlags, because GetFlags will
3633   // pay attention to what's in m_newSet, and this new hdr isn't
3634   // in m_newSet yet.
3635   newHdr->GetRawFlags(&newHdrFlags);
3636   newHdr->GetNumReferences(&numReferences);
3637   newHdr->GetMessageKey(&newHdrKey);
3638 
3639   // try reference threading first
3640   for (int32_t i = numReferences - 1; i >= 0; i--) {
3641     nsAutoCString reference;
3642 
3643     newHdr->GetStringReference(i, reference);
3644     // first reference we have hdr for is best top-level hdr.
3645     // but we have to handle case of promoting new header to top-level
3646     // in case the top-level header comes after a reply.
3647 
3648     if (reference.IsEmpty()) break;
3649 
3650     thread = dont_AddRef(
3651         GetThreadForReference(reference, getter_AddRefs(replyToHdr)));
3652     if (thread) {
3653       if (replyToHdr) {
3654         nsMsgKey replyToKey;
3655         replyToHdr->GetMessageKey(&replyToKey);
3656         // message claims to be a reply to itself - ignore that since it leads
3657         // to corrupt threading.
3658         if (replyToKey == newHdrKey) {
3659           // bad references - throw them all away.
3660           newHdr->SetMessageId("");
3661           thread = nullptr;
3662           break;
3663         }
3664       }
3665       thread->GetThreadKey(&threadId);
3666       newHdr->SetThreadId(threadId);
3667       result = AddToThread(newHdr, thread, replyToHdr, true);
3668       break;
3669     }
3670   }
3671   // if user hasn't said "only thread by ref headers", thread by subject
3672   if (!thread && !UseStrictThreading()) {
3673     // try subject threading if we couldn't find a reference and the subject
3674     // starts with Re:
3675     nsCString subject;
3676     newHdr->GetSubject(getter_Copies(subject));
3677     if (ThreadBySubjectWithoutRe() ||
3678         (newHdrFlags & nsMsgMessageFlags::HasRe)) {
3679       nsAutoCString cSubject(subject);
3680       thread = dont_AddRef(GetThreadForSubject(cSubject));
3681       if (thread) {
3682         thread->GetThreadKey(&threadId);
3683         newHdr->SetThreadId(threadId);
3684         // TRACE("threading based on subject %s\n", (const char *)
3685         // msgHdr->m_subject);
3686         // if we move this and do subject threading after, ref threading,
3687         // don't thread within children, since we know it won't work. But for
3688         // now, pass TRUE.
3689         result = AddToThread(newHdr, thread, nullptr, true);
3690       }
3691     }
3692   }
3693 
3694   // Check if this is a new parent to an existing message (that has a reference
3695   // to this message)
3696   if (!thread && UseCorrectThreading()) {
3697     nsCString msgId;
3698     newHdr->GetMessageId(getter_Copies(msgId));
3699 
3700     thread = dont_AddRef(GetThreadForMessageId(msgId));
3701     if (thread) {
3702       thread->GetThreadKey(&threadId);
3703       newHdr->SetThreadId(threadId);
3704       result = AddToThread(newHdr, thread, nullptr, true);
3705     }
3706   }
3707 
3708   if (!thread) {
3709     // Not a parent or child, make it a new thread for now
3710     result = AddNewThread(newHdr);
3711     newThread = true;
3712   } else {
3713     newThread = false;
3714   }
3715   return result;
3716 }
3717 
AddToThread(nsMsgHdr * newHdr,nsIMsgThread * thread,nsIMsgDBHdr * inReplyTo,bool threadInThread)3718 nsresult nsMsgDatabase::AddToThread(nsMsgHdr* newHdr, nsIMsgThread* thread,
3719                                     nsIMsgDBHdr* inReplyTo,
3720                                     bool threadInThread) {
3721   // don't worry about real threading yet.
3722   return thread->AddChild(newHdr, inReplyTo, threadInThread, this);
3723 }
3724 
GetMsgHdrForReference(nsCString & reference)3725 nsMsgHdr* nsMsgDatabase::GetMsgHdrForReference(nsCString& reference) {
3726   NS_ASSERTION(false, "not implemented yet.");
3727   return nullptr;
3728 }
3729 
GetMsgHdrForMessageID(const char * aMsgID,nsIMsgDBHdr ** aHdr)3730 NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForMessageID(const char* aMsgID,
3731                                                    nsIMsgDBHdr** aHdr) {
3732   NS_ENSURE_ARG_POINTER(aHdr);
3733   NS_ENSURE_ARG_POINTER(aMsgID);
3734   nsIMsgDBHdr* msgHdr = nullptr;
3735   nsresult rv = NS_OK;
3736   mdbYarn messageIdYarn;
3737 
3738   messageIdYarn.mYarn_Buf = (void*)aMsgID;
3739   messageIdYarn.mYarn_Fill = PL_strlen(aMsgID);
3740   messageIdYarn.mYarn_Form = 0;
3741   messageIdYarn.mYarn_Size = messageIdYarn.mYarn_Fill;
3742 
3743   nsIMdbRow* hdrRow;
3744   mdbOid outRowId;
3745   nsresult result;
3746   if (m_mdbStore)
3747     result = m_mdbStore->FindRow(GetEnv(), m_hdrRowScopeToken,
3748                                  m_messageIdColumnToken, &messageIdYarn,
3749                                  &outRowId, &hdrRow);
3750   else
3751     return NS_ERROR_FAILURE;
3752   if (NS_SUCCEEDED(result) && hdrRow) {
3753     // Get key from row
3754     mdbOid outOid;
3755     nsMsgKey key = nsMsgKey_None;
3756     rv = hdrRow->GetOid(GetEnv(), &outOid);
3757     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
3758     key = outOid.mOid_Id;
3759 
3760     rv = CreateMsgHdr(hdrRow, key, &msgHdr);
3761     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
3762   }
3763   *aHdr = msgHdr;  // already addreffed above.
3764   return NS_OK;    // it's not an error not to find a msg hdr.
3765 }
3766 
GetMsgHdrForGMMsgID(const char * aGMMsgId,nsIMsgDBHdr ** aHdr)3767 NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForGMMsgID(const char* aGMMsgId,
3768                                                  nsIMsgDBHdr** aHdr) {
3769   NS_ENSURE_ARG_POINTER(aGMMsgId);
3770   NS_ENSURE_ARG_POINTER(aHdr);
3771   nsIMsgDBHdr* msgHdr = nullptr;
3772   nsresult rv = NS_OK;
3773   mdbYarn gMailMessageIdYarn;
3774   gMailMessageIdYarn.mYarn_Buf = (void*)aGMMsgId;
3775   gMailMessageIdYarn.mYarn_Fill = strlen(aGMMsgId);
3776   gMailMessageIdYarn.mYarn_Form = 0;
3777   gMailMessageIdYarn.mYarn_Size = gMailMessageIdYarn.mYarn_Fill;
3778 
3779   nsIMdbRow* hdrRow;
3780   mdbOid outRowId;
3781   nsresult result;
3782   mdb_token property_token;
3783   NS_ENSURE_TRUE(m_mdbStore, NS_ERROR_NULL_POINTER);
3784   result = m_mdbStore->StringToToken(GetEnv(), "X-GM-MSGID", &property_token);
3785   NS_ENSURE_SUCCESS(result, result);
3786   result = m_mdbStore->FindRow(GetEnv(), m_hdrRowScopeToken, property_token,
3787                                &gMailMessageIdYarn, &outRowId, &hdrRow);
3788   if (NS_SUCCEEDED(result) && hdrRow) {
3789     // Get key from row
3790     mdbOid outOid;
3791     rv = hdrRow->GetOid(GetEnv(), &outOid);
3792     NS_ENSURE_SUCCESS(rv, rv);
3793     nsMsgKey key = outOid.mOid_Id;
3794     rv = CreateMsgHdr(hdrRow, key, &msgHdr);
3795     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
3796   }
3797   *aHdr = msgHdr;
3798   return NS_OK;  // it's not an error not to find a msg hdr.
3799 }
3800 
GetMsgHdrForSubject(nsCString & subject)3801 nsIMsgDBHdr* nsMsgDatabase::GetMsgHdrForSubject(nsCString& subject) {
3802   nsIMsgDBHdr* msgHdr = nullptr;
3803   nsresult rv = NS_OK;
3804   mdbYarn subjectYarn;
3805 
3806   subjectYarn.mYarn_Buf = (void*)subject.get();
3807   subjectYarn.mYarn_Fill = PL_strlen(subject.get());
3808   subjectYarn.mYarn_Form = 0;
3809   subjectYarn.mYarn_Size = subjectYarn.mYarn_Fill;
3810 
3811   nsIMdbRow* hdrRow;
3812   mdbOid outRowId;
3813   nsresult result =
3814       GetStore()->FindRow(GetEnv(), m_hdrRowScopeToken, m_subjectColumnToken,
3815                           &subjectYarn, &outRowId, &hdrRow);
3816   if (NS_SUCCEEDED(result) && hdrRow) {
3817     // Get key from row
3818     mdbOid outOid;
3819     nsMsgKey key = nsMsgKey_None;
3820     rv = hdrRow->GetOid(GetEnv(), &outOid);
3821     if (NS_WARN_IF(NS_FAILED(rv))) return nullptr;
3822     key = outOid.mOid_Id;
3823 
3824     rv = CreateMsgHdr(hdrRow, key, &msgHdr);
3825     if (NS_WARN_IF(NS_FAILED(rv))) return nullptr;
3826   }
3827   return msgHdr;
3828 }
3829 
GetThreadContainingMsgHdr(nsIMsgDBHdr * msgHdr,nsIMsgThread ** result)3830 NS_IMETHODIMP nsMsgDatabase::GetThreadContainingMsgHdr(nsIMsgDBHdr* msgHdr,
3831                                                        nsIMsgThread** result) {
3832   NS_ENSURE_ARG_POINTER(msgHdr);
3833   NS_ENSURE_ARG_POINTER(result);
3834 
3835   *result = nullptr;
3836   nsMsgKey threadId = nsMsgKey_None;
3837   (void)msgHdr->GetThreadId(&threadId);
3838   if (threadId != nsMsgKey_None) *result = GetThreadForThreadId(threadId);
3839 
3840   // if we can't find the thread, try using the msg key as the thread id,
3841   // because the msg hdr might not have the thread id set correctly
3842   // Or maybe the message was deleted?
3843   if (!*result) {
3844     nsMsgKey msgKey;
3845     msgHdr->GetMessageKey(&msgKey);
3846     *result = GetThreadForThreadId(msgKey);
3847   }
3848   // failure is normal when message was deleted
3849   return (*result) ? NS_OK : NS_ERROR_FAILURE;
3850 }
3851 
GetThreadForMsgKey(nsMsgKey msgKey,nsIMsgThread ** aResult)3852 nsresult nsMsgDatabase::GetThreadForMsgKey(nsMsgKey msgKey,
3853                                            nsIMsgThread** aResult) {
3854   NS_ENSURE_ARG_POINTER(aResult);
3855 
3856   nsCOMPtr<nsIMsgDBHdr> msg;
3857   nsresult rv = GetMsgHdrForKey(msgKey, getter_AddRefs(msg));
3858 
3859   if (NS_SUCCEEDED(rv) && msg) rv = GetThreadContainingMsgHdr(msg, aResult);
3860 
3861   return rv;
3862 }
3863 
3864 // caller needs to unrefer.
GetThreadForThreadId(nsMsgKey threadId)3865 nsIMsgThread* nsMsgDatabase::GetThreadForThreadId(nsMsgKey threadId) {
3866   nsIMsgThread* retThread = (threadId == m_cachedThreadId && m_cachedThread)
3867                                 ? m_cachedThread.get()
3868                                 : FindExistingThread(threadId);
3869   if (retThread) {
3870     NS_ADDREF(retThread);
3871     return retThread;
3872   }
3873   if (m_mdbStore) {
3874     mdbOid tableId;
3875     tableId.mOid_Id = threadId;
3876     tableId.mOid_Scope = m_hdrRowScopeToken;
3877 
3878     nsCOMPtr<nsIMdbTable> threadTable;
3879     nsresult res =
3880         m_mdbStore->GetTable(GetEnv(), &tableId, getter_AddRefs(threadTable));
3881 
3882     if (NS_SUCCEEDED(res) && threadTable) {
3883       retThread = new nsMsgThread(this, threadTable);
3884       if (retThread) {
3885         NS_ADDREF(retThread);
3886         m_cachedThread = retThread;
3887         m_cachedThreadId = threadId;
3888       }
3889     }
3890   }
3891   return retThread;
3892 }
3893 
3894 // make the passed in header a thread header
AddNewThread(nsMsgHdr * msgHdr)3895 nsresult nsMsgDatabase::AddNewThread(nsMsgHdr* msgHdr) {
3896   if (!msgHdr) return NS_ERROR_NULL_POINTER;
3897 
3898   nsMsgThread* threadHdr = nullptr;
3899 
3900   nsCString subject;
3901   nsMsgKey threadKey;
3902   msgHdr->GetMessageKey(&threadKey);
3903   // can't have a thread with key 1 since that's the table id of the all msg hdr
3904   // table, so give it kTableKeyForThreadOne (0xfffffffe).
3905   if (threadKey == kAllMsgHdrsTableKey) threadKey = kTableKeyForThreadOne;
3906 
3907   nsresult err = msgHdr->GetSubject(getter_Copies(subject));
3908 
3909   err = CreateNewThread(threadKey, subject.get(), &threadHdr);
3910   msgHdr->SetThreadId(threadKey);
3911   if (threadHdr) {
3912     NS_ADDREF(threadHdr);
3913     // err = msgHdr->GetSubject(subject);
3914     // threadHdr->SetThreadKey(msgHdr->m_messageKey);
3915     // threadHdr->SetSubject(subject.get());
3916     // need to add the thread table to the db.
3917     AddToThread(msgHdr, threadHdr, nullptr, false);
3918     NS_RELEASE(threadHdr);
3919   }
3920   return err;
3921 }
3922 
GetBoolPref(const char * prefName,bool * result)3923 nsresult nsMsgDatabase::GetBoolPref(const char* prefName, bool* result) {
3924   bool prefValue = false;
3925   nsresult rv;
3926   nsCOMPtr<nsIPrefBranch> pPrefBranch(
3927       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
3928   if (pPrefBranch) {
3929     rv = pPrefBranch->GetBoolPref(prefName, &prefValue);
3930     *result = prefValue;
3931   }
3932   return rv;
3933 }
3934 
GetIntPref(const char * prefName,int32_t * result)3935 nsresult nsMsgDatabase::GetIntPref(const char* prefName, int32_t* result) {
3936   int32_t prefValue = 0;
3937   nsresult rv;
3938   nsCOMPtr<nsIPrefBranch> pPrefBranch(
3939       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
3940   if (pPrefBranch) {
3941     rv = pPrefBranch->GetIntPref(prefName, &prefValue);
3942     *result = prefValue;
3943   }
3944   return rv;
3945 }
3946 
SetAttributeOnPendingHdr(nsIMsgDBHdr * pendingHdr,const char * property,const char * propertyVal)3947 NS_IMETHODIMP nsMsgDatabase::SetAttributeOnPendingHdr(nsIMsgDBHdr* pendingHdr,
3948                                                       const char* property,
3949                                                       const char* propertyVal) {
3950   return NS_ERROR_NOT_IMPLEMENTED;
3951 }
3952 
SetUint32AttributeOnPendingHdr(nsIMsgDBHdr * pendingHdr,const char * property,uint32_t propertyVal)3953 NS_IMETHODIMP nsMsgDatabase::SetUint32AttributeOnPendingHdr(
3954     nsIMsgDBHdr* pendingHdr, const char* property, uint32_t propertyVal) {
3955   return NS_ERROR_NOT_IMPLEMENTED;
3956 }
3957 
3958 NS_IMETHODIMP
SetUint64AttributeOnPendingHdr(nsIMsgDBHdr * aPendingHdr,const char * aProperty,uint64_t aPropertyVal)3959 nsMsgDatabase::SetUint64AttributeOnPendingHdr(nsIMsgDBHdr* aPendingHdr,
3960                                               const char* aProperty,
3961                                               uint64_t aPropertyVal) {
3962   return NS_ERROR_NOT_IMPLEMENTED;
3963 }
3964 
3965 NS_IMETHODIMP
UpdatePendingAttributes(nsIMsgDBHdr * aNewHdr)3966 nsMsgDatabase::UpdatePendingAttributes(nsIMsgDBHdr* aNewHdr) { return NS_OK; }
3967 
GetOfflineOpForKey(nsMsgKey msgKey,bool create,nsIMsgOfflineImapOperation ** offlineOp)3968 NS_IMETHODIMP nsMsgDatabase::GetOfflineOpForKey(
3969     nsMsgKey msgKey, bool create, nsIMsgOfflineImapOperation** offlineOp) {
3970   NS_ASSERTION(false, "overridden by nsMailDatabase");
3971   return NS_ERROR_NOT_IMPLEMENTED;
3972 }
3973 
RemoveOfflineOp(nsIMsgOfflineImapOperation * op)3974 NS_IMETHODIMP nsMsgDatabase::RemoveOfflineOp(nsIMsgOfflineImapOperation* op) {
3975   NS_ASSERTION(false, "overridden by nsMailDatabase");
3976   return NS_ERROR_NOT_IMPLEMENTED;
3977 }
3978 
ListAllOfflineMsgs(nsTArray<nsMsgKey> & keys)3979 NS_IMETHODIMP nsMsgDatabase::ListAllOfflineMsgs(nsTArray<nsMsgKey>& keys) {
3980   keys.Clear();
3981   nsCOMPtr<nsIMsgEnumerator> enumerator;
3982   uint32_t flag = nsMsgMessageFlags::Offline;
3983   // if we change this routine to return an enumerator that generates the keys
3984   // one by one, we'll need to somehow make a copy of flag for the enumerator
3985   // to own, since the enumerator will persist past the life of flag on the
3986   // stack.
3987   nsresult rv = EnumerateMessagesWithFlag(getter_AddRefs(enumerator), &flag);
3988   if (NS_SUCCEEDED(rv) && enumerator) {
3989     bool hasMoreElements;
3990     while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) &&
3991            hasMoreElements) {
3992       // clear out db hdr, because it won't be valid when we get rid of the .msf
3993       // file
3994       nsCOMPtr<nsIMsgDBHdr> dbMessage;
3995       rv = enumerator->GetNext(getter_AddRefs(dbMessage));
3996       if (NS_SUCCEEDED(rv) && dbMessage) {
3997         nsMsgKey msgKey;
3998         dbMessage->GetMessageKey(&msgKey);
3999         keys.AppendElement(msgKey);
4000       }
4001     }
4002   }
4003   return rv;
4004 }
4005 
ListAllOfflineOpIds(nsTArray<nsMsgKey> * offlineOpIds)4006 NS_IMETHODIMP nsMsgDatabase::ListAllOfflineOpIds(
4007     nsTArray<nsMsgKey>* offlineOpIds) {
4008   NS_ASSERTION(false, "overridden by nsMailDatabase");
4009   return NS_ERROR_NOT_IMPLEMENTED;
4010 }
4011 
ListAllOfflineDeletes(nsTArray<nsMsgKey> * offlineDeletes)4012 NS_IMETHODIMP nsMsgDatabase::ListAllOfflineDeletes(
4013     nsTArray<nsMsgKey>* offlineDeletes) {
4014   nsresult ret = NS_OK;
4015   if (!offlineDeletes) return NS_ERROR_NULL_POINTER;
4016 
4017   // technically, notimplemented, but no one's putting offline ops in anyway.
4018   return ret;
4019 }
GetHighWaterArticleNum(nsMsgKey * key)4020 NS_IMETHODIMP nsMsgDatabase::GetHighWaterArticleNum(nsMsgKey* key) {
4021   if (!m_dbFolderInfo) return NS_ERROR_NULL_POINTER;
4022   return m_dbFolderInfo->GetHighWater(key);
4023 }
4024 
GetLowWaterArticleNum(nsMsgKey * key)4025 NS_IMETHODIMP nsMsgDatabase::GetLowWaterArticleNum(nsMsgKey* key) {
4026   return NS_ERROR_NOT_IMPLEMENTED;
4027 }
4028 
4029 /* attribute nsMsgKey NextPseudoMsgKey */
4030 
GetNextPseudoMsgKey(nsMsgKey * nextPseudoMsgKey)4031 NS_IMETHODIMP nsMsgDatabase::GetNextPseudoMsgKey(nsMsgKey* nextPseudoMsgKey) {
4032   NS_ENSURE_ARG_POINTER(nextPseudoMsgKey);
4033   *nextPseudoMsgKey = m_nextPseudoMsgKey--;
4034   return NS_OK;
4035 }
4036 
SetNextPseudoMsgKey(nsMsgKey nextPseudoMsgKey)4037 NS_IMETHODIMP nsMsgDatabase::SetNextPseudoMsgKey(nsMsgKey nextPseudoMsgKey) {
4038   m_nextPseudoMsgKey = nextPseudoMsgKey;
4039   return NS_OK;
4040 }
4041 
GetNextFakeOfflineMsgKey(nsMsgKey * nextFakeOfflineMsgKey)4042 NS_IMETHODIMP nsMsgDatabase::GetNextFakeOfflineMsgKey(
4043     nsMsgKey* nextFakeOfflineMsgKey) {
4044   NS_ENSURE_ARG_POINTER(nextFakeOfflineMsgKey);
4045   // iterate over hdrs looking for first non-existent fake offline msg key
4046   nsMsgKey fakeMsgKey = kIdStartOfFake;
4047 
4048   bool containsKey;
4049   do {
4050     ContainsKey(fakeMsgKey, &containsKey);
4051     if (!containsKey) break;
4052     fakeMsgKey--;
4053   } while (containsKey);
4054 
4055   *nextFakeOfflineMsgKey = fakeMsgKey;
4056   return NS_OK;
4057 }
4058 
4059 #ifdef DEBUG
DumpContents()4060 nsresult nsMsgDatabase::DumpContents() {
4061   nsTArray<nsMsgKey> keys;
4062   nsresult rv = ListAllKeys(keys);
4063   NS_ENSURE_SUCCESS(rv, rv);
4064   for (nsMsgKey key : keys) {
4065     nsCOMPtr<nsIMsgDBHdr> msgHdr;
4066     rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
4067     if (NS_SUCCEEDED(rv)) {
4068       nsCString author;
4069       nsCString subject;
4070 
4071       msgHdr->GetMessageKey(&key);
4072       msgHdr->GetAuthor(getter_Copies(author));
4073       msgHdr->GetSubject(getter_Copies(subject));
4074       printf("hdr key = %u, author = %s subject = %s\n", key, author.get(),
4075              subject.get());
4076     }
4077   }
4078 
4079   nsCOMPtr<nsIMsgThreadEnumerator> threads;
4080   rv = EnumerateThreads(getter_AddRefs(threads));
4081   NS_ENSURE_SUCCESS(rv, rv);
4082   bool hasMore = false;
4083   while (NS_SUCCEEDED(rv = threads->HasMoreElements(&hasMore)) && hasMore) {
4084     nsCOMPtr<nsIMsgThread> thread;
4085     rv = threads->GetNext(getter_AddRefs(thread));
4086     NS_ENSURE_SUCCESS(rv, rv);
4087 
4088     nsMsgKey key;
4089     thread->GetThreadKey(&key);
4090     printf("thread key = %u\n", key);
4091     // DumpThread(key);
4092   }
4093   return NS_OK;
4094 }
4095 #endif /* DEBUG */
4096 
SetMsgRetentionSettings(nsIMsgRetentionSettings * retentionSettings)4097 NS_IMETHODIMP nsMsgDatabase::SetMsgRetentionSettings(
4098     nsIMsgRetentionSettings* retentionSettings) {
4099   m_retentionSettings = retentionSettings;
4100   if (retentionSettings && m_dbFolderInfo) {
4101     nsresult rv;
4102 
4103     nsMsgRetainByPreference retainByPreference;
4104     uint32_t daysToKeepHdrs;
4105     uint32_t numHeadersToKeep;
4106     uint32_t daysToKeepBodies;
4107     bool cleanupBodiesByDays;
4108     bool useServerDefaults;
4109     bool applyToFlaggedMessages;
4110 
4111     rv = retentionSettings->GetRetainByPreference(&retainByPreference);
4112     NS_ENSURE_SUCCESS(rv, rv);
4113     rv = retentionSettings->GetDaysToKeepHdrs(&daysToKeepHdrs);
4114     NS_ENSURE_SUCCESS(rv, rv);
4115     rv = retentionSettings->GetNumHeadersToKeep(&numHeadersToKeep);
4116     NS_ENSURE_SUCCESS(rv, rv);
4117     rv = retentionSettings->GetDaysToKeepBodies(&daysToKeepBodies);
4118     NS_ENSURE_SUCCESS(rv, rv);
4119     (void)retentionSettings->GetCleanupBodiesByDays(&cleanupBodiesByDays);
4120     (void)retentionSettings->GetUseServerDefaults(&useServerDefaults);
4121     rv = retentionSettings->GetApplyToFlaggedMessages(&applyToFlaggedMessages);
4122     NS_ENSURE_SUCCESS(rv, rv);
4123     // need to write this to the db. We'll just use the dbfolderinfo to write
4124     // properties.
4125     m_dbFolderInfo->SetUint32Property("retainBy", retainByPreference);
4126     m_dbFolderInfo->SetUint32Property("daysToKeepHdrs", daysToKeepHdrs);
4127     m_dbFolderInfo->SetUint32Property("numHdrsToKeep", numHeadersToKeep);
4128     m_dbFolderInfo->SetUint32Property("daysToKeepBodies", daysToKeepBodies);
4129     m_dbFolderInfo->SetBooleanProperty("cleanupBodies", cleanupBodiesByDays);
4130     m_dbFolderInfo->SetBooleanProperty("useServerDefaults", useServerDefaults);
4131     m_dbFolderInfo->SetBooleanProperty("applyToFlaggedMessages",
4132                                        applyToFlaggedMessages);
4133   }
4134   Commit(nsMsgDBCommitType::kLargeCommit);
4135   return NS_OK;
4136 }
4137 
GetMsgRetentionSettings(nsIMsgRetentionSettings ** retentionSettings)4138 NS_IMETHODIMP nsMsgDatabase::GetMsgRetentionSettings(
4139     nsIMsgRetentionSettings** retentionSettings) {
4140   NS_ENSURE_ARG_POINTER(retentionSettings);
4141   if (!m_retentionSettings) {
4142     // create a new one, and initialize it from the db.
4143     m_retentionSettings = new nsMsgRetentionSettings;
4144     if (m_retentionSettings && m_dbFolderInfo) {
4145       nsMsgRetainByPreference retainByPreference;
4146       uint32_t daysToKeepHdrs = 0;
4147       uint32_t numHeadersToKeep = 0;
4148       bool useServerDefaults;
4149       uint32_t daysToKeepBodies = 0;
4150       bool cleanupBodiesByDays = false;
4151       bool applyToFlaggedMessages;
4152 
4153       m_dbFolderInfo->GetUint32Property("retainBy",
4154                                         nsIMsgRetentionSettings::nsMsgRetainAll,
4155                                         &retainByPreference);
4156       m_dbFolderInfo->GetUint32Property("daysToKeepHdrs", 0, &daysToKeepHdrs);
4157       m_dbFolderInfo->GetUint32Property("numHdrsToKeep", 0, &numHeadersToKeep);
4158       m_dbFolderInfo->GetUint32Property("daysToKeepBodies", 0,
4159                                         &daysToKeepBodies);
4160       m_dbFolderInfo->GetBooleanProperty("useServerDefaults", true,
4161                                          &useServerDefaults);
4162       m_dbFolderInfo->GetBooleanProperty("cleanupBodies", false,
4163                                          &cleanupBodiesByDays);
4164       m_dbFolderInfo->GetBooleanProperty("applyToFlaggedMessages", false,
4165                                          &applyToFlaggedMessages);
4166       m_retentionSettings->SetRetainByPreference(retainByPreference);
4167       m_retentionSettings->SetDaysToKeepHdrs(daysToKeepHdrs);
4168       m_retentionSettings->SetNumHeadersToKeep(numHeadersToKeep);
4169       m_retentionSettings->SetDaysToKeepBodies(daysToKeepBodies);
4170       m_retentionSettings->SetUseServerDefaults(useServerDefaults);
4171       m_retentionSettings->SetCleanupBodiesByDays(cleanupBodiesByDays);
4172       m_retentionSettings->SetApplyToFlaggedMessages(applyToFlaggedMessages);
4173     }
4174   }
4175   NS_IF_ADDREF(*retentionSettings = m_retentionSettings);
4176   return NS_OK;
4177 }
4178 
SetMsgDownloadSettings(nsIMsgDownloadSettings * downloadSettings)4179 NS_IMETHODIMP nsMsgDatabase::SetMsgDownloadSettings(
4180     nsIMsgDownloadSettings* downloadSettings) {
4181   m_downloadSettings = downloadSettings;
4182   if (downloadSettings && m_dbFolderInfo) {
4183     nsresult rv;
4184 
4185     bool useServerDefaults;
4186     bool downloadByDate;
4187     uint32_t ageLimitOfMsgsToDownload;
4188     bool downloadUnreadOnly;
4189 
4190     rv = downloadSettings->GetUseServerDefaults(&useServerDefaults);
4191     NS_ENSURE_SUCCESS(rv, rv);
4192     rv = downloadSettings->GetDownloadByDate(&downloadByDate);
4193     NS_ENSURE_SUCCESS(rv, rv);
4194     rv = downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly);
4195     NS_ENSURE_SUCCESS(rv, rv);
4196     rv = downloadSettings->GetAgeLimitOfMsgsToDownload(
4197         &ageLimitOfMsgsToDownload);
4198     NS_ENSURE_SUCCESS(rv, rv);
4199     // need to write this to the db. We'll just use the dbfolderinfo to write
4200     // properties.
4201     m_dbFolderInfo->SetBooleanProperty("useServerDefaults", useServerDefaults);
4202     m_dbFolderInfo->SetBooleanProperty("downloadByDate", downloadByDate);
4203     m_dbFolderInfo->SetBooleanProperty("downloadUnreadOnly",
4204                                        downloadUnreadOnly);
4205     m_dbFolderInfo->SetUint32Property("ageLimit", ageLimitOfMsgsToDownload);
4206   }
4207   return NS_OK;
4208 }
4209 
GetMsgDownloadSettings(nsIMsgDownloadSettings ** downloadSettings)4210 NS_IMETHODIMP nsMsgDatabase::GetMsgDownloadSettings(
4211     nsIMsgDownloadSettings** downloadSettings) {
4212   NS_ENSURE_ARG_POINTER(downloadSettings);
4213   if (!m_downloadSettings) {
4214     // create a new one, and initialize it from the db.
4215     m_downloadSettings = new nsMsgDownloadSettings;
4216     if (m_downloadSettings && m_dbFolderInfo) {
4217       bool useServerDefaults;
4218       bool downloadByDate;
4219       uint32_t ageLimitOfMsgsToDownload;
4220       bool downloadUnreadOnly;
4221 
4222       m_dbFolderInfo->GetBooleanProperty("useServerDefaults", true,
4223                                          &useServerDefaults);
4224       m_dbFolderInfo->GetBooleanProperty("downloadByDate", false,
4225                                          &downloadByDate);
4226       m_dbFolderInfo->GetBooleanProperty("downloadUnreadOnly", false,
4227                                          &downloadUnreadOnly);
4228       m_dbFolderInfo->GetUint32Property("ageLimit", 0,
4229                                         &ageLimitOfMsgsToDownload);
4230 
4231       m_downloadSettings->SetUseServerDefaults(useServerDefaults);
4232       m_downloadSettings->SetDownloadByDate(downloadByDate);
4233       m_downloadSettings->SetDownloadUnreadOnly(downloadUnreadOnly);
4234       m_downloadSettings->SetAgeLimitOfMsgsToDownload(ageLimitOfMsgsToDownload);
4235     }
4236   }
4237   NS_IF_ADDREF(*downloadSettings = m_downloadSettings);
4238   return NS_OK;
4239 }
4240 
ApplyRetentionSettings(nsIMsgRetentionSettings * aMsgRetentionSettings,bool aDeleteViaFolder)4241 NS_IMETHODIMP nsMsgDatabase::ApplyRetentionSettings(
4242     nsIMsgRetentionSettings* aMsgRetentionSettings, bool aDeleteViaFolder) {
4243   NS_ENSURE_ARG_POINTER(aMsgRetentionSettings);
4244   nsresult rv = NS_OK;
4245 
4246   if (!m_folder) return NS_ERROR_NULL_POINTER;
4247 
4248   bool isDraftsTemplatesOutbox;
4249   uint32_t dtoFlags = nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates |
4250                       nsMsgFolderFlags::Queue;
4251   (void)m_folder->IsSpecialFolder(dtoFlags, true, &isDraftsTemplatesOutbox);
4252   // Never apply retention settings to Drafts/Templates/Outbox.
4253   if (isDraftsTemplatesOutbox) return NS_OK;
4254 
4255   nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrsToDelete;
4256   nsMsgRetainByPreference retainByPreference;
4257   aMsgRetentionSettings->GetRetainByPreference(&retainByPreference);
4258 
4259   bool applyToFlaggedMessages = false;
4260   aMsgRetentionSettings->GetApplyToFlaggedMessages(&applyToFlaggedMessages);
4261 
4262   uint32_t daysToKeepHdrs = 0;
4263   uint32_t numHeadersToKeep = 0;
4264   switch (retainByPreference) {
4265     case nsIMsgRetentionSettings::nsMsgRetainAll:
4266       break;
4267     case nsIMsgRetentionSettings::nsMsgRetainByAge:
4268       aMsgRetentionSettings->GetDaysToKeepHdrs(&daysToKeepHdrs);
4269       rv = FindMessagesOlderThan(daysToKeepHdrs, applyToFlaggedMessages,
4270                                  msgHdrsToDelete);
4271       break;
4272     case nsIMsgRetentionSettings::nsMsgRetainByNumHeaders:
4273       aMsgRetentionSettings->GetNumHeadersToKeep(&numHeadersToKeep);
4274       rv = FindExcessMessages(numHeadersToKeep, applyToFlaggedMessages,
4275                               msgHdrsToDelete);
4276       break;
4277   }
4278   if (m_folder) {
4279     // update the time we attempted to purge this folder
4280     char dateBuf[100];
4281     dateBuf[0] = '\0';
4282     PRExplodedTime exploded;
4283     PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
4284     PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%a %b %d %H:%M:%S %Y",
4285                            &exploded);
4286     m_folder->SetStringProperty("LastPurgeTime", nsDependentCString(dateBuf));
4287   }
4288   NS_ENSURE_SUCCESS(rv, rv);
4289 
4290   if (msgHdrsToDelete.IsEmpty()) {
4291     return NS_OK;  // No action required.
4292   }
4293 
4294   if (aDeleteViaFolder) {
4295     // The folder delete will also delete headers from the DB.
4296     rv = m_folder->DeleteMessages(msgHdrsToDelete, nullptr, true, false,
4297                                   nullptr, false);
4298   } else {
4299     // We're just deleting headers in the DB.
4300     uint32_t kindex = 0;
4301     for (nsIMsgDBHdr* hdr : msgHdrsToDelete) {
4302       // Commit after every 300.
4303       rv = DeleteHeader(hdr, nullptr, kindex % 300, true);
4304       if (NS_FAILED(rv)) {
4305         break;
4306       }
4307     }
4308     // compress commit if we deleted more than 10
4309     if (msgHdrsToDelete.Length() > 10) {
4310       Commit(nsMsgDBCommitType::kCompressCommit);
4311     } else {
4312       Commit(nsMsgDBCommitType::kLargeCommit);
4313     }
4314   }
4315   return rv;
4316 }
4317 
FindMessagesOlderThan(uint32_t daysToKeepHdrs,bool applyToFlaggedMessages,nsTArray<RefPtr<nsIMsgDBHdr>> & hdrsToDelete)4318 nsresult nsMsgDatabase::FindMessagesOlderThan(
4319     uint32_t daysToKeepHdrs, bool applyToFlaggedMessages,
4320     nsTArray<RefPtr<nsIMsgDBHdr>>& hdrsToDelete) {
4321   nsresult rv = NS_OK;
4322   hdrsToDelete.Clear();
4323 
4324   nsCOMPtr<nsIMsgEnumerator> hdrs;
4325   rv = EnumerateMessages(getter_AddRefs(hdrs));
4326   NS_ENSURE_SUCCESS(rv, rv);
4327 
4328   // cutOffDay is the PRTime cut-off point. Any msg with a date less than
4329   // that will get purged.
4330   PRTime cutOffDay = PR_Now() - daysToKeepHdrs * PR_USEC_PER_DAY;
4331 
4332   bool hasMore = false;
4333   while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
4334     nsCOMPtr<nsIMsgDBHdr> msg;
4335     rv = hdrs->GetNext(getter_AddRefs(msg));
4336     NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
4337     NS_ENSURE_SUCCESS(rv, rv);
4338 
4339     if (!applyToFlaggedMessages) {
4340       uint32_t flags;
4341       (void)msg->GetFlags(&flags);
4342       if (flags & nsMsgMessageFlags::Marked) {
4343         continue;
4344       }
4345     }
4346 
4347     PRTime date;
4348     msg->GetDate(&date);
4349     if (date < cutOffDay) {
4350       hdrsToDelete.AppendElement(msg);
4351     }
4352   }
4353 
4354   return NS_OK;
4355 }
4356 
FindExcessMessages(uint32_t numHeadersToKeep,bool applyToFlaggedMessages,nsTArray<RefPtr<nsIMsgDBHdr>> & hdrsToDelete)4357 nsresult nsMsgDatabase::FindExcessMessages(
4358     uint32_t numHeadersToKeep, bool applyToFlaggedMessages,
4359     nsTArray<RefPtr<nsIMsgDBHdr>>& hdrsToDelete) {
4360   nsresult rv = NS_OK;
4361   hdrsToDelete.Clear();
4362 
4363   nsCOMPtr<nsIMsgEnumerator> hdrs;
4364   rv = EnumerateMessages(getter_AddRefs(hdrs));
4365   NS_ENSURE_SUCCESS(rv, rv);
4366 
4367   mdb_count numHdrs = 0;
4368   if (m_mdbAllMsgHeadersTable)
4369     m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrs);
4370   else
4371     return NS_ERROR_NULL_POINTER;
4372 
4373   bool hasMore = false;
4374   while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
4375     nsCOMPtr<nsIMsgDBHdr> msg;
4376     rv = hdrs->GetNext(getter_AddRefs(msg));
4377     NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
4378     NS_ENSURE_SUCCESS(rv, rv);
4379 
4380     if (!applyToFlaggedMessages) {
4381       uint32_t flags;
4382       (void)msg->GetFlags(&flags);
4383       if (flags & nsMsgMessageFlags::Marked) {
4384         continue;
4385       }
4386     }
4387 
4388     // this isn't quite right - we want to prefer unread messages (keep all of
4389     // those we can)
4390     if (numHdrs > numHeadersToKeep) {
4391       numHdrs--;
4392       hdrsToDelete.AppendElement(msg);
4393     }
4394   }
4395 
4396   return NS_OK;
4397 }
4398 
NS_IMPL_ISUPPORTS(nsMsgRetentionSettings,nsIMsgRetentionSettings)4399 NS_IMPL_ISUPPORTS(nsMsgRetentionSettings, nsIMsgRetentionSettings)
4400 
4401 // Initialise the member variables to reasonable defaults.
4402 nsMsgRetentionSettings::nsMsgRetentionSettings()
4403     : m_retainByPreference(1),
4404       m_daysToKeepHdrs(0),
4405       m_numHeadersToKeep(0),
4406       m_useServerDefaults(true),
4407       m_cleanupBodiesByDays(false),
4408       m_daysToKeepBodies(0),
4409       m_applyToFlaggedMessages(false) {}
4410 
~nsMsgRetentionSettings()4411 nsMsgRetentionSettings::~nsMsgRetentionSettings() {}
4412 
4413 /* attribute unsigned long retainByPreference */
4414 
GetRetainByPreference(nsMsgRetainByPreference * retainByPreference)4415 NS_IMETHODIMP nsMsgRetentionSettings::GetRetainByPreference(
4416     nsMsgRetainByPreference* retainByPreference) {
4417   NS_ENSURE_ARG_POINTER(retainByPreference);
4418   *retainByPreference = m_retainByPreference;
4419   return NS_OK;
4420 }
4421 
SetRetainByPreference(nsMsgRetainByPreference retainByPreference)4422 NS_IMETHODIMP nsMsgRetentionSettings::SetRetainByPreference(
4423     nsMsgRetainByPreference retainByPreference) {
4424   m_retainByPreference = retainByPreference;
4425   return NS_OK;
4426 }
4427 
4428 /* attribute long daysToKeepHdrs; */
GetDaysToKeepHdrs(uint32_t * aDaysToKeepHdrs)4429 NS_IMETHODIMP nsMsgRetentionSettings::GetDaysToKeepHdrs(
4430     uint32_t* aDaysToKeepHdrs) {
4431   NS_ENSURE_ARG_POINTER(aDaysToKeepHdrs);
4432   *aDaysToKeepHdrs = m_daysToKeepHdrs;
4433   return NS_OK;
4434 }
4435 
SetDaysToKeepHdrs(uint32_t aDaysToKeepHdrs)4436 NS_IMETHODIMP nsMsgRetentionSettings::SetDaysToKeepHdrs(
4437     uint32_t aDaysToKeepHdrs) {
4438   m_daysToKeepHdrs = aDaysToKeepHdrs;
4439   return NS_OK;
4440 }
4441 
4442 /* attribute long numHeadersToKeep; */
GetNumHeadersToKeep(uint32_t * aNumHeadersToKeep)4443 NS_IMETHODIMP nsMsgRetentionSettings::GetNumHeadersToKeep(
4444     uint32_t* aNumHeadersToKeep) {
4445   NS_ENSURE_ARG_POINTER(aNumHeadersToKeep);
4446   *aNumHeadersToKeep = m_numHeadersToKeep;
4447   return NS_OK;
4448 }
SetNumHeadersToKeep(uint32_t aNumHeadersToKeep)4449 NS_IMETHODIMP nsMsgRetentionSettings::SetNumHeadersToKeep(
4450     uint32_t aNumHeadersToKeep) {
4451   m_numHeadersToKeep = aNumHeadersToKeep;
4452   return NS_OK;
4453 }
4454 /* attribute boolean useServerDefaults; */
GetUseServerDefaults(bool * aUseServerDefaults)4455 NS_IMETHODIMP nsMsgRetentionSettings::GetUseServerDefaults(
4456     bool* aUseServerDefaults) {
4457   NS_ENSURE_ARG_POINTER(aUseServerDefaults);
4458   *aUseServerDefaults = m_useServerDefaults;
4459   return NS_OK;
4460 }
SetUseServerDefaults(bool aUseServerDefaults)4461 NS_IMETHODIMP nsMsgRetentionSettings::SetUseServerDefaults(
4462     bool aUseServerDefaults) {
4463   m_useServerDefaults = aUseServerDefaults;
4464   return NS_OK;
4465 }
4466 
4467 /* attribute boolean cleanupBodiesByDays; */
GetCleanupBodiesByDays(bool * aCleanupBodiesByDays)4468 NS_IMETHODIMP nsMsgRetentionSettings::GetCleanupBodiesByDays(
4469     bool* aCleanupBodiesByDays) {
4470   NS_ENSURE_ARG_POINTER(aCleanupBodiesByDays);
4471   *aCleanupBodiesByDays = m_cleanupBodiesByDays;
4472   return NS_OK;
4473 }
SetCleanupBodiesByDays(bool aCleanupBodiesByDays)4474 NS_IMETHODIMP nsMsgRetentionSettings::SetCleanupBodiesByDays(
4475     bool aCleanupBodiesByDays) {
4476   m_cleanupBodiesByDays = aCleanupBodiesByDays;
4477   return NS_OK;
4478 }
4479 
4480 /* attribute long daysToKeepBodies; */
GetDaysToKeepBodies(uint32_t * aDaysToKeepBodies)4481 NS_IMETHODIMP nsMsgRetentionSettings::GetDaysToKeepBodies(
4482     uint32_t* aDaysToKeepBodies) {
4483   NS_ENSURE_ARG_POINTER(aDaysToKeepBodies);
4484   *aDaysToKeepBodies = m_daysToKeepBodies;
4485   return NS_OK;
4486 }
SetDaysToKeepBodies(uint32_t aDaysToKeepBodies)4487 NS_IMETHODIMP nsMsgRetentionSettings::SetDaysToKeepBodies(
4488     uint32_t aDaysToKeepBodies) {
4489   m_daysToKeepBodies = aDaysToKeepBodies;
4490   return NS_OK;
4491 }
4492 
4493 /* attribute boolean applyToFlaggedMessages; */
GetApplyToFlaggedMessages(bool * aApplyToFlaggedMessages)4494 NS_IMETHODIMP nsMsgRetentionSettings::GetApplyToFlaggedMessages(
4495     bool* aApplyToFlaggedMessages) {
4496   NS_ENSURE_ARG_POINTER(aApplyToFlaggedMessages);
4497   *aApplyToFlaggedMessages = m_applyToFlaggedMessages;
4498   return NS_OK;
4499 }
SetApplyToFlaggedMessages(bool aApplyToFlaggedMessages)4500 NS_IMETHODIMP nsMsgRetentionSettings::SetApplyToFlaggedMessages(
4501     bool aApplyToFlaggedMessages) {
4502   m_applyToFlaggedMessages = aApplyToFlaggedMessages;
4503   return NS_OK;
4504 }
4505 
NS_IMPL_ISUPPORTS(nsMsgDownloadSettings,nsIMsgDownloadSettings)4506 NS_IMPL_ISUPPORTS(nsMsgDownloadSettings, nsIMsgDownloadSettings)
4507 
4508 nsMsgDownloadSettings::nsMsgDownloadSettings() {
4509   m_useServerDefaults = false;
4510   m_downloadUnreadOnly = false;
4511   m_downloadByDate = false;
4512   m_ageLimitOfMsgsToDownload = 0;
4513 }
4514 
~nsMsgDownloadSettings()4515 nsMsgDownloadSettings::~nsMsgDownloadSettings() {}
4516 
4517 /* attribute boolean useServerDefaults; */
GetUseServerDefaults(bool * aUseServerDefaults)4518 NS_IMETHODIMP nsMsgDownloadSettings::GetUseServerDefaults(
4519     bool* aUseServerDefaults) {
4520   NS_ENSURE_ARG_POINTER(aUseServerDefaults);
4521   *aUseServerDefaults = m_useServerDefaults;
4522   return NS_OK;
4523 }
SetUseServerDefaults(bool aUseServerDefaults)4524 NS_IMETHODIMP nsMsgDownloadSettings::SetUseServerDefaults(
4525     bool aUseServerDefaults) {
4526   m_useServerDefaults = aUseServerDefaults;
4527   return NS_OK;
4528 }
4529 
4530 /* attribute boolean downloadUnreadOnly; */
GetDownloadUnreadOnly(bool * aDownloadUnreadOnly)4531 NS_IMETHODIMP nsMsgDownloadSettings::GetDownloadUnreadOnly(
4532     bool* aDownloadUnreadOnly) {
4533   NS_ENSURE_ARG_POINTER(aDownloadUnreadOnly);
4534   *aDownloadUnreadOnly = m_downloadUnreadOnly;
4535   return NS_OK;
4536 }
SetDownloadUnreadOnly(bool aDownloadUnreadOnly)4537 NS_IMETHODIMP nsMsgDownloadSettings::SetDownloadUnreadOnly(
4538     bool aDownloadUnreadOnly) {
4539   m_downloadUnreadOnly = aDownloadUnreadOnly;
4540   return NS_OK;
4541 }
4542 
4543 /* attribute boolean downloadByDate; */
GetDownloadByDate(bool * aDownloadByDate)4544 NS_IMETHODIMP nsMsgDownloadSettings::GetDownloadByDate(bool* aDownloadByDate) {
4545   NS_ENSURE_ARG_POINTER(aDownloadByDate);
4546   *aDownloadByDate = m_downloadByDate;
4547   return NS_OK;
4548 }
4549 
SetDownloadByDate(bool aDownloadByDate)4550 NS_IMETHODIMP nsMsgDownloadSettings::SetDownloadByDate(bool aDownloadByDate) {
4551   m_downloadByDate = aDownloadByDate;
4552   return NS_OK;
4553 }
4554 
4555 /* attribute long ageLimitOfMsgsToDownload; */
GetAgeLimitOfMsgsToDownload(uint32_t * ageLimitOfMsgsToDownload)4556 NS_IMETHODIMP nsMsgDownloadSettings::GetAgeLimitOfMsgsToDownload(
4557     uint32_t* ageLimitOfMsgsToDownload) {
4558   NS_ENSURE_ARG_POINTER(ageLimitOfMsgsToDownload);
4559   *ageLimitOfMsgsToDownload = m_ageLimitOfMsgsToDownload;
4560   return NS_OK;
4561 }
SetAgeLimitOfMsgsToDownload(uint32_t ageLimitOfMsgsToDownload)4562 NS_IMETHODIMP nsMsgDownloadSettings::SetAgeLimitOfMsgsToDownload(
4563     uint32_t ageLimitOfMsgsToDownload) {
4564   m_ageLimitOfMsgsToDownload = ageLimitOfMsgsToDownload;
4565   return NS_OK;
4566 }
4567 
GetDefaultViewFlags(nsMsgViewFlagsTypeValue * aDefaultViewFlags)4568 NS_IMETHODIMP nsMsgDatabase::GetDefaultViewFlags(
4569     nsMsgViewFlagsTypeValue* aDefaultViewFlags) {
4570   NS_ENSURE_ARG_POINTER(aDefaultViewFlags);
4571   GetIntPref("mailnews.default_view_flags", aDefaultViewFlags);
4572   if (*aDefaultViewFlags < nsMsgViewFlagsType::kNone ||
4573       *aDefaultViewFlags >
4574           (nsMsgViewFlagsType::kThreadedDisplay |
4575            nsMsgViewFlagsType::kShowIgnored | nsMsgViewFlagsType::kUnreadOnly |
4576            nsMsgViewFlagsType::kExpandAll | nsMsgViewFlagsType::kGroupBySort))
4577     *aDefaultViewFlags = nsMsgViewFlagsType::kNone;
4578   return NS_OK;
4579 }
4580 
GetDefaultSortType(nsMsgViewSortTypeValue * aDefaultSortType)4581 NS_IMETHODIMP nsMsgDatabase::GetDefaultSortType(
4582     nsMsgViewSortTypeValue* aDefaultSortType) {
4583   NS_ENSURE_ARG_POINTER(aDefaultSortType);
4584   GetIntPref("mailnews.default_sort_type", aDefaultSortType);
4585   if (*aDefaultSortType < nsMsgViewSortType::byDate ||
4586       *aDefaultSortType > nsMsgViewSortType::byAccount)
4587     *aDefaultSortType = nsMsgViewSortType::byDate;
4588   return NS_OK;
4589 }
4590 
GetDefaultSortOrder(nsMsgViewSortOrderValue * aDefaultSortOrder)4591 NS_IMETHODIMP nsMsgDatabase::GetDefaultSortOrder(
4592     nsMsgViewSortOrderValue* aDefaultSortOrder) {
4593   NS_ENSURE_ARG_POINTER(aDefaultSortOrder);
4594   GetIntPref("mailnews.default_sort_order", aDefaultSortOrder);
4595   if (*aDefaultSortOrder != nsMsgViewSortOrder::descending)
4596     *aDefaultSortOrder = nsMsgViewSortOrder::ascending;
4597   return NS_OK;
4598 }
4599 
ResetHdrCacheSize(uint32_t aSize)4600 NS_IMETHODIMP nsMsgDatabase::ResetHdrCacheSize(uint32_t aSize) {
4601   if (m_cacheSize > aSize) {
4602     m_cacheSize = aSize;
4603     ClearHdrCache(false);
4604   }
4605   return NS_OK;
4606 }
4607 
4608 NS_IMETHODIMP
GetNewList(nsTArray<nsMsgKey> & aNewKeys)4609 nsMsgDatabase::GetNewList(nsTArray<nsMsgKey>& aNewKeys) {
4610   aNewKeys = m_newSet.Clone();
4611   return NS_OK;
4612 }
4613 
GetSearchResultsTable(const nsACString & searchFolderUri,bool createIfMissing,nsIMdbTable ** table)4614 nsresult nsMsgDatabase::GetSearchResultsTable(const nsACString& searchFolderUri,
4615                                               bool createIfMissing,
4616                                               nsIMdbTable** table) {
4617   mdb_kind kindToken;
4618   mdb_count numTables;
4619   mdb_bool mustBeUnique;
4620   NS_ENSURE_TRUE(m_mdbStore, NS_ERROR_NULL_POINTER);
4621 
4622   nsresult err = m_mdbStore->StringToToken(
4623       GetEnv(), PromiseFlatCString(searchFolderUri).get(), &kindToken);
4624   err = m_mdbStore->GetTableKind(GetEnv(), m_hdrRowScopeToken, kindToken,
4625                                  &numTables, &mustBeUnique, table);
4626   if ((!*table || NS_FAILED(err)) && createIfMissing)
4627     err = m_mdbStore->NewTable(GetEnv(), m_hdrRowScopeToken, kindToken, true,
4628                                nullptr, table);
4629 
4630   return *table ? err : NS_ERROR_FAILURE;
4631 }
4632 
4633 NS_IMETHODIMP
GetCachedHits(const nsACString & aSearchFolderUri,nsIMsgEnumerator ** aEnumerator)4634 nsMsgDatabase::GetCachedHits(const nsACString& aSearchFolderUri,
4635                              nsIMsgEnumerator** aEnumerator) {
4636   nsCOMPtr<nsIMdbTable> table;
4637   (void)GetSearchResultsTable(aSearchFolderUri, false, getter_AddRefs(table));
4638   if (!table) return NS_ERROR_FAILURE;  // expected result for no cached hits
4639   NS_ADDREF(*aEnumerator =
4640                 new nsMsgDBEnumerator(this, table, nullptr, nullptr));
4641   return NS_OK;
4642 }
4643 
RefreshCache(const nsACString & aSearchFolderUri,nsTArray<nsMsgKey> const & aNewHits,nsTArray<nsMsgKey> & aStaleHits)4644 NS_IMETHODIMP nsMsgDatabase::RefreshCache(const nsACString& aSearchFolderUri,
4645                                           nsTArray<nsMsgKey> const& aNewHits,
4646                                           nsTArray<nsMsgKey>& aStaleHits) {
4647   nsCOMPtr<nsIMdbTable> table;
4648   nsresult err =
4649       GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table));
4650   NS_ENSURE_SUCCESS(err, err);
4651   // update the table so that it just contains aNewHits.
4652   // And, keep track of the headers in the original table but not in aNewHits,
4653   // so we can put those in aStaleHits. both aNewHits and the db table are
4654   // sorted by uid/key. So, start at the beginning of the table and the aNewHits
4655   // array.
4656   uint32_t newHitIndex = 0;
4657   uint32_t tableRowIndex = 0;
4658 
4659   uint32_t rowCount;
4660   table->GetCount(GetEnv(), &rowCount);
4661   aStaleHits.Clear();
4662   // should assert that each array is sorted
4663   while (newHitIndex < aNewHits.Length() || tableRowIndex < rowCount) {
4664     mdbOid oid;
4665     nsMsgKey tableRowKey = nsMsgKey_None;
4666     if (tableRowIndex < rowCount) {
4667       nsresult ret = table->PosToOid(GetEnv(), tableRowIndex, &oid);
4668       if (NS_FAILED(ret)) {
4669         tableRowIndex++;
4670         continue;
4671       }
4672       tableRowKey =
4673           oid.mOid_Id;  // ### TODO need the real key for the 0th key problem.
4674     }
4675 
4676     if (newHitIndex < aNewHits.Length() &&
4677         aNewHits[newHitIndex] == tableRowKey) {
4678       newHitIndex++;
4679       tableRowIndex++;
4680       continue;
4681     } else if (tableRowIndex >= rowCount ||
4682                (newHitIndex < aNewHits.Length() &&
4683                 aNewHits[newHitIndex] < tableRowKey)) {
4684       nsCOMPtr<nsIMdbRow> hdrRow;
4685       mdbOid rowObjectId;
4686 
4687       rowObjectId.mOid_Id = aNewHits[newHitIndex];
4688       rowObjectId.mOid_Scope = m_hdrRowScopeToken;
4689       err = m_mdbStore->GetRow(GetEnv(), &rowObjectId, getter_AddRefs(hdrRow));
4690       if (hdrRow) {
4691         table->AddRow(GetEnv(), hdrRow);
4692         mdb_pos newPos;
4693         table->MoveRow(GetEnv(), hdrRow, rowCount, tableRowIndex, &newPos);
4694         rowCount++;
4695         tableRowIndex++;
4696       }
4697       newHitIndex++;
4698       continue;
4699     } else if (newHitIndex >= aNewHits.Length() ||
4700                aNewHits[newHitIndex] > tableRowKey) {
4701       aStaleHits.AppendElement(tableRowKey);
4702       table->CutOid(GetEnv(), &oid);
4703       rowCount--;
4704       continue;  // don't increment tableRowIndex since we removed that row.
4705     }
4706   }
4707 
4708 #ifdef DEBUG_David_Bienvenu
4709   printf("after refreshing cache\n");
4710   // iterate over table and assert that it's in id order
4711   table->GetCount(GetEnv(), &rowCount);
4712   mdbOid oid;
4713   tableRowIndex = 0;
4714   mdb_id prevId = 0;
4715   while (tableRowIndex < rowCount) {
4716     nsresult ret = table->PosToOid(m_mdbEnv, tableRowIndex++, &oid);
4717     if (tableRowIndex > 1 && oid.mOid_Id <= prevId) {
4718       NS_ASSERTION(
4719           false, "inserting row into cached hits table, not sorted correctly");
4720       printf("key %lx is before or equal %lx\n", prevId, oid.mOid_Id);
4721     }
4722     prevId = oid.mOid_Id;
4723   }
4724 
4725 #endif
4726   Commit(nsMsgDBCommitType::kLargeCommit);
4727   return NS_OK;
4728 }
4729 
4730 // search sorted table
FindInsertIndexInSortedTable(nsIMdbTable * table,mdb_id idToInsert)4731 mdb_pos nsMsgDatabase::FindInsertIndexInSortedTable(nsIMdbTable* table,
4732                                                     mdb_id idToInsert) {
4733   mdb_pos searchPos = 0;
4734   uint32_t rowCount;
4735   table->GetCount(GetEnv(), &rowCount);
4736   mdb_pos hi = rowCount;
4737   mdb_pos lo = 0;
4738 
4739   while (hi > lo) {
4740     mdbOid outOid;
4741     searchPos = (lo + hi - 1) / 2;
4742     table->PosToOid(GetEnv(), searchPos, &outOid);
4743     if (outOid.mOid_Id == idToInsert) {
4744       NS_ASSERTION(false, "id shouldn't be in table");
4745       return hi;
4746     }
4747     if (outOid.mOid_Id > idToInsert)
4748       hi = searchPos;
4749     else  // if (outOid.mOid_Id <  idToInsert)
4750       lo = searchPos + 1;
4751   }
4752   return hi;
4753 }
4754 NS_IMETHODIMP
UpdateHdrInCache(const nsACString & aSearchFolderUri,nsIMsgDBHdr * aHdr,bool aAdd)4755 nsMsgDatabase::UpdateHdrInCache(const nsACString& aSearchFolderUri,
4756                                 nsIMsgDBHdr* aHdr, bool aAdd) {
4757   nsCOMPtr<nsIMdbTable> table;
4758   nsresult err =
4759       GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table));
4760   NS_ENSURE_SUCCESS(err, err);
4761   nsMsgKey key;
4762   err = aHdr->GetMessageKey(&key);
4763   nsMsgHdr* msgHdr =
4764       static_cast<nsMsgHdr*>(aHdr);  // closed system, so this is ok
4765   nsIMdbRow* hdrRow = msgHdr->GetMDBRow();
4766   if (NS_SUCCEEDED(err) && m_mdbStore && hdrRow) {
4767     if (!aAdd) {
4768       table->CutRow(m_mdbEnv, hdrRow);
4769     } else {
4770       mdbOid rowId;
4771       hdrRow->GetOid(m_mdbEnv, &rowId);
4772       mdb_pos insertPos = FindInsertIndexInSortedTable(table, rowId.mOid_Id);
4773       uint32_t rowCount;
4774       table->GetCount(m_mdbEnv, &rowCount);
4775       table->AddRow(m_mdbEnv, hdrRow);
4776       mdb_pos newPos;
4777       table->MoveRow(m_mdbEnv, hdrRow, rowCount, insertPos, &newPos);
4778     }
4779   }
4780 
4781   //  if (aAdd)
4782   // if we need to add this hdr, we need to insert it in key order.
4783   return NS_OK;
4784 }
4785 NS_IMETHODIMP
HdrIsInCache(const nsACString & aSearchFolderUri,nsIMsgDBHdr * aHdr,bool * aResult)4786 nsMsgDatabase::HdrIsInCache(const nsACString& aSearchFolderUri,
4787                             nsIMsgDBHdr* aHdr, bool* aResult) {
4788   NS_ENSURE_ARG_POINTER(aResult);
4789   nsCOMPtr<nsIMdbTable> table;
4790   nsresult err =
4791       GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table));
4792   NS_ENSURE_SUCCESS(err, err);
4793   nsMsgKey key;
4794   aHdr->GetMessageKey(&key);
4795   mdbOid rowObjectId;
4796   rowObjectId.mOid_Id = key;
4797   rowObjectId.mOid_Scope = m_hdrRowScopeToken;
4798   mdb_bool hasOid;
4799   err = table->HasOid(GetEnv(), &rowObjectId, &hasOid);
4800   *aResult = hasOid;
4801   return err;
4802 }
4803