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