1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
2 
3    This file is part of the Trojita Qt IMAP e-mail client,
4    http://trojita.flaska.net/
5 
6    This program is free software; you can redistribute it and/or
7    modify it under the terms of the GNU General Public License as
8    published by the Free Software Foundation; either version 2 of
9    the License or (at your option) version 3 or any later version
10    accepted by the membership of KDE e.V. (or its successor approved
11    by the membership of KDE e.V.), which shall act as a proxy
12    defined in Section 14 of version 3 of the license.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22 
23 #include <QAbstractProxyModel>
24 #include <QAuthenticator>
25 #include <QCoreApplication>
26 #include <QDebug>
27 #include <QtAlgorithms>
28 #include "Model.h"
29 #include "MailboxTree.h"
30 #include "SpecialFlagNames.h"
31 #include "TaskPresentationModel.h"
32 #include "Utils.h"
33 #include "Common/FindWithUnknown.h"
34 #include "Common/InvokeMethod.h"
35 #include "Imap/Encoders.h"
36 #include "Imap/Tasks/AppendTask.h"
37 #include "Imap/Tasks/CreateMailboxTask.h"
38 #include "Imap/Tasks/GetAnyConnectionTask.h"
39 #include "Imap/Tasks/KeepMailboxOpenTask.h"
40 #include "Imap/Tasks/OpenConnectionTask.h"
41 #include "Imap/Tasks/UpdateFlagsTask.h"
42 #include "Streams/SocketFactory.h"
43 
44 //#define DEBUG_PERIODICALLY_DUMP_TASKS
45 //#define DEBUG_TASK_ROUTING
46 
47 namespace
48 {
49 
50 using namespace Imap::Mailbox;
51 
52 /** @short Return true iff the two mailboxes have the same name
53 
54 It's an error to call this function on anything else but a mailbox.
55 */
MailboxNamesEqual(const TreeItem * const a,const TreeItem * const b)56 bool MailboxNamesEqual(const TreeItem *const a, const TreeItem *const b)
57 {
58     const TreeItemMailbox *const mailboxA = dynamic_cast<const TreeItemMailbox *const>(a);
59     const TreeItemMailbox *const mailboxB = dynamic_cast<const TreeItemMailbox *const>(b);
60     Q_ASSERT(mailboxA);
61     Q_ASSERT(mailboxB);
62 
63     return mailboxA->mailbox() == mailboxB->mailbox();
64 }
65 
66 /** @short Mailbox name comparator to be used when sorting mailbox names
67 
68 The special-case mailbox name, the "INBOX", is always sorted as the first one.
69 */
MailboxNameComparator(const TreeItem * const a,const TreeItem * const b)70 bool MailboxNameComparator(const TreeItem *const a, const TreeItem *const b)
71 {
72     const TreeItemMailbox *const mailboxA = dynamic_cast<const TreeItemMailbox *const>(a);
73     const TreeItemMailbox *const mailboxB = dynamic_cast<const TreeItemMailbox *const>(b);
74 
75     if (mailboxA->mailbox() == QLatin1String("INBOX"))
76         return true;
77     if (mailboxB->mailbox() == QLatin1String("INBOX"))
78         return false;
79     return mailboxA->mailbox().compare(mailboxB->mailbox(), Qt::CaseInsensitive) < 1;
80 }
81 
uidComparator(const TreeItem * const item,const uint uid)82 bool uidComparator(const TreeItem *const item, const uint uid)
83 {
84     const TreeItemMessage *const message = static_cast<const TreeItemMessage *const>(item);
85     uint messageUid = message->uid();
86     Q_ASSERT(messageUid);
87     return messageUid < uid;
88 }
89 
messageHasUidZero(const TreeItem * const item)90 bool messageHasUidZero(const TreeItem *const item)
91 {
92     const TreeItemMessage *const message = static_cast<const TreeItemMessage *const>(item);
93     return message->uid() == 0;
94 }
95 
96 }
97 
98 namespace Imap
99 {
100 namespace Mailbox
101 {
102 
Model(QObject * parent,AbstractCache * cache,SocketFactoryPtr socketFactory,TaskFactoryPtr taskFactory)103 Model::Model(QObject *parent, AbstractCache *cache, SocketFactoryPtr socketFactory, TaskFactoryPtr taskFactory):
104     // parent
105     QAbstractItemModel(parent),
106     // our tools
107     m_cache(cache), m_socketFactory(std::move(socketFactory)), m_taskFactory(std::move(taskFactory)), m_maxParsers(4), m_mailboxes(0),
108     m_netPolicy(NETWORK_OFFLINE),  m_taskModel(0), m_hasImapPassword(PasswordAvailability::NOT_REQUESTED)
109 {
110     m_cache->setParent(this);
111     m_startTls = m_socketFactory->startTlsRequired();
112 
113     m_mailboxes = new TreeItemMailbox(0);
114 
115     onlineMessageFetch << QStringLiteral("ENVELOPE") << QStringLiteral("BODYSTRUCTURE") << QStringLiteral("RFC822.SIZE") <<
116                           QStringLiteral("UID") << QStringLiteral("FLAGS");
117 
118     EMIT_LATER_NOARG(this, networkPolicyOffline);
119     EMIT_LATER_NOARG(this, networkPolicyChanged);
120 
121 #ifdef DEBUG_PERIODICALLY_DUMP_TASKS
122     QTimer *periodicTaskDumper = new QTimer(this);
123     periodicTaskDumper->setInterval(1000);
124     connect(periodicTaskDumper, &QTimer::timeout, this, &Model::slotTasksChanged);
125     periodicTaskDumper->start();
126 #endif
127 
128     m_taskModel = new TaskPresentationModel(this);
129 
130     m_periodicMailboxNumbersRefresh = new QTimer(this);
131     // polling every five minutes
132     m_periodicMailboxNumbersRefresh->setInterval(5 * 60 * 1000);
133     connect(m_periodicMailboxNumbersRefresh, &QTimer::timeout, this, &Model::invalidateAllMessageCounts);
134 }
135 
~Model()136 Model::~Model()
137 {
138     delete m_mailboxes;
139 }
140 
141 /** @short Process responses from all sockets */
responseReceived()142 void Model::responseReceived()
143 {
144     for (QMap<Parser *,ParserState>::iterator it = m_parsers.begin(); it != m_parsers.end(); ++it) {
145         responseReceived(it);
146     }
147 }
148 
149 /** @short Process responses from the specified parser */
responseReceived(Parser * parser)150 void Model::responseReceived(Parser *parser)
151 {
152     QMap<Parser *,ParserState>::iterator it = m_parsers.find(parser);
153     if (it == m_parsers.end()) {
154         // This is a queued signal, so it's perfectly possible that the sender is gone already
155         return;
156     }
157     responseReceived(it);
158 }
159 
160 /** @short Process responses from the specified parser */
responseReceived(const QMap<Parser *,ParserState>::iterator it)161 void Model::responseReceived(const QMap<Parser *,ParserState>::iterator it)
162 {
163     Q_ASSERT(it->parser);
164 
165     int counter = 0;
166     while (it->parser && it->parser->hasResponse()) {
167         QSharedPointer<Imap::Responses::AbstractResponse> resp = it->parser->getResponse();
168         Q_ASSERT(resp);
169         // Always log BAD responses from a central place. They're bad enough to warant an extra treatment.
170         // FIXME: is it worth an UI popup?
171         if (Responses::State *stateResponse = dynamic_cast<Responses::State *>(resp.data())) {
172             if (stateResponse->kind == Responses::BAD) {
173                 QString buf;
174                 QTextStream s(&buf);
175                 s << *stateResponse;
176                 logTrace(it->parser->parserId(), Common::LOG_OTHER, QStringLiteral("Model"), QStringLiteral("BAD response: %1").arg(buf));
177                 qDebug() << buf;
178             }
179         }
180         try {
181             /* At this point, we want to iterate over all active tasks and try them
182             for processing the server's responses (the plug() method). However, this
183             is rather complex -- this call to plug() could result in signals being
184             emitted, and certain slots connected to those signals might in turn want
185             to queue more Tasks. Therefore, it->activeTasks could be modified, some
186             items could be appended to it using the QList::append, which in turn could
187             cause a realloc to happen, happily invalidating our iterators, and that
188             kind of sucks.
189 
190             So, we have to iterate over a copy of the original list and instead of
191             deleting Tasks, we store them into a temporary list. When we're done with
192             processing, we walk the original list once again and simply remove all
193             "deleted" items for real.
194 
195             This took me 3+ hours to track it down to what the hell was happening here,
196             even though the underlying reason is simple -- QList::append() could invalidate
197             existing iterators.
198             */
199 
200             bool handled = false;
201             QList<ImapTask *> taskSnapshot = it->activeTasks;
202             QList<ImapTask *> deletedTasks;
203             QList<ImapTask *>::const_iterator taskEnd = taskSnapshot.constEnd();
204 
205             // Try various tasks, perhaps it's their response. Also check if they're already finished and remove them.
206             for (QList<ImapTask *>::const_iterator taskIt = taskSnapshot.constBegin(); taskIt != taskEnd; ++taskIt) {
207                 if (! handled) {
208 
209 #ifdef DEBUG_TASK_ROUTING
210                     try {
211                         logTrace(it->parser->parserId(), Common::LOG_TASKS, QString(),
212                                  QString::fromAscii("Routing to %1 %2").arg(QString::fromAscii((*taskIt)->metaObject()->className()),
213                                                                             (*taskIt)->debugIdentification()));
214 #endif
215                     handled = resp->plug(*taskIt);
216 #ifdef DEBUG_TASK_ROUTING
217                         if (handled) {
218                             logTrace(it->parser->parserId(), Common::LOG_TASKS, (*taskIt)->debugIdentification(), QLatin1String("Handled"));
219                         }
220                     } catch (std::exception &e) {
221                         logTrace(it->parser->parserId(), Common::LOG_TASKS, (*taskIt)->debugIdentification(), QLatin1String("Got exception when handling"));
222                         throw;
223                     }
224 #endif
225                 }
226 
227                 if ((*taskIt)->isFinished()) {
228                     deletedTasks << *taskIt;
229                 }
230             }
231 
232             removeDeletedTasks(deletedTasks, it->activeTasks);
233 
234             runReadyTasks();
235 
236             if (! handled) {
237                 resp->plug(it->parser, this);
238 #ifdef DEBUG_TASK_ROUTING
239                 if (it->parser) {
240                     logTrace(it->parser->parserId(), Common::LOG_TASKS, QLatin1String("Model"), QLatin1String("Handled"));
241                 } else {
242                     logTrace(0, Common::LOG_TASKS, QLatin1String("Model"), QLatin1String("Handled"));
243                 }
244 #endif
245             }
246         } catch (Imap::ImapException &e) {
247             uint parserId = it->parser->parserId();
248             killParser(it->parser, PARSER_KILL_HARD);
249             broadcastParseError(parserId, QString::fromStdString(e.exceptionClass()), QString::fromUtf8(e.what()), e.line(), e.offset());
250             break;
251         }
252 
253         // Return to the event loop every 100 messages to handle GUI events
254         ++counter;
255         if (counter == 100) {
256             QTimer::singleShot(0, this, SLOT(responseReceived()));
257             break;
258         }
259     }
260 
261     if (!it->parser) {
262         // He's dead, Jim
263         m_taskModel->beginResetModel();
264         killParser(it.key(), PARSER_JUST_DELETE_LATER);
265         m_parsers.erase(it);
266         m_taskModel->endResetModel();
267     }
268 }
269 
handleState(Imap::Parser * ptr,const Imap::Responses::State * const resp)270 void Model::handleState(Imap::Parser *ptr, const Imap::Responses::State *const resp)
271 {
272     // OK/NO/BAD/PREAUTH/BYE
273     using namespace Imap::Responses;
274 
275     const QByteArray &tag = resp->tag;
276 
277     if (!tag.isEmpty()) {
278         if (tag == accessParser(ptr).logoutCmd) {
279             // The LOGOUT is special, as it isn't associated with any task
280             killParser(ptr, PARSER_KILL_EXPECTED);
281         } else {
282             if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
283                 return;
284             // Unhandled command -- this is *extremely* weird
285             throw CantHappen("The following command should have been handled elsewhere", *resp);
286         }
287     } else {
288         // untagged response
289         // FIXME: we should probably just eat them and don't bother, as untagged OK/NO could be rather common...
290         switch (resp->kind) {
291         case BYE:
292             if (accessParser(ptr).logoutCmd.isEmpty()) {
293                 // The connection got closed but we haven't really requested that -- we better treat that as error, including
294                 // going offline...
295                 // ... but before that, expect that the connection will get closed soon
296                 changeConnectionState(ptr, CONN_STATE_LOGOUT);
297                 EMIT_LATER(this, imapError, Q_ARG(QString, resp->message));
298                 setNetworkPolicy(NETWORK_OFFLINE);
299             }
300             if (accessParser(ptr).parser) {
301                 // previous block could enter the event loop and hence kill our parser; we shouldn't try to kill it twice
302                 killParser(ptr, PARSER_KILL_EXPECTED);
303             }
304             break;
305         case OK:
306             if (resp->respCode == NONE) {
307                 // This one probably should not be logged at all; dovecot sends these reponses to keep NATted connections alive
308                 break;
309             } else {
310                 logTrace(ptr->parserId(), Common::LOG_OTHER, QString(), QStringLiteral("Warning: unhandled untagged OK with a response code"));
311                 break;
312             }
313         case NO:
314             logTrace(ptr->parserId(), Common::LOG_OTHER, QString(), QStringLiteral("Warning: unhandled untagged NO..."));
315             break;
316         default:
317             throw UnexpectedResponseReceived("Unhandled untagged response, sorry", *resp);
318         }
319     }
320 }
321 
finalizeList(Parser * parser,TreeItemMailbox * mailboxPtr)322 void Model::finalizeList(Parser *parser, TreeItemMailbox *mailboxPtr)
323 {
324     TreeItemChildrenList mailboxes;
325 
326     QList<Responses::List> &listResponses = accessParser(parser).listResponses;
327     const QString prefix = mailboxPtr->mailbox() + mailboxPtr->separator();
328     for (QList<Responses::List>::iterator it = listResponses.begin(); it != listResponses.end(); /* nothing */) {
329         if (it->mailbox == mailboxPtr->mailbox() || it->mailbox == prefix) {
330             // rubbish, ignore
331             it = listResponses.erase(it);
332         } else if (it->mailbox.startsWith(prefix)) {
333             if (!mailboxPtr->separator().isEmpty() && it->mailbox.midRef(prefix.length()).contains(mailboxPtr->separator())) {
334                 // This is about a mailbox which is nested deeper beneath the current thing (e.g., we're listing A.B.%,
335                 // and the current response is A.B.C.1), so let's assume that it's some else's LIST response.
336                 // The separator/delimiter checking is (hopefully) correct -- see https://tools.ietf.org/html/rfc3501#page-70 .
337                 ++it;
338             } else {
339                 mailboxes << new TreeItemMailbox(mailboxPtr, *it);
340                 it = listResponses.erase(it);
341             }
342         } else {
343             // it clearly is someone else's LIST response
344             ++it;
345         }
346     }
347     qSort(mailboxes.begin(), mailboxes.end(), MailboxNameComparator);
348 
349     // Remove duplicates; would be great if this could be done in a STLish way,
350     // but unfortunately std::unique won't help here (the "duped" part of the
351     // sequence contains undefined items)
352     if (mailboxes.size() > 1) {
353         auto it = mailboxes.begin();
354         // We've got to ignore the first one, that's the message list
355         ++it;
356         while (it != mailboxes.end()) {
357             if (MailboxNamesEqual(it[-1], *it)) {
358                 delete *it;
359                 it = mailboxes.erase(it);
360             } else {
361                 ++it;
362             }
363         }
364     }
365 
366     QList<MailboxMetadata> metadataToCache;
367     TreeItemChildrenList mailboxesWithoutChildren;
368     for (auto it = mailboxes.constBegin(); it != mailboxes.constEnd(); ++it) {
369         TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(*it);
370         Q_ASSERT(mailbox);
371         metadataToCache.append(mailbox->mailboxMetadata());
372         if (mailbox->hasNoChildMailboxesAlreadyKnown()) {
373             mailboxesWithoutChildren << mailbox;
374         }
375     }
376     cache()->setChildMailboxes(mailboxPtr->mailbox(), metadataToCache);
377     for (auto it = mailboxesWithoutChildren.constBegin(); it != mailboxesWithoutChildren.constEnd(); ++it)
378         cache()->setChildMailboxes(static_cast<TreeItemMailbox *>(*it)->mailbox(), QList<MailboxMetadata>());
379     replaceChildMailboxes(mailboxPtr, mailboxes);
380 }
381 
finalizeIncrementalList(Parser * parser,const QString & parentMailboxName)382 void Model::finalizeIncrementalList(Parser *parser, const QString &parentMailboxName)
383 {
384     TreeItemMailbox *parentMbox = findParentMailboxByName(parentMailboxName);
385     if (! parentMbox) {
386         qDebug() << "Weird, no idea where to put the newly created mailbox" << parentMailboxName;
387         return;
388     }
389 
390     QList<TreeItem *> mailboxes;
391 
392     QList<Responses::List> &listResponses = accessParser(parser).listResponses;
393     for (QList<Responses::List>::iterator it = listResponses.begin();
394          it != listResponses.end(); /* nothing */) {
395         if (it->mailbox == parentMailboxName) {
396             mailboxes << new TreeItemMailbox(parentMbox, *it);
397             it = listResponses.erase(it);
398         } else {
399             // it clearly is someone else's LIST response
400             ++it;
401         }
402     }
403     qSort(mailboxes.begin(), mailboxes.end(), MailboxNameComparator);
404 
405     if (mailboxes.size() == 0) {
406         qDebug() << "Weird, no matching LIST response for our prompt after CREATE";
407         qDeleteAll(mailboxes);
408         return;
409     } else if (mailboxes.size() > 1) {
410         qDebug() << "Weird, too many LIST responses for our prompt after CREATE";
411         qDeleteAll(mailboxes);
412         return;
413     }
414 
415     auto it = parentMbox->m_children.begin();
416     Q_ASSERT(it != parentMbox->m_children.end());
417     ++it;
418     while (it != parentMbox->m_children.end() && MailboxNameComparator(*it, mailboxes[0]))
419         ++it;
420     QModelIndex parentIdx = parentMbox == m_mailboxes ? QModelIndex() : parentMbox->toIndex(this);
421     if (it == parentMbox->m_children.end())
422         beginInsertRows(parentIdx, parentMbox->m_children.size(), parentMbox->m_children.size());
423     else
424         beginInsertRows(parentIdx, (*it)->row(), (*it)->row());
425     parentMbox->m_children.insert(it, mailboxes[0]);
426     endInsertRows();
427 }
428 
replaceChildMailboxes(TreeItemMailbox * mailboxPtr,const TreeItemChildrenList & mailboxes)429 void Model::replaceChildMailboxes(TreeItemMailbox *mailboxPtr, const TreeItemChildrenList &mailboxes)
430 {
431     /* Previously, we would call layoutAboutToBeChanged() and layoutChanged() here, but it
432        resulted in invalid memory access in the attached QSortFilterProxyModels like this one:
433 
434     ==23294== Invalid read of size 4
435     ==23294==    at 0x5EA34B1: QSortFilterProxyModelPrivate::index_to_iterator(QModelIndex const&) const (qsortfilterproxymodel.cpp:191)
436     ==23294==    by 0x5E9F8A3: QSortFilterProxyModel::parent(QModelIndex const&) const (qsortfilterproxymodel.cpp:1654)
437     ==23294==    by 0x5C5D45D: QModelIndex::parent() const (qabstractitemmodel.h:389)
438     ==23294==    by 0x5E47C48: QTreeView::drawRow(QPainter*, QStyleOptionViewItem const&, QModelIndex const&) const (qtreeview.cpp:1479)
439     ==23294==    by 0x5E479D9: QTreeView::drawTree(QPainter*, QRegion const&) const (qtreeview.cpp:1441)
440     ==23294==    by 0x5E4703A: QTreeView::paintEvent(QPaintEvent*) (qtreeview.cpp:1274)
441     ==23294==    by 0x5810C30: QWidget::event(QEvent*) (qwidget.cpp:8346)
442     ==23294==    by 0x5C91D03: QFrame::event(QEvent*) (qframe.cpp:557)
443     ==23294==    by 0x5D4259C: QAbstractScrollArea::viewportEvent(QEvent*) (qabstractscrollarea.cpp:1043)
444     ==23294==    by 0x5DFFD6E: QAbstractItemView::viewportEvent(QEvent*) (qabstractitemview.cpp:1619)
445     ==23294==    by 0x5E46EE0: QTreeView::viewportEvent(QEvent*) (qtreeview.cpp:1256)
446     ==23294==    by 0x5D43110: QAbstractScrollAreaPrivate::viewportEvent(QEvent*) (qabstractscrollarea_p.h:100)
447     ==23294==  Address 0x908dbec is 20 bytes inside a block of size 24 free'd
448     ==23294==    at 0x4024D74: operator delete(void*) (vg_replace_malloc.c:346)
449     ==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)
450     ==23294==    by 0x5EA3C06: void qDeleteAll<QHash<QModelIndex, QSortFilterProxyModelPrivate::Mapping*> >(QHash<QModelIndex, QSortFilterProxyModelPrivate::Mapping*> const&) (qalgorithms.h:330)
451     ==23294==    by 0x5E9E64B: QSortFilterProxyModelPrivate::_q_sourceLayoutChanged() (qsortfilterproxymodel.cpp:1249)
452     ==23294==    by 0x5EA29EC: QSortFilterProxyModel::qt_metacall(QMetaObject::Call, int, void**) (moc_qsortfilterproxymodel.cpp:133)
453     ==23294==    by 0x80EB205: Imap::Mailbox::PrettyMailboxModel::qt_metacall(QMetaObject::Call, int, void**) (moc_PrettyMailboxModel.cpp:64)
454     ==23294==    by 0x65D3EAD: QMetaObject::metacall(QObject*, QMetaObject::Call, int, void**) (qmetaobject.cpp:237)
455     ==23294==    by 0x65E8D7C: QMetaObject::activate(QObject*, QMetaObject const*, int, void**) (qobject.cpp:3272)
456     ==23294==    by 0x664A7E8: QAbstractItemModel::layoutChanged() (moc_qabstractitemmodel.cpp:161)
457     ==23294==    by 0x664A354: QAbstractItemModel::qt_metacall(QMetaObject::Call, int, void**) (moc_qabstractitemmodel.cpp:118)
458     ==23294==    by 0x5E9A3A9: QAbstractProxyModel::qt_metacall(QMetaObject::Call, int, void**) (moc_qabstractproxymodel.cpp:67)
459     ==23294==    by 0x80EAF3D: Imap::Mailbox::MailboxModel::qt_metacall(QMetaObject::Call, int, void**) (moc_MailboxModel.cpp:81)
460 
461        I have no idea why something like that happens -- layoutChanged() should be a hint that the indexes are gone now, which means
462        that the code should *not* use tham after that point. That's just weird.
463     */
464 
465     QModelIndex parent = mailboxPtr == m_mailboxes ? QModelIndex() : mailboxPtr->toIndex(this);
466 
467     if (mailboxPtr->m_children.size() != 1) {
468         // There's something besides the TreeItemMsgList and we're going to
469         // overwrite them, so we have to delete them right now
470         int count = mailboxPtr->rowCount(this);
471         beginRemoveRows(parent, 1, count - 1);
472         auto oldItems = mailboxPtr->setChildren(TreeItemChildrenList());
473         endRemoveRows();
474 
475         qDeleteAll(oldItems);
476     }
477 
478     if (! mailboxes.isEmpty()) {
479         beginInsertRows(parent, 1, mailboxes.size());
480         auto dummy = mailboxPtr->setChildren(mailboxes);
481         endInsertRows();
482         Q_ASSERT(dummy.isEmpty());
483     } else {
484         auto dummy = mailboxPtr->setChildren(mailboxes);
485         Q_ASSERT(dummy.isEmpty());
486     }
487     emit dataChanged(parent, parent);
488 }
489 
emitMessageCountChanged(TreeItemMailbox * const mailbox)490 void Model::emitMessageCountChanged(TreeItemMailbox *const mailbox)
491 {
492     TreeItemMsgList *list = static_cast<TreeItemMsgList *>(mailbox->m_children[0]);
493     QModelIndex msgListIndex = list->toIndex(this);
494     emit dataChanged(msgListIndex, msgListIndex);
495     QModelIndex mailboxIndex = mailbox->toIndex(this);
496     emit dataChanged(mailboxIndex, mailboxIndex);
497     emit messageCountPossiblyChanged(mailboxIndex);
498 }
499 
500 /** @short Retrieval of a message part has completed */
finalizeFetchPart(TreeItemMailbox * const mailbox,const uint sequenceNo,const QByteArray & partId)501 bool Model::finalizeFetchPart(TreeItemMailbox *const mailbox, const uint sequenceNo, const QByteArray &partId)
502 {
503     // At first, verify that the message itself is marked as loaded.
504     // If it isn't, it's probably because of Model::releaseMessageData().
505     TreeItem *item = mailbox->m_children[0]; // TreeItemMsgList
506     item = item->child(sequenceNo - 1, this);   // TreeItemMessage
507     Q_ASSERT(item);   // FIXME: or rather throw an exception?
508     if (item->accessFetchStatus() == TreeItem::NONE) {
509         // ...and it indeed got released, so let's just return and don't try to check anything
510         return false;
511     }
512 
513     TreeItemPart *part = mailbox->partIdToPtr(this, static_cast<TreeItemMessage *>(item), partId);
514     if (! part) {
515         qDebug() << "Can't verify part fetching status: part is not here!";
516         return false;
517     }
518     if (part->loading()) {
519         part->setFetchStatus(TreeItem::UNAVAILABLE);
520         QModelIndex idx = part->toIndex(this);
521         emit dataChanged(idx, idx);
522         return false;
523     } else {
524         return true;
525     }
526 }
527 
handleCapability(Imap::Parser * ptr,const Imap::Responses::Capability * const resp)528 void Model::handleCapability(Imap::Parser *ptr, const Imap::Responses::Capability *const resp)
529 {
530     updateCapabilities(ptr, resp->capabilities);
531 }
532 
handleNumberResponse(Imap::Parser * ptr,const Imap::Responses::NumberResponse * const resp)533 void Model::handleNumberResponse(Imap::Parser *ptr, const Imap::Responses::NumberResponse *const resp)
534 {
535     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
536         return;
537     throw UnexpectedResponseReceived("[Tasks API Port] Unhandled NumberResponse", *resp);
538 }
539 
handleList(Imap::Parser * ptr,const Imap::Responses::List * const resp)540 void Model::handleList(Imap::Parser *ptr, const Imap::Responses::List *const resp)
541 {
542     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
543         return;
544     accessParser(ptr).listResponses << *resp;
545 }
546 
handleFlags(Imap::Parser * ptr,const Imap::Responses::Flags * const resp)547 void Model::handleFlags(Imap::Parser *ptr, const Imap::Responses::Flags *const resp)
548 {
549     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
550         return;
551     throw UnexpectedResponseReceived("[Tasks API Port] Unhandled Flags", *resp);
552 }
553 
handleSearch(Imap::Parser * ptr,const Imap::Responses::Search * const resp)554 void Model::handleSearch(Imap::Parser *ptr, const Imap::Responses::Search *const resp)
555 {
556     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
557         return;
558     throw UnexpectedResponseReceived("[Tasks API Port] Unhandled Search", *resp);
559 }
560 
handleESearch(Imap::Parser * ptr,const Imap::Responses::ESearch * const resp)561 void Model::handleESearch(Imap::Parser *ptr, const Imap::Responses::ESearch *const resp)
562 {
563     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
564         return;
565     throw UnexpectedResponseReceived("Unhandled ESEARCH", *resp);
566 }
567 
handleStatus(Imap::Parser * ptr,const Imap::Responses::Status * const resp)568 void Model::handleStatus(Imap::Parser *ptr, const Imap::Responses::Status *const resp)
569 {
570     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
571         return;
572     Q_UNUSED(ptr);
573     TreeItemMailbox *mailbox = findMailboxByName(resp->mailbox);
574     if (! mailbox) {
575         qDebug() << "Couldn't find out which mailbox is" << resp->mailbox << "when parsing a STATUS reply";
576         return;
577     }
578     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(mailbox->m_children[0]);
579     Q_ASSERT(list);
580     bool updateCache = false;
581     Imap::Responses::Status::stateDataType::const_iterator it = resp->states.constEnd();
582     if ((it = resp->states.constFind(Imap::Responses::Status::MESSAGES)) != resp->states.constEnd()) {
583         updateCache |= list->m_totalMessageCount != static_cast<const int>(it.value());
584         list->m_totalMessageCount = it.value();
585     }
586     if ((it = resp->states.constFind(Imap::Responses::Status::UNSEEN)) != resp->states.constEnd()) {
587         updateCache |= list->m_unreadMessageCount != static_cast<const int>(it.value());
588         list->m_unreadMessageCount = it.value();
589     }
590     if ((it = resp->states.constFind(Imap::Responses::Status::RECENT)) != resp->states.constEnd()) {
591         updateCache |= list->m_recentMessageCount != static_cast<const int>(it.value());
592         list->m_recentMessageCount = it.value();
593     }
594     list->m_numberFetchingStatus = TreeItem::DONE;
595     emitMessageCountChanged(mailbox);
596 
597     if (updateCache) {
598         // We have to be very careful to only touch the bits which are *not* used by the mailbox syncing code.
599         // This is absolutely crucial -- STATUS is just a meaningless indicator, and stuff like the UID mapping
600         // is definitely *not* updated at the same time. That's also why we completely ignore any data whatsoever
601         // from the TreeItemMailbox' syncState, if any, and just work with the cache directly.
602         auto state = cache()->mailboxSyncState(mailbox->mailbox());
603         // We are *not* updating the total message count as that conflicts with the mailbox syncing
604         if (list->m_unreadMessageCount != -1) {
605             state.setUnSeenCount(list->m_unreadMessageCount);
606         }
607         if (list->m_recentMessageCount != -1) {
608             state.setRecent(list->m_recentMessageCount);
609         }
610         cache()->setMailboxSyncState(mailbox->mailbox(), state);
611     }
612 }
613 
handleFetch(Imap::Parser * ptr,const Imap::Responses::Fetch * const resp)614 void Model::handleFetch(Imap::Parser *ptr, const Imap::Responses::Fetch *const resp)
615 {
616     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
617         return;
618     throw UnexpectedResponseReceived("[Tasks API Port] Unhandled Fetch", *resp);
619 }
620 
handleNamespace(Imap::Parser * ptr,const Imap::Responses::Namespace * const resp)621 void Model::handleNamespace(Imap::Parser *ptr, const Imap::Responses::Namespace *const resp)
622 {
623     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
624         return;
625     return; // because it's broken and won't fly
626     Q_UNUSED(ptr);
627     Q_UNUSED(resp);
628 }
629 
handleSort(Imap::Parser * ptr,const Imap::Responses::Sort * const resp)630 void Model::handleSort(Imap::Parser *ptr, const Imap::Responses::Sort *const resp)
631 {
632     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
633         return;
634     throw UnexpectedResponseReceived("[Tasks API Port] Unhandled Sort", *resp);
635 }
636 
handleThread(Imap::Parser * ptr,const Imap::Responses::Thread * const resp)637 void Model::handleThread(Imap::Parser *ptr, const Imap::Responses::Thread *const resp)
638 {
639     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
640         return;
641     throw UnexpectedResponseReceived("[Tasks API Port] Unhandled Thread", *resp);
642 }
643 
handleId(Parser * ptr,const Responses::Id * const resp)644 void Model::handleId(Parser *ptr, const Responses::Id *const resp)
645 {
646     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
647         return;
648     throw UnexpectedResponseReceived("Unhandled ID response", *resp);
649 }
650 
handleEnabled(Parser * ptr,const Responses::Enabled * const resp)651 void Model::handleEnabled(Parser *ptr, const Responses::Enabled *const resp)
652 {
653     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
654         return;
655     throw UnexpectedResponseReceived("Unhandled ENABLED response", *resp);
656 }
657 
handleVanished(Parser * ptr,const Responses::Vanished * const resp)658 void Model::handleVanished(Parser *ptr, const Responses::Vanished *const resp)
659 {
660     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
661         return;
662     throw UnexpectedResponseReceived("Unhandled VANISHED response", *resp);
663 }
664 
handleGenUrlAuth(Parser * ptr,const Responses::GenUrlAuth * const resp)665 void Model::handleGenUrlAuth(Parser *ptr, const Responses::GenUrlAuth *const resp)
666 {
667     if (accessParser(ptr).connState == CONN_STATE_LOGOUT)
668         return;
669     throw UnexpectedResponseReceived("Unhandled GENURLAUTH response", *resp);
670 }
671 
handleSocketEncryptedResponse(Parser * ptr,const Responses::SocketEncryptedResponse * const resp)672 void Model::handleSocketEncryptedResponse(Parser *ptr, const Responses::SocketEncryptedResponse *const resp)
673 {
674     Q_UNUSED(resp);
675     logTrace(ptr->parserId(), Common::LOG_IO_READ, QStringLiteral("Model"), QStringLiteral("Information about the SSL state not handled by the upper layers -- disconnect?"));
676     killParser(ptr, PARSER_KILL_EXPECTED);
677 }
678 
translatePtr(const QModelIndex & index) const679 TreeItem *Model::translatePtr(const QModelIndex &index) const
680 {
681     return index.internalPointer() ? static_cast<TreeItem *>(index.internalPointer()) : m_mailboxes;
682 }
683 
data(const QModelIndex & index,int role) const684 QVariant Model::data(const QModelIndex &index, int role) const
685 {
686     return translatePtr(index)->data(const_cast<Model *>(this), role);
687 }
688 
index(int row,int column,const QModelIndex & parent) const689 QModelIndex Model::index(int row, int column, const QModelIndex &parent) const
690 {
691     if (parent.isValid()) {
692         Q_ASSERT(parent.model() == this);
693     }
694 
695     TreeItem *parentItem = translatePtr(parent);
696 
697     // Deal with the possibility of an "irregular shape" of our model here.
698     // The issue is that some items have child items not only in column #0
699     // and in specified number of rows, but also in row #0 and various columns.
700     if (column != 0) {
701         TreeItem *item = parentItem->specialColumnPtr(row, column);
702         if (item)
703             return QAbstractItemModel::createIndex(row, column, item);
704         else
705             return QModelIndex();
706     }
707 
708     TreeItem *child = parentItem->child(row, const_cast<Model *>(this));
709 
710     return child ? QAbstractItemModel::createIndex(row, column, child) : QModelIndex();
711 }
712 
parent(const QModelIndex & index) const713 QModelIndex Model::parent(const QModelIndex &index) const
714 {
715     if (!index.isValid())
716         return QModelIndex();
717 
718     Q_ASSERT(index.model() == this);
719 
720     TreeItem *childItem = static_cast<TreeItem *>(index.internalPointer());
721     TreeItem *parentItem = childItem->parent();
722 
723     if (! parentItem || parentItem == m_mailboxes)
724         return QModelIndex();
725 
726     return QAbstractItemModel::createIndex(parentItem->row(), 0, parentItem);
727 }
728 
rowCount(const QModelIndex & index) const729 int Model::rowCount(const QModelIndex &index) const
730 {
731     TreeItem *node = static_cast<TreeItem *>(index.internalPointer());
732     if (!node) {
733         node = m_mailboxes;
734     } else {
735         Q_ASSERT(index.model() == this);
736     }
737     Q_ASSERT(node);
738     return node->rowCount(const_cast<Model *>(this));
739 }
740 
columnCount(const QModelIndex & index) const741 int Model::columnCount(const QModelIndex &index) const
742 {
743     TreeItem *node = static_cast<TreeItem *>(index.internalPointer());
744     if (!node) {
745         node = m_mailboxes;
746     } else {
747         Q_ASSERT(index.model() == this);
748     }
749     Q_ASSERT(node);
750     return node->columnCount();
751 }
752 
hasChildren(const QModelIndex & parent) const753 bool Model::hasChildren(const QModelIndex &parent) const
754 {
755     if (parent.isValid()) {
756         Q_ASSERT(parent.model() == this);
757     }
758 
759     TreeItem *node = translatePtr(parent);
760 
761     if (node)
762         return node->hasChildren(const_cast<Model *>(this));
763     else
764         return false;
765 }
766 
askForChildrenOfMailbox(const QModelIndex & index,const CacheLoadingMode cacheMode)767 void Model::askForChildrenOfMailbox(const QModelIndex &index, const CacheLoadingMode cacheMode)
768 {
769     TreeItemMailbox *mailbox = 0;
770     if (index.isValid()) {
771         Q_ASSERT(index.model() == this);
772         mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(index.internalPointer()));
773     } else {
774         mailbox = m_mailboxes;
775     }
776     Q_ASSERT(mailbox);
777     askForChildrenOfMailbox(mailbox, cacheMode == LOAD_FORCE_RELOAD);
778 }
779 
askForMessagesInMailbox(const QModelIndex & index)780 void Model::askForMessagesInMailbox(const QModelIndex &index)
781 {
782     if (!index.isValid())
783         return;
784     Q_ASSERT(index.model() == this);
785     auto msgList = dynamic_cast<TreeItemMsgList *>(static_cast<TreeItem *>(index.internalPointer()));
786     Q_ASSERT(msgList);
787     askForMessagesInMailbox(msgList);
788 }
789 
askForChildrenOfMailbox(TreeItemMailbox * item,bool forceReload)790 void Model::askForChildrenOfMailbox(TreeItemMailbox *item, bool forceReload)
791 {
792     if (!forceReload && cache()->childMailboxesFresh(item->mailbox())) {
793         // The permanent cache contains relevant data
794         QList<MailboxMetadata> metadata = cache()->childMailboxes(item->mailbox());
795         TreeItemChildrenList mailboxes;
796         for (QList<MailboxMetadata>::const_iterator it = metadata.constBegin(); it != metadata.constEnd(); ++it) {
797             TreeItemMailbox *mailbox = TreeItemMailbox::fromMetadata(item, *it);
798             TreeItemMsgList *list = dynamic_cast<TreeItemMsgList*>(mailbox->m_children[0]);
799             Q_ASSERT(list);
800             Imap::Mailbox::SyncState syncState = cache()->mailboxSyncState(mailbox->mailbox());
801             if (syncState.isUsableForNumbers()) {
802                 list->m_unreadMessageCount = syncState.unSeenCount();
803                 list->m_totalMessageCount = syncState.exists();
804                 list->m_recentMessageCount = syncState.recent();
805                 list->m_numberFetchingStatus = TreeItem::LOADING;
806             } else {
807                 list->m_numberFetchingStatus = TreeItem::UNAVAILABLE;
808             }
809             mailboxes << mailbox;
810         }
811         TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(item);
812         Q_ASSERT(mailboxPtr);
813         replaceChildMailboxes(mailboxPtr, mailboxes);
814     } else if (networkPolicy() == NETWORK_OFFLINE) {
815         // No cached data, no network -> fail
816         item->setFetchStatus(TreeItem::UNAVAILABLE);
817         QModelIndex idx = item->toIndex(this);
818         emit dataChanged(idx, idx);
819         return;
820     }
821 
822     // We shall ask the network
823     m_taskFactory->createListChildMailboxesTask(this, item->toIndex(this));
824 
825     QModelIndex idx = item->toIndex(this);
826     emit dataChanged(idx, idx);
827 }
828 
reloadMailboxList()829 void Model::reloadMailboxList()
830 {
831     m_mailboxes->rescanForChildMailboxes(this);
832 }
833 
askForMessagesInMailbox(TreeItemMsgList * item)834 void Model::askForMessagesInMailbox(TreeItemMsgList *item)
835 {
836     Q_ASSERT(item->parent());
837     TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(item->parent());
838     Q_ASSERT(mailboxPtr);
839 
840     QString mailbox = mailboxPtr->mailbox();
841 
842     Q_ASSERT(item->m_children.size() == 0);
843 
844     auto uidMapping = cache()->uidMapping(mailbox);
845     auto oldSyncState = cache()->mailboxSyncState(mailbox);
846     if (networkPolicy() == NETWORK_OFFLINE && oldSyncState.isUsableForSyncing()
847             && static_cast<uint>(uidMapping.size()) != oldSyncState.exists()) {
848         // Problem with the cached data
849         qDebug() << "UID cache stale for mailbox" << mailbox
850                  << "(" << uidMapping.size() << "in UID cache vs." << oldSyncState.exists() << "in the sync state and"
851                  << item->m_totalMessageCount << "as totalMessageCount (possibly updated by STATUS))";
852         item->setFetchStatus(TreeItem::UNAVAILABLE);
853     } else if (networkPolicy() == NETWORK_OFFLINE && !oldSyncState.isUsableForSyncing()) {
854         // Nothing in the cache
855         item->setFetchStatus(TreeItem::UNAVAILABLE);
856     } else if (oldSyncState.isUsableForSyncing()) {
857         // We can pre-populate the tree with data from cache
858         Q_ASSERT(item->m_children.isEmpty());
859         Q_ASSERT(item->accessFetchStatus() == TreeItem::LOADING);
860         QModelIndex listIndex = item->toIndex(this);
861         if (uidMapping.size()) {
862             beginInsertRows(listIndex, 0, uidMapping.size() - 1);
863             for (uint seq = 0; seq < static_cast<uint>(uidMapping.size()); ++seq) {
864                 TreeItemMessage *message = new TreeItemMessage(item);
865                 message->m_offset = seq;
866                 message->m_uid = uidMapping[seq];
867                 item->m_children << message;
868                 QStringList flags = cache()->msgFlags(mailbox, message->m_uid);
869                 flags.removeOne(QStringLiteral("\\Recent"));
870                 message->m_flags = normalizeFlags(flags);
871             }
872             endInsertRows();
873         }
874         mailboxPtr->syncState = oldSyncState;
875         item->setFetchStatus(TreeItem::DONE); // required for FETCH processing later on
876         // The list of messages was satisfied from cache. Do the same for the message counts, if applicable
877         item->recalcVariousMessageCounts(this);
878     }
879 
880     if (networkPolicy() != NETWORK_OFFLINE) {
881         findTaskResponsibleFor(mailboxPtr);
882         // and that's all -- the task will detect following replies and sync automatically
883     }
884 }
885 
askForNumberOfMessages(TreeItemMsgList * item)886 void Model::askForNumberOfMessages(TreeItemMsgList *item)
887 {
888     Q_ASSERT(item->parent());
889     TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(item->parent());
890     Q_ASSERT(mailboxPtr);
891 
892     if (networkPolicy() == NETWORK_OFFLINE) {
893         Imap::Mailbox::SyncState syncState = cache()->mailboxSyncState(mailboxPtr->mailbox());
894         if (syncState.isUsableForNumbers()) {
895             item->m_unreadMessageCount = syncState.unSeenCount();
896             item->m_totalMessageCount = syncState.exists();
897             item->m_recentMessageCount = syncState.recent();
898             item->m_numberFetchingStatus = TreeItem::DONE;
899             emitMessageCountChanged(mailboxPtr);
900         } else {
901             item->m_numberFetchingStatus = TreeItem::UNAVAILABLE;
902         }
903     } else {
904         m_taskFactory->createNumberOfMessagesTask(this, mailboxPtr->toIndex(this));
905     }
906 }
907 
askForMsgMetadata(TreeItemMessage * item,const PreloadingMode preloadMode)908 void Model::askForMsgMetadata(TreeItemMessage *item, const PreloadingMode preloadMode)
909 {
910     Q_ASSERT(item->uid());
911     Q_ASSERT(!item->fetched());
912     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(item->parent());
913     Q_ASSERT(list);
914     TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(list->parent());
915     Q_ASSERT(mailboxPtr);
916 
917     if (item->uid()) {
918         AbstractCache::MessageDataBundle data = cache()->messageMetadata(mailboxPtr->mailbox(), item->uid());
919         if (data.uid == item->uid()) {
920             item->data()->setEnvelope(data.envelope);
921             item->data()->setSize(data.size);
922             item->data()->setHdrReferences(data.hdrReferences);
923             item->data()->setHdrListPost(data.hdrListPost);
924             item->data()->setHdrListPostNo(data.hdrListPostNo);
925             QDataStream stream(&data.serializedBodyStructure, QIODevice::ReadOnly);
926             stream.setVersion(QDataStream::Qt_4_6);
927             QVariantList unserialized;
928             stream >> unserialized;
929             QSharedPointer<Message::AbstractMessage> abstractMessage;
930             try {
931                 abstractMessage = Message::AbstractMessage::fromList(unserialized, QByteArray(), 0);
932             } catch (Imap::ParserException &e) {
933                 qDebug() << "Error when parsing cached BODYSTRUCTURE" << e.what();
934             }
935             if (! abstractMessage) {
936                 item->setFetchStatus(TreeItem::UNAVAILABLE);
937             } else {
938                 auto newChildren = abstractMessage->createTreeItems(item);
939                 if (item->m_children.isEmpty()) {
940                     TreeItemChildrenList oldChildren = item->setChildren(newChildren);
941                     Q_ASSERT(oldChildren.size() == 0);
942                 } else {
943                     // The following assert guards against that crazy signal emitting we had when various askFor*()
944                     // functions were not delayed. If it gets hit, it means that someone tried to call this function
945                     // on an item which was already loaded.
946                     Q_ASSERT(item->m_children.isEmpty());
947                     item->setChildren(newChildren);
948                 }
949                 item->setFetchStatus(TreeItem::DONE);
950             }
951         }
952     }
953 
954     switch (networkPolicy()) {
955     case NETWORK_OFFLINE:
956         if (item->accessFetchStatus() != TreeItem::DONE)
957             item->setFetchStatus(TreeItem::UNAVAILABLE);
958         break;
959     case NETWORK_EXPENSIVE:
960         if (item->accessFetchStatus() != TreeItem::DONE) {
961             item->setFetchStatus(TreeItem::LOADING);
962             findTaskResponsibleFor(mailboxPtr)->requestEnvelopeDownload(item->uid());
963         }
964         break;
965     case NETWORK_ONLINE:
966     {
967         if (item->accessFetchStatus() != TreeItem::DONE) {
968             item->setFetchStatus(TreeItem::LOADING);
969             findTaskResponsibleFor(mailboxPtr)->requestEnvelopeDownload(item->uid());
970         }
971 
972         // preload
973         if (preloadMode != PRELOAD_PER_POLICY)
974             break;
975         bool ok;
976         int preload = property("trojita-imap-preload-msg-metadata").toInt(&ok);
977         if (! ok)
978             preload = 50;
979         int order = item->row();
980         for (int i = qMax(0, order - preload); i < qMin(list->m_children.size(), order + preload); ++i) {
981             TreeItemMessage *message = dynamic_cast<TreeItemMessage *>(list->m_children[i]);
982             Q_ASSERT(message);
983             if (item != message && !message->fetched() && !message->loading() && message->uid()) {
984                 message->setFetchStatus(TreeItem::LOADING);
985                 // cannot ask the KeepTask directly, that'd completely ignore the cache
986                 // but we absolutely have to block the preload :)
987                 askForMsgMetadata(message, PRELOAD_DISABLED);
988             }
989         }
990     }
991     break;
992     }
993     EMIT_LATER(this, dataChanged, Q_ARG(QModelIndex, item->toIndex(this)), Q_ARG(QModelIndex, item->toIndex(this)));
994 }
995 
askForMsgPart(TreeItemPart * item,bool onlyFromCache)996 void Model::askForMsgPart(TreeItemPart *item, bool onlyFromCache)
997 {
998     Q_ASSERT(item->message());   // TreeItemMessage
999     Q_ASSERT(item->message()->parent());   // TreeItemMsgList
1000     Q_ASSERT(item->message()->parent()->parent());   // TreeItemMailbox
1001     TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(item->message()->parent()->parent());
1002     Q_ASSERT(mailboxPtr);
1003 
1004     // We are asking for a message part, which means that the structure of a message is already known.
1005     // If the UID was zero at this point, it would mean that we are completely doomed.
1006     uint uid = static_cast<TreeItemMessage *>(item->message())->uid();
1007     Q_ASSERT(uid);
1008 
1009     // Check whether this is a request for fetching the special item representing the raw contents prior to any CTE undoing
1010     TreeItemPart *itemForFetchOperation = item;
1011     TreeItemModifiedPart *modifiedPart = dynamic_cast<TreeItemModifiedPart*>(item);
1012     bool isSpecialRawPart = modifiedPart && modifiedPart->kind() == TreeItem::OFFSET_RAW_CONTENTS;
1013     if (isSpecialRawPart) {
1014         itemForFetchOperation = dynamic_cast<TreeItemPart*>(item->parent());
1015         Q_ASSERT(itemForFetchOperation);
1016     }
1017 
1018     const QByteArray &data = cache()->messagePart(mailboxPtr->mailbox(), uid,
1019                                                   isSpecialRawPart ?
1020                                                       itemForFetchOperation->partId() + ".X-RAW"
1021                                                     : item->partId());
1022     if (! data.isNull()) {
1023         item->m_data = data;
1024         item->setFetchStatus(TreeItem::DONE);
1025         return;
1026     }
1027 
1028     if (!isSpecialRawPart) {
1029         const QByteArray &data = cache()->messagePart(mailboxPtr->mailbox(), uid,
1030                                                       itemForFetchOperation->partId() + ".X-RAW");
1031 
1032         if (!data.isNull()) {
1033             Imap::decodeContentTransferEncoding(data, item->encoding(), item->dataPtr());
1034             item->setFetchStatus(TreeItem::DONE);
1035             return;
1036         }
1037 
1038         if (item->m_partRaw && item->m_partRaw->loading()) {
1039             // There's already a request for the raw data. Let's use it and don't queue an extra fetch here.
1040             item->setFetchStatus(TreeItem::LOADING);
1041             return;
1042         }
1043     }
1044 
1045     if (networkPolicy() == NETWORK_OFFLINE) {
1046         if (item->accessFetchStatus() != TreeItem::DONE)
1047             item->setFetchStatus(TreeItem::UNAVAILABLE);
1048     } else if (! onlyFromCache) {
1049         KeepMailboxOpenTask *keepTask = findTaskResponsibleFor(mailboxPtr);
1050         TreeItemPart::PartFetchingMode fetchingMode = TreeItemPart::FETCH_PART_IMAP;
1051         if (!isSpecialRawPart && keepTask->parser && accessParser(keepTask->parser).capabilitiesFresh &&
1052                 accessParser(keepTask->parser).capabilities.contains(QStringLiteral("BINARY"))) {
1053             if (!item->hasChildren(0)) {
1054                 // The BINARY only actually makes sense on leaf MIME nodes
1055                 fetchingMode = TreeItemPart::FETCH_PART_BINARY;
1056             }
1057         }
1058         keepTask->requestPartDownload(item->message()->m_uid, itemForFetchOperation->partIdForFetch(fetchingMode), item->octets());
1059     }
1060 }
1061 
resyncMailbox(const QModelIndex & mbox)1062 void Model::resyncMailbox(const QModelIndex &mbox)
1063 {
1064     findTaskResponsibleFor(mbox)->resynchronizeMailbox();
1065 }
1066 
setNetworkPolicy(const NetworkPolicy policy)1067 void Model::setNetworkPolicy(const NetworkPolicy policy)
1068 {
1069     bool networkReconnected = m_netPolicy == NETWORK_OFFLINE && policy != NETWORK_OFFLINE;
1070     switch (policy) {
1071     case NETWORK_OFFLINE:
1072         for (QMap<Parser *,ParserState>::iterator it = m_parsers.begin(); it != m_parsers.end(); ++it) {
1073             if (!it->parser || it->connState == CONN_STATE_LOGOUT) {
1074                 // there's no point in sending LOGOUT over these
1075                 continue;
1076             }
1077             Q_ASSERT(it->parser);
1078             if (it->maintainingTask) {
1079                 // First of all, give the maintaining task a chance to finish its housekeeping
1080                 it->maintainingTask->stopForLogout();
1081             }
1082             // Kill all tasks that are also using this connection
1083             Q_FOREACH(ImapTask *task, it->activeTasks) {
1084                 task->die(tr("Going offline"));
1085             }
1086             it->logoutCmd = it->parser->logout();
1087             changeConnectionState(it->parser, CONN_STATE_LOGOUT);
1088         }
1089         m_netPolicy = NETWORK_OFFLINE;
1090         m_periodicMailboxNumbersRefresh->stop();
1091         emit networkPolicyChanged();
1092         emit networkPolicyOffline();
1093 
1094         // FIXME: kill the connection
1095         break;
1096     case NETWORK_EXPENSIVE:
1097         m_netPolicy = NETWORK_EXPENSIVE;
1098         m_periodicMailboxNumbersRefresh->stop();
1099         emit networkPolicyChanged();
1100         emit networkPolicyExpensive();
1101         break;
1102     case NETWORK_ONLINE:
1103         m_netPolicy = NETWORK_ONLINE;
1104         m_periodicMailboxNumbersRefresh->start();
1105         emit networkPolicyChanged();
1106         emit networkPolicyOnline();
1107         break;
1108     }
1109 
1110     if (networkReconnected) {
1111         // We're connecting after being offline
1112         if (m_mailboxes->accessFetchStatus() != TreeItem::NONE) {
1113             // We should ask for an updated list of mailboxes
1114             // The main reason is that this happens after entering wrong password and going back online
1115             reloadMailboxList();
1116         }
1117     } else if (m_netPolicy == NETWORK_ONLINE) {
1118         // The connection is online after some time in a different mode. Let's use this opportunity to request
1119         // updated message counts from all visible mailboxes.
1120         invalidateAllMessageCounts();
1121     }
1122 }
1123 
handleSocketDisconnectedResponse(Parser * ptr,const Responses::SocketDisconnectedResponse * const resp)1124 void Model::handleSocketDisconnectedResponse(Parser *ptr, const Responses::SocketDisconnectedResponse *const resp)
1125 {
1126     if (!accessParser(ptr).logoutCmd.isEmpty() || accessParser(ptr).connState == CONN_STATE_LOGOUT) {
1127         // If we're already scheduled for logout, don't treat connection errors as, well, errors.
1128         // This branch can be reached by e.g. user selecting offline after a network change, with logout
1129         // already on the fly.
1130 
1131         // But we still absolutely want to clean up and kill the connection/Parser anyway
1132         killParser(ptr, PARSER_KILL_EXPECTED);
1133     } else {
1134         logTrace(ptr->parserId(), Common::LOG_PARSE_ERROR, QString(), resp->message);
1135         changeConnectionState(ptr, CONN_STATE_LOGOUT);
1136         killParser(ptr, PARSER_KILL_EXPECTED);
1137         EMIT_LATER(this, networkError, Q_ARG(QString, resp->message));
1138         setNetworkPolicy(NETWORK_OFFLINE);
1139     }
1140 }
1141 
handleParseErrorResponse(Imap::Parser * ptr,const Imap::Responses::ParseErrorResponse * const resp)1142 void Model::handleParseErrorResponse(Imap::Parser *ptr, const Imap::Responses::ParseErrorResponse *const resp)
1143 {
1144     Q_ASSERT(ptr);
1145     broadcastParseError(ptr->parserId(), resp->exceptionClass, resp->message, resp->line, resp->offset);
1146     killParser(ptr, PARSER_KILL_HARD);
1147 }
1148 
broadcastParseError(const uint parser,const QString & exceptionClass,const QString & errorMessage,const QByteArray & line,int position)1149 void Model::broadcastParseError(const uint parser, const QString &exceptionClass, const QString &errorMessage, const QByteArray &line, int position)
1150 {
1151     emit logParserFatalError(parser, exceptionClass, errorMessage, line, position);
1152     QString details = (position == -1) ? QString() : QString(position, QLatin1Char(' ')) + QLatin1String("^ here");
1153     logTrace(parser, Common::LOG_PARSE_ERROR, exceptionClass, QStringLiteral("%1\n%2\n%3").arg(errorMessage, QString::fromUtf8(line), details));
1154     QString message;
1155     if (exceptionClass == QLatin1String("NotAnImapServerError")) {
1156         QString service;
1157         if (line.startsWith("+OK") || line.startsWith("-ERR")) {
1158             service = tr("<p>It appears that you are connecting to a POP3 server. That won't work here.</p>");
1159         } else if (line.startsWith("220 ") || line.startsWith("220-")) {
1160             service = tr("<p>It appears that you are connecting to an SMTP server. That won't work here.</p>");
1161         }
1162         message = trUtf8("<h2>This is not an IMAP server</h2>"
1163                          "%1"
1164                          "<p>Please check your settings to make sure you are connecting to the IMAP service. "
1165                          "A typical port number for IMAP is 143 or 993.</p>"
1166                          "<p>The server said:</p>"
1167                          "<pre>%2</pre>").arg(service, QString::fromUtf8(line));
1168     } else {
1169         message = trUtf8("<p>The IMAP server sent us a reply which we could not parse. "
1170                          "This might either mean that there's a bug in Trojitá's code, or "
1171                          "that the IMAP server you are connected to is broken. Please "
1172                          "report this as a bug anyway. Here are the details:</p>"
1173                          "<p><b>%1</b>: %2</p>"
1174                          "<pre>%3\n%4</pre>"
1175                          ).arg(exceptionClass, errorMessage, QString::fromUtf8(line), details);
1176     }
1177     EMIT_LATER(this, imapError, Q_ARG(QString, message));
1178     setNetworkPolicy(NETWORK_OFFLINE);
1179 }
1180 
switchToMailbox(const QModelIndex & mbox)1181 void Model::switchToMailbox(const QModelIndex &mbox)
1182 {
1183     if (! mbox.isValid())
1184         return;
1185 
1186     if (m_netPolicy == NETWORK_OFFLINE)
1187         return;
1188 
1189     findTaskResponsibleFor(mbox);
1190 }
1191 
updateCapabilities(Parser * parser,const QStringList capabilities)1192 void Model::updateCapabilities(Parser *parser, const QStringList capabilities)
1193 {
1194     Q_ASSERT(parser);
1195     QStringList uppercaseCaps;
1196     Q_FOREACH(const QString& str, capabilities) {
1197         QString cap = str.toUpper();
1198         if (m_capabilitiesBlacklist.contains(cap)) {
1199             logTrace(parser->parserId(), Common::LOG_OTHER, QStringLiteral("Model"), QStringLiteral("Ignoring capability \"%1\"").arg(cap));
1200             continue;
1201         }
1202         uppercaseCaps << cap;
1203     }
1204     accessParser(parser).capabilities = uppercaseCaps;
1205     accessParser(parser).capabilitiesFresh = true;
1206     if (uppercaseCaps.contains(QStringLiteral("LITERAL-"))) {
1207         parser->enableLiteralPlus(Parser::LiteralPlus::Minus);
1208     } else if (uppercaseCaps.contains(QStringLiteral("LITERAL+"))) {
1209         parser->enableLiteralPlus(Parser::LiteralPlus::Plus);
1210     } else {
1211         parser->enableLiteralPlus(Parser::LiteralPlus::Unsupported);
1212     }
1213 
1214     for (QMap<Parser *,ParserState>::const_iterator it = m_parsers.constBegin(); it != m_parsers.constEnd(); ++it) {
1215         if (it->connState == CONN_STATE_LOGOUT) {
1216             // Skip all parsers which are currently stuck in LOGOUT
1217             continue;
1218         } else {
1219             // The CAPABILITIES were received by a first "usable" parser; let's treat this one as the authoritative one
1220             emit capabilitiesUpdated(uppercaseCaps);
1221         }
1222     }
1223 
1224     if (!uppercaseCaps.contains(QStringLiteral("IMAP4REV1"))) {
1225         changeConnectionState(parser, CONN_STATE_LOGOUT);
1226         accessParser(parser).logoutCmd = parser->logout();
1227         EMIT_LATER(this, imapError, Q_ARG(QString, tr("We aren't talking to an IMAP4 server")));
1228         setNetworkPolicy(NETWORK_OFFLINE);
1229     }
1230 }
1231 
setMessageFlags(const QModelIndexList & messages,const QString flag,const FlagsOperation marked)1232 ImapTask *Model::setMessageFlags(const QModelIndexList &messages, const QString flag, const FlagsOperation marked)
1233 {
1234     Q_ASSERT(!messages.isEmpty());
1235     Q_ASSERT(messages.front().model() == this);
1236     return m_taskFactory->createUpdateFlagsTask(this, messages, marked, QLatin1Char('(') + flag + QLatin1Char(')'));
1237 }
1238 
markMessagesDeleted(const QModelIndexList & messages,const FlagsOperation marked)1239 void Model::markMessagesDeleted(const QModelIndexList &messages, const FlagsOperation marked)
1240 {
1241     this->setMessageFlags(messages, QStringLiteral("\\Deleted"), marked);
1242 }
1243 
markMailboxAsRead(const QModelIndex & mailbox)1244 void Model::markMailboxAsRead(const QModelIndex &mailbox)
1245 {
1246     if (!mailbox.isValid())
1247         return;
1248 
1249     QModelIndex index;
1250     realTreeItem(mailbox, 0, &index);
1251     Q_ASSERT(index.isValid());
1252     Q_ASSERT(index.model() == this);
1253     Q_ASSERT(dynamic_cast<TreeItemMailbox*>(static_cast<TreeItem*>(index.internalPointer())));
1254     m_taskFactory->createUpdateFlagsOfAllMessagesTask(this, index, Imap::Mailbox::FLAG_ADD_SILENT, QStringLiteral("\\Seen"));
1255 }
1256 
markMessagesRead(const QModelIndexList & messages,const FlagsOperation marked)1257 void Model::markMessagesRead(const QModelIndexList &messages, const FlagsOperation marked)
1258 {
1259     this->setMessageFlags(messages, QStringLiteral("\\Seen"), marked);
1260 }
1261 
copyMoveMessages(TreeItemMailbox * sourceMbox,const QString & destMailboxName,Imap::Uids uids,const CopyMoveOperation op)1262 void Model::copyMoveMessages(TreeItemMailbox *sourceMbox, const QString &destMailboxName, Imap::Uids uids, const CopyMoveOperation op)
1263 {
1264     if (m_netPolicy == NETWORK_OFFLINE) {
1265         // FIXME: error signalling
1266         return;
1267     }
1268 
1269     Q_ASSERT(sourceMbox);
1270 
1271     qSort(uids);
1272 
1273     QModelIndexList messages;
1274     Sequence seq;
1275     Q_FOREACH(TreeItemMessage* m, findMessagesByUids(sourceMbox, uids)) {
1276         messages << m->toIndex(this);
1277         seq.add(m->uid());
1278     }
1279     m_taskFactory->createCopyMoveMessagesTask(this, messages, destMailboxName, op);
1280 }
1281 
1282 /** @short Convert a list of UIDs to a list of pointers to the relevant message nodes */
findMessagesByUids(const TreeItemMailbox * const mailbox,const Imap::Uids & uids)1283 QList<TreeItemMessage *> Model::findMessagesByUids(const TreeItemMailbox *const mailbox, const Imap::Uids &uids)
1284 {
1285     const TreeItemMsgList *const list = dynamic_cast<const TreeItemMsgList *const>(mailbox->m_children[0]);
1286     Q_ASSERT(list);
1287     QList<TreeItemMessage *> res;
1288     auto it = list->m_children.constBegin();
1289     uint lastUid = 0;
1290     Q_FOREACH(const uint uid, uids) {
1291         if (lastUid == uid) {
1292             // we have to filter out duplicates
1293             continue;
1294         }
1295         lastUid = uid;
1296         it = Common::lowerBoundWithUnknownElements(it, list->m_children.constEnd(), uid, messageHasUidZero, uidComparator);
1297         if (it != list->m_children.end() && static_cast<TreeItemMessage *>(*it)->uid() == uid) {
1298             res << static_cast<TreeItemMessage *>(*it);
1299         } else {
1300             qDebug() << "Can't find UID" << uid;
1301         }
1302     }
1303     return res;
1304 }
1305 
1306 /** @short Find a message with UID that matches the passed key, handling those with UID zero correctly
1307 
1308 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
1309 point to a message with UID zero or to the end of the list.
1310 */
findMessageOrNextOneByUid(TreeItemMsgList * list,const uint uid)1311 TreeItemChildrenList::iterator Model::findMessageOrNextOneByUid(TreeItemMsgList *list, const uint uid)
1312 {
1313     return Common::lowerBoundWithUnknownElements(list->m_children.begin(), list->m_children.end(), uid, messageHasUidZero, uidComparator);
1314 }
1315 
findMailboxByName(const QString & name) const1316 TreeItemMailbox *Model::findMailboxByName(const QString &name) const
1317 {
1318     return findMailboxByName(name, m_mailboxes);
1319 }
1320 
findMailboxByName(const QString & name,const TreeItemMailbox * const root) const1321 TreeItemMailbox *Model::findMailboxByName(const QString &name, const TreeItemMailbox *const root) const
1322 {
1323     Q_ASSERT(!root->m_children.isEmpty());
1324     // Names are sorted, so linear search is not required. On the ohterhand, the mailbox sizes are typically small enough
1325     // so that this shouldn't matter at all, and linear search is simple enough.
1326     for (int i = 1; i < root->m_children.size(); ++i) {
1327         TreeItemMailbox *mailbox = static_cast<TreeItemMailbox *>(root->m_children[i]);
1328         if (name == mailbox->mailbox())
1329             return mailbox;
1330         else if (name.startsWith(mailbox->mailbox() + mailbox->separator()))
1331             return findMailboxByName(name, mailbox);
1332     }
1333     return 0;
1334 }
1335 
1336 /** @short Find a parent mailbox for the specified name */
findParentMailboxByName(const QString & name) const1337 TreeItemMailbox *Model::findParentMailboxByName(const QString &name) const
1338 {
1339     TreeItemMailbox *root = m_mailboxes;
1340     while (true) {
1341         if (root->m_children.size() == 1) {
1342             break;
1343         }
1344         bool found = false;
1345         for (int i = 1; !found && i < root->m_children.size(); ++i) {
1346             TreeItemMailbox *const item = dynamic_cast<TreeItemMailbox *>(root->m_children[i]);
1347             Q_ASSERT(item);
1348             if (name.startsWith(item->mailbox() + item->separator())) {
1349                 root = item;
1350                 found = true;
1351             }
1352         }
1353         if (!found) {
1354             return root;
1355         }
1356     }
1357     return root;
1358 }
1359 
1360 
expungeMailbox(const QModelIndex & mailbox)1361 void Model::expungeMailbox(const QModelIndex &mailbox)
1362 {
1363     if (!mailbox.isValid())
1364         return;
1365 
1366     if (m_netPolicy == NETWORK_OFFLINE) {
1367         qDebug() << "Can't expunge while offline";
1368         return;
1369     }
1370 
1371     m_taskFactory->createExpungeMailboxTask(this, mailbox);
1372 }
1373 
createMailbox(const QString & name,const AutoSubscription subscription)1374 void Model::createMailbox(const QString &name, const AutoSubscription subscription)
1375 {
1376     if (m_netPolicy == NETWORK_OFFLINE) {
1377         qDebug() << "Can't create mailboxes while offline";
1378         return;
1379     }
1380 
1381     auto task = m_taskFactory->createCreateMailboxTask(this, name);
1382     if (subscription == AutoSubscription::SUBSCRIBE) {
1383         m_taskFactory->createSubscribeUnsubscribeTask(this, task, name, SUBSCRIBE);
1384     }
1385 }
1386 
deleteMailbox(const QString & name)1387 void Model::deleteMailbox(const QString &name)
1388 {
1389     if (m_netPolicy == NETWORK_OFFLINE) {
1390         qDebug() << "Can't delete mailboxes while offline";
1391         return;
1392     }
1393 
1394     m_taskFactory->createDeleteMailboxTask(this, name);
1395 }
1396 
subscribeMailbox(const QString & name)1397 void Model::subscribeMailbox(const QString &name)
1398 {
1399     if (m_netPolicy == NETWORK_OFFLINE) {
1400         qDebug() << "Can't manage subscriptions while offline";
1401         return;
1402     }
1403 
1404     TreeItemMailbox *mailbox = findMailboxByName(name);
1405     if (!mailbox) {
1406         qDebug() << "SUBSCRIBE: No such mailbox.";
1407         return;
1408     }
1409     m_taskFactory->createSubscribeUnsubscribeTask(this, name, SUBSCRIBE);
1410 }
1411 
unsubscribeMailbox(const QString & name)1412 void Model::unsubscribeMailbox(const QString &name)
1413 {
1414     if (m_netPolicy == NETWORK_OFFLINE) {
1415         qDebug() << "Can't manage subscriptions while offline";
1416         return;
1417     }
1418 
1419     TreeItemMailbox *mailbox = findMailboxByName(name);
1420     if (!mailbox) {
1421         qDebug() << "UNSUBSCRIBE: No such mailbox.";
1422         return;
1423     }
1424     m_taskFactory->createSubscribeUnsubscribeTask(this, name, UNSUBSCRIBE);
1425 }
1426 
saveUidMap(TreeItemMsgList * list)1427 void Model::saveUidMap(TreeItemMsgList *list)
1428 {
1429     Imap::Uids seqToUid;
1430     seqToUid.reserve(list->m_children.size());
1431     auto end = list->m_children.constEnd();
1432     for (auto it = list->m_children.constBegin(); it != end; ++it)
1433         seqToUid << static_cast<TreeItemMessage *>(*it)->uid();
1434     cache()->setUidMapping(static_cast<TreeItemMailbox *>(list->parent())->mailbox(), seqToUid);
1435 }
1436 
1437 
realTreeItem(QModelIndex index,const Model ** whichModel,QModelIndex * translatedIndex)1438 TreeItem *Model::realTreeItem(QModelIndex index, const Model **whichModel, QModelIndex *translatedIndex)
1439 {
1440     index = Imap::deproxifiedIndex(index);
1441     const Model *model = qobject_cast<const Model *>(index.model());
1442     Q_ASSERT(model);
1443     if (whichModel)
1444         *whichModel = model;
1445     if (translatedIndex)
1446         *translatedIndex = index;
1447     return static_cast<TreeItem *>(index.internalPointer());
1448 }
1449 
changeConnectionState(Parser * parser,ConnectionState state)1450 void Model::changeConnectionState(Parser *parser, ConnectionState state)
1451 {
1452     accessParser(parser).connState = state;
1453     logTrace(parser->parserId(), Common::LOG_TASKS, QStringLiteral("conn"), connectionStateToString(state));
1454     emit connectionStateChanged(parser->parserId(), state);
1455 }
1456 
handleSocketStateChanged(Parser * parser,Imap::ConnectionState state)1457 void Model::handleSocketStateChanged(Parser *parser, Imap::ConnectionState state)
1458 {
1459     Q_ASSERT(parser);
1460     if (accessParser(parser).connState < state) {
1461         changeConnectionState(parser, state);
1462     }
1463 }
1464 
killParser(Parser * parser,ParserKillingMethod method)1465 void Model::killParser(Parser *parser, ParserKillingMethod method)
1466 {
1467     if (method == PARSER_JUST_DELETE_LATER) {
1468         Q_ASSERT(accessParser(parser).parser == 0);
1469         Q_FOREACH(ImapTask *task, accessParser(parser).activeTasks) {
1470             task->deleteLater();
1471         }
1472         parser->deleteLater();
1473         return;
1474     }
1475 
1476     Q_FOREACH(ImapTask *task, accessParser(parser).activeTasks) {
1477         // FIXME: now this message sucks
1478         task->die(tr("The connection is being killed for unspecified reason"));
1479     }
1480 
1481     parser->disconnect();
1482     Q_ASSERT(accessParser(parser).parser);
1483     accessParser(parser).parser = 0;
1484     switch (method) {
1485     case PARSER_KILL_EXPECTED:
1486         logTrace(parser->parserId(), Common::LOG_IO_WRITTEN, QString(), QStringLiteral("*** Connection closed."));
1487         return;
1488     case PARSER_KILL_HARD:
1489         logTrace(parser->parserId(), Common::LOG_IO_WRITTEN, QString(), QStringLiteral("*** Connection killed."));
1490         return;
1491     case PARSER_JUST_DELETE_LATER:
1492         // already handled
1493         return;
1494     }
1495     Q_ASSERT(false);
1496 }
1497 
slotParserLineReceived(Parser * parser,const QByteArray & line)1498 void Model::slotParserLineReceived(Parser *parser, const QByteArray &line)
1499 {
1500     logTrace(parser->parserId(), Common::LOG_IO_READ, QString(), QString::fromUtf8(line));
1501 }
1502 
slotParserLineSent(Parser * parser,const QByteArray & line)1503 void Model::slotParserLineSent(Parser *parser, const QByteArray &line)
1504 {
1505     logTrace(parser->parserId(), Common::LOG_IO_WRITTEN, QString(), QString::fromUtf8(line));
1506 }
1507 
setCache(AbstractCache * cache)1508 void Model::setCache(AbstractCache *cache)
1509 {
1510     if (m_cache)
1511         m_cache->deleteLater();
1512     m_cache = cache;
1513     m_cache->setParent(this);
1514 }
1515 
runReadyTasks()1516 void Model::runReadyTasks()
1517 {
1518     for (QMap<Parser *,ParserState>::iterator parserIt = m_parsers.begin(); parserIt != m_parsers.end(); ++parserIt) {
1519         bool runSomething = false;
1520         do {
1521             runSomething = false;
1522             // See responseReceived() for more details about why we do need to iterate over a copy here.
1523             // Basically, calls to ImapTask::perform could invalidate our precious iterators.
1524             QList<ImapTask *> origList = parserIt->activeTasks;
1525             QList<ImapTask *> deletedList;
1526             QList<ImapTask *>::const_iterator taskEnd = origList.constEnd();
1527             for (QList<ImapTask *>::const_iterator taskIt = origList.constBegin(); taskIt != taskEnd; ++taskIt) {
1528                 ImapTask *task = *taskIt;
1529                 if (task->isReadyToRun()) {
1530                     task->perform();
1531                     runSomething = true;
1532                 }
1533                 if (task->isFinished()) {
1534                     deletedList << task;
1535                 }
1536             }
1537             removeDeletedTasks(deletedList, parserIt->activeTasks);
1538 #ifdef TROJITA_DEBUG_TASK_TREE
1539             if (!deletedList.isEmpty())
1540                 checkTaskTreeConsistency();
1541 #endif
1542         } while (runSomething);
1543     }
1544 }
1545 
removeDeletedTasks(const QList<ImapTask * > & deletedTasks,QList<ImapTask * > & activeTasks)1546 void Model::removeDeletedTasks(const QList<ImapTask *> &deletedTasks, QList<ImapTask *> &activeTasks)
1547 {
1548     // Remove the finished commands
1549     for (QList<ImapTask *>::const_iterator deletedIt = deletedTasks.begin(); deletedIt != deletedTasks.end(); ++deletedIt) {
1550         (*deletedIt)->deleteLater();
1551         activeTasks.removeOne(*deletedIt);
1552         // It isn't destroyed yet, but should be removed from the model nonetheless
1553         m_taskModel->slotTaskDestroyed(*deletedIt);
1554     }
1555 }
1556 
findTaskResponsibleFor(const QModelIndex & mailbox)1557 KeepMailboxOpenTask *Model::findTaskResponsibleFor(const QModelIndex &mailbox)
1558 {
1559     Q_ASSERT(mailbox.isValid());
1560     QModelIndex translatedIndex;
1561     TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(realTreeItem(mailbox, 0, &translatedIndex));
1562     return findTaskResponsibleFor(mailboxPtr);
1563 }
1564 
findTaskResponsibleFor(TreeItemMailbox * mailboxPtr)1565 KeepMailboxOpenTask *Model::findTaskResponsibleFor(TreeItemMailbox *mailboxPtr)
1566 {
1567     Q_ASSERT(mailboxPtr);
1568     bool canCreateParallelConn = m_parsers.isEmpty(); // FIXME: multiple connections
1569 
1570     if (mailboxPtr->maintainingTask) {
1571         // The requested mailbox already has the maintaining task associated
1572         if (accessParser(mailboxPtr->maintainingTask->parser).connState == CONN_STATE_LOGOUT) {
1573             // The connection is currently getting closed, so we have to create another one
1574             return m_taskFactory->createKeepMailboxOpenTask(this, mailboxPtr->toIndex(this), 0);
1575         } else {
1576             // it's usable as-is
1577             return mailboxPtr->maintainingTask;
1578         }
1579     } else if (canCreateParallelConn) {
1580         // The mailbox is not being maintained, but we can create a new connection
1581         return m_taskFactory->createKeepMailboxOpenTask(this, mailboxPtr->toIndex(this), 0);
1582     } else {
1583         // Too bad, we have to re-use an existing parser. That will probably lead to
1584         // stealing it from some mailbox, but there's no other way.
1585         Q_ASSERT(!m_parsers.isEmpty());
1586 
1587         for (QMap<Parser *,ParserState>::const_iterator it = m_parsers.constBegin(); it != m_parsers.constEnd(); ++it) {
1588             if (it->connState == CONN_STATE_LOGOUT) {
1589                 // this one is not usable
1590                 continue;
1591             }
1592             return m_taskFactory->createKeepMailboxOpenTask(this, mailboxPtr->toIndex(this), it.key());
1593         }
1594         // At this point, we have no other choice than to create a new connection
1595         return m_taskFactory->createKeepMailboxOpenTask(this, mailboxPtr->toIndex(this), 0);
1596     }
1597 }
1598 
genericHandleFetch(TreeItemMailbox * mailbox,const Imap::Responses::Fetch * const resp)1599 void Model::genericHandleFetch(TreeItemMailbox *mailbox, const Imap::Responses::Fetch *const resp)
1600 {
1601     Q_ASSERT(mailbox);
1602     QList<TreeItemPart *> changedParts;
1603     TreeItemMessage *changedMessage = 0;
1604     mailbox->handleFetchResponse(this, *resp, changedParts, changedMessage, false);
1605     if (! changedParts.isEmpty()) {
1606         Q_FOREACH(TreeItemPart* part, changedParts) {
1607             QModelIndex index = part->toIndex(this);
1608             emit dataChanged(index, index);
1609         }
1610     }
1611     if (changedMessage) {
1612         QModelIndex index = changedMessage->toIndex(this);
1613         emit dataChanged(index, index);
1614         emitMessageCountChanged(mailbox);
1615     }
1616 }
1617 
findMailboxForItems(const QModelIndexList & items)1618 QModelIndex Model::findMailboxForItems(const QModelIndexList &items)
1619 {
1620     TreeItemMailbox *mailbox = 0;
1621     Q_FOREACH(const QModelIndex& index, items) {
1622         TreeItemMailbox *currentMailbox = 0;
1623         Q_ASSERT(index.model() == this);
1624 
1625         TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
1626         Q_ASSERT(item);
1627 
1628         if ((currentMailbox = dynamic_cast<TreeItemMailbox *>(item))) {
1629             // yes, that's an assignment, not a comparison
1630 
1631             // This case is OK
1632         } else {
1633             // TreeItemMessage and TreeItemPart have to walk the tree, which is why they are lumped together in this branch
1634             TreeItemMessage *message = dynamic_cast<TreeItemMessage *>(item);
1635             if (!message) {
1636                 if (TreeItemPart *part = dynamic_cast<TreeItemPart *>(item)) {
1637                     message = part->message();
1638                 } else {
1639                     throw CantHappen("findMailboxForItems() called on strange items");
1640                 }
1641             }
1642             Q_ASSERT(message);
1643             TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(message->parent());
1644             Q_ASSERT(list);
1645             currentMailbox = dynamic_cast<TreeItemMailbox *>(list->parent());
1646         }
1647 
1648         Q_ASSERT(currentMailbox);
1649         if (!mailbox) {
1650             mailbox = currentMailbox;
1651         } else if (mailbox != currentMailbox) {
1652             throw CantHappen("Messages from several mailboxes");
1653         }
1654     }
1655     return mailbox->toIndex(this);
1656 }
1657 
slotTasksChanged()1658 void Model::slotTasksChanged()
1659 {
1660     dumpModelContents(m_taskModel);
1661 }
1662 
slotTaskDying(QObject * obj)1663 void Model::slotTaskDying(QObject *obj)
1664 {
1665     ImapTask *task = static_cast<ImapTask *>(obj);
1666     for (QMap<Parser *,ParserState>::iterator it = m_parsers.begin(); it != m_parsers.end(); ++it) {
1667         it->activeTasks.removeOne(task);
1668     }
1669     m_taskModel->slotTaskDestroyed(task);
1670 }
1671 
mailboxForSomeItem(QModelIndex index)1672 TreeItemMailbox *Model::mailboxForSomeItem(QModelIndex index)
1673 {
1674     TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(index.internalPointer()));
1675     while (index.isValid() && ! mailbox) {
1676         index = index.parent();
1677         mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(index.internalPointer()));
1678     }
1679     return mailbox;
1680 }
1681 
accessParser(Parser * parser)1682 ParserState &Model::accessParser(Parser *parser)
1683 {
1684     Q_ASSERT(m_parsers.contains(parser));
1685     return m_parsers[ parser ];
1686 }
1687 
releaseMessageData(const QModelIndex & message)1688 void Model::releaseMessageData(const QModelIndex &message)
1689 {
1690     if (! message.isValid())
1691         return;
1692 
1693     const Model *whichModel = 0;
1694     QModelIndex realMessage;
1695     realTreeItem(message, &whichModel, &realMessage);
1696     Q_ASSERT(whichModel == this);
1697 
1698     TreeItemMessage *msg = dynamic_cast<TreeItemMessage *>(static_cast<TreeItem *>(realMessage.internalPointer()));
1699     if (! msg)
1700         return;
1701 
1702     msg->setFetchStatus(TreeItem::NONE);
1703 
1704 #ifndef XTUPLE_CONNECT
1705     beginRemoveRows(realMessage, 0, msg->m_children.size() - 1);
1706 #endif
1707     if (msg->data()->partHeader()) {
1708         msg->data()->partHeader()->silentlyReleaseMemoryRecursive();
1709         msg->data()->setPartHeader(nullptr);
1710     }
1711     if (msg->data()->partText()) {
1712         msg->data()->partText()->silentlyReleaseMemoryRecursive();
1713         msg->data()->setPartText(nullptr);
1714     }
1715     delete msg->m_data;
1716     msg->m_data = 0;
1717     Q_FOREACH(TreeItem *item, msg->m_children) {
1718         TreeItemPart *part = dynamic_cast<TreeItemPart *>(item);
1719         Q_ASSERT(part);
1720         part->silentlyReleaseMemoryRecursive();
1721         delete part;
1722     }
1723     msg->m_children.clear();
1724 #ifndef XTUPLE_CONNECT
1725     endRemoveRows();
1726     emit dataChanged(realMessage, realMessage);
1727 #endif
1728 }
1729 
capabilities() const1730 QStringList Model::capabilities() const
1731 {
1732     if (m_parsers.isEmpty())
1733         return QStringList();
1734 
1735     if (m_parsers.constBegin()->capabilitiesFresh)
1736         return m_parsers.constBegin()->capabilities;
1737 
1738     return QStringList();
1739 }
1740 
logTrace(uint parserId,const Common::LogKind kind,const QString & source,const QString & message)1741 void Model::logTrace(uint parserId, const Common::LogKind kind, const QString &source, const QString &message)
1742 {
1743     Common::LogMessage m(QDateTime::currentDateTime(), kind, source,  message, 0);
1744     emit logged(parserId, m);
1745 }
1746 
1747 /** @short Overloaded version which accepts a QModelIndex of an item which is somehow "related" to the logged message
1748 
1749 The relevantIndex argument is used for finding out what parser to send the message to.
1750 */
logTrace(const QModelIndex & relevantIndex,const Common::LogKind kind,const QString & source,const QString & message)1751 void Model::logTrace(const QModelIndex &relevantIndex, const Common::LogKind kind, const QString &source, const QString &message)
1752 {
1753     Q_ASSERT(relevantIndex.isValid());
1754     QModelIndex translatedIndex;
1755     realTreeItem(relevantIndex, 0, &translatedIndex);
1756 
1757     // It appears that it's OK to use 0 here; the attached loggers apparently deal with random parsers appearing just OK
1758     uint parserId = 0;
1759 
1760     if (translatedIndex.isValid()) {
1761         Q_ASSERT(translatedIndex.model() == this);
1762         QModelIndex mailboxIndex = findMailboxForItems(QModelIndexList() << translatedIndex);
1763         Q_ASSERT(mailboxIndex.isValid());
1764         TreeItemMailbox *mailboxPtr = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
1765         Q_ASSERT(mailboxPtr);
1766         if (mailboxPtr->maintainingTask) {
1767             parserId = mailboxPtr->maintainingTask->parser->parserId();
1768         }
1769     }
1770 
1771     logTrace(parserId, kind, source, message);
1772 }
1773 
taskModel() const1774 QAbstractItemModel *Model::taskModel() const
1775 {
1776     return m_taskModel;
1777 }
1778 
serverId() const1779 QMap<QByteArray,QByteArray> Model::serverId() const
1780 {
1781     return m_idResult;
1782 }
1783 
1784 /** @short Handle explicit sharing and case mapping for message flags
1785 
1786 This function will try to minimize the amount of QString instances used for storage of individual message flags via Qt's implicit
1787 sharing that is built into QString.
1788 
1789 At the same time, some well-known flags are converted to their "canonical" form (like \\SEEN -> \\Seen etc).
1790 */
normalizeFlags(const QStringList & source) const1791 QStringList Model::normalizeFlags(const QStringList &source) const
1792 {
1793     QStringList res;
1794     res.reserve(source.size());
1795     for (QStringList::const_iterator flag = source.constBegin(); flag != source.constEnd(); ++flag) {
1796 
1797         // At first, perform a case-insensitive lookup in the (rather short) list of known special flags
1798         // Only call the toLower for flags which could possibly be in that mapping. Looking at the first letter is
1799         // a good approximation.
1800         if (!flag->isEmpty() && ((*flag)[0] == QLatin1Char('\\') || (*flag)[0] == QLatin1Char('$'))) {
1801             QString lowerCase = flag->toLower();
1802             QHash<QString,QString>::const_iterator known = FlagNames::toCanonical.constFind(lowerCase);
1803             if (known != FlagNames::toCanonical.constEnd()) {
1804                 res.append(*known);
1805                 continue;
1806             }
1807         }
1808 
1809         // If it isn't a special flag, just check whether it's been encountered already
1810         QSet<QString>::const_iterator it = m_flagLiterals.constFind(*flag);
1811         if (it == m_flagLiterals.constEnd()) {
1812             // Not in cache, so add it and return an implicitly shared copy
1813             m_flagLiterals.insert(*flag);
1814             res.append(*flag);
1815         } else {
1816             // It's in the cache already, se let's QString share the data
1817             res.append(*it);
1818         }
1819     }
1820     // Always sort the flags when performing normalization to obtain reasonable results and be ready for possible future
1821     // deduplication of the actual QLists
1822     res.sort();
1823     return res;
1824 }
1825 
1826 /** @short Set the IMAP username */
setImapUser(const QString & imapUser)1827 void Model::setImapUser(const QString &imapUser)
1828 {
1829     m_imapUser = imapUser;
1830 }
1831 
1832 /** @short Username to use for login */
imapUser() const1833 QString Model::imapUser() const
1834 {
1835     return m_imapUser;
1836 }
1837 
1838 /** @short Set the password that the user wants to use */
setImapPassword(const QString & password)1839 void Model::setImapPassword(const QString &password)
1840 {
1841     m_imapPassword = password;
1842     m_hasImapPassword = PasswordAvailability::AVAILABLE;
1843     informTasksAboutNewPassword();
1844 }
1845 
1846 /** @short Return the user's password, if cached */
imapPassword() const1847 QString Model::imapPassword() const
1848 {
1849     return m_imapPassword;
1850 }
1851 
1852 /** @short Indicate that the user doesn't want to provide her password */
unsetImapPassword()1853 void Model::unsetImapPassword()
1854 {
1855     m_imapPassword.clear();
1856     m_hasImapPassword = PasswordAvailability::NOT_REQUESTED;
1857     informTasksAboutNewPassword();
1858 }
1859 
imapAuthError() const1860 QString Model::imapAuthError() const
1861 {
1862     return m_imapAuthError;
1863 }
1864 
setImapAuthError(const QString & error)1865 void Model::setImapAuthError(const QString &error)
1866 {
1867     if (m_imapAuthError == error)
1868         return;
1869     m_imapAuthError = error;
1870     emit imapAuthErrorChanged(error);
1871 }
1872 
1873 /** @short Tell all tasks which want to know about the availability of a password */
informTasksAboutNewPassword()1874 void Model::informTasksAboutNewPassword()
1875 {
1876     Q_FOREACH(const ParserState &p, m_parsers) {
1877         Q_FOREACH(ImapTask *task, p.activeTasks) {
1878             OpenConnectionTask *openTask = dynamic_cast<OpenConnectionTask *>(task);
1879             if (!openTask)
1880                 continue;
1881             openTask->authCredentialsNowAvailable();
1882         }
1883     }
1884 }
1885 
1886 /** @short Forward a policy decision about accepting or rejecting a SSL state */
setSslPolicy(const QList<QSslCertificate> & sslChain,const QList<QSslError> & sslErrors,bool proceed)1887 void Model::setSslPolicy(const QList<QSslCertificate> &sslChain, const QList<QSslError> &sslErrors, bool proceed)
1888 {
1889     if (proceed) {
1890         // Only remember positive values; there is no point in blocking any further connections until settings reload
1891         m_sslErrorPolicy.prepend(qMakePair(qMakePair(sslChain, sslErrors), proceed));
1892     }
1893     Q_FOREACH(const ParserState &p, m_parsers) {
1894         Q_FOREACH(ImapTask *task, p.activeTasks) {
1895             OpenConnectionTask *openTask = dynamic_cast<OpenConnectionTask *>(task);
1896             if (!openTask)
1897                 continue;
1898             if (openTask->sslCertificateChain() == sslChain && openTask->sslErrors() == sslErrors) {
1899                 openTask->sslConnectionPolicyDecided(proceed);
1900             }
1901         }
1902     }
1903 }
1904 
processSslErrors(OpenConnectionTask * task)1905 void Model::processSslErrors(OpenConnectionTask *task)
1906 {
1907     // Qt doesn't define either operator< or a qHash specialization for QList<QSslError> (what a surprise),
1908     // so we use a plain old QList. Given that there will be at most one different QList<QSslError> sequence for
1909     // each connection attempt (and more realistically, for each server at all), this O(n) complexity shall not matter
1910     // at all.
1911     QList<QPair<QPair<QList<QSslCertificate>, QList<QSslError> >, bool> >::const_iterator it = m_sslErrorPolicy.constBegin();
1912     while (it != m_sslErrorPolicy.constEnd()) {
1913         if (it->first.first == task->sslCertificateChain() && it->first.second == task->sslErrors()) {
1914             task->sslConnectionPolicyDecided(it->second);
1915             return;
1916         }
1917         ++it;
1918     }
1919     EMIT_LATER(this, needsSslDecision, Q_ARG(QList<QSslCertificate>, task->sslCertificateChain()), Q_ARG(QList<QSslError>, task->sslErrors()));
1920 }
1921 
messageIndexByUid(const QString & mailboxName,const uint uid)1922 QModelIndex Model::messageIndexByUid(const QString &mailboxName, const uint uid)
1923 {
1924     TreeItemMailbox *mailbox = findMailboxByName(mailboxName);
1925     Q_ASSERT(mailbox);
1926     QList<TreeItemMessage*> messages = findMessagesByUids(mailbox, Imap::Uids() << uid);
1927     if (messages.isEmpty()) {
1928         return QModelIndex();
1929     } else {
1930         Q_ASSERT(messages.size() == 1);
1931         return messages.front()->toIndex(this);
1932     }
1933 }
1934 
1935 /** @short Forget any cached data about number of messages in all mailboxes */
invalidateAllMessageCounts()1936 void Model::invalidateAllMessageCounts()
1937 {
1938     QList<TreeItemMailbox*> queue;
1939     queue.append(m_mailboxes);
1940     while (!queue.isEmpty()) {
1941         TreeItemMailbox *head = queue.takeFirst();
1942         // ignore first child, the TreeItemMsgList
1943         for (auto it = head->m_children.constBegin() + 1; it != head->m_children.constEnd(); ++it) {
1944             queue.append(static_cast<TreeItemMailbox*>(*it));
1945         }
1946         TreeItemMsgList *list = dynamic_cast<TreeItemMsgList*>(head->m_children[0]);
1947 
1948         if (list->m_numberFetchingStatus == TreeItem::DONE && !head->maintainingTask) {
1949             // Ask only for data which were previously available
1950             // Also don't mess with a mailbox which is already being kept up-to-date because it's selected.
1951             list->m_numberFetchingStatus = TreeItem::NONE;
1952             emitMessageCountChanged(head);
1953         }
1954     }
1955 }
1956 
appendIntoMailbox(const QString & mailbox,const QByteArray & rawMessageData,const QStringList & flags,const QDateTime & timestamp)1957 AppendTask *Model::appendIntoMailbox(const QString &mailbox, const QByteArray &rawMessageData, const QStringList &flags,
1958                                      const QDateTime &timestamp)
1959 {
1960     return m_taskFactory->createAppendTask(this, mailbox, rawMessageData, flags, timestamp);
1961 }
1962 
appendIntoMailbox(const QString & mailbox,const QList<CatenatePair> & data,const QStringList & flags,const QDateTime & timestamp)1963 AppendTask *Model::appendIntoMailbox(const QString &mailbox, const QList<CatenatePair> &data, const QStringList &flags,
1964                                      const QDateTime &timestamp)
1965 {
1966     return m_taskFactory->createAppendTask(this, mailbox, data, flags, timestamp);
1967 }
1968 
generateUrlAuthForMessage(const QString & host,const QString & user,const QString & mailbox,const uint uidValidity,const uint uid,const QString & part,const QString & access)1969 GenUrlAuthTask *Model::generateUrlAuthForMessage(const QString &host, const QString &user, const QString &mailbox,
1970                                                  const uint uidValidity, const uint uid, const QString &part, const QString &access)
1971 {
1972     return m_taskFactory->createGenUrlAuthTask(this, host, user, mailbox, uidValidity, uid, part, access);
1973 }
1974 
sendMailViaUidSubmit(const QString & mailbox,const uint uidValidity,const uint uid,const UidSubmitOptionsList & options)1975 UidSubmitTask *Model::sendMailViaUidSubmit(const QString &mailbox, const uint uidValidity, const uint uid,
1976                                            const UidSubmitOptionsList &options)
1977 {
1978     return m_taskFactory->createUidSubmitTask(this, mailbox, uidValidity, uid, options);
1979 }
1980 
1981 #ifdef TROJITA_DEBUG_TASK_TREE
1982 #define TROJITA_DEBUG_TASK_TREE_VERBOSE
checkTaskTreeConsistency()1983 void Model::checkTaskTreeConsistency()
1984 {
1985     for (QMap<Parser *,ParserState>::const_iterator parserIt = m_parsers.constBegin(); parserIt != m_parsers.constEnd(); ++parserIt) {
1986 #ifdef TROJITA_DEBUG_TASK_TREE_VERBOSE
1987         qDebug() << "\nParser" << parserIt.key() << "; all active tasks:";
1988         Q_FOREACH(ImapTask *activeTask, parserIt.value().activeTasks) {
1989             qDebug() << ' ' << activeTask << activeTask->debugIdentification() << activeTask->parser;
1990         }
1991 #endif
1992         Q_FOREACH(ImapTask *activeTask, parserIt.value().activeTasks) {
1993 #ifdef TROJITA_DEBUG_TASK_TREE_VERBOSE
1994             qDebug() << "Active task" << activeTask << activeTask->debugIdentification() << activeTask->parser;
1995 #endif
1996             Q_ASSERT(activeTask->parser == parserIt.key());
1997             Q_ASSERT(!activeTask->parentTask);
1998             checkDependentTasksConsistency(parserIt.key(), activeTask, 0, 0);
1999         }
2000 
2001         // Make sure that no task is present twice in here
2002         QList<ImapTask*> taskQueue = parserIt.value().activeTasks;
2003         for (int i = 0; i < taskQueue.size(); ++i) {
2004             Q_FOREACH(ImapTask *yetAnotherTask, taskQueue[i]->dependentTasks) {
2005                 Q_ASSERT(!taskQueue.contains(yetAnotherTask));
2006                 taskQueue.push_back(yetAnotherTask);
2007             }
2008         }
2009     }
2010 }
2011 
checkDependentTasksConsistency(Parser * parser,ImapTask * task,ImapTask * expectedParentTask,int depth)2012 void Model::checkDependentTasksConsistency(Parser *parser, ImapTask *task, ImapTask *expectedParentTask, int depth)
2013 {
2014 #ifdef TROJITA_DEBUG_TASK_TREE_VERBOSE
2015     QByteArray prefix;
2016     prefix.fill(' ', depth);
2017     qDebug() << prefix.constData() << "Checking" << task << task->debugIdentification();
2018 #endif
2019     Q_ASSERT(parser);
2020     Q_ASSERT(!task->parser || task->parser == parser);
2021     Q_ASSERT(task->parentTask == expectedParentTask);
2022     if (task->parentTask) {
2023         Q_ASSERT(task->parentTask->dependentTasks.contains(task));
2024         if (task->parentTask->parentTask) {
2025             Q_ASSERT(task->parentTask->parentTask->dependentTasks.contains(task->parentTask));
2026         } else {
2027             Q_ASSERT(task->parentTask->parser);
2028             Q_ASSERT(accessParser(task->parentTask->parser).activeTasks.contains(task->parentTask));
2029         }
2030     } else {
2031         Q_ASSERT(accessParser(parser).activeTasks.contains(task));
2032     }
2033 
2034     Q_FOREACH(ImapTask *childTask, task->dependentTasks) {
2035         checkDependentTasksConsistency(parser, childTask, task, depth + 1);
2036     }
2037 }
2038 #endif
2039 
setCapabilitiesBlacklist(const QStringList & blacklist)2040 void Model::setCapabilitiesBlacklist(const QStringList &blacklist)
2041 {
2042     m_capabilitiesBlacklist = blacklist;
2043 }
2044 
isCatenateSupported() const2045 bool Model::isCatenateSupported() const
2046 {
2047     return capabilities().contains(QStringLiteral("CATENATE"));
2048 }
2049 
isGenUrlAuthSupported() const2050 bool Model::isGenUrlAuthSupported() const
2051 {
2052     return capabilities().contains(QStringLiteral("URLAUTH"));
2053 }
2054 
isImapSubmissionSupported() const2055 bool Model::isImapSubmissionSupported() const
2056 {
2057     QStringList caps = capabilities();
2058     return caps.contains(QStringLiteral("UIDPLUS")) && caps.contains(QStringLiteral("X-DRAFT-I01-SENDMAIL"));
2059 }
2060 
setNumberRefreshInterval(const int interval)2061 void Model::setNumberRefreshInterval(const int interval)
2062 {
2063     if (interval == m_periodicMailboxNumbersRefresh->interval())
2064         return; // QTimer does not check idempotency
2065     m_periodicMailboxNumbersRefresh->start(interval * 1000);
2066 }
2067 
2068 }
2069 }
2070