File indexing completed on 2024-06-23 05:21:14

0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
0002 
0003    This file is part of the Trojita Qt IMAP e-mail client,
0004    http://trojita.flaska.net/
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include <sstream>
0024 #include "KeepMailboxOpenTask.h"
0025 #include "Common/InvokeMethod.h"
0026 #include "Imap/Model/ItemRoles.h"
0027 #include "Imap/Model/MailboxTree.h"
0028 #include "Imap/Model/Model.h"
0029 #include "Imap/Model/TaskFactory.h"
0030 #include "Imap/Model/TaskPresentationModel.h"
0031 #include "DeleteMailboxTask.h"
0032 #include "FetchMsgMetadataTask.h"
0033 #include "FetchMsgPartTask.h"
0034 #include "IdleLauncher.h"
0035 #include "OpenConnectionTask.h"
0036 #include "ObtainSynchronizedMailboxTask.h"
0037 #include "OfflineConnectionTask.h"
0038 #include "SortTask.h"
0039 #include "NoopTask.h"
0040 #include "UnSelectTask.h"
0041 
0042 namespace Imap
0043 {
0044 namespace Mailbox
0045 {
0046 
0047 KeepMailboxOpenTask::KeepMailboxOpenTask(Model *model, const QModelIndex &mailboxIndex, Parser *oldParser) :
0048     ImapTask(model), mailboxIndex(mailboxIndex), synchronizeConn(0), shouldExit(false), isRunning(Running::NOT_YET),
0049     shouldRunNoop(false), shouldRunIdle(false), idleLauncher(0), unSelectTask(0),
0050     m_skippedStateSynces(0), m_performedStateSynces(0), m_syncingTimer(nullptr)
0051 {
0052     Q_ASSERT(mailboxIndex.isValid());
0053     Q_ASSERT(mailboxIndex.model() == model);
0054     TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
0055     Q_ASSERT(mailbox);
0056 
0057     // Now make sure that we at least try to load data from the cache
0058     Q_ASSERT(mailbox->m_children.size() > 0);
0059     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList*>(mailbox->m_children[0]);
0060     Q_ASSERT(list);
0061     list->fetch(model);
0062 
0063     // We're the latest KeepMailboxOpenTask, so it makes a lot of sense to add us as the active
0064     // maintainingTask to the target mailbox
0065     mailbox->maintainingTask = this;
0066 
0067     if (oldParser) {
0068         // We're asked to re-use an existing connection. Let's see if there's something associated with it
0069 
0070         // We will use its parser, that's for sure already
0071         parser = oldParser;
0072 
0073         // Find if there's a KeepMailboxOpenTask already associated; if it is, we have to register with it
0074         if (model->accessParser(parser).maintainingTask) {
0075             // The parser looks busy -- some task is associated with it and has a mailbox open, so
0076             // let's just wait till we get a chance to play
0077             synchronizeConn = model->m_taskFactory->
0078                               createObtainSynchronizedMailboxTask(model, mailboxIndex, model->accessParser(oldParser).maintainingTask, this);
0079         } else if (model->accessParser(parser).connState < CONN_STATE_AUTHENTICATED) {
0080             // The parser is still in the process of being initialized, let's wait until it's completed
0081             Q_ASSERT(!model->accessParser(oldParser).activeTasks.isEmpty());
0082             ImapTask *task = model->accessParser(oldParser).activeTasks.front();
0083             synchronizeConn = model->m_taskFactory->createObtainSynchronizedMailboxTask(model, mailboxIndex, task, this);
0084         } else {
0085             // The parser is free, or at least there's no KeepMailboxOpenTask associated with it.
0086             // There's no mailbox besides us in the game, yet, so we can simply schedule us for immediate execution.
0087             synchronizeConn = model->m_taskFactory->createObtainSynchronizedMailboxTask(model, mailboxIndex, 0, this);
0088             // We'll also register with the model, so that all other KeepMailboxOpenTask which could get constructed in future
0089             // know about us and don't step on our toes.  This means that further KeepMailboxOpenTask which could possibly want
0090             // to use this connection will have to go through this task at first.
0091             model->accessParser(parser).maintainingTask = this;
0092             QTimer::singleShot(0, this, SLOT(slotPerformConnection()));
0093         }
0094 
0095         // We shall catch destruction of any preexisting tasks so that we can properly launch IDLE etc in response to their termination
0096         Q_FOREACH(ImapTask *task, model->accessParser(parser).activeTasks) {
0097             connect(task, &QObject::destroyed, this, &KeepMailboxOpenTask::slotTaskDeleted);
0098         }
0099     } else {
0100         ImapTask *conn = 0;
0101         if (model->networkPolicy() == NETWORK_OFFLINE) {
0102             // Well, except that we cannot really open a new connection now
0103             conn = new OfflineConnectionTask(model);
0104         } else {
0105             conn = model->m_taskFactory->createOpenConnectionTask(model);
0106         }
0107         parser = conn->parser;
0108         Q_ASSERT(parser);
0109         model->accessParser(parser).maintainingTask = this;
0110         synchronizeConn = model->m_taskFactory->createObtainSynchronizedMailboxTask(model, mailboxIndex, conn, this);
0111     }
0112 
0113     Q_ASSERT(synchronizeConn);
0114 
0115     // Setup the timer for NOOPing. It won't get started at this time, though.
0116     noopTimer = new QTimer(this);
0117     connect(noopTimer, &QTimer::timeout, this, &KeepMailboxOpenTask::slotPerformNoop);
0118     bool ok;
0119     int timeout = model->property("trojita-imap-noop-period").toUInt(&ok);
0120     if (! ok)
0121         timeout = 2 * 60 * 1000; // once every two minutes
0122     noopTimer->setInterval(timeout);
0123     noopTimer->setSingleShot(true);
0124 
0125     fetchPartTimer = new QTimer(this);
0126     connect(fetchPartTimer, &QTimer::timeout, this, &KeepMailboxOpenTask::slotFetchRequestedParts);
0127     timeout = model->property("trojita-imap-delayed-fetch-part").toUInt(&ok);
0128     if (! ok)
0129         timeout = 50;
0130     fetchPartTimer->setInterval(timeout);
0131     fetchPartTimer->setSingleShot(true);
0132 
0133     fetchEnvelopeTimer = new QTimer(this);
0134     connect(fetchEnvelopeTimer, &QTimer::timeout, this, &KeepMailboxOpenTask::slotFetchRequestedEnvelopes);
0135     fetchEnvelopeTimer->setInterval(0); // message metadata is pretty important, hence an immediate fetch
0136     fetchEnvelopeTimer->setSingleShot(true);
0137 
0138     limitBytesAtOnce = model->property("trojita-imap-limit-fetch-bytes-per-group").toUInt(&ok);
0139     if (! ok)
0140         limitBytesAtOnce = 1024 * 1024;
0141 
0142     limitMessagesAtOnce = model->property("trojita-imap-limit-fetch-messages-per-group").toInt(&ok);
0143     if (! ok)
0144         limitMessagesAtOnce = 300;
0145 
0146     limitParallelFetchTasks = model->property("trojita-imap-limit-parallel-fetch-tasks").toInt(&ok);
0147     if (! ok)
0148         limitParallelFetchTasks = 10;
0149 
0150     limitActiveTasks = model->property("trojita-imap-limit-active-tasks").toInt(&ok);
0151     if (! ok)
0152         limitActiveTasks = 100;
0153 
0154     CHECK_TASK_TREE
0155     emit model->mailboxSyncingProgress(mailboxIndex, STATE_WAIT_FOR_CONN);
0156 
0157     /** @short How often to reset the time window (in ms) for determining mass-change mode */
0158     const int throttlingWindowLength = 1000;
0159 
0160     m_syncingTimer = new QTimer(this);
0161     m_syncingTimer->setSingleShot(true);
0162     // This timeout specifies how long we're going to wait for an incoming reply which usually triggers state syncing.
0163     // If no such response arrives during this time window, the changes are saved on disk; if, however, something does
0164     // arrive, the rate of saving is only moderated based on the number of reponses which were already received,
0165     // but which did not result in state saving yet.
0166     m_syncingTimer->setInterval(throttlingWindowLength);
0167     connect(m_syncingTimer, &QTimer::timeout, this, &KeepMailboxOpenTask::syncingTimeout);
0168 }
0169 
0170 void KeepMailboxOpenTask::slotPerformConnection()
0171 {
0172     CHECK_TASK_TREE
0173     Q_ASSERT(synchronizeConn);
0174     Q_ASSERT(!synchronizeConn->isFinished());
0175     if (_dead) {
0176         _failed(tr("Asked to die"));
0177         synchronizeConn->die(QStringLiteral("KeepMailboxOpenTask died before the sync started"));
0178         return;
0179     }
0180 
0181     connect(synchronizeConn, &QObject::destroyed, this, &KeepMailboxOpenTask::slotTaskDeleted);
0182     synchronizeConn->perform();
0183 }
0184 
0185 void KeepMailboxOpenTask::addDependentTask(ImapTask *task)
0186 {
0187     CHECK_TASK_TREE
0188     Q_ASSERT(task);
0189 
0190     // FIXME: what about abort()/die() here?
0191 
0192     breakOrCancelPossibleIdle();
0193 
0194     DeleteMailboxTask *deleteTask = qobject_cast<DeleteMailboxTask*>(task);
0195     if (!deleteTask || deleteTask->mailbox != mailboxIndex.data(RoleMailboxName).toString()) {
0196         deleteTask = 0;
0197     }
0198 
0199     if (ObtainSynchronizedMailboxTask *obtainTask = qobject_cast<ObtainSynchronizedMailboxTask *>(task)) {
0200         // Another KeepMailboxOpenTask would like to replace us, so we shall die, eventually.
0201         // This branch is reimplemented from ImapTask
0202 
0203         dependentTasks.append(task);
0204         waitingObtainTasks.append(obtainTask);
0205         shouldExit = true;
0206         task->updateParentTask(this);
0207 
0208         // Before we can die, though, we have to accommodate fetch requests for all envelopes and parts queued so far.
0209         slotFetchRequestedEnvelopes();
0210         slotFetchRequestedParts();
0211 
0212         if (! hasPendingInternalActions() && (! synchronizeConn || synchronizeConn->isFinished())) {
0213             QTimer::singleShot(0, this, SLOT(terminate()));
0214         }
0215 
0216         Q_FOREACH(ImapTask *abortable, abortableTasks) {
0217             abortable->abort();
0218         }
0219     } else if (deleteTask) {
0220         // Got a request to delete the current mailbox. Fair enough, here we go!
0221 
0222         if (hasPendingInternalActions() || (synchronizeConn && !synchronizeConn->isFinished())) {
0223             // Hmm, this is bad -- the caller has instructed us to delete the current mailbox, but we still have
0224             // some pending actions (or have not even started yet). Better reject the request for deleting than lose some data.
0225             // Alternatively, we might pretend that we're a performance-oriented library and "optimize out" the
0226             // data transfer by deleting early :)
0227             deleteTask->mailboxHasPendingActions();
0228             return;
0229         }
0230 
0231         m_deleteCurrentMailboxTask = deleteTask;
0232         shouldExit = true;
0233         connect(task, &QObject::destroyed, this, &KeepMailboxOpenTask::slotTaskDeleted);
0234         ImapTask::addDependentTask(task);
0235         QTimer::singleShot(0, this, SLOT(slotActivateTasks()));
0236 
0237     } else {
0238         // This branch calls the inherited ImapTask::addDependentTask()
0239         connect(task, &QObject::destroyed, this, &KeepMailboxOpenTask::slotTaskDeleted);
0240         ImapTask::addDependentTask(task);
0241         if (task->needsMailbox()) {
0242             // it's a task which is tied to a particular mailbox
0243             dependingTasksForThisMailbox.append(task);
0244         } else {
0245             dependingTasksNoMailbox.append(task);
0246         }
0247         QTimer::singleShot(0, this, SLOT(slotActivateTasks()));
0248     }
0249 }
0250 
0251 void KeepMailboxOpenTask::slotTaskDeleted(QObject *object)
0252 {
0253     if (_finished)
0254         return;
0255 
0256     if (!model) {
0257         // we're very likely hitting this during some rather unclean destruction -> ignore this and die ASAP
0258         // See https://bugs.kde.org/show_bug.cgi?id=336090
0259         return;
0260     }
0261 
0262     if (!model->m_parsers.contains(parser)) {
0263         // The parser is gone; we have to get out of here ASAP
0264         _failed(tr("Parser is gone"));
0265         die(tr("Parser is gone"));
0266         return;
0267     }
0268     // FIXME: abort/die
0269 
0270     // Now, object is no longer an ImapTask*, as this gets emitted from inside QObject's destructor. However,
0271     // we can't use the passed pointer directly, and therefore we have to perform the cast here. It is safe
0272     // to do that here, as we're only interested in raw pointer value.
0273     if (object) {
0274         dependentTasks.removeOne(reinterpret_cast<ImapTask *>(object));
0275         dependingTasksForThisMailbox.removeOne(reinterpret_cast<ImapTask *>(object));
0276         dependingTasksNoMailbox.removeOne(reinterpret_cast<ImapTask *>(object));
0277         runningTasksForThisMailbox.removeOne(reinterpret_cast<ImapTask *>(object));
0278         fetchPartTasks.removeOne(reinterpret_cast<FetchMsgPartTask *>(object));
0279         fetchMetadataTasks.removeOne(reinterpret_cast<FetchMsgMetadataTask *>(object));
0280         abortableTasks.removeOne(reinterpret_cast<FetchMsgMetadataTask *>(object));
0281     }
0282 
0283     if (isReadyToTerminate()) {
0284         terminate();
0285     } else if (shouldRunNoop) {
0286         // A command just completed, and NOOPing is active, so let's schedule/postpone it again
0287         noopTimer->start();
0288     } else if (canRunIdleRightNow()) {
0289         // A command just completed and IDLE is supported, so let's queue/schedule/postpone it
0290         idleLauncher->enterIdleLater();
0291     }
0292     // It's possible that we can start more tasks at this time...
0293     activateTasks();
0294 }
0295 
0296 void KeepMailboxOpenTask::terminate()
0297 {
0298     if (_aborted) {
0299         // We've already been there, so we *cannot* proceed towards activating our replacement tasks
0300         return;
0301     }
0302     abort();
0303 
0304     m_syncingTimer->stop();
0305     syncingTimeout();
0306 
0307     // FIXME: abort/die
0308 
0309     Q_ASSERT(dependingTasksForThisMailbox.isEmpty());
0310     Q_ASSERT(dependingTasksNoMailbox.isEmpty());
0311     Q_ASSERT(requestedParts.isEmpty());
0312     Q_ASSERT(requestedEnvelopes.isEmpty());
0313     Q_ASSERT(runningTasksForThisMailbox.isEmpty());
0314     Q_ASSERT(abortableTasks.isEmpty());
0315     Q_ASSERT(!m_syncingTimer->isActive());
0316     Q_ASSERT(m_skippedStateSynces == 0);
0317 
0318     // Break periodic activities
0319     shouldRunIdle = false;
0320     shouldRunNoop = false;
0321     isRunning = Running::NOT_ANYMORE;
0322 
0323     // Merge the lists of waiting tasks
0324     if (!waitingObtainTasks.isEmpty()) {
0325         ObtainSynchronizedMailboxTask *first = waitingObtainTasks.takeFirst();
0326         dependentTasks.removeOne(first);
0327         Q_ASSERT(first);
0328         Q_ASSERT(first->keepTaskChild);
0329         Q_ASSERT(first->keepTaskChild->synchronizeConn == first);
0330 
0331         CHECK_TASK_TREE
0332         // Update the parent information for the moved tasks
0333         Q_FOREACH(ObtainSynchronizedMailboxTask *movedObtainTask, waitingObtainTasks) {
0334             Q_ASSERT(movedObtainTask->parentTask);
0335             movedObtainTask->parentTask->dependentTasks.removeOne(movedObtainTask);
0336             movedObtainTask->parentTask = first->keepTaskChild;
0337             first->keepTaskChild->dependentTasks.append(movedObtainTask);
0338         }
0339         CHECK_TASK_TREE
0340 
0341         // And launch the replacement
0342         first->keepTaskChild->waitingObtainTasks = waitingObtainTasks + first->keepTaskChild->waitingObtainTasks;
0343         model->accessParser(parser).maintainingTask = first->keepTaskChild;
0344         // make sure that if the SELECT dies uncleanly, such as with a missing [CLOSED], we get killed as well
0345         connect(first->keepTaskChild, &ImapTask::failed, this, &KeepMailboxOpenTask::finalizeTermination);
0346         first->keepTaskChild->slotPerformConnection();
0347     } else {
0348         Q_ASSERT(dependentTasks.isEmpty());
0349     }
0350     if (model->accessParser(parser).connState == CONN_STATE_SELECTING_WAIT_FOR_CLOSE) {
0351         // we have to be kept busy, otherwise the responses which are still destined for *this* mailbox would
0352         // get delivered to the new one
0353     } else {
0354         finalizeTermination();
0355     }
0356     CHECK_TASK_TREE
0357 }
0358 
0359 void KeepMailboxOpenTask::perform()
0360 {
0361     // FIXME: abort/die
0362 
0363     Q_ASSERT(synchronizeConn);
0364     Q_ASSERT(synchronizeConn->isFinished());
0365     parser = synchronizeConn->parser;
0366     synchronizeConn = 0; // will get deleted by Model
0367     markAsActiveTask();
0368 
0369     isRunning = Running::RUNNING;
0370     fetchPartTimer->start();
0371     fetchEnvelopeTimer->start();
0372 
0373     if (!waitingObtainTasks.isEmpty()) {
0374         shouldExit = true;
0375     }
0376 
0377     activateTasks();
0378 
0379     if (model->accessParser(parser).capabilitiesFresh && model->accessParser(parser).capabilities.contains(QStringLiteral("IDLE"))) {
0380         shouldRunIdle = true;
0381     } else {
0382         shouldRunNoop = true;
0383     }
0384 
0385     if (shouldRunNoop) {
0386         noopTimer->start();
0387     } else if (shouldRunIdle) {
0388         idleLauncher = new IdleLauncher(this);
0389         if (canRunIdleRightNow()) {
0390             // There's no task yet, so we have to start IDLE now
0391             idleLauncher->enterIdleLater();
0392         }
0393     }
0394 }
0395 
0396 void KeepMailboxOpenTask::resynchronizeMailbox()
0397 {
0398     // FIXME: abort/die
0399 
0400     if (isRunning != Running::NOT_YET) {
0401         // Instead of wild magic with re-creating synchronizeConn, it's way easier to
0402         // just have us replaced by another KeepMailboxOpenTask
0403         model->m_taskFactory->createKeepMailboxOpenTask(model, mailboxIndex, parser);
0404     } else {
0405         // We aren't running yet, which means that the sync hadn't happened yet, and therefore
0406         // we don't have to do it "once again" -- it will happen automatically later on.
0407     }
0408 }
0409 
0410 #define CHECK_IS_RUNNING \
0411     switch (isRunning) { \
0412     case Running::NOT_YET: \
0413         return false; \
0414     case Running::NOT_ANYMORE: \
0415         /* OK, a lost reply -- we're already switching to another mailbox, and even though this might seem */ \
0416         /* to be a safe change, we just cannot react to this right now :(. */ \
0417         /* Also, don't eat further replies once we're dead :) */ \
0418         return model->accessParser(parser).connState == CONN_STATE_SELECTING_WAIT_FOR_CLOSE; \
0419     case Running::RUNNING: \
0420         /* normal state -> handle this */ \
0421         break; \
0422     }
0423 
0424 bool KeepMailboxOpenTask::handleNumberResponse(const Imap::Responses::NumberResponse *const resp)
0425 {
0426     if (_dead) {
0427         _failed(tr("Asked to die"));
0428         return true;
0429     }
0430 
0431     if (dieIfInvalidMailbox())
0432         return true;
0433 
0434     CHECK_IS_RUNNING
0435 
0436     TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
0437     Q_ASSERT(mailbox);
0438     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(mailbox->m_children[0]);
0439     Q_ASSERT(list);
0440     // FIXME: tests!
0441     if (resp->kind == Imap::Responses::EXPUNGE) {
0442         mailbox->handleExpunge(model, *resp);
0443         mailbox->syncState.setExists(mailbox->syncState.exists() - 1);
0444         saveSyncStateNowOrLater(mailbox);
0445         return true;
0446     } else if (resp->kind == Imap::Responses::EXISTS) {
0447 
0448         if (resp->number == static_cast<uint>(list->m_children.size())) {
0449             // no changes
0450             return true;
0451         }
0452 
0453         mailbox->handleExists(model, *resp);
0454 
0455         breakOrCancelPossibleIdle();
0456 
0457         Q_ASSERT(list->m_children.size());
0458         uint highestKnownUid = 0;
0459         for (int i = list->m_children.size() - 1; ! highestKnownUid && i >= 0; --i) {
0460             highestKnownUid = static_cast<const TreeItemMessage *>(list->m_children[i])->uid();
0461             //qDebug() << "UID disco: trying seq" << i << highestKnownUid;
0462         }
0463         breakOrCancelPossibleIdle();
0464         newArrivalsFetch.append(parser->uidFetch(Sequence::startingAt(
0465                                                 // Did the UID walk return a usable number?
0466                                                 highestKnownUid ?
0467                                                 // Yes, we've got at least one message with a UID known -> ask for higher
0468                                                 // but don't forget to compensate for an pre-existing UIDNEXT value
0469                                                 qMax(mailbox->syncState.uidNext(), highestKnownUid + 1)
0470                                                 :
0471                                                 // No messages, or no messages with valid UID -> use the UIDNEXT from the syncing state
0472                                                 // but prevent a possible invalid 0:*
0473                                                 qMax(mailbox->syncState.uidNext(), 1u)
0474                                             ), QList<QByteArray>() << "FLAGS"));
0475         model->m_taskModel->slotTaskMighHaveChanged(this);
0476         return true;
0477     } else if (resp->kind == Imap::Responses::RECENT) {
0478         mailbox->syncState.setRecent(resp->number);
0479         list->m_recentMessageCount = resp->number;
0480         model->emitMessageCountChanged(mailbox);
0481         saveSyncStateNowOrLater(mailbox);
0482         return true;
0483     } else {
0484         return false;
0485     }
0486 }
0487 
0488 bool KeepMailboxOpenTask::handleVanished(const Responses::Vanished *const resp)
0489 {
0490     if (_dead) {
0491         _failed(tr("Asked to die"));
0492         return true;
0493     }
0494 
0495     if (dieIfInvalidMailbox())
0496         return true;
0497 
0498     CHECK_IS_RUNNING
0499 
0500     if (resp->earlier != Responses::Vanished::NOT_EARLIER)
0501         return false;
0502 
0503     TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
0504     Q_ASSERT(mailbox);
0505 
0506     mailbox->handleVanished(model, *resp);
0507     saveSyncStateNowOrLater(mailbox);
0508     return true;
0509 }
0510 
0511 bool KeepMailboxOpenTask::handleFetch(const Imap::Responses::Fetch *const resp)
0512 {
0513     if (dieIfInvalidMailbox())
0514         return true;
0515 
0516     if (_dead) {
0517         _failed(tr("Asked to die"));
0518         return true;
0519     }
0520 
0521     CHECK_IS_RUNNING
0522 
0523     TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
0524     Q_ASSERT(mailbox);
0525     model->genericHandleFetch(mailbox, resp);
0526     return true;
0527 }
0528 
0529 void KeepMailboxOpenTask::slotPerformNoop()
0530 {
0531     // FIXME: abort/die
0532     model->m_taskFactory->createNoopTask(model, this);
0533 }
0534 
0535 bool KeepMailboxOpenTask::handleStateHelper(const Imap::Responses::State *const resp)
0536 {
0537     // FIXME: abort/die
0538 
0539     if (handleResponseCodeInsideState(resp))
0540         return true;
0541 
0542     // FIXME: checks for shouldExit and proper boundaries?
0543 
0544     if (resp->respCode == Responses::CLOSED) {
0545         switch (model->accessParser(parser).connState) {
0546         case CONN_STATE_SELECTING:
0547         case CONN_STATE_SELECTING_WAIT_FOR_CLOSE:
0548             model->changeConnectionState(parser, CONN_STATE_SELECTING);
0549             finalizeTermination();
0550             break;
0551         case CONN_STATE_LOGOUT:
0552             finalizeTermination();
0553             break;
0554         default:
0555             throw UnexpectedResponseReceived("No other mailbox is being selected, but got a [CLOSED] response", *resp);
0556         }
0557     }
0558 
0559     if (resp->tag.isEmpty())
0560         return false;
0561 
0562     if (resp->tag == tagIdle) {
0563 
0564         Q_ASSERT(idleLauncher);
0565         if (resp->kind == Responses::OK) {
0566             // The IDLE got terminated for whatever reason, so we should schedule its restart
0567             idleLauncher->idleCommandCompleted();
0568             if (canRunIdleRightNow()) {
0569                 idleLauncher->enterIdleLater();
0570             }
0571         } else {
0572             // The IDLE command has failed. Let's assume it's a permanent error and don't request it in future.
0573             log(QStringLiteral("The IDLE command has failed"));
0574             shouldRunIdle = false;
0575             idleLauncher->idleCommandFailed();
0576             idleLauncher->deleteLater();
0577             idleLauncher = 0;
0578         }
0579         tagIdle.clear();
0580         // IDLE is special because it's not really a native Task. Therefore, we have to duplicate the check for its completion
0581         // and possible termination request here.
0582         // FIXME: maybe rewrite IDLE to be a native task and get all the benefits for free? Any drawbacks?
0583         if (shouldExit && ! hasPendingInternalActions() && (! synchronizeConn || synchronizeConn->isFinished())) {
0584             terminate();
0585         }
0586         return true;
0587     } else if (newArrivalsFetch.contains(resp->tag)) {
0588         newArrivalsFetch.removeOne(resp->tag);
0589 
0590         if (newArrivalsFetch.isEmpty() && mailboxIndex.isValid()) {
0591             // No pending commands for fetches of the mailbox state -> we have a consistent and accurate, up-to-date view
0592             // -> we should save this
0593             TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
0594             Q_ASSERT(mailbox);
0595             mailbox->saveSyncStateAndUids(model);
0596         }
0597 
0598         if (resp->kind != Responses::OK) {
0599             _failed(QLatin1String("FETCH of new arrivals failed: ") + resp->message);
0600         }
0601         // Don't forget to resume IDLE, if desired; that's easiest by simply behaving as if a "task" has just finished
0602         slotTaskDeleted(0);
0603         model->m_taskModel->slotTaskMighHaveChanged(this);
0604         return true;
0605     } else if (resp->tag == tagClose) {
0606         tagClose.clear();
0607         model->changeConnectionState(parser, CONN_STATE_AUTHENTICATED);
0608         if (m_deleteCurrentMailboxTask) {
0609             m_deleteCurrentMailboxTask->perform();
0610         }
0611         if (resp->kind != Responses::OK) {
0612             _failed(QLatin1String("CLOSE failed: ") + resp->message);
0613         }
0614         terminate();
0615         return true;
0616     } else {
0617         return false;
0618     }
0619 }
0620 
0621 /** @short Reimplemented from ImapTask
0622 
0623 This function's semantics is slightly shifted from ImapTask::abort(). It gets called when the KeepMailboxOpenTask has decided to
0624 terminate and its biggest goals are to:
0625 
0626 - Prevent any further activity from hitting this parser. We're here to "guard" access to it, and we're about to terminate, so the
0627   tasks shall negotiate their access through some other KeepMailboxOpenTask.
0628 - Terminate our internal code which might want to access the connection (NoopTask, IdleLauncher,...)
0629 */
0630 void KeepMailboxOpenTask::abort()
0631 {
0632     if (noopTimer)
0633         noopTimer->stop();
0634     if (idleLauncher)
0635         idleLauncher->die();
0636 
0637     detachFromMailbox();
0638 
0639     _aborted = true;
0640     // We do not want to propagate the signal to the child tasks, though -- the KeepMailboxOpenTask::abort() is used in the course
0641     // of the regular "hey, free this connection and pass it to another KeepMailboxOpenTask" situations.
0642 }
0643 
0644 /** @short Stop working as a maintaining task */
0645 void KeepMailboxOpenTask::detachFromMailbox()
0646 {
0647     if (mailboxIndex.isValid()) {
0648         // Mark current mailbox as "orphaned by the housekeeping task"
0649         TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
0650         Q_ASSERT(mailbox);
0651 
0652         // We're already obsolete -> don't pretend to accept new tasks
0653         if (mailbox->maintainingTask == this)
0654             mailbox->maintainingTask = 0;
0655     }
0656     if (model->m_parsers.contains(parser) && model->accessParser(parser).maintainingTask == this) {
0657         model->accessParser(parser).maintainingTask = 0;
0658     }
0659 }
0660 
0661 /** @short Reimplemented from ImapTask
0662 
0663 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!
0664 */
0665 void KeepMailboxOpenTask::die(const QString &message)
0666 {
0667     if (shouldExit) {
0668         // OK, we're done, and getting killed. This is fine; just don't emit failed()
0669         // because we aren't actually failing.
0670         // This is a speciality of the KeepMailboxOpenTask because it's the only task
0671         // this has a very long life.
0672         _finished = true;
0673     }
0674     ImapTask::die(message);
0675     detachFromMailbox();
0676 }
0677 
0678 /** @short Kill all pending tasks -- both the regular one and the replacement ObtainSynchronizedMailboxTask instances
0679 
0680 Reimplemented from the ImapTask.
0681 */
0682 void KeepMailboxOpenTask::killAllPendingTasks(const QString &message)
0683 {
0684     Q_FOREACH(ImapTask *task, dependingTasksForThisMailbox) {
0685         task->die(message);
0686     }
0687     Q_FOREACH(ImapTask *task, dependingTasksNoMailbox) {
0688         task->die(message);
0689     }
0690     Q_FOREACH(ImapTask *task, waitingObtainTasks) {
0691         task->die(message);
0692     }
0693 }
0694 
0695 QString KeepMailboxOpenTask::debugIdentification() const
0696 {
0697     if (! mailboxIndex.isValid())
0698         return QStringLiteral("[invalid mailboxIndex]");
0699 
0700     TreeItemMailbox *mailbox = dynamic_cast<TreeItemMailbox *>(static_cast<TreeItem *>(mailboxIndex.internalPointer()));
0701     Q_ASSERT(mailbox);
0702     return QStringLiteral("attached to %1%2%3").arg(mailbox->mailbox(),
0703             (synchronizeConn && ! synchronizeConn->isFinished()) ? QStringLiteral(" [syncConn unfinished]") : QString(),
0704             shouldExit ? QStringLiteral(" [shouldExit]") : QString()
0705                                                        );
0706 }
0707 
0708 /** @short The user wants us to go offline */
0709 void KeepMailboxOpenTask::stopForLogout()
0710 {
0711     abort();
0712     breakOrCancelPossibleIdle();
0713     killAllPendingTasks(tr("Logging off..."));
0714 
0715     // We're supposed to go offline. Given that we're a long-running task, I do not consider this a "failure".
0716     // In particular, if the initial SELECT has not finished yet, the ObtainSynchronizedMailboxTask would get
0717     // killed as well, and hence the mailboxSyncFailed() signal will get emitted.
0718     // The worst thing which can possibly happen is that we're in the middle of checking the new arrivals.
0719     // That's bad, because we've got unknown UIDs in our in-memory map, which is going to hurt during the next sync
0720     // -- but that's something which should be handled elsewhere, IMHO.
0721     // Therefore, make sure a subsequent call to die() doesn't propagate a failure.
0722     shouldExit = true;
0723 }
0724 
0725 bool KeepMailboxOpenTask::handleFlags(const Imap::Responses::Flags *const resp)
0726 {
0727     if (dieIfInvalidMailbox())
0728         return true;
0729 
0730     // Well, there isn't much point in keeping track of these flags, but given that
0731     // IMAP servers are happy to send these responses even after the initial sync, we
0732     // better handle them explicitly here.
0733     TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
0734     Q_ASSERT(mailbox);
0735     mailbox->syncState.setFlags(resp->flags);
0736     return true;
0737 }
0738 
0739 void KeepMailboxOpenTask::activateTasks()
0740 {
0741     // FIXME: abort/die
0742 
0743     if (isRunning != Running::RUNNING)
0744         return;
0745 
0746     breakOrCancelPossibleIdle();
0747 
0748     if (m_deleteCurrentMailboxTask) {
0749         closeMailboxDestructively();
0750         return;
0751     }
0752 
0753     slotFetchRequestedEnvelopes();
0754     slotFetchRequestedParts();
0755 
0756     while (!dependingTasksForThisMailbox.isEmpty() && model->accessParser(parser).activeTasks.size() < limitActiveTasks) {
0757         breakOrCancelPossibleIdle();
0758         ImapTask *task = dependingTasksForThisMailbox.takeFirst();
0759         runningTasksForThisMailbox.append(task);
0760         dependentTasks.removeOne(task);
0761         task->perform();
0762     }
0763     while (!dependingTasksNoMailbox.isEmpty() && model->accessParser(parser).activeTasks.size() < limitActiveTasks) {
0764         breakOrCancelPossibleIdle();
0765         ImapTask *task = dependingTasksNoMailbox.takeFirst();
0766         dependentTasks.removeOne(task);
0767         task->perform();
0768     }
0769 
0770     if (idleLauncher && canRunIdleRightNow())
0771         idleLauncher->enterIdleLater();
0772 }
0773 
0774 void KeepMailboxOpenTask::requestPartDownload(const uint uid, const QByteArray &partId, const uint estimatedSize)
0775 {
0776     requestedParts[uid].insert(partId);
0777     requestedPartSizes[uid] += estimatedSize;
0778     if (!fetchPartTimer->isActive()) {
0779         fetchPartTimer->start();
0780     }
0781 }
0782 
0783 void KeepMailboxOpenTask::requestEnvelopeDownload(const uint uid)
0784 {
0785     requestedEnvelopes.append(uid);
0786     if (!fetchEnvelopeTimer->isActive()) {
0787         fetchEnvelopeTimer->start();
0788     }
0789 }
0790 
0791 void KeepMailboxOpenTask::slotFetchRequestedParts()
0792 {
0793     // FIXME: abort/die
0794 
0795     if (requestedParts.isEmpty())
0796         return;
0797 
0798     breakOrCancelPossibleIdle();
0799 
0800     auto it = requestedParts.begin();
0801     auto parts = *it;
0802 
0803     // When asked to exit, do as much as possible and die
0804     while (shouldExit || fetchPartTasks.size() < limitParallelFetchTasks) {
0805         Imap::Uids uids;
0806         uint totalSize = 0;
0807         while (uids.size() < limitMessagesAtOnce && it != requestedParts.end() && totalSize < limitBytesAtOnce) {
0808             if (parts != *it)
0809                 break;
0810             parts = *it;
0811             uids << it.key();
0812             totalSize += requestedPartSizes.take(it.key());
0813             it = requestedParts.erase(it);
0814         }
0815         if (uids.isEmpty())
0816             return;
0817 
0818         fetchPartTasks << model->m_taskFactory->createFetchMsgPartTask(model, mailboxIndex, uids, parts.values());
0819     }
0820 }
0821 
0822 void KeepMailboxOpenTask::slotFetchRequestedEnvelopes()
0823 {
0824     // FIXME: abort/die
0825 
0826     if (requestedEnvelopes.isEmpty())
0827         return;
0828 
0829     breakOrCancelPossibleIdle();
0830 
0831     Imap::Uids fetchNow;
0832     if (shouldExit) {
0833         fetchNow = requestedEnvelopes;
0834         requestedEnvelopes.clear();
0835     } else {
0836         const int amount = qMin(requestedEnvelopes.size(), limitMessagesAtOnce); // FIXME: add an extra limit?
0837         fetchNow = requestedEnvelopes.mid(0, amount);
0838         requestedEnvelopes.erase(requestedEnvelopes.begin(), requestedEnvelopes.begin() + amount);
0839     }
0840     fetchMetadataTasks << model->m_taskFactory->createFetchMsgMetadataTask(model, mailboxIndex, fetchNow);
0841 }
0842 
0843 void KeepMailboxOpenTask::breakOrCancelPossibleIdle()
0844 {
0845     if (idleLauncher) {
0846         idleLauncher->finishIdle();
0847     }
0848 }
0849 
0850 bool KeepMailboxOpenTask::handleResponseCodeInsideState(const Imap::Responses::State *const resp)
0851 {
0852     switch (resp->respCode) {
0853     case Responses::UIDNEXT:
0854     {
0855         if (dieIfInvalidMailbox())
0856             return resp->tag.isEmpty();
0857 
0858         TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
0859         Q_ASSERT(mailbox);
0860         const Responses::RespData<uint> *const num = dynamic_cast<const Responses::RespData<uint>* const>(resp->respCodeData.data());
0861         if (num) {
0862             mailbox->syncState.setUidNext(num->data);
0863             saveSyncStateNowOrLater(mailbox);
0864             // We shouldn't eat tagged responses from this context
0865             return resp->tag.isEmpty();
0866         } else {
0867             throw CantHappen("State response has invalid UIDNEXT respCodeData", *resp);
0868         }
0869         break;
0870     }
0871     case Responses::PERMANENTFLAGS:
0872         // Another useless one, but we want to consume it now to prevent a warning about
0873         // an unhandled message
0874     {
0875         if (dieIfInvalidMailbox())
0876             return resp->tag.isEmpty();
0877 
0878         TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
0879         Q_ASSERT(mailbox);
0880         const Responses::RespData<QStringList> *const num = dynamic_cast<const Responses::RespData<QStringList>* const>(resp->respCodeData.data());
0881         if (num) {
0882             mailbox->syncState.setPermanentFlags(num->data);
0883             // We shouldn't eat tagged responses from this context
0884             return resp->tag.isEmpty();
0885         } else {
0886             throw CantHappen("State response has invalid PERMANENTFLAGS respCodeData", *resp);
0887         }
0888         break;
0889     }
0890     case Responses::HIGHESTMODSEQ:
0891     {
0892         if (dieIfInvalidMailbox())
0893             return resp->tag.isEmpty();
0894 
0895         TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
0896         Q_ASSERT(mailbox);
0897         const Responses::RespData<quint64> *const num = dynamic_cast<const Responses::RespData<quint64>* const>(resp->respCodeData.data());
0898         Q_ASSERT(num);
0899         mailbox->syncState.setHighestModSeq(num->data);
0900         saveSyncStateNowOrLater(mailbox);
0901         return resp->tag.isEmpty();
0902     }
0903     case Responses::UIDVALIDITY:
0904     {
0905         if (dieIfInvalidMailbox())
0906             return resp->tag.isEmpty();
0907 
0908         TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
0909         Q_ASSERT(mailbox);
0910         const Responses::RespData<uint> *const num = dynamic_cast<const Responses::RespData<uint>* const>(resp->respCodeData.data());
0911         Q_ASSERT(num);
0912         if (mailbox->syncState.uidValidity() == num->data) {
0913             // this is a harmless and useless message
0914             return resp->tag.isEmpty();
0915         } else {
0916             // On the other hand, this a serious condition -- the server is telling us that the UIDVALIDITY has changed while
0917             // a mailbox is open. There isn't much we could do here; having code for handling this gracefuly is just too much
0918             // work for little to no benefit.
0919             // The sane thing is to disconnect from this mailbox.
0920             EMIT_LATER(model, imapError, Q_ARG(QString, tr("The UIDVALIDITY has changed while mailbox is open. Please reconnect.")));
0921             model->setNetworkPolicy(NETWORK_OFFLINE);
0922             return resp->tag.isEmpty();
0923         }
0924     }
0925     default:
0926         // Do nothing here
0927         break;
0928     }
0929     return false;
0930 }
0931 
0932 void KeepMailboxOpenTask::slotUnselected()
0933 {
0934     switch (model->accessParser(parser).connState) {
0935     case CONN_STATE_SYNCING:
0936     case CONN_STATE_SELECTED:
0937     case CONN_STATE_FETCHING_PART:
0938     case CONN_STATE_FETCHING_MSG_METADATA:
0939         model->changeConnectionState(parser, CONN_STATE_AUTHENTICATED);
0940         break;
0941     default:
0942         // no need to do anything from here
0943         break;
0944     }
0945     detachFromMailbox();
0946     isRunning = Running::RUNNING; // WTF?
0947     shouldExit = true;
0948     _failed(tr("UNSELECTed"));
0949 }
0950 
0951 bool KeepMailboxOpenTask::dieIfInvalidMailbox()
0952 {
0953     if (mailboxIndex.isValid())
0954         return false;
0955 
0956     if (m_deleteCurrentMailboxTask) {
0957         // The current mailbox was supposed to be deleted; don't try to UNSELECT from this context
0958         return true;
0959     }
0960 
0961     // See ObtainSynchronizedMailboxTask::dieIfInvalidMailbox() for details
0962     if (!unSelectTask && isRunning == Running::RUNNING) {
0963         unSelectTask = model->m_taskFactory->createUnSelectTask(model, this);
0964         connect(unSelectTask, &ImapTask::completed, this, &KeepMailboxOpenTask::slotUnselected);
0965         unSelectTask->perform();
0966     }
0967 
0968     return true;
0969 }
0970 
0971 bool KeepMailboxOpenTask::hasPendingInternalActions() const
0972 {
0973     bool hasToWaitForIdleTermination = idleLauncher ? idleLauncher->waitingForIdleTaggedTermination() : false;
0974     return !(dependingTasksForThisMailbox.isEmpty() && dependingTasksNoMailbox.isEmpty() && runningTasksForThisMailbox.isEmpty() &&
0975              requestedParts.isEmpty() && requestedEnvelopes.isEmpty() && newArrivalsFetch.isEmpty()) || hasToWaitForIdleTermination;
0976 }
0977 
0978 /** @short Returns true if this task can be safely terminated
0979 
0980 FIXME: document me
0981 */
0982 bool KeepMailboxOpenTask::isReadyToTerminate() const
0983 {
0984     return shouldExit && !hasPendingInternalActions() && (!synchronizeConn || synchronizeConn->isFinished());
0985 }
0986 
0987 /** @short Return true if we're configured to run IDLE and if there's no ongoing activity */
0988 bool KeepMailboxOpenTask::canRunIdleRightNow() const
0989 {
0990     bool res = shouldRunIdle && dependingTasksForThisMailbox.isEmpty() &&
0991             dependingTasksNoMailbox.isEmpty() && newArrivalsFetch.isEmpty();
0992 
0993     // 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
0994     // and that one more thing is a SortTask which is in the "just updating" mode.
0995     // If that is the case, we can still allow further IDLE, that task will abort idling when it needs to.
0996     // Nifty, isn't it?
0997     if (model->accessParser(parser).activeTasks.size() > 1) {
0998         if (model->accessParser(parser).activeTasks.size() == 2 &&
0999                 dynamic_cast<SortTask*>(model->accessParser(parser).activeTasks[1]) &&
1000                 dynamic_cast<SortTask*>(model->accessParser(parser).activeTasks[1])->isJustUpdatingNow()) {
1001             // This is OK, so no need to clear the "OK" flag
1002         } else {
1003             // Too bad, cannot IDLE
1004             res = false;
1005         }
1006     }
1007 
1008     if (!res)
1009         return false;
1010 
1011     Q_ASSERT(model->accessParser(parser).activeTasks.front() == this);
1012     return true;
1013 }
1014 
1015 QVariant KeepMailboxOpenTask::taskData(const int role) const
1016 {
1017     // FIXME
1018     Q_UNUSED(role);
1019     return QVariant();
1020 }
1021 
1022 /** @short The specified task can be abort()ed when the mailbox shall be vacanted
1023 
1024 It's an error to call this on a task which we aren't tracking already.
1025 */
1026 void KeepMailboxOpenTask::feelFreeToAbortCaller(ImapTask *task)
1027 {
1028     abortableTasks.append(task);
1029 }
1030 
1031 /** @short It's time to reset the counters and perform the sync */
1032 void KeepMailboxOpenTask::syncingTimeout()
1033 {
1034     if (!mailboxIndex.isValid()) {
1035         return;
1036     }
1037     if (m_skippedStateSynces == 0) {
1038         // This means that state syncing it not being throttled, i.e. we're just resetting the window
1039         // which determines the rate of requests which would normally trigger saving
1040         m_performedStateSynces = 0;
1041     } else {
1042         // there's been no fresh arrivals for our timeout period -> let's flush the pending events
1043         TreeItemMailbox *mailbox = Model::mailboxForSomeItem(mailboxIndex);
1044         Q_ASSERT(mailbox);
1045         saveSyncStateIfPossible(mailbox);
1046     }
1047 }
1048 
1049 void KeepMailboxOpenTask::saveSyncStateNowOrLater(Imap::Mailbox::TreeItemMailbox *mailbox)
1050 {
1051     bool saveImmediately = true;
1052 
1053     /** @short After processing so many responses immediately, switch to a delayed mode where the saving is deferred */
1054     const uint throttlingThreshold = 100;
1055 
1056     /** @short Flush the queue after postponing this number of messages.
1057 
1058     It's "ridiculously high", but it's still a number so that our integer newer wraps.
1059     */
1060     const uint maxDelayedResponses = 10000;
1061 
1062     if (m_skippedStateSynces > 0) {
1063         // we're actively throttling
1064         if (m_skippedStateSynces >= maxDelayedResponses) {
1065             // ...but we've been throttling too many responses, let's flush them now
1066             Q_ASSERT(m_syncingTimer->isActive());
1067             // do not go back to 0, otherwise there will be a lot of immediate events within each interval
1068             m_performedStateSynces = throttlingThreshold;
1069             saveImmediately = true;
1070         } else {
1071             ++m_skippedStateSynces;
1072             saveImmediately = false;
1073         }
1074     } else {
1075         // no throttling, cool
1076         if (m_performedStateSynces >= throttlingThreshold) {
1077             // ...but we should start throttling now
1078             ++m_skippedStateSynces;
1079             saveImmediately = false;
1080         } else {
1081             ++m_performedStateSynces;
1082             if (!m_syncingTimer->isActive()) {
1083                 // reset the sliding window
1084                 m_syncingTimer->start();
1085             }
1086             saveImmediately = true;
1087         }
1088     }
1089 
1090     if (saveImmediately) {
1091         saveSyncStateIfPossible(mailbox);
1092     } else {
1093         m_performedStateSynces = 0;
1094         m_syncingTimer->start();
1095     }
1096 }
1097 
1098 void KeepMailboxOpenTask::saveSyncStateIfPossible(TreeItemMailbox *mailbox)
1099 {
1100     m_skippedStateSynces = 0;
1101     TreeItemMsgList *list = static_cast<TreeItemMsgList*>(mailbox->m_children[0]);
1102     if (list->fetched()) {
1103         mailbox->saveSyncStateAndUids(model);
1104     } else {
1105         list->setFetchStatus(Imap::Mailbox::TreeItem::LOADING);
1106     }
1107 }
1108 
1109 void KeepMailboxOpenTask::closeMailboxDestructively()
1110 {
1111     tagClose = parser->close();
1112 }
1113 
1114 /** @short Is this task on its own keeping the connection busy?
1115 
1116 Right now, only fetching of new arrivals is being done in the context of this KeepMailboxOpenTask task.
1117 */
1118 bool KeepMailboxOpenTask::hasItsOwnActivity() const
1119 {
1120     return !newArrivalsFetch.isEmpty();
1121 }
1122 
1123 /** @short Signal the final termination of this task */
1124 void KeepMailboxOpenTask::finalizeTermination()
1125 {
1126     if (!_finished) {
1127         _finished = true;
1128         emit completed(this);
1129     }
1130     CHECK_TASK_TREE;
1131 }
1132 
1133 }
1134 }