1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net> 2 3 This file is part of the Trojita Qt IMAP e-mail client, 4 http://trojita.flaska.net/ 5 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. 13 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. 18 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/>. 21 */ 22 23 #ifndef IMAP_KEEPMAILBOXOPENTASK_H 24 #define IMAP_KEEPMAILBOXOPENTASK_H 25 26 #include <QModelIndex> 27 #include <QSet> 28 #include "ImapTask.h" 29 30 class QTimer; 31 class ImapModelIdleTest; 32 class LibMailboxSync; 33 34 namespace Imap 35 { 36 37 class Parser; 38 39 namespace Mailbox 40 { 41 42 class DeleteMailboxTask; 43 class ObtainSynchronizedMailboxTask; 44 class IdleLauncher; 45 class FetchMsgMetadataTask; 46 class FetchMsgPartTask; 47 class TreeItemMailbox; 48 class UnSelectTask; 49 50 /** @short Maintain a connection to a mailbox 51 52 This Task shall maintain a connection to a remote mailbox, updating the mailbox state while receiving various messages from the 53 IMAP server. 54 55 Essentially, this Task is responsible for processing stuff like EXPUNGE replies while the mailbox is selected. It's a bit special 56 task because it will not emit completed() unless something else wants to re-use its Parser instance. 57 58 There are four sorts of tasks: 59 60 1) Those that require a synchronized mailbox connection and are already running 61 2) Those that require such a connection but are not running yet 62 3) Tasks which are going to replace this KeepMailboxOpenTask when it's done 63 4) Tasks that do not need any particular mailbox; they can work no matter if there's any mailbox opened 64 65 Of these sorts, 4) are relevant only when dispatching the IDLE command (as IDLE can only run when nothing else is using this 66 connection because it is implemented as a "command in progress" thing) and also when deciding whether we can die already. 67 68 Sorts 2) and 3) have a common feature -- they are somehow "waiting" for their turn, so that they could get their job done. 69 They will start when this KeepMailboxOpenTask asks them to start. 70 71 Finally, tasks of the first kind shall not be treated as "children of the KeepMailboxOpenTask", but shall be able to keep the 72 KeepMailboxOpenTask from disappearing. 73 74 An "active task" means that a task is receiving the server's responses. An active task in this sense is always present in the 75 corresponding ParserState's activeTasks list. These tasks have their ImapTask::parentTask set to zero because they are no 76 longer waiting for their chance to run, they are running already. They are also *not* included in any other task's 77 dependentTasks -- simply because the sanity rule says that: 78 79 (ImapTask::parentTask is non-zero) <=> (the parentTask::dependingTasks contains that task) 80 81 This approach is also used for visualization of the task tree. 82 83 This is the mapping of the task sorts into the places which keep track of them: 84 85 * If the KeepTask is already active, the 4) goes straight to the ParserState::activeTasks and remain there until they terminate. 86 In case the task isn't active yet, the tasks remain waiting in dependingTasksNoMailbox until the KeepTask gets activated. 87 * The 3) gets added to the waitingKeepTasks *and* to the dependentTasks. They remain there until the time this 88 KeepMailboxOpenTask terminates. At that time, the first of them gets activated while the others get prepended to the first 89 task's waitingKeepTasks & dependentTasks list. 90 * The 2) are found in the dependentTasks. They used to be present in dependingTasksForThisMailbox as well, but got removed when they 91 got started. 92 * The 1) originally existed as 2), but got run at some time. When they got run, they got also added to the 93 ParserState::activeTasks, but vanished from this KeepMailboxOpenTask::dependentTasks. However, to prevent this 94 KeepMailboxOpenTask from disappearing, they are also kept in the runningTasksForThisMailbox list. 95 96 97 The KeepMailboxOpenTask can be in one of the following four states: 98 99 - Waiting for its synchronizeConn to finish. Nothing else can happen during this phase. 100 - Resynchronizing changes which have happened to the mailbox while the KeepMailboxOpenTask was in charge. In this state, other 101 commands can be scheduled and executing. 102 - Not doing anything on its own. Various tasks can be scheduled from this state. 103 - An ObtainSynchronizedMailboxTask is waiting to replace us. Existing tasks are allowed to finish, tasks for this mailbox are 104 still accepted and will be executed and tasks which aren't dependent on this mailbox can also run. When everything finishes, the 105 waiting task will replace this one. 106 - Finally, the KeepMailboxOpenTask can be executing the IDLE command. Nothing else can ever run from this context. 107 108 */ 109 110 class KeepMailboxOpenTask : public ImapTask 111 { 112 Q_OBJECT 113 public: 114 /** @short Create new task for maintaining a mailbox 115 116 @arg mailboxIndex the new mailbox to open and keep open 117 118 @arg formerMailbox the mailbox which was kept open by the previous KeepMailboxOpenTask; 119 that mailbox now loses its KeepMailboxOpenTask and the underlying parser is reused for this task 120 */ 121 KeepMailboxOpenTask(Model *model, const QModelIndex &mailboxIndex, Parser *oldParser); 122 123 virtual void abort(); 124 virtual void die(const QString &message); 125 126 void stopForLogout(); 127 128 /** @short Start child processes 129 130 This function is called when the synchronizing task finished successfully, that is, when we are ready 131 to execute regular tasks which depend on us. 132 */ 133 virtual void perform(); 134 135 /** @short Add any other task which somehow needs our current mailbox 136 137 This function also automatically registers the depending task in a special list which will make 138 sure that we won't emit finished() until all the dependant tasks have finished. This essnetially 139 prevents replacing an "alive" KeepMailboxOpenTask with a different one. 140 */ 141 virtual void addDependentTask(ImapTask *task); 142 143 /** @short Make sure to re-open the mailbox, even if it is already open */ 144 void resynchronizeMailbox(); 145 146 QString debugIdentification() const; 147 148 void requestPartDownload(const uint uid, const QByteArray &partId, const uint estimatedSize); 149 /** @short Request a delayed loading of a message envelope */ 150 void requestEnvelopeDownload(const uint uid); 151 152 virtual QVariant taskData(const int role) const; 153 needsMailbox()154 virtual bool needsMailbox() const {return true;} 155 156 bool isReadyToTerminate() const; 157 158 void feelFreeToAbortCaller(ImapTask *task); 159 160 bool hasItsOwnActivity() const; 161 162 private slots: 163 void slotTaskDeleted(QObject *object); 164 165 /** @short Start mailbox synchronization process 166 167 This function is called when we know that the underlying Parser is no longer in active use 168 in any mailbox and that it is ready to be used for our purposes. It doesn't matter if that 169 happened because the older KeepMailboxOpenTask got finished or because new connection got 170 established and entered the authenticated state; the important part is that we should 171 initialize synchronization now. 172 */ 173 void slotPerformConnection(); 174 175 /** @short The synchronization is done, let's start working now */ slotSyncHasCompleted()176 void slotSyncHasCompleted() { perform(); } 177 178 virtual bool handleNumberResponse(const Imap::Responses::NumberResponse *const resp); 179 virtual bool handleFetch(const Imap::Responses::Fetch *const resp); 180 virtual bool handleStateHelper(const Imap::Responses::State *const resp); 181 virtual bool handleFlags(const Imap::Responses::Flags *const resp); 182 virtual bool handleVanished(const Responses::Vanished *const resp); 183 bool handleResponseCodeInsideState(const Imap::Responses::State *const resp); 184 185 void slotPerformNoop(); slotActivateTasks()186 void slotActivateTasks() { activateTasks(); } 187 void slotFetchRequestedParts(); 188 /** @short Fetch the ENVELOPEs which were queued for later retrieval */ 189 void slotFetchRequestedEnvelopes(); 190 191 /** @short Something bad has happened to the connection, and we're no longer in that mailbox */ 192 void slotUnselected(); 193 194 void terminate(); 195 196 void finalizeTermination(); 197 198 void syncingTimeout(); 199 200 private: 201 /** @short Activate the dependent tasks while also limiting the rate */ 202 void activateTasks(); 203 204 /** @short If there's an IDLE running, be sure to stop it. If it's queued, delay it. */ 205 void breakOrCancelPossibleIdle(); 206 207 /** @short Check current mailbox for validity, and take an evasive action if it disappeared 208 209 This is an equivalent of ObtainSynchronizedMailboxTask::dieIfInvalidMailbox. It will check whether 210 the underlying index is still valid, and do best to detach from this mailbox if the index disappears. 211 More details about why this is needed along the fix to ObtainSynchronizedMailboxTask can be found in 212 issue #124. 213 214 @see ObtainSynchronizedMailboxTask::dieIfInvalidMailbox 215 */ 216 bool dieIfInvalidMailbox(); 217 218 /** @short Close mailbox forcifully, destroying its content in the process 219 220 Closing is performed via the CLOSE command, which is mandatory in all IMAP implementations. It has the ugly 221 side effect of removing any messages marked as \\Deleted. That's why this is a private method, only to be used 222 by the DeleteMailboxTask. 223 */ 224 void closeMailboxDestructively(); 225 226 /** @short Return true if this has a list of stuff to do */ 227 bool hasPendingInternalActions() const; 228 229 void detachFromMailbox(); 230 231 bool canRunIdleRightNow() const; 232 233 void saveSyncStateNowOrLater(Imap::Mailbox::TreeItemMailbox *mailbox); 234 void saveSyncStateIfPossible(Imap::Mailbox::TreeItemMailbox *mailbox); 235 236 protected: 237 virtual void killAllPendingTasks(const QString &message); 238 239 QPersistentModelIndex mailboxIndex; 240 241 /** @short Future maintaining tasks which are waiting for their opportunity to run 242 243 A list of KeepMailboxOpenTask which would like to use this connection to the IMAP server for conducting their business. They 244 have to wait until this KeepMailboxOpenTask finished whatever it has to do. 245 */ 246 QList<ObtainSynchronizedMailboxTask *> waitingObtainTasks; 247 248 /** @short List of pending or running tasks which require this mailbox 249 250 */ 251 QList<ImapTask *> runningTasksForThisMailbox; 252 /** @short Contents of the dependentTasks without the waitingObtainTasks */ 253 QList<ImapTask *> dependingTasksForThisMailbox; 254 /** @short Depending tasks which don't need this mailbox */ 255 QList<ImapTask *> dependingTasksNoMailbox; 256 /** @short Tasks which shall be aborted when the mailbox is to be abandoned */ 257 QList<ImapTask *> abortableTasks; 258 /** @short An ImapTask that will be started to actually sync to a mailbox once the connection is free */ 259 ObtainSynchronizedMailboxTask *synchronizeConn; 260 261 bool shouldExit; 262 enum class Running { 263 NOT_YET, 264 RUNNING, 265 NOT_ANYMORE, 266 }; 267 Running isRunning; 268 269 QTimer *noopTimer; 270 QTimer *fetchPartTimer; 271 QTimer *fetchEnvelopeTimer; 272 bool shouldRunNoop; 273 bool shouldRunIdle; 274 IdleLauncher *idleLauncher; 275 QList<FetchMsgPartTask *> fetchPartTasks; 276 QList<FetchMsgMetadataTask *> fetchMetadataTasks; 277 QPointer<DeleteMailboxTask> m_deleteCurrentMailboxTask; 278 CommandHandle tagIdle; 279 QList<CommandHandle> newArrivalsFetch; 280 CommandHandle tagClose; 281 friend class IdleLauncher; 282 friend class ImapTask; // needs access to slotTaskDeleted() 283 friend class ObtainSynchronizedMailboxTask; // needs access to slotUnSelectCompleted() 284 friend class SortTask; // needs access to breakOrCancelPossibleIdle() 285 friend class UnSelectTask; // needs access to breakPossibleIdle() 286 friend class DeleteMailboxTask; // needs access to the closeMailboxDestructively() 287 friend class TreeItemMailbox; // wants to know if our index is OK 288 friend class ::ImapModelIdleTest; 289 friend class ::LibMailboxSync; 290 291 QMap<uint, QSet<QByteArray> > requestedParts; 292 QMap<uint, uint> requestedPartSizes; 293 /** @short UIDs of messages with pending FetchMsgMetadataTask request 294 295 QList is used in preference to the QSet in an attempt to maintain the order of requests. Simply ordering via UID is 296 not enough because of output sorting, threads etc etc. 297 */ 298 Imap::Uids requestedEnvelopes; 299 300 uint limitBytesAtOnce; 301 int limitMessagesAtOnce; 302 int limitParallelFetchTasks; 303 int limitActiveTasks; 304 305 /** @short An UNSELECT task, if active */ 306 UnSelectTask *unSelectTask; 307 308 /** @short Number of skipped syncing the mailbox state since the last performed sync */ 309 uint m_skippedStateSynces; 310 /** @short Number of times the sync state got saved since the last timer reset */ 311 uint m_performedStateSynces; 312 /** @short Tracking time since the last reset of our counters */ 313 QTimer *m_syncingTimer; 314 }; 315 316 } 317 } 318 319 #endif // IMAP_KEEPMAILBOXOPENTASK_H 320