File indexing completed on 2024-11-24 04:53:15
0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net> 0002 0003 This file is part of the Trojita Qt IMAP e-mail client, 0004 http://trojita.flaska.net/ 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License as 0008 published by the Free Software Foundation; either version 2 of 0009 the License or (at your option) version 3 or any later version 0010 accepted by the membership of KDE e.V. (or its successor approved 0011 by the membership of KDE e.V.), which shall act as a proxy 0012 defined in Section 14 of version 3 of the license. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #ifndef IMAP_THREADINGMSGLISTMODEL_H 0024 #define IMAP_THREADINGMSGLISTMODEL_H 0025 0026 #include <functional> 0027 #include <QAbstractProxyModel> 0028 #include <QPointer> 0029 #include <QSet> 0030 #include "MailboxTree.h" 0031 #include "Imap/Parser/Response.h" 0032 0033 class QTimer; 0034 class ImapModelThreadingTest; 0035 0036 /** @short Namespace for IMAP interaction */ 0037 namespace Imap 0038 { 0039 0040 /** @short Classes for handling of mailboxes and connections */ 0041 namespace Mailbox 0042 { 0043 0044 class SortTask; 0045 class TreeItem; 0046 class TreeItemMsgList; 0047 0048 /** @short A node in tree structure used for threading representation */ 0049 struct ThreadNodeInfo { 0050 /** @short Internal unique identifier used for model indexes */ 0051 uint internalId; 0052 /** @short A UID of the message in a mailbox */ 0053 uint uid; 0054 /** @short internalId of a parent of this message */ 0055 uint parent; 0056 /** @short List of children of current node */ 0057 QList<uint> children; 0058 /** @short Pointer to the TreeItemMessage* of the corresponding message */ 0059 TreeItem *ptr; 0060 /** @short Position among our parent's children */ 0061 int offset; 0062 ThreadNodeInfo(): internalId(0), uid(0), parent(0), ptr(0), offset(0) {} 0063 }; 0064 0065 QDebug operator<<(QDebug debug, const ThreadNodeInfo &node); 0066 0067 /** @short A model implementing view of the whole IMAP server 0068 0069 The problem with threading is that due to the extremely asynchronous nature of the IMAP Model, we often get informed about indexes 0070 to messages which "just arrived", and therefore do not have even their UID available. That sucks, because we have to somehow handle 0071 them. Situation gets a bit more complicated by the initial syncing -- this ThreadingMsgListModel can't tell whether the rowsInserted() 0072 signals mean that the underlying model is getting populated, or whether it's a sign of a just-arrived message. On a plus side, the Model 0073 guarantees that the only occurrence when a message could have UID 0 is when the mailbox has been synced previously, and the message is a new 0074 arrival. In all other contexts (that is, during the mailbox re-synchronization), there is a hard guarantee that the UID of any message 0075 available via the MVC API will always be non-zero. 0076 0077 The model should also refrain from sending extra THREAD commands to the server, and cache the responses locally. This is pretty easy for 0078 message deletions, as it should be only a matter of replacing some node in the threading info with a fake ThreadNodeInfo node and running 0079 the pruneTree() method, except that we might not know the UID of the message in question, and hence can't know what to delete. 0080 0081 */ 0082 class ThreadingMsgListModel: public QAbstractProxyModel 0083 { 0084 Q_OBJECT 0085 Q_ENUMS(SortCriterium) 0086 0087 public: 0088 0089 /** @short On which column to sort 0090 0091 The possible columns are described in RFC 5256, section 3. No support for multiple columns is present. 0092 0093 Trojitá will automatically upgrade to the display-based search criteria from RFC 5957 if support for that RFC is indicated by 0094 the server. 0095 */ 0096 typedef enum { 0097 /** @short Don't do any explicit sorting 0098 0099 If threading is not active, the order of messages represnets the order in which they appear in the IMAP mailbox. 0100 In case the display is threaded already, the order depends on the threading algorithm. 0101 */ 0102 SORT_NONE, 0103 0104 /** @short RFC5256's ARRIVAL key, ie. the INTERNALDATE */ 0105 SORT_ARRIVAL, 0106 0107 /** @short The Cc field from the IMAP ENVELOPE */ 0108 SORT_CC, 0109 0110 /** @short Timestamp when the message was created, if available */ 0111 SORT_DATE, 0112 0113 /** @short Either the display name or the mailbox of the "sender" of a message from the "From" header */ 0114 SORT_FROM, 0115 0116 /** @short Size of the message */ 0117 SORT_SIZE, 0118 0119 /** @short The subject of the e-mail */ 0120 SORT_SUBJECT, 0121 0122 /** @short Recipient of the message, either their mailbox or their display name */ 0123 SORT_TO 0124 } SortCriterium; 0125 0126 explicit ThreadingMsgListModel(QObject *parent); 0127 virtual void setSourceModel(QAbstractItemModel *sourceModel); 0128 0129 virtual QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const; 0130 virtual QModelIndex parent(const QModelIndex &index) const; 0131 virtual int rowCount(const QModelIndex &parent=QModelIndex()) const; 0132 virtual int columnCount(const QModelIndex &parent=QModelIndex()) const; 0133 virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const; 0134 virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; 0135 virtual bool hasChildren(const QModelIndex &parent=QModelIndex()) const; 0136 virtual QVariant data(const QModelIndex &proxyIndex, int role) const; 0137 virtual Qt::ItemFlags flags(const QModelIndex &index) const; 0138 QVariant headerData(int section, Qt::Orientation orientation, int role) const; 0139 // Qt5 reimplements sibling() within the proxy models, and the default implementation constitutes 0140 // a behavior change compared to Qt4. 0141 virtual QModelIndex sibling(int row, int column, const QModelIndex &idx) const; 0142 0143 virtual QStringList mimeTypes() const; 0144 virtual QMimeData *mimeData(const QModelIndexList &indexes) const; 0145 0146 /** @short List of capabilities which could be used for threading 0147 0148 If any of them are present in server's capabilities, at least some level of threading will be possible. 0149 */ 0150 static QStringList supportedCapabilities(); 0151 0152 QStringList currentSearchCondition() const; 0153 SortCriterium currentSortCriterium() const; 0154 Q_INVOKABLE Qt::SortOrder currentSortOrder() const; 0155 0156 public slots: 0157 void resetMe(); 0158 void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); 0159 void handleRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); 0160 void handleRowsRemoved(const QModelIndex &parent, int start, int end); 0161 void handleRowsAboutToBeInserted(const QModelIndex &parent, int start, int end); 0162 void handleRowsInserted(const QModelIndex &parent, int start, int end); 0163 /** @short Feed this with the data from a THREAD response */ 0164 void slotThreadingAvailable(const QModelIndex &mailbox, const QByteArray &algorithm, const QStringList &searchCriteria, 0165 const QVector<Imap::Responses::ThreadingNode> &mapping); 0166 void slotThreadingFailed(const QModelIndex &mailbox, const QByteArray &algorithm, const QStringList &searchCriteria); 0167 /** @short Really apply threading to this model */ 0168 void applyThreading(const QVector<Imap::Responses::ThreadingNode> &mapping); 0169 0170 /** @short SORT response has arrived */ 0171 void slotSortingAvailable(const Imap::Uids &uids); 0172 0173 /** @short SORT has failed */ 0174 void slotSortingFailed(); 0175 0176 /** @short Dynamic update to the current SORT order */ 0177 void slotSortingIncrementalUpdate(const Imap::Responses::ESearch::IncrementalContextData_t &updates); 0178 0179 void applySort(); 0180 0181 /** @short Enable or disable threading */ 0182 void setUserWantsThreading(bool enable); 0183 0184 Q_INVOKABLE bool setUserSearchingSortingPreference(const QStringList &searchConditions, const SortCriterium criterium, 0185 const Qt::SortOrder order = Qt::AscendingOrder); 0186 0187 void slotIncrementalThreadingAvailable(const Responses::ESearch::IncrementalThreadingData_t &data); 0188 void slotIncrementalThreadingFailed(); 0189 0190 void delayedPrune(); 0191 0192 signals: 0193 void sortingFailed(); 0194 0195 private: 0196 /** @short Display messages without any threading at all, as a liner list */ 0197 void updateNoThreading(); 0198 0199 /** @short Ask the model for a THREAD response 0200 0201 If the firstUnknownUid is different than zero, an incremental response is requested. 0202 */ 0203 void askForThreading(const uint firstUnknownUid = 0); 0204 0205 void updatePersistentIndexesPhase1(); 0206 void updatePersistentIndexesPhase2(); 0207 0208 /** @short Shall we ask for SORT/SEARCH automatically? */ 0209 typedef enum { 0210 AUTO_SORT_SEARCH, 0211 SKIP_SORT_SEARCH 0212 } SkipSortSearch; 0213 0214 /** @short Apply cached THREAD response or ask for threading again */ 0215 void wantThreading(const SkipSortSearch skipSortSearch = AUTO_SORT_SEARCH); 0216 0217 /** @short Convert the threading from a THREAD response and apply that threading to this model */ 0218 void registerThreading(const QVector<Imap::Responses::ThreadingNode> &mapping, uint parentId, 0219 const QHash<uint,void *> &uidToPtr, QSet<uint> &usedNodes); 0220 0221 bool searchSortPreferenceImplementation(const QStringList &searchConditions, const SortCriterium criterium, 0222 const Qt::SortOrder order = Qt::AscendingOrder); 0223 0224 /** @short Remove fake messages from the threading tree */ 0225 void pruneTree(); 0226 0227 /** @short Execute the provided function once for each message */ 0228 template<typename T> void threadForeach(const uint &root, std::function<T(const TreeItemMessage &)> callback) const; 0229 0230 /** @short Check current thread for "unread messages" */ 0231 bool threadContainsUnreadMessages(const uint root) const; 0232 0233 /** @short Return aggregated flags from the thread */ 0234 QStringList threadAggregatedFlags(const uint root) const; 0235 0236 /** @short Is this someone else's THREAD response? */ 0237 bool shouldIgnoreThisThreadingResponse(const QModelIndex &mailbox, const QByteArray &algorithm, 0238 const QStringList &searchCriteria, const Model **realModel=0); 0239 0240 /** @short Return some number from the thread mapping @arg mapping which is either the highest among them, or at least as high as the marker*/ 0241 static uint findHighEnoughNumber(const QVector<Imap::Responses::ThreadingNode> &mapping, uint marker); 0242 0243 void calculateNullSort(); 0244 0245 uint findHighestUidInMailbox(TreeItemMsgList *list); 0246 0247 void logTrace(const QString &message); 0248 0249 0250 ThreadingMsgListModel &operator=(const ThreadingMsgListModel &); // don't implement 0251 ThreadingMsgListModel(const ThreadingMsgListModel &); // don't implement 0252 0253 /** @short Mapping from the upstream model's internalId to ThreadingMsgListModel's internal IDs */ 0254 QHash<void *,uint> ptrToInternal; 0255 0256 /** @short Tree for the threading 0257 0258 This tree is indexed by our internal ID. 0259 */ 0260 QHash<uint,ThreadNodeInfo> threading; 0261 0262 /** @short Last assigned internal ID */ 0263 uint threadingHelperLastId; 0264 0265 /** @short Messages with unknown UIDs */ 0266 QSet<TreeItem*> unknownUids; 0267 0268 /** @short Threading algorithm we're using for this request */ 0269 QByteArray requestedAlgorithm; 0270 0271 /** @short Recursion guard for "is the model currently being reset?" 0272 0273 We can't be sure what happens when we call rowCount() from updateNoThreading(). It is 0274 possible that the rowCount() would propagate to Model's askForMessagesInMailbox(), 0275 which could in turn call beginInsertRows, leading to a possible recursion. 0276 */ 0277 bool modelResetInProgress; 0278 0279 QModelIndexList oldPersistentIndexes; 0280 QList<void *> oldPtrs; 0281 0282 /** @short There's a pending THREAD command for which we haven't received data yet */ 0283 bool threadingInFlight; 0284 0285 /** @short Is threading enabled, or shall we just use other features like sorting and filtering? */ 0286 bool m_shallBeThreading; 0287 0288 /** @short Are we filtering the mailbox by search? */ 0289 bool m_filteredBySearch; 0290 0291 /** @short Task handling the SORT command */ 0292 QPointer<SortTask> m_sortTask; 0293 0294 /** @short Shall we sort in a reversed order? */ 0295 bool m_sortReverse; 0296 0297 /** @short IDs of all thread roots when no sorting or filtering is applied */ 0298 QList<uint> threadedRootIds; 0299 0300 /** @short Sorting criteria of the current copy of the sort result */ 0301 SortCriterium m_currentSortingCriteria; 0302 0303 /** @short Search criteria of the current copy of the search/sort result */ 0304 QStringList m_currentSearchConditions; 0305 0306 /** @short The current result of the SORT operation 0307 0308 This variable holds the UIDs of all messages in this mailbox, sorted according to the current sorting criteria. 0309 */ 0310 Imap::Uids m_currentSortResult; 0311 0312 /** @short Is the cached result of SEARCH/SORT fresh enough? */ 0313 typedef enum { 0314 RESULT_ASKED, /**< We've asked for the data */ 0315 RESULT_FRESH, /**< The response has just arrived and didn't get invalidated since then */ 0316 RESULT_INVALIDATED /**< A new message has arrived, rendering our copy invalid */ 0317 } ResultValidity; 0318 0319 ResultValidity m_searchValidity; 0320 0321 QTimer *m_delayedPrune; 0322 0323 friend class ::ImapModelThreadingTest; // needs access to wantThreading(); 0324 }; 0325 0326 } 0327 0328 } 0329 0330 #endif /* IMAP_THREADINGMSGLISTMODEL_H */