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