File indexing completed on 2024-11-24 04:53:12
0001 /* Copyright (C) 2006 - 2021 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 <QAbstractProxyModel> 0025 #include <QAuthenticator> 0026 #include <QCoreApplication> 0027 #include <QDebug> 0028 #include "Model.h" 0029 #include "Common/FindWithUnknown.h" 0030 #include "Common/InvokeMethod.h" 0031 #include "Imap/Encoders.h" 0032 #include "Imap/Model/ItemRoles.h" 0033 #include "Imap/Model/MailboxTree.h" 0034 #include "Imap/Model/SpecialFlagNames.h" 0035 #include "Imap/Model/TaskPresentationModel.h" 0036 #include "Imap/Model/Utils.h" 0037 #include "Imap/Tasks/AppendTask.h" 0038 #include "Imap/Tasks/CreateMailboxTask.h" 0039 #include "Imap/Tasks/GetAnyConnectionTask.h" 0040 #include "Imap/Tasks/KeepMailboxOpenTask.h" 0041 #include "Imap/Tasks/OpenConnectionTask.h" 0042 #include "Imap/Tasks/UpdateFlagsTask.h" 0043 #include "Imap/Tasks/CopyMoveMessagesTask.h" 0044 #include "Streams/SocketFactory.h" 0045 0046 //#define DEBUG_PERIODICALLY_DUMP_TASKS 0047 //#define DEBUG_TASK_ROUTING 0048 0049 namespace 0050 { 0051 0052 using namespace Imap::Mailbox; 0053 0054 /** @short Return true iff the two mailboxes have the same name 0055 0056 It's an error to call this function on anything else but a mailbox. 0057 */ 0058 bool MailboxNamesEqual(const TreeItem *const a, const TreeItem *const b) 0059 { 0060 const TreeItemMailbox *const mailboxA = dynamic_cast<const TreeItemMailbox *const>(a); 0061 const TreeItemMailbox *const mailboxB = dynamic_cast<const TreeItemMailbox *const>(b); 0062 Q_ASSERT(mailboxA); 0063 Q_ASSERT(mailboxB); 0064 0065 return mailboxA->mailbox() == mailboxB->mailbox(); 0066 } 0067 0068 /** @short Mailbox name comparator to be used when sorting mailbox names 0069 0070 The special-case mailbox name, the "INBOX", is always sorted as the first one. 0071 */ 0072 bool MailboxNameComparator(const TreeItem *const a, const TreeItem *const b) 0073 { 0074 const TreeItemMailbox *const mailboxA = dynamic_cast<const TreeItemMailbox *const>(a); 0075 const TreeItemMailbox *const mailboxB = dynamic_cast<const TreeItemMailbox *const>(b); 0076 0077 if (mailboxA->mailbox() == QLatin1String("INBOX")) 0078 return mailboxB->mailbox() != QLatin1String("INBOX"); 0079 if (mailboxB->mailbox() == QLatin1String("INBOX")) 0080 return false; 0081 return mailboxA->mailbox().compare(mailboxB->mailbox(), Qt::CaseInsensitive) < 0; 0082 } 0083 0084 bool uidComparator(const TreeItem *const item, const uint uid) 0085 { 0086 const TreeItemMessage *const message = static_cast<const TreeItemMessage *const>(item); 0087 uint messageUid = message->uid(); 0088 Q_ASSERT(messageUid); 0089 return messageUid < uid; 0090 } 0091 0092 bool messageHasUidZero(const TreeItem *const item) 0093 { 0094 const TreeItemMessage *const message = static_cast<const TreeItemMessage *const>(item); 0095 return message->uid() == 0; 0096 } 0097 0098 } 0099 0100 namespace Imap 0101 { 0102 namespace Mailbox 0103 { 0104 0105 Model::Model(QObject *parent, std::shared_ptr<AbstractCache> cache, SocketFactoryPtr socketFactory, TaskFactoryPtr taskFactory) 0106 : QAbstractItemModel(parent) 0107 , m_cache(cache) 0108 , m_socketFactory(std::move(socketFactory)) 0109 , m_taskFactory(std::move(taskFactory)) 0110 , m_maxParsers(4) 0111 , m_mailboxes(nullptr) 0112 , m_netPolicy(NETWORK_OFFLINE) 0113 , m_taskModel(nullptr) 0114 , m_hasImapPassword(PasswordAvailability::NOT_REQUESTED) 0115 { 0116 m_startTls = m_socketFactory->startTlsRequired(); 0117 0118 m_mailboxes = new TreeItemMailbox(0); 0119 0120 onlineMessageFetch << QStringLiteral("ENVELOPE") << QStringLiteral("BODYSTRUCTURE") << QStringLiteral("RFC822.SIZE") << 0121 QStringLiteral("UID") << QStringLiteral("FLAGS"); 0122 0123 EMIT_LATER_NOARG(this, networkPolicyOffline); 0124 EMIT_LATER_NOARG(this, networkPolicyChanged); 0125 0126 #ifdef DEBUG_PERIODICALLY_DUMP_TASKS 0127 QTimer *periodicTaskDumper = new QTimer(this); 0128 periodicTaskDumper->setInterval(1000); 0129 connect(periodicTaskDumper, &QTimer::timeout, this, &Model::slotTasksChanged); 0130 periodicTaskDumper->start(); 0131 #endif 0132 0133 m_taskModel = new TaskPresentationModel(this); 0134 0135 m_periodicMailboxNumbersRefresh = new QTimer(this); 0136 // polling every five minutes 0137 m_periodicMailboxNumbersRefresh->setInterval(5 * 60 * 1000); 0138 connect(m_periodicMailboxNumbersRefresh, &QTimer::timeout, this, &Model::invalidateAllMessageCounts); 0139 } 0140 0141 Model::~Model() 0142 { 0143 delete m_mailboxes; 0144 } 0145 0146 /** @short Process responses from all sockets */ 0147 void Model::responseReceived() 0148 { 0149 for (QMap<Parser *,ParserState>::iterator it = m_parsers.begin(); it != m_parsers.end(); ++it) { 0150 responseReceived(it); 0151 } 0152 } 0153 0154 /** @short Process responses from the specified parser */ 0155 void Model::responseReceived(Parser *parser) 0156 { 0157 QMap<Parser *,ParserState>::iterator it = m_parsers.find(parser); 0158 if (it == m_parsers.end()) { 0159 // This is a queued signal, so it's perfectly possible that the sender is gone already 0160 return; 0161 } 0162 responseReceived(it); 0163 } 0164 0165 /** @short Process responses from the specified parser */ 0166 void Model::responseReceived(const QMap<Parser *,ParserState>::iterator it) 0167 { 0168 Q_ASSERT(it->parser); 0169 0170 int counter = 0; 0171 while (it->parser && it->parser->hasResponse()) { 0172 QSharedPointer<Imap::Responses::AbstractResponse> resp = it->parser->getResponse(); 0173 Q_ASSERT(resp); 0174 // Always log BAD responses from a central place. They're bad enough to warant an extra treatment. 0175 // FIXME: is it worth an UI popup? 0176 if (Responses::State *stateResponse = dynamic_cast<Responses::State *>(resp.data())) { 0177 if (stateResponse->kind == Responses::BAD) { 0178 QString buf; 0179 QTextStream s(&buf); 0180 s << *stateResponse; 0181 logTrace(it->parser->parserId(), Common::LOG_OTHER, QStringLiteral("Model"), QStringLiteral("BAD response: %1").arg(buf)); 0182 qDebug() << buf; 0183 } 0184 } 0185 try { 0186 /* At this point, we want to iterate over all active tasks and try them 0187 for processing the server's responses (the plug() method). However, this 0188 is rather complex -- this call to plug() could result in signals being 0189 emitted, and certain slots connected to those signals might in turn want 0190 to queue more Tasks. Therefore, it->activeTasks could be modified, some 0191 items could be appended to it using the QList::append, which in turn could 0192 cause a realloc to happen, happily invalidating our iterators, and that 0193 kind of sucks. 0194 0195 So, we have to iterate over a copy of the original list and instead of 0196 deleting Tasks, we store them into a temporary list. When we're done with 0197 processing, we walk the original list once again and simply remove all 0198 "deleted" items for real. 0199 0200 This took me 3+ hours to track it down to what the hell was happening here, 0201 even though the underlying reason is simple -- QList::append() could invalidate 0202 existing iterators. 0203 */ 0204 0205 bool handled = false; 0206 QList<ImapTask *> taskSnapshot = it->activeTasks; 0207 QList<ImapTask *> deletedTasks; 0208 QList<ImapTask *>::const_iterator taskEnd = taskSnapshot.constEnd(); 0209 0210 // Try various tasks, perhaps it's their response. Also check if they're already finished and remove them. 0211 for (QList<ImapTask *>::const_iterator taskIt = taskSnapshot.constBegin(); taskIt != taskEnd; ++taskIt) { 0212 if (! handled) { 0213 0214 #ifdef DEBUG_TASK_ROUTING 0215 try { 0216 logTrace(it->parser->parserId(), Common::LOG_TASKS, QString(), 0217 QString::fromLocal8Bit("Routing to %1 %2").arg(QString::fromLocal8Bit((*taskIt)->metaObject()->className()), 0218 (*taskIt)->debugIdentification())); 0219 #endif 0220 handled = resp->plug(*taskIt); 0221 #ifdef DEBUG_TASK_ROUTING 0222 if (handled) { 0223 logTrace(it->parser->parserId(), Common::LOG_TASKS, (*taskIt)->debugIdentification(), QLatin1String("Handled")); 0224 } 0225 } catch (std::exception &e) { 0226 logTrace(it->parser->parserId(), Common::LOG_TASKS, (*taskIt)->debugIdentification(), QLatin1String("Got exception when handling")); 0227 throw; 0228 } 0229 #endif 0230 } 0231 0232 if ((*taskIt)->isFinished()) { 0233 deletedTasks << *taskIt; 0234 } 0235 } 0236 0237 removeDeletedTasks(deletedTasks, it->activeTasks); 0238 0239 runReadyTasks(); 0240 0241 if (! handled) { 0242 resp->plug(it->parser, this); 0243 #ifdef DEBUG_TASK_ROUTING 0244 if (it->parser) { 0245 logTrace(it->parser->parserId(), Common::LOG_TASKS, QLatin1String("Model"), QLatin1String("Handled")); 0246 } else { 0247 logTrace(0, Common::LOG_TASKS, QLatin1String("Model"), QLatin1String("Handled")); 0248 } 0249 #endif 0250 } 0251 } catch (Imap::ImapException &e) { 0252 uint parserId = it->parser->parserId(); 0253 killParser(it->parser, PARSER_KILL_HARD); 0254 broadcastParseError(parserId, QString::fromStdString(e.exceptionClass()), QString::fromUtf8(e.what()), e.line(), e.offset()); 0255 break; 0256 } 0257 0258 // Return to the event loop every 100 messages to handle GUI events 0259 ++counter; 0260 if (counter == 100) { 0261 QTimer::singleShot(0, this, SLOT(responseReceived())); 0262 break; 0263 } 0264 } 0265 0266 if (!it->parser) { 0267 // He's dead, Jim 0268 m_taskModel->beginResetModel(); 0269 killParser(it.key(), PARSER_JUST_DELETE_LATER); 0270 m_parsers.erase(it); 0271 m_taskModel->endResetModel(); 0272 } 0273 } 0274 0275 void Model::handleState(Imap::Parser *ptr, const Imap::Responses::State *const resp) 0276 { 0277 // OK/NO/BAD/PREAUTH/BYE 0278 using namespace Imap::Responses; 0279 0280 const QByteArray &tag = resp->tag; 0281 0282 if (!tag.isEmpty()) { 0283 if (tag == accessParser(ptr).logoutCmd) { 0284 // The LOGOUT is special, as it isn't associated with any task 0285 killParser(ptr, PARSER_KILL_EXPECTED); 0286 } else { 0287 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0288 return; 0289 // Unhandled command -- this is *extremely* weird 0290 throw CantHappen("The following command should have been handled elsewhere", *resp); 0291 } 0292 } else { 0293 // untagged response 0294 // FIXME: we should probably just eat them and don't bother, as untagged OK/NO could be rather common... 0295 switch (resp->kind) { 0296 case BYE: 0297 if (accessParser(ptr).logoutCmd.isEmpty()) { 0298 // The connection got closed but we haven't really requested that -- we better treat that as error, including 0299 // going offline... 0300 // ... but before that, expect that the connection will get closed soon 0301 changeConnectionState(ptr, CONN_STATE_LOGOUT); 0302 EMIT_LATER(this, imapError, Q_ARG(QString, resp->message)); 0303 setNetworkPolicy(NETWORK_OFFLINE); 0304 } 0305 if (accessParser(ptr).parser) { 0306 // previous block could enter the event loop and hence kill our parser; we shouldn't try to kill it twice 0307 killParser(ptr, PARSER_KILL_EXPECTED); 0308 } 0309 break; 0310 case OK: 0311 if (resp->respCode == NONE) { 0312 // This one probably should not be logged at all; dovecot sends these reponses to keep NATted connections alive 0313 break; 0314 } else { 0315 logTrace(ptr->parserId(), Common::LOG_OTHER, QString(), QStringLiteral("Warning: unhandled untagged OK with a response code")); 0316 break; 0317 } 0318 case NO: 0319 logTrace(ptr->parserId(), Common::LOG_OTHER, QString(), QStringLiteral("Warning: unhandled untagged NO...")); 0320 break; 0321 default: 0322 throw UnexpectedResponseReceived("Unhandled untagged response, sorry", *resp); 0323 } 0324 } 0325 } 0326 0327 void Model::finalizeList(Parser *parser, TreeItemMailbox *mailboxPtr) 0328 { 0329 TreeItemChildrenList mailboxes; 0330 0331 QList<Responses::List> &listResponses = accessParser(parser).listResponses; 0332 const QString prefix = mailboxPtr->mailbox() + mailboxPtr->separator(); 0333 for (QList<Responses::List>::iterator it = listResponses.begin(); it != listResponses.end(); /* nothing */) { 0334 if (it->mailbox == mailboxPtr->mailbox() || it->mailbox == prefix) { 0335 // rubbish, ignore 0336 it = listResponses.erase(it); 0337 } else if (it->mailbox.startsWith(prefix)) { 0338 if (!mailboxPtr->separator().isEmpty() && it->mailbox.midRef(prefix.length()).contains(mailboxPtr->separator())) { 0339 // This is about a mailbox which is nested deeper beneath the current thing (e.g., we're listing A.B.%, 0340 // and the current response is A.B.C.1), so let's assume that it's some else's LIST response. 0341 // The separator/delimiter checking is (hopefully) correct -- see https://tools.ietf.org/html/rfc3501#page-70 . 0342 ++it; 0343 } else { 0344 mailboxes << new TreeItemMailbox(mailboxPtr, *it); 0345 it = listResponses.erase(it); 0346 } 0347 } else { 0348 // it clearly is someone else's LIST response 0349 ++it; 0350 } 0351 } 0352 std::sort(mailboxes.begin(), mailboxes.end(), MailboxNameComparator); 0353 0354 // Remove duplicates; would be great if this could be done in a STLish way, 0355 // but unfortunately std::unique won't help here (the "duped" part of the 0356 // sequence contains undefined items) 0357 if (mailboxes.size() > 1) { 0358 auto it = mailboxes.begin(); 0359 // We've got to ignore the first one, that's the message list 0360 ++it; 0361 while (it != mailboxes.end()) { 0362 if (MailboxNamesEqual(it[-1], *it)) { 0363 delete *it; 0364 it = mailboxes.erase(it); 0365 } else { 0366 ++it; 0367 } 0368 } 0369 } 0370 0371 QList<MailboxMetadata> metadataToCache; 0372 TreeItemChildrenList mailboxesWithoutChildren; 0373 for (auto it = mailboxes.constBegin(); it != mailboxes.constEnd(); ++it) { 0374 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(*it); 0375 Q_ASSERT(mailbox); 0376 metadataToCache.append(mailbox->mailboxMetadata()); 0377 if (mailbox->hasNoChildMailboxesAlreadyKnown()) { 0378 mailboxesWithoutChildren << mailbox; 0379 } 0380 } 0381 cache()->setChildMailboxes(mailboxPtr->mailbox(), metadataToCache); 0382 for (auto it = mailboxesWithoutChildren.constBegin(); it != mailboxesWithoutChildren.constEnd(); ++it) 0383 cache()->setChildMailboxes(static_cast<TreeItemMailbox *>(*it)->mailbox(), QList<MailboxMetadata>()); 0384 replaceChildMailboxes(mailboxPtr, mailboxes); 0385 } 0386 0387 void Model::finalizeIncrementalList(Parser *parser, const QString &parentMailboxName) 0388 { 0389 TreeItemMailbox *parentMbox = findParentMailboxByName(parentMailboxName); 0390 if (! parentMbox) { 0391 qDebug() << "Weird, no idea where to put the newly created mailbox" << parentMailboxName; 0392 return; 0393 } 0394 0395 QList<TreeItem *> mailboxes; 0396 0397 QList<Responses::List> &listResponses = accessParser(parser).listResponses; 0398 for (QList<Responses::List>::iterator it = listResponses.begin(); 0399 it != listResponses.end(); /* nothing */) { 0400 if (it->mailbox == parentMailboxName) { 0401 mailboxes << new TreeItemMailbox(parentMbox, *it); 0402 it = listResponses.erase(it); 0403 } else { 0404 // it clearly is someone else's LIST response 0405 ++it; 0406 } 0407 } 0408 std::sort(mailboxes.begin(), mailboxes.end(), MailboxNameComparator); 0409 0410 if (mailboxes.size() == 0) { 0411 qDebug() << "Weird, no matching LIST response for our prompt after CREATE"; 0412 qDeleteAll(mailboxes); 0413 return; 0414 } else if (mailboxes.size() > 1) { 0415 qDebug() << "Weird, too many LIST responses for our prompt after CREATE"; 0416 qDeleteAll(mailboxes); 0417 return; 0418 } 0419 0420 auto it = parentMbox->m_children.begin(); 0421 Q_ASSERT(it != parentMbox->m_children.end()); 0422 ++it; 0423 while (it != parentMbox->m_children.end() && MailboxNameComparator(*it, mailboxes[0])) 0424 ++it; 0425 QModelIndex parentIdx = parentMbox == m_mailboxes ? QModelIndex() : parentMbox->toIndex(this); 0426 if (it == parentMbox->m_children.end()) 0427 beginInsertRows(parentIdx, parentMbox->m_children.size(), parentMbox->m_children.size()); 0428 else 0429 beginInsertRows(parentIdx, (*it)->row(), (*it)->row()); 0430 parentMbox->m_children.insert(it, mailboxes[0]); 0431 endInsertRows(); 0432 } 0433 0434 void Model::replaceChildMailboxes(TreeItemMailbox *mailboxPtr, const TreeItemChildrenList &mailboxes) 0435 { 0436 /* Previously, we would call layoutAboutToBeChanged() and layoutChanged() here, but it 0437 resulted in invalid memory access in the attached QSortFilterProxyModels like this one: 0438 0439 ==23294== Invalid read of size 4 0440 ==23294== at 0x5EA34B1: QSortFilterProxyModelPrivate::index_to_iterator(QModelIndex const&) const (qsortfilterproxymodel.cpp:191) 0441 ==23294== by 0x5E9F8A3: QSortFilterProxyModel::parent(QModelIndex const&) const (qsortfilterproxymodel.cpp:1654) 0442 ==23294== by 0x5C5D45D: QModelIndex::parent() const (qabstractitemmodel.h:389) 0443 ==23294== by 0x5E47C48: QTreeView::drawRow(QPainter*, QStyleOptionViewItem const&, QModelIndex const&) const (qtreeview.cpp:1479) 0444 ==23294== by 0x5E479D9: QTreeView::drawTree(QPainter*, QRegion const&) const (qtreeview.cpp:1441) 0445 ==23294== by 0x5E4703A: QTreeView::paintEvent(QPaintEvent*) (qtreeview.cpp:1274) 0446 ==23294== by 0x5810C30: QWidget::event(QEvent*) (qwidget.cpp:8346) 0447 ==23294== by 0x5C91D03: QFrame::event(QEvent*) (qframe.cpp:557) 0448 ==23294== by 0x5D4259C: QAbstractScrollArea::viewportEvent(QEvent*) (qabstractscrollarea.cpp:1043) 0449 ==23294== by 0x5DFFD6E: QAbstractItemView::viewportEvent(QEvent*) (qabstractitemview.cpp:1619) 0450 ==23294== by 0x5E46EE0: QTreeView::viewportEvent(QEvent*) (qtreeview.cpp:1256) 0451 ==23294== by 0x5D43110: QAbstractScrollAreaPrivate::viewportEvent(QEvent*) (qabstractscrollarea_p.h:100) 0452 ==23294== Address 0x908dbec is 20 bytes inside a block of size 24 free'd 0453 ==23294== at 0x4024D74: operator delete(void*) (vg_replace_malloc.c:346) 0454 ==23294== by 0x5EA5236: void qDeleteAll<QHash<QModelIndex, QSortFilterProxyModelPrivate::Mapping*>::const_iterator>(QHash<QModelIndex, QSortFilterProxyModelPrivate::Mapping*>::const_iterator, QHash<QModelIndex, QSortFilterProxyModelPrivate::Mapping*>::const_iterator) (qalgorithms.h:322) 0455 ==23294== by 0x5EA3C06: void qDeleteAll<QHash<QModelIndex, QSortFilterProxyModelPrivate::Mapping*> >(QHash<QModelIndex, QSortFilterProxyModelPrivate::Mapping*> const&) (qalgorithms.h:330) 0456 ==23294== by 0x5E9E64B: QSortFilterProxyModelPrivate::_q_sourceLayoutChanged() (qsortfilterproxymodel.cpp:1249) 0457 ==23294== by 0x5EA29EC: QSortFilterProxyModel::qt_metacall(QMetaObject::Call, int, void**) (moc_qsortfilterproxymodel.cpp:133) 0458 ==23294== by 0x80EB205: Imap::Mailbox::PrettyMailboxModel::qt_metacall(QMetaObject::Call, int, void**) (moc_PrettyMailboxModel.cpp:64) 0459 ==23294== by 0x65D3EAD: QMetaObject::metacall(QObject*, QMetaObject::Call, int, void**) (qmetaobject.cpp:237) 0460 ==23294== by 0x65E8D7C: QMetaObject::activate(QObject*, QMetaObject const*, int, void**) (qobject.cpp:3272) 0461 ==23294== by 0x664A7E8: QAbstractItemModel::layoutChanged() (moc_qabstractitemmodel.cpp:161) 0462 ==23294== by 0x664A354: QAbstractItemModel::qt_metacall(QMetaObject::Call, int, void**) (moc_qabstractitemmodel.cpp:118) 0463 ==23294== by 0x5E9A3A9: QAbstractProxyModel::qt_metacall(QMetaObject::Call, int, void**) (moc_qabstractproxymodel.cpp:67) 0464 ==23294== by 0x80EAF3D: Imap::Mailbox::MailboxModel::qt_metacall(QMetaObject::Call, int, void**) (moc_MailboxModel.cpp:81) 0465 0466 I have no idea why something like that happens -- layoutChanged() should be a hint that the indexes are gone now, which means 0467 that the code should *not* use tham after that point. That's just weird. 0468 */ 0469 0470 QModelIndex parent = mailboxPtr == m_mailboxes ? QModelIndex() : mailboxPtr->toIndex(this); 0471 0472 if (mailboxPtr->m_children.size() != 1) { 0473 // There's something besides the TreeItemMsgList and we're going to 0474 // overwrite them, so we have to delete them right now 0475 int count = mailboxPtr->rowCount(this); 0476 beginRemoveRows(parent, 1, count - 1); 0477 auto oldItems = mailboxPtr->setChildren(TreeItemChildrenList()); 0478 endRemoveRows(); 0479 0480 qDeleteAll(oldItems); 0481 } 0482 0483 if (! mailboxes.isEmpty()) { 0484 beginInsertRows(parent, 1, mailboxes.size()); 0485 auto dummy = mailboxPtr->setChildren(mailboxes); 0486 endInsertRows(); 0487 Q_ASSERT(dummy.isEmpty()); 0488 } else { 0489 auto dummy = mailboxPtr->setChildren(mailboxes); 0490 Q_ASSERT(dummy.isEmpty()); 0491 } 0492 emit dataChanged(parent, parent); 0493 } 0494 0495 void Model::emitMessageCountChanged(TreeItemMailbox *const mailbox) 0496 { 0497 TreeItemMsgList *list = static_cast<TreeItemMsgList *>(mailbox->m_children[0]); 0498 QModelIndex msgListIndex = list->toIndex(this); 0499 emit dataChanged(msgListIndex, msgListIndex); 0500 QModelIndex mailboxIndex = mailbox->toIndex(this); 0501 emit dataChanged(mailboxIndex, mailboxIndex); 0502 emit messageCountPossiblyChanged(mailboxIndex); 0503 } 0504 0505 void Model::handleCapability(Imap::Parser *ptr, const Imap::Responses::Capability *const resp) 0506 { 0507 updateCapabilities(ptr, resp->capabilities); 0508 } 0509 0510 void Model::handleNumberResponse(Imap::Parser *ptr, const Imap::Responses::NumberResponse *const resp) 0511 { 0512 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0513 return; 0514 throw UnexpectedResponseReceived("[Tasks API Port] Unhandled NumberResponse", *resp); 0515 } 0516 0517 void Model::handleList(Imap::Parser *ptr, const Imap::Responses::List *const resp) 0518 { 0519 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0520 return; 0521 if (accessParser(ptr).connState < CONN_STATE_AUTHENTICATED) 0522 throw UnexpectedResponseReceived("Unexpected LIST response before authentication succeeded", *resp); 0523 accessParser(ptr).listResponses << *resp; 0524 } 0525 0526 void Model::handleFlags(Imap::Parser *ptr, const Imap::Responses::Flags *const resp) 0527 { 0528 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0529 return; 0530 throw UnexpectedResponseReceived("[Tasks API Port] Unhandled Flags", *resp); 0531 } 0532 0533 void Model::handleSearch(Imap::Parser *ptr, const Imap::Responses::Search *const resp) 0534 { 0535 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0536 return; 0537 throw UnexpectedResponseReceived("[Tasks API Port] Unhandled Search", *resp); 0538 } 0539 0540 void Model::handleESearch(Imap::Parser *ptr, const Imap::Responses::ESearch *const resp) 0541 { 0542 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0543 return; 0544 throw UnexpectedResponseReceived("Unhandled ESEARCH", *resp); 0545 } 0546 0547 void Model::handleStatus(Imap::Parser *ptr, const Imap::Responses::Status *const resp) 0548 { 0549 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0550 return; 0551 if (accessParser(ptr).connState < CONN_STATE_AUTHENTICATED) 0552 throw UnexpectedResponseReceived("Unexpected STATUS response before authentication succeeded", *resp); 0553 0554 Q_UNUSED(ptr); 0555 TreeItemMailbox *mailbox = findMailboxByName(resp->mailbox); 0556 if (! mailbox) { 0557 qDebug() << "Couldn't find out which mailbox is" << resp->mailbox << "when parsing a STATUS reply"; 0558 return; 0559 } 0560 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(mailbox->m_children[0]); 0561 Q_ASSERT(list); 0562 bool updateCache = false; 0563 Imap::Responses::Status::stateDataType::const_iterator it = resp->states.constEnd(); 0564 if ((it = resp->states.constFind(Imap::Responses::Status::MESSAGES)) != resp->states.constEnd()) { 0565 updateCache |= list->m_totalMessageCount != static_cast<const int>(it.value()); 0566 list->m_totalMessageCount = it.value(); 0567 } 0568 if ((it = resp->states.constFind(Imap::Responses::Status::UNSEEN)) != resp->states.constEnd()) { 0569 updateCache |= list->m_unreadMessageCount != static_cast<const int>(it.value()); 0570 list->m_unreadMessageCount = it.value(); 0571 } 0572 if ((it = resp->states.constFind(Imap::Responses::Status::RECENT)) != resp->states.constEnd()) { 0573 updateCache |= list->m_recentMessageCount != static_cast<const int>(it.value()); 0574 list->m_recentMessageCount = it.value(); 0575 } 0576 list->m_numberFetchingStatus = TreeItem::DONE; 0577 emitMessageCountChanged(mailbox); 0578 0579 if (updateCache) { 0580 // We have to be very careful to only touch the bits which are *not* used by the mailbox syncing code. 0581 // This is absolutely crucial -- STATUS is just a meaningless indicator, and stuff like the UID mapping 0582 // is definitely *not* updated at the same time. That's also why we completely ignore any data whatsoever 0583 // from the TreeItemMailbox' syncState, if any, and just work with the cache directly. 0584 auto state = cache()->mailboxSyncState(mailbox->mailbox()); 0585 // We are *not* updating the total message count as that conflicts with the mailbox syncing 0586 if (list->m_unreadMessageCount != -1) { 0587 state.setUnSeenCount(list->m_unreadMessageCount); 0588 } 0589 if (list->m_recentMessageCount != -1) { 0590 state.setRecent(list->m_recentMessageCount); 0591 } 0592 cache()->setMailboxSyncState(mailbox->mailbox(), state); 0593 } 0594 } 0595 0596 void Model::handleFetch(Imap::Parser *ptr, const Imap::Responses::Fetch *const resp) 0597 { 0598 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0599 return; 0600 throw UnexpectedResponseReceived("[Tasks API Port] Unhandled Fetch", *resp); 0601 } 0602 0603 void Model::handleNamespace(Imap::Parser *ptr, const Imap::Responses::Namespace *const resp) 0604 { 0605 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0606 return; 0607 return; // because it's broken and won't fly 0608 Q_UNUSED(ptr); 0609 Q_UNUSED(resp); 0610 } 0611 0612 void Model::handleSort(Imap::Parser *ptr, const Imap::Responses::Sort *const resp) 0613 { 0614 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0615 return; 0616 throw UnexpectedResponseReceived("[Tasks API Port] Unhandled Sort", *resp); 0617 } 0618 0619 void Model::handleThread(Imap::Parser *ptr, const Imap::Responses::Thread *const resp) 0620 { 0621 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0622 return; 0623 throw UnexpectedResponseReceived("[Tasks API Port] Unhandled Thread", *resp); 0624 } 0625 0626 void Model::handleId(Parser *ptr, const Responses::Id *const resp) 0627 { 0628 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0629 return; 0630 throw UnexpectedResponseReceived("Unhandled ID response", *resp); 0631 } 0632 0633 void Model::handleEnabled(Parser *ptr, const Responses::Enabled *const resp) 0634 { 0635 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0636 return; 0637 throw UnexpectedResponseReceived("Unhandled ENABLED response", *resp); 0638 } 0639 0640 void Model::handleVanished(Parser *ptr, const Responses::Vanished *const resp) 0641 { 0642 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0643 return; 0644 throw UnexpectedResponseReceived("Unhandled VANISHED response", *resp); 0645 } 0646 0647 void Model::handleGenUrlAuth(Parser *ptr, const Responses::GenUrlAuth *const resp) 0648 { 0649 if (accessParser(ptr).connState == CONN_STATE_LOGOUT) 0650 return; 0651 throw UnexpectedResponseReceived("Unhandled GENURLAUTH response", *resp); 0652 } 0653 0654 void Model::handleSocketEncryptedResponse(Parser *ptr, const Responses::SocketEncryptedResponse *const resp) 0655 { 0656 Q_UNUSED(resp); 0657 logTrace(ptr->parserId(), Common::LOG_IO_READ, QStringLiteral("Model"), QStringLiteral("Information about the SSL state not handled by the upper layers -- disconnect?")); 0658 killParser(ptr, PARSER_KILL_EXPECTED); 0659 } 0660 0661 TreeItem *Model::translatePtr(const QModelIndex &index) const 0662 { 0663 return index.internalPointer() ? static_cast<TreeItem *>(index.internalPointer()) : m_mailboxes; 0664 } 0665 0666 QVariant Model::data(const QModelIndex &index, int role) const 0667 { 0668 if (role == RoleIsNetworkOffline) 0669 return !isNetworkAvailable(); 0670 0671 return translatePtr(index)->data(const_cast<Model *>(this), role); 0672 } 0673 0674 QModelIndex Model::index(int row, int column, const QModelIndex &parent) const 0675 { 0676 if (parent.isValid()) { 0677 Q_ASSERT(parent.model() == this); 0678 } 0679 0680 TreeItem *parentItem = translatePtr(parent); 0681 0682 // Deal with the possibility of an "irregular shape" of our model here. 0683 // The issue is that some items have child items not only in column #0 0684 // and in specified number of rows, but also in row #0 and various columns. 0685 if (column != 0) { 0686 TreeItem *item = parentItem->specialColumnPtr(row, column); 0687 if (item) 0688 return QAbstractItemModel::createIndex(row, column, item); 0689 else 0690 return QModelIndex(); 0691 } 0692 0693 TreeItem *child = parentItem->child(row, const_cast<Model *>(this)); 0694 0695 return child ? QAbstractItemModel::createIndex(row, column, child) : QModelIndex(); 0696 } 0697 0698 QModelIndex Model::parent(const QModelIndex &index) const 0699 { 0700 if (!index.isValid()) 0701 return QModelIndex(); 0702 0703 Q_ASSERT(index.model() == this); 0704 0705 TreeItem *childItem = static_cast<TreeItem *>(index.internalPointer()); 0706 TreeItem *parentItem = childItem->parent(); 0707 0708 if (! parentItem || parentItem == m_mailboxes) 0709 return QModelIndex(); 0710 0711 return QAbstractItemModel::createIndex(parentItem->row(), 0, parentItem); 0712 } 0713 0714 int Model::rowCount(const QModelIndex &index) const 0715 { 0716 TreeItem *node = static_cast<TreeItem *>(index.internalPointer()); 0717 if (!node) { 0718 node = m_mailboxes; 0719 } else { 0720 Q_ASSERT(index.model() == this); 0721 } 0722 Q_ASSERT(node); 0723 return node->rowCount(const_cast<Model *>(this)); 0724 } 0725 0726 int Model::columnCount(const QModelIndex &index) const 0727 { 0728 TreeItem *node = static_cast<TreeItem *>(index.internalPointer()); 0729 if (!node) { 0730 node = m_mailboxes; 0731 } else { 0732 Q_ASSERT(index.model() == this); 0733 } 0734 Q_ASSERT(node); 0735 return node->columnCount(); 0736 } 0737 0738 bool Model::hasChildren(const QModelIndex &parent) const 0739 { 0740 if (parent.isValid()) { 0741 Q_ASSERT(parent.model() == this); 0742 } 0743 0744 TreeItem *node = translatePtr(parent); 0745 0746 if (node) 0747 return node->hasChildren(const_cast<Model *>(this)); 0748 else 0749 return false; 0750 } 0751 0752 void Model::askForChildrenOfMailbox(const QModelIndex &index, const CacheLoadingMode cacheMode) 0753 { 0754 TreeItemMailbox *mailbox = 0; 0755 if (index.isValid()) { 0756 Q_ASSERT(index.model() == this); 0757 mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(index.internalPointer())); 0758 } else { 0759 mailbox = m_mailboxes; 0760 } 0761 Q_ASSERT(mailbox); 0762 askForChildrenOfMailbox(mailbox, cacheMode == LOAD_FORCE_RELOAD); 0763 } 0764 0765 void Model::askForMessagesInMailbox(const QModelIndex &index) 0766 { 0767 if (!index.isValid()) 0768 return; 0769 Q_ASSERT(index.model() == this); 0770 auto msgList = dynamic_cast<TreeItemMsgList *>(static_cast<TreeItem *>(index.internalPointer())); 0771 Q_ASSERT(msgList); 0772 askForMessagesInMailbox(msgList); 0773 } 0774 0775 void Model::askForChildrenOfMailbox(TreeItemMailbox *item, bool forceReload) 0776 { 0777 if (!forceReload && cache()->childMailboxesFresh(item->mailbox())) { 0778 // The permanent cache contains relevant data 0779 QList<MailboxMetadata> metadata = cache()->childMailboxes(item->mailbox()); 0780 TreeItemChildrenList mailboxes; 0781 for (QList<MailboxMetadata>::const_iterator it = metadata.constBegin(); it != metadata.constEnd(); ++it) { 0782 TreeItemMailbox *mailbox = TreeItemMailbox::fromMetadata(item, *it); 0783 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList*>(mailbox->m_children[0]); 0784 Q_ASSERT(list); 0785 Imap::Mailbox::SyncState syncState = cache()->mailboxSyncState(mailbox->mailbox()); 0786 if (syncState.isUsableForNumbers()) { 0787 list->m_unreadMessageCount = syncState.unSeenCount(); 0788 list->m_totalMessageCount = syncState.exists(); 0789 list->m_recentMessageCount = syncState.recent(); 0790 list->m_numberFetchingStatus = TreeItem::LOADING; 0791 } else { 0792 list->m_numberFetchingStatus = TreeItem::UNAVAILABLE; 0793 } 0794 mailboxes << mailbox; 0795 } 0796 TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(item); 0797 Q_ASSERT(mailboxPtr); 0798 replaceChildMailboxes(mailboxPtr, mailboxes); 0799 } else if (networkPolicy() == NETWORK_OFFLINE) { 0800 // No cached data, no network -> fail 0801 item->setFetchStatus(TreeItem::UNAVAILABLE); 0802 QModelIndex idx = item->toIndex(this); 0803 emit dataChanged(idx, idx); 0804 return; 0805 } 0806 0807 // We shall ask the network 0808 m_taskFactory->createListChildMailboxesTask(this, item->toIndex(this)); 0809 0810 QModelIndex idx = item->toIndex(this); 0811 emit dataChanged(idx, idx); 0812 } 0813 0814 void Model::reloadMailboxList() 0815 { 0816 m_mailboxes->rescanForChildMailboxes(this); 0817 } 0818 0819 void Model::askForMessagesInMailbox(TreeItemMsgList *item) 0820 { 0821 Q_ASSERT(item->parent()); 0822 TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(item->parent()); 0823 Q_ASSERT(mailboxPtr); 0824 0825 QString mailbox = mailboxPtr->mailbox(); 0826 0827 Q_ASSERT(item->m_children.size() == 0); 0828 0829 auto uidMapping = cache()->uidMapping(mailbox); 0830 auto oldSyncState = cache()->mailboxSyncState(mailbox); 0831 if (networkPolicy() == NETWORK_OFFLINE && oldSyncState.isUsableForSyncing() 0832 && static_cast<uint>(uidMapping.size()) != oldSyncState.exists()) { 0833 // Problem with the cached data 0834 qDebug() << "UID cache stale for mailbox" << mailbox 0835 << "(" << uidMapping.size() << "in UID cache vs." << oldSyncState.exists() << "in the sync state and" 0836 << item->m_totalMessageCount << "as totalMessageCount (possibly updated by STATUS))"; 0837 item->setFetchStatus(TreeItem::UNAVAILABLE); 0838 } else if (networkPolicy() == NETWORK_OFFLINE && !oldSyncState.isUsableForSyncing()) { 0839 // Nothing in the cache 0840 item->setFetchStatus(TreeItem::UNAVAILABLE); 0841 } else if (oldSyncState.isUsableForSyncing()) { 0842 // We can pre-populate the tree with data from cache 0843 Q_ASSERT(item->m_children.isEmpty()); 0844 Q_ASSERT(item->accessFetchStatus() == TreeItem::LOADING); 0845 QModelIndex listIndex = item->toIndex(this); 0846 if (uidMapping.size()) { 0847 beginInsertRows(listIndex, 0, uidMapping.size() - 1); 0848 for (uint seq = 0; seq < static_cast<uint>(uidMapping.size()); ++seq) { 0849 TreeItemMessage *message = new TreeItemMessage(item); 0850 message->m_offset = seq; 0851 message->m_uid = uidMapping[seq]; 0852 item->m_children << message; 0853 QStringList flags = cache()->msgFlags(mailbox, message->m_uid); 0854 flags.removeOne(QStringLiteral("\\Recent")); 0855 message->m_flags = normalizeFlags(flags); 0856 } 0857 endInsertRows(); 0858 } 0859 mailboxPtr->syncState = oldSyncState; 0860 item->setFetchStatus(TreeItem::DONE); // required for FETCH processing later on 0861 // The list of messages was satisfied from cache. Do the same for the message counts, if applicable 0862 item->recalcVariousMessageCounts(this); 0863 } 0864 0865 if (networkPolicy() != NETWORK_OFFLINE) { 0866 findTaskResponsibleFor(mailboxPtr); 0867 // and that's all -- the task will detect following replies and sync automatically 0868 } 0869 } 0870 0871 void Model::askForNumberOfMessages(TreeItemMsgList *item) 0872 { 0873 Q_ASSERT(item->parent()); 0874 TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(item->parent()); 0875 Q_ASSERT(mailboxPtr); 0876 0877 if (networkPolicy() == NETWORK_OFFLINE) { 0878 Imap::Mailbox::SyncState syncState = cache()->mailboxSyncState(mailboxPtr->mailbox()); 0879 if (syncState.isUsableForNumbers()) { 0880 item->m_unreadMessageCount = syncState.unSeenCount(); 0881 item->m_totalMessageCount = syncState.exists(); 0882 item->m_recentMessageCount = syncState.recent(); 0883 item->m_numberFetchingStatus = TreeItem::DONE; 0884 emitMessageCountChanged(mailboxPtr); 0885 } else { 0886 item->m_numberFetchingStatus = TreeItem::UNAVAILABLE; 0887 } 0888 } else { 0889 m_taskFactory->createNumberOfMessagesTask(this, mailboxPtr->toIndex(this)); 0890 } 0891 } 0892 0893 void Model::askForMsgMetadata(TreeItemMessage *item, const PreloadingMode preloadMode) 0894 { 0895 Q_ASSERT(item->uid()); 0896 Q_ASSERT(!item->fetched()); 0897 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(item->parent()); 0898 Q_ASSERT(list); 0899 TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(list->parent()); 0900 Q_ASSERT(mailboxPtr); 0901 0902 if (item->uid()) { 0903 AbstractCache::MessageDataBundle data = cache()->messageMetadata(mailboxPtr->mailbox(), item->uid()); 0904 if (data.uid == item->uid()) { 0905 item->data()->setEnvelope(data.envelope); 0906 item->data()->setSize(data.size); 0907 item->data()->setHdrReferences(data.hdrReferences); 0908 item->data()->setHdrListPost(data.hdrListPost); 0909 item->data()->setHdrListPostNo(data.hdrListPostNo); 0910 QDataStream stream(&data.serializedBodyStructure, QIODevice::ReadOnly); 0911 stream.setVersion(QDataStream::Qt_4_6); 0912 QVariantList unserialized; 0913 stream >> unserialized; 0914 QSharedPointer<Message::AbstractMessage> abstractMessage; 0915 try { 0916 abstractMessage = Message::AbstractMessage::fromList(unserialized, QByteArray(), 0); 0917 } catch (Imap::ParserException &e) { 0918 qDebug() << "Error when parsing cached BODYSTRUCTURE" << e.what(); 0919 } 0920 if (! abstractMessage) { 0921 item->setFetchStatus(TreeItem::UNAVAILABLE); 0922 } else { 0923 auto newChildren = abstractMessage->createTreeItems(item); 0924 if (item->m_children.isEmpty()) { 0925 TreeItemChildrenList oldChildren = item->setChildren(newChildren); 0926 Q_ASSERT(oldChildren.size() == 0); 0927 } else { 0928 // The following assert guards against that crazy signal emitting we had when various askFor*() 0929 // functions were not delayed. If it gets hit, it means that someone tried to call this function 0930 // on an item which was already loaded. 0931 Q_ASSERT(item->m_children.isEmpty()); 0932 item->setChildren(newChildren); 0933 } 0934 item->setFetchStatus(TreeItem::DONE); 0935 } 0936 } 0937 } 0938 0939 switch (networkPolicy()) { 0940 case NETWORK_OFFLINE: 0941 if (item->accessFetchStatus() != TreeItem::DONE) 0942 item->setFetchStatus(TreeItem::UNAVAILABLE); 0943 break; 0944 case NETWORK_EXPENSIVE: 0945 if (item->accessFetchStatus() != TreeItem::DONE) { 0946 item->setFetchStatus(TreeItem::LOADING); 0947 findTaskResponsibleFor(mailboxPtr)->requestEnvelopeDownload(item->uid()); 0948 } 0949 break; 0950 case NETWORK_ONLINE: 0951 { 0952 if (item->accessFetchStatus() != TreeItem::DONE) { 0953 item->setFetchStatus(TreeItem::LOADING); 0954 findTaskResponsibleFor(mailboxPtr)->requestEnvelopeDownload(item->uid()); 0955 } 0956 0957 // preload 0958 if (preloadMode != PRELOAD_PER_POLICY) 0959 break; 0960 bool ok; 0961 int preload = property("trojita-imap-preload-msg-metadata").toInt(&ok); 0962 if (! ok) 0963 preload = 50; 0964 int order = item->row(); 0965 for (int i = qMax(0, order - preload); i < qMin(list->m_children.size(), order + preload); ++i) { 0966 TreeItemMessage *message = dynamic_cast<TreeItemMessage *>(list->m_children[i]); 0967 Q_ASSERT(message); 0968 if (item != message && !message->fetched() && !message->loading() && message->uid()) { 0969 message->setFetchStatus(TreeItem::LOADING); 0970 // cannot ask the KeepTask directly, that'd completely ignore the cache 0971 // but we absolutely have to block the preload :) 0972 askForMsgMetadata(message, PRELOAD_DISABLED); 0973 } 0974 } 0975 } 0976 break; 0977 } 0978 EMIT_LATER(this, dataChanged, Q_ARG(QModelIndex, item->toIndex(this)), Q_ARG(QModelIndex, item->toIndex(this))); 0979 } 0980 0981 void Model::askForMsgPart(TreeItemPart *item, bool onlyFromCache) 0982 { 0983 Q_ASSERT(item->message()); // TreeItemMessage 0984 Q_ASSERT(item->message()->parent()); // TreeItemMsgList 0985 Q_ASSERT(item->message()->parent()->parent()); // TreeItemMailbox 0986 TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(item->message()->parent()->parent()); 0987 Q_ASSERT(mailboxPtr); 0988 0989 // We are asking for a message part, which means that the structure of a message is already known. 0990 // If the UID was zero at this point, it would mean that we are completely doomed. 0991 uint uid = static_cast<TreeItemMessage *>(item->message())->uid(); 0992 Q_ASSERT(uid); 0993 0994 // Check whether this is a request for fetching the special item representing the raw contents prior to any CTE undoing 0995 TreeItemPart *itemForFetchOperation = item; 0996 TreeItemModifiedPart *modifiedPart = dynamic_cast<TreeItemModifiedPart*>(item); 0997 bool isSpecialRawPart = modifiedPart && modifiedPart->kind() == TreeItem::OFFSET_RAW_CONTENTS; 0998 if (isSpecialRawPart) { 0999 itemForFetchOperation = dynamic_cast<TreeItemPart*>(item->parent()); 1000 Q_ASSERT(itemForFetchOperation); 1001 } 1002 1003 const QByteArray &data = cache()->messagePart(mailboxPtr->mailbox(), uid, 1004 isSpecialRawPart ? 1005 itemForFetchOperation->partId() + ".X-RAW" 1006 : item->partId()); 1007 if (! data.isNull()) { 1008 item->m_data = data; 1009 item->setFetchStatus(TreeItem::DONE); 1010 return; 1011 } 1012 1013 if (!isSpecialRawPart) { 1014 const QByteArray &data = cache()->messagePart(mailboxPtr->mailbox(), uid, 1015 itemForFetchOperation->partId() + ".X-RAW"); 1016 1017 if (!data.isNull()) { 1018 Imap::decodeContentTransferEncoding(data, item->transferEncoding(), item->dataPtr()); 1019 item->setFetchStatus(TreeItem::DONE); 1020 return; 1021 } 1022 1023 if (item->m_partRaw && item->m_partRaw->loading()) { 1024 // There's already a request for the raw data. Let's use it and don't queue an extra fetch here. 1025 item->setFetchStatus(TreeItem::LOADING); 1026 return; 1027 } 1028 } 1029 1030 if (networkPolicy() == NETWORK_OFFLINE) { 1031 if (item->accessFetchStatus() != TreeItem::DONE) 1032 item->setFetchStatus(TreeItem::UNAVAILABLE); 1033 } else if (! onlyFromCache) { 1034 KeepMailboxOpenTask *keepTask = findTaskResponsibleFor(mailboxPtr); 1035 TreeItemPart::PartFetchingMode fetchingMode = TreeItemPart::FETCH_PART_IMAP; 1036 if (!isSpecialRawPart && keepTask->parser && accessParser(keepTask->parser).capabilitiesFresh && 1037 accessParser(keepTask->parser).capabilities.contains(QStringLiteral("BINARY"))) { 1038 if (!item->hasChildren(0) && !item->m_binaryCTEFailed) { 1039 // The BINARY only actually makes sense on leaf MIME nodes 1040 fetchingMode = TreeItemPart::FETCH_PART_BINARY; 1041 } 1042 } 1043 keepTask->requestPartDownload(item->message()->m_uid, itemForFetchOperation->partIdForFetch(fetchingMode), item->octets()); 1044 } 1045 } 1046 1047 void Model::resyncMailbox(const QModelIndex &mbox) 1048 { 1049 findTaskResponsibleFor(mbox)->resynchronizeMailbox(); 1050 } 1051 1052 void Model::setNetworkPolicy(const NetworkPolicy policy) 1053 { 1054 bool networkReconnected = m_netPolicy == NETWORK_OFFLINE && policy != NETWORK_OFFLINE; 1055 switch (policy) { 1056 case NETWORK_OFFLINE: 1057 for (QMap<Parser *,ParserState>::iterator it = m_parsers.begin(); it != m_parsers.end(); ++it) { 1058 if (!it->parser || it->connState == CONN_STATE_LOGOUT) { 1059 // there's no point in sending LOGOUT over these 1060 continue; 1061 } 1062 Q_ASSERT(it->parser); 1063 if (it->maintainingTask) { 1064 // First of all, give the maintaining task a chance to finish its housekeeping 1065 it->maintainingTask->stopForLogout(); 1066 } 1067 // Kill all tasks that are also using this connection 1068 Q_FOREACH(ImapTask *task, it->activeTasks) { 1069 task->die(tr("Going offline")); 1070 } 1071 it->logoutCmd = it->parser->logout(); 1072 changeConnectionState(it->parser, CONN_STATE_LOGOUT); 1073 } 1074 m_netPolicy = NETWORK_OFFLINE; 1075 m_periodicMailboxNumbersRefresh->stop(); 1076 emit networkPolicyChanged(); 1077 emit networkPolicyOffline(); 1078 1079 // FIXME: kill the connection 1080 break; 1081 case NETWORK_EXPENSIVE: 1082 m_netPolicy = NETWORK_EXPENSIVE; 1083 m_periodicMailboxNumbersRefresh->stop(); 1084 emit networkPolicyChanged(); 1085 emit networkPolicyExpensive(); 1086 break; 1087 case NETWORK_ONLINE: 1088 m_netPolicy = NETWORK_ONLINE; 1089 m_periodicMailboxNumbersRefresh->start(); 1090 emit networkPolicyChanged(); 1091 emit networkPolicyOnline(); 1092 break; 1093 } 1094 1095 if (networkReconnected) { 1096 // We're connecting after being offline 1097 if (m_mailboxes->accessFetchStatus() != TreeItem::NONE) { 1098 // We should ask for an updated list of mailboxes 1099 // The main reason is that this happens after entering wrong password and going back online 1100 reloadMailboxList(); 1101 } 1102 } else if (m_netPolicy == NETWORK_ONLINE) { 1103 // The connection is online after some time in a different mode. Let's use this opportunity to request 1104 // updated message counts from all visible mailboxes. 1105 invalidateAllMessageCounts(); 1106 } 1107 } 1108 1109 void Model::handleSocketDisconnectedResponse(Parser *ptr, const Responses::SocketDisconnectedResponse *const resp) 1110 { 1111 if (!accessParser(ptr).logoutCmd.isEmpty() || accessParser(ptr).connState == CONN_STATE_LOGOUT) { 1112 // If we're already scheduled for logout, don't treat connection errors as, well, errors. 1113 // This branch can be reached by e.g. user selecting offline after a network change, with logout 1114 // already on the fly. 1115 1116 // But we still absolutely want to clean up and kill the connection/Parser anyway 1117 killParser(ptr, PARSER_KILL_EXPECTED); 1118 } else { 1119 logTrace(ptr->parserId(), Common::LOG_PARSE_ERROR, QString(), resp->message); 1120 changeConnectionState(ptr, CONN_STATE_LOGOUT); 1121 killParser(ptr, PARSER_KILL_EXPECTED); 1122 EMIT_LATER(this, networkError, Q_ARG(QString, resp->message)); 1123 setNetworkPolicy(NETWORK_OFFLINE); 1124 } 1125 } 1126 1127 void Model::handleParseErrorResponse(Imap::Parser *ptr, const Imap::Responses::ParseErrorResponse *const resp) 1128 { 1129 Q_ASSERT(ptr); 1130 broadcastParseError(ptr->parserId(), resp->exceptionClass, resp->message, resp->line, resp->offset); 1131 killParser(ptr, PARSER_KILL_HARD); 1132 } 1133 1134 void Model::broadcastParseError(const uint parser, const QString &exceptionClass, const QString &errorMessage, const QByteArray &line, int position) 1135 { 1136 emit logParserFatalError(parser, exceptionClass, errorMessage, line, position); 1137 QString details = (position == -1) ? QString() : QString(position, QLatin1Char(' ')) + QLatin1String("^ here"); 1138 logTrace(parser, Common::LOG_PARSE_ERROR, exceptionClass, QStringLiteral("%1\n%2\n%3").arg(errorMessage, QString::fromUtf8(line), details)); 1139 QString message; 1140 if (exceptionClass == QLatin1String("NotAnImapServerError")) { 1141 QString service; 1142 if (line.startsWith("+OK") || line.startsWith("-ERR")) { 1143 service = tr("<p>It appears that you are connecting to a POP3 server. That won't work here.</p>"); 1144 } else if (line.startsWith("220 ") || line.startsWith("220-")) { 1145 service = tr("<p>It appears that you are connecting to an SMTP server. That won't work here.</p>"); 1146 } 1147 message = tr("<h2>This is not an IMAP server</h2>" 1148 "%1" 1149 "<p>Please check your settings to make sure you are connecting to the IMAP service. " 1150 "A typical port number for IMAP is 143 or 993.</p>" 1151 "<p>The server said:</p>" 1152 "<pre>%2</pre>").arg(service, QString::fromUtf8(line)); 1153 } else { 1154 message = tr("<p>The IMAP server sent us a reply which we could not parse. " 1155 "This might either mean that there's a bug in Trojitá's code, or " 1156 "that the IMAP server you are connected to is broken. Please " 1157 "report this as a bug anyway. Here are the details:</p>" 1158 "<p><b>%1</b>: %2</p>" 1159 "<pre>%3\n%4</pre>" 1160 ).arg(exceptionClass, errorMessage, QString::fromUtf8(line), details); 1161 } 1162 EMIT_LATER(this, imapError, Q_ARG(QString, message)); 1163 setNetworkPolicy(NETWORK_OFFLINE); 1164 } 1165 1166 void Model::switchToMailbox(const QModelIndex &mbox) 1167 { 1168 if (! mbox.isValid()) 1169 return; 1170 1171 if (m_netPolicy == NETWORK_OFFLINE) 1172 return; 1173 1174 findTaskResponsibleFor(mbox); 1175 } 1176 1177 void Model::updateCapabilities(Parser *parser, const QStringList capabilities) 1178 { 1179 Q_ASSERT(parser); 1180 QStringList uppercaseCaps; 1181 Q_FOREACH(const QString& str, capabilities) { 1182 QString cap = str.toUpper(); 1183 if (m_capabilitiesBlacklist.contains(cap)) { 1184 logTrace(parser->parserId(), Common::LOG_OTHER, QStringLiteral("Model"), QStringLiteral("Ignoring capability \"%1\"").arg(cap)); 1185 continue; 1186 } 1187 uppercaseCaps << cap; 1188 } 1189 accessParser(parser).capabilities = uppercaseCaps; 1190 accessParser(parser).capabilitiesFresh = true; 1191 if (uppercaseCaps.contains(QStringLiteral("LITERAL-"))) { 1192 parser->enableLiteralPlus(Parser::LiteralPlus::Minus); 1193 } else if (uppercaseCaps.contains(QStringLiteral("LITERAL+"))) { 1194 parser->enableLiteralPlus(Parser::LiteralPlus::Plus); 1195 } else { 1196 parser->enableLiteralPlus(Parser::LiteralPlus::Unsupported); 1197 } 1198 1199 for (QMap<Parser *,ParserState>::const_iterator it = m_parsers.constBegin(); it != m_parsers.constEnd(); ++it) { 1200 if (it->connState == CONN_STATE_LOGOUT) { 1201 // Skip all parsers which are currently stuck in LOGOUT 1202 continue; 1203 } else { 1204 // The CAPABILITIES were received by a first "usable" parser; let's treat this one as the authoritative one 1205 emit capabilitiesUpdated(uppercaseCaps); 1206 } 1207 } 1208 1209 if (!uppercaseCaps.contains(QStringLiteral("IMAP4REV1"))) { 1210 changeConnectionState(parser, CONN_STATE_LOGOUT); 1211 accessParser(parser).logoutCmd = parser->logout(); 1212 EMIT_LATER(this, imapError, Q_ARG(QString, tr("We aren't talking to an IMAP4 server"))); 1213 setNetworkPolicy(NETWORK_OFFLINE); 1214 } 1215 } 1216 1217 ImapTask *Model::setMessageFlags(const QModelIndexList &messages, const QString flag, const FlagsOperation marked) 1218 { 1219 Q_ASSERT(!messages.isEmpty()); 1220 Q_ASSERT(messages.front().model() == this); 1221 return m_taskFactory->createUpdateFlagsTask(this, messages, marked, QLatin1Char('(') + flag + QLatin1Char(')')); 1222 } 1223 1224 void Model::markMessagesDeleted(const QModelIndexList &messages, const FlagsOperation marked) 1225 { 1226 this->setMessageFlags(messages, QStringLiteral("\\Deleted"), marked); 1227 } 1228 1229 void Model::markMailboxAsRead(const QModelIndex &mailbox) 1230 { 1231 if (!mailbox.isValid()) 1232 return; 1233 1234 QModelIndex index; 1235 realTreeItem(mailbox, 0, &index); 1236 Q_ASSERT(index.isValid()); 1237 Q_ASSERT(index.model() == this); 1238 Q_ASSERT(dynamic_cast<TreeItemMailbox*>(static_cast<TreeItem*>(index.internalPointer()))); 1239 m_taskFactory->createUpdateFlagsOfAllMessagesTask(this, index, Imap::Mailbox::FLAG_ADD_SILENT, QStringLiteral("\\Seen")); 1240 } 1241 1242 void Model::markMessagesRead(const QModelIndexList &messages, const FlagsOperation marked) 1243 { 1244 this->setMessageFlags(messages, QStringLiteral("\\Seen"), marked); 1245 } 1246 1247 ImapTask *Model::copyMoveMessages(const QString &destMailboxName, const QModelIndexList &messages, const CopyMoveOperation op) 1248 { 1249 return m_taskFactory->createCopyMoveMessagesTask(this, messages, destMailboxName, op); 1250 } 1251 1252 void Model::copyMoveMessages(TreeItemMailbox *sourceMbox, const QString &destMailboxName, Imap::Uids uids, const CopyMoveOperation op) 1253 { 1254 if (m_netPolicy == NETWORK_OFFLINE) { 1255 // FIXME: error signalling 1256 return; 1257 } 1258 1259 Q_ASSERT(sourceMbox); 1260 1261 std::sort(uids.begin(), uids.end()); 1262 1263 QModelIndexList messages; 1264 Sequence seq; 1265 Q_FOREACH(TreeItemMessage* m, findMessagesByUids(sourceMbox, uids)) { 1266 messages << m->toIndex(this); 1267 seq.add(m->uid()); 1268 } 1269 m_taskFactory->createCopyMoveMessagesTask(this, messages, destMailboxName, op); 1270 } 1271 1272 /** @short Convert a list of UIDs to a list of pointers to the relevant message nodes */ 1273 QList<TreeItemMessage *> Model::findMessagesByUids(const TreeItemMailbox *const mailbox, const Imap::Uids &uids) 1274 { 1275 const TreeItemMsgList *const list = dynamic_cast<const TreeItemMsgList *const>(mailbox->m_children[0]); 1276 Q_ASSERT(list); 1277 QList<TreeItemMessage *> res; 1278 auto it = list->m_children.constBegin(); 1279 uint lastUid = 0; 1280 Q_FOREACH(const uint uid, uids) { 1281 if (lastUid == uid) { 1282 // we have to filter out duplicates 1283 continue; 1284 } 1285 lastUid = uid; 1286 it = Common::lowerBoundWithUnknownElements(it, list->m_children.constEnd(), uid, messageHasUidZero, uidComparator); 1287 if (it != list->m_children.end() && static_cast<TreeItemMessage *>(*it)->uid() == uid) { 1288 res << static_cast<TreeItemMessage *>(*it); 1289 } else { 1290 qDebug() << "Can't find UID" << uid; 1291 } 1292 } 1293 return res; 1294 } 1295 1296 /** @short Find a message with UID that matches the passed key, handling those with UID zero correctly 1297 1298 If there's no such message, the next message with a valid UID is returned instead. If there are no such messages, the iterator can 1299 point to a message with UID zero or to the end of the list. 1300 */ 1301 TreeItemChildrenList::iterator Model::findMessageOrNextOneByUid(TreeItemMsgList *list, const uint uid) 1302 { 1303 return Common::lowerBoundWithUnknownElements(list->m_children.begin(), list->m_children.end(), uid, messageHasUidZero, uidComparator); 1304 } 1305 1306 TreeItemMailbox *Model::findMailboxByName(const QString &name) const 1307 { 1308 return findMailboxByName(name, m_mailboxes); 1309 } 1310 1311 TreeItemMailbox *Model::findMailboxByName(const QString &name, const TreeItemMailbox *const root) const 1312 { 1313 Q_ASSERT(!root->m_children.isEmpty()); 1314 // Names are sorted, so linear search is not required. On the ohterhand, the mailbox sizes are typically small enough 1315 // so that this shouldn't matter at all, and linear search is simple enough. 1316 for (int i = 1; i < root->m_children.size(); ++i) { 1317 TreeItemMailbox *mailbox = static_cast<TreeItemMailbox *>(root->m_children[i]); 1318 if (name == mailbox->mailbox()) 1319 return mailbox; 1320 else if (name.startsWith(mailbox->mailbox() + mailbox->separator())) 1321 return findMailboxByName(name, mailbox); 1322 } 1323 return 0; 1324 } 1325 1326 /** @short Find a parent mailbox for the specified name */ 1327 TreeItemMailbox *Model::findParentMailboxByName(const QString &name) const 1328 { 1329 TreeItemMailbox *root = m_mailboxes; 1330 while (true) { 1331 if (root->m_children.size() == 1) { 1332 break; 1333 } 1334 bool found = false; 1335 for (int i = 1; !found && i < root->m_children.size(); ++i) { 1336 TreeItemMailbox *const item = dynamic_cast<TreeItemMailbox *>(root->m_children[i]); 1337 Q_ASSERT(item); 1338 if (name.startsWith(item->mailbox() + item->separator())) { 1339 root = item; 1340 found = true; 1341 } 1342 } 1343 if (!found) { 1344 return root; 1345 } 1346 } 1347 return root; 1348 } 1349 1350 1351 void Model::expungeMailbox(const QModelIndex &mailbox) 1352 { 1353 if (!mailbox.isValid()) 1354 return; 1355 1356 if (m_netPolicy == NETWORK_OFFLINE) { 1357 qDebug() << "Can't expunge while offline"; 1358 return; 1359 } 1360 1361 m_taskFactory->createExpungeMailboxTask(this, mailbox); 1362 } 1363 1364 void Model::createMailbox(const QString &name, const AutoSubscription subscription) 1365 { 1366 if (m_netPolicy == NETWORK_OFFLINE) { 1367 qDebug() << "Can't create mailboxes while offline"; 1368 return; 1369 } 1370 1371 auto task = m_taskFactory->createCreateMailboxTask(this, name); 1372 if (subscription == AutoSubscription::SUBSCRIBE) { 1373 m_taskFactory->createSubscribeUnsubscribeTask(this, task, name, SUBSCRIBE); 1374 } 1375 } 1376 1377 void Model::deleteMailbox(const QString &name) 1378 { 1379 if (m_netPolicy == NETWORK_OFFLINE) { 1380 qDebug() << "Can't delete mailboxes while offline"; 1381 return; 1382 } 1383 1384 m_taskFactory->createDeleteMailboxTask(this, name); 1385 } 1386 1387 void Model::subscribeMailbox(const QString &name) 1388 { 1389 if (m_netPolicy == NETWORK_OFFLINE) { 1390 qDebug() << "Can't manage subscriptions while offline"; 1391 return; 1392 } 1393 1394 TreeItemMailbox *mailbox = findMailboxByName(name); 1395 if (!mailbox) { 1396 qDebug() << "SUBSCRIBE: No such mailbox."; 1397 return; 1398 } 1399 m_taskFactory->createSubscribeUnsubscribeTask(this, name, SUBSCRIBE); 1400 } 1401 1402 void Model::unsubscribeMailbox(const QString &name) 1403 { 1404 if (m_netPolicy == NETWORK_OFFLINE) { 1405 qDebug() << "Can't manage subscriptions while offline"; 1406 return; 1407 } 1408 1409 TreeItemMailbox *mailbox = findMailboxByName(name); 1410 if (!mailbox) { 1411 qDebug() << "UNSUBSCRIBE: No such mailbox."; 1412 return; 1413 } 1414 m_taskFactory->createSubscribeUnsubscribeTask(this, name, UNSUBSCRIBE); 1415 } 1416 1417 void Model::saveUidMap(TreeItemMsgList *list) 1418 { 1419 Imap::Uids seqToUid(list->m_children.size(), 0); 1420 std::transform(list->m_children.constBegin(), list->m_children.cend(), seqToUid.begin(), [](TreeItem *item) { 1421 return static_cast<TreeItemMessage *>(item)->uid(); 1422 }); 1423 cache()->setUidMapping(static_cast<TreeItemMailbox *>(list->parent())->mailbox(), seqToUid); 1424 } 1425 1426 1427 TreeItem *Model::realTreeItem(QModelIndex index, const Model **whichModel, QModelIndex *translatedIndex) 1428 { 1429 index = Imap::deproxifiedIndex(index); 1430 const Model *model = qobject_cast<const Model *>(index.model()); 1431 Q_ASSERT(model); 1432 if (whichModel) 1433 *whichModel = model; 1434 if (translatedIndex) 1435 *translatedIndex = index; 1436 return static_cast<TreeItem *>(index.internalPointer()); 1437 } 1438 1439 void Model::changeConnectionState(Parser *parser, ConnectionState state) 1440 { 1441 accessParser(parser).connState = state; 1442 logTrace(parser->parserId(), Common::LOG_TASKS, QStringLiteral("conn"), connectionStateToString(state)); 1443 emit connectionStateChanged(parser->parserId(), state); 1444 } 1445 1446 void Model::handleSocketStateChanged(Parser *parser, Imap::ConnectionState state) 1447 { 1448 Q_ASSERT(parser); 1449 if (accessParser(parser).connState < state) { 1450 changeConnectionState(parser, state); 1451 } 1452 } 1453 1454 void Model::killParser(Parser *parser, ParserKillingMethod method) 1455 { 1456 if (method == PARSER_JUST_DELETE_LATER) { 1457 Q_ASSERT(accessParser(parser).parser == 0); 1458 Q_FOREACH(ImapTask *task, accessParser(parser).activeTasks) { 1459 task->deleteLater(); 1460 } 1461 parser->deleteLater(); 1462 return; 1463 } 1464 1465 Q_FOREACH(ImapTask *task, accessParser(parser).activeTasks) { 1466 // FIXME: now this message sucks 1467 task->die(tr("The connection is being killed for unspecified reason")); 1468 } 1469 1470 parser->disconnect(); 1471 Q_ASSERT(accessParser(parser).parser); 1472 accessParser(parser).parser = 0; 1473 switch (method) { 1474 case PARSER_KILL_EXPECTED: 1475 logTrace(parser->parserId(), Common::LOG_IO_WRITTEN, QString(), QStringLiteral("*** Connection closed.")); 1476 return; 1477 case PARSER_KILL_HARD: 1478 logTrace(parser->parserId(), Common::LOG_IO_WRITTEN, QString(), QStringLiteral("*** Connection killed.")); 1479 return; 1480 case PARSER_JUST_DELETE_LATER: 1481 // already handled 1482 return; 1483 } 1484 Q_ASSERT(false); 1485 } 1486 1487 void Model::slotParserLineReceived(Parser *parser, const QByteArray &line) 1488 { 1489 logTrace(parser->parserId(), Common::LOG_IO_READ, QString(), QString::fromUtf8(line)); 1490 } 1491 1492 void Model::slotParserLineSent(Parser *parser, const QByteArray &line) 1493 { 1494 logTrace(parser->parserId(), Common::LOG_IO_WRITTEN, QString(), QString::fromUtf8(line)); 1495 } 1496 1497 void Model::setCache(std::shared_ptr<AbstractCache> cache) 1498 { 1499 m_cache = cache; 1500 } 1501 1502 void Model::runReadyTasks() 1503 { 1504 for (QMap<Parser *,ParserState>::iterator parserIt = m_parsers.begin(); parserIt != m_parsers.end(); ++parserIt) { 1505 bool runSomething = false; 1506 do { 1507 runSomething = false; 1508 // See responseReceived() for more details about why we do need to iterate over a copy here. 1509 // Basically, calls to ImapTask::perform could invalidate our precious iterators. 1510 QList<ImapTask *> origList = parserIt->activeTasks; 1511 QList<ImapTask *> deletedList; 1512 QList<ImapTask *>::const_iterator taskEnd = origList.constEnd(); 1513 for (QList<ImapTask *>::const_iterator taskIt = origList.constBegin(); taskIt != taskEnd; ++taskIt) { 1514 ImapTask *task = *taskIt; 1515 if (task->isReadyToRun()) { 1516 task->perform(); 1517 runSomething = true; 1518 } 1519 if (task->isFinished()) { 1520 deletedList << task; 1521 } 1522 } 1523 removeDeletedTasks(deletedList, parserIt->activeTasks); 1524 #ifdef TROJITA_DEBUG_TASK_TREE 1525 if (!deletedList.isEmpty()) 1526 checkTaskTreeConsistency(); 1527 #endif 1528 } while (runSomething); 1529 } 1530 } 1531 1532 void Model::removeDeletedTasks(const QList<ImapTask *> &deletedTasks, QList<ImapTask *> &activeTasks) 1533 { 1534 // Remove the finished commands 1535 for (QList<ImapTask *>::const_iterator deletedIt = deletedTasks.begin(); deletedIt != deletedTasks.end(); ++deletedIt) { 1536 (*deletedIt)->deleteLater(); 1537 activeTasks.removeOne(*deletedIt); 1538 // It isn't destroyed yet, but should be removed from the model nonetheless 1539 m_taskModel->slotSomeTaskDestroyed(); 1540 } 1541 } 1542 1543 KeepMailboxOpenTask *Model::findTaskResponsibleFor(const QModelIndex &mailbox) 1544 { 1545 Q_ASSERT(mailbox.isValid()); 1546 QModelIndex translatedIndex; 1547 TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(realTreeItem(mailbox, 0, &translatedIndex)); 1548 return findTaskResponsibleFor(mailboxPtr); 1549 } 1550 1551 KeepMailboxOpenTask *Model::findTaskResponsibleFor(TreeItemMailbox *mailboxPtr) 1552 { 1553 Q_ASSERT(mailboxPtr); 1554 bool canCreateParallelConn = m_parsers.isEmpty(); // FIXME: multiple connections 1555 1556 if (mailboxPtr->maintainingTask) { 1557 // The requested mailbox already has the maintaining task associated 1558 if (accessParser(mailboxPtr->maintainingTask->parser).connState == CONN_STATE_LOGOUT) { 1559 // The connection is currently getting closed, so we have to create another one 1560 return m_taskFactory->createKeepMailboxOpenTask(this, mailboxPtr->toIndex(this), 0); 1561 } else { 1562 // it's usable as-is 1563 return mailboxPtr->maintainingTask; 1564 } 1565 } else if (canCreateParallelConn) { 1566 // The mailbox is not being maintained, but we can create a new connection 1567 return m_taskFactory->createKeepMailboxOpenTask(this, mailboxPtr->toIndex(this), 0); 1568 } else { 1569 // Too bad, we have to re-use an existing parser. That will probably lead to 1570 // stealing it from some mailbox, but there's no other way. 1571 Q_ASSERT(!m_parsers.isEmpty()); 1572 1573 for (QMap<Parser *,ParserState>::const_iterator it = m_parsers.constBegin(); it != m_parsers.constEnd(); ++it) { 1574 if (it->connState == CONN_STATE_LOGOUT) { 1575 // this one is not usable 1576 continue; 1577 } 1578 return m_taskFactory->createKeepMailboxOpenTask(this, mailboxPtr->toIndex(this), it.key()); 1579 } 1580 // At this point, we have no other choice than to create a new connection 1581 return m_taskFactory->createKeepMailboxOpenTask(this, mailboxPtr->toIndex(this), 0); 1582 } 1583 } 1584 1585 void Model::genericHandleFetch(TreeItemMailbox *mailbox, const Imap::Responses::Fetch *const resp) 1586 { 1587 Q_ASSERT(mailbox); 1588 QList<TreeItemPart *> changedParts; 1589 TreeItemMessage *changedMessage = 0; 1590 mailbox->handleFetchResponse(this, *resp, changedParts, changedMessage, false); 1591 if (! changedParts.isEmpty()) { 1592 Q_FOREACH(TreeItemPart* part, changedParts) { 1593 QModelIndex index = part->toIndex(this); 1594 emit dataChanged(index, index); 1595 } 1596 } 1597 if (changedMessage) { 1598 QModelIndex index = changedMessage->toIndex(this); 1599 emit dataChanged(index, index); 1600 emitMessageCountChanged(mailbox); 1601 } 1602 } 1603 1604 QModelIndex Model::findMailboxForItems(const QModelIndexList &items) 1605 { 1606 TreeItemMailbox *mailbox = 0; 1607 Q_FOREACH(const QModelIndex& index, items) { 1608 TreeItemMailbox *currentMailbox = 0; 1609 Q_ASSERT(index.model() == this); 1610 1611 TreeItem *item = static_cast<TreeItem *>(index.internalPointer()); 1612 Q_ASSERT(item); 1613 1614 if ((currentMailbox = dynamic_cast<TreeItemMailbox *>(item))) { 1615 // yes, that's an assignment, not a comparison 1616 1617 // This case is OK 1618 } else { 1619 // TreeItemMessage and TreeItemPart have to walk the tree, which is why they are lumped together in this branch 1620 TreeItemMessage *message = dynamic_cast<TreeItemMessage *>(item); 1621 if (!message) { 1622 if (TreeItemPart *part = dynamic_cast<TreeItemPart *>(item)) { 1623 message = part->message(); 1624 } else { 1625 throw CantHappen("findMailboxForItems() called on strange items"); 1626 } 1627 } 1628 Q_ASSERT(message); 1629 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(message->parent()); 1630 Q_ASSERT(list); 1631 currentMailbox = dynamic_cast<TreeItemMailbox *>(list->parent()); 1632 } 1633 1634 Q_ASSERT(currentMailbox); 1635 if (!mailbox) { 1636 mailbox = currentMailbox; 1637 } else if (mailbox != currentMailbox) { 1638 throw CantHappen("Messages from several mailboxes"); 1639 } 1640 } 1641 return mailbox->toIndex(this); 1642 } 1643 1644 void Model::slotTasksChanged() 1645 { 1646 dumpModelContents(m_taskModel); 1647 } 1648 1649 void Model::slotTaskDying(QObject *obj) 1650 { 1651 std::for_each(m_parsers.begin(), m_parsers.end(), [obj](ParserState &state) { 1652 state.activeTasks.removeOne(reinterpret_cast<ImapTask*>(obj)); 1653 }); 1654 m_taskModel->slotSomeTaskDestroyed(); 1655 } 1656 1657 TreeItemMailbox *Model::mailboxForSomeItem(QModelIndex index) 1658 { 1659 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(index.internalPointer())); 1660 while (index.isValid() && ! mailbox) { 1661 index = index.parent(); 1662 mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(index.internalPointer())); 1663 } 1664 return mailbox; 1665 } 1666 1667 ParserState &Model::accessParser(Parser *parser) 1668 { 1669 Q_ASSERT(m_parsers.contains(parser)); 1670 return m_parsers[ parser ]; 1671 } 1672 1673 void Model::releaseMessageData(const QModelIndex &message) 1674 { 1675 if (! message.isValid()) 1676 return; 1677 1678 const Model *whichModel = 0; 1679 QModelIndex realMessage; 1680 realTreeItem(message, &whichModel, &realMessage); 1681 Q_ASSERT(whichModel == this); 1682 1683 TreeItemMessage *msg = dynamic_cast<TreeItemMessage *>(static_cast<TreeItem *>(realMessage.internalPointer())); 1684 if (! msg) 1685 return; 1686 1687 msg->setFetchStatus(TreeItem::NONE); 1688 1689 if (msg->data()->partHeader()) { 1690 msg->data()->partHeader()->silentlyReleaseMemoryRecursive(); 1691 msg->data()->setPartHeader(nullptr); 1692 } 1693 if (msg->data()->partText()) { 1694 msg->data()->partText()->silentlyReleaseMemoryRecursive(); 1695 msg->data()->setPartText(nullptr); 1696 } 1697 delete msg->m_data; 1698 msg->m_data = 0; 1699 Q_FOREACH(TreeItem *item, msg->m_children) { 1700 TreeItemPart *part = dynamic_cast<TreeItemPart *>(item); 1701 Q_ASSERT(part); 1702 part->silentlyReleaseMemoryRecursive(); 1703 delete part; 1704 } 1705 msg->m_children.clear(); 1706 } 1707 1708 QStringList Model::capabilities() const 1709 { 1710 if (m_parsers.isEmpty()) 1711 return QStringList(); 1712 1713 if (m_parsers.constBegin()->capabilitiesFresh) 1714 return m_parsers.constBegin()->capabilities; 1715 1716 return QStringList(); 1717 } 1718 1719 void Model::logTrace(uint parserId, const Common::LogKind kind, const QString &source, const QString &message) 1720 { 1721 Common::LogMessage m(QDateTime::currentDateTime(), kind, source, message, 0); 1722 emit logged(parserId, m); 1723 } 1724 1725 /** @short Overloaded version which accepts a QModelIndex of an item which is somehow "related" to the logged message 1726 1727 The relevantIndex argument is used for finding out what parser to send the message to. 1728 */ 1729 void Model::logTrace(const QModelIndex &relevantIndex, const Common::LogKind kind, const QString &source, const QString &message) 1730 { 1731 Q_ASSERT(relevantIndex.isValid()); 1732 QModelIndex translatedIndex; 1733 realTreeItem(relevantIndex, 0, &translatedIndex); 1734 1735 // It appears that it's OK to use 0 here; the attached loggers apparently deal with random parsers appearing just OK 1736 uint parserId = 0; 1737 1738 if (translatedIndex.isValid()) { 1739 Q_ASSERT(translatedIndex.model() == this); 1740 QModelIndex mailboxIndex = findMailboxForItems(QModelIndexList() << translatedIndex); 1741 Q_ASSERT(mailboxIndex.isValid()); 1742 TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer())); 1743 Q_ASSERT(mailboxPtr); 1744 if (mailboxPtr->maintainingTask) { 1745 parserId = mailboxPtr->maintainingTask->parser->parserId(); 1746 } 1747 } 1748 1749 logTrace(parserId, kind, source, message); 1750 } 1751 1752 QAbstractItemModel *Model::taskModel() const 1753 { 1754 return m_taskModel; 1755 } 1756 1757 QMap<QByteArray,QByteArray> Model::serverId() const 1758 { 1759 return m_idResult; 1760 } 1761 1762 /** @short Handle explicit sharing and case mapping for message flags 1763 1764 This function will try to minimize the amount of QString instances used for storage of individual message flags via Qt's implicit 1765 sharing that is built into QString. 1766 1767 At the same time, some well-known flags are converted to their "canonical" form (like \\SEEN -> \\Seen etc). 1768 */ 1769 QStringList Model::normalizeFlags(const QStringList &source) const 1770 { 1771 QStringList res; 1772 res.reserve(source.size()); 1773 for (QStringList::const_iterator flag = source.constBegin(); flag != source.constEnd(); ++flag) { 1774 1775 // At first, perform a case-insensitive lookup in the (rather short) list of known special flags 1776 // Only call the toLower for flags which could possibly be in that mapping. Looking at the first letter is 1777 // a good approximation. 1778 if (!flag->isEmpty() && ((*flag)[0] == QLatin1Char('\\') || (*flag)[0] == QLatin1Char('$'))) { 1779 QString lowerCase = flag->toLower(); 1780 QHash<QString,QString>::const_iterator known = FlagNames::toCanonical.constFind(lowerCase); 1781 if (known != FlagNames::toCanonical.constEnd()) { 1782 res.append(*known); 1783 continue; 1784 } 1785 } 1786 1787 // If it isn't a special flag, just check whether it's been encountered already 1788 QSet<QString>::const_iterator it = m_flagLiterals.constFind(*flag); 1789 if (it == m_flagLiterals.constEnd()) { 1790 // Not in cache, so add it and return an implicitly shared copy 1791 m_flagLiterals.insert(*flag); 1792 res.append(*flag); 1793 } else { 1794 // It's in the cache already, se let's QString share the data 1795 res.append(*it); 1796 } 1797 } 1798 // Always sort the flags when performing normalization to obtain reasonable results and be ready for possible future 1799 // deduplication of the actual QLists 1800 res.sort(); 1801 return res; 1802 } 1803 1804 /** @short Set the IMAP username */ 1805 void Model::setImapUser(const QString &imapUser) 1806 { 1807 m_imapUser = imapUser; 1808 } 1809 1810 /** @short Username to use for login */ 1811 QString Model::imapUser() const 1812 { 1813 return m_imapUser; 1814 } 1815 1816 /** @short Set the password that the user wants to use */ 1817 void Model::setImapPassword(const QString &password) 1818 { 1819 m_imapPassword = password; 1820 m_hasImapPassword = PasswordAvailability::AVAILABLE; 1821 informTasksAboutNewPassword(); 1822 } 1823 1824 /** @short Return the user's password, if cached */ 1825 QString Model::imapPassword() const 1826 { 1827 return m_imapPassword; 1828 } 1829 1830 /** @short Indicate that the user doesn't want to provide her password */ 1831 void Model::unsetImapPassword() 1832 { 1833 m_imapPassword.clear(); 1834 m_hasImapPassword = PasswordAvailability::NOT_REQUESTED; 1835 informTasksAboutNewPassword(); 1836 } 1837 1838 QString Model::imapAuthError() const 1839 { 1840 return m_imapAuthError; 1841 } 1842 1843 void Model::setImapAuthError(const QString &error) 1844 { 1845 if (m_imapAuthError == error) 1846 return; 1847 m_imapAuthError = error; 1848 emit imapAuthErrorChanged(error); 1849 } 1850 1851 /** @short Tell all tasks which want to know about the availability of a password */ 1852 void Model::informTasksAboutNewPassword() 1853 { 1854 Q_FOREACH(const ParserState &p, m_parsers) { 1855 Q_FOREACH(ImapTask *task, p.activeTasks) { 1856 OpenConnectionTask *openTask = dynamic_cast<OpenConnectionTask *>(task); 1857 if (!openTask) 1858 continue; 1859 openTask->authCredentialsNowAvailable(); 1860 } 1861 } 1862 } 1863 1864 /** @short Forward a policy decision about accepting or rejecting a SSL state */ 1865 void Model::setSslPolicy(const QList<QSslCertificate> &sslChain, const QList<QSslError> &sslErrors, bool proceed) 1866 { 1867 if (proceed) { 1868 // Only remember positive values; there is no point in blocking any further connections until settings reload 1869 m_sslErrorPolicy.prepend(qMakePair(qMakePair(sslChain, sslErrors), proceed)); 1870 } 1871 Q_FOREACH(const ParserState &p, m_parsers) { 1872 Q_FOREACH(ImapTask *task, p.activeTasks) { 1873 OpenConnectionTask *openTask = dynamic_cast<OpenConnectionTask *>(task); 1874 if (!openTask) 1875 continue; 1876 if (openTask->sslCertificateChain() == sslChain && openTask->sslErrors() == sslErrors) { 1877 openTask->sslConnectionPolicyDecided(proceed); 1878 } 1879 } 1880 } 1881 } 1882 1883 void Model::processSslErrors(OpenConnectionTask *task) 1884 { 1885 // Qt doesn't define either operator< or a qHash specialization for QList<QSslError> (what a surprise), 1886 // so we use a plain old QList. Given that there will be at most one different QList<QSslError> sequence for 1887 // each connection attempt (and more realistically, for each server at all), this O(n) complexity shall not matter 1888 // at all. 1889 QList<QPair<QPair<QList<QSslCertificate>, QList<QSslError> >, bool> >::const_iterator it = m_sslErrorPolicy.constBegin(); 1890 while (it != m_sslErrorPolicy.constEnd()) { 1891 if (it->first.first == task->sslCertificateChain() && it->first.second == task->sslErrors()) { 1892 task->sslConnectionPolicyDecided(it->second); 1893 return; 1894 } 1895 ++it; 1896 } 1897 EMIT_LATER(this, needsSslDecision, Q_ARG(QList<QSslCertificate>, task->sslCertificateChain()), Q_ARG(QList<QSslError>, task->sslErrors())); 1898 } 1899 1900 QModelIndex Model::messageIndexByUid(const QString &mailboxName, const uint uid) 1901 { 1902 TreeItemMailbox *mailbox = findMailboxByName(mailboxName); 1903 Q_ASSERT(mailbox); 1904 QList<TreeItemMessage*> messages = findMessagesByUids(mailbox, Imap::Uids() << uid); 1905 if (messages.isEmpty()) { 1906 return QModelIndex(); 1907 } else { 1908 Q_ASSERT(messages.size() == 1); 1909 return messages.front()->toIndex(this); 1910 } 1911 } 1912 1913 /** @short Forget any cached data about number of messages in all mailboxes */ 1914 void Model::invalidateAllMessageCounts() 1915 { 1916 QList<TreeItemMailbox*> queue; 1917 queue.append(m_mailboxes); 1918 while (!queue.isEmpty()) { 1919 TreeItemMailbox *head = queue.takeFirst(); 1920 // ignore first child, the TreeItemMsgList 1921 for (auto it = head->m_children.constBegin() + 1; it != head->m_children.constEnd(); ++it) { 1922 queue.append(static_cast<TreeItemMailbox*>(*it)); 1923 } 1924 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList*>(head->m_children[0]); 1925 1926 if (list->m_numberFetchingStatus == TreeItem::DONE && !head->maintainingTask) { 1927 // Ask only for data which were previously available 1928 // Also don't mess with a mailbox which is already being kept up-to-date because it's selected. 1929 list->m_numberFetchingStatus = TreeItem::NONE; 1930 emitMessageCountChanged(head); 1931 } 1932 } 1933 } 1934 1935 AppendTask *Model::appendIntoMailbox(const QString &mailbox, const QByteArray &rawMessageData, const QStringList &flags, 1936 const QDateTime ×tamp) 1937 { 1938 return m_taskFactory->createAppendTask(this, mailbox, rawMessageData, flags, timestamp); 1939 } 1940 1941 AppendTask *Model::appendIntoMailbox(const QString &mailbox, const QList<CatenatePair> &data, const QStringList &flags, 1942 const QDateTime ×tamp) 1943 { 1944 return m_taskFactory->createAppendTask(this, mailbox, data, flags, timestamp); 1945 } 1946 1947 GenUrlAuthTask *Model::generateUrlAuthForMessage(const QString &host, const QString &user, const QString &mailbox, 1948 const uint uidValidity, const uint uid, const QString &part, const QString &access) 1949 { 1950 return m_taskFactory->createGenUrlAuthTask(this, host, user, mailbox, uidValidity, uid, part, access); 1951 } 1952 1953 UidSubmitTask *Model::sendMailViaUidSubmit(const QString &mailbox, const uint uidValidity, const uint uid, 1954 const UidSubmitOptionsList &options) 1955 { 1956 return m_taskFactory->createUidSubmitTask(this, mailbox, uidValidity, uid, options); 1957 } 1958 1959 #ifdef TROJITA_DEBUG_TASK_TREE 1960 #define TROJITA_DEBUG_TASK_TREE_VERBOSE 1961 void Model::checkTaskTreeConsistency() 1962 { 1963 for (QMap<Parser *,ParserState>::const_iterator parserIt = m_parsers.constBegin(); parserIt != m_parsers.constEnd(); ++parserIt) { 1964 #ifdef TROJITA_DEBUG_TASK_TREE_VERBOSE 1965 qDebug() << "\nParser" << parserIt.key() << "; all active tasks:"; 1966 Q_FOREACH(ImapTask *activeTask, parserIt.value().activeTasks) { 1967 qDebug() << ' ' << activeTask << activeTask->debugIdentification() << activeTask->parser; 1968 } 1969 #endif 1970 Q_FOREACH(ImapTask *activeTask, parserIt.value().activeTasks) { 1971 #ifdef TROJITA_DEBUG_TASK_TREE_VERBOSE 1972 qDebug() << "Active task" << activeTask << activeTask->debugIdentification() << activeTask->parser; 1973 #endif 1974 Q_ASSERT(activeTask->parser == parserIt.key()); 1975 Q_ASSERT(!activeTask->parentTask); 1976 checkDependentTasksConsistency(parserIt.key(), activeTask, 0, 0); 1977 } 1978 1979 // Make sure that no task is present twice in here 1980 QList<ImapTask*> taskQueue = parserIt.value().activeTasks; 1981 for (int i = 0; i < taskQueue.size(); ++i) { 1982 Q_FOREACH(ImapTask *yetAnotherTask, taskQueue[i]->dependentTasks) { 1983 Q_ASSERT(!taskQueue.contains(yetAnotherTask)); 1984 taskQueue.push_back(yetAnotherTask); 1985 } 1986 } 1987 } 1988 } 1989 1990 void Model::checkDependentTasksConsistency(Parser *parser, ImapTask *task, ImapTask *expectedParentTask, int depth) 1991 { 1992 #ifdef TROJITA_DEBUG_TASK_TREE_VERBOSE 1993 QByteArray prefix; 1994 prefix.fill(' ', depth); 1995 qDebug() << prefix.constData() << "Checking" << task << task->debugIdentification(); 1996 #endif 1997 Q_ASSERT(parser); 1998 Q_ASSERT(!task->parser || task->parser == parser); 1999 Q_ASSERT(task->parentTask == expectedParentTask); 2000 if (task->parentTask) { 2001 Q_ASSERT(task->parentTask->dependentTasks.contains(task)); 2002 if (task->parentTask->parentTask) { 2003 Q_ASSERT(task->parentTask->parentTask->dependentTasks.contains(task->parentTask)); 2004 } else { 2005 Q_ASSERT(task->parentTask->parser); 2006 Q_ASSERT(accessParser(task->parentTask->parser).activeTasks.contains(task->parentTask)); 2007 } 2008 } else { 2009 Q_ASSERT(accessParser(parser).activeTasks.contains(task)); 2010 } 2011 2012 Q_FOREACH(ImapTask *childTask, task->dependentTasks) { 2013 checkDependentTasksConsistency(parser, childTask, task, depth + 1); 2014 } 2015 } 2016 #endif 2017 2018 void Model::setCapabilitiesBlacklist(const QStringList &blacklist) 2019 { 2020 m_capabilitiesBlacklist = blacklist; 2021 } 2022 2023 bool Model::isCatenateSupported() const 2024 { 2025 return capabilities().contains(QStringLiteral("CATENATE")); 2026 } 2027 2028 bool Model::isGenUrlAuthSupported() const 2029 { 2030 return capabilities().contains(QStringLiteral("URLAUTH")); 2031 } 2032 2033 bool Model::isImapSubmissionSupported() const 2034 { 2035 QStringList caps = capabilities(); 2036 return caps.contains(QStringLiteral("UIDPLUS")) && caps.contains(QStringLiteral("X-DRAFT-I01-SENDMAIL")); 2037 } 2038 2039 void Model::setNumberRefreshInterval(const int interval) 2040 { 2041 if (interval == m_periodicMailboxNumbersRefresh->interval()) 2042 return; // QTimer does not check idempotency 2043 m_periodicMailboxNumbersRefresh->start(interval * 1000); 2044 } 2045 2046 } 2047 }