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 }