01a36b9f7644a9010e6796cd2f81850d93cf8522
[modest] / src / modest-mail-operation-queue.c
1 /* Copyright (c) 2006, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "config.h"
31 #include "modest-marshal.h"
32 #include "modest-mail-operation-queue.h"
33 #include "modest-runtime.h"
34 #include "modest-debug.h"
35
36 /* 'private'/'protected' functions */
37 static void modest_mail_operation_queue_class_init (ModestMailOperationQueueClass *klass);
38 static void modest_mail_operation_queue_init       (ModestMailOperationQueue *obj);
39 static void modest_mail_operation_queue_finalize   (GObject *obj);
40
41 static void
42 on_operation_finished (ModestMailOperation *mail_op,
43                        gpointer user_data);
44
45 /* list my signals  */
46 enum {
47         QUEUE_CHANGED_SIGNAL,
48         QUEUE_EMPTY_SIGNAL,
49         NUM_SIGNALS
50 };
51
52 typedef struct _ModestMailOperationQueuePrivate ModestMailOperationQueuePrivate;
53 struct _ModestMailOperationQueuePrivate {
54         GQueue *op_queue;
55         GMutex *queue_lock;
56         guint   op_id;
57         guint   queue_empty_handler;
58         gboolean running_final_sync;
59 };
60 #define MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
61                                                          MODEST_TYPE_MAIL_OPERATION_QUEUE, \
62                                                          ModestMailOperationQueuePrivate))
63 /* globals */
64 static GObjectClass *parent_class = NULL;
65
66 static guint signals[NUM_SIGNALS] = {0};
67
68 GType
69 modest_mail_operation_queue_get_type (void)
70 {
71         static GType my_type = 0;
72         if (!my_type) {
73                 static const GTypeInfo my_info = {
74                         sizeof(ModestMailOperationQueueClass),
75                         NULL,           /* base init */
76                         NULL,           /* base finalize */
77                         (GClassInitFunc) modest_mail_operation_queue_class_init,
78                         NULL,           /* class finalize */
79                         NULL,           /* class data */
80                         sizeof(ModestMailOperationQueue),
81                         1,              /* n_preallocs */
82                         (GInstanceInitFunc) modest_mail_operation_queue_init,
83                         NULL
84                 };
85
86                 my_type = g_type_register_static (G_TYPE_OBJECT,
87                                                   "ModestMailOperationQueue",
88                                                   &my_info, 0);
89         }
90         return my_type;
91 }
92
93 static void
94 modest_mail_operation_queue_class_init (ModestMailOperationQueueClass *klass)
95 {
96         GObjectClass *gobject_class;
97
98         gobject_class = (GObjectClass*) klass;
99         parent_class  = g_type_class_peek_parent (klass);
100
101         gobject_class->finalize    = modest_mail_operation_queue_finalize;
102
103         g_type_class_add_private (gobject_class, sizeof(ModestMailOperationQueuePrivate));
104
105         /**
106          * ModestMailOperationQueue::queue-changed
107          * @self: the #ModestMailOperationQueue that emits the signal
108          * @mail_op: the #ModestMailOperation affected
109          * @type: the type of change in the queue
110          * @user_data: user data set when the signal handler was connected
111          *
112          * Emitted whenever the contents of the queue change
113          */
114         signals[QUEUE_CHANGED_SIGNAL] =
115                 g_signal_new ("queue-changed",
116                               G_TYPE_FROM_CLASS (gobject_class),
117                               G_SIGNAL_RUN_FIRST,
118                               G_STRUCT_OFFSET (ModestMailOperationQueueClass, queue_changed),
119                               NULL, NULL,
120                               modest_marshal_VOID__POINTER_INT,
121                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_INT);
122
123         /**
124          * ModestMailOperationQueue::queue-empty
125          * @self: the #ModestMailOperationQueue that emits the signal
126          * @user_data: user data set when the signal handler was connected
127          *
128          * Issued whenever the queue is empty
129          */
130         signals[QUEUE_EMPTY_SIGNAL] =
131                 g_signal_new ("queue-empty",
132                               G_TYPE_FROM_CLASS (gobject_class),
133                               G_SIGNAL_RUN_FIRST,
134                               G_STRUCT_OFFSET (ModestMailOperationQueueClass, queue_empty),
135                               NULL, NULL,
136                               g_cclosure_marshal_VOID__VOID,
137                               G_TYPE_NONE, 0);
138
139 }
140
141 static void
142 modest_mail_operation_queue_init (ModestMailOperationQueue *obj)
143 {
144         ModestMailOperationQueuePrivate *priv;
145
146         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(obj);
147
148         priv->op_queue   = g_queue_new ();
149         priv->queue_lock = g_mutex_new ();
150         priv->op_id = 0;
151         priv->queue_empty_handler = 0;
152         priv->running_final_sync = FALSE;
153 }
154
155 static void
156 print_queue_item (ModestMailOperation *op, const gchar* prefix)
157 {
158         gchar *op_str = modest_mail_operation_to_string (op);
159         g_debug ("%s: %s",
160                  prefix ? prefix : "",
161                  op_str);
162         g_free (op_str);
163 }
164
165 static void
166 on_finalize_foreach(gpointer op,
167                     gpointer user_data)
168 {
169         ModestMailOperationQueue *queue;
170         ModestMailOperationQueuePrivate *priv;
171         ModestMailOperation *mail_op;
172
173         queue = MODEST_MAIL_OPERATION_QUEUE (user_data);
174         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE (queue);
175         mail_op = MODEST_MAIL_OPERATION (op);
176
177         /* Simply remove from queue, but without emitting a
178          * QUEUE_CHANGED_SIGNAL because we are in finalize anyway and have
179          * the lock acquired. */
180         g_signal_handlers_disconnect_by_func (mail_op, G_CALLBACK (on_operation_finished), user_data);
181
182         MODEST_DEBUG_BLOCK (print_queue_item (mail_op, "cancel/remove"););
183
184         modest_mail_operation_cancel (mail_op);
185         g_queue_remove (priv->op_queue, mail_op);
186         g_object_unref (G_OBJECT (mail_op));
187 }
188
189 static void
190 modest_mail_operation_queue_finalize (GObject *obj)
191 {
192         ModestMailOperationQueuePrivate *priv;
193
194         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(obj);
195
196         g_mutex_lock (priv->queue_lock);
197
198         MODEST_DEBUG_BLOCK (
199                 g_debug ("%s; items in queue: %d",
200                          __FUNCTION__, g_queue_get_length (priv->op_queue));
201                 g_queue_foreach (priv->op_queue, (GFunc)print_queue_item, "in queue");
202         );
203
204         
205         if (priv->op_queue) {
206                 /* Cancel all */
207                 if (!g_queue_is_empty (priv->op_queue)) {
208                         g_queue_foreach (priv->op_queue,
209                                          (GFunc)on_finalize_foreach,
210                                          MODEST_MAIL_OPERATION_QUEUE (obj));
211                 }
212
213                 g_queue_free (priv->op_queue);
214         }
215
216         g_mutex_unlock (priv->queue_lock);
217         g_mutex_free (priv->queue_lock);
218         
219         G_OBJECT_CLASS(parent_class)->finalize (obj);
220 }
221
222 ModestMailOperationQueue *
223 modest_mail_operation_queue_new (void)
224 {
225         ModestMailOperationQueue *self = g_object_new (MODEST_TYPE_MAIL_OPERATION_QUEUE, NULL);
226
227         return MODEST_MAIL_OPERATION_QUEUE (self);
228 }
229
230 static void
231 on_operation_finished (ModestMailOperation *mail_op,
232                        gpointer user_data)
233 {
234         ModestMailOperationQueue *queue = MODEST_MAIL_OPERATION_QUEUE (user_data);
235
236         /* Remove operation from queue when finished */
237         modest_mail_operation_queue_remove (queue, mail_op);
238 }
239
240 void 
241 modest_mail_operation_queue_add (ModestMailOperationQueue *self, 
242                                  ModestMailOperation *mail_op)
243 {
244         ModestMailOperationQueuePrivate *priv;
245
246         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
247         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
248
249         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
250
251         priv->running_final_sync = (modest_mail_operation_get_type_operation (mail_op) == MODEST_MAIL_OPERATION_TYPE_SHUTDOWN);
252
253         g_mutex_lock (priv->queue_lock);
254         g_queue_push_tail (priv->op_queue, g_object_ref (mail_op));
255         g_mutex_unlock (priv->queue_lock);
256
257         MODEST_DEBUG_BLOCK (print_queue_item (mail_op, "add"););
258
259         /* Get notified when the operation ends to remove it from the
260            queue. We connect it using the *after* because we want to
261            let the other handlers for the finish function happen
262            before this */
263         g_signal_connect_after (G_OBJECT (mail_op), 
264                                 "operation-finished",
265                                 G_CALLBACK (on_operation_finished), 
266                                 self);
267
268         /* Notify observers */
269         g_signal_emit (self, signals[QUEUE_CHANGED_SIGNAL], 0,
270                        mail_op, MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED);
271 }
272
273 static gboolean
274 notify_queue_empty (gpointer user_data)
275 {
276         ModestMailOperationQueue *self = (ModestMailOperationQueue *) user_data;
277         ModestMailOperationQueuePrivate *priv;
278         guint num_elements;
279
280         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
281
282         g_mutex_lock (priv->queue_lock);
283         num_elements = priv->op_queue->length;
284         g_mutex_unlock (priv->queue_lock);
285
286         /* We re-check again that the queue is empty. It could happen
287            that we had issued a tny_send_queue_flush before the send
288            queue could add a mail operation to the queue as a response
289            to the "start-queue" signal, because that signal is issued
290            by tinymail in the main loop. Therefor it could happen that
291            we emit the queue-empty signal while a send-queue is still
292            waiting for the "start-queue" signal from tinymail, so the
293            send queue will never try to send the items because modest
294            is finalized before */
295         if (num_elements == 0) {
296                 gdk_threads_enter ();
297                 g_signal_emit (self, signals[QUEUE_EMPTY_SIGNAL], 0);
298                 gdk_threads_leave ();
299         }
300
301         return FALSE;
302 }
303
304
305 void
306 modest_mail_operation_queue_remove (ModestMailOperationQueue *self,
307                                     ModestMailOperation *mail_op)
308 {
309         ModestMailOperationQueuePrivate *priv;
310         ModestMailOperationStatus status;
311         guint num_elements;
312
313         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
314         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
315
316         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
317
318         g_mutex_lock (priv->queue_lock);
319         g_queue_remove (priv->op_queue, mail_op);
320         num_elements = priv->op_queue->length;
321         g_mutex_unlock (priv->queue_lock);
322
323         MODEST_DEBUG_BLOCK (print_queue_item (mail_op, "remove"););
324
325         g_signal_handlers_disconnect_by_func (G_OBJECT (mail_op),
326                                               G_CALLBACK (on_operation_finished),
327                                               self);
328
329         /* Notify observers */
330         g_signal_emit (self, signals[QUEUE_CHANGED_SIGNAL], 0,
331                        mail_op, MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED);
332
333         /* Check errors */
334         status = modest_mail_operation_get_status (mail_op);
335         if (status != MODEST_MAIL_OPERATION_STATUS_SUCCESS &&
336             status != MODEST_MAIL_OPERATION_STATUS_INVALID) {
337                 /* This is a sanity check. Shouldn't be needed, but
338                    prevent possible application crashes. It's useful
339                    also for detecting mail operations with invalid
340                    status and error handling */
341                 if (modest_mail_operation_get_error (mail_op) != NULL) {
342                         modest_mail_operation_execute_error_handler (mail_op);
343                 } else {
344                         if (status == MODEST_MAIL_OPERATION_STATUS_CANCELED) 
345                                 g_warning ("%s: operation canceled \n", __FUNCTION__);
346                         else
347                                 g_warning ("%s: possible error in a mail operation " \
348                                            "implementation. The status is not successful " \
349                                            "but the mail operation does not have any " \
350                                            "error set\n", __FUNCTION__);
351                 }
352         }
353
354         /* Free object */
355         g_object_unref (G_OBJECT (mail_op));
356
357         /* Emit the queue empty-signal. See the function to know why
358            we emit it in an idle */
359         if (num_elements == 0) {
360                 if (priv->queue_empty_handler) {
361                         g_source_remove (priv->queue_empty_handler);
362                         priv->queue_empty_handler = 0;
363                 }
364                 priv->queue_empty_handler = g_idle_add_full (G_PRIORITY_LOW, 
365                                                              notify_queue_empty, 
366                                                              self, NULL);
367         }
368 }
369
370 guint
371 modest_mail_operation_queue_num_elements (ModestMailOperationQueue *self)
372 {
373         ModestMailOperationQueuePrivate *priv;
374         guint length = 0;
375
376         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self), 0);
377
378         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
379
380         g_mutex_lock (priv->queue_lock);
381         length = g_queue_get_length (priv->op_queue);
382         g_mutex_unlock (priv->queue_lock);
383
384         return length;
385 }
386
387 void 
388 modest_mail_operation_queue_cancel (ModestMailOperationQueue *self, 
389                                     ModestMailOperation *mail_op)
390 {
391         ModestMailOperationQueuePrivate *priv;
392
393         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
394         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
395
396         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
397
398         MODEST_DEBUG_BLOCK (print_queue_item (mail_op, "cancel"););
399         
400         /* This triggers a progess_changed signal in which we remove
401          * the operation from the queue. */
402         modest_mail_operation_cancel (mail_op);
403 }
404
405 static void
406 on_cancel_all_foreach (gpointer op, gpointer list)
407 {
408         GSList **new_list;
409
410         new_list = (GSList**) list;
411         *new_list = g_slist_prepend (*new_list, MODEST_MAIL_OPERATION (op));
412 }
413
414 void 
415 modest_mail_operation_queue_cancel_all (ModestMailOperationQueue *self)
416 {
417         ModestMailOperationQueuePrivate *priv;
418         GSList* operations_to_cancel = NULL;
419         GSList* cur = NULL;
420
421         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
422
423         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
424
425         /* Remember which operations to cancel. This is the only thing that
426          * is done while holding the lock, so we do not need to call
427          * functions from other files while holding the lock, which could
428          * lead to a deadlock if such a call re-enters into this queue and
429          * tries to acquire another lock. */
430         g_mutex_lock (priv->queue_lock);
431         g_queue_foreach (priv->op_queue, (GFunc) on_cancel_all_foreach, &operations_to_cancel);
432         g_mutex_unlock (priv->queue_lock);
433         
434         operations_to_cancel = g_slist_reverse (operations_to_cancel);
435
436         for(cur = operations_to_cancel; cur != NULL; cur = cur->next) {
437                 if (!MODEST_IS_MAIL_OPERATION(cur->data))
438                         g_printerr ("modest: cur->data is not a valid mail operation\n");
439                 else
440                         modest_mail_operation_cancel (MODEST_MAIL_OPERATION (cur->data));
441         }
442
443         g_slist_free(operations_to_cancel);
444 }
445
446 typedef struct
447 {
448         GSList **new_list;
449         GObject *source;
450 } FindBySourceInfo;
451
452 static void
453 on_find_by_source_foreach (gpointer op, gpointer data)
454 {
455         FindBySourceInfo *info = (FindBySourceInfo*) data;
456         GObject *source = NULL;
457
458         source = modest_mail_operation_get_source (MODEST_MAIL_OPERATION (op));
459         if (info->source == source) {
460                 g_object_ref (G_OBJECT (op));
461                 *(info->new_list) = g_slist_prepend (*(info->new_list), MODEST_MAIL_OPERATION (op));
462         }
463         if (source)
464                 g_object_unref (source);
465 }
466
467 GSList*
468 modest_mail_operation_queue_get_by_source (ModestMailOperationQueue *self,
469                                            GObject *source)
470 {
471         ModestMailOperationQueuePrivate *priv;
472         GSList* found_operations= NULL;
473         FindBySourceInfo *info = g_new0 (FindBySourceInfo, 1);
474
475         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self), NULL);
476         g_return_val_if_fail (source != NULL, NULL);
477
478         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
479
480         info->new_list = &found_operations;
481         info->source = source;
482
483         g_mutex_lock (priv->queue_lock);
484         g_queue_foreach (priv->op_queue, (GFunc) on_find_by_source_foreach, info);
485         g_mutex_unlock (priv->queue_lock);
486
487         g_free (info);
488
489         return found_operations;
490 }
491
492 static void
493 accumulate_mail_op_strings (ModestMailOperation *op, gchar **str)
494 {
495         *str = g_strdup_printf ("%s\n%s", *str, modest_mail_operation_to_string (op));
496 }
497
498
499 gchar*
500 modest_mail_operation_queue_to_string (ModestMailOperationQueue *self)
501 {
502         gchar *str;
503         guint len;
504         ModestMailOperationQueuePrivate *priv;
505         
506         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self), NULL);
507         
508         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
509
510         len = g_queue_get_length (priv->op_queue);
511         str = g_strdup_printf ("mail operation queue (%02d)\n-------------------------", len);
512         if (len == 0)
513                 str = g_strdup_printf ("%s\n%s", str, "<empty>");
514         else {
515                 g_mutex_lock (priv->queue_lock);
516                 g_queue_foreach (priv->op_queue, (GFunc)accumulate_mail_op_strings, &str);
517                 g_mutex_unlock (priv->queue_lock);
518         }
519
520         return str;
521 }
522
523 gboolean
524 modest_mail_operation_queue_running_shutdown (ModestMailOperationQueue *self)
525 {
526         ModestMailOperationQueuePrivate *priv;
527
528         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self), FALSE);
529
530         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
531         return priv->running_final_sync;
532 }
533
534 void
535 modest_mail_operation_queue_set_running_shutdown (ModestMailOperationQueue *self)
536 {
537         ModestMailOperationQueuePrivate *priv;
538
539         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
540
541         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
542         priv->running_final_sync = TRUE;
543 }