1 /******************************************************************************
2  *
3  *  SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *
7  *******************************************************************************/
8 
9 #pragma once
10 
11 #include "model.h"
12 #include "threadingcache.h"
13 #include <QTimer>
14 #include <config-messagelist.h>
15 
16 class QElapsedTimer;
17 namespace MessageList
18 {
19 namespace Core
20 {
21 class ViewItemJob;
22 class ModelInvariantRowMapper;
23 class MessageItemSetManager;
24 class ModelPrivate
25 {
26 public:
ModelPrivate(Model * owner)27     explicit ModelPrivate(Model *owner)
28         : q(owner)
29     {
30     }
31 
32     void fillView();
33 
34     /**
35      * This is called by MessageList::Manager once in a while.
36      * It is a good place to check if the date has changed and
37      * trigger a view reload.
38      */
39     void checkIfDateChanged();
40 
41     void viewItemJobStep();
42 
43     /**
44      * Attempt to find the threading parent for the specified message item.
45      * Sets the message threading status to the appropriate value.
46      *
47      * This function performs In-Reply-To and References threading.
48      */
49     MessageItem *findMessageParent(MessageItem *mi);
50     /**
51      * Attempt to find the threading parent for the specified message item.
52      * Sets the message threading status to the appropriate value.
53      *
54      * This function performs Subject based threading.
55      */
56     MessageItem *guessMessageParent(MessageItem *mi);
57 
58     enum AttachOptions { SkipCacheUpdate = 0, StoreInCache = 1 };
59     void attachMessageToParent(Item *pParent, MessageItem *mi, AttachOptions attachOptions = StoreInCache);
60     void messageDetachedUpdateParentProperties(Item *oldParent, MessageItem *mi);
61     void attachMessageToGroupHeader(MessageItem *mi);
62     void attachGroup(GroupHeaderItem *ghi);
63 
64     enum ViewItemJobResult { ViewItemJobCompleted, ViewItemJobInterrupted };
65     ViewItemJobResult viewItemJobStepInternal();
66     ViewItemJobResult viewItemJobStepInternalForJob(ViewItemJob *job, QElapsedTimer elapsedTimer);
67 
68     // FIXME: Those look like they should be made virtual in some job class! -> Refactor
69     ViewItemJobResult viewItemJobStepInternalForJobPass1Fill(ViewItemJob *job, QElapsedTimer elapsedTimer);
70     ViewItemJobResult viewItemJobStepInternalForJobPass1Cleanup(ViewItemJob *job, QElapsedTimer elapsedTimer);
71     ViewItemJobResult viewItemJobStepInternalForJobPass1Update(ViewItemJob *job, QElapsedTimer elapsedTimer);
72     ViewItemJobResult viewItemJobStepInternalForJobPass2(ViewItemJob *job, QElapsedTimer elapsedTimer);
73     ViewItemJobResult viewItemJobStepInternalForJobPass3(ViewItemJob *job, QElapsedTimer elapsedTimer);
74     ViewItemJobResult viewItemJobStepInternalForJobPass4(ViewItemJob *job, QElapsedTimer elapsedTimer);
75     ViewItemJobResult viewItemJobStepInternalForJobPass5(ViewItemJob *job, QElapsedTimer elapsedTimer);
76     void clearJobList();
77     void clearUnassignedMessageLists();
78     void clearOrphanChildrenHash();
79     void clearThreadingCacheReferencesIdMD5ToMessageItem();
80     void clearThreadingCacheMessageSubjectMD5ToMessageItem();
81     void addMessageToReferencesBasedThreadingCache(MessageItem *mi);
82     void removeMessageFromReferencesBasedThreadingCache(MessageItem *mi);
83     void addMessageToSubjectBasedThreadingCache(MessageItem *mi);
84     void removeMessageFromSubjectBasedThreadingCache(MessageItem *mi);
85     void clear();
86     /**
87      * Sync the expanded state of the subtree with the specified root.
88      * This will cause the items that are marked with Item::ExpandNeeded to be
89      * expanded also in the view. For optimization purposes the specified root
90      * is assumed to be marked as Item::ExpandNeeded so be sure to check it
91      * before calling this function.
92      */
93     void syncExpandedStateOfSubtree(Item *root);
94     /**
95      * Save the expanded state of the subtree with the specified root.
96      * The state will be saved in the initialExpandStatus() variable.
97      * For optimization purposes the specified root is assumed to be expanded
98      * and viewable.
99      */
100     void saveExpandedStateOfSubtree(Item *root);
101 
102 #ifdef KDEPIM_FOLDEROPEN_PROFILE
103     // This prints out all the stats we collected
104     void printStatistics();
105 #endif
106 
107     enum PropertyChanges {
108         DateChanged = 1,
109         MaxDateChanged = (1 << 1),
110         ActionItemStatusChanged = (1 << 2),
111         UnreadStatusChanged = (1 << 3),
112         ImportantStatusChanged = (1 << 4),
113         AttachmentStatusChanged = (1 << 5)
114     };
115 
116     /**
117      * Handle the specified property changes in item. Depending on the item
118      * position inside the parent and the types of item and parent the item
119      * might need re-grouping or re-sorting. This function takes care of that.
120      * It is meant to be called from somewhere inside viewItemJobStepInternal()
121      * as it postpones group updates to Pass5.
122      *
123      * parent and item must not be null. propertyChangeMask should not be zero.
124      *
125      * Return true if parent might be affected by the item property changes
126      * and false otherwise.
127      */
128     bool handleItemPropertyChanges(int propertyChangeMask, Item *parent, Item *item);
129 
130     /**
131      * This one checks if the parent of item requires an update due to the
132      * properties of item (that might have been changed or the item might
133      * have been simply added to the parent). The properties
134      * are propagated up to the root item. As optimization we ASSUME that
135      * the item->parent() exists (is non 0) and is NOT the root item.
136      * Be sure to check it before calling this function (it will assert in debug mode anyway).
137      * ... ah... and don't be afraid: this is NOT (directly) recursive :)
138      */
139     void propagateItemPropertiesToParent(Item *item);
140 
141     /**
142      * Recursively applies the current filter to the tree originating at the specified item.
143      * The item is hidden if the filter doesn't match (the item or any children of it)
144      * and this function returns false.
145      * If the filter matches somewhere in the subtree then the item isn't hidden
146      * and this function returns true.
147      *
148      * Assumes that the specified item is viewable.
149      */
150     bool applyFilterToSubtree(Item *item, const QModelIndex &parentIndex);
151 
152     // Slots connected to the underlying StorageModel.
153 
154     void slotStorageModelRowsInserted(const QModelIndex &parent, int from, int to);
155     void slotStorageModelRowsRemoved(const QModelIndex &parent, int from, int to);
156     void slotStorageModelDataChanged(const QModelIndex &fromIndex, const QModelIndex &toIndex);
157     void slotStorageModelHeaderDataChanged(Qt::Orientation orientation, int first, int last);
158     void slotStorageModelLayoutChanged();
159     void slotApplyFilter();
160 
161     Model *const q;
162 
163     /** counter to avoid infinite recursions in the setStorageModel() function */
164     int mRecursionCounterForReset;
165 
166     /**
167      * The currently set storage model: shallow pointer.
168      */
169     StorageModel *mStorageModel = nullptr;
170 
171     /**
172      * The currently set aggregation mode: shallow pointer set by Widget
173      */
174     const Aggregation *mAggregation = nullptr;
175 
176     /**
177      * The currently used theme: shallow pointer
178      */
179     const Theme *mTheme = nullptr;
180 
181     /**
182      * The currently used sort order. Pointer not owned by us, but by the Widget.
183      */
184     const SortOrder *mSortOrder = nullptr;
185 
186     /**
187      * The filter to apply on messages. Shallow. Never 0.
188      */
189     const Filter *mFilter = nullptr;
190 
191     /**
192      * The timer involved in breaking the "fill" operation in steps
193      */
194     QTimer mFillStepTimer;
195 
196     /**
197      * Group Key (usually the label) -> GroupHeaderItem, used to quickly find groups, pointers are shallow copies
198      */
199     QHash<QString, GroupHeaderItem *> mGroupHeaderItemHash;
200 
201     /**
202      * Threading cache.
203      * MessageIdMD5 -> MessageItem, pointers are shallow copies
204      */
205     QHash<QByteArray, MessageItem *> mThreadingCacheMessageIdMD5ToMessageItem;
206 
207     /**
208      * Threading cache.
209      * MessageInReplyToIdMD5 -> MessageItem, pointers are shallow copies
210      */
211     QMultiHash<QByteArray, MessageItem *> mThreadingCacheMessageInReplyToIdMD5ToMessageItem;
212 
213     /**
214      * Threading cache.
215      * ReferencesIdMD5 -> MessageItem, pointers are shallow copies
216      */
217     QHash<QByteArray, QList<MessageItem *> *> mThreadingCacheMessageReferencesIdMD5ToMessageItem;
218 
219     /**
220      * Threading cache.
221      * SubjectMD5 -> MessageItem, pointers are shallow copies
222      */
223     QHash<QByteArray, QList<MessageItem *> *> mThreadingCacheMessageSubjectMD5ToMessageItem;
224 
225     /**
226      * List of group headers that either need to be re-sorted or must be removed because empty
227      */
228     QHash<GroupHeaderItem *, GroupHeaderItem *> mGroupHeadersThatNeedUpdate;
229 
230     /**
231      * List of unassigned messages, used to handle threading in two passes, pointers are owned!
232      */
233     QList<MessageItem *> mUnassignedMessageListForPass2;
234 
235     /**
236      * List of unassigned messages, used to handle threading in two passes, pointers are owned!
237      */
238     QList<MessageItem *> mUnassignedMessageListForPass3;
239 
240     /**
241      * List of unassigned messages, used to handle threading in two passes, pointers are owned!
242      */
243     QList<MessageItem *> mUnassignedMessageListForPass4;
244 
245     /**
246      * Hash of orphan children used in Pass1Cleanup.
247      */
248     QHash<MessageItem *, MessageItem *> mOrphanChildrenHash;
249 
250     /**
251      * Pending fill view jobs, pointers are owned
252      */
253     QList<ViewItemJob *> mViewItemJobs;
254 
255     /**
256      * The today's date. Set when the StorageModel is set and thus grouping is performed.
257      * This is used to put the today's messages in the "Today" group, for instance.
258      */
259     QDate mTodayDate;
260 
261     /**
262      * Owned invisible root item, useful to implement algorithms that not need
263      * to handle the special case of parentless items. This is never 0.
264      */
265     Item *mRootItem = nullptr;
266 
267     /**
268      * The view we're attached to. Shallow pointer (the View owns us).
269      */
270     View *mView = nullptr;
271 
272     /**
273      * The time at the current ViewItemJob step started. Used to compute the time we
274      * spent inside this step and eventually jump out on timeout.
275      */
276     time_t mViewItemJobStepStartTime;
277 
278     /**
279      * The timeout for a single ViewItemJob step
280      */
281     int mViewItemJobStepChunkTimeout;
282 
283     /**
284      * The idle time between two ViewItemJob steps
285      */
286     int mViewItemJobStepIdleInterval;
287 
288     /**
289      * The number of messages we process at once in a ViewItemJob step without
290      * checking the timeouts above.
291      */
292     int mViewItemJobStepMessageCheckCount;
293 
294     /**
295      * Our mighty ModelInvariantRowMapper: used to workaround an
296      * issue related to the Model/View architecture.
297      *
298      * \sa ModelInvariantRowMapper
299      */
300     ModelInvariantRowMapper *mInvariantRowMapper = nullptr;
301 
302     /**
303      * The label for the "Today" group item, cached, so we don't translate it multiple times.
304      */
305     QString mCachedTodayLabel;
306 
307     /**
308      * The label for the "Yesterday" group item, cached, so we don't translate it multiple times.
309      */
310     QString mCachedYesterdayLabel;
311 
312     /**
313      * The label for the "Unknown" group item, cached, so we don't translate it multiple times.
314      */
315     QString mCachedUnknownLabel;
316 
317     /**
318      * The label for the "Last Week" group item, cached, so we don't translate it multiple times.
319      */
320     QString mCachedLastWeekLabel;
321 
322     /**
323      * The label for the "Two Weeks Ago" group item, cached, so we don't translate it multiple times.
324      */
325     QString mCachedTwoWeeksAgoLabel;
326 
327     /**
328      * The label for the "Three Weeks Ago" group item, cached, so we don't translate it multiple times.
329      */
330     QString mCachedThreeWeeksAgoLabel;
331 
332     /**
333      * The label for the "Four Weeks Ago" group item, cached, so we don't translate it multiple times.
334      */
335     QString mCachedFourWeeksAgoLabel;
336 
337     /**
338      * The label for the "Five Weeks Ago" group item, cached, so we don't translate it multiple times.
339      */
340     QString mCachedFiveWeeksAgoLabel;
341 
342     /**
343      * Cached bits that we use for fast status checks
344      */
345     qint32 mCachedWatchedOrIgnoredStatusBits;
346 
347     /**
348      * The labels for week days names group items, cached, so we don't query QLocale multiple times.
349      */
350     QMap<int, QString> mCachedDayNameLabel;
351 
352     /*
353      * The labels for month names group items, cached, so we don't query QLocale multiple times.
354      */
355     QMap<int, QString> mCachedMonthNameLabel;
356 
357     /**
358      * Flag signaling a possibly long job batch. This is checked by other
359      * classes and used to display some kind of "please wait" feedback to the user.
360      */
361     bool mInLengthyJobBatch;
362 
363     /**
364      * We need to save the current item before each job step. This is because
365      * our job may cause items to be reparented (thus removed and readded with the current Qt API)
366      * and QTreeView will loose the current setting. We also use this to force the current
367      * to a specific item after a cleanup job.
368      */
369     Item *mCurrentItemToRestoreAfterViewItemJobStep = nullptr;
370 
371     /**
372      * Set to true in the first large loading job.
373      * Reset to false when the job finishes.
374      *
375      * Please note that this is NOT set for later jobs: only for the first (possibly huge) one.
376      */
377     bool mLoading;
378 
379     /**
380      * Pre-selection is the action of automatically selecting a message just after the folder
381      * has finished loading. We may want to select the message that was selected the last
382      * time this folder has been open, or we may want to select the first unread message.
383      * We also may want to do no pre-selection at all (for example, when the user
384      * starts navigating the view before the pre-selection could actually be made
385      * and pre-selecting would confuse him). This member holds the option.
386      *
387      * See also setStorageModel() and abortMessagePreSelection()
388      */
389     PreSelectionMode mPreSelectionMode;
390 
391     // Oldest and newest item while loading the model
392     // Not valid afterwards anymore. Used for pre-selection of the newest/oldest message
393     MessageItem *mOldestItem = nullptr;
394     MessageItem *mNewestItem = nullptr;
395 
396     /**
397      * The id of the preselected ;essage is "translated" to a message pointer when it's fetched
398      * from the storage. This message is then selected when it becomes viewable
399      * (so at the end of the job). 0 if we have no message to select.
400      *
401      * See also setStorageModel() and abortMessagePreSelection()
402      */
403     MessageItem *mLastSelectedMessageInFolder = nullptr;
404 
405     /**
406      * The "persistent message item sets" are (guess what?) sets of messages
407      * that can be referenced globally via a persistent id. The MessageItemSetManager
408      * and this class keep the persistent sets coherent: messages that are deleted
409      * are automatically removed from all the sets.
410      *
411      * Users of this class typically create persistent sets when they start
412      * an asynchronous job and they query them back on the way or when the job is terminated.
413      *
414      * So mPersistentSetManager is in fact the manager for the outstanding "user" jobs.
415      * 0 if no jobs are pending (so there are no persistent sets at the moment).
416      */
417     MessageItemSetManager *mPersistentSetManager = nullptr;
418 
419     /**
420      * This pointer is passed to the Item functions that insert children.
421      * When we work with disconnected UI this pointer becomes 0.
422      */
423     Model *mModelForItemFunctions = nullptr;
424 
425     /**
426      * The cached result of StorageModel::containsOutboundMessages().
427      * We access this property at each incoming message and StorageModel::containsOutboundMessages() is
428      * virtual (so it's always an indirect function call). Caching makes sense.
429      */
430     bool mStorageModelContainsOutboundMessages;
431 
432     /**
433      * Vector of signal-slot connections between StorageModel and us
434      */
435     QVector<QMetaObject::Connection> mStorageModelConnections;
436 
437     /**
438      * Caches child - parent relation based on Akonadi ID and persists the cache
439      * in a file for each Collection. This allows for very fast reconstruction of
440      * threading.
441      */
442     ThreadingCache mThreadingCache;
443 };
444 } // namespace Core
445 } // namespace MessageList
446 
447