Merge changes Ief52e93b,Id9c6a73e
[trojita.git] / src / Imap / Tasks / KeepMailboxOpenTask.cpp
blob520847aaf70ebc3c490893eeff063f4174e30439
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 /** @short How often to reset the time window (in ms) for determining mass-change mode */
158 const int throttlingWindowLength = 1000;
160 m_syncingTimer = new QTimer(this);
161 m_syncingTimer->setSingleShot(true);
162 // This timeout specifies how long we're going to wait for an incoming reply which usually triggers state syncing.
163 // If no such response arrives during this time window, the changes are saved on disk; if, however, something does
164 // arrive, the rate of saving is only moderated based on the number of reponses which were already received,
165 // but which did not result in state saving yet.
166 m_syncingTimer->setInterval(throttlingWindowLength);
167 connect(m_syncingTimer, &QTimer::timeout, this, &KeepMailboxOpenTask::syncingTimeout);
170 void KeepMailboxOpenTask::slotPerformConnection()
172 CHECK_TASK_TREE
173 Q_ASSERT(synchronizeConn);
174 Q_ASSERT(!synchronizeConn->isFinished());
175 if (_dead) {
176 _failed(tr("Asked to die"));
177 synchronizeConn->die(QStringLiteral("KeepMailboxOpenTask died before the sync started"));
178 return;
181 connect(synchronizeConn, &QObject::destroyed, this, &KeepMailboxOpenTask::slotTaskDeleted);
182 synchronizeConn->perform();
185 void KeepMailboxOpenTask::addDependentTask(ImapTask *task)
187 CHECK_TASK_TREE
188 Q_ASSERT(task);
190 // FIXME: what about abort()/die() here?
192 breakOrCancelPossibleIdle();
194 DeleteMailboxTask *deleteTask = qobject_cast<DeleteMailboxTask*>(task);
195 if (!deleteTask || deleteTask->mailbox != mailboxIndex.data(RoleMailboxName).toString()) {
196 deleteTask = 0;
199 if (ObtainSynchronizedMailboxTask *obtainTask = qobject_cast<ObtainSynchronizedMailboxTask *>(task)) {
200 // Another KeepMailboxOpenTask would like to replace us, so we shall die, eventually.
201 // This branch is reimplemented from ImapTask
203 dependentTasks.append(task);
204 waitingObtainTasks.append(obtainTask);
205 shouldExit = true;
206 task->updateParentTask(this);
208 // Before we can die, though, we have to accommodate fetch requests for all envelopes and parts queued so far.
209 slotFetchRequestedEnvelopes();
210 slotFetchRequestedParts();
212 if (! hasPendingInternalActions() && (! synchronizeConn || synchronizeConn->isFinished())) {
213 QTimer::singleShot(0, this, SLOT(terminate()));
216 Q_FOREACH(ImapTask *abortable, abortableTasks) {
217 abortable->abort();
219 } else if (deleteTask) {
220 // Got a request to delete the current mailbox. Fair enough, here we go!
222 if (hasPendingInternalActions() || (synchronizeConn && !synchronizeConn->isFinished())) {
223 // Hmm, this is bad -- the caller has instructed us to delete the current mailbox, but we still have
224 // some pending actions (or have not even started yet). Better reject the request for deleting than lose some data.
225 // Alternatively, we might pretend that we're a performance-oriented library and "optimize out" the
226 // data transfer by deleting early :)
227 deleteTask->mailboxHasPendingActions();
228 return;
231 m_deleteCurrentMailboxTask = deleteTask;
232 shouldExit = true;
233 connect(task, &QObject::destroyed, this, &KeepMailboxOpenTask::slotTaskDeleted);
234 ImapTask::addDependentTask(task);
235 QTimer::singleShot(0, this, SLOT(slotActivateTasks()));
237 } else {
238 // This branch calls the inherited ImapTask::addDependentTask()
239 connect(task, &QObject::destroyed, this, &KeepMailboxOpenTask::slotTaskDeleted);
240 ImapTask::addDependentTask(task);
241 if (task->needsMailbox()) {
242 // it's a task which is tied to a particular mailbox
243 dependingTasksForThisMailbox.append(task);
244 } else {
245 dependingTasksNoMailbox.append(task);
247 QTimer::singleShot(0, this, SLOT(slotActivateTasks()));
251 void KeepMailboxOpenTask::slotTaskDeleted(QObject *object)
253 if (_finished)
254 return;
256 if (!model) {
257 // we're very likely hitting this during some rather unclean destruction -> ignore this and die ASAP
258 // See https://bugs.kde.org/show_bug.cgi?id=336090
259 return;
262 if (!model->m_parsers.contains(parser)) {
263 // The parser is gone; we have to get out of here ASAP
264 _failed(tr("Parser is gone"));
265 die(tr("Parser is gone"));
266 return;
268 // FIXME: abort/die
270 // Now, object is no longer an ImapTask*, as this gets emitted from inside QObject's destructor. However,
271 // we can't use the passed pointer directly, and therefore we have to perform the cast here. It is safe
272 // to do that here, as we're only interested in raw pointer value.
273 if (object) {
274 dependentTasks.removeOne(static_cast<ImapTask *>(object));
275 dependingTasksForThisMailbox.removeOne(static_cast<ImapTask *>(object));
276 dependingTasksNoMailbox.removeOne(static_cast<ImapTask *>(object));
277 runningTasksForThisMailbox.removeOne(static_cast<ImapTask *>(object));
278 fetchPartTasks.removeOne(static_cast<FetchMsgPartTask *>(object));
279 fetchMetadataTasks.removeOne(static_cast<FetchMsgMetadataTask *>(object));
280 abortableTasks.removeOne(static_cast<FetchMsgMetadataTask *>(object));
283 if (isReadyToTerminate()) {
284 terminate();
285 } else if (shouldRunNoop) {
286 // A command just completed, and NOOPing is active, so let's schedule/postpone it again
287 noopTimer->start();
288 } else if (canRunIdleRightNow()) {
289 // A command just completed and IDLE is supported, so let's queue/schedule/postpone it
290 idleLauncher->enterIdleLater();
292 // It's possible that we can start more tasks at this time...
293 activateTasks();
296 void KeepMailboxOpenTask::terminate()
298 if (_aborted) {
299 // We've already been there, so we *cannot* proceed towards activating our replacement tasks
300 return;
302 abort();
304 m_syncingTimer->stop();
305 syncingTimeout();
307 // FIXME: abort/die
309 Q_ASSERT(dependingTasksForThisMailbox.isEmpty());
310 Q_ASSERT(dependingTasksNoMailbox.isEmpty());
311 Q_ASSERT(requestedParts.isEmpty());
312 Q_ASSERT(requestedEnvelopes.isEmpty());
313 Q_ASSERT(runningTasksForThisMailbox.isEmpty());
314 Q_ASSERT(abortableTasks.isEmpty());
315 Q_ASSERT(!m_syncingTimer->isActive());
316 Q_ASSERT(m_skippedStateSynces == 0);
318 // Break periodic activities
319 shouldRunIdle = false;
320 shouldRunNoop = false;
321 isRunning = Running::NOT_ANYMORE;
323 // Merge the lists of waiting tasks
324 if (!waitingObtainTasks.isEmpty()) {
325 ObtainSynchronizedMailboxTask *first = waitingObtainTasks.takeFirst();
326 dependentTasks.removeOne(first);
327 Q_ASSERT(first);
328 Q_ASSERT(first->keepTaskChild);
329 Q_ASSERT(first->keepTaskChild->synchronizeConn == first);
331 CHECK_TASK_TREE
332 // Update the parent information for the moved tasks
333 Q_FOREACH(ObtainSynchronizedMailboxTask *movedObtainTask, waitingObtainTasks) {
334 Q_ASSERT(movedObtainTask->parentTask);
335 movedObtainTask->parentTask->dependentTasks.removeOne(movedObtainTask);
336 movedObtainTask->parentTask = first->keepTaskChild;
337 first->keepTaskChild->dependentTasks.append(movedObtainTask);
339 CHECK_TASK_TREE
341 // And launch the replacement
342 first->keepTaskChild->waitingObtainTasks = waitingObtainTasks + first->keepTaskChild->waitingObtainTasks;
343 model->accessParser(parser).maintainingTask = first->keepTaskChild;
344 // make sure that if the SELECT dies uncleanly, such as with a missing [CLOSED], we get killed as well
345 connect(first->keepTaskChild, &ImapTask::failed, this, &KeepMailboxOpenTask::finalizeTermination);
346 first->keepTaskChild->slotPerformConnection();
347 } else {
348 Q_ASSERT(dependentTasks.isEmpty());
350 if (model->accessParser(parser).connState == CONN_STATE_SELECTING_WAIT_FOR_CLOSE) {
351 // we have to be kept busy, otherwise the responses which are still destined for *this* mailbox would
352 // get delivered to the new one
353 } else {
354 finalizeTermination();
356 CHECK_TASK_TREE
359 void KeepMailboxOpenTask::perform()
361 // FIXME: abort/die
363 Q_ASSERT(synchronizeConn);
364 Q_ASSERT(synchronizeConn->isFinished());
365 parser = synchronizeConn->parser;
366 synchronizeConn = 0; // will get deleted by Model
367 markAsActiveTask();
369 isRunning = Running::RUNNING;
370 fetchPartTimer->start();
371 fetchEnvelopeTimer->start();
373 if (!waitingObtainTasks.isEmpty()) {
374 shouldExit = true;
377 activateTasks();
379 if (model->accessParser(parser).capabilitiesFresh && model->accessParser(parser).capabilities.contains(QStringLiteral("IDLE"))) {
380 shouldRunIdle = true;
381 } else {
382 shouldRunNoop = true;
385 if (shouldRunNoop) {
386 noopTimer->start();
387 } else if (shouldRunIdle) {
388 idleLauncher = new IdleLauncher(this);
389 if (canRunIdleRightNow()) {
390 // There's no task yet, so we have to start IDLE now
391 idleLauncher->enterIdleLater();
396 void KeepMailboxOpenTask::resynchronizeMailbox()
398 // FIXME: abort/die
400 if (isRunning != Running::NOT_YET) {
401 // Instead of wild magic with re-creating synchronizeConn, it's way easier to
402 // just have us replaced by another KeepMailboxOpenTask
403 model->m_taskFactory->createKeepMailboxOpenTask(model, mailboxIndex, parser);
404 } else {
405 // We aren't running yet, which means that the sync hadn't happened yet, and therefore
406 // we don't have to do it "once again" -- it will happen automatically later on.
410 #define CHECK_IS_RUNNING \
411 switch (isRunning) { \
412 case Running::NOT_YET: \
413 return false; \
414 case Running::NOT_ANYMORE: \
415 /* OK, a lost reply -- we're already switching to another mailbox, and even though this might seem */ \
416 /* to be a safe change, we just cannot react to this right now :(. */ \
417 /* Also, don't eat further replies once we're dead :) */ \
418 return model->accessParser(parser).connState == CONN_STATE_SELECTING_WAIT_FOR_CLOSE; \
419 case Running::RUNNING: \
420 /* normal state -> handle this */ \
421 break; \
424 bool KeepMailboxOpenTask::handleNumberResponse(const Imap::Responses::NumberResponse *const resp)
426 if (_dead) {
427 _failed(tr("Asked to die"));
428 return true;
431 if (dieIfInvalidMailbox())
432 return true;
434 CHECK_IS_RUNNING
436 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
437 Q_ASSERT(mailbox);
438 TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(mailbox->m_children[0]);
439 Q_ASSERT(list);
440 // FIXME: tests!
441 if (resp->kind == Imap::Responses::EXPUNGE) {
442 mailbox->handleExpunge(model, *resp);
443 mailbox->syncState.setExists(mailbox->syncState.exists() - 1);
444 saveSyncStateNowOrLater(mailbox);
445 return true;
446 } else if (resp->kind == Imap::Responses::EXISTS) {
448 if (resp->number == static_cast<uint>(list->m_children.size())) {
449 // no changes
450 return true;
453 mailbox->handleExists(model, *resp);
455 breakOrCancelPossibleIdle();
457 Q_ASSERT(list->m_children.size());
458 uint highestKnownUid = 0;
459 for (int i = list->m_children.size() - 1; ! highestKnownUid && i >= 0; --i) {
460 highestKnownUid = static_cast<const TreeItemMessage *>(list->m_children[i])->uid();
461 //qDebug() << "UID disco: trying seq" << i << highestKnownUid;
463 breakOrCancelPossibleIdle();
464 newArrivalsFetch.append(parser->uidFetch(Sequence::startingAt(
465 // Did the UID walk return a usable number?
466 highestKnownUid ?
467 // Yes, we've got at least one message with a UID known -> ask for higher
468 // but don't forget to compensate for an pre-existing UIDNEXT value
469 qMax(mailbox->syncState.uidNext(), highestKnownUid + 1)
471 // No messages, or no messages with valid UID -> use the UIDNEXT from the syncing state
472 // but prevent a possible invalid 0:*
473 qMax(mailbox->syncState.uidNext(), 1u)
474 ), QList<QByteArray>() << "FLAGS"));
475 model->m_taskModel->slotTaskMighHaveChanged(this);
476 return true;
477 } else if (resp->kind == Imap::Responses::RECENT) {
478 mailbox->syncState.setRecent(resp->number);
479 list->m_recentMessageCount = resp->number;
480 model->emitMessageCountChanged(mailbox);
481 saveSyncStateNowOrLater(mailbox);
482 return true;
483 } else {
484 return false;
488 bool KeepMailboxOpenTask::handleVanished(const Responses::Vanished *const resp)
490 if (_dead) {
491 _failed(tr("Asked to die"));
492 return true;
495 if (dieIfInvalidMailbox())
496 return true;
498 CHECK_IS_RUNNING
500 if (resp->earlier != Responses::Vanished::NOT_EARLIER)
501 return false;
503 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
504 Q_ASSERT(mailbox);
506 mailbox->handleVanished(model, *resp);
507 saveSyncStateNowOrLater(mailbox);
508 return true;
511 bool KeepMailboxOpenTask::handleFetch(const Imap::Responses::Fetch *const resp)
513 if (dieIfInvalidMailbox())
514 return true;
516 if (_dead) {
517 _failed(tr("Asked to die"));
518 return true;
521 CHECK_IS_RUNNING
523 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
524 Q_ASSERT(mailbox);
525 model->genericHandleFetch(mailbox, resp);
526 return true;
529 void KeepMailboxOpenTask::slotPerformNoop()
531 // FIXME: abort/die
532 model->m_taskFactory->createNoopTask(model, this);
535 bool KeepMailboxOpenTask::handleStateHelper(const Imap::Responses::State *const resp)
537 // FIXME: abort/die
539 if (handleResponseCodeInsideState(resp))
540 return true;
542 // FIXME: checks for shouldExit and proper boundaries?
544 if (resp->respCode == Responses::CLOSED) {
545 switch (model->accessParser(parser).connState) {
546 case CONN_STATE_SELECTING:
547 case CONN_STATE_SELECTING_WAIT_FOR_CLOSE:
548 model->changeConnectionState(parser, CONN_STATE_SELECTING);
549 finalizeTermination();
550 break;
551 case CONN_STATE_LOGOUT:
552 finalizeTermination();
553 break;
554 default:
555 throw UnexpectedResponseReceived("No other mailbox is being selected, but got a [CLOSED] response", *resp);
559 if (resp->tag.isEmpty())
560 return false;
562 if (resp->tag == tagIdle) {
564 Q_ASSERT(idleLauncher);
565 if (resp->kind == Responses::OK) {
566 // The IDLE got terminated for whatever reason, so we should schedule its restart
567 idleLauncher->idleCommandCompleted();
568 if (canRunIdleRightNow()) {
569 idleLauncher->enterIdleLater();
571 } else {
572 // The IDLE command has failed. Let's assume it's a permanent error and don't request it in future.
573 log(QStringLiteral("The IDLE command has failed"));
574 shouldRunIdle = false;
575 idleLauncher->idleCommandFailed();
576 idleLauncher->deleteLater();
577 idleLauncher = 0;
579 tagIdle.clear();
580 // IDLE is special because it's not really a native Task. Therefore, we have to duplicate the check for its completion
581 // and possible termination request here.
582 // FIXME: maybe rewrite IDLE to be a native task and get all the benefits for free? Any drawbacks?
583 if (shouldExit && ! hasPendingInternalActions() && (! synchronizeConn || synchronizeConn->isFinished())) {
584 terminate();
586 return true;
587 } else if (newArrivalsFetch.contains(resp->tag)) {
588 newArrivalsFetch.removeOne(resp->tag);
590 if (newArrivalsFetch.isEmpty() && mailboxIndex.isValid()) {
591 // No pending commands for fetches of the mailbox state -> we have a consistent and accurate, up-to-date view
592 // -> we should save this
593 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
594 Q_ASSERT(mailbox);
595 mailbox->saveSyncStateAndUids(model);
598 if (resp->kind != Responses::OK) {
599 _failed(QLatin1String("FETCH of new arrivals failed: ") + resp->message);
601 // Don't forget to resume IDLE, if desired; that's easiest by simply behaving as if a "task" has just finished
602 slotTaskDeleted(0);
603 model->m_taskModel->slotTaskMighHaveChanged(this);
604 return true;
605 } else if (resp->tag == tagClose) {
606 tagClose.clear();
607 if (m_deleteCurrentMailboxTask) {
608 m_deleteCurrentMailboxTask->perform();
610 if (resp->kind != Responses::OK) {
611 _failed(QLatin1String("CLOSE failed: ") + resp->message);
613 terminate();
614 return true;
615 } else {
616 return false;
620 /** @short Reimplemented from ImapTask
622 This function's semantics is slightly shifted from ImapTask::abort(). It gets called when the KeepMailboxOpenTask has decided to
623 terminate and its biggest goals are to:
625 - Prevent any further activity from hitting this parser. We're here to "guard" access to it, and we're about to terminate, so the
626 tasks shall negotiate their access through some other KeepMailboxOpenTask.
627 - Terminate our internal code which might want to access the connection (NoopTask, IdleLauncher,...)
629 void KeepMailboxOpenTask::abort()
631 if (noopTimer)
632 noopTimer->stop();
633 if (idleLauncher)
634 idleLauncher->die();
636 detachFromMailbox();
638 _aborted = true;
639 // We do not want to propagate the signal to the child tasks, though -- the KeepMailboxOpenTask::abort() is used in the course
640 // of the regular "hey, free this connection and pass it to another KeepMailboxOpenTask" situations.
643 /** @short Stop working as a maintaining task */
644 void KeepMailboxOpenTask::detachFromMailbox()
646 if (mailboxIndex.isValid()) {
647 // Mark current mailbox as "orphaned by the housekeeping task"
648 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
649 Q_ASSERT(mailbox);
651 // We're already obsolete -> don't pretend to accept new tasks
652 if (mailbox->maintainingTask == this)
653 mailbox->maintainingTask = 0;
655 if (model->m_parsers.contains(parser) && model->accessParser(parser).maintainingTask == this) {
656 model->accessParser(parser).maintainingTask = 0;
660 /** @short Reimplemented from ImapTask
662 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!
664 void KeepMailboxOpenTask::die(const QString &message)
666 if (shouldExit) {
667 // OK, we're done, and getting killed. This is fine; just don't emit failed()
668 // because we aren't actually failing.
669 // This is a speciality of the KeepMailboxOpenTask because it's the only task
670 // this has a very long life.
671 _finished = true;
673 ImapTask::die(message);
674 detachFromMailbox();
677 /** @short Kill all pending tasks -- both the regular one and the replacement ObtainSynchronizedMailboxTask instances
679 Reimplemented from the ImapTask.
681 void KeepMailboxOpenTask::killAllPendingTasks(const QString &message)
683 Q_FOREACH(ImapTask *task, dependingTasksForThisMailbox) {
684 task->die(message);
686 Q_FOREACH(ImapTask *task, dependingTasksNoMailbox) {
687 task->die(message);
689 Q_FOREACH(ImapTask *task, waitingObtainTasks) {
690 task->die(message);
694 QString KeepMailboxOpenTask::debugIdentification() const
696 if (! mailboxIndex.isValid())
697 return QStringLiteral("[invalid mailboxIndex]");
699 TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
700 Q_ASSERT(mailbox);
701 return QStringLiteral("attached to %1%2%3").arg(mailbox->mailbox(),
702 (synchronizeConn && ! synchronizeConn->isFinished()) ? QStringLiteral(" [syncConn unfinished]") : QString(),
703 shouldExit ? QStringLiteral(" [shouldExit]") : QString()
707 /** @short The user wants us to go offline */
708 void KeepMailboxOpenTask::stopForLogout()
710 abort();
711 breakOrCancelPossibleIdle();
712 killAllPendingTasks(tr("Logging off..."));
714 // We're supposed to go offline. Given that we're a long-running task, I do not consider this a "failure".
715 // In particular, if the initial SELECT has not finished yet, the ObtainSynchronizedMailboxTask would get
716 // killed as well, and hence the mailboxSyncFailed() signal will get emitted.
717 // The worst thing which can possibly happen is that we're in the middle of checking the new arrivals.
718 // That's bad, because we've got unknown UIDs in our in-memory map, which is going to hurt during the next sync
719 // -- but that's something which should be handled elsewhere, IMHO.
720 // Therefore, make sure a subsequent call to die() doesn't propagate a failure.
721 shouldExit = true;
724 bool KeepMailboxOpenTask::handleFlags(const Imap::Responses::Flags *const resp)
726 if (dieIfInvalidMailbox())
727 return true;
729 // Well, there isn't much point in keeping track of these flags, but given that
730 // IMAP servers are happy to send these responses even after the initial sync, we
731 // better handle them explicitly here.
732 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
733 Q_ASSERT(mailbox);
734 mailbox->syncState.setFlags(resp->flags);
735 return true;
738 void KeepMailboxOpenTask::activateTasks()
740 // FIXME: abort/die
742 if (isRunning != Running::RUNNING)
743 return;
745 breakOrCancelPossibleIdle();
747 if (m_deleteCurrentMailboxTask) {
748 closeMailboxDestructively();
749 return;
752 slotFetchRequestedEnvelopes();
753 slotFetchRequestedParts();
755 while (!dependingTasksForThisMailbox.isEmpty() && model->accessParser(parser).activeTasks.size() < limitActiveTasks) {
756 breakOrCancelPossibleIdle();
757 ImapTask *task = dependingTasksForThisMailbox.takeFirst();
758 runningTasksForThisMailbox.append(task);
759 dependentTasks.removeOne(task);
760 task->perform();
762 while (!dependingTasksNoMailbox.isEmpty() && model->accessParser(parser).activeTasks.size() < limitActiveTasks) {
763 breakOrCancelPossibleIdle();
764 ImapTask *task = dependingTasksNoMailbox.takeFirst();
765 dependentTasks.removeOne(task);
766 task->perform();
769 if (idleLauncher && canRunIdleRightNow())
770 idleLauncher->enterIdleLater();
773 void KeepMailboxOpenTask::requestPartDownload(const uint uid, const QByteArray &partId, const uint estimatedSize)
775 requestedParts[uid].insert(partId);
776 requestedPartSizes[uid] += estimatedSize;
777 if (!fetchPartTimer->isActive()) {
778 fetchPartTimer->start();
782 void KeepMailboxOpenTask::requestEnvelopeDownload(const uint uid)
784 requestedEnvelopes.append(uid);
785 if (!fetchEnvelopeTimer->isActive()) {
786 fetchEnvelopeTimer->start();
790 void KeepMailboxOpenTask::slotFetchRequestedParts()
792 // FIXME: abort/die
794 if (requestedParts.isEmpty())
795 return;
797 breakOrCancelPossibleIdle();
799 auto it = requestedParts.begin();
800 auto parts = *it;
802 // When asked to exit, do as much as possible and die
803 while (shouldExit || fetchPartTasks.size() < limitParallelFetchTasks) {
804 Imap::Uids uids;
805 uint totalSize = 0;
806 while (uids.size() < limitMessagesAtOnce && it != requestedParts.end() && totalSize < limitBytesAtOnce) {
807 if (parts != *it)
808 break;
809 parts = *it;
810 uids << it.key();
811 totalSize += requestedPartSizes.take(it.key());
812 it = requestedParts.erase(it);
814 if (uids.isEmpty())
815 return;
817 fetchPartTasks << model->m_taskFactory->createFetchMsgPartTask(model, mailboxIndex, uids, parts.toList());
821 void KeepMailboxOpenTask::slotFetchRequestedEnvelopes()
823 // FIXME: abort/die
825 if (requestedEnvelopes.isEmpty())
826 return;
828 breakOrCancelPossibleIdle();
830 Imap::Uids fetchNow;
831 if (shouldExit) {
832 fetchNow = requestedEnvelopes;
833 requestedEnvelopes.clear();
834 } else {
835 const int amount = qMin(requestedEnvelopes.size(), limitMessagesAtOnce); // FIXME: add an extra limit?
836 fetchNow = requestedEnvelopes.mid(0, amount);
837 requestedEnvelopes.erase(requestedEnvelopes.begin(), requestedEnvelopes.begin() + amount);
839 fetchMetadataTasks << model->m_taskFactory->createFetchMsgMetadataTask(model, mailboxIndex, fetchNow);
842 void KeepMailboxOpenTask::breakOrCancelPossibleIdle()
844 if (idleLauncher) {
845 idleLauncher->finishIdle();
849 bool KeepMailboxOpenTask::handleResponseCodeInsideState(const Imap::Responses::State *const resp)
851 switch (resp->respCode) {
852 case Responses::UIDNEXT:
854 if (dieIfInvalidMailbox())
855 return resp->tag.isEmpty();
857 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
858 Q_ASSERT(mailbox);
859 const Responses::RespData<uint> *const num = dynamic_cast<const Responses::RespData<uint>* const>(resp->respCodeData.data());
860 if (num) {
861 mailbox->syncState.setUidNext(num->data);
862 saveSyncStateNowOrLater(mailbox);
863 // We shouldn't eat tagged responses from this context
864 return resp->tag.isEmpty();
865 } else {
866 throw CantHappen("State response has invalid UIDNEXT respCodeData", *resp);
868 break;
870 case Responses::PERMANENTFLAGS:
871 // Another useless one, but we want to consume it now to prevent a warning about
872 // an unhandled message
874 if (dieIfInvalidMailbox())
875 return resp->tag.isEmpty();
877 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
878 Q_ASSERT(mailbox);
879 const Responses::RespData<QStringList> *const num = dynamic_cast<const Responses::RespData<QStringList>* const>(resp->respCodeData.data());
880 if (num) {
881 mailbox->syncState.setPermanentFlags(num->data);
882 // We shouldn't eat tagged responses from this context
883 return resp->tag.isEmpty();
884 } else {
885 throw CantHappen("State response has invalid PERMANENTFLAGS respCodeData", *resp);
887 break;
889 case Responses::HIGHESTMODSEQ:
891 if (dieIfInvalidMailbox())
892 return resp->tag.isEmpty();
894 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
895 Q_ASSERT(mailbox);
896 const Responses::RespData<quint64> *const num = dynamic_cast<const Responses::RespData<quint64>* const>(resp->respCodeData.data());
897 Q_ASSERT(num);
898 mailbox->syncState.setHighestModSeq(num->data);
899 saveSyncStateNowOrLater(mailbox);
900 return resp->tag.isEmpty();
902 case Responses::UIDVALIDITY:
904 if (dieIfInvalidMailbox())
905 return resp->tag.isEmpty();
907 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
908 Q_ASSERT(mailbox);
909 const Responses::RespData<uint> *const num = dynamic_cast<const Responses::RespData<uint>* const>(resp->respCodeData.data());
910 Q_ASSERT(num);
911 if (mailbox->syncState.uidValidity() == num->data) {
912 // this is a harmless and useless message
913 return resp->tag.isEmpty();
914 } else {
915 // On the other hand, this a serious condition -- the server is telling us that the UIDVALIDITY has changed while
916 // a mailbox is open. There isn't much we could do here; having code for handling this gracefuly is just too much
917 // work for little to no benefit.
918 // The sane thing is to disconnect from this mailbox.
919 EMIT_LATER(model, imapError, Q_ARG(QString, tr("The UIDVALIDITY has changed while mailbox is open. Please reconnect.")));
920 model->setNetworkPolicy(NETWORK_OFFLINE);
921 return resp->tag.isEmpty();
924 default:
925 // Do nothing here
926 break;
928 return false;
931 void KeepMailboxOpenTask::slotUnselected()
933 switch (model->accessParser(parser).connState) {
934 case CONN_STATE_SYNCING:
935 case CONN_STATE_SELECTED:
936 case CONN_STATE_FETCHING_PART:
937 case CONN_STATE_FETCHING_MSG_METADATA:
938 model->changeConnectionState(parser, CONN_STATE_AUTHENTICATED);
939 break;
940 default:
941 // no need to do anything from here
942 break;
944 detachFromMailbox();
945 isRunning = Running::RUNNING; // WTF?
946 shouldExit = true;
947 _failed(tr("UNSELECTed"));
950 bool KeepMailboxOpenTask::dieIfInvalidMailbox()
952 if (mailboxIndex.isValid())
953 return false;
955 if (m_deleteCurrentMailboxTask) {
956 // The current mailbox was supposed to be deleted; don't try to UNSELECT from this context
957 return true;
960 // See ObtainSynchronizedMailboxTask::dieIfInvalidMailbox() for details
961 if (!unSelectTask && isRunning == Running::RUNNING) {
962 unSelectTask = model->m_taskFactory->createUnSelectTask(model, this);
963 connect(unSelectTask, &ImapTask::completed, this, &KeepMailboxOpenTask::slotUnselected);
964 unSelectTask->perform();
967 return true;
970 bool KeepMailboxOpenTask::hasPendingInternalActions() const
972 bool hasToWaitForIdleTermination = idleLauncher ? idleLauncher->waitingForIdleTaggedTermination() : false;
973 return !(dependingTasksForThisMailbox.isEmpty() && dependingTasksNoMailbox.isEmpty() && runningTasksForThisMailbox.isEmpty() &&
974 requestedParts.isEmpty() && requestedEnvelopes.isEmpty() && newArrivalsFetch.isEmpty()) || hasToWaitForIdleTermination;
977 /** @short Returns true if this task can be safely terminated
979 FIXME: document me
981 bool KeepMailboxOpenTask::isReadyToTerminate() const
983 return shouldExit && !hasPendingInternalActions() && (!synchronizeConn || synchronizeConn->isFinished());
986 /** @short Return true if we're configured to run IDLE and if there's no ongoing activity */
987 bool KeepMailboxOpenTask::canRunIdleRightNow() const
989 bool res = shouldRunIdle && dependingTasksForThisMailbox.isEmpty() &&
990 dependingTasksNoMailbox.isEmpty() && newArrivalsFetch.isEmpty();
992 // 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
993 // and that one more thing is a SortTask which is in the "just updating" mode.
994 // If that is the case, we can still allow further IDLE, that task will abort idling when it needs to.
995 // Nifty, isn't it?
996 if (model->accessParser(parser).activeTasks.size() > 1) {
997 if (model->accessParser(parser).activeTasks.size() == 2 &&
998 dynamic_cast<SortTask*>(model->accessParser(parser).activeTasks[1]) &&
999 dynamic_cast<SortTask*>(model->accessParser(parser).activeTasks[1])->isJustUpdatingNow()) {
1000 // This is OK, so no need to clear the "OK" flag
1001 } else {
1002 // Too bad, cannot IDLE
1003 res = false;
1007 if (!res)
1008 return false;
1010 Q_ASSERT(model->accessParser(parser).activeTasks.front() == this);
1011 return true;
1014 QVariant KeepMailboxOpenTask::taskData(const int role) const
1016 // FIXME
1017 Q_UNUSED(role);
1018 return QVariant();
1021 /** @short The specified task can be abort()ed when the mailbox shall be vacanted
1023 It's an error to call this on a task which we aren't tracking already.
1025 void KeepMailboxOpenTask::feelFreeToAbortCaller(ImapTask *task)
1027 abortableTasks.append(task);
1030 /** @short It's time to reset the counters and perform the sync */
1031 void KeepMailboxOpenTask::syncingTimeout()
1033 if (!mailboxIndex.isValid()) {
1034 return;
1036 if (m_skippedStateSynces == 0) {
1037 // This means that state syncing it not being throttled, i.e. we're just resetting the window
1038 // which determines the rate of requests which would normally trigger saving
1039 m_performedStateSynces = 0;
1040 } else {
1041 // there's been no fresh arrivals for our timeout period -> let's flush the pending events
1042 TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
1043 Q_ASSERT(mailbox);
1044 saveSyncStateIfPossible(mailbox);
1048 void KeepMailboxOpenTask::saveSyncStateNowOrLater(Imap::Mailbox::TreeItemMailbox *mailbox)
1050 bool saveImmediately = true;
1052 /** @short After processing so many responses immediately, switch to a delayed mode where the saving is deferred */
1053 const uint throttlingThreshold = 100;
1055 /** @short Flush the queue after postponing this number of messages.
1057 It's "ridiculously high", but it's still a number so that our integer newer wraps.
1059 const uint maxDelayedResponses = 10000;
1061 if (m_skippedStateSynces > 0) {
1062 // we're actively throttling
1063 if (m_skippedStateSynces >= maxDelayedResponses) {
1064 // ...but we've been throttling too many responses, let's flush them now
1065 Q_ASSERT(m_syncingTimer->isActive());
1066 // do not go back to 0, otherwise there will be a lot of immediate events within each interval
1067 m_performedStateSynces = throttlingThreshold;
1068 saveImmediately = true;
1069 } else {
1070 ++m_skippedStateSynces;
1071 saveImmediately = false;
1073 } else {
1074 // no throttling, cool
1075 if (m_performedStateSynces >= throttlingThreshold) {
1076 // ...but we should start throttling now
1077 ++m_skippedStateSynces;
1078 saveImmediately = false;
1079 } else {
1080 ++m_performedStateSynces;
1081 if (!m_syncingTimer->isActive()) {
1082 // reset the sliding window
1083 m_syncingTimer->start();
1085 saveImmediately = true;
1089 if (saveImmediately) {
1090 saveSyncStateIfPossible(mailbox);
1091 } else {
1092 m_performedStateSynces = 0;
1093 m_syncingTimer->start();
1097 void KeepMailboxOpenTask::saveSyncStateIfPossible(TreeItemMailbox *mailbox)
1099 m_skippedStateSynces = 0;
1100 TreeItemMsgList *list = static_cast<TreeItemMsgList*>(mailbox->m_children[0]);
1101 if (list->fetched()) {
1102 mailbox->saveSyncStateAndUids(model);
1103 } else {
1104 list->setFetchStatus(Imap::Mailbox::TreeItem::LOADING);
1108 void KeepMailboxOpenTask::closeMailboxDestructively()
1110 tagClose = parser->close();
1113 /** @short Is this task on its own keeping the connection busy?
1115 Right now, only fetching of new arrivals is being done in the context of this KeepMailboxOpenTask task.
1117 bool KeepMailboxOpenTask::hasItsOwnActivity() const
1119 return !newArrivalsFetch.isEmpty();
1122 /** @short Signal the final termination of this task */
1123 void KeepMailboxOpenTask::finalizeTermination()
1125 if (!_finished) {
1126 _finished = true;
1127 emit completed(this);
1129 CHECK_TASK_TREE;