File indexing completed on 2024-11-24 04:53:10

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 #include <algorithm>
0024 #include <QTextStream>
0025 #include "Common/FindWithUnknown.h"
0026 #include "Common/InvokeMethod.h"
0027 #include "Common/MetaTypes.h"
0028 #include "Imap/Encoders.h"
0029 #include "Imap/Parser/Rfc5322HeaderParser.h"
0030 #include "Imap/Tasks/KeepMailboxOpenTask.h"
0031 #include "UiUtils/Formatting.h"
0032 #include "ItemRoles.h"
0033 #include "MailboxTree.h"
0034 #include "Model.h"
0035 #include "SpecialFlagNames.h"
0036 #include <QtDebug>
0037 
0038 
0039 namespace Imap
0040 {
0041 namespace Mailbox
0042 {
0043 
0044 TreeItem::TreeItem(TreeItem *parent): m_parent(parent)
0045 {
0046     // These just have to be present in the context of TreeItem, otherwise they couldn't access the protected members
0047     static_assert(static_cast<intptr_t>(alignof(TreeItem&)) > TreeItem::TagMask,
0048                   "class TreeItem must be aligned at at least four bytes due to the FetchingState optimization");
0049     static_assert(DONE <= TagMask, "Invalid masking for pointer tag access");
0050 }
0051 
0052 TreeItem::~TreeItem()
0053 {
0054     qDeleteAll(m_children);
0055 }
0056 
0057 unsigned int TreeItem::childrenCount(Model *const model)
0058 {
0059     fetch(model);
0060     return m_children.size();
0061 }
0062 
0063 TreeItem *TreeItem::child(int offset, Model *const model)
0064 {
0065     fetch(model);
0066     if (offset >= 0 && offset < m_children.size())
0067         return m_children[ offset ];
0068     else
0069         return 0;
0070 }
0071 
0072 int TreeItem::row() const
0073 {
0074     return parent() ? parent()->m_children.indexOf(const_cast<TreeItem *>(this)) : 0;
0075 }
0076 
0077 TreeItemChildrenList TreeItem::setChildren(const TreeItemChildrenList &items)
0078 {
0079     auto res = m_children;
0080     m_children = items;
0081     setFetchStatus(DONE);
0082     return res;
0083 }
0084 
0085 bool TreeItem::isUnavailable() const
0086 {
0087     return accessFetchStatus() == UNAVAILABLE;
0088 }
0089 
0090 unsigned int TreeItem::columnCount()
0091 {
0092     return 1;
0093 }
0094 
0095 TreeItem *TreeItem::specialColumnPtr(int row, int column) const
0096 {
0097     Q_UNUSED(row);
0098     Q_UNUSED(column);
0099     return 0;
0100 }
0101 
0102 QModelIndex TreeItem::toIndex(Model *const model) const
0103 {
0104     Q_ASSERT(model);
0105     if (this == model->m_mailboxes)
0106         return QModelIndex();
0107     // void* != const void*, but I believe that it's safe in this context
0108     return model->createIndex(row(), 0, const_cast<TreeItem *>(this));
0109 }
0110 
0111 
0112 TreeItemMailbox::TreeItemMailbox(TreeItem *parent): TreeItem(parent), maintainingTask(0)
0113 {
0114     m_children.prepend(new TreeItemMsgList(this));
0115 }
0116 
0117 TreeItemMailbox::TreeItemMailbox(TreeItem *parent, Responses::List response):
0118     TreeItem(parent), m_metadata(response.mailbox, response.separator, QStringList()), maintainingTask(0)
0119 {
0120     for (QStringList::const_iterator it = response.flags.constBegin(); it != response.flags.constEnd(); ++it)
0121         m_metadata.flags.append(it->toUpper());
0122     m_children.prepend(new TreeItemMsgList(this));
0123 }
0124 
0125 TreeItemMailbox::~TreeItemMailbox()
0126 {
0127     if (maintainingTask) {
0128         maintainingTask->dieIfInvalidMailbox();
0129     }
0130 }
0131 
0132 TreeItemMailbox *TreeItemMailbox::fromMetadata(TreeItem *parent, const MailboxMetadata &metadata)
0133 {
0134     TreeItemMailbox *res = new TreeItemMailbox(parent);
0135     res->m_metadata = metadata;
0136     return res;
0137 }
0138 
0139 void TreeItemMailbox::fetch(Model *const model)
0140 {
0141     fetchWithCacheControl(model, false);
0142 }
0143 
0144 void TreeItemMailbox::fetchWithCacheControl(Model *const model, bool forceReload)
0145 {
0146     if (fetched() || isUnavailable())
0147         return;
0148 
0149     if (hasNoChildMailboxesAlreadyKnown()) {
0150         setFetchStatus(DONE);
0151         return;
0152     }
0153 
0154     if (! loading()) {
0155         setFetchStatus(LOADING);
0156         QModelIndex mailboxIndex = toIndex(model);
0157         CALL_LATER(model, askForChildrenOfMailbox, Q_ARG(QModelIndex, mailboxIndex),
0158                    Q_ARG(Imap::Mailbox::CacheLoadingMode, forceReload ? LOAD_FORCE_RELOAD : LOAD_CACHED_IS_OK));
0159     }
0160 }
0161 
0162 void TreeItemMailbox::rescanForChildMailboxes(Model *const model)
0163 {
0164     if (accessFetchStatus() != LOADING) {
0165         setFetchStatus(NONE);
0166         fetchWithCacheControl(model, true);
0167     }
0168 }
0169 
0170 unsigned int TreeItemMailbox::rowCount(Model *const model)
0171 {
0172     fetch(model);
0173     return m_children.size();
0174 }
0175 
0176 QVariant TreeItemMailbox::data(Model *const model, int role)
0177 {
0178     switch (role) {
0179     case RoleIsFetched:
0180         return fetched();
0181     case RoleIsUnavailable:
0182         return isUnavailable();
0183     };
0184 
0185     if (!parent())
0186         return QVariant();
0187 
0188     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[0]);
0189     Q_ASSERT(list);
0190 
0191     switch (role) {
0192     case Qt::DisplayRole:
0193     {
0194         // this one is used only for a dumb view attached to the Model
0195         QString res = separator().isEmpty() ? mailbox() : mailbox().split(separator(), Qt::SkipEmptyParts).last();
0196         return loading() ? res + QLatin1String(" [loading]") : res;
0197     }
0198     case RoleShortMailboxName:
0199         return separator().isEmpty() ? mailbox() : mailbox().split(separator(), Qt::SkipEmptyParts).last();
0200     case RoleMailboxName:
0201         return mailbox();
0202     case RoleMailboxSeparator:
0203         return separator();
0204     case RoleMailboxHasChildMailboxes:
0205         return hasChildMailboxes(model);
0206     case RoleMailboxIsINBOX:
0207         return mailbox().toUpper() == QLatin1String("INBOX");
0208     case RoleMailboxIsSelectable:
0209         return isSelectable();
0210     case RoleMailboxNumbersFetched:
0211         return list->numbersFetched();
0212     case RoleTotalMessageCount:
0213     {
0214         if (! isSelectable())
0215             return QVariant();
0216         // At first, register that request for count
0217         int res = list->totalMessageCount(model);
0218         // ...and now that it's been sent, display a number if it's available, or if it was available before.
0219         // It's better to return a value which is maybe already obsolete than to hide a value which might
0220         // very well be still correct.
0221         return list->numbersFetched() || res != -1 ? QVariant(res) : QVariant();
0222     }
0223     case RoleUnreadMessageCount:
0224     {
0225         if (! isSelectable())
0226             return QVariant();
0227         // This one is similar to the case of RoleTotalMessageCount
0228         int res = list->unreadMessageCount(model);
0229         return list->numbersFetched() || res != -1 ? QVariant(res): QVariant();
0230     }
0231     case RoleRecentMessageCount:
0232     {
0233         if (! isSelectable())
0234             return QVariant();
0235         // see above
0236         int res = list->recentMessageCount(model);
0237         return list->numbersFetched() || res != -1 ? QVariant(res): QVariant();
0238     }
0239     case RoleMailboxItemsAreLoading:
0240         return list->loading() || (isSelectable() && ! list->numbersFetched());
0241     case RoleMailboxUidValidity:
0242     {
0243         list->fetch(model);
0244         return list->fetched() ? QVariant(syncState.uidValidity()) : QVariant();
0245     }
0246     case RoleMailboxIsSubscribed:
0247         return QVariant::fromValue<bool>(m_metadata.flags.contains(QStringLiteral("\\SUBSCRIBED")));
0248     default:
0249         return QVariant();
0250     }
0251 }
0252 
0253 bool TreeItemMailbox::hasChildren(Model *const model)
0254 {
0255     Q_UNUSED(model);
0256     return true; // we have that "messages" thing built in
0257 }
0258 
0259 QLatin1String TreeItemMailbox::flagNoInferiors("\\NOINFERIORS");
0260 QLatin1String TreeItemMailbox::flagHasNoChildren("\\HASNOCHILDREN");
0261 QLatin1String TreeItemMailbox::flagHasChildren("\\HASCHILDREN");
0262 
0263 bool TreeItemMailbox::hasNoChildMailboxesAlreadyKnown()
0264 {
0265     if (m_metadata.flags.contains(flagNoInferiors) ||
0266         (m_metadata.flags.contains(flagHasNoChildren) &&
0267          ! m_metadata.flags.contains(flagHasChildren)))
0268         return true;
0269     else
0270         return false;
0271 }
0272 
0273 bool TreeItemMailbox::hasChildMailboxes(Model *const model)
0274 {
0275     if (fetched() || isUnavailable()) {
0276         return m_children.size() > 1;
0277     } else if (hasNoChildMailboxesAlreadyKnown()) {
0278         return false;
0279     } else if (m_metadata.flags.contains(flagHasChildren) && ! m_metadata.flags.contains(flagHasNoChildren)) {
0280         return true;
0281     } else {
0282         fetch(model);
0283         return m_children.size() > 1;
0284     }
0285 }
0286 
0287 TreeItem *TreeItemMailbox::child(const int offset, Model *const model)
0288 {
0289     // accessing TreeItemMsgList doesn't need fetch()
0290     if (offset == 0)
0291         return m_children[ 0 ];
0292 
0293     return TreeItem::child(offset, model);
0294 }
0295 
0296 TreeItemChildrenList TreeItemMailbox::setChildren(const TreeItemChildrenList &items)
0297 {
0298     // This function has to be special because we want to preserve m_children[0]
0299 
0300     TreeItemMsgList *msgList = dynamic_cast<TreeItemMsgList *>(m_children[0]);
0301     Q_ASSERT(msgList);
0302     m_children.erase(m_children.begin());
0303 
0304     auto list = TreeItem::setChildren(items);  // this also adjusts m_loading and m_fetched
0305     m_children.prepend(msgList);
0306 
0307     return list;
0308 }
0309 
0310 void TreeItemMailbox::handleFetchResponse(Model *const model,
0311         const Responses::Fetch &response,
0312         QList<TreeItemPart *> &changedParts,
0313         TreeItemMessage *&changedMessage, bool usingQresync)
0314 {
0315     TreeItemMsgList *list = static_cast<TreeItemMsgList *>(m_children[0]);
0316 
0317     Responses::Fetch::dataType::const_iterator uidRecord = response.data.find("UID");
0318 
0319     // Previously, we would ignore any FETCH responses until we are fully synced. This is rather hard do to "properly",
0320     // though.
0321     // What we want to achieve is to never store data into a "wrong" message. Theoretically, we are prone to just this
0322     // in case the server sends us unsolicited data before we are fully synced. When this happens for flags, it's a pretty
0323     // harmless operation as we're going to re-fetch the flags for the concerned part of mailbox anyway (even with CONDSTORE,
0324     // and this is never an issue with QRESYNC).
0325     // It's worse when the data refer to some immutable piece of information like the bodystructure or body parts.
0326     // If that happens, then we have to actively prevent the data from being stored because we cannot know whether we would
0327     // be putting it into a correct bucket^Hmessage.
0328     bool ignoreImmutableData = !list->fetched() && uidRecord == response.data.constEnd();
0329 
0330     int number = response.number - 1;
0331     if (number < 0 || number >= list->m_children.size())
0332         throw UnknownMessageIndex(QStringLiteral("Got FETCH that is out of bounds -- got %1 messages").arg(
0333                                       QString::number(list->m_children.size())).toUtf8().constData(), response);
0334 
0335     TreeItemMessage *message = static_cast<TreeItemMessage *>(list->child(number, model));
0336 
0337     // At first, have a look at the response and check the UID of the message
0338     if (uidRecord != response.data.constEnd()) {
0339         uint receivedUid = static_cast<const Responses::RespData<uint>&>(*(uidRecord.value())).data;
0340         if (receivedUid == 0) {
0341             throw MailboxException(QStringLiteral("Server claims that message #%1 has UID 0")
0342                                    .arg(QString::number(response.number)).toUtf8().constData(), response);
0343         } else if (message->uid() == receivedUid) {
0344             // That's what we expect -> do nothing
0345         } else if (message->uid() == 0) {
0346             // This is the first time we see the UID, so let's take a note
0347             message->m_uid = receivedUid;
0348             changedMessage = message;
0349             if (message->loading()) {
0350                 // The Model tried to ask for data for this message. That couldn't succeeded because the UID
0351                 // wasn't known at that point, so let's ask now
0352                 //
0353                 // FIXME: tweak this to keep a high watermark of "highest UID we requested an ENVELOPE for",
0354                 // issue bulk fetches in the same manner as we do the UID FETCH (FLAGS) when discovering UIDs,
0355                 // and at this place in code, only ask for the metadata when the UID is higher than the watermark.
0356                 // Optionally, simply ask for the ENVELOPE etc along with the FLAGS upon new message arrivals, maybe
0357                 // with some limit on the number of pending fetches. And make that dapandent on online/expensive modes.
0358                 message->setFetchStatus(NONE);
0359                 message->fetch(model);
0360             }
0361             if (syncState.uidNext() <= receivedUid) {
0362                 // Try to guess the UIDNEXT. We have to take an educated guess here, and I believe that this approach
0363                 // at least is not wrong. The server won't tell us the UIDNEXT (well, it could, but it doesn't have to),
0364                 // the only way of asking for it is via STATUS which is not allowed to reference the current mailbox and
0365                 // even if it was, it wouldn't be atomic. So, what could the UIDNEXT possibly be? It can't be smaller
0366                 // than the UID_of_highest_message, and it can't be the same, either, so it really has to be higher.
0367                 // Let's just increment it by one, this is our lower bound.
0368                 // Not guessing the UIDNEXT correctly would result at decreased performance at the next sync, and we
0369                 // can't really do better -> let's just set it now, along with the UID mapping.
0370                 syncState.setUidNext(receivedUid + 1);
0371                 list->setFetchStatus(LOADING);
0372             }
0373         } else {
0374             throw MailboxException(QStringLiteral("FETCH response: UID consistency error for message #%1 -- expected UID %2, got UID %3").arg(
0375                                        QString::number(response.number), QString::number(message->uid()), QString::number(receivedUid)
0376                                        ).toUtf8().constData(), response);
0377         }
0378     } else if (! message->uid()) {
0379         qDebug() << "FETCH: received a FETCH response for message #" << response.number << "whose UID is not yet known. This sucks.";
0380         QList<uint> uidsInMailbox;
0381         Q_FOREACH(TreeItem *node, list->m_children) {
0382             uidsInMailbox << static_cast<TreeItemMessage *>(node)->uid();
0383         }
0384         qDebug() << "UIDs in the mailbox now: " << uidsInMailbox;
0385     }
0386 
0387     bool updatedFlags = false;
0388 
0389     for (Responses::Fetch::dataType::const_iterator it = response.data.begin(); it != response.data.end(); ++ it) {
0390         if (it.key() == "UID") {
0391             // established above
0392             Q_ASSERT(static_cast<const Responses::RespData<uint>&>(*(it.value())).data == message->uid());
0393         } else if (it.key() == "FLAGS") {
0394             // Only emit signals when the flags have actually changed
0395             QStringList newFlags = model->normalizeFlags(static_cast<const Responses::RespData<QStringList>&>(*(it.value())).data);
0396             bool forceChange = !message->m_flagsHandled || (message->m_flags != newFlags);
0397             message->setFlags(list, newFlags);
0398             if (forceChange) {
0399                 updatedFlags = true;
0400                 changedMessage = message;
0401             }
0402         } else if (it.key() == "MODSEQ") {
0403             quint64 num = static_cast<const Responses::RespData<quint64>&>(*(it.value())).data;
0404             if (num > syncState.highestModSeq()) {
0405                 syncState.setHighestModSeq(num);
0406                 if (list->accessFetchStatus() == DONE) {
0407                     // This means that everything is known already, so we are by definition OK to save stuff to disk.
0408                     // We can also skip rebuilding the UID map and save just the HIGHESTMODSEQ, i.e. the SyncState.
0409                     model->cache()->setMailboxSyncState(mailbox(), syncState);
0410                 } else {
0411                     // it's already marked as dirty -> nothing to do here
0412                 }
0413             }
0414         } else if (ignoreImmutableData) {
0415             QByteArray buf;
0416             QTextStream ss(&buf);
0417             ss << response;
0418             ss.flush();
0419             qDebug() << "Ignoring FETCH response to a mailbox that isn't synced yet:" << buf;
0420             continue;
0421         } else if (it.key() == "ENVELOPE") {
0422             message->data()->setEnvelope(static_cast<const Responses::RespData<Message::Envelope>&>(*(it.value())).data);
0423             changedMessage = message;
0424         } else if (it.key() == "BODYSTRUCTURE") {
0425             if (message->data()->gotRemeberedBodyStructure() || message->fetched()) {
0426                 // The message structure is already known, so we are free to ignore it
0427             } else {
0428                 // We had no idea about the structure of the message
0429 
0430                 // At first, save the bodystructure. This is needed so that our overridden rowCount() works properly.
0431                 // (The rowCount() gets called through QAIM::beginInsertRows(), for example.)
0432                 auto xtbIt = response.data.constFind("x-trojita-bodystructure");
0433                 Q_ASSERT(xtbIt != response.data.constEnd());
0434                 message->data()->setRememberedBodyStructure(
0435                         static_cast<const Responses::RespData<QByteArray>&>(*(xtbIt.value())).data);
0436 
0437                 // Now insert the children. We're of course assuming that the TreeItemMessage is now empty.
0438                 auto newChildren = static_cast<const Message::AbstractMessage &>(*(it.value())).createTreeItems(message);
0439                 Q_ASSERT(!newChildren.isEmpty());
0440                 Q_ASSERT(message->m_children.isEmpty());
0441                 QModelIndex messageIdx = message->toIndex(model);
0442                 model->beginInsertRows(messageIdx, 0, newChildren.size() - 1);
0443                 message->setChildren(newChildren);
0444                 model->endInsertRows();
0445             }
0446         } else if (it.key() == "x-trojita-bodystructure") {
0447             // do nothing here, it's been already taken care of from the BODYSTRUCTURE handler
0448         } else if (it.key() == "RFC822.SIZE") {
0449             message->data()->setSize(static_cast<const Responses::RespData<quint64>&>(*(it.value())).data);
0450         } else if (it.key().startsWith("BODY[HEADER.FIELDS (")) {
0451             // Process any headers found in any such response bit
0452             const QByteArray &rawHeaders = static_cast<const Responses::RespData<QByteArray>&>(*(it.value())).data;
0453             message->processAdditionalHeaders(model, rawHeaders);
0454             changedMessage = message;
0455         } else if (it.key().startsWith("BODY[") || it.key().startsWith("BINARY[")) {
0456             if (it.key()[ it.key().size() - 1 ] != ']')
0457                 throw UnknownMessageIndex("Can't parse such BODY[]/BINARY[]", response);
0458             TreeItemPart *part = partIdToPtr(model, message, it.key());
0459             if (! part)
0460                 throw UnknownMessageIndex("Got BODY[]/BINARY[] fetch that did not resolve to any known part", response);
0461             const QByteArray &data = static_cast<const Responses::RespData<QByteArray>&>(*(it.value())).data;
0462             if (it.key().startsWith("BODY[")) {
0463 
0464                 // Check whether we are supposed to be loading the raw, undecoded part as well.
0465                 // The check has to be done via a direct pointer access to m_partRaw to make sure that it does not
0466                 // get instantiated when not actually needed.
0467                 if (part->m_partRaw && part->m_partRaw->loading()) {
0468                     part->m_partRaw->m_data = data;
0469                     part->m_partRaw->setFetchStatus(DONE);
0470                     changedParts.append(part->m_partRaw);
0471                     if (message->uid()) {
0472                         model->cache()->forgetMessagePart(mailbox(), message->uid(), part->partId());
0473                         model->cache()->setMsgPart(mailbox(), message->uid(), part->partId() + ".X-RAW", data);
0474                     }
0475                 }
0476 
0477                 // Do not overwrite the part data if we were not asked to fetch it.
0478                 // One possibility is that it's already there because it was fetched before. The second option is that
0479                 // we were in fact asked to only fetch the raw data and the user is not itnerested in the processed data at all.
0480                 if (part->loading()) {
0481                     // got to decode the part data by hand
0482                     Imap::decodeContentTransferEncoding(data, part->transferEncoding(), part->dataPtr());
0483                     part->setFetchStatus(DONE);
0484                     changedParts.append(part);
0485                     if (message->uid()
0486                             && model->cache()->messagePart(mailbox(), message->uid(), part->partId() + ".X-RAW").isNull()) {
0487                         // Do not store the data into cache if the raw data are already there
0488                         model->cache()->setMsgPart(mailbox(), message->uid(), part->partId(), part->m_data);
0489                     }
0490                 }
0491 
0492             } else {
0493                 // A BINARY FETCH item is already decoded for us, yay
0494                 part->m_data = data;
0495                 part->setFetchStatus(DONE);
0496                 changedParts.append(part);
0497                 if (message->uid()) {
0498                     model->cache()->setMsgPart(mailbox(), message->uid(), part->partId(), part->m_data);
0499                 }
0500             }
0501         } else if (it.key() == "INTERNALDATE") {
0502             message->data()->setInternalDate(static_cast<const Responses::RespData<QDateTime>&>(*(it.value())).data);
0503         } else {
0504             qDebug() << "TreeItemMailbox::handleFetchResponse: unknown FETCH identifier" << it.key();
0505         }
0506     }
0507     if (message->uid()) {
0508         if (message->data()->isComplete() && model->cache()->messageMetadata(mailbox(), message->uid()).uid == 0) {
0509              model->cache()->setMessageMetadata(
0510                          mailbox(), message->uid(),
0511                          Imap::Mailbox::AbstractCache::MessageDataBundle(
0512                              message->uid(),
0513                              message->data()->envelope(),
0514                              message->data()->internalDate(),
0515                              message->data()->size(),
0516                              message->data()->rememberedBodyStructure(),
0517                              message->data()->hdrReferences(),
0518                              message->data()->hdrListPost(),
0519                              message->data()->hdrListPostNo()
0520                          ));
0521              message->setFetchStatus(DONE);
0522         }
0523         if (updatedFlags) {
0524             model->cache()->setMsgFlags(mailbox(), message->uid(), message->m_flags);
0525         }
0526     }
0527 }
0528 
0529 /** @short Save the sync state and the UID mapping into the cache
0530 
0531 Please note that FLAGS are still being updated "asynchronously", i.e. immediately when an update arrives. The motivation
0532 behind this is that both SyncState and UID mapping just absolutely have to be kept in sync due to the way they are used where
0533 our syncing code simply expects both to match each other. There cannot ever be any 0 UIDs in the saved UID mapping, and the
0534 number in EXISTS and the amount of cached UIDs is not supposed to differ or all bets are off.
0535 
0536 The flags, on the other hand, are not critical -- if a message gets saved with the correct flags "too early", i.e. before
0537 the corresponding SyncState and/or UIDs are saved, the wors case which could possibly happen are data which do not match the
0538 old state any longer. But the old state is not important anyway because it's already gone on the server.
0539 */
0540 void TreeItemMailbox::saveSyncStateAndUids(Model * model)
0541 {
0542     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList*>(m_children[0]);
0543     if (list->m_unreadMessageCount != -1) {
0544         syncState.setUnSeenCount(list->m_unreadMessageCount);
0545     }
0546     if (list->m_recentMessageCount != -1) {
0547         syncState.setRecent(list->m_recentMessageCount);
0548     }
0549     model->cache()->setMailboxSyncState(mailbox(), syncState);
0550     model->saveUidMap(list);
0551     list->setFetchStatus(DONE);
0552 }
0553 
0554 /** @short Process the EXPUNGE response when the UIDs are already synced */
0555 void TreeItemMailbox::handleExpunge(Model *const model, const Responses::NumberResponse &resp)
0556 {
0557     Q_ASSERT(resp.kind == Responses::EXPUNGE);
0558     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[ 0 ]);
0559     Q_ASSERT(list);
0560     if (resp.number > static_cast<uint>(list->m_children.size()) || resp.number == 0) {
0561         throw UnknownMessageIndex("EXPUNGE references message number which is out-of-bounds");
0562     }
0563     uint offset = resp.number - 1;
0564 
0565     model->beginRemoveRows(list->toIndex(model), offset, offset);
0566     auto it = list->m_children.begin() + offset;
0567     TreeItemMessage *message = static_cast<TreeItemMessage *>(*it);
0568     list->m_children.erase(it);
0569     model->cache()->clearMessage(static_cast<TreeItemMailbox *>(list->parent())->mailbox(), message->uid());
0570     for (int i = offset; i < list->m_children.size(); ++i) {
0571         --static_cast<TreeItemMessage *>(list->m_children[i])->m_offset;
0572     }
0573     model->endRemoveRows();
0574 
0575     --list->m_totalMessageCount;
0576     list->recalcVariousMessageCountsOnExpunge(const_cast<Model *>(model), message);
0577 
0578     delete message;
0579 
0580     // The UID map is not synced at this time, though, and we defer a decision on when to do this to the context
0581     // of the task which invoked this method. The idea is that this task has a better insight for potentially
0582     // batching these changes to prevent useless hammering of the saveUidMap() etc.
0583     // Previously, the code would simetimes do this twice in a row, which is kinda suboptimal...
0584 }
0585 
0586 void TreeItemMailbox::handleVanished(Model *const model, const Responses::Vanished &resp)
0587 {
0588     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[ 0 ]);
0589     Q_ASSERT(list);
0590     QModelIndex listIndex = list->toIndex(model);
0591 
0592     auto uids = resp.uids;
0593     std::sort(uids.begin(), uids.end());
0594     // Remove duplicates -- even that garbage can be present in a perfectly valid VANISHED :(
0595     uids.erase(std::unique(uids.begin(), uids.end()), uids.end());
0596 
0597     auto it = list->m_children.end();
0598     while (!uids.isEmpty()) {
0599         // We have to process each UID separately because the UIDs in the mailbox are not necessarily present
0600         // in a continuous range; zeros might be present
0601         uint uid = uids.last();
0602         uids.pop_back();
0603 
0604         if (uid == 0) {
0605             qDebug() << "VANISHED informs about removal of UID zero...";
0606             model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"),
0607                             QStringLiteral("VANISHED contains UID zero for increased fun"));
0608             break;
0609         }
0610 
0611         if (list->m_children.isEmpty()) {
0612             // Well, it'd be cool to throw an exception here but VANISHED is free to contain references to UIDs which are not here
0613             // at all...
0614             qDebug() << "VANISHED attempted to remove too many messages";
0615             model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"),
0616                             QStringLiteral("VANISHED attempted to remove too many messages"));
0617             break;
0618         }
0619 
0620         // Find a highest message with UID zero such as no message with non-zero UID higher than the current UID exists
0621         // at a position after the target message
0622         it = model->findMessageOrNextOneByUid(list, uid);
0623 
0624         if (it == list->m_children.end()) {
0625             // this is a legitimate situation, the UID of the last message in the mailbox which is getting expunged right now
0626             // could very well be not know at this point
0627             --it;
0628         }
0629         // there's a special case above guarding against an empty list
0630         Q_ASSERT(it >= list->m_children.begin());
0631 
0632         TreeItemMessage *msgCandidate = static_cast<TreeItemMessage*>(*it);
0633         if (msgCandidate->uid() == uid) {
0634             // will be deleted
0635         } else if (resp.earlier == Responses::Vanished::EARLIER) {
0636             // We don't have any such UID in our UID mapping, so we can safely ignore this one
0637             continue;
0638         } else if (msgCandidate->uid() == 0) {
0639             // will be deleted
0640         } else {
0641             if (it != list->m_children.begin()) {
0642                 --it;
0643                 msgCandidate = static_cast<TreeItemMessage*>(*it);
0644                 if (msgCandidate->uid() == 0) {
0645                     // will be deleted
0646                 } else {
0647                     // VANISHED is free to refer to a non-existing UID...
0648                     QString str;
0649                     QTextStream ss(&str);
0650                     ss << "VANISHED refers to UID " << uid << " which wasn't found in the mailbox (found adjacent UIDs " <<
0651                           msgCandidate->uid() << " and " << static_cast<TreeItemMessage*>(*(it + 1))->uid() << " with " <<
0652                           static_cast<TreeItemMessage*>(*(list->m_children.end() - 1))->uid() << " at the end)";
0653                     ss.flush();
0654                     qDebug() << str.toUtf8().constData();
0655                     model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"), str);
0656                     continue;
0657                 }
0658             } else {
0659                 // Again, VANISHED can refer to non-existing UIDs
0660                 QString str;
0661                 QTextStream ss(&str);
0662                 ss << "VANISHED refers to UID " << uid << " which is too low (lowest UID is " <<
0663                       static_cast<TreeItemMessage*>(list->m_children.front())->uid() << ")";
0664                 ss.flush();
0665                 qDebug() << str.toUtf8().constData();
0666                 model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"), str);
0667                 continue;
0668             }
0669         }
0670 
0671         int row = msgCandidate->row();
0672         Q_ASSERT(row == it - list->m_children.begin());
0673         model->beginRemoveRows(listIndex, row, row);
0674         it = list->m_children.erase(it);
0675         for (auto furtherMessage = it; furtherMessage != list->m_children.end(); ++furtherMessage) {
0676             --static_cast<TreeItemMessage *>(*furtherMessage)->m_offset;
0677         }
0678         model->endRemoveRows();
0679 
0680         if (syncState.uidNext() <= uid) {
0681             // We're informed about a message being deleted; this means that that UID must have been in the mailbox for some
0682             // (possibly tiny) time and we can therefore use it to get an idea about the UIDNEXT
0683             syncState.setUidNext(uid + 1);
0684         }
0685         model->cache()->clearMessage(mailbox(), uid);
0686         delete msgCandidate;
0687     }
0688 
0689     if (resp.earlier == Responses::Vanished::EARLIER && static_cast<uint>(list->m_children.size()) < syncState.exists()) {
0690         // Okay, there were some new arrivals which we failed to take into account because we had processed EXISTS
0691         // before VANISHED (EARLIER). That means that we have to add some of that messages back right now.
0692         int newArrivals = syncState.exists() - list->m_children.size();
0693         Q_ASSERT(newArrivals > 0);
0694         QModelIndex parent = list->toIndex(model);
0695         int offset = list->m_children.size();
0696         model->beginInsertRows(parent, offset, syncState.exists() - 1);
0697         for (int i = 0; i < newArrivals; ++i) {
0698             TreeItemMessage *msg = new TreeItemMessage(list);
0699             msg->m_offset = i + offset;
0700             list->m_children << msg;
0701             // yes, we really have to add this message with UID 0 :(
0702         }
0703         model->endInsertRows();
0704     }
0705 
0706     list->m_totalMessageCount = list->m_children.size();
0707     syncState.setExists(list->m_totalMessageCount);
0708     list->recalcVariousMessageCounts(const_cast<Model *>(model));
0709 
0710     if (list->accessFetchStatus() == DONE) {
0711         // Previously, we were synced, so we got to save this update
0712         saveSyncStateAndUids(model);
0713     }
0714 }
0715 
0716 /** @short Process the EXISTS response
0717 
0718 This function assumes that the mailbox is already synced.
0719 */
0720 void TreeItemMailbox::handleExists(Model *const model, const Responses::NumberResponse &resp)
0721 {
0722     Q_ASSERT(resp.kind == Responses::EXISTS);
0723     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[0]);
0724     Q_ASSERT(list);
0725     // This is a bit tricky -- unfortunately, we can't assume anything about the UID of new arrivals. On the other hand,
0726     // these messages can be referenced by (even unrequested) FETCH responses and deleted by EXPUNGE, so we really want
0727     // to add them to the tree.
0728     int newArrivals = resp.number - list->m_children.size();
0729     if (newArrivals < 0) {
0730         throw UnexpectedResponseReceived("EXISTS response attempted to decrease number of messages", resp);
0731     }
0732     syncState.setExists(resp.number);
0733     if (newArrivals == 0) {
0734         // remains unchanged...
0735         return;
0736     }
0737 
0738     QModelIndex parent = list->toIndex(model);
0739     int offset = list->m_children.size();
0740     model->beginInsertRows(parent, offset, resp.number - 1);
0741     for (int i = 0; i < newArrivals; ++i) {
0742         TreeItemMessage *msg = new TreeItemMessage(list);
0743         msg->m_offset = i + offset;
0744         list->m_children << msg;
0745         // yes, we really have to add this message with UID 0 :(
0746     }
0747     model->endInsertRows();
0748     list->m_totalMessageCount = resp.number;
0749     list->setFetchStatus(LOADING);
0750     model->emitMessageCountChanged(this);
0751 }
0752 
0753 TreeItemPart *TreeItemMailbox::partIdToPtr(Model *const model, TreeItemMessage *message, const QByteArray &msgId)
0754 {
0755     QByteArray partIdentification;
0756     if (msgId.startsWith("BODY[")) {
0757         partIdentification = msgId.mid(5, msgId.size() - 6);
0758     } else if (msgId.startsWith("BODY.PEEK[")) {
0759         partIdentification = msgId.mid(10, msgId.size() - 11);
0760     } else if (msgId.startsWith("BINARY.PEEK[")) {
0761         partIdentification = msgId.mid(12, msgId.size() - 13);
0762     } else if (msgId.startsWith("BINARY[")) {
0763         partIdentification = msgId.mid(7, msgId.size() - 8);
0764     } else {
0765         throw UnknownMessageIndex(QByteArray("Fetch identifier doesn't start with reasonable prefix: " + msgId).constData());
0766     }
0767 
0768     TreeItem *item = message;
0769     Q_ASSERT(item);
0770     QList<QByteArray> separated = partIdentification.split('.');
0771     for (QList<QByteArray>::const_iterator it = separated.constBegin(); it != separated.constEnd(); ++it) {
0772         bool ok;
0773         uint number = it->toUInt(&ok);
0774         if (!ok) {
0775             // It isn't a number, so let's check for that special modifiers
0776             if (it + 1 != separated.constEnd()) {
0777                 // If it isn't at the very end, it's an error
0778                 throw UnknownMessageIndex(QByteArray("Part offset contains non-numeric identifiers in the middle: " + msgId).constData());
0779             }
0780             // Recognize the valid modifiers
0781             if (*it == "HEADER")
0782                 item = item->specialColumnPtr(0, OFFSET_HEADER);
0783             else if (*it == "TEXT")
0784                 item = item->specialColumnPtr(0, OFFSET_TEXT);
0785             else if (*it == "MIME")
0786                 item = item->specialColumnPtr(0, OFFSET_MIME);
0787             else
0788                 throw UnknownMessageIndex(QByteArray("Can't translate received offset of the message part to a number: " + msgId).constData());
0789             break;
0790         }
0791 
0792         // Normal path: descending down and finding the correct part
0793         TreeItemPart *part = dynamic_cast<TreeItemPart *>(item->child(0, model));
0794         if (part && part->isTopLevelMultiPart())
0795             item = part;
0796         item = item->child(number - 1, model);
0797         if (! item) {
0798             throw UnknownMessageIndex(QStringLiteral(
0799                                           "Offset of the message part not found: message %1 (UID %2), current number %3, full identification %4")
0800                                       .arg(QString::number(message->row()), QString::number(message->uid()),
0801                                            QString::number(number), QString::fromUtf8(msgId)).toUtf8().constData());
0802         }
0803     }
0804     TreeItemPart *part = dynamic_cast<TreeItemPart *>(item);
0805     return part;
0806 }
0807 
0808 bool TreeItemMailbox::isSelectable() const
0809 {
0810     return !m_metadata.flags.contains(QStringLiteral("\\NOSELECT")) && !m_metadata.flags.contains(QStringLiteral("\\NONEXISTENT"));
0811 }
0812 
0813 
0814 
0815 TreeItemMsgList::TreeItemMsgList(TreeItem *parent):
0816     TreeItem(parent), m_numberFetchingStatus(NONE), m_totalMessageCount(-1),
0817     m_unreadMessageCount(-1), m_recentMessageCount(-1)
0818 {
0819     if (!parent->parent())
0820         setFetchStatus(DONE);
0821 }
0822 
0823 void TreeItemMsgList::fetch(Model *const model)
0824 {
0825     if (fetched() || isUnavailable())
0826         return;
0827 
0828     if (!loading()) {
0829         setFetchStatus(LOADING);
0830         // We can't ask right now, has to wait till the end of the event loop
0831         CALL_LATER(model, askForMessagesInMailbox, Q_ARG(QModelIndex, toIndex(model)));
0832     }
0833 }
0834 
0835 void TreeItemMsgList::fetchNumbers(Model *const model)
0836 {
0837     if (m_numberFetchingStatus == NONE) {
0838         m_numberFetchingStatus = LOADING;
0839         model->askForNumberOfMessages(this);
0840     }
0841 }
0842 
0843 unsigned int TreeItemMsgList::rowCount(Model *const model)
0844 {
0845     return childrenCount(model);
0846 }
0847 
0848 QVariant TreeItemMsgList::data(Model *const model, int role)
0849 {
0850     if (role == RoleIsFetched)
0851         return fetched();
0852     if (role == RoleIsUnavailable)
0853         return isUnavailable();
0854 
0855     if (role != Qt::DisplayRole)
0856         return QVariant();
0857 
0858     if (!parent())
0859         return QVariant();
0860 
0861     if (loading())
0862         return QLatin1String("[loading messages...]");
0863 
0864     if (isUnavailable())
0865         return QLatin1String("[offline]");
0866 
0867     if (fetched())
0868         return hasChildren(model) ? QStringLiteral("[%1 messages]").arg(childrenCount(model)) : QStringLiteral("[no messages]");
0869 
0870     return QLatin1String("[messages?]");
0871 }
0872 
0873 bool TreeItemMsgList::hasChildren(Model *const model)
0874 {
0875     Q_UNUSED(model);
0876     return true; // we can easily wait here
0877 }
0878 
0879 int TreeItemMsgList::totalMessageCount(Model *const model)
0880 {
0881     // Yes, the numbers can be accommodated by a full mailbox sync, but that's not really what we shall do from this context.
0882     // Because we want to allow the old-school polling for message numbers, we have to look just at the numberFetched() state.
0883     if (!numbersFetched())
0884         fetchNumbers(model);
0885     return m_totalMessageCount;
0886 }
0887 
0888 int TreeItemMsgList::unreadMessageCount(Model *const model)
0889 {
0890     // See totalMessageCount()
0891     if (!numbersFetched())
0892         fetchNumbers(model);
0893     return m_unreadMessageCount;
0894 }
0895 
0896 int TreeItemMsgList::recentMessageCount(Model *const model)
0897 {
0898     // See totalMessageCount()
0899     if (!numbersFetched())
0900         fetchNumbers(model);
0901     return m_recentMessageCount;
0902 }
0903 
0904 void TreeItemMsgList::recalcVariousMessageCounts(Model *model)
0905 {
0906     m_unreadMessageCount = 0;
0907     m_recentMessageCount = 0;
0908     for (int i = 0; i < m_children.size(); ++i) {
0909         TreeItemMessage *message = static_cast<TreeItemMessage *>(m_children[i]);
0910         bool isRead, isRecent;
0911         message->checkFlagsReadRecent(isRead, isRecent);
0912         if (!message->m_flagsHandled)
0913             message->m_wasUnread = ! isRead;
0914         message->m_flagsHandled = true;
0915         if (!isRead)
0916             ++m_unreadMessageCount;
0917         if (isRecent)
0918             ++m_recentMessageCount;
0919     }
0920     m_totalMessageCount = m_children.size();
0921     m_numberFetchingStatus = DONE;
0922     model->emitMessageCountChanged(static_cast<TreeItemMailbox *>(parent()));
0923 }
0924 
0925 void TreeItemMsgList::recalcVariousMessageCountsOnExpunge(Model *model, TreeItemMessage *expungedMessage)
0926 {
0927     if (m_numberFetchingStatus != DONE) {
0928         // In case the counts weren't synced before, we cannot really rely on them now -> go to the slow path
0929         recalcVariousMessageCounts(model);
0930         return;
0931     }
0932 
0933     bool isRead, isRecent;
0934     expungedMessage->checkFlagsReadRecent(isRead, isRecent);
0935     if (expungedMessage->m_flagsHandled) {
0936         if (!isRead)
0937             --m_unreadMessageCount;
0938         if (isRecent)
0939             --m_recentMessageCount;
0940     }
0941     model->emitMessageCountChanged(static_cast<TreeItemMailbox *>(parent()));
0942 }
0943 
0944 void TreeItemMsgList::resetWasUnreadState()
0945 {
0946     for (int i = 0; i < m_children.size(); ++i) {
0947         TreeItemMessage *message = static_cast<TreeItemMessage *>(m_children[i]);
0948         message->m_wasUnread = ! message->isMarkedAsRead();
0949     }
0950 }
0951 
0952 bool TreeItemMsgList::numbersFetched() const
0953 {
0954     return m_numberFetchingStatus == DONE;
0955 }
0956 
0957 
0958 
0959 MessageDataPayload::MessageDataPayload()
0960     : m_size(0)
0961     , m_hdrListPostNo(false)
0962     , m_partHeader(nullptr)
0963     , m_partText(nullptr)
0964     , m_gotEnvelope(false)
0965     , m_gotInternalDate(false)
0966     , m_gotSize(false)
0967     , m_gotBodystructure(false)
0968     , m_gotHdrReferences(false)
0969     , m_gotHdrListPost(false)
0970 {
0971 }
0972 
0973 bool MessageDataPayload::isComplete() const
0974 {
0975     return m_gotEnvelope && m_gotInternalDate && m_gotSize && m_gotBodystructure;
0976 }
0977 
0978 bool MessageDataPayload::gotEnvelope() const
0979 {
0980     return m_gotEnvelope;
0981 }
0982 
0983 bool MessageDataPayload::gotInternalDate() const
0984 {
0985     return m_gotInternalDate;
0986 }
0987 
0988 bool MessageDataPayload::gotSize() const
0989 {
0990     return m_gotSize;
0991 }
0992 
0993 bool MessageDataPayload::gotHdrReferences() const
0994 {
0995     return m_gotHdrReferences;
0996 }
0997 
0998 bool MessageDataPayload::gotHdrListPost() const
0999 {
1000     return m_gotHdrListPost;
1001 }
1002 
1003 const Message::Envelope &MessageDataPayload::envelope() const
1004 {
1005     return m_envelope;
1006 }
1007 
1008 void MessageDataPayload::setEnvelope(const Message::Envelope &envelope)
1009 {
1010     m_envelope = envelope;
1011     m_gotEnvelope = true;
1012 }
1013 
1014 const QDateTime &MessageDataPayload::internalDate() const
1015 {
1016     return m_internalDate;
1017 }
1018 
1019 void MessageDataPayload::setInternalDate(const QDateTime &internalDate)
1020 {
1021     m_internalDate = internalDate;
1022     m_gotInternalDate = true;
1023 }
1024 
1025 quint64 MessageDataPayload::size() const
1026 {
1027     return m_size;
1028 }
1029 
1030 void MessageDataPayload::setSize(quint64 size)
1031 {
1032     m_size = size;
1033     m_gotSize = true;
1034 }
1035 
1036 const QList<QByteArray> &MessageDataPayload::hdrReferences() const
1037 {
1038     return m_hdrReferences;
1039 }
1040 
1041 void MessageDataPayload::setHdrReferences(const QList<QByteArray> &hdrReferences)
1042 {
1043     m_hdrReferences = hdrReferences;
1044     m_gotHdrReferences = true;
1045 }
1046 
1047 const QList<QUrl> &MessageDataPayload::hdrListPost() const
1048 {
1049     return m_hdrListPost;
1050 }
1051 
1052 bool MessageDataPayload::hdrListPostNo() const
1053 {
1054     return m_hdrListPostNo;
1055 }
1056 
1057 void MessageDataPayload::setHdrListPost(const QList<QUrl> &hdrListPost)
1058 {
1059     m_hdrListPost = hdrListPost;
1060     m_gotHdrListPost = true;
1061 }
1062 
1063 void MessageDataPayload::setHdrListPostNo(const bool hdrListPostNo)
1064 {
1065     m_hdrListPostNo = hdrListPostNo;
1066     m_gotHdrListPost = true;
1067 }
1068 
1069 const QByteArray &MessageDataPayload::rememberedBodyStructure() const
1070 {
1071     return m_rememberedBodyStructure;
1072 }
1073 
1074 void MessageDataPayload::setRememberedBodyStructure(const QByteArray &blob)
1075 {
1076     m_rememberedBodyStructure = blob;
1077     m_gotBodystructure = true;
1078 }
1079 
1080 bool MessageDataPayload::gotRemeberedBodyStructure() const
1081 {
1082     return m_gotBodystructure;
1083 }
1084 
1085 TreeItemPart *MessageDataPayload::partHeader() const
1086 {
1087     return m_partHeader.get();
1088 }
1089 
1090 void MessageDataPayload::setPartHeader(std::unique_ptr<TreeItemPart> part)
1091 {
1092     m_partHeader = std::move(part);
1093 }
1094 
1095 TreeItemPart *MessageDataPayload::partText() const
1096 {
1097     return m_partText.get();
1098 }
1099 
1100 void MessageDataPayload::setPartText(std::unique_ptr<TreeItemPart> part)
1101 {
1102     m_partText = std::move(part);
1103 }
1104 
1105 
1106 TreeItemMessage::TreeItemMessage(TreeItem *parent):
1107     TreeItem(parent), m_offset(-1), m_uid(0), m_data(0), m_flagsHandled(false), m_wasUnread(false)
1108 {
1109 }
1110 
1111 TreeItemMessage::~TreeItemMessage()
1112 {
1113     delete m_data;
1114 }
1115 
1116 void TreeItemMessage::fetch(Model *const model)
1117 {
1118     if (fetched() || loading() || isUnavailable())
1119         return;
1120 
1121     if (m_uid) {
1122         // Message UID is already known, which means that we can request data for this message
1123         model->askForMsgMetadata(this, Model::PRELOAD_PER_POLICY);
1124     } else {
1125         // The UID is not known yet, so we can't initiate a UID FETCH at this point. However, we mark
1126         // this message as "loading", which has the side effect that it will get re-fetched as soon as
1127         // the UID arrives -- see TreeItemMailbox::handleFetchResponse(), the section which deals with
1128         // setting previously unknown UIDs, and the similar code in ObtainSynchronizedMailboxTask.
1129         //
1130         // Even though this breaks the message preload done in Model::_askForMsgmetadata, chances are that
1131         // the UIDs will arrive rather soon for all of the pending messages, and the request for metadata
1132         // will therefore get queued roughly at the same time.  This gives the KeepMailboxOpenTask a chance
1133         // to group similar requests together.  To reiterate:
1134         // - Messages are attempted to get *preloaded* (ie. requesting metadata even for messages that are not
1135         //   yet shown) as usual; this could fail because the UIDs might not be known yet.
1136         // - The actual FETCH could be batched by the KeepMailboxOpenTask anyway
1137         // - Hence, this should be still pretty fast and efficient
1138         setFetchStatus(LOADING);
1139     }
1140 }
1141 
1142 unsigned int TreeItemMessage::rowCount(Model *const model)
1143 {
1144     if (!data()->gotRemeberedBodyStructure()) {
1145         fetch(model);
1146     }
1147     return m_children.size();
1148 }
1149 
1150 TreeItemChildrenList TreeItemMessage::setChildren(const TreeItemChildrenList &items)
1151 {
1152     auto origStatus = accessFetchStatus();
1153     auto res = TreeItem::setChildren(items);
1154     setFetchStatus(origStatus);
1155     return res;
1156 }
1157 
1158 unsigned int TreeItemMessage::columnCount()
1159 {
1160     static_assert(OFFSET_HEADER < OFFSET_TEXT && OFFSET_MIME == OFFSET_TEXT + 1,
1161                   "We need column 0 for regular children and columns 1 and 2 for OFFSET_HEADER and OFFSET_TEXT.");
1162     // Oh, and std::max is not constexpr in C++11.
1163     return OFFSET_MIME;
1164 }
1165 
1166 TreeItem *TreeItemMessage::specialColumnPtr(int row, int column) const
1167 {
1168     // This is a nasty one -- we have an irregular shape...
1169 
1170     // No extra columns on other rows
1171     if (row != 0)
1172         return 0;
1173 
1174     switch (column) {
1175     case OFFSET_TEXT:
1176         if (!data()->partText()) {
1177             data()->setPartText(std::unique_ptr<TreeItemPart>(new TreeItemModifiedPart(const_cast<TreeItemMessage *>(this), OFFSET_TEXT)));
1178         }
1179         return data()->partText();
1180     case OFFSET_HEADER:
1181         if (!data()->partHeader()) {
1182             data()->setPartHeader(std::unique_ptr<TreeItemPart>(new TreeItemModifiedPart(const_cast<TreeItemMessage *>(this), OFFSET_HEADER)));
1183         }
1184         return data()->partHeader();
1185     default:
1186         return 0;
1187     }
1188 }
1189 
1190 int TreeItemMessage::row() const
1191 {
1192     Q_ASSERT(m_offset != -1);
1193     return m_offset;
1194 }
1195 
1196 QVariant TreeItemMessage::data(Model *const model, int role)
1197 {
1198     if (!parent())
1199         return QVariant();
1200 
1201     // Special item roles which should not trigger fetching of message metadata
1202     switch (role) {
1203     case RoleMessageUid:
1204         return m_uid ? QVariant(m_uid) : QVariant();
1205     case RoleIsFetched:
1206         return fetched();
1207     case RoleIsUnavailable:
1208         return isUnavailable();
1209     case RoleMessageFlags:
1210         // The flags are already sorted by Model::normalizeFlags()
1211         return m_flags;
1212     case RoleMessageIsMarkedDeleted:
1213         return isMarkedAsDeleted();
1214     case RoleMessageIsMarkedRead:
1215         return isMarkedAsRead();
1216     case RoleMessageIsMarkedForwarded:
1217         return isMarkedAsForwarded();
1218     case RoleMessageIsMarkedReplied:
1219         return isMarkedAsReplied();
1220     case RoleMessageIsMarkedRecent:
1221         return isMarkedAsRecent();
1222     case RoleMessageIsMarkedFlagged:
1223         return isMarkedAsFlagged();
1224     case RoleMessageIsMarkedJunk:
1225         return isMarkedAsJunk();
1226     case RoleMessageIsMarkedNotJunk:
1227         return isMarkedAsNotJunk();
1228     case RoleMessageFuzzyDate:
1229     {
1230         // When the QML ListView is configured with its section.* properties, it will call the corresponding data() section *very*
1231         // often.  The data are however only "needed" when the real items are visible, and when they are visible, the data() will
1232         // get called anyway and the correct stuff will ultimately arrive.  This is why we don't call fetch() from here.
1233         //
1234         // FIXME: double-check the above once again! Maybe it was just a side effect of too fast updates of the currentIndex?
1235         if (!fetched()) {
1236             return QVariant();
1237         }
1238 
1239         QDateTime timestamp = envelope(model).date;
1240         if (!timestamp.isValid())
1241             return QString();
1242 
1243         if (timestamp.date() == QDate::currentDate())
1244             return Model::tr("Today");
1245 
1246         int beforeDays = timestamp.date().daysTo(QDate::currentDate());
1247         if (beforeDays >= 0 && beforeDays < 7)
1248             return Model::tr("Last Week");
1249 
1250         return QDate(timestamp.date().year(), timestamp.date().month(), 1).toString(
1251             Model::tr("MMMM yyyy", "The format specifiers (yyyy, etc) must not be translated, "
1252                       "but their order can be changed to follow the local conventions. "
1253                       "For valid specifiers see http://doc.qt.io/qt-5/qdate.html#toString"));
1254     }
1255     case RoleMessageWasUnread:
1256         return m_wasUnread;
1257     case RoleThreadRootWithUnreadMessages:
1258         // This one doesn't really make much sense here, but we do want to catch it to prevent a fetch request from this context
1259         qDebug() << "Warning: asked for RoleThreadRootWithUnreadMessages on TreeItemMessage. This does not make sense.";
1260         return QVariant();
1261     case RoleMailboxName:
1262     case RoleMailboxUidValidity:
1263         return parent()->parent()->data(model, role);
1264     case RolePartMimeType:
1265         return QByteArrayLiteral("message/rfc822");
1266     case RoleIMAPRelativeUrl:
1267         if (m_uid) {
1268             return QByteArray("/" + QUrl::toPercentEncoding(data(model, RoleMailboxName).toString())
1269                               + ";UIDVALIDITY=" + data(model, RoleMailboxUidValidity).toByteArray()
1270                               + "/;UID=" + QByteArray::number(m_uid));
1271         } else {
1272             return QVariant();
1273         }
1274     }
1275 
1276     // Any other roles will result in fetching the data; however, we won't exit if the data isn't available yet
1277     fetch(model);
1278 
1279     switch (role) {
1280     case Qt::DisplayRole:
1281         if (loading()) {
1282             return QStringLiteral("[loading UID %1...]").arg(QString::number(uid()));
1283         } else if (isUnavailable()) {
1284             return QStringLiteral("[offline UID %1]").arg(QString::number(uid()));
1285         } else {
1286             return QStringLiteral("UID %1: %2").arg(QString::number(uid()), data()->envelope().subject);
1287         }
1288     case Qt::ToolTipRole:
1289         if (fetched()) {
1290             QString buf;
1291             QTextStream stream(&buf);
1292             stream << data()->envelope();
1293             return UiUtils::Formatting::htmlEscaped(buf);
1294         } else {
1295             return QVariant();
1296         }
1297     case RoleMessageSize:
1298         return data()->gotSize() ? QVariant::fromValue(data()->size()) : QVariant();
1299     case RoleMessageInternalDate:
1300         return data()->gotInternalDate() ? data()->internalDate() : QVariant();
1301     case RoleMessageHeaderReferences:
1302         return data()->gotHdrReferences() ? QVariant::fromValue(data()->hdrReferences()) : QVariant();
1303     case RoleMessageHeaderListPost:
1304         if (data()->gotHdrListPost()) {
1305             QVariantList res;
1306             Q_FOREACH(const QUrl &url, data()->hdrListPost())
1307                 res << url;
1308             return res;
1309         } else {
1310             return QVariant();
1311         }
1312     case RoleMessageHeaderListPostNo:
1313         return data()->gotHdrListPost() ? QVariant(data()->hdrListPostNo()) : QVariant();
1314     }
1315 
1316     if (data()->gotEnvelope()) {
1317         // If the envelope is already available, we might be able to deliver some bits even prior to full fetch being done.
1318         //
1319         // Only those values which need ENVELOPE go here!
1320         switch (role) {
1321         case RoleMessageSubject:
1322             return data()->gotEnvelope() ? QVariant(data()->envelope().subject) : QVariant();
1323         case RoleMessageDate:
1324             return data()->gotEnvelope() ? envelope(model).date : QVariant();
1325         case RoleMessageFrom:
1326             return addresListToQVariant(envelope(model).from);
1327         case RoleMessageTo:
1328             return addresListToQVariant(envelope(model).to);
1329         case RoleMessageCc:
1330             return addresListToQVariant(envelope(model).cc);
1331         case RoleMessageBcc:
1332             return addresListToQVariant(envelope(model).bcc);
1333         case RoleMessageSender:
1334             return addresListToQVariant(envelope(model).sender);
1335         case RoleMessageReplyTo:
1336             return addresListToQVariant(envelope(model).replyTo);
1337         case RoleMessageInReplyTo:
1338             return QVariant::fromValue(envelope(model).inReplyTo);
1339         case RoleMessageMessageId:
1340             return envelope(model).messageId;
1341         case RoleMessageEnvelope:
1342             return QVariant::fromValue<Message::Envelope>(envelope(model));
1343         case RoleMessageHasAttachments:
1344             return hasAttachments(model);
1345         }
1346     }
1347 
1348     return QVariant();
1349 }
1350 
1351 QVariantList TreeItemMessage::addresListToQVariant(const QList<Imap::Message::MailAddress> &addressList)
1352 {
1353     QVariantList res;
1354     foreach(const Imap::Message::MailAddress& address, addressList) {
1355         res.append(QVariant(QStringList() << address.name << address.adl << address.mailbox << address.host));
1356     }
1357     return res;
1358 }
1359 
1360 
1361 namespace {
1362 
1363 /** @short Find a string based on d-ptr equality
1364 
1365 This works because our flags always use implicit sharing. If they didn't use that, this method wouldn't work.
1366 */
1367 bool containsStringByDPtr(const QStringList &haystack, const QString &needle)
1368 {
1369     const auto sentinel = const_cast<QString&>(needle).data_ptr();
1370     Q_FOREACH(const auto &item, haystack) {
1371         if (const_cast<QString&>(item).data_ptr() == sentinel)
1372             return true;
1373     }
1374     return false;
1375 }
1376 
1377 }
1378 
1379 bool TreeItemMessage::isMarkedAsDeleted() const
1380 {
1381     return containsStringByDPtr(m_flags, FlagNames::deleted);
1382 }
1383 
1384 bool TreeItemMessage::isMarkedAsRead() const
1385 {
1386     return containsStringByDPtr(m_flags, FlagNames::seen);
1387 }
1388 
1389 bool TreeItemMessage::isMarkedAsReplied() const
1390 {
1391     return containsStringByDPtr(m_flags, FlagNames::answered);
1392 }
1393 
1394 bool TreeItemMessage::isMarkedAsForwarded() const
1395 {
1396     return containsStringByDPtr(m_flags, FlagNames::forwarded);
1397 }
1398 
1399 bool TreeItemMessage::isMarkedAsRecent() const
1400 {
1401     return containsStringByDPtr(m_flags, FlagNames::recent);
1402 }
1403 
1404 bool TreeItemMessage::isMarkedAsFlagged() const
1405 {
1406     return containsStringByDPtr(m_flags, FlagNames::flagged);
1407 }
1408 
1409 bool TreeItemMessage::isMarkedAsJunk() const
1410 {
1411     return containsStringByDPtr(m_flags, FlagNames::junk);
1412 }
1413 
1414 bool TreeItemMessage::isMarkedAsNotJunk() const
1415 {
1416     return containsStringByDPtr(m_flags, FlagNames::notjunk);
1417 }
1418 
1419 void TreeItemMessage::checkFlagsReadRecent(bool &isRead, bool &isRecent) const
1420 {
1421     const auto dRead = const_cast<QString&>(FlagNames::seen).data_ptr();
1422     const auto dRecent = const_cast<QString&>(FlagNames::recent).data_ptr();
1423     auto end = m_flags.end();
1424     auto it = m_flags.begin();
1425     isRead = isRecent = false;
1426     while (it != end && !(isRead && isRecent)) {
1427         isRead |= const_cast<QString&>(*it).data_ptr() == dRead;
1428         isRecent |= const_cast<QString&>(*it).data_ptr() == dRecent;
1429         ++it;
1430     }
1431 }
1432 
1433 uint TreeItemMessage::uid() const
1434 {
1435     return m_uid;
1436 }
1437 
1438 Message::Envelope TreeItemMessage::envelope(Model *const model)
1439 {
1440     fetch(model);
1441     return data()->envelope();
1442 }
1443 
1444 QDateTime TreeItemMessage::internalDate(Model *const model)
1445 {
1446     fetch(model);
1447     return data()->internalDate();
1448 }
1449 
1450 quint64 TreeItemMessage::size(Model *const model)
1451 {
1452     fetch(model);
1453     return data()->size();
1454 }
1455 
1456 void TreeItemMessage::setFlags(TreeItemMsgList *list, const QStringList &flags)
1457 {
1458     // wasSeen is used to determine if the message was marked as read before this operation
1459     bool wasSeen = isMarkedAsRead();
1460     m_flags = flags;
1461     if (list->m_numberFetchingStatus == DONE) {
1462         bool isSeen = isMarkedAsRead();
1463         if (m_flagsHandled) {
1464             if (wasSeen && !isSeen) {
1465                 ++list->m_unreadMessageCount;
1466                 // leave the message as "was unread" so it persists in the view when read messages are hidden
1467                 m_wasUnread = true;
1468             } else if (!wasSeen && isSeen) {
1469                 --list->m_unreadMessageCount;
1470             }
1471         } else {
1472             // it's a new message
1473             m_flagsHandled = true;
1474             if (!isSeen) {
1475                 ++list->m_unreadMessageCount;
1476                 // mark the message as "was unread" so it shows up in the view when read messages are hidden
1477                 m_wasUnread = true;
1478             }
1479         }
1480     }
1481 }
1482 
1483 /** @short Process the data found in the headers passed along and file in auxiliary metadata
1484 
1485 This function accepts a snippet containing some RFC5322 headers of a message, no matter what headers are actually
1486 present in the passed text.  The headers are parsed and those recognized are used as a source of data to file
1487 the "auxiliary metadata" of this TreeItemMessage (basically anything not available in ENVELOPE, UID, FLAGS,
1488 INTERNALDATE etc).
1489 */
1490 void TreeItemMessage::processAdditionalHeaders(Model *model, const QByteArray &rawHeaders)
1491 {
1492     Imap::LowLevelParser::Rfc5322HeaderParser parser;
1493     bool ok = parser.parse(rawHeaders);
1494     if (!ok) {
1495         model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Rfc5322HeaderParser"),
1496                         QStringLiteral("Unspecified error during RFC5322 header parsing"));
1497     }
1498 
1499     data()->setHdrReferences(parser.references);
1500     QList<QUrl> hdrListPost;
1501     if (!parser.listPost.isEmpty()) {
1502         Q_FOREACH(const QByteArray &item, parser.listPost)
1503             hdrListPost << QUrl(QString::fromUtf8(item));
1504         data()->setHdrListPost(hdrListPost);
1505     }
1506     // That's right, this can only be set, not ever reset from this context.
1507     // This is because we absolutely want to support incremental header arrival.
1508     if (parser.listPostNo)
1509         data()->setHdrListPostNo(true);
1510 }
1511 
1512 bool TreeItemMessage::hasAttachments(Model *const model)
1513 {
1514     fetch(model);
1515 
1516     if (!fetched())
1517         return false;
1518 
1519     if (m_children.isEmpty()) {
1520         // strange, but why not, I guess
1521         return false;
1522     } else if (m_children.size() > 1) {
1523         // Again, very strange -- the message should have had a single multipart as a root node, but let's cope with this as well
1524         return true;
1525     } else {
1526         return hasNestedAttachments(model, static_cast<TreeItemPart*>(m_children[0]));
1527     }
1528 }
1529 
1530 /** @short Walk the MIME tree starting at @arg part and check if there are any attachments below (or at there) */
1531 bool TreeItemMessage::hasNestedAttachments(Model *const model, TreeItemPart *part)
1532 {
1533     while (true) {
1534 
1535         const QByteArray mimeType = part->mimeType();
1536 
1537         if (mimeType == "multipart/signed" && part->childrenCount(model) == 2) {
1538             // "strip" the signature, look at what's inside
1539             part = static_cast<TreeItemPart*>(part->child(0, model));
1540         } else if (mimeType.startsWith("multipart/")) {
1541             // Return false iff no children is/has an attachment.
1542             // Originally this code was like this only for multipart/alternative, but in the end Stephan Platz lobbied for
1543             // treating ML signatures the same (which means multipart/mixed) has to be included, and there's also a RFC
1544             // which says that unrecognized multiparts should be treated exactly like a multipart/mixed.
1545             // As a bonus, this makes it possible to get rid of an extra branch for single-childed multiparts.
1546             for (uint i = 0; i < part->childrenCount(model); ++i) {
1547                 if (hasNestedAttachments(model, static_cast<TreeItemPart*>(part->child(i, model)))) {
1548                     return true;
1549                 }
1550             }
1551             return false;
1552         } else if (mimeType == "text/html" || mimeType == "text/plain") {
1553             // See AttachmentView for details behind this.
1554             const QByteArray contentDisposition = part->bodyDisposition().toLower();
1555             const bool isInline = contentDisposition.isEmpty() || contentDisposition == "inline";
1556             const bool looksLikeAttachment = !part->fileName().isEmpty();
1557 
1558             return looksLikeAttachment || !isInline;
1559         } else {
1560             // anything else must surely be an attachment
1561             return true;
1562         }
1563     }
1564 }
1565 
1566 
1567 TreeItemPart::TreeItemPart(TreeItem *parent, const QByteArray &mimeType)
1568     : TreeItem(parent)
1569     , m_mimeType(mimeType.toLower())
1570     , m_octets(0)
1571     , m_partMime(nullptr)
1572     , m_partRaw(nullptr)
1573     , m_binaryCTEFailed(false)
1574 {
1575 }
1576 
1577 TreeItemPart::TreeItemPart(TreeItem *parent)
1578     : TreeItem(parent)
1579     , m_mimeType("text/plain")
1580     , m_octets(0)
1581     , m_partMime(nullptr)
1582     , m_partRaw(nullptr)
1583     , m_binaryCTEFailed(false)
1584 {
1585 }
1586 
1587 TreeItemPart::~TreeItemPart()
1588 {
1589     delete m_partMime;
1590     delete m_partRaw;
1591 }
1592 
1593 unsigned int TreeItemPart::childrenCount(Model *const model)
1594 {
1595     Q_UNUSED(model);
1596     return m_children.size();
1597 }
1598 
1599 TreeItem *TreeItemPart::child(const int offset, Model *const model)
1600 {
1601     Q_UNUSED(model);
1602     if (offset >= 0 && offset < m_children.size())
1603         return m_children[ offset ];
1604     else
1605         return 0;
1606 }
1607 
1608 TreeItemChildrenList TreeItemPart::setChildren(const TreeItemChildrenList &items)
1609 {
1610     FetchingState fetchStatus = accessFetchStatus();
1611     auto res = TreeItem::setChildren(items);
1612     setFetchStatus(fetchStatus);
1613     return res;
1614 }
1615 
1616 void TreeItemPart::fetch(Model *const model)
1617 {
1618     if (fetched() || loading() || isUnavailable())
1619         return;
1620 
1621     if (isTopLevelMultiPart()) {
1622         // Note that top-level multipart messages are special, their immediate contents
1623         // can't be fetched.
1624         setFetchStatus(DONE);
1625         return;
1626     }
1627 
1628     setFetchStatus(LOADING);
1629     model->askForMsgPart(this);
1630 }
1631 
1632 void TreeItemPart::fetchFromCache(Model *const model)
1633 {
1634     if (fetched() || loading() || isUnavailable())
1635         return;
1636 
1637     model->askForMsgPart(this, true);
1638 }
1639 
1640 unsigned int TreeItemPart::rowCount(Model *const model)
1641 {
1642     // no call to fetch() required
1643     Q_UNUSED(model);
1644     return m_children.size();
1645 }
1646 
1647 QVariant TreeItemPart::data(Model *const model, int role)
1648 {
1649     if (!parent())
1650         return QVariant();
1651 
1652     // these data are available immediately
1653     switch (role) {
1654     case RoleIsFetched:
1655         return fetched();
1656     case RoleIsUnavailable:
1657         return isUnavailable();
1658     case RolePartMimeType:
1659         return m_mimeType;
1660     case RolePartCharset:
1661         return m_charset;
1662     case RolePartContentFormat:
1663         return m_contentFormat;
1664     case RolePartContentDelSp:
1665         return m_delSp;
1666     case RolePartTransferEncoding:
1667         return m_transferEncoding;
1668     case RolePartBodyFldId:
1669         return m_bodyFldId;
1670     case RolePartBodyDisposition:
1671         return m_bodyDisposition;
1672     case RolePartFileName:
1673         return m_fileName;
1674     case RolePartOctets:
1675         return QVariant::fromValue(m_octets);
1676     case RolePartId:
1677         return partId();
1678     case RolePartPathToPart:
1679         return pathToPart();
1680     case RolePartMultipartRelatedMainCid:
1681         if (!multipartRelatedStartPart().isEmpty())
1682             return multipartRelatedStartPart();
1683         else
1684             return QVariant();
1685     case RolePartMessageIndex:
1686         return QVariant::fromValue(message()->toIndex(model));
1687     case RoleMailboxName:
1688     case RoleMailboxUidValidity:
1689         return message()->parent()->parent()->data(model, role);
1690     case RoleMessageUid:
1691         return message()->uid();
1692     case RolePartIsTopLevelMultipart:
1693         return isTopLevelMultiPart();
1694     case RolePartForceFetchFromCache:
1695         fetchFromCache(model);
1696         return QVariant();
1697     case RolePartBufferPtr:
1698         return QVariant::fromValue(dataPtr());
1699     case RolePartBodyFldParam:
1700         return QVariant::fromValue(m_bodyFldParam);
1701     case RoleIMAPRelativeUrl:
1702         if (message() && message()->uid()) {
1703             return QByteArray("/" + QUrl::toPercentEncoding(data(model, RoleMailboxName).toString())
1704                               + ";UIDVALIDITY=" + data(model, RoleMailboxUidValidity).toByteArray()
1705                               + "/;UID=" + QByteArray::number(message()->uid())
1706                               + "/;SECTION=" + partId());
1707         } else {
1708             return QVariant();
1709         }
1710     }
1711 
1712 
1713     fetch(model);
1714 
1715     if (loading()) {
1716         if (role == Qt::DisplayRole) {
1717             return isTopLevelMultiPart() ?
1718                    Model::tr("[loading %1...]").arg(QString::fromUtf8(m_mimeType)) :
1719                    Model::tr("[loading %1: %2...]").arg(QString::fromUtf8(partId()), QString::fromUtf8(m_mimeType));
1720         } else {
1721             return QVariant();
1722         }
1723     }
1724 
1725     switch (role) {
1726     case Qt::DisplayRole:
1727         return isTopLevelMultiPart() ?
1728                QString::fromUtf8(m_mimeType) :
1729                QStringLiteral("%1: %2").arg(QString::fromUtf8(partId()), QString::fromUtf8(m_mimeType));
1730     case Qt::ToolTipRole:
1731         return QStringLiteral("%1 bytes of data").arg(m_data.size());
1732     case RolePartData:
1733         return m_data;
1734     case RolePartUnicodeText:
1735         if (m_mimeType.startsWith("text/")) {
1736             return decodeByteArray(m_data, m_charset);
1737         } else {
1738             return QVariant();
1739         }
1740     default:
1741         return QVariant();
1742     }
1743 }
1744 
1745 bool TreeItemPart::hasChildren(Model *const model)
1746 {
1747     // no need to fetch() here
1748     Q_UNUSED(model);
1749     return ! m_children.isEmpty();
1750 }
1751 
1752 /** @short Returns true if we're a multipart, top-level item in the body of a message */
1753 bool TreeItemPart::isTopLevelMultiPart() const
1754 {
1755     TreeItemMessage *msg = dynamic_cast<TreeItemMessage *>(parent());
1756     TreeItemPart *part = dynamic_cast<TreeItemPart *>(parent());
1757     return m_mimeType.startsWith("multipart/") && (msg || (part && part->m_mimeType.startsWith("message/")));
1758 }
1759 
1760 QByteArray TreeItemPart::partId() const
1761 {
1762     if (isTopLevelMultiPart()) {
1763         return QByteArray();
1764     } else if (dynamic_cast<TreeItemMessage *>(parent())) {
1765         return QByteArray::number(row() + 1);
1766     } else {
1767         QByteArray parentId;
1768         TreeItemPart *parentPart = dynamic_cast<TreeItemPart *>(parent());
1769         Q_ASSERT(parentPart);
1770         if (parentPart->isTopLevelMultiPart()) {
1771             if (TreeItemPart *parentOfParent = dynamic_cast<TreeItemPart *>(parentPart->parent())) {
1772                 Q_ASSERT(!parentOfParent->isTopLevelMultiPart());
1773                 // grand parent: message/rfc822 with a part-id, parent: top-level multipart
1774                 parentId = parentOfParent->partId();
1775             } else {
1776                 // grand parent: TreeItemMessage, parent: some multipart, me: some part
1777                 return QByteArray::number(row() + 1);
1778             }
1779         } else {
1780             parentId = parentPart->partId();
1781         }
1782         Q_ASSERT(!parentId.isEmpty());
1783         return parentId + '.' + QByteArray::number(row() + 1);
1784     }
1785 }
1786 
1787 QByteArray TreeItemPart::partIdForFetch(const PartFetchingMode mode) const
1788 {
1789     return QByteArray(mode == FETCH_PART_BINARY ? "BINARY" : "BODY") + ".PEEK[" + partId() + "]";
1790 }
1791 
1792 QByteArray TreeItemPart::pathToPart() const
1793 {
1794     TreeItemPart *part = dynamic_cast<TreeItemPart *>(parent());
1795     TreeItemMessage *msg = dynamic_cast<TreeItemMessage *>(parent());
1796     if (part)
1797         return part->pathToPart() + '/' + QByteArray::number(row());
1798     else if (msg)
1799         return '/' + QByteArray::number(row());
1800     else {
1801         Q_ASSERT(false);
1802         return QByteArray();
1803     }
1804 }
1805 
1806 TreeItemMessage *TreeItemPart::message() const
1807 {
1808     const TreeItemPart *part = this;
1809     while (part) {
1810         TreeItemMessage *message = dynamic_cast<TreeItemMessage *>(part->parent());
1811         if (message)
1812             return message;
1813         part = dynamic_cast<TreeItemPart *>(part->parent());
1814     }
1815     return 0;
1816 }
1817 
1818 QByteArray *TreeItemPart::dataPtr()
1819 {
1820     return &m_data;
1821 }
1822 
1823 unsigned int TreeItemPart::columnCount()
1824 {
1825     if (isTopLevelMultiPart()) {
1826         // Because a top-level multipart doesn't have its own part number, one cannot really fetch from it
1827         return 1;
1828     }
1829 
1830     // This one includes the OFFSET_MIME and OFFSET_RAW_CONTENTS, unlike the TreeItemMessage
1831     static_assert(OFFSET_MIME < OFFSET_RAW_CONTENTS, "The OFFSET_RAW_CONTENTS shall be the biggest one for tree invariants to work");
1832     return OFFSET_RAW_CONTENTS + 1;
1833 }
1834 
1835 TreeItem *TreeItemPart::specialColumnPtr(int row, int column) const
1836 {
1837     if (row == 0 && !isTopLevelMultiPart()) {
1838         switch (column) {
1839         case OFFSET_MIME:
1840             if (!m_partMime) {
1841                 m_partMime = new TreeItemModifiedPart(const_cast<TreeItemPart*>(this), OFFSET_MIME);
1842             }
1843             return m_partMime;
1844         case OFFSET_RAW_CONTENTS:
1845             if (!m_partRaw) {
1846                 m_partRaw = new TreeItemModifiedPart(const_cast<TreeItemPart*>(this), OFFSET_RAW_CONTENTS);
1847             }
1848             return m_partRaw;
1849         }
1850     }
1851     return 0;
1852 }
1853 
1854 void TreeItemPart::silentlyReleaseMemoryRecursive()
1855 {
1856     Q_FOREACH(TreeItem *item, m_children) {
1857         TreeItemPart *part = dynamic_cast<TreeItemPart *>(item);
1858         Q_ASSERT(part);
1859         part->silentlyReleaseMemoryRecursive();
1860     }
1861     if (m_partMime) {
1862         m_partMime->silentlyReleaseMemoryRecursive();
1863         delete m_partMime;
1864         m_partMime = 0;
1865     }
1866     if (m_partRaw) {
1867         m_partRaw->silentlyReleaseMemoryRecursive();
1868         delete m_partRaw;
1869         m_partRaw = 0;
1870     }
1871     m_data.clear();
1872     setFetchStatus(NONE);
1873     qDeleteAll(m_children);
1874     m_children.clear();
1875 }
1876 
1877 
1878 
1879 TreeItemModifiedPart::TreeItemModifiedPart(TreeItem *parent, const PartModifier kind):
1880     TreeItemPart(parent), m_modifier(kind)
1881 {
1882 }
1883 
1884 int TreeItemModifiedPart::row() const
1885 {
1886     // we're always at the very top
1887     return 0;
1888 }
1889 
1890 TreeItem *TreeItemModifiedPart::specialColumnPtr(int row, int column) const
1891 {
1892     Q_UNUSED(row);
1893     Q_UNUSED(column);
1894     // no special children below the current special one
1895     return 0;
1896 }
1897 
1898 bool TreeItemModifiedPart::isTopLevelMultiPart() const
1899 {
1900     // we're special enough not to ever act like a "top-level multipart"
1901     return false;
1902 }
1903 
1904 unsigned int TreeItemModifiedPart::columnCount()
1905 {
1906     // no child items, either
1907     return 0;
1908 }
1909 
1910 QByteArray TreeItemModifiedPart::partId() const
1911 {
1912     if (m_modifier == OFFSET_RAW_CONTENTS) {
1913         // This item is not directly fetcheable, so it does *not* make sense to ask for it.
1914         // We cannot really assert at this point, though, because this function is published via the MVC interface.
1915         return "application-bug-dont-fetch-this";
1916     } else if (TreeItemPart *part = dynamic_cast<TreeItemPart *>(parent())) {
1917         // The TreeItemPart is supposed to prevent creation of any special subparts if it's a top-level multipart
1918         Q_ASSERT(!part->isTopLevelMultiPart());
1919         return part->partId() + '.' + modifierToByteArray();
1920     } else {
1921         // Our parent is a message/rfc822, and it's definitely not nested -> no need for parent id here
1922         // Cannot assert() on a dynamic_cast<TreeItemMessage*> at this point because the part is already nullptr at this time
1923         Q_ASSERT(dynamic_cast<TreeItemMessage*>(parent()));
1924         return modifierToByteArray();
1925     }
1926 }
1927 
1928 TreeItem::PartModifier TreeItemModifiedPart::kind() const
1929 {
1930     return m_modifier;
1931 }
1932 
1933 QByteArray TreeItemModifiedPart::modifierToByteArray() const
1934 {
1935     switch (m_modifier) {
1936     case OFFSET_HEADER:
1937         return "HEADER";
1938     case OFFSET_TEXT:
1939         return "TEXT";
1940     case OFFSET_MIME:
1941         return "MIME";
1942     case OFFSET_RAW_CONTENTS:
1943         Q_ASSERT(!"Cannot get the fetch modifier for an OFFSET_RAW_CONTENTS item");
1944         // fall through
1945     default:
1946         Q_ASSERT(false);
1947         return QByteArray();
1948     }
1949 }
1950 
1951 QByteArray TreeItemModifiedPart::pathToPart() const
1952 {
1953     if (TreeItemPart *parentPart = dynamic_cast<TreeItemPart *>(parent())) {
1954         return parentPart->pathToPart() + "/" + modifierToByteArray();
1955     } else {
1956         Q_ASSERT(dynamic_cast<TreeItemMessage *>(parent()));
1957         return "/" + modifierToByteArray();
1958     }
1959 }
1960 
1961 QModelIndex TreeItemModifiedPart::toIndex(Model *const model) const
1962 {
1963     Q_ASSERT(model);
1964     // see TreeItem::toIndex() for the const_cast explanation
1965     return model->createIndex(row(), static_cast<int>(kind()), const_cast<TreeItemModifiedPart *>(this));
1966 }
1967 
1968 QByteArray TreeItemModifiedPart::partIdForFetch(const PartFetchingMode mode) const
1969 {
1970     Q_UNUSED(mode);
1971     // Don't try to use BINARY for special message parts, it's forbidden. One can only use that for the "regular" MIME parts
1972     return TreeItemPart::partIdForFetch(FETCH_PART_IMAP);
1973 }
1974 
1975 TreeItemPartMultipartMessage::TreeItemPartMultipartMessage(TreeItem *parent, const Message::Envelope &envelope):
1976     TreeItemPart(parent, "message/rfc822"), m_envelope(envelope)
1977 {
1978 }
1979 
1980 TreeItemPartMultipartMessage::~TreeItemPartMultipartMessage()
1981 {
1982 }
1983 
1984 /** @short Overridden from TreeItemPart::data with added support for RoleMessageEnvelope */
1985 QVariant TreeItemPartMultipartMessage::data(Model * const model, int role)
1986 {
1987     switch (role) {
1988     case RoleMessageEnvelope:
1989         return QVariant::fromValue<Message::Envelope>(m_envelope);
1990     case RoleMessageHeaderReferences:
1991     case RoleMessageHeaderListPost:
1992     case RoleMessageHeaderListPostNo:
1993         // FIXME: implement me; TreeItemPart has no path for this
1994         return QVariant();
1995     default:
1996         return TreeItemPart::data(model, role);
1997     }
1998 }
1999 
2000 TreeItem *TreeItemPartMultipartMessage::specialColumnPtr(int row, int column) const
2001 {
2002     if (row != 0)
2003         return 0;
2004     switch (column) {
2005     case OFFSET_HEADER:
2006         if (!m_partHeader) {
2007             m_partHeader.reset(new TreeItemModifiedPart(const_cast<TreeItemPartMultipartMessage*>(this), OFFSET_HEADER));
2008         }
2009         return m_partHeader.get();
2010     case OFFSET_TEXT:
2011         if (!m_partText) {
2012             m_partText.reset(new TreeItemModifiedPart(const_cast<TreeItemPartMultipartMessage*>(this), OFFSET_TEXT));
2013         }
2014         return m_partText.get();
2015     default:
2016         return TreeItemPart::specialColumnPtr(row, column);
2017     }
2018 }
2019 
2020 void TreeItemPartMultipartMessage::silentlyReleaseMemoryRecursive()
2021 {
2022     TreeItemPart::silentlyReleaseMemoryRecursive();
2023     if (m_partHeader) {
2024         m_partHeader->silentlyReleaseMemoryRecursive();
2025         m_partHeader = nullptr;
2026     }
2027     if (m_partText) {
2028         m_partText->silentlyReleaseMemoryRecursive();
2029         m_partText = nullptr;
2030     }
2031 }
2032 
2033 }
2034 }