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 ×tamp)
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 ×tamp)
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