2007-06-12 Murray Cumming <murrayc@murrayc.com>
[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_progress_changed (ModestMailOperation *mail_op,
42                      ModestMailOperationState *state,
43                      gpointer user_data);
44
45 /* list my signals  */
46 enum {
47         QUEUE_CHANGED_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 static void
122 modest_mail_operation_queue_init (ModestMailOperationQueue *obj)
123 {
124         ModestMailOperationQueuePrivate *priv;
125
126         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(obj);
127
128         priv->op_queue   = g_queue_new ();
129         priv->queue_lock = g_mutex_new ();
130         priv->op_id = 0;
131 }
132
133 static void
134 on_finalize_foreach(gpointer op,
135                     gpointer user_data)
136 {
137         ModestMailOperationQueue *queue;
138         ModestMailOperationQueuePrivate *priv;
139         ModestMailOperation *mail_op;
140
141         queue = MODEST_MAIL_OPERATION_QUEUE (user_data);
142         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE (queue);
143         mail_op = MODEST_MAIL_OPERATION (op);
144
145         /* Simply remove from queue, but without emitting a
146          * QUEUE_CHANGED_SIGNAL because we are in finalize anyway and have
147          * the lock acquired. */
148         g_signal_handlers_disconnect_by_func (mail_op, G_CALLBACK (on_progress_changed), user_data);
149
150         modest_mail_operation_cancel (mail_op);
151         g_queue_remove (priv->op_queue, mail_op);
152         g_object_unref (G_OBJECT (mail_op));
153 }
154
155 static void
156 modest_mail_operation_queue_finalize (GObject *obj)
157 {
158         ModestMailOperationQueuePrivate *priv;
159
160         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(obj);
161
162         g_mutex_lock (priv->queue_lock);
163
164         if (priv->op_queue) {
165                 /* Cancel all */
166                 if (!g_queue_is_empty (priv->op_queue)) {
167                         g_queue_foreach (priv->op_queue,
168                                          (GFunc)on_finalize_foreach,
169                                          MODEST_MAIL_OPERATION_QUEUE (obj));
170                 }
171
172                 g_queue_free (priv->op_queue);
173         }
174
175         g_mutex_unlock (priv->queue_lock);
176         g_mutex_free (priv->queue_lock);
177         
178         G_OBJECT_CLASS(parent_class)->finalize (obj);
179 }
180
181 ModestMailOperationQueue *
182 modest_mail_operation_queue_new (void)
183 {
184         ModestMailOperationQueue *self = g_object_new (MODEST_TYPE_MAIL_OPERATION_QUEUE, NULL);
185
186         return MODEST_MAIL_OPERATION_QUEUE (self);
187 }
188
189 static void
190 on_progress_changed (ModestMailOperation *mail_op,
191                      ModestMailOperationState *state,
192                      gpointer user_data)
193 {
194         ModestMailOperationQueue *queue;
195
196         if(!state->finished)
197                 return;
198
199         /* Remove operation from queue when finished */
200         queue = MODEST_MAIL_OPERATION_QUEUE (user_data);
201         modest_mail_operation_queue_remove (queue, mail_op);
202 }
203
204 void 
205 modest_mail_operation_queue_add (ModestMailOperationQueue *self, 
206                                  ModestMailOperation *mail_op)
207 {
208         ModestMailOperationQueuePrivate *priv;
209
210         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
211         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
212         
213         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
214
215         g_mutex_lock (priv->queue_lock);
216         g_queue_push_tail (priv->op_queue, g_object_ref (mail_op));
217         modest_mail_operation_set_id (mail_op, priv->op_id++);
218         g_mutex_unlock (priv->queue_lock);
219
220         /* Get notified when the operation ends to remove it from the queue */
221         g_signal_connect (G_OBJECT (mail_op), "progress_changed",
222                           G_CALLBACK (on_progress_changed), self);
223
224         /* Notify observers */
225         g_signal_emit (self, signals[QUEUE_CHANGED_SIGNAL], 0,
226                        mail_op, MODEST_MAIL_OPERATION_QUEUE_OPERATION_ADDED);
227 }
228
229 void 
230 modest_mail_operation_queue_remove (ModestMailOperationQueue *self,
231                                     ModestMailOperation *mail_op)
232 {
233         ModestMailOperationQueuePrivate *priv;
234         ModestMailOperationStatus status;
235
236         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
237         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
238
239         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
240
241         g_mutex_lock (priv->queue_lock);
242         g_queue_remove (priv->op_queue, mail_op);
243         g_mutex_unlock (priv->queue_lock);
244
245         g_signal_handlers_disconnect_by_func (G_OBJECT (mail_op),
246                                               G_CALLBACK (on_progress_changed),
247                                               self);
248
249         /* Notify observers */
250         g_signal_emit (self, signals[QUEUE_CHANGED_SIGNAL], 0,
251                        mail_op, MODEST_MAIL_OPERATION_QUEUE_OPERATION_REMOVED);
252
253         /* Check errors */
254         status = modest_mail_operation_get_status (mail_op);
255         if (status != MODEST_MAIL_OPERATION_STATUS_SUCCESS) {
256                 /* This is a sanity check. Shouldn't be needed, but
257                    prevent possible application crashes. It's useful
258                    also for detecting mail operations with invalid
259                    status and error handling */
260                 if (modest_mail_operation_get_error (mail_op) != NULL)
261                         modest_mail_operation_execute_error_handler (mail_op);
262                 else {
263                         if (status == MODEST_MAIL_OPERATION_STATUS_CANCELED) 
264                                 g_warning ("%s: operation canceled \n", __FUNCTION__);
265                         else
266                                 g_warning ("%s: possible error in a mail operation " \
267                                            "implementation. The status is not successful " \
268                                            "but the mail operation does not have any " \
269                                            "error set\n", __FUNCTION__);
270                 }
271         }
272
273         /* Free object */
274
275         /* We do not own the last reference when this operation is deleted
276          * as response to a progress changed signal from the mail operation
277          * itself, in which case the glib signal system owns a reference
278          * until the signal emission is complete. armin. */
279         /* modest_runtime_verify_object_last_ref (mail_op, ""); */
280         g_object_unref (G_OBJECT (mail_op));
281 }
282
283 guint 
284 modest_mail_operation_queue_num_elements (ModestMailOperationQueue *self)
285 {
286         ModestMailOperationQueuePrivate *priv;
287         guint length = 0;
288
289         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self), 0);
290         
291         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
292
293         g_mutex_lock (priv->queue_lock);
294         length = g_queue_get_length (priv->op_queue);
295         g_mutex_unlock (priv->queue_lock);
296
297         return length;
298 }
299
300 void 
301 modest_mail_operation_queue_cancel (ModestMailOperationQueue *self, 
302                                     ModestMailOperation *mail_op)
303 {
304         ModestMailOperationQueuePrivate *priv;
305
306         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
307         g_return_if_fail (MODEST_IS_MAIL_OPERATION (mail_op));
308
309         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
310
311         /* This triggers a progess_changed signal in which we remove
312          * the operation from the queue. */
313         modest_mail_operation_cancel (mail_op);
314 }
315
316 static void
317 on_cancel_all_foreach (gpointer op, gpointer list)
318 {
319         g_return_if_fail (list);
320         *((GSList**)list) = g_slist_prepend (*((GSList**)list), MODEST_MAIL_OPERATION (op));
321 }
322
323 void 
324 modest_mail_operation_queue_cancel_all (ModestMailOperationQueue *self)
325 {
326         ModestMailOperationQueuePrivate *priv;
327         GSList* operations_to_cancel = NULL;
328         GSList* cur;
329
330         g_return_if_fail (MODEST_IS_MAIL_OPERATION_QUEUE (self));
331
332         priv = MODEST_MAIL_OPERATION_QUEUE_GET_PRIVATE(self);
333
334         /* Remember which operations to cancel. This is the only thing that
335          * is done while holding the lock, so we do not need to call
336          * functions from other files while holding the lock, which could
337          * lead to a deadlock if such a call re-enters into this queue and
338          * tries to acquire another lock. */
339         g_mutex_lock (priv->queue_lock);
340         g_queue_foreach (priv->op_queue, (GFunc) on_cancel_all_foreach, &operations_to_cancel);
341         g_mutex_unlock (priv->queue_lock);
342         
343         /* TODO: Reverse the list, to remove operations in order? */
344
345         for(cur = operations_to_cancel; cur != NULL; cur = cur->next) {
346                 /* This triggers a progress_changed signal in which we remove
347                  * the operation from the queue. */
348                 if (!MODEST_IS_MAIL_OPERATION(cur->data))
349                         g_printerr ("modest: cur->data is not a valid mail operation\n");
350                 else
351                         modest_mail_operation_cancel (MODEST_MAIL_OPERATION (cur->data));
352         }
353
354         g_slist_free(operations_to_cancel);
355 }