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 */