Fixed Modest crashes after tapping back button in new mail editor
[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_started (ModestMailOperation *mail_op,
232                        gpointer user_data)
233 {
234         ModestMailOperationQueue *self = MODEST_MAIL_OPERATION_QUEUE (user_data);
235         ModestMailOperationTypeOperation op_type;
236         ModestMailOperationQueuePrivate *priv;
237         TnyDevice *device;
238
239         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
240
241         op_type = modest_mail_operation_get_type_operation (mail_op);
242         device = modest_runtime_get_device ();
243
244         if ((op_type != MODEST_MAIL_OPERATION_TYPE_SHUTDOWN) &&
245             priv->running_final_sync &&
246             tny_device_is_forced (device))
247                 tny_device_reset (device);
248
249         priv->running_final_sync = (op_type == MODEST_MAIL_OPERATION_TYPE_SHUTDOWN);
250 }
251
252 static void
253 on_operation_finished (ModestMailOperation *mail_op,
254                        gpointer user_data)
255 {
256         ModestMailOperationQueue *queue = MODEST_MAIL_OPERATION_QUEUE (user_data);
257
258         /* Remove operation from queue when finished */
259         modest_mail_operation_queue_remove (queue, mail_op);
260 }
261
262 void 
263 modest_mail_operation_queue_add (ModestMailOperationQueue *self, 
264                                  ModestMailOperation *mail_op)
265 {
266         ModestMailOperationQueuePrivate *priv;
267
268         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
269         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
270
271         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
272
273         g_mutex_lock (priv->queue_lock);
274         g_queue_push_tail (priv->op_queue, g_object_ref (mail_op));
275         g_mutex_unlock (priv->queue_lock);
276
277         MODEST_DEBUG_BLOCK (print_queue_item (mail_op, "add"););
278
279         g_signal_connect (G_OBJECT (mail_op),
280                           "operation-started",
281                           G_CALLBACK (on_operation_started),
282                           self);
283
284         /* Get notified when the operation ends to remove it from the
285            queue. We connect it using the *after* because we want to
286            let the other handlers for the finish function happen
287            before this */
288         g_signal_connect_after (G_OBJECT (mail_op),
289                                 "operation-finished",
290                                 G_CALLBACK (on_operation_finished),
291                                 self);
292
293         /* Notify observers */
294         g_signal_emit (self, signals[QUEUE_CHANGED_SIGNAL], 0,
295                        mail_op, MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED);
296 }
297
298 static gboolean
299 notify_queue_empty (gpointer user_data)
300 {
301         ModestMailOperationQueue *self = (ModestMailOperationQueue *) user_data;
302         ModestMailOperationQueuePrivate *priv;
303         guint num_elements;
304
305         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
306
307         g_mutex_lock (priv->queue_lock);
308         num_elements = priv->op_queue->length;
309         g_mutex_unlock (priv->queue_lock);
310
311         /* We re-check again that the queue is empty. It could happen
312            that we had issued a tny_send_queue_flush before the send
313            queue could add a mail operation to the queue as a response
314            to the "start-queue" signal, because that signal is issued
315            by tinymail in the main loop. Therefor it could happen that
316            we emit the queue-empty signal while a send-queue is still
317            waiting for the "start-queue" signal from tinymail, so the
318            send queue will never try to send the items because modest
319            is finalized before */
320         if (num_elements == 0) {
321                 gdk_threads_enter ();
322                 g_signal_emit (self, signals[QUEUE_EMPTY_SIGNAL], 0);
323                 gdk_threads_leave ();
324         }
325
326         return FALSE;
327 }
328
329
330 void
331 modest_mail_operation_queue_remove (ModestMailOperationQueue *self,
332                                     ModestMailOperation *mail_op)
333 {
334         ModestMailOperationQueuePrivate *priv;
335         ModestMailOperationStatus status;
336         guint num_elements;
337
338         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
339         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
340
341         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
342
343         g_mutex_lock (priv->queue_lock);
344         g_queue_remove (priv->op_queue, mail_op);
345         num_elements = priv->op_queue->length;
346         g_mutex_unlock (priv->queue_lock);
347
348         MODEST_DEBUG_BLOCK (print_queue_item (mail_op, "remove"););
349
350         g_signal_handlers_disconnect_by_func (G_OBJECT (mail_op),
351                                               G_CALLBACK (on_operation_finished),
352                                               self);
353
354         /* Notify observers */
355         g_signal_emit (self, signals[QUEUE_CHANGED_SIGNAL], 0,
356                        mail_op, MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED);
357
358         /* Check errors */
359         status = modest_mail_operation_get_status (mail_op);
360         if (status != MODEST_MAIL_OPERATION_STATUS_SUCCESS &&
361             status != MODEST_MAIL_OPERATION_STATUS_INVALID) {
362                 /* This is a sanity check. Shouldn't be needed, but
363                    prevent possible application crashes. It's useful
364                    also for detecting mail operations with invalid
365                    status and error handling */
366                 if (modest_mail_operation_get_error (mail_op) != NULL) {
367                         modest_mail_operation_execute_error_handler (mail_op);
368                 } else {
369                         if (status == MODEST_MAIL_OPERATION_STATUS_CANCELED) 
370                                 g_debug ("%s: operation canceled \n", __FUNCTION__);
371                         else
372                                 g_warning ("%s: possible error in a mail operation " \
373                                            "implementation. The status is not successful " \
374                                            "but the mail operation does not have any " \
375                                            "error set\n", __FUNCTION__);
376                 }
377         }
378
379         /* Free object */
380         g_object_unref (G_OBJECT (mail_op));
381
382         /* Emit the queue empty-signal. See the function to know why
383            we emit it in an idle */
384         if (num_elements == 0) {
385                 if (priv->queue_empty_handler) {
386                         g_source_remove (priv->queue_empty_handler);
387                         priv->queue_empty_handler = 0;
388                 }
389                 priv->queue_empty_handler = g_idle_add_full (G_PRIORITY_LOW, 
390                                                              notify_queue_empty, 
391                                                              self, NULL);
392         }
393 }
394
395 guint
396 modest_mail_operation_queue_num_elements (ModestMailOperationQueue *self)
397 {
398         ModestMailOperationQueuePrivate *priv;
399         guint length = 0;
400
401         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self), 0);
402
403         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
404
405         g_mutex_lock (priv->queue_lock);
406         length = g_queue_get_length (priv->op_queue);
407         g_mutex_unlock (priv->queue_lock);
408
409         return length;
410 }
411
412 void 
413 modest_mail_operation_queue_cancel (ModestMailOperationQueue *self, 
414                                     ModestMailOperation *mail_op)
415 {
416         ModestMailOperationQueuePrivate *priv;
417
418         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
419         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
420
421         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
422
423         MODEST_DEBUG_BLOCK (print_queue_item (mail_op, "cancel"););
424         
425         /* This triggers a progess_changed signal in which we remove
426          * the operation from the queue. */
427         modest_mail_operation_cancel (mail_op);
428 }
429
430 static void
431 on_cancel_all_foreach (gpointer op, gpointer list)
432 {
433         GSList **new_list;
434
435         new_list = (GSList**) list;
436         *new_list = g_slist_prepend (*new_list, MODEST_MAIL_OPERATION (op));
437 }
438
439 void 
440 modest_mail_operation_queue_cancel_all (ModestMailOperationQueue *self)
441 {
442         ModestMailOperationQueuePrivate *priv;
443         GSList* operations_to_cancel = NULL;
444         GSList* cur = NULL;
445
446         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
447
448         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
449
450         /* Remember which operations to cancel. This is the only thing that
451          * is done while holding the lock, so we do not need to call
452          * functions from other files while holding the lock, which could
453          * lead to a deadlock if such a call re-enters into this queue and
454          * tries to acquire another lock. */
455         g_mutex_lock (priv->queue_lock);
456         g_queue_foreach (priv->op_queue, (GFunc) on_cancel_all_foreach, &operations_to_cancel);
457         g_mutex_unlock (priv->queue_lock);
458         
459         operations_to_cancel = g_slist_reverse (operations_to_cancel);
460
461         for(cur = operations_to_cancel; cur != NULL; cur = cur->next) {
462                 if (!MODEST_IS_MAIL_OPERATION(cur->data))
463                         g_printerr ("modest: cur->data is not a valid mail operation\n");
464                 else
465                         modest_mail_operation_cancel (MODEST_MAIL_OPERATION (cur->data));
466         }
467
468         g_slist_free(operations_to_cancel);
469 }
470
471 typedef struct
472 {
473         GSList **new_list;
474         GObject *source;
475 } FindBySourceInfo;
476
477 static void
478 on_find_by_source_foreach (gpointer op, gpointer data)
479 {
480         FindBySourceInfo *info = (FindBySourceInfo*) data;
481         GObject *source = NULL;
482
483         source = modest_mail_operation_get_source (MODEST_MAIL_OPERATION (op));
484         if (info->source == source) {
485                 g_object_ref (G_OBJECT (op));
486                 *(info->new_list) = g_slist_prepend (*(info->new_list), MODEST_MAIL_OPERATION (op));
487         }
488         if (source)
489                 g_object_unref (source);
490 }
491
492 GSList*
493 modest_mail_operation_queue_get_by_source (ModestMailOperationQueue *self,
494                                            GObject *source)
495 {
496         ModestMailOperationQueuePrivate *priv;
497         GSList* found_operations= NULL;
498         FindBySourceInfo *info;
499
500         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self), NULL);
501         g_return_val_if_fail (source != NULL, NULL);
502
503         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
504
505         info = g_new0 (FindBySourceInfo, 1);
506         info->new_list = &found_operations;
507         info->source = source;
508
509         g_mutex_lock (priv->queue_lock);
510         g_queue_foreach (priv->op_queue, (GFunc) on_find_by_source_foreach, info);
511         g_mutex_unlock (priv->queue_lock);
512
513         g_free (info);
514
515         return found_operations;
516 }
517
518 static void
519 accumulate_mail_op_strings (ModestMailOperation *op, gchar **str)
520 {
521         gchar *mail_op_to_str = modest_mail_operation_to_string (op);
522         *str = g_strdup_printf ("%s\n%s", *str, mail_op_to_str);
523         g_free (mail_op_to_str);
524 }
525
526
527 gchar*
528 modest_mail_operation_queue_to_string (ModestMailOperationQueue *self)
529 {
530         gchar *str = NULL;
531         guint len;
532         ModestMailOperationQueuePrivate *priv;
533
534         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self), NULL);
535
536         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
537
538         len = g_queue_get_length (priv->op_queue);
539         str = g_strdup_printf ("mail operation queue (%02d)\n-------------------------", len);
540         if (len == 0) {
541                 gchar *copy;
542                 copy = str;
543                 str = g_strdup_printf ("%s\n%s", copy, "<empty>");
544                 g_free (copy);
545         } else {
546                 g_mutex_lock (priv->queue_lock);
547                 g_queue_foreach (priv->op_queue, (GFunc)accumulate_mail_op_strings, &str);
548                 g_mutex_unlock (priv->queue_lock);
549         }
550
551         return str;
552 }
553
554 gboolean
555 modest_mail_operation_queue_running_shutdown (ModestMailOperationQueue *self)
556 {
557         ModestMailOperationQueuePrivate *priv;
558
559         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self), FALSE);
560
561         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
562         return priv->running_final_sync;
563 }
564
565 void
566 modest_mail_operation_queue_set_running_shutdown (ModestMailOperationQueue *self)
567 {
568         ModestMailOperationQueuePrivate *priv;
569
570         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
571
572         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
573         priv->running_final_sync = TRUE;
574 }