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 &timestamp)
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 &timestamp)
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 }