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, ¬ify);
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, ¬ify);
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