clazy: fix QString(QLatin1String(...))
[trojita.git] / src / Imap / Tasks / KeepMailboxOpenTask.cpp
blobd3b19c274523ddb855fa574d7bf611ab208a8068
1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
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.
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.
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/>.
23 #include <sstream>
24 #include "KeepMailboxOpenTask.h"
25 #include "Common/InvokeMethod.h"
26 #include "Imap/Model/ItemRoles.h"
27 #include "Imap/Model/MailboxTree.h"
28 #include "Imap/Model/Model.h"
29 #include "Imap/Model/TaskFactory.h"
30 #include "Imap/Model/TaskPresentationModel.h"
31 #include "DeleteMailboxTask.h"
32 #include "FetchMsgMetadataTask.h"
33 #include "FetchMsgPartTask.h"
34 #include "IdleLauncher.h"
35 #include "OpenConnectionTask.h"
36 #include "ObtainSynchronizedMailboxTask.h"
37 #include "OfflineConnectionTask.h"
38 #include "SortTask.h"
39 #include "NoopTask.h"
40 #include "UnSelectTask.h"
42 namespace Imap
44 namespace Mailbox
47 KeepMailboxOpenTask::KeepMailboxOpenTask(Model *model, const QModelIndex &mailboxIndex, Parser *oldParser) :
48 ImapTask(model), mailboxIndex(mailboxIndex), synchronizeConn(0), shouldExit(false), isRunning(Running::NOT_YET),
49 shouldRunNoop(false), shouldRunIdle(false), idleLauncher(0), unSelectTask(0),
50 m_skippedStateSynces(0), m_performedStateSynces(0), m_syncingTimer(nullptr)
52 Q_ASSERT(mailboxIndex.isValid());
53 Q_ASSERT(mailboxIndex.model() == model);
54 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
55 Q_ASSERT(mailbox);
57 // Now make sure that we at least try to load data from the cache
58 Q_ASSERT(mailbox->m_children.size() > 0);
59 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList*>(mailbox->m_children[0]);
60 Q_ASSERT(list);
61 list->fetch(model);
63 // We're the latest KeepMailboxOpenTask, so it makes a lot of sense to add us as the active
64 // maintainingTask to the target mailbox
65 mailbox->maintainingTask = this;
67 if (oldParser) {
68 // We're asked to re-use an existing connection. Let's see if there's something associated with it
70 // We will use its parser, that's for sure already
71 parser = oldParser;
73 // Find if there's a KeepMailboxOpenTask already associated; if it is, we have to register with it
74 if (model->accessParser(parser).maintainingTask) {
75 // The parser looks busy -- some task is associated with it and has a mailbox open, so
76 // let's just wait till we get a chance to play
77 synchronizeConn = model->m_taskFactory->
78 createObtainSynchronizedMailboxTask(model, mailboxIndex, model->accessParser(oldParser).maintainingTask, this);
79 } else if (model->accessParser(parser).connState < CONN_STATE_AUTHENTICATED) {
80 // The parser is still in the process of being initialized, let's wait until it's completed
81 Q_ASSERT(!model->accessParser(oldParser).activeTasks.isEmpty());
82 ImapTask *task = model->accessParser(oldParser).activeTasks.front();
83 synchronizeConn = model->m_taskFactory->createObtainSynchronizedMailboxTask(model, mailboxIndex, task, this);
84 } else {
85 // The parser is free, or at least there's no KeepMailboxOpenTask associated with it.
86 // There's no mailbox besides us in the game, yet, so we can simply schedule us for immediate execution.
87 synchronizeConn = model->m_taskFactory->createObtainSynchronizedMailboxTask(model, mailboxIndex, 0, this);
88 // We'll also register with the model, so that all other KeepMailboxOpenTask which could get constructed in future
89 // know about us and don't step on our toes. This means that further KeepMailboxOpenTask which could possibly want
90 // to use this connection will have to go through this task at first.
91 model->accessParser(parser).maintainingTask = this;
92 QTimer::singleShot(0, this, SLOT(slotPerformConnection()));
95 // We shall catch destruction of any preexisting tasks so that we can properly launch IDLE etc in response to their termination
96 Q_FOREACH(ImapTask *task, model->accessParser(parser).activeTasks) {
97 connect(task, &QObject::destroyed, this, &KeepMailboxOpenTask::slotTaskDeleted);
99 } else {
100 ImapTask *conn = 0;
101 if (model->networkPolicy() == NETWORK_OFFLINE) {
102 // Well, except that we cannot really open a new connection now
103 conn = new OfflineConnectionTask(model);
104 } else {
105 conn = model->m_taskFactory->createOpenConnectionTask(model);
107 parser = conn->parser;
108 Q_ASSERT(parser);
109 model->accessParser(parser).maintainingTask = this;
110 synchronizeConn = model->m_taskFactory->createObtainSynchronizedMailboxTask(model, mailboxIndex, conn, this);
113 Q_ASSERT(synchronizeConn);
115 // Setup the timer for NOOPing. It won't get started at this time, though.
116 noopTimer = new QTimer(this);
117 connect(noopTimer, &QTimer::timeout, this, &KeepMailboxOpenTask::slotPerformNoop);
118 bool ok;
119 int timeout = model->property("trojita-imap-noop-period").toUInt(&ok);
120 if (! ok)
121 timeout = 2 * 60 * 1000; // once every two minutes
122 noopTimer->setInterval(timeout);
123 noopTimer->setSingleShot(true);
125 fetchPartTimer = new QTimer(this);
126 connect(fetchPartTimer, &QTimer::timeout, this, &KeepMailboxOpenTask::slotFetchRequestedParts);
127 timeout = model->property("trojita-imap-delayed-fetch-part").toUInt(&ok);
128 if (! ok)
129 timeout = 50;
130 fetchPartTimer->setInterval(timeout);
131 fetchPartTimer->setSingleShot(true);
133 fetchEnvelopeTimer = new QTimer(this);
134 connect(fetchEnvelopeTimer, &QTimer::timeout, this, &KeepMailboxOpenTask::slotFetchRequestedEnvelopes);
135 fetchEnvelopeTimer->setInterval(0); // message metadata is pretty important, hence an immediate fetch
136 fetchEnvelopeTimer->setSingleShot(true);
138 limitBytesAtOnce = model->property("trojita-imap-limit-fetch-bytes-per-group").toUInt(&ok);
139 if (! ok)
140 limitBytesAtOnce = 1024 * 1024;
142 limitMessagesAtOnce = model->property("trojita-imap-limit-fetch-messages-per-group").toInt(&ok);
143 if (! ok)
144 limitMessagesAtOnce = 300;
146 limitParallelFetchTasks = model->property("trojita-imap-limit-parallel-fetch-tasks").toInt(&ok);
147 if (! ok)
148 limitParallelFetchTasks = 10;
150 limitActiveTasks = model->property("trojita-imap-limit-active-tasks").toInt(&ok);
151 if (! ok)
152 limitActiveTasks = 100;
154 CHECK_TASK_TREE
155 emit model->mailboxSyncingProgress(mailboxIndex, STATE_WAIT_FOR_CONN);
157 connect(this, &ImapTask::failed, this, &KeepMailboxOpenTask::signalSyncFailure);
159 /** @short How often to reset the time window (in ms) for determining mass-change mode */
160 const int throttlingWindowLength = 1000;
162 m_syncingTimer = new QTimer(this);
163 m_syncingTimer->setSingleShot(true);
164 // This timeout specifies how long we're going to wait for an incoming reply which usually triggers state syncing.
165 // If no such response arrives during this time window, the changes are saved on disk; if, however, something does
166 // arrive, the rate of saving is only moderated based on the number of reponses which were already received,
167 // but which did not result in state saving yet.
168 m_syncingTimer->setInterval(throttlingWindowLength);
169 connect(m_syncingTimer, &QTimer::timeout, this, &KeepMailboxOpenTask::syncingTimeout);
172 void KeepMailboxOpenTask::slotPerformConnection()
174 CHECK_TASK_TREE
175 Q_ASSERT(synchronizeConn);
176 Q_ASSERT(!synchronizeConn->isFinished());
177 if (_dead) {
178 _failed(tr("Asked to die"));
179 synchronizeConn->die(QStringLiteral("KeepMailboxOpenTask died before the sync started"));
180 return;
183 connect(synchronizeConn, &QObject::destroyed, this, &KeepMailboxOpenTask::slotTaskDeleted);
184 synchronizeConn->perform();
187 void KeepMailboxOpenTask::addDependentTask(ImapTask *task)
189 CHECK_TASK_TREE
190 Q_ASSERT(task);
192 // FIXME: what about abort()/die() here?
194 breakOrCancelPossibleIdle();
196 DeleteMailboxTask *deleteTask = qobject_cast<DeleteMailboxTask*>(task);
197 if (!deleteTask || deleteTask->mailbox != mailboxIndex.data(RoleMailboxName).toString()) {
198 deleteTask = 0;
201 if (ObtainSynchronizedMailboxTask *obtainTask = qobject_cast<ObtainSynchronizedMailboxTask *>(task)) {
202 // Another KeepMailboxOpenTask would like to replace us, so we shall die, eventually.
203 // This branch is reimplemented from ImapTask
205 dependentTasks.append(task);
206 waitingObtainTasks.append(obtainTask);
207 shouldExit = true;
208 task->updateParentTask(this);
210 // Before we can die, though, we have to accommodate fetch requests for all envelopes and parts queued so far.
211 slotFetchRequestedEnvelopes();
212 slotFetchRequestedParts();
214 if (! hasPendingInternalActions() && (! synchronizeConn || synchronizeConn->isFinished())) {
215 QTimer::singleShot(0, this, SLOT(terminate()));
218 Q_FOREACH(ImapTask *abortable, abortableTasks) {
219 abortable->abort();
221 } else if (deleteTask) {
222 // Got a request to delete the current mailbox. Fair enough, here we go!
224 if (hasPendingInternalActions() || (synchronizeConn && !synchronizeConn->isFinished())) {
225 // Hmm, this is bad -- the caller has instructed us to delete the current mailbox, but we still have
226 // some pending actions (or have not even started yet). Better reject the request for deleting than lose some data.
227 // Alternatively, we might pretend that we're a performance-oriented library and "optimize out" the
228 // data transfer by deleting early :)
229 deleteTask->mailboxHasPendingActions();
230 return;
233 m_deleteCurrentMailboxTask = deleteTask;
234 shouldExit = true;
235 connect(task, &QObject::destroyed, this, &KeepMailboxOpenTask::slotTaskDeleted);
236 ImapTask::addDependentTask(task);
237 QTimer::singleShot(0, this, SLOT(slotActivateTasks()));
239 } else {
240 // This branch calls the inherited ImapTask::addDependentTask()
241 connect(task, &QObject::destroyed, this, &KeepMailboxOpenTask::slotTaskDeleted);
242 ImapTask::addDependentTask(task);
243 if (task->needsMailbox()) {
244 // it's a task which is tied to a particular mailbox
245 dependingTasksForThisMailbox.append(task);
246 } else {
247 dependingTasksNoMailbox.append(task);
249 QTimer::singleShot(0, this, SLOT(slotActivateTasks()));
253 void KeepMailboxOpenTask::slotTaskDeleted(QObject *object)
255 if (_finished)
256 return;
258 if (!model) {
259 // we're very likely hitting this during some rather unclean destruction -> ignore this and die ASAP
260 // See https://bugs.kde.org/show_bug.cgi?id=336090
261 return;
264 if (!model->m_parsers.contains(parser)) {
265 // The parser is gone; we have to get out of here ASAP
266 _failed(tr("Parser is gone"));
267 die(tr("Parser is gone"));
268 return;
270 // FIXME: abort/die
272 // Now, object is no longer an ImapTask*, as this gets emitted from inside QObject's destructor. However,
273 // we can't use the passed pointer directly, and therefore we have to perform the cast here. It is safe
274 // to do that here, as we're only interested in raw pointer value.
275 if (object) {
276 dependentTasks.removeOne(static_cast<ImapTask *>(object));
277 dependingTasksForThisMailbox.removeOne(static_cast<ImapTask *>(object));
278 dependingTasksNoMailbox.removeOne(static_cast<ImapTask *>(object));
279 runningTasksForThisMailbox.removeOne(static_cast<ImapTask *>(object));
280 fetchPartTasks.removeOne(static_cast<FetchMsgPartTask *>(object));
281 fetchMetadataTasks.removeOne(static_cast<FetchMsgMetadataTask *>(object));
282 abortableTasks.removeOne(static_cast<FetchMsgMetadataTask *>(object));
285 if (isReadyToTerminate()) {
286 terminate();
287 } else if (shouldRunNoop) {
288 // A command just completed, and NOOPing is active, so let's schedule/postpone it again
289 noopTimer->start();
290 } else if (canRunIdleRightNow()) {
291 // A command just completed and IDLE is supported, so let's queue/schedule/postpone it
292 idleLauncher->enterIdleLater();
294 // It's possible that we can start more tasks at this time...
295 activateTasks();
298 void KeepMailboxOpenTask::terminate()
300 if (_aborted) {
301 // We've already been there, so we *cannot* proceed towards activating our replacement tasks
302 return;
304 abort();
306 m_syncingTimer->stop();
307 syncingTimeout();
309 // FIXME: abort/die
311 Q_ASSERT(dependingTasksForThisMailbox.isEmpty());
312 Q_ASSERT(dependingTasksNoMailbox.isEmpty());
313 Q_ASSERT(requestedParts.isEmpty());
314 Q_ASSERT(requestedEnvelopes.isEmpty());
315 Q_ASSERT(runningTasksForThisMailbox.isEmpty());
316 Q_ASSERT(abortableTasks.isEmpty());
317 Q_ASSERT(!m_syncingTimer->isActive());
318 Q_ASSERT(m_skippedStateSynces == 0);
320 // Break periodic activities
321 shouldRunIdle = false;
322 shouldRunNoop = false;
323 isRunning = Running::NOT_ANYMORE;
325 // Merge the lists of waiting tasks
326 if (!waitingObtainTasks.isEmpty()) {
327 ObtainSynchronizedMailboxTask *first = waitingObtainTasks.takeFirst();
328 dependentTasks.removeOne(first);
329 Q_ASSERT(first);
330 Q_ASSERT(first->keepTaskChild);
331 Q_ASSERT(first->keepTaskChild->synchronizeConn == first);
333 CHECK_TASK_TREE
334 // Update the parent information for the moved tasks
335 Q_FOREACH(ObtainSynchronizedMailboxTask *movedObtainTask, waitingObtainTasks) {
336 Q_ASSERT(movedObtainTask->parentTask);
337 movedObtainTask->parentTask->dependentTasks.removeOne(movedObtainTask);
338 movedObtainTask->parentTask = first->keepTaskChild;
339 first->keepTaskChild->dependentTasks.append(movedObtainTask);
341 CHECK_TASK_TREE
343 // And launch the replacement
344 first->keepTaskChild->waitingObtainTasks = waitingObtainTasks + first->keepTaskChild->waitingObtainTasks;
345 model->accessParser(parser).maintainingTask = first->keepTaskChild;
346 // make sure that if the SELECT dies uncleanly, such as with a missing [CLOSED], we get killed as well
347 connect(first->keepTaskChild, &ImapTask::failed, this, &KeepMailboxOpenTask::finalizeTermination);
348 first->keepTaskChild->slotPerformConnection();
349 } else {
350 Q_ASSERT(dependentTasks.isEmpty());
352 if (model->accessParser(parser).connState == CONN_STATE_SELECTING_WAIT_FOR_CLOSE) {
353 // we have to be kept busy, otherwise the responses which are still destined for *this* mailbox would
354 // get delivered to the new one
355 } else {
356 finalizeTermination();
358 CHECK_TASK_TREE
361 void KeepMailboxOpenTask::perform()
363 // FIXME: abort/die
365 Q_ASSERT(synchronizeConn);
366 Q_ASSERT(synchronizeConn->isFinished());
367 parser = synchronizeConn->parser;
368 synchronizeConn = 0; // will get deleted by Model
369 markAsActiveTask();
371 isRunning = Running::RUNNING;
372 fetchPartTimer->start();
373 fetchEnvelopeTimer->start();
375 if (!waitingObtainTasks.isEmpty()) {
376 shouldExit = true;
379 activateTasks();
381 if (model->accessParser(parser).capabilitiesFresh && model->accessParser(parser).capabilities.contains(QStringLiteral("IDLE"))) {
382 shouldRunIdle = true;
383 } else {
384 shouldRunNoop = true;
387 if (shouldRunNoop) {
388 noopTimer->start();
389 } else if (shouldRunIdle) {
390 idleLauncher = new IdleLauncher(this);
391 if (canRunIdleRightNow()) {
392 // There's no task yet, so we have to start IDLE now
393 idleLauncher->enterIdleLater();
398 void KeepMailboxOpenTask::resynchronizeMailbox()
400 // FIXME: abort/die
402 if (isRunning != Running::NOT_YET) {
403 // Instead of wild magic with re-creating synchronizeConn, it's way easier to
404 // just have us replaced by another KeepMailboxOpenTask
405 model->m_taskFactory->createKeepMailboxOpenTask(model, mailboxIndex, parser);
406 } else {
407 // We aren't running yet, which means that the sync hadn't happened yet, and therefore
408 // we don't have to do it "once again" -- it will happen automatically later on.
412 #define CHECK_IS_RUNNING \
413 switch (isRunning) { \
414 case Running::NOT_YET: \
415 return false; \
416 case Running::NOT_ANYMORE: \
417 /* OK, a lost reply -- we're already switching to another mailbox, and even though this might seem */ \
418 /* to be a safe change, we just cannot react to this right now :(. */ \
419 /* Also, don't eat further replies once we're dead :) */ \
420 return model->accessParser(parser).connState == CONN_STATE_SELECTING_WAIT_FOR_CLOSE; \
421 case Running::RUNNING: \
422 /* normal state -> handle this */ \
423 break; \
426 bool KeepMailboxOpenTask::handleNumberResponse(const Imap::Responses::NumberResponse *const resp)
428 if (_dead) {
429 _failed(tr("Asked to die"));
430 return true;
433 if (dieIfInvalidMailbox())
434 return true;
436 CHECK_IS_RUNNING
438 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
439 Q_ASSERT(mailbox);
440 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(mailbox->m_children[0]);
441 Q_ASSERT(list);
442 // FIXME: tests!
443 if (resp->kind == Imap::Responses::EXPUNGE) {
444 mailbox->handleExpunge(model, *resp);
445 mailbox->syncState.setExists(mailbox->syncState.exists() - 1);
446 saveSyncStateNowOrLater(mailbox);
447 return true;
448 } else if (resp->kind == Imap::Responses::EXISTS) {
450 if (resp->number == static_cast<uint>(list->m_children.size())) {
451 // no changes
452 return true;
455 mailbox->handleExists(model, *resp);
457 breakOrCancelPossibleIdle();
459 Q_ASSERT(list->m_children.size());
460 uint highestKnownUid = 0;
461 for (int i = list->m_children.size() - 1; ! highestKnownUid && i >= 0; --i) {
462 highestKnownUid = static_cast<const TreeItemMessage *>(list->m_children[i])->uid();
463 //qDebug() << "UID disco: trying seq" << i << highestKnownUid;
465 breakOrCancelPossibleIdle();
466 newArrivalsFetch.append(parser->uidFetch(Sequence::startingAt(
467 // Did the UID walk return a usable number?
468 highestKnownUid ?
469 // Yes, we've got at least one message with a UID known -> ask for higher
470 // but don't forget to compensate for an pre-existing UIDNEXT value
471 qMax(mailbox->syncState.uidNext(), highestKnownUid + 1)
473 // No messages, or no messages with valid UID -> use the UIDNEXT from the syncing state
474 // but prevent a possible invalid 0:*
475 qMax(mailbox->syncState.uidNext(), 1u)
476 ), QList<QByteArray>() << "FLAGS"));
477 model->m_taskModel->slotTaskMighHaveChanged(this);
478 return true;
479 } else if (resp->kind == Imap::Responses::RECENT) {
480 mailbox->syncState.setRecent(resp->number);
481 list->m_recentMessageCount = resp->number;
482 model->emitMessageCountChanged(mailbox);
483 saveSyncStateNowOrLater(mailbox);
484 return true;
485 } else {
486 return false;
490 bool KeepMailboxOpenTask::handleVanished(const Responses::Vanished *const resp)
492 if (_dead) {
493 _failed(tr("Asked to die"));
494 return true;
497 if (dieIfInvalidMailbox())
498 return true;
500 CHECK_IS_RUNNING
502 if (resp->earlier != Responses::Vanished::NOT_EARLIER)
503 return false;
505 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
506 Q_ASSERT(mailbox);
508 mailbox->handleVanished(model, *resp);
509 saveSyncStateNowOrLater(mailbox);
510 return true;
513 bool KeepMailboxOpenTask::handleFetch(const Imap::Responses::Fetch *const resp)
515 if (dieIfInvalidMailbox())
516 return true;
518 if (_dead) {
519 _failed(tr("Asked to die"));
520 return true;
523 CHECK_IS_RUNNING
525 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
526 Q_ASSERT(mailbox);
527 model->genericHandleFetch(mailbox, resp);
528 return true;
531 void KeepMailboxOpenTask::slotPerformNoop()
533 // FIXME: abort/die
534 model->m_taskFactory->createNoopTask(model, this);
537 bool KeepMailboxOpenTask::handleStateHelper(const Imap::Responses::State *const resp)
539 // FIXME: abort/die
541 if (handleResponseCodeInsideState(resp))
542 return true;
544 // FIXME: checks for shouldExit and proper boundaries?
546 if (resp->respCode == Responses::CLOSED) {
547 switch (model->accessParser(parser).connState) {
548 case CONN_STATE_SELECTING:
549 case CONN_STATE_SELECTING_WAIT_FOR_CLOSE:
550 model->changeConnectionState(parser, CONN_STATE_SELECTING);
551 finalizeTermination();
552 break;
553 default:
554 throw UnexpectedResponseReceived("No other mailbox is being selected, but got a [CLOSED] response", *resp);
558 if (resp->tag.isEmpty())
559 return false;
561 if (resp->tag == tagIdle) {
563 Q_ASSERT(idleLauncher);
564 if (resp->kind == Responses::OK) {
565 // The IDLE got terminated for whatever reason, so we should schedule its restart
566 idleLauncher->idleCommandCompleted();
567 if (canRunIdleRightNow()) {
568 idleLauncher->enterIdleLater();
570 } else {
571 // The IDLE command has failed. Let's assume it's a permanent error and don't request it in future.
572 log(QStringLiteral("The IDLE command has failed"));
573 shouldRunIdle = false;
574 idleLauncher->idleCommandFailed();
575 idleLauncher->deleteLater();
576 idleLauncher = 0;
578 tagIdle.clear();
579 // IDLE is special because it's not really a native Task. Therefore, we have to duplicate the check for its completion
580 // and possible termination request here.
581 // FIXME: maybe rewrite IDLE to be a native task and get all the benefits for free? Any drawbacks?
582 if (shouldExit && ! hasPendingInternalActions() && (! synchronizeConn || synchronizeConn->isFinished())) {
583 terminate();
585 return true;
586 } else if (newArrivalsFetch.contains(resp->tag)) {
587 newArrivalsFetch.removeOne(resp->tag);
589 if (newArrivalsFetch.isEmpty() && mailboxIndex.isValid()) {
590 // No pending commands for fetches of the mailbox state -> we have a consistent and accurate, up-to-date view
591 // -> we should save this
592 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
593 Q_ASSERT(mailbox);
594 mailbox->saveSyncStateAndUids(model);
597 if (resp->kind != Responses::OK) {
598 _failed(QLatin1String("FETCH of new arrivals failed: ") + resp->message);
600 // Don't forget to resume IDLE, if desired; that's easiest by simply behaving as if a "task" has just finished
601 slotTaskDeleted(0);
602 model->m_taskModel->slotTaskMighHaveChanged(this);
603 return true;
604 } else if (resp->tag == tagClose) {
605 tagClose.clear();
606 if (m_deleteCurrentMailboxTask) {
607 m_deleteCurrentMailboxTask->perform();
609 if (resp->kind != Responses::OK) {
610 _failed(QLatin1String("CLOSE failed: ") + resp->message);
612 terminate();
613 return true;
614 } else {
615 return false;
619 /** @short Reimplemented from ImapTask
621 This function's semantics is slightly shifted from ImapTask::abort(). It gets called when the KeepMailboxOpenTask has decided to
622 terminate and its biggest goals are to:
624 - Prevent any further activity from hitting this parser. We're here to "guard" access to it, and we're about to terminate, so the
625 tasks shall negotiate their access through some other KeepMailboxOpenTask.
626 - Terminate our internal code which might want to access the connection (NoopTask, IdleLauncher,...)
628 void KeepMailboxOpenTask::abort()
630 if (noopTimer)
631 noopTimer->stop();
632 if (idleLauncher)
633 idleLauncher->die();
635 detachFromMailbox();
637 _aborted = true;
638 // We do not want to propagate the signal to the child tasks, though -- the KeepMailboxOpenTask::abort() is used in the course
639 // of the regular "hey, free this connection and pass it to another KeepMailboxOpenTask" situations.
642 /** @short Stop working as a maintaining task */
643 void KeepMailboxOpenTask::detachFromMailbox()
645 if (mailboxIndex.isValid()) {
646 // Mark current mailbox as "orphaned by the housekeeping task"
647 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
648 Q_ASSERT(mailbox);
650 // We're already obsolete -> don't pretend to accept new tasks
651 if (mailbox->maintainingTask == this)
652 mailbox->maintainingTask = 0;
654 if (model->m_parsers.contains(parser) && model->accessParser(parser).maintainingTask == this) {
655 model->accessParser(parser).maintainingTask = 0;
659 /** @short Reimplemented from ImapTask
661 We're aksed to die right now, so we better take any depending stuff with us. That poor tasks are not going to outlive me!
663 void KeepMailboxOpenTask::die(const QString &message)
665 if (shouldExit) {
666 // OK, we're done, and getting killed. This is fine; just don't emit failed()
667 // because we aren't actually failing.
668 // This is a speciality of the KeepMailboxOpenTask because it's the only task
669 // this has a very long life.
670 _finished = true;
672 ImapTask::die(message);
673 detachFromMailbox();
676 /** @short Kill all pending tasks -- both the regular one and the replacement ObtainSynchronizedMailboxTask instances
678 Reimplemented from the ImapTask.
680 void KeepMailboxOpenTask::killAllPendingTasks(const QString &message)
682 Q_FOREACH(ImapTask *task, dependingTasksForThisMailbox) {
683 task->die(message);
685 Q_FOREACH(ImapTask *task, dependingTasksNoMailbox) {
686 task->die(message);
688 Q_FOREACH(ImapTask *task, waitingObtainTasks) {
689 task->die(message);
693 QString KeepMailboxOpenTask::debugIdentification() const
695 if (! mailboxIndex.isValid())
696 return QStringLiteral("[invalid mailboxIndex]");
698 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
699 Q_ASSERT(mailbox);
700 return QString::fromUtf8("attached to %1%2%3").arg(mailbox->mailbox(),
701 (synchronizeConn && ! synchronizeConn->isFinished()) ? QStringLiteral(" [syncConn unfinished]") : QString(),
702 shouldExit ? QStringLiteral(" [shouldExit]") : QString()
706 /** @short The user wants us to go offline */
707 void KeepMailboxOpenTask::stopForLogout()
709 abort();
710 breakOrCancelPossibleIdle();
711 killAllPendingTasks(tr("Logging off..."));
713 // We're supposed to go offline. Given that we're a long-running task, I do not consider this a "failure".
714 // In particular, if the initial SELECT has not finished yet, the ObtainSynchronizedMailboxTask would get
715 // killed as well, and hence the mailboxSyncFailed() signal will get emitted.
716 // The worst thing which can possibly happen is that we're in the middle of checking the new arrivals.
717 // That's bad, because we've got unknown UIDs in our in-memory map, which is going to hurt during the next sync
718 // -- but that's something which should be handled elsewhere, IMHO.
719 // Therefore, make sure a subsequent call to die() doesn't propagate a failure.
720 shouldExit = true;
723 bool KeepMailboxOpenTask::handleFlags(const Imap::Responses::Flags *const resp)
725 if (dieIfInvalidMailbox())
726 return true;
728 // Well, there isn't much point in keeping track of these flags, but given that
729 // IMAP servers are happy to send these responses even after the initial sync, we
730 // better handle them explicitly here.
731 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
732 Q_ASSERT(mailbox);
733 mailbox->syncState.setFlags(resp->flags);
734 return true;
737 void KeepMailboxOpenTask::activateTasks()
739 // FIXME: abort/die
741 if (isRunning != Running::RUNNING)
742 return;
744 breakOrCancelPossibleIdle();
746 if (m_deleteCurrentMailboxTask) {
747 closeMailboxDestructively();
748 return;
751 slotFetchRequestedEnvelopes();
752 slotFetchRequestedParts();
754 while (!dependingTasksForThisMailbox.isEmpty() && model->accessParser(parser).activeTasks.size() < limitActiveTasks) {
755 breakOrCancelPossibleIdle();
756 ImapTask *task = dependingTasksForThisMailbox.takeFirst();
757 runningTasksForThisMailbox.append(task);
758 dependentTasks.removeOne(task);
759 task->perform();
761 while (!dependingTasksNoMailbox.isEmpty() && model->accessParser(parser).activeTasks.size() < limitActiveTasks) {
762 breakOrCancelPossibleIdle();
763 ImapTask *task = dependingTasksNoMailbox.takeFirst();
764 dependentTasks.removeOne(task);
765 task->perform();
768 if (idleLauncher && canRunIdleRightNow())
769 idleLauncher->enterIdleLater();
772 void KeepMailboxOpenTask::requestPartDownload(const uint uid, const QByteArray &partId, const uint estimatedSize)
774 requestedParts[uid].insert(partId);
775 requestedPartSizes[uid] += estimatedSize;
776 if (!fetchPartTimer->isActive()) {
777 fetchPartTimer->start();
781 void KeepMailboxOpenTask::requestEnvelopeDownload(const uint uid)
783 requestedEnvelopes.append(uid);
784 if (!fetchEnvelopeTimer->isActive()) {
785 fetchEnvelopeTimer->start();
789 void KeepMailboxOpenTask::slotFetchRequestedParts()
791 // FIXME: abort/die
793 if (requestedParts.isEmpty())
794 return;
796 breakOrCancelPossibleIdle();
798 auto it = requestedParts.begin();
799 auto parts = *it;
801 // When asked to exit, do as much as possible and die
802 while (shouldExit || fetchPartTasks.size() < limitParallelFetchTasks) {
803 Imap::Uids uids;
804 uint totalSize = 0;
805 while (uids.size() < limitMessagesAtOnce && it != requestedParts.end() && totalSize < limitBytesAtOnce) {
806 if (parts != *it)
807 break;
808 parts = *it;
809 uids << it.key();
810 totalSize += requestedPartSizes.take(it.key());
811 it = requestedParts.erase(it);
813 if (uids.isEmpty())
814 return;
816 fetchPartTasks << model->m_taskFactory->createFetchMsgPartTask(model, mailboxIndex, uids, parts.toList());
820 void KeepMailboxOpenTask::slotFetchRequestedEnvelopes()
822 // FIXME: abort/die
824 if (requestedEnvelopes.isEmpty())
825 return;
827 breakOrCancelPossibleIdle();
829 Imap::Uids fetchNow;
830 if (shouldExit) {
831 fetchNow = requestedEnvelopes;
832 requestedEnvelopes.clear();
833 } else {
834 const int amount = qMin(requestedEnvelopes.size(), limitMessagesAtOnce); // FIXME: add an extra limit?
835 fetchNow = requestedEnvelopes.mid(0, amount);
836 requestedEnvelopes.erase(requestedEnvelopes.begin(), requestedEnvelopes.begin() + amount);
838 fetchMetadataTasks << model->m_taskFactory->createFetchMsgMetadataTask(model, mailboxIndex, fetchNow);
841 void KeepMailboxOpenTask::breakOrCancelPossibleIdle()
843 if (idleLauncher) {
844 idleLauncher->finishIdle();
848 bool KeepMailboxOpenTask::handleResponseCodeInsideState(const Imap::Responses::State *const resp)
850 switch (resp->respCode) {
851 case Responses::UIDNEXT:
853 if (dieIfInvalidMailbox())
854 return resp->tag.isEmpty();
856 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
857 Q_ASSERT(mailbox);
858 const Responses::RespData<uint> *const num = dynamic_cast<const Responses::RespData<uint>* const>(resp->respCodeData.data());
859 if (num) {
860 mailbox->syncState.setUidNext(num->data);
861 saveSyncStateNowOrLater(mailbox);
862 // We shouldn't eat tagged responses from this context
863 return resp->tag.isEmpty();
864 } else {
865 throw CantHappen("State response has invalid UIDNEXT respCodeData", *resp);
867 break;
869 case Responses::PERMANENTFLAGS:
870 // Another useless one, but we want to consume it now to prevent a warning about
871 // an unhandled message
873 if (dieIfInvalidMailbox())
874 return resp->tag.isEmpty();
876 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
877 Q_ASSERT(mailbox);
878 const Responses::RespData<QStringList> *const num = dynamic_cast<const Responses::RespData<QStringList>* const>(resp->respCodeData.data());
879 if (num) {
880 mailbox->syncState.setPermanentFlags(num->data);
881 // We shouldn't eat tagged responses from this context
882 return resp->tag.isEmpty();
883 } else {
884 throw CantHappen("State response has invalid PERMANENTFLAGS respCodeData", *resp);
886 break;
888 case Responses::HIGHESTMODSEQ:
890 if (dieIfInvalidMailbox())
891 return resp->tag.isEmpty();
893 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
894 Q_ASSERT(mailbox);
895 const Responses::RespData<quint64> *const num = dynamic_cast<const Responses::RespData<quint64>* const>(resp->respCodeData.data());
896 Q_ASSERT(num);
897 mailbox->syncState.setHighestModSeq(num->data);
898 saveSyncStateNowOrLater(mailbox);
899 return resp->tag.isEmpty();
901 case Responses::UIDVALIDITY:
903 if (dieIfInvalidMailbox())
904 return resp->tag.isEmpty();
906 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
907 Q_ASSERT(mailbox);
908 const Responses::RespData<uint> *const num = dynamic_cast<const Responses::RespData<uint>* const>(resp->respCodeData.data());
909 Q_ASSERT(num);
910 if (mailbox->syncState.uidValidity() == num->data) {
911 // this is a harmless and useless message
912 return resp->tag.isEmpty();
913 } else {
914 // On the other hand, this a serious condition -- the server is telling us that the UIDVALIDITY has changed while
915 // a mailbox is open. There isn't much we could do here; having code for handling this gracefuly is just too much
916 // work for little to no benefit.
917 // The sane thing is to disconnect from this mailbox.
918 EMIT_LATER(model, imapError, Q_ARG(QString, tr("The UIDVALIDITY has changed while mailbox is open. Please reconnect.")));
919 model->setNetworkPolicy(NETWORK_OFFLINE);
920 return resp->tag.isEmpty();
923 default:
924 // Do nothing here
925 break;
927 return false;
930 void KeepMailboxOpenTask::slotUnselected()
932 switch (model->accessParser(parser).connState) {
933 case CONN_STATE_SYNCING:
934 case CONN_STATE_SELECTED:
935 case CONN_STATE_FETCHING_PART:
936 case CONN_STATE_FETCHING_MSG_METADATA:
937 model->changeConnectionState(parser, CONN_STATE_AUTHENTICATED);
938 break;
939 default:
940 // no need to do anything from here
941 break;
943 detachFromMailbox();
944 isRunning = Running::RUNNING; // WTF?
945 shouldExit = true;
946 _failed(tr("UNSELECTed"));
949 bool KeepMailboxOpenTask::dieIfInvalidMailbox()
951 if (mailboxIndex.isValid())
952 return false;
954 if (m_deleteCurrentMailboxTask) {
955 // The current mailbox was supposed to be deleted; don't try to UNSELECT from this context
956 return true;
959 // See ObtainSynchronizedMailboxTask::dieIfInvalidMailbox() for details
960 if (!unSelectTask && isRunning == Running::RUNNING) {
961 unSelectTask = model->m_taskFactory->createUnSelectTask(model, this);
962 connect(unSelectTask, &ImapTask::completed, this, &KeepMailboxOpenTask::slotUnselected);
963 unSelectTask->perform();
966 return true;
969 bool KeepMailboxOpenTask::hasPendingInternalActions() const
971 bool hasToWaitForIdleTermination = idleLauncher ? idleLauncher->waitingForIdleTaggedTermination() : false;
972 return !(dependingTasksForThisMailbox.isEmpty() && dependingTasksNoMailbox.isEmpty() && runningTasksForThisMailbox.isEmpty() &&
973 requestedParts.isEmpty() && requestedEnvelopes.isEmpty() && newArrivalsFetch.isEmpty()) || hasToWaitForIdleTermination;
976 /** @short Returns true if this task can be safely terminated
978 FIXME: document me
980 bool KeepMailboxOpenTask::isReadyToTerminate() const
982 return shouldExit && !hasPendingInternalActions() && (!synchronizeConn || synchronizeConn->isFinished());
985 /** @short Return true if we're configured to run IDLE and if there's no ongoing activity */
986 bool KeepMailboxOpenTask::canRunIdleRightNow() const
988 bool res = shouldRunIdle && dependingTasksForThisMailbox.isEmpty() &&
989 dependingTasksNoMailbox.isEmpty() && newArrivalsFetch.isEmpty();
991 // If there's just one active tasks, it's the "this" one. If there are more of them, let's see if it's just one more
992 // and that one more thing is a SortTask which is in the "just updating" mode.
993 // If that is the case, we can still allow further IDLE, that task will abort idling when it needs to.
994 // Nifty, isn't it?
995 if (model->accessParser(parser).activeTasks.size() > 1) {
996 if (model->accessParser(parser).activeTasks.size() == 2 &&
997 dynamic_cast<SortTask*>(model->accessParser(parser).activeTasks[1]) &&
998 dynamic_cast<SortTask*>(model->accessParser(parser).activeTasks[1])->isJustUpdatingNow()) {
999 // This is OK, so no need to clear the "OK" flag
1000 } else {
1001 // Too bad, cannot IDLE
1002 res = false;
1006 if (!res)
1007 return false;
1009 Q_ASSERT(model->accessParser(parser).activeTasks.front() == this);
1010 return true;
1013 QVariant KeepMailboxOpenTask::taskData(const int role) const
1015 // FIXME
1016 Q_UNUSED(role);
1017 return QVariant();
1020 /** @short The specified task can be abort()ed when the mailbox shall be vacanted
1022 It's an error to call this on a task which we aren't tracking already.
1024 void KeepMailboxOpenTask::feelFreeToAbortCaller(ImapTask *task)
1026 abortableTasks.append(task);
1029 /** @short It's time to reset the counters and perform the sync */
1030 void KeepMailboxOpenTask::syncingTimeout()
1032 if (!mailboxIndex.isValid()) {
1033 return;
1035 if (m_skippedStateSynces == 0) {
1036 // This means that state syncing it not being throttled, i.e. we're just resetting the window
1037 // which determines the rate of requests which would normally trigger saving
1038 m_performedStateSynces = 0;
1039 } else {
1040 // there's been no fresh arrivals for our timeout period -> let's flush the pending events
1041 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
1042 Q_ASSERT(mailbox);
1043 saveSyncStateIfPossible(mailbox);
1047 void KeepMailboxOpenTask::saveSyncStateNowOrLater(Imap::Mailbox::TreeItemMailbox *mailbox)
1049 bool saveImmediately = true;
1051 /** @short After processing so many responses immediately, switch to a delayed mode where the saving is deferred */
1052 const uint throttlingThreshold = 100;
1054 /** @short Flush the queue after postponing this number of messages.
1056 It's "ridiculously high", but it's still a number so that our integer newer wraps.
1058 const uint maxDelayedResponses = 10000;
1060 if (m_skippedStateSynces > 0) {
1061 // we're actively throttling
1062 if (m_skippedStateSynces >= maxDelayedResponses) {
1063 // ...but we've been throttling too many responses, let's flush them now
1064 Q_ASSERT(m_syncingTimer->isActive());
1065 // do not go back to 0, otherwise there will be a lot of immediate events within each interval
1066 m_performedStateSynces = throttlingThreshold;
1067 saveImmediately = true;
1068 } else {
1069 ++m_skippedStateSynces;
1070 saveImmediately = false;
1072 } else {
1073 // no throttling, cool
1074 if (m_performedStateSynces >= throttlingThreshold) {
1075 // ...but we should start throttling now
1076 ++m_skippedStateSynces;
1077 saveImmediately = false;
1078 } else {
1079 ++m_performedStateSynces;
1080 if (!m_syncingTimer->isActive()) {
1081 // reset the sliding window
1082 m_syncingTimer->start();
1084 saveImmediately = true;
1088 if (saveImmediately) {
1089 saveSyncStateIfPossible(mailbox);
1090 } else {
1091 m_performedStateSynces = 0;
1092 m_syncingTimer->start();
1096 void KeepMailboxOpenTask::saveSyncStateIfPossible(TreeItemMailbox *mailbox)
1098 m_skippedStateSynces = 0;
1099 TreeItemMsgList *list = static_cast<TreeItemMsgList*>(mailbox->m_children[0]);
1100 if (list->fetched()) {
1101 mailbox->saveSyncStateAndUids(model);
1102 } else {
1103 list->setFetchStatus(Imap::Mailbox::TreeItem::LOADING);
1107 void KeepMailboxOpenTask::closeMailboxDestructively()
1109 tagClose = parser->close();
1112 /** @short Let the model know that a mailbox synchronization has failed */
1113 void KeepMailboxOpenTask::signalSyncFailure(const QString &message)
1115 if (!mailboxIndex.isValid()) {
1116 // Well, that mailbox is no longer there; perhaps this is because the list of mailboxes got replaced.
1117 // Seems that there's nothing to report here.
1118 return;
1121 if (synchronizeConn) {
1122 // Well, we aren't synced yet. We're going to rely on the ObtainSynchronizedMailboxTask's own
1123 // emitting of mailboxSyncFailed() to prevent duplicate signals.
1124 return;
1127 EMIT_LATER(model, mailboxSyncFailed, Q_ARG(QString, mailboxIndex.data(RoleMailboxName).toString()), Q_ARG(QString, message));
1130 /** @short Is this task on its own keeping the connection busy?
1132 Right now, only fetching of new arrivals is being done in the context of this KeepMailboxOpenTask task.
1134 bool KeepMailboxOpenTask::hasItsOwnActivity() const
1136 return !newArrivalsFetch.isEmpty();
1139 /** @short Signal the final termination of this task */
1140 void KeepMailboxOpenTask::finalizeTermination()
1142 if (!_finished) {
1143 _finished = true;
1144 emit completed(this);
1146 CHECK_TASK_TREE;