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/>.
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"
40 #include "UnSelectTask.h"
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()));
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]);
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;
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
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);
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
);
101 if (model
->networkPolicy() == NETWORK_OFFLINE
) {
102 // Well, except that we cannot really open a new connection now
103 conn
= new OfflineConnectionTask(model
);
105 conn
= model
->m_taskFactory
->createOpenConnectionTask(model
);
107 parser
= conn
->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
);
119 int timeout
= model
->property("trojita-imap-noop-period").toUInt(&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
);
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
);
140 limitBytesAtOnce
= 1024 * 1024;
142 limitMessagesAtOnce
= model
->property("trojita-imap-limit-fetch-messages-per-group").toInt(&ok
);
144 limitMessagesAtOnce
= 300;
146 limitParallelFetchTasks
= model
->property("trojita-imap-limit-parallel-fetch-tasks").toInt(&ok
);
148 limitParallelFetchTasks
= 10;
150 limitActiveTasks
= model
->property("trojita-imap-limit-active-tasks").toInt(&ok
);
152 limitActiveTasks
= 100;
155 emit model
->mailboxSyncingProgress(mailboxIndex
, STATE_WAIT_FOR_CONN
);
157 connect(this, &ImapTask::failed
, this, &KeepMailboxOpenTask::signalSyncFailure
);
159 /** @short How often to reset the time window (in ms) for determining mass-change mode */
160 const int throttlingWindowLength
= 1000;
162 m_syncingTimer
= new QTimer(this);
163 m_syncingTimer
->setSingleShot(true);
164 // This timeout specifies how long we're going to wait for an incoming reply which usually triggers state syncing.
165 // If no such response arrives during this time window, the changes are saved on disk; if, however, something does
166 // arrive, the rate of saving is only moderated based on the number of reponses which were already received,
167 // but which did not result in state saving yet.
168 m_syncingTimer
->setInterval(throttlingWindowLength
);
169 connect(m_syncingTimer
, &QTimer::timeout
, this, &KeepMailboxOpenTask::syncingTimeout
);
172 void KeepMailboxOpenTask::slotPerformConnection()
175 Q_ASSERT(synchronizeConn
);
176 Q_ASSERT(!synchronizeConn
->isFinished());
178 _failed(tr("Asked to die"));
179 synchronizeConn
->die(QStringLiteral("KeepMailboxOpenTask died before the sync started"));
183 connect(synchronizeConn
, &QObject::destroyed
, this, &KeepMailboxOpenTask::slotTaskDeleted
);
184 synchronizeConn
->perform();
187 void KeepMailboxOpenTask::addDependentTask(ImapTask
*task
)
192 // FIXME: what about abort()/die() here?
194 breakOrCancelPossibleIdle();
196 DeleteMailboxTask
*deleteTask
= qobject_cast
<DeleteMailboxTask
*>(task
);
197 if (!deleteTask
|| deleteTask
->mailbox
!= mailboxIndex
.data(RoleMailboxName
).toString()) {
201 if (ObtainSynchronizedMailboxTask
*obtainTask
= qobject_cast
<ObtainSynchronizedMailboxTask
*>(task
)) {
202 // Another KeepMailboxOpenTask would like to replace us, so we shall die, eventually.
203 // This branch is reimplemented from ImapTask
205 dependentTasks
.append(task
);
206 waitingObtainTasks
.append(obtainTask
);
208 task
->updateParentTask(this);
210 // Before we can die, though, we have to accommodate fetch requests for all envelopes and parts queued so far.
211 slotFetchRequestedEnvelopes();
212 slotFetchRequestedParts();
214 if (! hasPendingInternalActions() && (! synchronizeConn
|| synchronizeConn
->isFinished())) {
215 QTimer::singleShot(0, this, SLOT(terminate()));
218 Q_FOREACH(ImapTask
*abortable
, abortableTasks
) {
221 } else if (deleteTask
) {
222 // Got a request to delete the current mailbox. Fair enough, here we go!
224 if (hasPendingInternalActions() || (synchronizeConn
&& !synchronizeConn
->isFinished())) {
225 // Hmm, this is bad -- the caller has instructed us to delete the current mailbox, but we still have
226 // some pending actions (or have not even started yet). Better reject the request for deleting than lose some data.
227 // Alternatively, we might pretend that we're a performance-oriented library and "optimize out" the
228 // data transfer by deleting early :)
229 deleteTask
->mailboxHasPendingActions();
233 m_deleteCurrentMailboxTask
= deleteTask
;
235 connect(task
, &QObject::destroyed
, this, &KeepMailboxOpenTask::slotTaskDeleted
);
236 ImapTask::addDependentTask(task
);
237 QTimer::singleShot(0, this, SLOT(slotActivateTasks()));
240 // This branch calls the inherited ImapTask::addDependentTask()
241 connect(task
, &QObject::destroyed
, this, &KeepMailboxOpenTask::slotTaskDeleted
);
242 ImapTask::addDependentTask(task
);
243 if (task
->needsMailbox()) {
244 // it's a task which is tied to a particular mailbox
245 dependingTasksForThisMailbox
.append(task
);
247 dependingTasksNoMailbox
.append(task
);
249 QTimer::singleShot(0, this, SLOT(slotActivateTasks()));
253 void KeepMailboxOpenTask::slotTaskDeleted(QObject
*object
)
259 // we're very likely hitting this during some rather unclean destruction -> ignore this and die ASAP
260 // See https://bugs.kde.org/show_bug.cgi?id=336090
264 if (!model
->m_parsers
.contains(parser
)) {
265 // The parser is gone; we have to get out of here ASAP
266 _failed(tr("Parser is gone"));
267 die(tr("Parser is gone"));
272 // Now, object is no longer an ImapTask*, as this gets emitted from inside QObject's destructor. However,
273 // we can't use the passed pointer directly, and therefore we have to perform the cast here. It is safe
274 // to do that here, as we're only interested in raw pointer value.
276 dependentTasks
.removeOne(static_cast<ImapTask
*>(object
));
277 dependingTasksForThisMailbox
.removeOne(static_cast<ImapTask
*>(object
));
278 dependingTasksNoMailbox
.removeOne(static_cast<ImapTask
*>(object
));
279 runningTasksForThisMailbox
.removeOne(static_cast<ImapTask
*>(object
));
280 fetchPartTasks
.removeOne(static_cast<FetchMsgPartTask
*>(object
));
281 fetchMetadataTasks
.removeOne(static_cast<FetchMsgMetadataTask
*>(object
));
282 abortableTasks
.removeOne(static_cast<FetchMsgMetadataTask
*>(object
));
285 if (isReadyToTerminate()) {
287 } else if (shouldRunNoop
) {
288 // A command just completed, and NOOPing is active, so let's schedule/postpone it again
290 } else if (canRunIdleRightNow()) {
291 // A command just completed and IDLE is supported, so let's queue/schedule/postpone it
292 idleLauncher
->enterIdleLater();
294 // It's possible that we can start more tasks at this time...
298 void KeepMailboxOpenTask::terminate()
301 // We've already been there, so we *cannot* proceed towards activating our replacement tasks
306 m_syncingTimer
->stop();
311 Q_ASSERT(dependingTasksForThisMailbox
.isEmpty());
312 Q_ASSERT(dependingTasksNoMailbox
.isEmpty());
313 Q_ASSERT(requestedParts
.isEmpty());
314 Q_ASSERT(requestedEnvelopes
.isEmpty());
315 Q_ASSERT(runningTasksForThisMailbox
.isEmpty());
316 Q_ASSERT(abortableTasks
.isEmpty());
317 Q_ASSERT(!m_syncingTimer
->isActive());
318 Q_ASSERT(m_skippedStateSynces
== 0);
320 // Break periodic activities
321 shouldRunIdle
= false;
322 shouldRunNoop
= false;
323 isRunning
= Running::NOT_ANYMORE
;
325 // Merge the lists of waiting tasks
326 if (!waitingObtainTasks
.isEmpty()) {
327 ObtainSynchronizedMailboxTask
*first
= waitingObtainTasks
.takeFirst();
328 dependentTasks
.removeOne(first
);
330 Q_ASSERT(first
->keepTaskChild
);
331 Q_ASSERT(first
->keepTaskChild
->synchronizeConn
== first
);
334 // Update the parent information for the moved tasks
335 Q_FOREACH(ObtainSynchronizedMailboxTask
*movedObtainTask
, waitingObtainTasks
) {
336 Q_ASSERT(movedObtainTask
->parentTask
);
337 movedObtainTask
->parentTask
->dependentTasks
.removeOne(movedObtainTask
);
338 movedObtainTask
->parentTask
= first
->keepTaskChild
;
339 first
->keepTaskChild
->dependentTasks
.append(movedObtainTask
);
343 // And launch the replacement
344 first
->keepTaskChild
->waitingObtainTasks
= waitingObtainTasks
+ first
->keepTaskChild
->waitingObtainTasks
;
345 model
->accessParser(parser
).maintainingTask
= first
->keepTaskChild
;
346 // make sure that if the SELECT dies uncleanly, such as with a missing [CLOSED], we get killed as well
347 connect(first
->keepTaskChild
, &ImapTask::failed
, this, &KeepMailboxOpenTask::finalizeTermination
);
348 first
->keepTaskChild
->slotPerformConnection();
350 Q_ASSERT(dependentTasks
.isEmpty());
352 if (model
->accessParser(parser
).connState
== CONN_STATE_SELECTING_WAIT_FOR_CLOSE
) {
353 // we have to be kept busy, otherwise the responses which are still destined for *this* mailbox would
354 // get delivered to the new one
356 finalizeTermination();
361 void KeepMailboxOpenTask::perform()
365 Q_ASSERT(synchronizeConn
);
366 Q_ASSERT(synchronizeConn
->isFinished());
367 parser
= synchronizeConn
->parser
;
368 synchronizeConn
= 0; // will get deleted by Model
371 isRunning
= Running::RUNNING
;
372 fetchPartTimer
->start();
373 fetchEnvelopeTimer
->start();
375 if (!waitingObtainTasks
.isEmpty()) {
381 if (model
->accessParser(parser
).capabilitiesFresh
&& model
->accessParser(parser
).capabilities
.contains(QStringLiteral("IDLE"))) {
382 shouldRunIdle
= true;
384 shouldRunNoop
= true;
389 } else if (shouldRunIdle
) {
390 idleLauncher
= new IdleLauncher(this);
391 if (canRunIdleRightNow()) {
392 // There's no task yet, so we have to start IDLE now
393 idleLauncher
->enterIdleLater();
398 void KeepMailboxOpenTask::resynchronizeMailbox()
402 if (isRunning
!= Running::NOT_YET
) {
403 // Instead of wild magic with re-creating synchronizeConn, it's way easier to
404 // just have us replaced by another KeepMailboxOpenTask
405 model
->m_taskFactory
->createKeepMailboxOpenTask(model
, mailboxIndex
, parser
);
407 // We aren't running yet, which means that the sync hadn't happened yet, and therefore
408 // we don't have to do it "once again" -- it will happen automatically later on.
412 #define CHECK_IS_RUNNING \
413 switch (isRunning) { \
414 case Running::NOT_YET: \
416 case Running::NOT_ANYMORE: \
417 /* OK, a lost reply -- we're already switching to another mailbox, and even though this might seem */ \
418 /* to be a safe change, we just cannot react to this right now :(. */ \
419 /* Also, don't eat further replies once we're dead :) */ \
420 return model->accessParser(parser).connState == CONN_STATE_SELECTING_WAIT_FOR_CLOSE; \
421 case Running::RUNNING: \
422 /* normal state -> handle this */ \
426 bool KeepMailboxOpenTask::handleNumberResponse(const Imap::Responses::NumberResponse
*const resp
)
429 _failed(tr("Asked to die"));
433 if (dieIfInvalidMailbox())
438 TreeItemMailbox
*mailbox
= Model::mailboxForSomeItem(mailboxIndex
);
440 TreeItemMsgList
*list
= dynamic_cast<TreeItemMsgList
*>(mailbox
->m_children
[0]);
443 if (resp
->kind
== Imap::Responses::EXPUNGE
) {
444 mailbox
->handleExpunge(model
, *resp
);
445 mailbox
->syncState
.setExists(mailbox
->syncState
.exists() - 1);
446 saveSyncStateNowOrLater(mailbox
);
448 } else if (resp
->kind
== Imap::Responses::EXISTS
) {
450 if (resp
->number
== static_cast<uint
>(list
->m_children
.size())) {
455 mailbox
->handleExists(model
, *resp
);
457 breakOrCancelPossibleIdle();
459 Q_ASSERT(list
->m_children
.size());
460 uint highestKnownUid
= 0;
461 for (int i
= list
->m_children
.size() - 1; ! highestKnownUid
&& i
>= 0; --i
) {
462 highestKnownUid
= static_cast<const TreeItemMessage
*>(list
->m_children
[i
])->uid();
463 //qDebug() << "UID disco: trying seq" << i << highestKnownUid;
465 breakOrCancelPossibleIdle();
466 newArrivalsFetch
.append(parser
->uidFetch(Sequence::startingAt(
467 // Did the UID walk return a usable number?
469 // Yes, we've got at least one message with a UID known -> ask for higher
470 // but don't forget to compensate for an pre-existing UIDNEXT value
471 qMax(mailbox
->syncState
.uidNext(), highestKnownUid
+ 1)
473 // No messages, or no messages with valid UID -> use the UIDNEXT from the syncing state
474 // but prevent a possible invalid 0:*
475 qMax(mailbox
->syncState
.uidNext(), 1u)
476 ), QList
<QByteArray
>() << "FLAGS"));
477 model
->m_taskModel
->slotTaskMighHaveChanged(this);
479 } else if (resp
->kind
== Imap::Responses::RECENT
) {
480 mailbox
->syncState
.setRecent(resp
->number
);
481 list
->m_recentMessageCount
= resp
->number
;
482 model
->emitMessageCountChanged(mailbox
);
483 saveSyncStateNowOrLater(mailbox
);
490 bool KeepMailboxOpenTask::handleVanished(const Responses::Vanished
*const resp
)
493 _failed(tr("Asked to die"));
497 if (dieIfInvalidMailbox())
502 if (resp
->earlier
!= Responses::Vanished::NOT_EARLIER
)
505 TreeItemMailbox
*mailbox
= Model::mailboxForSomeItem(mailboxIndex
);
508 mailbox
->handleVanished(model
, *resp
);
509 saveSyncStateNowOrLater(mailbox
);
513 bool KeepMailboxOpenTask::handleFetch(const Imap::Responses::Fetch
*const resp
)
515 if (dieIfInvalidMailbox())
519 _failed(tr("Asked to die"));
525 TreeItemMailbox
*mailbox
= Model::mailboxForSomeItem(mailboxIndex
);
527 model
->genericHandleFetch(mailbox
, resp
);
531 void KeepMailboxOpenTask::slotPerformNoop()
534 model
->m_taskFactory
->createNoopTask(model
, this);
537 bool KeepMailboxOpenTask::handleStateHelper(const Imap::Responses::State
*const resp
)
541 if (handleResponseCodeInsideState(resp
))
544 // FIXME: checks for shouldExit and proper boundaries?
546 if (resp
->respCode
== Responses::CLOSED
) {
547 switch (model
->accessParser(parser
).connState
) {
548 case CONN_STATE_SELECTING
:
549 case CONN_STATE_SELECTING_WAIT_FOR_CLOSE
:
550 model
->changeConnectionState(parser
, CONN_STATE_SELECTING
);
551 finalizeTermination();
554 throw UnexpectedResponseReceived("No other mailbox is being selected, but got a [CLOSED] response", *resp
);
558 if (resp
->tag
.isEmpty())
561 if (resp
->tag
== tagIdle
) {
563 Q_ASSERT(idleLauncher
);
564 if (resp
->kind
== Responses::OK
) {
565 // The IDLE got terminated for whatever reason, so we should schedule its restart
566 idleLauncher
->idleCommandCompleted();
567 if (canRunIdleRightNow()) {
568 idleLauncher
->enterIdleLater();
571 // The IDLE command has failed. Let's assume it's a permanent error and don't request it in future.
572 log(QStringLiteral("The IDLE command has failed"));
573 shouldRunIdle
= false;
574 idleLauncher
->idleCommandFailed();
575 idleLauncher
->deleteLater();
579 // IDLE is special because it's not really a native Task. Therefore, we have to duplicate the check for its completion
580 // and possible termination request here.
581 // FIXME: maybe rewrite IDLE to be a native task and get all the benefits for free? Any drawbacks?
582 if (shouldExit
&& ! hasPendingInternalActions() && (! synchronizeConn
|| synchronizeConn
->isFinished())) {
586 } else if (newArrivalsFetch
.contains(resp
->tag
)) {
587 newArrivalsFetch
.removeOne(resp
->tag
);
589 if (newArrivalsFetch
.isEmpty() && mailboxIndex
.isValid()) {
590 // No pending commands for fetches of the mailbox state -> we have a consistent and accurate, up-to-date view
591 // -> we should save this
592 TreeItemMailbox
*mailbox
= dynamic_cast<TreeItemMailbox
*>(static_cast<TreeItem
*>(mailboxIndex
.internalPointer()));
594 mailbox
->saveSyncStateAndUids(model
);
597 if (resp
->kind
!= Responses::OK
) {
598 _failed(QLatin1String("FETCH of new arrivals failed: ") + resp
->message
);
600 // Don't forget to resume IDLE, if desired; that's easiest by simply behaving as if a "task" has just finished
602 model
->m_taskModel
->slotTaskMighHaveChanged(this);
604 } else if (resp
->tag
== tagClose
) {
606 if (m_deleteCurrentMailboxTask
) {
607 m_deleteCurrentMailboxTask
->perform();
609 if (resp
->kind
!= Responses::OK
) {
610 _failed(QLatin1String("CLOSE failed: ") + resp
->message
);
619 /** @short Reimplemented from ImapTask
621 This function's semantics is slightly shifted from ImapTask::abort(). It gets called when the KeepMailboxOpenTask has decided to
622 terminate and its biggest goals are to:
624 - Prevent any further activity from hitting this parser. We're here to "guard" access to it, and we're about to terminate, so the
625 tasks shall negotiate their access through some other KeepMailboxOpenTask.
626 - Terminate our internal code which might want to access the connection (NoopTask, IdleLauncher,...)
628 void KeepMailboxOpenTask::abort()
638 // We do not want to propagate the signal to the child tasks, though -- the KeepMailboxOpenTask::abort() is used in the course
639 // of the regular "hey, free this connection and pass it to another KeepMailboxOpenTask" situations.
642 /** @short Stop working as a maintaining task */
643 void KeepMailboxOpenTask::detachFromMailbox()
645 if (mailboxIndex
.isValid()) {
646 // Mark current mailbox as "orphaned by the housekeeping task"
647 TreeItemMailbox
*mailbox
= dynamic_cast<TreeItemMailbox
*>(static_cast<TreeItem
*>(mailboxIndex
.internalPointer()));
650 // We're already obsolete -> don't pretend to accept new tasks
651 if (mailbox
->maintainingTask
== this)
652 mailbox
->maintainingTask
= 0;
654 if (model
->m_parsers
.contains(parser
) && model
->accessParser(parser
).maintainingTask
== this) {
655 model
->accessParser(parser
).maintainingTask
= 0;
659 /** @short Reimplemented from ImapTask
661 We're aksed to die right now, so we better take any depending stuff with us. That poor tasks are not going to outlive me!
663 void KeepMailboxOpenTask::die(const QString
&message
)
666 // OK, we're done, and getting killed. This is fine; just don't emit failed()
667 // because we aren't actually failing.
668 // This is a speciality of the KeepMailboxOpenTask because it's the only task
669 // this has a very long life.
672 ImapTask::die(message
);
676 /** @short Kill all pending tasks -- both the regular one and the replacement ObtainSynchronizedMailboxTask instances
678 Reimplemented from the ImapTask.
680 void KeepMailboxOpenTask::killAllPendingTasks(const QString
&message
)
682 Q_FOREACH(ImapTask
*task
, dependingTasksForThisMailbox
) {
685 Q_FOREACH(ImapTask
*task
, dependingTasksNoMailbox
) {
688 Q_FOREACH(ImapTask
*task
, waitingObtainTasks
) {
693 QString
KeepMailboxOpenTask::debugIdentification() const
695 if (! mailboxIndex
.isValid())
696 return QStringLiteral("[invalid mailboxIndex]");
698 TreeItemMailbox
*mailbox
= dynamic_cast<TreeItemMailbox
*>(static_cast<TreeItem
*>(mailboxIndex
.internalPointer()));
700 return QString::fromUtf8("attached to %1%2%3").arg(mailbox
->mailbox(),
701 (synchronizeConn
&& ! synchronizeConn
->isFinished()) ? QStringLiteral(" [syncConn unfinished]") : QString(),
702 shouldExit
? QStringLiteral(" [shouldExit]") : QString()
706 /** @short The user wants us to go offline */
707 void KeepMailboxOpenTask::stopForLogout()
710 breakOrCancelPossibleIdle();
711 killAllPendingTasks(tr("Logging off..."));
713 // We're supposed to go offline. Given that we're a long-running task, I do not consider this a "failure".
714 // In particular, if the initial SELECT has not finished yet, the ObtainSynchronizedMailboxTask would get
715 // killed as well, and hence the mailboxSyncFailed() signal will get emitted.
716 // The worst thing which can possibly happen is that we're in the middle of checking the new arrivals.
717 // That's bad, because we've got unknown UIDs in our in-memory map, which is going to hurt during the next sync
718 // -- but that's something which should be handled elsewhere, IMHO.
719 // Therefore, make sure a subsequent call to die() doesn't propagate a failure.
723 bool KeepMailboxOpenTask::handleFlags(const Imap::Responses::Flags
*const resp
)
725 if (dieIfInvalidMailbox())
728 // Well, there isn't much point in keeping track of these flags, but given that
729 // IMAP servers are happy to send these responses even after the initial sync, we
730 // better handle them explicitly here.
731 TreeItemMailbox
*mailbox
= Model::mailboxForSomeItem(mailboxIndex
);
733 mailbox
->syncState
.setFlags(resp
->flags
);
737 void KeepMailboxOpenTask::activateTasks()
741 if (isRunning
!= Running::RUNNING
)
744 breakOrCancelPossibleIdle();
746 if (m_deleteCurrentMailboxTask
) {
747 closeMailboxDestructively();
751 slotFetchRequestedEnvelopes();
752 slotFetchRequestedParts();
754 while (!dependingTasksForThisMailbox
.isEmpty() && model
->accessParser(parser
).activeTasks
.size() < limitActiveTasks
) {
755 breakOrCancelPossibleIdle();
756 ImapTask
*task
= dependingTasksForThisMailbox
.takeFirst();
757 runningTasksForThisMailbox
.append(task
);
758 dependentTasks
.removeOne(task
);
761 while (!dependingTasksNoMailbox
.isEmpty() && model
->accessParser(parser
).activeTasks
.size() < limitActiveTasks
) {
762 breakOrCancelPossibleIdle();
763 ImapTask
*task
= dependingTasksNoMailbox
.takeFirst();
764 dependentTasks
.removeOne(task
);
768 if (idleLauncher
&& canRunIdleRightNow())
769 idleLauncher
->enterIdleLater();
772 void KeepMailboxOpenTask::requestPartDownload(const uint uid
, const QByteArray
&partId
, const uint estimatedSize
)
774 requestedParts
[uid
].insert(partId
);
775 requestedPartSizes
[uid
] += estimatedSize
;
776 if (!fetchPartTimer
->isActive()) {
777 fetchPartTimer
->start();
781 void KeepMailboxOpenTask::requestEnvelopeDownload(const uint uid
)
783 requestedEnvelopes
.append(uid
);
784 if (!fetchEnvelopeTimer
->isActive()) {
785 fetchEnvelopeTimer
->start();
789 void KeepMailboxOpenTask::slotFetchRequestedParts()
793 if (requestedParts
.isEmpty())
796 breakOrCancelPossibleIdle();
798 auto it
= requestedParts
.begin();
801 // When asked to exit, do as much as possible and die
802 while (shouldExit
|| fetchPartTasks
.size() < limitParallelFetchTasks
) {
805 while (uids
.size() < limitMessagesAtOnce
&& it
!= requestedParts
.end() && totalSize
< limitBytesAtOnce
) {
810 totalSize
+= requestedPartSizes
.take(it
.key());
811 it
= requestedParts
.erase(it
);
816 fetchPartTasks
<< model
->m_taskFactory
->createFetchMsgPartTask(model
, mailboxIndex
, uids
, parts
.toList());
820 void KeepMailboxOpenTask::slotFetchRequestedEnvelopes()
824 if (requestedEnvelopes
.isEmpty())
827 breakOrCancelPossibleIdle();
831 fetchNow
= requestedEnvelopes
;
832 requestedEnvelopes
.clear();
834 const int amount
= qMin(requestedEnvelopes
.size(), limitMessagesAtOnce
); // FIXME: add an extra limit?
835 fetchNow
= requestedEnvelopes
.mid(0, amount
);
836 requestedEnvelopes
.erase(requestedEnvelopes
.begin(), requestedEnvelopes
.begin() + amount
);
838 fetchMetadataTasks
<< model
->m_taskFactory
->createFetchMsgMetadataTask(model
, mailboxIndex
, fetchNow
);
841 void KeepMailboxOpenTask::breakOrCancelPossibleIdle()
844 idleLauncher
->finishIdle();
848 bool KeepMailboxOpenTask::handleResponseCodeInsideState(const Imap::Responses::State
*const resp
)
850 switch (resp
->respCode
) {
851 case Responses::UIDNEXT
:
853 if (dieIfInvalidMailbox())
854 return resp
->tag
.isEmpty();
856 TreeItemMailbox
*mailbox
= Model::mailboxForSomeItem(mailboxIndex
);
858 const Responses::RespData
<uint
> *const num
= dynamic_cast<const Responses::RespData
<uint
>* const>(resp
->respCodeData
.data());
860 mailbox
->syncState
.setUidNext(num
->data
);
861 saveSyncStateNowOrLater(mailbox
);
862 // We shouldn't eat tagged responses from this context
863 return resp
->tag
.isEmpty();
865 throw CantHappen("State response has invalid UIDNEXT respCodeData", *resp
);
869 case Responses::PERMANENTFLAGS
:
870 // Another useless one, but we want to consume it now to prevent a warning about
871 // an unhandled message
873 if (dieIfInvalidMailbox())
874 return resp
->tag
.isEmpty();
876 TreeItemMailbox
*mailbox
= Model::mailboxForSomeItem(mailboxIndex
);
878 const Responses::RespData
<QStringList
> *const num
= dynamic_cast<const Responses::RespData
<QStringList
>* const>(resp
->respCodeData
.data());
880 mailbox
->syncState
.setPermanentFlags(num
->data
);
881 // We shouldn't eat tagged responses from this context
882 return resp
->tag
.isEmpty();
884 throw CantHappen("State response has invalid PERMANENTFLAGS respCodeData", *resp
);
888 case Responses::HIGHESTMODSEQ
:
890 if (dieIfInvalidMailbox())
891 return resp
->tag
.isEmpty();
893 TreeItemMailbox
*mailbox
= Model::mailboxForSomeItem(mailboxIndex
);
895 const Responses::RespData
<quint64
> *const num
= dynamic_cast<const Responses::RespData
<quint64
>* const>(resp
->respCodeData
.data());
897 mailbox
->syncState
.setHighestModSeq(num
->data
);
898 saveSyncStateNowOrLater(mailbox
);
899 return resp
->tag
.isEmpty();
901 case Responses::UIDVALIDITY
:
903 if (dieIfInvalidMailbox())
904 return resp
->tag
.isEmpty();
906 TreeItemMailbox
*mailbox
= Model::mailboxForSomeItem(mailboxIndex
);
908 const Responses::RespData
<uint
> *const num
= dynamic_cast<const Responses::RespData
<uint
>* const>(resp
->respCodeData
.data());
910 if (mailbox
->syncState
.uidValidity() == num
->data
) {
911 // this is a harmless and useless message
912 return resp
->tag
.isEmpty();
914 // On the other hand, this a serious condition -- the server is telling us that the UIDVALIDITY has changed while
915 // a mailbox is open. There isn't much we could do here; having code for handling this gracefuly is just too much
916 // work for little to no benefit.
917 // The sane thing is to disconnect from this mailbox.
918 EMIT_LATER(model
, imapError
, Q_ARG(QString
, tr("The UIDVALIDITY has changed while mailbox is open. Please reconnect.")));
919 model
->setNetworkPolicy(NETWORK_OFFLINE
);
920 return resp
->tag
.isEmpty();
930 void KeepMailboxOpenTask::slotUnselected()
932 switch (model
->accessParser(parser
).connState
) {
933 case CONN_STATE_SYNCING
:
934 case CONN_STATE_SELECTED
:
935 case CONN_STATE_FETCHING_PART
:
936 case CONN_STATE_FETCHING_MSG_METADATA
:
937 model
->changeConnectionState(parser
, CONN_STATE_AUTHENTICATED
);
940 // no need to do anything from here
944 isRunning
= Running::RUNNING
; // WTF?
946 _failed(tr("UNSELECTed"));
949 bool KeepMailboxOpenTask::dieIfInvalidMailbox()
951 if (mailboxIndex
.isValid())
954 if (m_deleteCurrentMailboxTask
) {
955 // The current mailbox was supposed to be deleted; don't try to UNSELECT from this context
959 // See ObtainSynchronizedMailboxTask::dieIfInvalidMailbox() for details
960 if (!unSelectTask
&& isRunning
== Running::RUNNING
) {
961 unSelectTask
= model
->m_taskFactory
->createUnSelectTask(model
, this);
962 connect(unSelectTask
, &ImapTask::completed
, this, &KeepMailboxOpenTask::slotUnselected
);
963 unSelectTask
->perform();
969 bool KeepMailboxOpenTask::hasPendingInternalActions() const
971 bool hasToWaitForIdleTermination
= idleLauncher
? idleLauncher
->waitingForIdleTaggedTermination() : false;
972 return !(dependingTasksForThisMailbox
.isEmpty() && dependingTasksNoMailbox
.isEmpty() && runningTasksForThisMailbox
.isEmpty() &&
973 requestedParts
.isEmpty() && requestedEnvelopes
.isEmpty() && newArrivalsFetch
.isEmpty()) || hasToWaitForIdleTermination
;
976 /** @short Returns true if this task can be safely terminated
980 bool KeepMailboxOpenTask::isReadyToTerminate() const
982 return shouldExit
&& !hasPendingInternalActions() && (!synchronizeConn
|| synchronizeConn
->isFinished());
985 /** @short Return true if we're configured to run IDLE and if there's no ongoing activity */
986 bool KeepMailboxOpenTask::canRunIdleRightNow() const
988 bool res
= shouldRunIdle
&& dependingTasksForThisMailbox
.isEmpty() &&
989 dependingTasksNoMailbox
.isEmpty() && newArrivalsFetch
.isEmpty();
991 // If there's just one active tasks, it's the "this" one. If there are more of them, let's see if it's just one more
992 // and that one more thing is a SortTask which is in the "just updating" mode.
993 // If that is the case, we can still allow further IDLE, that task will abort idling when it needs to.
995 if (model
->accessParser(parser
).activeTasks
.size() > 1) {
996 if (model
->accessParser(parser
).activeTasks
.size() == 2 &&
997 dynamic_cast<SortTask
*>(model
->accessParser(parser
).activeTasks
[1]) &&
998 dynamic_cast<SortTask
*>(model
->accessParser(parser
).activeTasks
[1])->isJustUpdatingNow()) {
999 // This is OK, so no need to clear the "OK" flag
1001 // Too bad, cannot IDLE
1009 Q_ASSERT(model
->accessParser(parser
).activeTasks
.front() == this);
1013 QVariant
KeepMailboxOpenTask::taskData(const int role
) const
1020 /** @short The specified task can be abort()ed when the mailbox shall be vacanted
1022 It's an error to call this on a task which we aren't tracking already.
1024 void KeepMailboxOpenTask::feelFreeToAbortCaller(ImapTask
*task
)
1026 abortableTasks
.append(task
);
1029 /** @short It's time to reset the counters and perform the sync */
1030 void KeepMailboxOpenTask::syncingTimeout()
1032 if (!mailboxIndex
.isValid()) {
1035 if (m_skippedStateSynces
== 0) {
1036 // This means that state syncing it not being throttled, i.e. we're just resetting the window
1037 // which determines the rate of requests which would normally trigger saving
1038 m_performedStateSynces
= 0;
1040 // there's been no fresh arrivals for our timeout period -> let's flush the pending events
1041 TreeItemMailbox
*mailbox
= Model::mailboxForSomeItem(mailboxIndex
);
1043 saveSyncStateIfPossible(mailbox
);
1047 void KeepMailboxOpenTask::saveSyncStateNowOrLater(Imap::Mailbox::TreeItemMailbox
*mailbox
)
1049 bool saveImmediately
= true;
1051 /** @short After processing so many responses immediately, switch to a delayed mode where the saving is deferred */
1052 const uint throttlingThreshold
= 100;
1054 /** @short Flush the queue after postponing this number of messages.
1056 It's "ridiculously high", but it's still a number so that our integer newer wraps.
1058 const uint maxDelayedResponses
= 10000;
1060 if (m_skippedStateSynces
> 0) {
1061 // we're actively throttling
1062 if (m_skippedStateSynces
>= maxDelayedResponses
) {
1063 // ...but we've been throttling too many responses, let's flush them now
1064 Q_ASSERT(m_syncingTimer
->isActive());
1065 // do not go back to 0, otherwise there will be a lot of immediate events within each interval
1066 m_performedStateSynces
= throttlingThreshold
;
1067 saveImmediately
= true;
1069 ++m_skippedStateSynces
;
1070 saveImmediately
= false;
1073 // no throttling, cool
1074 if (m_performedStateSynces
>= throttlingThreshold
) {
1075 // ...but we should start throttling now
1076 ++m_skippedStateSynces
;
1077 saveImmediately
= false;
1079 ++m_performedStateSynces
;
1080 if (!m_syncingTimer
->isActive()) {
1081 // reset the sliding window
1082 m_syncingTimer
->start();
1084 saveImmediately
= true;
1088 if (saveImmediately
) {
1089 saveSyncStateIfPossible(mailbox
);
1091 m_performedStateSynces
= 0;
1092 m_syncingTimer
->start();
1096 void KeepMailboxOpenTask::saveSyncStateIfPossible(TreeItemMailbox
*mailbox
)
1098 m_skippedStateSynces
= 0;
1099 TreeItemMsgList
*list
= static_cast<TreeItemMsgList
*>(mailbox
->m_children
[0]);
1100 if (list
->fetched()) {
1101 mailbox
->saveSyncStateAndUids(model
);
1103 list
->setFetchStatus(Imap::Mailbox::TreeItem::LOADING
);
1107 void KeepMailboxOpenTask::closeMailboxDestructively()
1109 tagClose
= parser
->close();
1112 /** @short Let the model know that a mailbox synchronization has failed */
1113 void KeepMailboxOpenTask::signalSyncFailure(const QString
&message
)
1115 if (!mailboxIndex
.isValid()) {
1116 // Well, that mailbox is no longer there; perhaps this is because the list of mailboxes got replaced.
1117 // Seems that there's nothing to report here.
1121 if (synchronizeConn
) {
1122 // Well, we aren't synced yet. We're going to rely on the ObtainSynchronizedMailboxTask's own
1123 // emitting of mailboxSyncFailed() to prevent duplicate signals.
1127 EMIT_LATER(model
, mailboxSyncFailed
, Q_ARG(QString
, mailboxIndex
.data(RoleMailboxName
).toString()), Q_ARG(QString
, message
));
1130 /** @short Is this task on its own keeping the connection busy?
1132 Right now, only fetching of new arrivals is being done in the context of this KeepMailboxOpenTask task.
1134 bool KeepMailboxOpenTask::hasItsOwnActivity() const
1136 return !newArrivalsFetch
.isEmpty();
1139 /** @short Signal the final termination of this task */
1140 void KeepMailboxOpenTask::finalizeTermination()
1144 emit
completed(this);