1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsMsgDatabaseEnumerators.h"
7 #include "nsMsgDatabase.h"
8 #include "nsIMsgHdr.h"
9 #include "nsMsgBaseCID.h"
10 #include "nsMsgThread.h"
11 
12 /*
13  * nsMsgDBEnumerator implementation
14  */
15 
nsMsgDBEnumerator(nsMsgDatabase * db,nsIMdbTable * table,nsMsgDBEnumeratorFilter filter,void * closure,bool iterateForwards)16 nsMsgDBEnumerator::nsMsgDBEnumerator(nsMsgDatabase* db, nsIMdbTable* table,
17                                      nsMsgDBEnumeratorFilter filter,
18                                      void* closure, bool iterateForwards)
19     : mDB(db),
20       mDone(false),
21       mIterateForwards(iterateForwards),
22       mFilter(filter),
23       mClosure(closure) {
24   mTable = table;
25   mRowPos = 0;
26   mDB->m_msgEnumerators.AppendElement(this);
27 }
28 
~nsMsgDBEnumerator()29 nsMsgDBEnumerator::~nsMsgDBEnumerator() { Invalidate(); }
30 
Invalidate()31 void nsMsgDBEnumerator::Invalidate() {
32   // Order is important here. If the database is destroyed first, releasing
33   // the cursor will crash (due, I think, to a disconnect between XPCOM and
34   // Mork internal memory management).
35   mRowCursor = nullptr;
36   mTable = nullptr;
37   mResultHdr = nullptr;
38   mDone = true;
39   if (mDB) {
40     mDB->m_msgEnumerators.RemoveElement(this);
41     mDB = nullptr;
42   }
43 }
44 
GetRowCursor()45 nsresult nsMsgDBEnumerator::GetRowCursor() {
46   mDone = false;
47 
48   if (!mDB || !mTable) return NS_ERROR_NULL_POINTER;
49 
50   if (mIterateForwards) {
51     mRowPos = -1;
52   } else {
53     mdb_count numRows;
54     mTable->GetCount(mDB->GetEnv(), &numRows);
55     mRowPos = numRows;  // startPos is 0 relative.
56   }
57   return mTable->GetTableRowCursor(mDB->GetEnv(), mRowPos,
58                                    getter_AddRefs(mRowCursor));
59 }
60 
GetNext(nsIMsgDBHdr ** aItem)61 NS_IMETHODIMP nsMsgDBEnumerator::GetNext(nsIMsgDBHdr** aItem) {
62   if (!aItem) return NS_ERROR_NULL_POINTER;
63   *aItem = nullptr;
64 
65   // If we've already got one ready, return it.
66   if (mResultHdr) {
67     mResultHdr.forget(aItem);
68     return NS_OK;
69   }
70 
71   // Bail out if enumerator has been invalidated.
72   if (!mDB) {
73     return NS_ERROR_FAILURE;
74   }
75 
76   nsresult rv = InternalGetNext(aItem);
77   NS_ENSURE_SUCCESS(rv, rv);
78   return *aItem ? NS_OK : NS_ERROR_FAILURE;
79 }
80 
InternalGetNext(nsIMsgDBHdr ** nextHdr)81 nsresult nsMsgDBEnumerator::InternalGetNext(nsIMsgDBHdr** nextHdr) {
82   nsresult rv;
83 
84   *nextHdr = nullptr;
85 
86   if (!mRowCursor) {
87     rv = GetRowCursor();
88     NS_ENSURE_SUCCESS(rv, rv);
89   }
90 
91   while (true) {
92     nsIMdbRow* hdrRow;
93     if (mIterateForwards) {
94       rv = mRowCursor->NextRow(mDB->GetEnv(), &hdrRow, &mRowPos);
95     } else {
96       rv = mRowCursor->PrevRow(mDB->GetEnv(), &hdrRow, &mRowPos);
97     }
98     NS_ENSURE_SUCCESS(rv, rv);
99     if (!hdrRow) {
100       // No more rows, so we're done.
101       *nextHdr = nullptr;
102       return NS_OK;
103     }
104 
105     // Get key from row
106     mdbOid outOid;
107     nsMsgKey key = nsMsgKey_None;
108     rv = hdrRow->GetOid(mDB->GetEnv(), &outOid);
109     NS_ENSURE_SUCCESS(rv, rv);
110     key = outOid.mOid_Id;
111 
112     nsCOMPtr<nsIMsgDBHdr> hdr;
113     rv = mDB->CreateMsgHdr(hdrRow, key, getter_AddRefs(hdr));
114     NS_ENSURE_SUCCESS(rv, rv);
115 
116     // Ignore expunged messages.
117     uint32_t flags;
118     hdr->GetFlags(&flags);
119     if (flags & nsMsgMessageFlags::Expunged) {
120       continue;
121     }
122 
123     // Ignore anything which doesn't pass the filter func (if there is one).
124     if (mFilter && NS_FAILED(mFilter(hdr, mClosure))) {
125       continue;
126     }
127 
128     // If we get this far, we've found it.
129     hdr.forget(nextHdr);
130     return NS_OK;
131   }
132 }
133 
HasMoreElements(bool * aResult)134 NS_IMETHODIMP nsMsgDBEnumerator::HasMoreElements(bool* aResult) {
135   if (!aResult) return NS_ERROR_NULL_POINTER;
136 
137   if (!mResultHdr) {
138     // Bail out if enumerator has been invalidated.
139     if (!mDB) {
140       return NS_ERROR_FAILURE;
141     }
142 
143     nsresult rv = InternalGetNext(getter_AddRefs(mResultHdr));
144     NS_ENSURE_SUCCESS(rv, rv);
145     if (!mResultHdr) {
146       mDone = true;
147     }
148   }
149 
150   *aResult = !mDone;
151   return NS_OK;
152 }
153 
154 /*
155  * nsMsgFilteredDBEnumerator implementation
156  */
157 
nsMsgFilteredDBEnumerator(nsMsgDatabase * db,nsIMdbTable * table,bool reverse)158 nsMsgFilteredDBEnumerator::nsMsgFilteredDBEnumerator(nsMsgDatabase* db,
159                                                      nsIMdbTable* table,
160                                                      bool reverse)
161     : nsMsgDBEnumerator(db, table, nullptr, nullptr, !reverse) {}
162 
~nsMsgFilteredDBEnumerator()163 nsMsgFilteredDBEnumerator::~nsMsgFilteredDBEnumerator() {}
164 
165 /**
166  * Create the search session for the enumerator,
167  * add the scope term for "folder" to the search session, and add the search
168  * terms in the array to the search session.
169  */
InitSearchSession(const nsTArray<RefPtr<nsIMsgSearchTerm>> & searchTerms,nsIMsgFolder * folder)170 nsresult nsMsgFilteredDBEnumerator::InitSearchSession(
171     const nsTArray<RefPtr<nsIMsgSearchTerm>>& searchTerms,
172     nsIMsgFolder* folder) {
173   nsresult rv;
174   m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
175   NS_ENSURE_SUCCESS(rv, rv);
176 
177   m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, folder);
178   for (auto searchTerm : searchTerms) {
179     m_searchSession->AppendTerm(searchTerm);
180   }
181   return NS_OK;
182 }
183 
InternalGetNext(nsIMsgDBHdr ** nextHdr)184 nsresult nsMsgFilteredDBEnumerator::InternalGetNext(nsIMsgDBHdr** nextHdr) {
185   nsCOMPtr<nsIMsgDBHdr> hdr;
186   while (true) {
187     nsresult rv = nsMsgDBEnumerator::InternalGetNext(getter_AddRefs(hdr));
188     NS_ENSURE_SUCCESS(rv, rv);
189     if (!hdr) {
190       break;  // No more.
191     }
192     bool matches;
193     rv = m_searchSession->MatchHdr(hdr, mDB, &matches);
194     NS_ENSURE_SUCCESS(rv, rv);
195     if (matches) {
196       break;  // Found one!
197     }
198   }
199   hdr.forget(nextHdr);
200   return NS_OK;
201 }
202 
203 /*
204  * nsMsgDBThreadEnumerator implementation
205  */
206 
nsMsgDBThreadEnumerator(nsMsgDatabase * db,nsMsgDBThreadEnumeratorFilter filter)207 nsMsgDBThreadEnumerator::nsMsgDBThreadEnumerator(
208     nsMsgDatabase* db, nsMsgDBThreadEnumeratorFilter filter)
209     : mDB(db),
210       mTableCursor(nullptr),
211       mResultThread(nullptr),
212       mDone(false),
213       mFilter(filter) {
214   mDB->m_threadEnumerators.AppendElement(this);
215   mNextPrefetched = false;
216 }
217 
~nsMsgDBThreadEnumerator()218 nsMsgDBThreadEnumerator::~nsMsgDBThreadEnumerator() { Invalidate(); }
219 
Invalidate()220 void nsMsgDBThreadEnumerator::Invalidate() {
221   // Order is important here. If the database is destroyed first, releasing
222   // the cursor will crash (due, I think, to a disconnect between XPCOM and
223   // Mork internal memory management).
224   mTableCursor = nullptr;
225   mResultThread = nullptr;
226   mDone = true;
227   if (mDB) {
228     mDB->m_threadEnumerators.RemoveElement(this);
229     mDB = nullptr;
230   }
231 }
232 
GetTableCursor(void)233 nsresult nsMsgDBThreadEnumerator::GetTableCursor(void) {
234   nsresult rv = NS_OK;
235 
236   // DB might have disappeared.
237   if (!mDB || !mDB->m_mdbStore) return NS_ERROR_NULL_POINTER;
238   if (NS_FAILED(rv)) return rv;
239   return NS_OK;
240 }
241 
HasMoreElements(bool * aResult)242 NS_IMETHODIMP nsMsgDBThreadEnumerator::HasMoreElements(bool* aResult) {
243   NS_ENSURE_ARG_POINTER(aResult);
244 
245   if (!mNextPrefetched) {
246     PrefetchNext();
247   }
248   *aResult = !mDone;
249   return NS_OK;
250 }
251 
GetNext(nsIMsgThread ** aItem)252 NS_IMETHODIMP nsMsgDBThreadEnumerator::GetNext(nsIMsgThread** aItem) {
253   NS_ENSURE_ARG_POINTER(aItem);
254 
255   *aItem = nullptr;
256   nsresult rv = NS_OK;
257   if (!mNextPrefetched) rv = PrefetchNext();
258   if (NS_SUCCEEDED(rv)) {
259     if (mResultThread) {
260       NS_ADDREF(*aItem = mResultThread);
261       mNextPrefetched = false;
262     }
263   }
264   return rv;
265 }
266 
PrefetchNext()267 nsresult nsMsgDBThreadEnumerator::PrefetchNext() {
268   nsresult rv;
269 
270   // DB might have disappeared.
271   if (!mDB || !mDB->m_mdbStore) {
272     return NS_ERROR_NULL_POINTER;
273   }
274 
275   if (!mTableCursor) {
276     rv = mDB->m_mdbStore->GetPortTableCursor(
277         mDB->GetEnv(), mDB->m_hdrRowScopeToken, mDB->m_threadTableKindToken,
278         getter_AddRefs(mTableCursor));
279     NS_ENSURE_SUCCESS(rv, rv);
280   }
281 
282   nsCOMPtr<nsIMdbTable> table;
283   while (true) {
284     mResultThread = nullptr;
285     rv = mTableCursor->NextTable(mDB->GetEnv(), getter_AddRefs(table));
286     if (!table) {
287       mDone = true;
288       return NS_ERROR_FAILURE;
289     }
290     if (NS_FAILED(rv)) {
291       mDone = true;
292       return rv;
293     }
294 
295     mdbOid tableId;
296     table->GetOid(mDB->GetEnv(), &tableId);
297 
298     mResultThread = mDB->FindExistingThread(tableId.mOid_Id);
299     if (!mResultThread) mResultThread = new nsMsgThread(mDB, table);
300 
301     if (mResultThread) {
302       uint32_t numChildren = 0;
303       mResultThread->GetNumChildren(&numChildren);
304       // we've got empty thread; don't tell caller about it.
305       if (numChildren == 0) continue;
306     }
307     if (mFilter && NS_FAILED(mFilter(mResultThread)))
308       continue;
309     else
310       break;
311   }
312   if (mResultThread) {
313     mNextPrefetched = true;
314     return NS_OK;
315   }
316   return NS_ERROR_FAILURE;
317 }
318