* Fixes NB#70692 with tinymail commit 2956. The device won't try to connect if it...
[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
35 /* 'private'/'protected' functions */
36 static void modest_mail_operation_queue_class_init (ModestMailOperationQueueClass *klass);
37 static void modest_mail_operation_queue_init       (ModestMailOperationQueue *obj);
38 static void modest_mail_operation_queue_finalize   (GObject *obj);
39
40 static void
41 on_operation_finished (ModestMailOperation *mail_op,
42                        gpointer user_data);
43
44 /* list my signals  */
45 enum {
46         QUEUE_CHANGED_SIGNAL,
47         QUEUE_EMPTY_SIGNAL,
48         NUM_SIGNALS
49 };
50
51 typedef struct _ModestMailOperationQueuePrivate ModestMailOperationQueuePrivate;
52 struct _ModestMailOperationQueuePrivate {
53         GQueue *op_queue;
54         GMutex *queue_lock;
55         guint   op_id;
56 };
57 #define MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
58                                                          MODEST_TYPE_MAIL_OPERATION_QUEUE, \
59                                                          ModestMailOperationQueuePrivate))
60 /* globals */
61 static GObjectClass *parent_class = NULL;
62
63 static guint signals[NUM_SIGNALS] = {0};
64
65 GType
66 modest_mail_operation_queue_get_type (void)
67 {
68         static GType my_type = 0;
69         if (!my_type) {
70                 static const GTypeInfo my_info = {
71                         sizeof(ModestMailOperationQueueClass),
72                         NULL,           /* base init */
73                         NULL,           /* base finalize */
74                         (GClassInitFunc) modest_mail_operation_queue_class_init,
75                         NULL,           /* class finalize */
76                         NULL,           /* class data */
77                         sizeof(ModestMailOperationQueue),
78                         1,              /* n_preallocs */
79                         (GInstanceInitFunc) modest_mail_operation_queue_init,
80                         NULL
81                 };
82
83                 my_type = g_type_register_static (G_TYPE_OBJECT,
84                                                   "ModestMailOperationQueue",
85                                                   &my_info, 0);
86         }
87         return my_type;
88 }
89
90 static void
91 modest_mail_operation_queue_class_init (ModestMailOperationQueueClass *klass)
92 {
93         GObjectClass *gobject_class;
94
95         gobject_class = (GObjectClass*) klass;
96         parent_class  = g_type_class_peek_parent (klass);
97
98         gobject_class->finalize    = modest_mail_operation_queue_finalize;
99
100         g_type_class_add_private (gobject_class, sizeof(ModestMailOperationQueuePrivate));
101
102         /**
103          * ModestMailOperationQueue::queue-changed
104          * @self: the #ModestMailOperationQueue that emits the signal
105          * @mail_op: the #ModestMailOperation affected
106          * @type: the type of change in the queue
107          * @user_data: user data set when the signal handler was connected
108          *
109          * Emitted whenever the contents of the queue change
110          */
111         signals[QUEUE_CHANGED_SIGNAL] =
112                 g_signal_new ("queue-changed",
113                               G_TYPE_FROM_CLASS (gobject_class),
114                               G_SIGNAL_RUN_FIRST,
115                               G_STRUCT_OFFSET (ModestMailOperationQueueClass, queue_changed),
116                               NULL, NULL,
117                               modest_marshal_VOID__POINTER_INT,
118                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_INT);
119
120         /**
121          * ModestMailOperationQueue::queue-empty
122          * @self: the #ModestMailOperationQueue that emits the signal
123          * @user_data: user data set when the signal handler was connected
124          *
125          * Issued whenever the queue is empty
126          */
127         signals[QUEUE_EMPTY_SIGNAL] =
128                 g_signal_new ("queue-empty",
129                               G_TYPE_FROM_CLASS (gobject_class),
130                               G_SIGNAL_RUN_FIRST,
131                               G_STRUCT_OFFSET (ModestMailOperationQueueClass, queue_empty),
132                               NULL, NULL,
133                               g_cclosure_marshal_VOID__VOID,
134                               G_TYPE_NONE, 0);
135
136 }
137
138 static void
139 modest_mail_operation_queue_init (ModestMailOperationQueue *obj)
140 {
141         ModestMailOperationQueuePrivate *priv;
142
143         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(obj);
144
145         priv->op_queue   = g_queue_new ();
146         priv->queue_lock = g_mutex_new ();
147         priv->op_id = 0;
148 }
149
150 static void
151 on_finalize_foreach(gpointer op,
152                     gpointer user_data)
153 {
154         ModestMailOperationQueue *queue;
155         ModestMailOperationQueuePrivate *priv;
156         ModestMailOperation *mail_op;
157
158         queue = MODEST_MAIL_OPERATION_QUEUE (user_data);
159         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE (queue);
160         mail_op = MODEST_MAIL_OPERATION (op);
161
162         /* Simply remove from queue, but without emitting a
163          * QUEUE_CHANGED_SIGNAL because we are in finalize anyway and have
164          * the lock acquired. */
165         g_signal_handlers_disconnect_by_func (mail_op, G_CALLBACK (on_operation_finished), user_data);
166
167         modest_mail_operation_cancel (mail_op);
168         g_queue_remove (priv->op_queue, mail_op);
169         g_object_unref (G_OBJECT (mail_op));
170 }
171
172 static void
173 modest_mail_operation_queue_finalize (GObject *obj)
174 {
175         ModestMailOperationQueuePrivate *priv;
176
177         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(obj);
178
179         g_mutex_lock (priv->queue_lock);
180
181         if (priv->op_queue) {
182                 /* Cancel all */
183                 if (!g_queue_is_empty (priv->op_queue)) {
184                         g_queue_foreach (priv->op_queue,
185                                          (GFunc)on_finalize_foreach,
186                                          MODEST_MAIL_OPERATION_QUEUE (obj));
187                 }
188
189                 g_queue_free (priv->op_queue);
190         }
191
192         g_mutex_unlock (priv->queue_lock);
193         g_mutex_free (priv->queue_lock);
194         
195         G_OBJECT_CLASS(parent_class)->finalize (obj);
196 }
197
198 ModestMailOperationQueue *
199 modest_mail_operation_queue_new (void)
200 {
201         ModestMailOperationQueue *self = g_object_new (MODEST_TYPE_MAIL_OPERATION_QUEUE, NULL);
202
203         return MODEST_MAIL_OPERATION_QUEUE (self);
204 }
205
206 static void
207 on_operation_finished (ModestMailOperation *mail_op,
208                        gpointer user_data)
209 {
210         ModestMailOperationQueue *queue = MODEST_MAIL_OPERATION_QUEUE (user_data);
211
212         /* Remove operation from queue when finished */
213         modest_mail_operation_queue_remove (queue, mail_op);
214 }
215
216 void 
217 modest_mail_operation_queue_add (ModestMailOperationQueue *self, 
218                                  ModestMailOperation *mail_op)
219 {
220         ModestMailOperationQueuePrivate *priv;
221
222         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
223         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
224         
225         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
226
227         g_mutex_lock (priv->queue_lock);
228         g_queue_push_tail (priv->op_queue, g_object_ref (mail_op));
229         g_mutex_unlock (priv->queue_lock);
230
231         /* Get notified when the operation ends to remove it from the
232            queue. We connect it using the *after* because we want to
233            let the other handlers for the finish function happen
234            before this */
235         g_signal_connect_after (G_OBJECT (mail_op), 
236                                 "operation-finished",
237                                 G_CALLBACK (on_operation_finished), 
238                                 self);
239
240         /* Notify observers */
241         g_signal_emit (self, signals[QUEUE_CHANGED_SIGNAL], 0,
242                        mail_op, MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED);
243 }
244
245 void 
246 modest_mail_operation_queue_remove (ModestMailOperationQueue *self,
247                                     ModestMailOperation *mail_op)
248 {
249         ModestMailOperationQueuePrivate *priv;
250         ModestMailOperationStatus status;
251
252         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
253         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
254
255         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
256
257         g_mutex_lock (priv->queue_lock);
258         g_queue_remove (priv->op_queue, mail_op);
259         g_mutex_unlock (priv->queue_lock);
260
261         g_signal_handlers_disconnect_by_func (G_OBJECT (mail_op),
262                                               G_CALLBACK (on_operation_finished),
263                                               self);
264
265         /* Notify observers */
266         g_signal_emit (self, signals[QUEUE_CHANGED_SIGNAL], 0,
267                        mail_op, MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED);
268
269         /* Check errors */
270         status = modest_mail_operation_get_status (mail_op);
271         if (status != MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
272                 /* This is a sanity check. Shouldn't be needed, but
273                    prevent possible application crashes. It's useful
274                    also for detecting mail operations with invalid
275                    status and error handling */
276                 if (modest_mail_operation_get_error (mail_op) != NULL) {
277                         modest_mail_operation_execute_error_handler (mail_op);
278                 } else {
279                         if (status == MODEST_MAIL_OPERATION_STATUS_CANCELED) 
280                                 g_warning ("%s: operation canceled \n", __FUNCTION__);
281                         else
282                                 g_warning ("%s: possible error in a mail operation " \
283                                            "implementation. The status is not successful " \
284                                            "but the mail operation does not have any " \
285                                            "error set\n", __FUNCTION__);
286                 }
287         }
288
289         /* Free object */
290
291         /* We do not own the last reference when this operation is deleted
292          * as response to a progress changed signal from the mail operation
293          * itself, in which case the glib signal system owns a reference
294          * until the signal emission is complete. armin. */
295         /* modest_runtime_verify_object_last_ref (mail_op, ""); */
296         g_object_unref (G_OBJECT (mail_op));
297
298         /* Emit the queue empty-signal */
299         g_signal_emit (self, signals[QUEUE_EMPTY_SIGNAL], 0);
300 }
301
302 guint 
303 modest_mail_operation_queue_num_elements (ModestMailOperationQueue *self)
304 {
305         ModestMailOperationQueuePrivate *priv;
306         guint length = 0;
307
308         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self), 0);
309         
310         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
311
312         g_mutex_lock (priv->queue_lock);
313         length = g_queue_get_length (priv->op_queue);
314         g_mutex_unlock (priv->queue_lock);
315
316         return length;
317 }
318
319 void 
320 modest_mail_operation_queue_cancel (ModestMailOperationQueue *self, 
321                                     ModestMailOperation *mail_op)
322 {
323         ModestMailOperationQueuePrivate *priv;
324
325         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
326         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
327
328         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
329
330         /* This triggers a progess_changed signal in which we remove
331          * the operation from the queue. */
332         modest_mail_operation_cancel (mail_op);
333 }
334
335 static void
336 on_cancel_all_foreach (gpointer op, gpointer list)
337 {
338         GSList **new_list;
339
340         new_list = (GSList**) list;
341         *new_list = g_slist_prepend (*new_list, MODEST_MAIL_OPERATION (op));
342 }
343
344 void 
345 modest_mail_operation_queue_cancel_all (ModestMailOperationQueue *self)
346 {
347         ModestMailOperationQueuePrivate *priv;
348         GSList* operations_to_cancel = NULL;
349         GSList* cur = NULL;
350
351         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
352
353         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
354
355         /* Remember which operations to cancel. This is the only thing that
356          * is done while holding the lock, so we do not need to call
357          * functions from other files while holding the lock, which could
358          * lead to a deadlock if such a call re-enters into this queue and
359          * tries to acquire another lock. */
360         g_mutex_lock (priv->queue_lock);
361         g_queue_foreach (priv->op_queue, (GFunc) on_cancel_all_foreach, &operations_to_cancel);
362         g_mutex_unlock (priv->queue_lock);
363         
364         /* TODO: Reverse the list, to remove operations in order? */
365
366         for(cur = operations_to_cancel; cur != NULL; cur = cur->next) {
367                 if (!MODEST_IS_MAIL_OPERATION(cur->data))
368                         g_printerr ("modest: cur->data is not a valid mail operation\n");
369                 else
370                         modest_mail_operation_cancel (MODEST_MAIL_OPERATION (cur->data));
371         }
372
373         g_slist_free(operations_to_cancel);
374 }