1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "msgCore.h"
7 #include "nsMsgUtils.h"
8 #include "nsMsgGroupView.h"
9 #include "nsIMsgHdr.h"
10 #include "nsIMsgThread.h"
11 #include "nsIDBFolderInfo.h"
12 #include "nsIMsgSearchSession.h"
13 #include "nsMsgGroupThread.h"
14 #include "nsTreeColumns.h"
15 #include "nsMsgMessageFlags.h"
16 #include <plhash.h>
17 #include "mozilla/Attributes.h"
18 
19 // Allocate this more to avoid reallocation on new mail.
20 #define MSGHDR_CACHE_LOOK_AHEAD_SIZE 25
21 // Max msghdr cache entries.
22 #define MSGHDR_CACHE_MAX_SIZE 8192
23 #define MSGHDR_CACHE_DEFAULT_SIZE 100
24 
nsMsgGroupView()25 nsMsgGroupView::nsMsgGroupView() {
26   m_dayChanged = false;
27   m_lastCurExplodedTime.tm_mday = 0;
28 }
29 
~nsMsgGroupView()30 nsMsgGroupView::~nsMsgGroupView() {}
31 
32 NS_IMETHODIMP
Open(nsIMsgFolder * aFolder,nsMsgViewSortTypeValue aSortType,nsMsgViewSortOrderValue aSortOrder,nsMsgViewFlagsTypeValue aViewFlags,int32_t * aCount)33 nsMsgGroupView::Open(nsIMsgFolder* aFolder, nsMsgViewSortTypeValue aSortType,
34                      nsMsgViewSortOrderValue aSortOrder,
35                      nsMsgViewFlagsTypeValue aViewFlags, int32_t* aCount) {
36   nsresult rv =
37       nsMsgDBView::Open(aFolder, aSortType, aSortOrder, aViewFlags, aCount);
38   NS_ENSURE_SUCCESS(rv, rv);
39 
40   nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
41   PersistFolderInfo(getter_AddRefs(dbFolderInfo));
42 
43   nsCOMPtr<nsIMsgEnumerator> headers;
44   rv = m_db->EnumerateMessages(getter_AddRefs(headers));
45   NS_ENSURE_SUCCESS(rv, rv);
46 
47   return OpenWithHdrs(headers, aSortType, aSortOrder, aViewFlags, aCount);
48 }
49 
InternalClose()50 void nsMsgGroupView::InternalClose() {
51   m_groupsTable.Clear();
52   // Nothing to do if we're not grouped.
53   if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) return;
54 
55   bool rcvDate = false;
56 
57   if (m_sortType == nsMsgViewSortType::byReceived) rcvDate = true;
58 
59   if (m_db && ((m_sortType == nsMsgViewSortType::byDate) ||
60                (m_sortType == nsMsgViewSortType::byReceived))) {
61     nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
62     m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
63     if (dbFolderInfo) {
64       uint32_t expandFlags = 0;
65       uint32_t num = GetSize();
66 
67       for (uint32_t i = 0; i < num; i++) {
68         if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD &&
69             !(m_flags[i] & nsMsgMessageFlags::Elided)) {
70           nsCOMPtr<nsIMsgDBHdr> msgHdr;
71           GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
72           if (msgHdr) {
73             uint32_t ageBucket;
74             nsresult rv = GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
75             if (NS_SUCCEEDED(rv)) expandFlags |= 1 << ageBucket;
76           }
77         }
78       }
79       dbFolderInfo->SetUint32Property("dateGroupFlags", expandFlags);
80     }
81   }
82 }
83 
84 NS_IMETHODIMP
Close()85 nsMsgGroupView::Close() {
86   InternalClose();
87   return nsMsgDBView::Close();
88 }
89 
90 // Set rcvDate to true to get the Received: date instead of the Date: date.
GetAgeBucketValue(nsIMsgDBHdr * aMsgHdr,uint32_t * aAgeBucket,bool rcvDate)91 nsresult nsMsgGroupView::GetAgeBucketValue(nsIMsgDBHdr* aMsgHdr,
92                                            uint32_t* aAgeBucket, bool rcvDate) {
93   NS_ENSURE_ARG_POINTER(aMsgHdr);
94   NS_ENSURE_ARG_POINTER(aAgeBucket);
95 
96   PRTime dateOfMsg;
97   nsresult rv;
98   if (!rcvDate)
99     rv = aMsgHdr->GetDate(&dateOfMsg);
100   else {
101     uint32_t rcvDateSecs;
102     rv = aMsgHdr->GetUint32Property("dateReceived", &rcvDateSecs);
103     Seconds2PRTime(rcvDateSecs, &dateOfMsg);
104   }
105   NS_ENSURE_SUCCESS(rv, rv);
106 
107   PRTime currentTime = PR_Now();
108   PRExplodedTime currentExplodedTime;
109   PR_ExplodeTime(currentTime, PR_LocalTimeParameters, &currentExplodedTime);
110   PRExplodedTime explodedMsgTime;
111   PR_ExplodeTime(dateOfMsg, PR_LocalTimeParameters, &explodedMsgTime);
112 
113   if (m_lastCurExplodedTime.tm_mday &&
114       m_lastCurExplodedTime.tm_mday != currentExplodedTime.tm_mday)
115     // This will cause us to rebuild the view.
116     m_dayChanged = true;
117 
118   m_lastCurExplodedTime = currentExplodedTime;
119   if (currentExplodedTime.tm_year == explodedMsgTime.tm_year &&
120       currentExplodedTime.tm_month == explodedMsgTime.tm_month &&
121       currentExplodedTime.tm_mday == explodedMsgTime.tm_mday) {
122     // Same day.
123     *aAgeBucket = 1;
124   }
125   // Figure out how many days ago this msg arrived.
126   else if (currentTime > dateOfMsg) {
127     // Setting the time variables to local time.
128     int64_t GMTLocalTimeShift = currentExplodedTime.tm_params.tp_gmt_offset +
129                                 currentExplodedTime.tm_params.tp_dst_offset;
130     GMTLocalTimeShift *= PR_USEC_PER_SEC;
131     currentTime += GMTLocalTimeShift;
132     dateOfMsg += GMTLocalTimeShift;
133 
134     // The most recent midnight, counting from current time.
135     int64_t mostRecentMidnight = currentTime - currentTime % PR_USEC_PER_DAY;
136     int64_t yesterday = mostRecentMidnight - PR_USEC_PER_DAY;
137     // Most recent midnight minus 6 days.
138     int64_t mostRecentWeek = mostRecentMidnight - (PR_USEC_PER_DAY * 6);
139 
140     // Was the message sent yesterday?
141     if (dateOfMsg >= yesterday)
142       *aAgeBucket = 2;
143     else if (dateOfMsg >= mostRecentWeek)
144       *aAgeBucket = 3;
145     else {
146       int64_t lastTwoWeeks = mostRecentMidnight - PR_USEC_PER_DAY * 13;
147       *aAgeBucket = (dateOfMsg >= lastTwoWeeks) ? 4 : 5;
148     }
149   } else {
150     // All that remains is a future date.
151     *aAgeBucket = 6;
152   }
153   return NS_OK;
154 }
155 
HashHdr(nsIMsgDBHdr * msgHdr,nsString & aHashKey)156 nsresult nsMsgGroupView::HashHdr(nsIMsgDBHdr* msgHdr, nsString& aHashKey) {
157   nsCString cStringKey;
158   aHashKey.Truncate();
159   nsresult rv = NS_OK;
160   bool rcvDate = false;
161 
162   switch (m_sortType) {
163     case nsMsgViewSortType::bySubject:
164       (void)msgHdr->GetSubject(getter_Copies(cStringKey));
165       CopyASCIItoUTF16(cStringKey, aHashKey);
166       break;
167     case nsMsgViewSortType::byAuthor:
168       rv = nsMsgDBView::FetchAuthor(msgHdr, aHashKey);
169       break;
170     case nsMsgViewSortType::byRecipient:
171       (void)msgHdr->GetRecipients(getter_Copies(cStringKey));
172       CopyASCIItoUTF16(cStringKey, aHashKey);
173       break;
174     case nsMsgViewSortType::byAccount:
175     case nsMsgViewSortType::byTags: {
176       nsCOMPtr<nsIMsgDatabase> dbToUse = m_db;
177       if (!dbToUse)
178         // Probably a search view.
179         GetDBForViewIndex(0, getter_AddRefs(dbToUse));
180 
181       rv = (m_sortType == nsMsgViewSortType::byAccount)
182                ? FetchAccount(msgHdr, aHashKey)
183                : FetchTags(msgHdr, aHashKey);
184     } break;
185     case nsMsgViewSortType::byAttachments: {
186       uint32_t flags;
187       msgHdr->GetFlags(&flags);
188       aHashKey.Assign(flags & nsMsgMessageFlags::Attachment ? '1' : '0');
189       break;
190     }
191     case nsMsgViewSortType::byFlagged: {
192       uint32_t flags;
193       msgHdr->GetFlags(&flags);
194       aHashKey.Assign(flags & nsMsgMessageFlags::Marked ? '1' : '0');
195       break;
196     }
197     case nsMsgViewSortType::byPriority: {
198       nsMsgPriorityValue priority;
199       msgHdr->GetPriority(&priority);
200       aHashKey.AppendInt(priority);
201     } break;
202     case nsMsgViewSortType::byStatus: {
203       uint32_t status = 0;
204       GetStatusSortValue(msgHdr, &status);
205       aHashKey.AppendInt(status);
206     } break;
207     case nsMsgViewSortType::byReceived:
208       rcvDate = true;
209       [[fallthrough]];
210     case nsMsgViewSortType::byDate: {
211       uint32_t ageBucket;
212       rv = GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
213       if (NS_SUCCEEDED(rv)) aHashKey.AppendInt(ageBucket);
214 
215       break;
216     }
217     case nsMsgViewSortType::byCustom: {
218       nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
219       if (colHandler) {
220         bool isString;
221         colHandler->IsString(&isString);
222         if (isString) {
223           rv = colHandler->GetSortStringForRow(msgHdr, aHashKey);
224         } else {
225           uint32_t intKey;
226           rv = colHandler->GetSortLongForRow(msgHdr, &intKey);
227           aHashKey.AppendInt(intKey);
228         }
229       }
230       break;
231     }
232     case nsMsgViewSortType::byCorrespondent:
233       if (IsOutgoingMsg(msgHdr))
234         rv = FetchRecipients(msgHdr, aHashKey);
235       else
236         rv = FetchAuthor(msgHdr, aHashKey);
237 
238       break;
239     default:
240       NS_ASSERTION(false, "no hash key for this type");
241       rv = NS_ERROR_FAILURE;
242   }
243   return rv;
244 }
245 
CreateGroupThread(nsIMsgDatabase * db)246 nsMsgGroupThread* nsMsgGroupView::CreateGroupThread(nsIMsgDatabase* db) {
247   return new nsMsgGroupThread(db);
248 }
249 
AddHdrToThread(nsIMsgDBHdr * msgHdr,bool * pNewThread)250 nsMsgGroupThread* nsMsgGroupView::AddHdrToThread(nsIMsgDBHdr* msgHdr,
251                                                  bool* pNewThread) {
252   nsMsgKey msgKey;
253   uint32_t msgFlags;
254   msgHdr->GetMessageKey(&msgKey);
255   msgHdr->GetFlags(&msgFlags);
256   nsString hashKey;
257   nsresult rv = HashHdr(msgHdr, hashKey);
258   if (NS_FAILED(rv)) return nullptr;
259 
260   // if (m_sortType == nsMsgViewSortType::byDate)
261   //    msgKey = ((nsPRUint32Key *)hashKey)->GetValue();
262   nsCOMPtr<nsIMsgThread> msgThread;
263   m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
264   bool newThread = !msgThread;
265   *pNewThread = newThread;
266   // Index of first message in thread in view.
267   nsMsgViewIndex viewIndexOfThread;
268   // Index of newly added header in thread.
269   nsMsgViewIndex threadInsertIndex;
270 
271   nsMsgGroupThread* foundThread =
272       static_cast<nsMsgGroupThread*>(msgThread.get());
273   if (foundThread) {
274     // Find the view index of the root node of the thread in the view.
275     viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(foundThread, true);
276     if (viewIndexOfThread == nsMsgViewIndex_None) {
277       // Something is wrong with the group table. Remove the old group and
278       // insert a new one.
279       m_groupsTable.Remove(hashKey);
280       foundThread = nullptr;
281       *pNewThread = newThread = true;
282     }
283   }
284 
285   // If the thread does not already exist, create one
286   if (!foundThread) {
287     foundThread = CreateGroupThread(m_db);
288     msgThread = foundThread;
289     m_groupsTable.InsertOrUpdate(hashKey, msgThread);
290     if (GroupViewUsesDummyRow()) {
291       foundThread->m_dummy = true;
292       msgFlags |= MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_HASCHILDREN;
293     }
294 
295     viewIndexOfThread = GetInsertIndex(msgHdr);
296     if (viewIndexOfThread == nsMsgViewIndex_None)
297       viewIndexOfThread = m_keys.Length();
298 
299     // Add the thread root node to the view.
300     InsertMsgHdrAt(
301         viewIndexOfThread, msgHdr, msgKey,
302         msgFlags | MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided, 0);
303 
304     // For dummy rows, Have the header serve as the dummy node (it will be
305     // added again for its actual content later).
306     if (GroupViewUsesDummyRow()) foundThread->InsertMsgHdrAt(0, msgHdr);
307 
308     // Calculate the (integer thread key); this really only needs to be done for
309     // the byDate case where the expanded state of the groups can be easily
310     // persisted and restored because of the bounded, consecutive value space
311     // occupied.  We calculate an integer value in all cases mainly because
312     // it's the sanest choice available...
313     // (The thread key needs to be an integer, so parse hash keys that are
314     // stringified integers to real integers, and hash actual strings into
315     // integers.)
316     if ((m_sortType == nsMsgViewSortType::byAttachments) ||
317         (m_sortType == nsMsgViewSortType::byFlagged) ||
318         (m_sortType == nsMsgViewSortType::byPriority) ||
319         (m_sortType == nsMsgViewSortType::byStatus) ||
320         (m_sortType == nsMsgViewSortType::byReceived) ||
321         (m_sortType == nsMsgViewSortType::byDate))
322       foundThread->m_threadKey =
323           atoi(NS_LossyConvertUTF16toASCII(hashKey).get());
324     else
325       foundThread->m_threadKey =
326           (nsMsgKey)PL_HashString(NS_LossyConvertUTF16toASCII(hashKey).get());
327   }
328 
329   // Add the message to the thread as an actual content-bearing header.
330   // (If we use dummy rows, it was already added to the thread during creation.)
331   threadInsertIndex = foundThread->AddChildFromGroupView(msgHdr, this);
332   // Check if new hdr became thread root.
333   if (!newThread && threadInsertIndex == 0) {
334     // Update the root node's header (in the view) to be the same as the root
335     // node in the thread.
336     SetMsgHdrAt(msgHdr, viewIndexOfThread, msgKey,
337                 (msgFlags & ~(nsMsgMessageFlags::Elided)) |
338                     // Maintain elided flag and dummy flag.
339                     (m_flags[viewIndexOfThread] &
340                      (nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_DUMMY)) |
341                     // Ensure thread and has-children flags are set.
342                     MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN,
343                 0);
344     // Update the content-bearing copy in the thread to match.  (the root and
345     // first nodes in the thread should always be the same header.)
346     // Note: the guy who used to be the root will still exist.  If our list of
347     // nodes was [A A], a new node B is introduced which sorts to be the first
348     // node, giving us [B A A], our copy makes that [B B A], and things are
349     // right in the world (since we want the first two headers to be the same
350     // since one is our dummy and one is real.)
351     if (GroupViewUsesDummyRow()) {
352       // Replace the old duplicate dummy header.
353       // We do not update the content-bearing copy in the view to match; we
354       // leave that up to OnNewHeader, which is the piece of code who gets to
355       // care about whether the thread's children are shown or not (elided).
356       foundThread->SetMsgHdrAt(1, msgHdr);
357     }
358   }
359 
360   return foundThread;
361 }
362 
363 NS_IMETHODIMP
OpenWithHdrs(nsIMsgEnumerator * aHeaders,nsMsgViewSortTypeValue aSortType,nsMsgViewSortOrderValue aSortOrder,nsMsgViewFlagsTypeValue aViewFlags,int32_t * aCount)364 nsMsgGroupView::OpenWithHdrs(nsIMsgEnumerator* aHeaders,
365                              nsMsgViewSortTypeValue aSortType,
366                              nsMsgViewSortOrderValue aSortOrder,
367                              nsMsgViewFlagsTypeValue aViewFlags,
368                              int32_t* aCount) {
369   nsresult rv = NS_OK;
370 
371   m_groupsTable.Clear();
372   if (aSortType == nsMsgViewSortType::byThread ||
373       aSortType == nsMsgViewSortType::byId ||
374       aSortType == nsMsgViewSortType::byNone ||
375       aSortType == nsMsgViewSortType::bySize)
376     return NS_ERROR_INVALID_ARG;
377 
378   m_sortType = aSortType;
379   m_sortOrder = aSortOrder;
380   m_viewFlags = aViewFlags | nsMsgViewFlagsType::kThreadedDisplay |
381                 nsMsgViewFlagsType::kGroupBySort;
382   SaveSortInfo(m_sortType, m_sortOrder);
383 
384   if (m_sortType == nsMsgViewSortType::byCustom) {
385     // If the desired sort is a custom column and there is no handler found,
386     // it hasn't been registered yet; after the custom column observer is
387     // notified with MsgCreateDBView and registers the handler, it will come
388     // back and build the view.
389     nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
390     if (!colHandler) return rv;
391   }
392 
393   bool hasMore;
394   nsCOMPtr<nsISupports> supports;
395   nsCOMPtr<nsIMsgDBHdr> msgHdr;
396   while (NS_SUCCEEDED(rv) &&
397          NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore) {
398     rv = aHeaders->GetNext(getter_AddRefs(msgHdr));
399     if (NS_SUCCEEDED(rv) && msgHdr) {
400       bool notUsed;
401       AddHdrToThread(msgHdr, &notUsed);
402     }
403   }
404   uint32_t expandFlags = 0;
405   bool expandAll = m_viewFlags & nsMsgViewFlagsType::kExpandAll;
406   uint32_t viewFlag =
407       (m_sortType == nsMsgViewSortType::byDate) ? MSG_VIEW_FLAG_DUMMY : 0;
408   if (viewFlag && m_db) {
409     nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
410     nsresult rv = m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
411     NS_ENSURE_SUCCESS(rv, rv);
412     if (dbFolderInfo)
413       dbFolderInfo->GetUint32Property("dateGroupFlags", 0, &expandFlags);
414   }
415   // Go through the view updating the flags for threads with more than one
416   // message, and if grouped by date, expanding threads that were expanded
417   // before.
418   for (uint32_t viewIndex = 0; viewIndex < m_keys.Length(); viewIndex++) {
419     nsCOMPtr<nsIMsgThread> thread;
420     GetThreadContainingIndex(viewIndex, getter_AddRefs(thread));
421     if (thread) {
422       uint32_t numChildren;
423       thread->GetNumChildren(&numChildren);
424       if (numChildren > 1 || viewFlag)
425         OrExtraFlag(viewIndex, viewFlag | MSG_VIEW_FLAG_HASCHILDREN);
426       if (expandAll || expandFlags) {
427         nsMsgGroupThread* groupThread =
428             static_cast<nsMsgGroupThread*>((nsIMsgThread*)thread);
429         if (expandAll || expandFlags & (1 << groupThread->m_threadKey)) {
430           uint32_t numExpanded;
431           ExpandByIndex(viewIndex, &numExpanded);
432           viewIndex += numExpanded;
433         }
434       }
435     }
436   }
437   *aCount = m_keys.Length();
438   return rv;
439 }
440 
441 // We wouldn't need this if we never instantiated this directly,
442 // but instead used nsMsgThreadedDBView with the grouping flag set.
443 // Or, we could get rid of the nsMsgThreadedDBView impl of this method.
444 NS_IMETHODIMP
GetViewType(nsMsgViewTypeValue * aViewType)445 nsMsgGroupView::GetViewType(nsMsgViewTypeValue* aViewType) {
446   NS_ENSURE_ARG_POINTER(aViewType);
447   *aViewType = nsMsgViewType::eShowAllThreads;
448   return NS_OK;
449 }
450 
451 NS_IMETHODIMP
CopyDBView(nsMsgDBView * aNewMsgDBView,nsIMessenger * aMessengerInstance,nsIMsgWindow * aMsgWindow,nsIMsgDBViewCommandUpdater * aCmdUpdater)452 nsMsgGroupView::CopyDBView(nsMsgDBView* aNewMsgDBView,
453                            nsIMessenger* aMessengerInstance,
454                            nsIMsgWindow* aMsgWindow,
455                            nsIMsgDBViewCommandUpdater* aCmdUpdater) {
456   nsMsgDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow,
457                           aCmdUpdater);
458   nsMsgGroupView* newMsgDBView = (nsMsgGroupView*)aNewMsgDBView;
459 
460   // If grouped, we need to clone the group thread hash table.
461   if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) {
462     for (auto iter = m_groupsTable.Iter(); !iter.Done(); iter.Next()) {
463       newMsgDBView->m_groupsTable.InsertOrUpdate(iter.Key(), iter.UserData());
464     }
465   }
466   return NS_OK;
467 }
468 
469 // E.g., if the day has changed, we need to close and re-open the view.
470 // Or, if we're switching between grouping and threading in a cross-folder
471 // saved search. In that case, we needed to build an enumerator based on the
472 // old view type, and internally close the view based on its old type, but
473 // rebuild the new view based on the new view type. So we pass the new
474 // view flags to OpenWithHdrs.
RebuildView(nsMsgViewFlagsTypeValue newFlags)475 nsresult nsMsgGroupView::RebuildView(nsMsgViewFlagsTypeValue newFlags) {
476   nsCOMPtr<nsIMsgEnumerator> headers;
477   if (NS_SUCCEEDED(GetMessageEnumerator(getter_AddRefs(headers)))) {
478     int32_t count;
479     m_dayChanged = false;
480     AutoTArray<nsMsgKey, 1> preservedSelection;
481     nsMsgKey curSelectedKey;
482     SaveAndClearSelection(&curSelectedKey, preservedSelection);
483     InternalClose();
484     int32_t oldSize = GetSize();
485     // This is important, because the tree will ask us for our row count,
486     // which gets determined from the number of keys.
487     m_keys.Clear();
488     // Be consistent.
489     m_flags.Clear();
490     m_levels.Clear();
491 
492     // This needs to happen after we remove all the keys, since
493     // RowCountChanged() will call our GetRowCount().
494     if (mTree) mTree->RowCountChanged(0, -oldSize);
495 
496     SetSuppressChangeNotifications(true);
497     nsresult rv =
498         OpenWithHdrs(headers, m_sortType, m_sortOrder, newFlags, &count);
499     SetSuppressChangeNotifications(false);
500     if (mTree) mTree->RowCountChanged(0, GetSize());
501 
502     NS_ENSURE_SUCCESS(rv, rv);
503 
504     // Now, restore our desired selection.
505     AutoTArray<nsMsgKey, 1> keyArray;
506     keyArray.AppendElement(curSelectedKey);
507 
508     return RestoreSelection(curSelectedKey, keyArray);
509   }
510   return NS_OK;
511 }
512 
OnNewHeader(nsIMsgDBHdr * newHdr,nsMsgKey aParentKey,bool ensureListed)513 nsresult nsMsgGroupView::OnNewHeader(nsIMsgDBHdr* newHdr, nsMsgKey aParentKey,
514                                      bool ensureListed) {
515   if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
516     return nsMsgDBView::OnNewHeader(newHdr, aParentKey, ensureListed);
517 
518   // Check if we're adding a header, and the current day has changed.
519   // If it has, we're just going to close and re-open the view so things
520   // will be correctly categorized.
521   if (m_dayChanged) return RebuildView(m_viewFlags);
522 
523   bool newThread;
524   nsMsgGroupThread* thread = AddHdrToThread(newHdr, &newThread);
525   if (thread) {
526     // Find the view index of (the root node of) the thread.
527     nsMsgViewIndex threadIndex = ThreadIndexOfMsgHdr(newHdr);
528     // May need to fix thread counts.
529     if (threadIndex != nsMsgViewIndex_None) {
530       if (newThread) {
531         // AddHdrToThread creates the header elided, so we need to un-elide it
532         // if we want it expanded.
533         if (m_viewFlags & nsMsgViewFlagsType::kExpandAll)
534           m_flags[threadIndex] &= ~nsMsgMessageFlags::Elided;
535       } else {
536         m_flags[threadIndex] |=
537             MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
538       }
539 
540       int32_t numRowsToInvalidate = 1;
541       // If the thread is expanded (not elided), we should add the header to
542       // the view.
543       if (!(m_flags[threadIndex] & nsMsgMessageFlags::Elided)) {
544         uint32_t msgIndexInThread = thread->FindMsgHdr(newHdr);
545         bool insertedAtThreadRoot = !msgIndexInThread;
546         // Add any new display node and potentially fix-up changes in the root.
547         // (If this is a new thread and we are not using a dummy row, the only
548         // node to display is the root node which has already been added by
549         // AddHdrToThread.  And since there is just the one, no change in root
550         // could have occurred, so we have nothing to do.)
551         if (!newThread || GroupViewUsesDummyRow()) {
552           // We never want to insert/update the root node, because
553           // AddHdrToThread has already done that for us (in all cases).
554           if (insertedAtThreadRoot) msgIndexInThread++;
555           // If this header is the new parent of the thread... AND
556           // If we are not using a dummy row, this means we need to append our
557           // old node as the first child of the new root.
558           // (If we are using a dummy row, the old node's "content" node already
559           // exists (at position threadIndex + 1) and we need to insert the
560           // "content" copy of the new root node there, pushing our old
561           // "content" node down.)
562           // Example mini-diagrams, wrapping the to-add thing with ()
563           // No dummy row; we had: [A], now we have [B], we want [B (A)].
564           // Dummy row; we had: [A A], now we have [B A], we want [B (B) A].
565           // (Coming into this we're adding 'B')
566           if (!newThread && insertedAtThreadRoot && !GroupViewUsesDummyRow()) {
567             // Grab a copy of the old root node ('A') from the thread so we can
568             // insert it. (offset msgIndexInThread=1 is the right thing; we are
569             // non-dummy.)
570             thread->GetChildHdrAt(msgIndexInThread, &newHdr);
571           }
572           // Nothing to do for dummy case, we're already inserting 'B'.
573 
574           nsMsgKey msgKey;
575           uint32_t msgFlags;
576           newHdr->GetMessageKey(&msgKey);
577           newHdr->GetFlags(&msgFlags);
578           InsertMsgHdrAt(threadIndex + msgIndexInThread, newHdr, msgKey,
579                          msgFlags, 1);
580         }
581         // The call to NoteChange() has to happen after we add the key
582         // as NoteChange() will call RowCountChanged() which will call our
583         // GetRowCount().
584         // (msgIndexInThread states - new thread: 0, old thread at root: 1).
585         if (newThread && GroupViewUsesDummyRow())
586           NoteChange(threadIndex, 2, nsMsgViewNotificationCode::insertOrDelete);
587         else
588           NoteChange(threadIndex + msgIndexInThread, 1,
589                      nsMsgViewNotificationCode::insertOrDelete);
590 
591         numRowsToInvalidate = msgIndexInThread;
592       } else if (newThread) {
593         // We still need the addition notification for new threads when elided.
594         NoteChange(threadIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
595       }
596 
597       NoteChange(threadIndex, numRowsToInvalidate,
598                  nsMsgViewNotificationCode::changed);
599     }
600   }
601 
602   // If thread is expanded, we need to add hdr to view...
603   return NS_OK;
604 }
605 
606 NS_IMETHODIMP
OnHdrFlagsChanged(nsIMsgDBHdr * aHdrChanged,uint32_t aOldFlags,uint32_t aNewFlags,nsIDBChangeListener * aInstigator)607 nsMsgGroupView::OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags,
608                                   uint32_t aNewFlags,
609                                   nsIDBChangeListener* aInstigator) {
610   if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
611     return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags,
612                                           aInstigator);
613 
614   nsCOMPtr<nsIMsgThread> thread;
615 
616   // Check if we're adding a header, and the current day has changed.
617   // If it has, we're just going to close and re-open the view so things
618   // will be correctly categorized.
619   if (m_dayChanged) return RebuildView(m_viewFlags);
620 
621   nsresult rv = GetThreadContainingMsgHdr(aHdrChanged, getter_AddRefs(thread));
622   NS_ENSURE_SUCCESS(rv, rv);
623   uint32_t deltaFlags = (aOldFlags ^ aNewFlags);
624   if (deltaFlags & nsMsgMessageFlags::Read)
625     thread->MarkChildRead(aNewFlags & nsMsgMessageFlags::Read);
626 
627   return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags,
628                                         aInstigator);
629 }
630 
631 NS_IMETHODIMP
OnHdrDeleted(nsIMsgDBHdr * aHdrDeleted,nsMsgKey aParentKey,int32_t aFlags,nsIDBChangeListener * aInstigator)632 nsMsgGroupView::OnHdrDeleted(nsIMsgDBHdr* aHdrDeleted, nsMsgKey aParentKey,
633                              int32_t aFlags, nsIDBChangeListener* aInstigator) {
634   if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
635     return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags,
636                                      aInstigator);
637 
638   // Check if we're adding a header, and the current day has changed.
639   // If it has, we're just going to close and re-open the view so things
640   // will be correctly categorized.
641   if (m_dayChanged) return RebuildView(m_viewFlags);
642 
643   nsCOMPtr<nsIMsgThread> thread;
644   nsMsgKey keyDeleted;
645   aHdrDeleted->GetMessageKey(&keyDeleted);
646 
647   nsresult rv = GetThreadContainingMsgHdr(aHdrDeleted, getter_AddRefs(thread));
648   NS_ENSURE_SUCCESS(rv, rv);
649   nsMsgViewIndex viewIndexOfThread =
650       GetIndexOfFirstDisplayedKeyInThread(thread, true);  // Yes to dummy node.
651 
652   thread->RemoveChildHdr(aHdrDeleted, nullptr);
653 
654   nsMsgGroupThread* groupThread =
655       static_cast<nsMsgGroupThread*>((nsIMsgThread*)thread);
656 
657   bool rootDeleted = viewIndexOfThread != nsMsgKey_None &&
658                      m_keys[viewIndexOfThread] == keyDeleted;
659   rv = nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator);
660   if (groupThread->m_dummy) {
661     if (!groupThread->NumRealChildren()) {
662       // Get rid of dummy.
663       thread->RemoveChildAt(0);
664       if (viewIndexOfThread != nsMsgKey_None) {
665         RemoveByIndex(viewIndexOfThread);
666         if (m_deletingRows)
667           mIndicesToNoteChange.AppendElement(viewIndexOfThread);
668       }
669     } else if (rootDeleted) {
670       // Reflect new thread root into view.dummy row.
671       nsCOMPtr<nsIMsgDBHdr> hdr;
672       thread->GetChildHdrAt(0, getter_AddRefs(hdr));
673       if (hdr) {
674         nsMsgKey msgKey;
675         hdr->GetMessageKey(&msgKey);
676         SetMsgHdrAt(hdr, viewIndexOfThread, msgKey, m_flags[viewIndexOfThread],
677                     0);
678       }
679     }
680   }
681   if (!groupThread->m_keys.Length()) {
682     nsString hashKey;
683     rv = HashHdr(aHdrDeleted, hashKey);
684     if (NS_SUCCEEDED(rv)) m_groupsTable.Remove(hashKey);
685   }
686   return rv;
687 }
688 
689 NS_IMETHODIMP
GetRowProperties(int32_t aRow,nsAString & aProperties)690 nsMsgGroupView::GetRowProperties(int32_t aRow, nsAString& aProperties) {
691   if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
692 
693   if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) {
694     aProperties.AssignLiteral("dummy");
695     return NS_OK;
696   }
697 
698   return nsMsgDBView::GetRowProperties(aRow, aProperties);
699 }
700 
701 NS_IMETHODIMP
GetCellProperties(int32_t aRow,nsTreeColumn * aCol,nsAString & aProperties)702 nsMsgGroupView::GetCellProperties(int32_t aRow, nsTreeColumn* aCol,
703                                   nsAString& aProperties) {
704   if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
705 
706   if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) {
707     aProperties.AssignLiteral("dummy read");
708 
709     if (!(m_flags[aRow] & nsMsgMessageFlags::Elided)) return NS_OK;
710 
711     // Set unread property if a collapsed group thread has unread.
712     nsCOMPtr<nsIMsgDBHdr> msgHdr;
713     nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
714     NS_ENSURE_SUCCESS(rv, rv);
715     nsString hashKey;
716     rv = HashHdr(msgHdr, hashKey);
717     if (NS_FAILED(rv)) return NS_OK;
718 
719     nsCOMPtr<nsIMsgThread> msgThread;
720     m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
721     nsMsgGroupThread* groupThread =
722         static_cast<nsMsgGroupThread*>(msgThread.get());
723     if (!groupThread) return NS_OK;
724 
725     uint32_t numUnrMsg = 0;
726     groupThread->GetNumUnreadChildren(&numUnrMsg);
727     if (numUnrMsg > 0) aProperties.AppendLiteral(" hasUnread");
728 
729     return NS_OK;
730   }
731 
732   return nsMsgDBView::GetCellProperties(aRow, aCol, aProperties);
733 }
734 
735 NS_IMETHODIMP
CellTextForColumn(int32_t aRow,const nsAString & aColumnName,nsAString & aValue)736 nsMsgGroupView::CellTextForColumn(int32_t aRow, const nsAString& aColumnName,
737                                   nsAString& aValue) {
738   if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
739 
740   if (!(m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) ||
741       aColumnName.EqualsLiteral("unreadCol"))
742     return nsMsgDBView::CellTextForColumn(aRow, aColumnName, aValue);
743 
744   // We only treat "subject" and "total" here.
745   bool isSubject;
746   if (!(isSubject = aColumnName.EqualsLiteral("subjectCol")) &&
747       !aColumnName.EqualsLiteral("totalCol"))
748     return NS_OK;
749 
750   nsCOMPtr<nsIMsgDBHdr> msgHdr;
751   nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
752   NS_ENSURE_SUCCESS(rv, rv);
753   nsString hashKey;
754   rv = HashHdr(msgHdr, hashKey);
755   if (NS_FAILED(rv)) return NS_OK;
756   nsCOMPtr<nsIMsgThread> msgThread;
757   m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
758   nsMsgGroupThread* groupThread =
759       static_cast<nsMsgGroupThread*>(msgThread.get());
760   if (isSubject) {
761     uint32_t flags;
762     bool rcvDate = false;
763     msgHdr->GetFlags(&flags);
764     aValue.Truncate();
765     nsString tmp_str;
766     switch (m_sortType) {
767       case nsMsgViewSortType::byReceived:
768         rcvDate = true;
769         [[fallthrough]];
770       case nsMsgViewSortType::byDate: {
771         uint32_t ageBucket = 0;
772         GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
773         switch (ageBucket) {
774           case 1:
775             if (m_kTodayString.IsEmpty())
776               m_kTodayString.Adopt(GetString(u"today"));
777 
778             aValue.Assign(m_kTodayString);
779             break;
780           case 2:
781             if (m_kYesterdayString.IsEmpty())
782               m_kYesterdayString.Adopt(GetString(u"yesterday"));
783 
784             aValue.Assign(m_kYesterdayString);
785             break;
786           case 3:
787             if (m_kLastWeekString.IsEmpty())
788               m_kLastWeekString.Adopt(GetString(u"last7Days"));
789 
790             aValue.Assign(m_kLastWeekString);
791             break;
792           case 4:
793             if (m_kTwoWeeksAgoString.IsEmpty())
794               m_kTwoWeeksAgoString.Adopt(GetString(u"last14Days"));
795 
796             aValue.Assign(m_kTwoWeeksAgoString);
797             break;
798           case 5:
799             if (m_kOldMailString.IsEmpty())
800               m_kOldMailString.Adopt(GetString(u"older"));
801 
802             aValue.Assign(m_kOldMailString);
803             break;
804           default:
805             // Future date, error/spoofed.
806             if (m_kFutureDateString.IsEmpty())
807               m_kFutureDateString.Adopt(GetString(u"futureDate"));
808 
809             aValue.Assign(m_kFutureDateString);
810             break;
811         }
812         break;
813       }
814       case nsMsgViewSortType::bySubject:
815         FetchSubject(msgHdr, m_flags[aRow], aValue);
816         break;
817       case nsMsgViewSortType::byAuthor:
818         FetchAuthor(msgHdr, aValue);
819         break;
820       case nsMsgViewSortType::byStatus:
821         rv = FetchStatus(m_flags[aRow], aValue);
822         if (aValue.IsEmpty()) {
823           tmp_str.Adopt(GetString(u"messagesWithNoStatus"));
824           aValue.Assign(tmp_str);
825         }
826         break;
827       case nsMsgViewSortType::byTags:
828         rv = FetchTags(msgHdr, aValue);
829         if (aValue.IsEmpty()) {
830           tmp_str.Adopt(GetString(u"untaggedMessages"));
831           aValue.Assign(tmp_str);
832         }
833         break;
834       case nsMsgViewSortType::byPriority:
835         FetchPriority(msgHdr, aValue);
836         if (aValue.IsEmpty()) {
837           tmp_str.Adopt(GetString(u"noPriority"));
838           aValue.Assign(tmp_str);
839         }
840         break;
841       case nsMsgViewSortType::byAccount:
842         FetchAccount(msgHdr, aValue);
843         break;
844       case nsMsgViewSortType::byRecipient:
845         FetchRecipients(msgHdr, aValue);
846         break;
847       case nsMsgViewSortType::byAttachments:
848         tmp_str.Adopt(GetString(flags & nsMsgMessageFlags::Attachment
849                                     ? u"attachments"
850                                     : u"noAttachments"));
851         aValue.Assign(tmp_str);
852         break;
853       case nsMsgViewSortType::byFlagged:
854         tmp_str.Adopt(GetString(flags & nsMsgMessageFlags::Marked
855                                     ? u"groupFlagged"
856                                     : u"notFlagged"));
857         aValue.Assign(tmp_str);
858         break;
859       // byLocation is a special case; we don't want to have duplicate
860       // all this logic in nsMsgSearchDBView, and its hash key is what we
861       // want anyways, so just copy it across.
862       case nsMsgViewSortType::byLocation:
863       case nsMsgViewSortType::byCorrespondent:
864         aValue = hashKey;
865         break;
866       case nsMsgViewSortType::byCustom: {
867         nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
868         if (colHandler) {
869           bool isString;
870           colHandler->IsString(&isString);
871           if (isString) {
872             rv = colHandler->GetSortStringForRow(msgHdr.get(), aValue);
873           } else {
874             uint32_t intKey;
875             rv = colHandler->GetSortLongForRow(msgHdr.get(), &intKey);
876             aValue.AppendInt(intKey);
877           }
878         }
879         if (aValue.IsEmpty()) aValue.Assign('*');
880         break;
881       }
882 
883       default:
884         NS_ASSERTION(false, "we don't sort by group for this type");
885         break;
886     }
887 
888     if (groupThread) {
889       // Get number of messages in group.
890       nsAutoString formattedCountMsg;
891       uint32_t numMsg = groupThread->NumRealChildren();
892       formattedCountMsg.AppendInt(numMsg);
893 
894       // Get number of unread messages.
895       nsAutoString formattedCountUnrMsg;
896       uint32_t numUnrMsg = 0;
897       groupThread->GetNumUnreadChildren(&numUnrMsg);
898       formattedCountUnrMsg.AppendInt(numUnrMsg);
899 
900       // Add text to header.
901       aValue.AppendLiteral(u" (");
902       if (numUnrMsg) {
903         aValue.Append(formattedCountUnrMsg);
904         aValue.Append(u'/');
905       }
906 
907       aValue.Append(formattedCountMsg);
908       aValue.Append(u')');
909     }
910   } else {
911     nsAutoString formattedCountString;
912     uint32_t numChildren = (groupThread) ? groupThread->NumRealChildren() : 0;
913     formattedCountString.AppendInt(numChildren);
914     aValue.Assign(formattedCountString);
915   }
916   return NS_OK;
917 }
918 
919 NS_IMETHODIMP
LoadMessageByViewIndex(nsMsgViewIndex aViewIndex)920 nsMsgGroupView::LoadMessageByViewIndex(nsMsgViewIndex aViewIndex) {
921   if (!IsValidIndex(aViewIndex)) return NS_MSG_INVALID_DBVIEW_INDEX;
922 
923   if (m_flags[aViewIndex] & MSG_VIEW_FLAG_DUMMY) {
924     // If we used to have one item selected, and now we have more than one,
925     // we should clear the message pane.
926     nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
927     nsCOMPtr<nsIMsgWindowCommands> windowCommands;
928     if (msgWindow &&
929         NS_SUCCEEDED(
930             msgWindow->GetWindowCommands(getter_AddRefs(windowCommands))) &&
931         windowCommands) {
932       windowCommands->ClearMsgPane();
933     }
934 
935     // Since we are selecting a dummy row, we should also clear out
936     // m_currentlyDisplayedMsgUri.
937     m_currentlyDisplayedMsgUri.Truncate();
938     return NS_OK;
939   } else {
940     return nsMsgDBView::LoadMessageByViewIndex(aViewIndex);
941   }
942 }
943 
944 NS_IMETHODIMP
GetThreadContainingMsgHdr(nsIMsgDBHdr * msgHdr,nsIMsgThread ** pThread)945 nsMsgGroupView::GetThreadContainingMsgHdr(nsIMsgDBHdr* msgHdr,
946                                           nsIMsgThread** pThread) {
947   if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
948     return nsMsgDBView::GetThreadContainingMsgHdr(msgHdr, pThread);
949 
950   nsString hashKey;
951   nsresult rv = HashHdr(msgHdr, hashKey);
952   *pThread = nullptr;
953   if (NS_SUCCEEDED(rv)) {
954     nsCOMPtr<nsIMsgThread> thread;
955     m_groupsTable.Get(hashKey, getter_AddRefs(thread));
956     thread.forget(pThread);
957   }
958 
959   return (*pThread) ? NS_OK : NS_ERROR_FAILURE;
960 }
961 
FindLevelInThread(nsIMsgDBHdr * msgHdr,nsMsgViewIndex startOfThread,nsMsgViewIndex viewIndex)962 int32_t nsMsgGroupView::FindLevelInThread(nsIMsgDBHdr* msgHdr,
963                                           nsMsgViewIndex startOfThread,
964                                           nsMsgViewIndex viewIndex) {
965   if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
966     return nsMsgDBView::FindLevelInThread(msgHdr, startOfThread, viewIndex);
967 
968   return (startOfThread == viewIndex) ? 0 : 1;
969 }
970 
GroupViewUsesDummyRow()971 bool nsMsgGroupView::GroupViewUsesDummyRow() {
972   // Return true to always use a header row as root grouped parent row.
973   return true;
974 }
975 
976 NS_IMETHODIMP
AddColumnHandler(const nsAString & column,nsIMsgCustomColumnHandler * handler)977 nsMsgGroupView::AddColumnHandler(const nsAString& column,
978                                  nsIMsgCustomColumnHandler* handler) {
979   nsMsgDBView::AddColumnHandler(column, handler);
980 
981   // If the sortType is byCustom and the desired custom column is the one just
982   // registered, build the view.
983   if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort &&
984       m_sortType == nsMsgViewSortType::byCustom) {
985     nsAutoString curCustomColumn;
986     GetCurCustomColumn(curCustomColumn);
987     if (curCustomColumn == column) RebuildView(m_viewFlags);
988   }
989 
990   return NS_OK;
991 }
992