File indexing completed on 2024-06-23 05:21:15
0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net> 0002 0003 This file is part of the Trojita Qt IMAP e-mail client, 0004 http://trojita.flaska.net/ 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License as 0008 published by the Free Software Foundation; either version 2 of 0009 the License or (at your option) version 3 or any later version 0010 accepted by the membership of KDE e.V. (or its successor approved 0011 by the membership of KDE e.V.), which shall act as a proxy 0012 defined in Section 14 of version 3 of the license. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include "ObtainSynchronizedMailboxTask.h" 0024 #include <algorithm> 0025 #include <sstream> 0026 #include <QTimer> 0027 #include "Common/InvokeMethod.h" 0028 #include "Imap/Model/ItemRoles.h" 0029 #include "Imap/Model/MailboxTree.h" 0030 #include "Imap/Model/Model.h" 0031 #include "KeepMailboxOpenTask.h" 0032 #include "UnSelectTask.h" 0033 0034 namespace Imap 0035 { 0036 namespace Mailbox 0037 { 0038 0039 ObtainSynchronizedMailboxTask::ObtainSynchronizedMailboxTask(Model *model, const QModelIndex &mailboxIndex, ImapTask *parentTask, 0040 KeepMailboxOpenTask *keepTask): 0041 ImapTask(model), conn(parentTask), mailboxIndex(mailboxIndex), status(STATE_WAIT_FOR_CONN), uidSyncingMode(UID_SYNC_ALL), 0042 firstUnknownUidOffset(0), m_usingQresync(false), unSelectTask(0), keepTaskChild(keepTask) 0043 { 0044 // The Parser* is not provided by our parent task, but instead through the keepTaskChild. The reason is simple, the parent 0045 // task might not even exist, but there's always an KeepMailboxOpenTask in the game. 0046 parser = keepTaskChild->parser; 0047 Q_ASSERT(parser); 0048 if (conn) { 0049 conn->addDependentTask(this); 0050 } 0051 CHECK_TASK_TREE 0052 addDependentTask(keepTaskChild); 0053 CHECK_TASK_TREE 0054 connect(this, &ImapTask::failed, this, &ObtainSynchronizedMailboxTask::signalSyncFailure); 0055 } 0056 0057 void ObtainSynchronizedMailboxTask::addDependentTask(ImapTask *task) 0058 { 0059 if (!dependentTasks.isEmpty()) { 0060 throw CantHappen("Attempted to add another dependent task to an ObtainSynchronizedMailboxTask"); 0061 } 0062 ImapTask::addDependentTask(task); 0063 } 0064 0065 void ObtainSynchronizedMailboxTask::perform() 0066 { 0067 CHECK_TASK_TREE 0068 markAsActiveTask(); 0069 0070 if (_dead || _aborted) { 0071 // We're at the very start, so let's try to abort in a sane way 0072 _failed(tr("Asked to abort or die")); 0073 die(tr("Mailbox syncing dead or aborted")); 0074 return; 0075 } 0076 0077 if (! mailboxIndex.isValid()) { 0078 // FIXME: proper error handling 0079 log(QStringLiteral("The mailbox went missing, sorry"), Common::LOG_MAILBOX_SYNC); 0080 _completed(); 0081 return; 0082 } 0083 0084 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer())); 0085 Q_ASSERT(mailbox); 0086 TreeItemMsgList *msgList = dynamic_cast<TreeItemMsgList *>(mailbox->m_children[0]); 0087 Q_ASSERT(msgList); 0088 0089 msgList->setFetchStatus(TreeItem::LOADING); 0090 0091 Q_ASSERT(model->m_parsers.contains(parser)); 0092 0093 oldSyncState = model->cache()->mailboxSyncState(mailbox->mailbox()); 0094 bool hasQresync = model->accessParser(parser).capabilities.contains(QStringLiteral("QRESYNC")); 0095 if (hasQresync && oldSyncState.isUsableForCondstore()) { 0096 m_usingQresync = true; 0097 auto oldUidMap = model->cache()->uidMapping(mailbox->mailbox()); 0098 if (oldUidMap.isEmpty()) { 0099 selectCmd = parser->selectQresync(mailbox->mailbox(), oldSyncState.uidValidity(), 0100 oldSyncState.highestModSeq()); 0101 } else { 0102 Sequence knownSeq, knownUid; 0103 int i = oldUidMap.size() / 2; 0104 while (i < oldUidMap.size()) { 0105 // Message sequence number is one-based, our indexes are zero-based 0106 knownSeq.add(i + 1); 0107 knownUid.add(oldUidMap[i]); 0108 i += (oldUidMap.size() - i) / 2 + 1; 0109 } 0110 // We absolutely want to maintain a complete UID->seq mapping at all times, which is why the known-uids shall remain 0111 // empty to indicate "anything". 0112 selectCmd = parser->selectQresync(mailbox->mailbox(), oldSyncState.uidValidity(), 0113 oldSyncState.highestModSeq(), Sequence(), knownSeq, knownUid); 0114 } 0115 } else if (model->accessParser(parser).capabilities.contains(QStringLiteral("CONDSTORE"))) { 0116 selectCmd = parser->select(mailbox->mailbox(), QList<QByteArray>() << "CONDSTORE"); 0117 } else { 0118 selectCmd = parser->select(mailbox->mailbox()); 0119 } 0120 if (hasQresync && model->accessParser(parser).connState > CONN_STATE_AUTHENTICATED) { 0121 // The CLOSED response code is defined in RFC 5162. It should be sent out even if the client does not actually use 0122 // the QRESYNC extension (such as when syncing a mailbox for the first time). 0123 // There will, however, be no CLOSED if no mailbox was selected previously, of course. 0124 model->changeConnectionState(parser, CONN_STATE_SELECTING_WAIT_FOR_CLOSE); 0125 } else { 0126 model->changeConnectionState(parser, CONN_STATE_SELECTING); 0127 } 0128 mailbox->syncState = SyncState(); 0129 status = STATE_SELECTING; 0130 log(QStringLiteral("Synchronizing mailbox"), Common::LOG_MAILBOX_SYNC); 0131 emit model->mailboxSyncingProgress(mailboxIndex, status); 0132 } 0133 0134 bool ObtainSynchronizedMailboxTask::handleStateHelper(const Imap::Responses::State *const resp) 0135 { 0136 if (dieIfInvalidMailbox()) 0137 return true; 0138 0139 if (handleResponseCodeInsideState(resp)) 0140 return true; 0141 0142 if (resp->tag.isEmpty()) 0143 return false; 0144 0145 if (_dead) { 0146 _failed(tr("Asked to die")); 0147 return true; 0148 } 0149 // We absolutely have to ignore the abort() request 0150 0151 if (resp->tag == selectCmd) { 0152 0153 if (resp->kind == Responses::OK) { 0154 Q_ASSERT(status == STATE_SELECTING); 0155 switch (model->accessParser(parser).connState) { 0156 case CONN_STATE_SELECTING_WAIT_FOR_CLOSE: 0157 throw UnexpectedResponseReceived("Server did not send the CLOSED response code to notify us that " 0158 "the previous mailbox was successfully closed. Stopping the sync to prevent data loss."); 0159 case CONN_STATE_SELECTING: 0160 // this is what we want 0161 break; 0162 default: 0163 throw UnexpectedResponseReceived("Wrong connection state -- how come that a mailbox was opened in this moment?"); 0164 } 0165 finalizeSelect(); 0166 } else { 0167 _failed(QLatin1String("SELECT failed: ") + resp->message); 0168 model->changeConnectionState(parser, CONN_STATE_AUTHENTICATED); 0169 } 0170 return true; 0171 } else if (resp->tag == uidSyncingCmd) { 0172 0173 if (resp->kind == Responses::OK) { 0174 // FIXME: move the finalizeSearch() here to support working with split SEARCH reposnes -- but beware of 0175 // arrivals/expunges which happen while the UID SEARCH is in progres... 0176 log(QStringLiteral("UIDs synchronized"), Common::LOG_MAILBOX_SYNC); 0177 Q_ASSERT(status == STATE_SYNCING_FLAGS); 0178 Q_ASSERT(mailboxIndex.isValid()); // FIXME 0179 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer())); 0180 Q_ASSERT(mailbox); 0181 syncFlags(mailbox); 0182 } else { 0183 _failed(QLatin1String("UID syncing failed: ") + resp->message); 0184 // FIXME: UNSELECT? 0185 } 0186 return true; 0187 } else if (resp->tag == flagsCmd) { 0188 0189 if (resp->kind == Responses::OK) { 0190 //qDebug() << "received OK for flagsCmd"; 0191 Q_ASSERT(status == STATE_SYNCING_FLAGS); 0192 Q_ASSERT(mailboxIndex.isValid()); 0193 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer())); 0194 Q_ASSERT(mailbox); 0195 status = STATE_DONE; 0196 log(QStringLiteral("Flags synchronized"), Common::LOG_MAILBOX_SYNC); 0197 notifyInterestingMessages(mailbox); 0198 flagsCmd.clear(); 0199 0200 if (newArrivalsFetch.isEmpty()) { 0201 mailbox->saveSyncStateAndUids(model); 0202 model->changeConnectionState(parser, CONN_STATE_SELECTED); 0203 _completed(); 0204 } else { 0205 log(QStringLiteral("Pending new arrival fetching, not terminating yet"), Common::LOG_MAILBOX_SYNC); 0206 } 0207 } else { 0208 status = STATE_DONE; 0209 _failed(QLatin1String("Flags synchronization failed: ") + resp->message); 0210 // FIXME: UNSELECT? 0211 } 0212 emit model->mailboxSyncingProgress(mailboxIndex, status); 0213 return true; 0214 } else if (newArrivalsFetch.contains(resp->tag)) { 0215 0216 if (resp->kind == Responses::OK) { 0217 newArrivalsFetch.removeOne(resp->tag); 0218 0219 if (newArrivalsFetch.isEmpty() && status == STATE_DONE && flagsCmd.isEmpty()) { 0220 Q_ASSERT(mailboxIndex.isValid()); 0221 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer())); 0222 Q_ASSERT(mailbox); 0223 mailbox->saveSyncStateAndUids(model); 0224 model->changeConnectionState(parser, CONN_STATE_SELECTED); 0225 _completed(); 0226 } 0227 } else { 0228 _failed(QLatin1String("UID discovery of new arrivals after initial UID sync has failed: ") + resp->message); 0229 // FIXME: UNSELECT? 0230 } 0231 return true; 0232 0233 } else { 0234 return false; 0235 } 0236 } 0237 0238 void ObtainSynchronizedMailboxTask::finalizeSelect() 0239 { 0240 Q_ASSERT(mailboxIndex.isValid()); 0241 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer())); 0242 Q_ASSERT(mailbox); 0243 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(mailbox->m_children[ 0 ]); 0244 Q_ASSERT(list); 0245 0246 model->changeConnectionState(parser, CONN_STATE_SYNCING); 0247 const SyncState &syncState = mailbox->syncState; 0248 oldSyncState = model->cache()->mailboxSyncState(mailbox->mailbox()); 0249 list->m_totalMessageCount = syncState.exists(); 0250 // Note: syncState.unSeen() is the NUMBER of the first unseen message, not their count! 0251 0252 uidMap = model->cache()->uidMapping(mailbox->mailbox()); 0253 0254 if (static_cast<uint>(uidMap.size()) != oldSyncState.exists()) { 0255 0256 QString buf; 0257 QDebug dbg(&buf); 0258 dbg << "Inconsistent cache data, falling back to full sync (" << uidMap.size() << "in UID map," << oldSyncState.exists() << 0259 "EXIST before)"; 0260 log(buf, Common::LOG_MAILBOX_SYNC); 0261 oldSyncState.setHighestModSeq(0); 0262 fullMboxSync(mailbox, list); 0263 } else { 0264 if (syncState.isUsableForSyncing() && oldSyncState.isUsableForSyncing() && syncState.uidValidity() == oldSyncState.uidValidity()) { 0265 // Perform a nice re-sync 0266 0267 // Check the QRESYNC support and availability 0268 if (m_usingQresync && oldSyncState.isUsableForCondstore() && syncState.isUsableForCondstore()) { 0269 // Looks like we can use QRESYNC for fast syncing 0270 if (oldSyncState.highestModSeq() > syncState.highestModSeq()) { 0271 // Looks like a corrupted cache or a server's bug 0272 log(QStringLiteral("Yuck, recycled HIGHESTMODSEQ when trying to use QRESYNC"), Common::LOG_MAILBOX_SYNC); 0273 mailbox->syncState.setHighestModSeq(0); 0274 model->cache()->clearAllMessages(mailbox->mailbox()); 0275 m_usingQresync = false; 0276 fullMboxSync(mailbox, list); 0277 } else { 0278 if (oldSyncState.highestModSeq() == syncState.highestModSeq()) { 0279 if (oldSyncState.exists() != syncState.exists()) { 0280 log(QStringLiteral("Sync error: QRESYNC says no changes but EXISTS has changed"), Common::LOG_MAILBOX_SYNC); 0281 mailbox->syncState.setHighestModSeq(0); 0282 model->cache()->clearAllMessages(mailbox->mailbox()); 0283 m_usingQresync = false; 0284 fullMboxSync(mailbox, list); 0285 } else if (oldSyncState.uidNext() != syncState.uidNext()) { 0286 log(QStringLiteral("Sync error: QRESYNC says no changes but UIDNEXT has changed"), Common::LOG_MAILBOX_SYNC); 0287 mailbox->syncState.setHighestModSeq(0); 0288 model->cache()->clearAllMessages(mailbox->mailbox()); 0289 m_usingQresync = false; 0290 fullMboxSync(mailbox, list); 0291 } else if (syncState.exists() != static_cast<uint>(list->m_children.size())) { 0292 log(QString::fromUtf8("Sync error: constant HIGHESTMODSEQ, EXISTS says %1 messages but in fact " 0293 "there are %2 when finalizing SELECT") 0294 .arg(QString::number(mailbox->syncState.exists()), QString::number(list->m_children.size())), 0295 Common::LOG_MAILBOX_SYNC); 0296 mailbox->syncState.setHighestModSeq(0); 0297 model->cache()->clearAllMessages(mailbox->mailbox()); 0298 m_usingQresync = false; 0299 fullMboxSync(mailbox, list); 0300 } else { 0301 // This should be enough 0302 list->setFetchStatus(TreeItem::DONE); 0303 notifyInterestingMessages(mailbox); 0304 mailbox->saveSyncStateAndUids(model); 0305 model->changeConnectionState(parser, CONN_STATE_SELECTED); 0306 _completed(); 0307 } 0308 return; 0309 } 0310 0311 if (static_cast<uint>(list->m_children.size()) != mailbox->syncState.exists()) { 0312 log(QStringLiteral("Sync error: EXISTS says %1 messages, msgList has %2") 0313 .arg(QString::number(mailbox->syncState.exists()), QString::number(list->m_children.size()))); 0314 mailbox->syncState.setHighestModSeq(0); 0315 model->cache()->clearAllMessages(mailbox->mailbox()); 0316 m_usingQresync = false; 0317 fullMboxSync(mailbox, list); 0318 return; 0319 } 0320 0321 0322 if (oldSyncState.uidNext() < syncState.uidNext()) { 0323 list->setFetchStatus(TreeItem::DONE); 0324 int seqWithLowestUnknownUid = -1; 0325 for (int i = 0; i < list->m_children.size(); ++i) { 0326 TreeItemMessage *msg = static_cast<TreeItemMessage*>(list->m_children[i]); 0327 if (!msg->uid()) { 0328 seqWithLowestUnknownUid = i; 0329 break; 0330 } 0331 } 0332 if (seqWithLowestUnknownUid >= 0) { 0333 // We've got some new arrivals, but unfortunately QRESYNC won't report them just yet :( 0334 CommandHandle fetchCmd = parser->uidFetch(Sequence::startingAt(qMax(oldSyncState.uidNext(), 1u)), 0335 QList<QByteArray>() << "FLAGS"); 0336 newArrivalsFetch.append(fetchCmd); 0337 status = STATE_DONE; 0338 } else { 0339 // All UIDs are known at this point, including the new arrivals, yay 0340 notifyInterestingMessages(mailbox); 0341 mailbox->saveSyncStateAndUids(model); 0342 model->changeConnectionState(parser, CONN_STATE_SELECTED); 0343 _completed(); 0344 } 0345 } else { 0346 // This should be enough, the server should've sent the data already 0347 list->setFetchStatus(TreeItem::DONE); 0348 notifyInterestingMessages(mailbox); 0349 mailbox->saveSyncStateAndUids(model); 0350 model->changeConnectionState(parser, CONN_STATE_SELECTED); 0351 _completed(); 0352 } 0353 } 0354 return; 0355 } 0356 0357 if (syncState.exists() == 0) { 0358 // This is a special case, the mailbox doesn't contain any messages now. 0359 // Let's just save ourselves some work and reuse the "smart" code in the fullMboxSync() here, it will 0360 // do the right thing. 0361 fullMboxSync(mailbox, list); 0362 return; 0363 } 0364 0365 if (syncState.uidNext() == oldSyncState.uidNext()) { 0366 // No new messages 0367 0368 if (syncState.exists() == oldSyncState.exists()) { 0369 // No deletions, either, so we resync only flag changes 0370 syncNoNewNoDeletions(mailbox, list); 0371 } else { 0372 // Some messages got deleted, but there have been no additions 0373 syncGeneric(mailbox, list); 0374 } 0375 0376 } else if (syncState.uidNext() > oldSyncState.uidNext()) { 0377 // Some new messages were delivered since we checked the last time. 0378 // There's no guarantee they are still present, though. 0379 0380 if (syncState.uidNext() - oldSyncState.uidNext() == syncState.exists() - oldSyncState.exists()) { 0381 // Only some new arrivals, no deletions 0382 syncOnlyAdditions(mailbox, list); 0383 } else { 0384 // Generic case; we don't know anything about which messages were deleted and which added 0385 syncGeneric(mailbox, list); 0386 } 0387 } else { 0388 // The UIDNEXT has decreased while UIDVALIDITY remains the same. This is forbidden, 0389 // so either a server's bug, or a completely invalid cache. 0390 Q_ASSERT(syncState.uidNext() < oldSyncState.uidNext()); 0391 Q_ASSERT(syncState.uidValidity() == oldSyncState.uidValidity()); 0392 log(QStringLiteral("Yuck, UIDVALIDITY remains same but UIDNEXT decreased"), Common::LOG_MAILBOX_SYNC); 0393 model->cache()->clearAllMessages(mailbox->mailbox()); 0394 fullMboxSync(mailbox, list); 0395 } 0396 } else if (oldSyncState.isUsableForSyncingWithoutUidNext() && syncState.isUsableForSyncingWithoutUidNext() && oldSyncState.uidValidity() == syncState.uidValidity()) { 0397 log(QStringLiteral("Did not receive UIDNEXT, but UIDVALIDITY remains same -> trying non-destructive generic sync")); 0398 syncGeneric(mailbox, list); 0399 } else { 0400 // Forget everything, do a dumb sync 0401 model->cache()->clearAllMessages(mailbox->mailbox()); 0402 fullMboxSync(mailbox, list); 0403 } 0404 } 0405 } 0406 0407 void ObtainSynchronizedMailboxTask::fullMboxSync(TreeItemMailbox *mailbox, TreeItemMsgList *list) 0408 { 0409 log(QStringLiteral("Full synchronization"), Common::LOG_MAILBOX_SYNC); 0410 0411 QModelIndex parent = list->toIndex(model); 0412 if (! list->m_children.isEmpty()) { 0413 model->beginRemoveRows(parent, 0, list->m_children.size() - 1); 0414 auto oldItems = list->m_children; 0415 list->m_children.clear(); 0416 model->endRemoveRows(); 0417 qDeleteAll(oldItems); 0418 } 0419 if (mailbox->syncState.exists()) { 0420 list->m_children.reserve(mailbox->syncState.exists()); 0421 model->beginInsertRows(parent, 0, mailbox->syncState.exists() - 1); 0422 for (uint i = 0; i < mailbox->syncState.exists(); ++i) { 0423 TreeItemMessage *msg = new TreeItemMessage(list); 0424 msg->m_offset = i; 0425 list->m_children << msg; 0426 } 0427 model->endInsertRows(); 0428 0429 syncUids(mailbox); 0430 list->m_numberFetchingStatus = TreeItem::LOADING; 0431 list->m_unreadMessageCount = 0; 0432 } else { 0433 // No messages, we're done here 0434 list->m_totalMessageCount = 0; 0435 list->m_unreadMessageCount = 0; 0436 list->m_numberFetchingStatus = TreeItem::DONE; 0437 list->setFetchStatus(TreeItem::DONE); 0438 0439 // The remote mailbox is empty -> we're done now 0440 model->changeConnectionState(parser, CONN_STATE_SELECTED); 0441 status = STATE_DONE; 0442 emit model->mailboxSyncingProgress(mailboxIndex, status); 0443 notifyInterestingMessages(mailbox); 0444 mailbox->saveSyncStateAndUids(model); 0445 model->changeConnectionState(parser, CONN_STATE_SELECTED); 0446 // Take care here: this call could invalidate our index (see test coverage) 0447 _completed(); 0448 } 0449 // Our mailbox might have actually been invalidated by various callbacks activated above 0450 if (mailboxIndex.isValid()) { 0451 Q_ASSERT(mailboxIndex.internalPointer() == mailbox); 0452 model->emitMessageCountChanged(mailbox); 0453 } 0454 } 0455 0456 void ObtainSynchronizedMailboxTask::syncNoNewNoDeletions(TreeItemMailbox *mailbox, TreeItemMsgList *list) 0457 { 0458 Q_ASSERT(mailbox->syncState.exists() == static_cast<uint>(uidMap.size())); 0459 log(QStringLiteral("No arrivals or deletions since the last time"), Common::LOG_MAILBOX_SYNC); 0460 if (mailbox->syncState.exists()) { 0461 // Verify that we indeed have all UIDs and not need them anymore 0462 #ifndef QT_NO_DEBUG 0463 for (int i = 0; i < list->m_children.size(); ++i) { 0464 // FIXME: This assert can fail if the mailbox contained messages with missing UIDs even before we opened it now. 0465 Q_ASSERT(static_cast<TreeItemMessage *>(list->m_children[i])->uid()); 0466 } 0467 #endif 0468 } else { 0469 list->m_unreadMessageCount = 0; 0470 list->m_totalMessageCount = 0; 0471 list->m_numberFetchingStatus = TreeItem::DONE; 0472 } 0473 0474 if (list->m_children.isEmpty()) { 0475 TreeItemChildrenList messages; 0476 list->m_children.reserve(mailbox->syncState.exists()); 0477 for (uint i = 0; i < mailbox->syncState.exists(); ++i) { 0478 TreeItemMessage *msg = new TreeItemMessage(list); 0479 msg->m_offset = i; 0480 msg->m_uid = uidMap[ i ]; 0481 messages << msg; 0482 } 0483 list->setChildren(messages); 0484 0485 } else { 0486 if (mailbox->syncState.exists() != static_cast<uint>(list->m_children.size())) { 0487 throw CantHappen("TreeItemMsgList has wrong number of " 0488 "children, even though no change of " 0489 "message count occurred"); 0490 } 0491 } 0492 0493 list->setFetchStatus(TreeItem::DONE); 0494 0495 if (mailbox->syncState.exists()) { 0496 syncFlags(mailbox); 0497 } else { 0498 status = STATE_DONE; 0499 emit model->mailboxSyncingProgress(mailboxIndex, status); 0500 notifyInterestingMessages(mailbox); 0501 0502 if (newArrivalsFetch.isEmpty()) { 0503 mailbox->saveSyncStateAndUids(model); 0504 model->changeConnectionState(parser, CONN_STATE_SELECTED); 0505 _completed(); 0506 } 0507 } 0508 } 0509 0510 void ObtainSynchronizedMailboxTask::syncOnlyAdditions(TreeItemMailbox *mailbox, TreeItemMsgList *list) 0511 { 0512 log(QStringLiteral("Syncing new arrivals"), Common::LOG_MAILBOX_SYNC); 0513 0514 // So, we know that messages only got added to the mailbox and that none were removed, 0515 // neither those that we already know or those that got added while we weren't around. 0516 // Therefore we ask only for UIDs of new messages 0517 0518 firstUnknownUidOffset = oldSyncState.exists(); 0519 list->m_numberFetchingStatus = TreeItem::LOADING; 0520 uidSyncingMode = UID_SYNC_ONLY_NEW; 0521 syncUids(mailbox, oldSyncState.uidNext()); 0522 } 0523 0524 void ObtainSynchronizedMailboxTask::syncGeneric(TreeItemMailbox *mailbox, TreeItemMsgList *list) 0525 { 0526 log(QStringLiteral("generic synchronization from previous state"), Common::LOG_MAILBOX_SYNC); 0527 0528 list->m_numberFetchingStatus = TreeItem::LOADING; 0529 list->m_unreadMessageCount = 0; 0530 uidSyncingMode = UID_SYNC_ALL; 0531 syncUids(mailbox); 0532 } 0533 0534 void ObtainSynchronizedMailboxTask::syncUids(TreeItemMailbox *mailbox, const uint lowestUidToQuery) 0535 { 0536 status = STATE_SYNCING_UIDS; 0537 log(QStringLiteral("Syncing UIDs"), Common::LOG_MAILBOX_SYNC); 0538 QByteArray uidSpecification; 0539 if (lowestUidToQuery == 0) { 0540 uidSpecification = "ALL"; 0541 } else { 0542 uidSpecification = QStringLiteral("UID %1:*").arg(QString::number(lowestUidToQuery)).toUtf8(); 0543 } 0544 uidMap.clear(); 0545 if (model->accessParser(parser).capabilities.contains(QStringLiteral("ESEARCH"))) { 0546 uidSyncingCmd = parser->uidESearchUid(uidSpecification); 0547 } else { 0548 uidSyncingCmd = parser->uidSearchUid(uidSpecification); 0549 } 0550 emit model->mailboxSyncingProgress(mailboxIndex, status); 0551 } 0552 0553 void ObtainSynchronizedMailboxTask::syncFlags(TreeItemMailbox *mailbox) 0554 { 0555 status = STATE_SYNCING_FLAGS; 0556 log(QStringLiteral("Syncing flags"), Common::LOG_MAILBOX_SYNC); 0557 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(mailbox->m_children[ 0 ]); 0558 Q_ASSERT(list); 0559 0560 // 0 => don't use it; >0 => use that as the old value 0561 quint64 useModSeq = 0; 0562 if ((model->accessParser(parser).capabilities.contains(QStringLiteral("CONDSTORE")) || 0563 model->accessParser(parser).capabilities.contains(QStringLiteral("QRESYNC"))) && 0564 oldSyncState.highestModSeq() > 0 && mailbox->syncState.isUsableForCondstore() && 0565 oldSyncState.uidValidity() == mailbox->syncState.uidValidity()) { 0566 // The CONDSTORE is available, UIDVALIDITY has not changed and the HIGHESTMODSEQ suggests that 0567 // it will be useful 0568 if (oldSyncState.highestModSeq() == mailbox->syncState.highestModSeq()) { 0569 // Looks like there were no changes in flags -- that's cool, we're done here, 0570 // but only after some sanity checks 0571 if (oldSyncState.exists() > mailbox->syncState.exists()) { 0572 log(QStringLiteral("Some messages have arrived to the mailbox, but HIGHESTMODSEQ hasn't changed. " 0573 "That's a bug in the server implementation."), Common::LOG_MAILBOX_SYNC); 0574 // will issue the ordinary FETCH command for FLAGS 0575 } else if (oldSyncState.uidNext() != mailbox->syncState.uidNext()) { 0576 log(QStringLiteral("UIDNEXT has changed, yet HIGHESTMODSEQ remained constant; that's server's bug"), Common::LOG_MAILBOX_SYNC); 0577 // and again, don't trust that HIGHESTMODSEQ 0578 } else { 0579 // According to HIGHESTMODSEQ, there hasn't been any change. UIDNEXT and EXISTS do not contradict 0580 // this interpretation, so we can go and call stuff finished. 0581 if (newArrivalsFetch.isEmpty()) { 0582 // No pending activity -> let's call it a day 0583 status = STATE_DONE; 0584 mailbox->saveSyncStateAndUids(model); 0585 model->changeConnectionState(parser, CONN_STATE_SELECTED); 0586 _completed(); 0587 return; 0588 } else { 0589 // ...but there's still some pending activity; let's wait for its termination 0590 status = STATE_DONE; 0591 } 0592 } 0593 } else if (oldSyncState.highestModSeq() > mailbox->syncState.highestModSeq()) { 0594 // Clearly a bug 0595 log(QStringLiteral("HIGHESTMODSEQ decreased, that's a bug in the IMAP server"), Common::LOG_MAILBOX_SYNC); 0596 // won't use HIGHESTMODSEQ 0597 } else { 0598 // Will use FETCH CHANGEDSINCE 0599 useModSeq = oldSyncState.highestModSeq(); 0600 } 0601 } 0602 if (useModSeq > 0) { 0603 QMap<QByteArray, quint64> fetchModifier; 0604 fetchModifier["CHANGEDSINCE"] = oldSyncState.highestModSeq(); 0605 flagsCmd = parser->fetch(Sequence(1, mailbox->syncState.exists()), QStringList() << QStringLiteral("FLAGS"), fetchModifier); 0606 } else { 0607 flagsCmd = parser->fetch(Sequence(1, mailbox->syncState.exists()), QStringList() << QStringLiteral("FLAGS")); 0608 } 0609 list->m_numberFetchingStatus = TreeItem::LOADING; 0610 emit model->mailboxSyncingProgress(mailboxIndex, status); 0611 } 0612 0613 bool ObtainSynchronizedMailboxTask::handleResponseCodeInsideState(const Imap::Responses::State *const resp) 0614 { 0615 if (dieIfInvalidMailbox()) 0616 return resp->tag.isEmpty(); 0617 0618 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex); 0619 Q_ASSERT(mailbox); 0620 switch (resp->respCode) { 0621 case Responses::UNSEEN: 0622 { 0623 const Responses::RespData<uint> *const num = dynamic_cast<const Responses::RespData<uint>* const>(resp->respCodeData.data()); 0624 if (num) { 0625 mailbox->syncState.setUnSeenOffset(num->data); 0626 return resp->tag.isEmpty(); 0627 } else { 0628 throw CantHappen("State response has invalid UNSEEN respCodeData", *resp); 0629 } 0630 } 0631 case Responses::PERMANENTFLAGS: 0632 { 0633 const Responses::RespData<QStringList> *const num = dynamic_cast<const Responses::RespData<QStringList>* const>(resp->respCodeData.data()); 0634 if (num) { 0635 mailbox->syncState.setPermanentFlags(num->data); 0636 return resp->tag.isEmpty(); 0637 } else { 0638 throw CantHappen("State response has invalid PERMANENTFLAGS respCodeData", *resp); 0639 } 0640 } 0641 case Responses::UIDNEXT: 0642 { 0643 const Responses::RespData<uint> *const num = dynamic_cast<const Responses::RespData<uint>* const>(resp->respCodeData.data()); 0644 if (num) { 0645 mailbox->syncState.setUidNext(num->data); 0646 return resp->tag.isEmpty(); 0647 } else { 0648 throw CantHappen("State response has invalid UIDNEXT respCodeData", *resp); 0649 } 0650 } 0651 case Responses::UIDVALIDITY: 0652 { 0653 const Responses::RespData<uint> *const num = dynamic_cast<const Responses::RespData<uint>* const>(resp->respCodeData.data()); 0654 if (num) { 0655 mailbox->syncState.setUidValidity(num->data); 0656 return resp->tag.isEmpty(); 0657 } else { 0658 throw CantHappen("State response has invalid UIDVALIDITY respCodeData", *resp); 0659 } 0660 } 0661 case Responses::NOMODSEQ: 0662 // NOMODSEQ means that this mailbox doesn't support CONDSTORE or QRESYNC. We have to avoid sending any fancy commands like 0663 // the FETCH CHANGEDSINCE etc. 0664 mailbox->syncState.setHighestModSeq(0); 0665 m_usingQresync = false; 0666 return resp->tag.isEmpty(); 0667 0668 case Responses::HIGHESTMODSEQ: 0669 { 0670 const Responses::RespData<quint64> *const num = dynamic_cast<const Responses::RespData<quint64>* const>(resp->respCodeData.data()); 0671 Q_ASSERT(num); 0672 mailbox->syncState.setHighestModSeq(num->data); 0673 return resp->tag.isEmpty(); 0674 } 0675 default: 0676 break; 0677 } 0678 return false; 0679 } 0680 0681 void ObtainSynchronizedMailboxTask::updateHighestKnownUid(TreeItemMailbox *mailbox, const TreeItemMsgList *list) const 0682 { 0683 uint highestKnownUid = 0; 0684 for (int i = list->m_children.size() - 1; ! highestKnownUid && i >= 0; --i) { 0685 highestKnownUid = static_cast<const TreeItemMessage *>(list->m_children[i])->uid(); 0686 } 0687 if (highestKnownUid) { 0688 // If the UID walk return a usable number, remember that and use it for updating our idea of the UIDNEXT 0689 mailbox->syncState.setUidNext(qMax(mailbox->syncState.uidNext(), highestKnownUid + 1)); 0690 } 0691 } 0692 0693 bool ObtainSynchronizedMailboxTask::handleNumberResponse(const Imap::Responses::NumberResponse *const resp) 0694 { 0695 if (dieIfInvalidMailbox()) 0696 return true; 0697 0698 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex); 0699 Q_ASSERT(mailbox); 0700 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(mailbox->m_children[0]); 0701 Q_ASSERT(list); 0702 switch (resp->kind) { 0703 case Imap::Responses::EXISTS: 0704 switch (status) { 0705 case STATE_WAIT_FOR_CONN: 0706 Q_ASSERT(false); 0707 return false; 0708 0709 case STATE_SELECTING: 0710 if (m_usingQresync) { 0711 // Because QRESYNC won't tell us anything about the new UIDs, we have to resort to this kludgy way of working. 0712 // I really, really wonder why there's no such thing like the ARRIVED to accompany VANISHED. Oh well. 0713 mailbox->syncState.setExists(resp->number); 0714 int newArrivals = resp->number - list->m_children.size(); 0715 if (newArrivals > 0) { 0716 // We have to add empty messages here 0717 QModelIndex parent = list->toIndex(model); 0718 int offset = list->m_children.size(); 0719 list->m_children.reserve(resp->number); 0720 model->beginInsertRows(parent, offset, resp->number - 1); 0721 for (int i = 0; i < newArrivals; ++i) { 0722 TreeItemMessage *msg = new TreeItemMessage(list); 0723 msg->m_offset = i + offset; 0724 list->m_children << msg; 0725 // yes, we really have to add this message with UID 0 :( 0726 } 0727 model->endInsertRows(); 0728 list->m_totalMessageCount = resp->number; 0729 } 0730 } else { 0731 // It's perfectly acceptable for the server to start its responses with EXISTS instead of UIDVALIDITY & UIDNEXT, so 0732 // we really cannot do anything besides remembering this value for later. 0733 mailbox->syncState.setExists(resp->number); 0734 } 0735 return true; 0736 0737 case STATE_SYNCING_UIDS: 0738 mailbox->handleExists(model, *resp); 0739 updateHighestKnownUid(mailbox, list); 0740 return true; 0741 0742 case STATE_SYNCING_FLAGS: 0743 case STATE_DONE: 0744 if (resp->number == static_cast<uint>(list->m_children.size())) { 0745 // no changes 0746 return true; 0747 } 0748 mailbox->handleExists(model, *resp); 0749 Q_ASSERT(list->m_children.size()); 0750 updateHighestKnownUid(mailbox, list); 0751 CommandHandle fetchCmd = parser->uidFetch(Sequence::startingAt( 0752 // prevent a possible invalid 0:* 0753 qMax(mailbox->syncState.uidNext(), 1u) 0754 ), QList<QByteArray>() << "FLAGS"); 0755 newArrivalsFetch.append(fetchCmd); 0756 return true; 0757 } 0758 Q_ASSERT(false); 0759 return false; 0760 0761 case Imap::Responses::EXPUNGE: 0762 0763 if (mailbox->syncState.exists() > 0) { 0764 // Always update the number of expected messages 0765 mailbox->syncState.setExists(mailbox->syncState.exists() - 1); 0766 } 0767 0768 switch (status) { 0769 case STATE_SYNCING_FLAGS: 0770 // The UID mapping has been already established, but we don't have enough information for 0771 // an atomic state transition yet 0772 mailbox->handleExpunge(model, *resp); 0773 // The SyncState and the UID map will be saved later, along with the flags, when this task finishes 0774 return true; 0775 0776 case STATE_DONE: 0777 // The UID mapping has been already established, so we just want to handle the EXPUNGE as usual 0778 mailbox->handleExpunge(model, *resp); 0779 mailbox->saveSyncStateAndUids(model); 0780 return true; 0781 0782 default: 0783 // This is handled by the code below 0784 break; 0785 } 0786 0787 // We shall track updates to the place where the unknown UIDs resign 0788 if (resp->number < firstUnknownUidOffset + 1) { 0789 // The message which we're deleting has UID which is already known, ie. it isn't among those whose UIDs got requested 0790 // by an incremental UID SEARCH 0791 Q_ASSERT(firstUnknownUidOffset > 0); 0792 --firstUnknownUidOffset; 0793 0794 // The deleted message has previously been present; that means that we shall immediately signal about its expunge 0795 mailbox->handleExpunge(model, *resp); 0796 } 0797 0798 switch(status) { 0799 case STATE_WAIT_FOR_CONN: 0800 Q_ASSERT(false); 0801 return false; 0802 0803 case STATE_SELECTING: 0804 // The actual change will be handled by the UID syncing code 0805 return true; 0806 0807 case STATE_SYNCING_UIDS: 0808 // We shouldn't delete stuff at this point, it will be handled by the UID syncing. 0809 // The response shall be consumed, though. 0810 return true; 0811 0812 case STATE_SYNCING_FLAGS: 0813 case STATE_DONE: 0814 // Already handled above 0815 Q_ASSERT(false); 0816 return false; 0817 0818 } 0819 0820 break; 0821 case Imap::Responses::RECENT: 0822 mailbox->syncState.setRecent(resp->number); 0823 list->m_recentMessageCount = resp->number; 0824 return true; 0825 break; 0826 default: 0827 throw CantHappen("Got a NumberResponse of invalid kind. This is supposed to be handled in its constructor!", *resp); 0828 } 0829 return false; 0830 } 0831 0832 bool ObtainSynchronizedMailboxTask::handleVanished(const Imap::Responses::Vanished *const resp) 0833 { 0834 if (dieIfInvalidMailbox()) 0835 return true; 0836 0837 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex); 0838 Q_ASSERT(mailbox); 0839 0840 switch (status) { 0841 case STATE_WAIT_FOR_CONN: 0842 Q_ASSERT(false); 0843 return false; 0844 0845 case STATE_SELECTING: 0846 case STATE_SYNCING_UIDS: 0847 case STATE_SYNCING_FLAGS: 0848 case STATE_DONE: 0849 mailbox->handleVanished(model, *resp); 0850 return true; 0851 } 0852 0853 Q_ASSERT(false); 0854 return false; 0855 } 0856 0857 bool ObtainSynchronizedMailboxTask::handleFlags(const Imap::Responses::Flags *const resp) 0858 { 0859 if (dieIfInvalidMailbox()) 0860 return true; 0861 0862 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex); 0863 Q_ASSERT(mailbox); 0864 mailbox->syncState.setFlags(resp->flags); 0865 return true; 0866 } 0867 0868 bool ObtainSynchronizedMailboxTask::handleSearch(const Imap::Responses::Search *const resp) 0869 { 0870 if (dieIfInvalidMailbox()) 0871 return true; 0872 0873 if (uidSyncingCmd.isEmpty()) 0874 return false; 0875 0876 Q_ASSERT(Model::mailboxForSomeItem(mailboxIndex)); 0877 0878 uidMap += resp->items; 0879 0880 finalizeSearch(); 0881 return true; 0882 } 0883 0884 bool ObtainSynchronizedMailboxTask::handleESearch(const Imap::Responses::ESearch *const resp) 0885 { 0886 if (dieIfInvalidMailbox()) 0887 return true; 0888 0889 if (resp->tag.isEmpty() || resp->tag != uidSyncingCmd) 0890 return false; 0891 0892 if (resp->seqOrUids != Imap::Responses::ESearch::UIDS) 0893 throw UnexpectedResponseReceived("ESEARCH response with matching tag uses sequence numbers instead of UIDs", *resp); 0894 0895 auto allComparator = [](const auto &listData) { return listData.first == "ALL"; }; 0896 auto listIterator = std::find_if(resp->listData.constBegin(), resp->listData.constEnd(), allComparator); 0897 0898 if (listIterator != resp->listData.constEnd()) { 0899 uidMap = listIterator->second; 0900 ++listIterator; 0901 if (std::find_if(listIterator, resp->listData.constEnd(), allComparator) != resp->listData.constEnd()) 0902 throw UnexpectedResponseReceived("ESEARCH contains the ALL key too many times", *resp); 0903 } else { 0904 // If the ALL key is not present, the server is telling us that there are no messages matching the query 0905 uidMap.clear(); 0906 } 0907 0908 finalizeSearch(); 0909 return true; 0910 } 0911 0912 bool ObtainSynchronizedMailboxTask::handleEnabled(const Responses::Enabled * const resp) 0913 { 0914 if (dieIfInvalidMailbox()) 0915 return false; 0916 0917 // This function is needed to work around a bug in Kolab's version of Cyrus which sometimes sends out untagged ENABLED 0918 // during the SELECT processing. RFC 5161 is pretty clear in saying that ENABLED shall be sent only in response to 0919 // the ENABLE command; the log submitted at https://bugs.kde.org/show_bug.cgi?id=329204#c5 shows that Trojita receives 0920 // an extra * ENABLED CONDSTORE QRESYNC even after we have issued the x ENABLE QRESYNC previously and the server already 0921 // replied with * ENABLED QRESYNC to that. 0922 if (resp->extensions.contains("CONDSTORE") || resp->extensions.contains("QRESYNC")) { 0923 return true; 0924 } 0925 0926 // In addition, https://bugs.kde.org/show_bug.cgi?id=350006 reports that Cyrus occasionally sends empty * ENABLED reponse. 0927 // At this point we don't have many options left, so let's just eat each and every ENABLED while we select mailboxes. 0928 // https://bugzilla.cyrusimap.org/show_bug.cgi?id=3898 0929 if (resp->extensions.isEmpty()) { 0930 return true; 0931 } 0932 0933 return false; 0934 } 0935 0936 /** @short Process the result of UID SEARCH or UID ESEARCH commands */ 0937 void ObtainSynchronizedMailboxTask::finalizeSearch() 0938 { 0939 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex); 0940 Q_ASSERT(mailbox); 0941 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList*>(mailbox->m_children[0]); 0942 Q_ASSERT(list); 0943 0944 switch (uidSyncingMode) { 0945 case UID_SYNC_ALL: 0946 if (static_cast<uint>(uidMap.size()) != mailbox->syncState.exists()) { 0947 // The (possibly updated) EXISTS does not match what we received for UID (E)SEARCH ALL. Please note that 0948 // it's the server's responsibility to feed us with valid data; scenarios like sending out-of-order responses 0949 // would clearly break this contract. 0950 std::ostringstream ss; 0951 ss << "Error when synchronizing all messages: server said that there are " << mailbox->syncState.exists() << 0952 " messages, but UID (E)SEARCH ALL response contains " << uidMap.size() << " entries" << std::endl; 0953 ss.flush(); 0954 throw MailboxException(ss.str().c_str()); 0955 } 0956 break; 0957 case UID_SYNC_ONLY_NEW: 0958 { 0959 // Be sure there really are some new messages 0960 const int newArrivals = mailbox->syncState.exists() - firstUnknownUidOffset; 0961 Q_ASSERT(newArrivals >= 0); 0962 0963 if (newArrivals != uidMap.size()) { 0964 std::ostringstream ss; 0965 ss << "Error when synchronizing new messages: server said that there are " << mailbox->syncState.exists() << 0966 " messages in total (" << newArrivals << " new), but UID (E)SEARCH response contains " << uidMap.size() << 0967 " entries" << std::endl; 0968 ss.flush(); 0969 throw MailboxException(ss.str().c_str()); 0970 } 0971 break; 0972 } 0973 } 0974 0975 std::sort(uidMap.begin(), uidMap.end()); 0976 if (!uidMap.isEmpty() && uidMap.front() == 0) { 0977 throw MailboxException("UID (E)SEARCH response contains invalid UID zero"); 0978 } 0979 applyUids(mailbox); 0980 uidMap.clear(); 0981 updateHighestKnownUid(mailbox, list); 0982 status = STATE_SYNCING_FLAGS; 0983 } 0984 0985 bool ObtainSynchronizedMailboxTask::handleFetch(const Imap::Responses::Fetch *const resp) 0986 { 0987 if (dieIfInvalidMailbox()) 0988 return true; 0989 0990 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex); 0991 Q_ASSERT(mailbox); 0992 QList<TreeItemPart *> changedParts; 0993 TreeItemMessage *changedMessage = 0; 0994 mailbox->handleFetchResponse(model, *resp, changedParts, changedMessage, m_usingQresync); 0995 if (changedMessage) { 0996 QModelIndex index = changedMessage->toIndex(model); 0997 emit model->dataChanged(index, index); 0998 if (mailbox->syncState.uidNext() <= changedMessage->uid()) { 0999 mailbox->syncState.setUidNext(changedMessage->uid() + 1); 1000 } 1001 // On the other hand, this one will be emitted at the very end 1002 // model->emitMessageCountChanged(mailbox); 1003 } 1004 if (!changedParts.isEmpty() && !m_usingQresync) { 1005 // On the other hand, with QRESYNC our code is ready to receive extra data that changes body parts... 1006 qDebug() << "Weird, FETCH when syncing has changed some body parts. We aren't ready for that."; 1007 log(QStringLiteral("This response has changed some message parts. That should not have happened, as we're still syncing.")); 1008 } 1009 return true; 1010 } 1011 1012 /** @short Apply the received UID map to the messages in mailbox 1013 1014 The @arg firstUnknownUidOffset corresponds to the offset of a message whose UID is specified by the first item in the UID map. 1015 */ 1016 void ObtainSynchronizedMailboxTask::applyUids(TreeItemMailbox *mailbox) 1017 { 1018 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(mailbox->m_children[0]); 1019 Q_ASSERT(list); 1020 QModelIndex parent = list->toIndex(model); 1021 list->m_children.reserve(mailbox->syncState.exists()); 1022 1023 int i = firstUnknownUidOffset; 1024 while (i < uidMap.size() + static_cast<int>(firstUnknownUidOffset)) { 1025 // Index inside the uidMap in which the UID of a message at offset i in the list->m_children can be found 1026 int uidOffset = i - firstUnknownUidOffset; 1027 Q_ASSERT(uidOffset >= 0); 1028 Q_ASSERT(uidOffset < uidMap.size()); 1029 1030 // For each UID which is really supposed to be there... 1031 1032 Q_ASSERT(i <= list->m_children.size()); 1033 if (i == list->m_children.size()) { 1034 // now we're just adding new messages to the end of the list 1035 const int futureTotalMessages = mailbox->syncState.exists(); 1036 model->beginInsertRows(parent, i, futureTotalMessages - 1); 1037 for (/*nothing*/; i < futureTotalMessages; ++i) { 1038 // Add all messages in one go 1039 TreeItemMessage *msg = new TreeItemMessage(list); 1040 msg->m_offset = i; 1041 // We're iterating with i, so we got to update the uidOffset 1042 uidOffset = i - firstUnknownUidOffset; 1043 Q_ASSERT(uidOffset >= 0); 1044 Q_ASSERT(uidOffset < uidMap.size()); 1045 msg->m_uid = uidMap[uidOffset]; 1046 list->m_children << msg; 1047 } 1048 model->endInsertRows(); 1049 Q_ASSERT(i == list->m_children.size()); 1050 Q_ASSERT(i == futureTotalMessages); 1051 } else if (static_cast<TreeItemMessage *>(list->m_children[i])->m_uid == uidMap[uidOffset]) { 1052 // If the UID of the "current message" matches, we're okay 1053 static_cast<TreeItemMessage *>(list->m_children[i])->m_offset = i; 1054 ++i; 1055 } else if (static_cast<TreeItemMessage *>(list->m_children[i])->m_uid == 0) { 1056 // If the UID of the "current message" is zero, replace that with this message 1057 TreeItemMessage *msg = static_cast<TreeItemMessage*>(list->m_children[i]); 1058 msg->m_uid = uidMap[uidOffset]; 1059 msg->m_offset = i; 1060 QModelIndex idx = model->createIndex(i, 0, msg); 1061 emit model->dataChanged(idx, idx); 1062 if (msg->accessFetchStatus() == TreeItem::LOADING) { 1063 // We've got to ask for the message metadata once again; the first attempt happened when the UID was still zero, 1064 // so this is our chance 1065 model->askForMsgMetadata(msg, Model::PRELOAD_PER_POLICY); 1066 } 1067 ++i; 1068 } else { 1069 // We've got an UID mismatch 1070 int pos = i; 1071 while (pos < list->m_children.size()) { 1072 // Remove any messages which have non-zero UID which is at the same time different than the UID we want to add 1073 // The key idea here is that IMAP guarantees that each and every new message will have greater UID than any 1074 // other message already in the mailbox. Just for the sake of completeness, should an evil server send us a 1075 // malformed response, we wouldn't care (or notice at this point), we'd just "needlessly" delete many "innocent" 1076 // messages due to that one out-of-place arrival -- but we'd still remain correct and not crash. 1077 TreeItemMessage *otherMessage = static_cast<TreeItemMessage*>(list->m_children[pos]); 1078 if (otherMessage->m_uid != 0 && otherMessage->m_uid != uidMap[uidOffset]) { 1079 model->cache()->clearMessage(mailbox->mailbox(), otherMessage->uid()); 1080 ++pos; 1081 } else { 1082 break; 1083 } 1084 } 1085 Q_ASSERT(pos > i); 1086 model->beginRemoveRows(parent, i, pos - 1); 1087 TreeItemChildrenList removedItems = list->m_children.mid(i, pos - i); 1088 list->m_children.erase(list->m_children.begin() + i, list->m_children.begin() + pos); 1089 model->endRemoveRows(); 1090 // the m_offset of all subsequent messages will be updated later, at the time *they* are processed 1091 qDeleteAll(removedItems); 1092 if (i == list->m_children.size()) { 1093 // We're asked to add messages to the end of the list. That's something that's already implemented above, 1094 // so let's reuse that code. That's why we do *not* want to increment the counter here. 1095 } else { 1096 Q_ASSERT(i < list->m_children.size()); 1097 // But this case is also already implemented above, so we won't touch the counter from here, either, 1098 // and let the existing code do its job 1099 } 1100 } 1101 } 1102 1103 if (i != list->m_children.size()) { 1104 // remove items at the end 1105 model->beginRemoveRows(parent, i, list->m_children.size() - 1); 1106 TreeItemChildrenList removedItems = list->m_children.mid(i); 1107 list->m_children.erase(list->m_children.begin() + i, list->m_children.end()); 1108 model->endRemoveRows(); 1109 qDeleteAll(removedItems); 1110 } 1111 1112 uidMap.clear(); 1113 1114 list->m_totalMessageCount = list->m_children.size(); 1115 list->setFetchStatus(TreeItem::DONE); 1116 1117 model->emitMessageCountChanged(mailbox); 1118 model->changeConnectionState(parser, CONN_STATE_SELECTED); 1119 } 1120 1121 QString ObtainSynchronizedMailboxTask::debugIdentification() const 1122 { 1123 if (! mailboxIndex.isValid()) 1124 return QStringLiteral("[invalid mailboxIndex]"); 1125 1126 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer())); 1127 Q_ASSERT(mailbox); 1128 1129 QString statusStr; 1130 switch (status) { 1131 case STATE_WAIT_FOR_CONN: 1132 statusStr = QStringLiteral("STATE_WAIT_FOR_CONN"); 1133 break; 1134 case STATE_SELECTING: 1135 statusStr = QStringLiteral("STATE_SELECTING"); 1136 break; 1137 case STATE_SYNCING_UIDS: 1138 statusStr = QStringLiteral("STATE_SYNCING_UIDS"); 1139 break; 1140 case STATE_SYNCING_FLAGS: 1141 statusStr = QStringLiteral("STATE_SYNCING_FLAGS"); 1142 break; 1143 case STATE_DONE: 1144 statusStr = QStringLiteral("STATE_DONE"); 1145 break; 1146 } 1147 return QStringLiteral("%1 %2").arg(statusStr, mailbox->mailbox()); 1148 } 1149 1150 void ObtainSynchronizedMailboxTask::notifyInterestingMessages(TreeItemMailbox *mailbox) 1151 { 1152 Q_ASSERT(mailbox); 1153 TreeItemMsgList *list = dynamic_cast<Imap::Mailbox::TreeItemMsgList *>(mailbox->m_children[0]); 1154 Q_ASSERT(list); 1155 list->recalcVariousMessageCounts(model); 1156 QModelIndex listIndex = list->toIndex(model); 1157 Q_ASSERT(listIndex.isValid()); 1158 QModelIndex firstInterestingMessage = model->index( 1159 // remember, the offset has one-based indexing 1160 mailbox->syncState.unSeenOffset() ? mailbox->syncState.unSeenOffset() - 1 : 0, 0, listIndex); 1161 if (!firstInterestingMessage.data(RoleMessageIsMarkedRecent).toBool() && 1162 firstInterestingMessage.data(RoleMessageIsMarkedRead).toBool()) { 1163 // Clearly the reported value is utter nonsense. Let's just scroll to the end instead 1164 int offset = model->rowCount(listIndex) - 1; 1165 log(QStringLiteral("\"First interesting message\" doesn't look terribly interesting (%1), scrolling to the end at %2 instead") 1166 .arg(firstInterestingMessage.data(RoleMessageFlags).toStringList().join(QStringLiteral(", ")), 1167 QString::number(offset)), Common::LOG_MAILBOX_SYNC); 1168 firstInterestingMessage = model->index(offset, 0, listIndex); 1169 } else { 1170 log(QStringLiteral("First interesting message at %1 (%2)") 1171 .arg(QString::number(mailbox->syncState.unSeenOffset()), 1172 firstInterestingMessage.data(RoleMessageFlags).toStringList().join(QStringLiteral(", ")) 1173 ), Common::LOG_MAILBOX_SYNC); 1174 } 1175 emit model->mailboxFirstUnseenMessage(mailbox->toIndex(model), firstInterestingMessage); 1176 } 1177 1178 bool ObtainSynchronizedMailboxTask::dieIfInvalidMailbox() 1179 { 1180 if (mailboxIndex.isValid()) 1181 return false; 1182 1183 // OK, so we are in trouble -- our mailbox has disappeared, but the IMAP server will likely keep us busy with its 1184 // status updates. This is bad, so we have to get out as fast as possible. All hands, evasive maneuvers! 1185 1186 log(QStringLiteral("Mailbox disappeared"), Common::LOG_MAILBOX_SYNC); 1187 1188 if (!unSelectTask) { 1189 unSelectTask = model->m_taskFactory->createUnSelectTask(model, this); 1190 connect(unSelectTask, &ImapTask::completed, this, &ObtainSynchronizedMailboxTask::slotUnSelectCompleted); 1191 unSelectTask->perform(); 1192 } 1193 1194 return true; 1195 } 1196 1197 void ObtainSynchronizedMailboxTask::slotUnSelectCompleted() 1198 { 1199 // Now, just finish and signal a failure 1200 _failed(tr("Escaped from mailbox")); 1201 } 1202 1203 QVariant ObtainSynchronizedMailboxTask::taskData(const int role) const 1204 { 1205 return role == RoleTaskCompactName ? QVariant(tr("Synchronizing mailbox")) : QVariant(); 1206 } 1207 1208 /** @short Let the model know that a mailbox synchronization has failed */ 1209 void ObtainSynchronizedMailboxTask::signalSyncFailure(const QString &message) 1210 { 1211 if (!mailboxIndex.isValid()) { 1212 // Well, that mailbox is no longer there; perhaps this is because the list of mailboxes got replaced. 1213 // Seems that there's nothing to report here. 1214 return; 1215 } 1216 1217 EMIT_LATER(model, mailboxSyncFailed, Q_ARG(QString, mailboxIndex.data(RoleMailboxName).toString()), Q_ARG(QString, message)); 1218 } 1219 1220 } 1221 }