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 }