Fixes NB#96600, added feed protocol support
[modest] / src / modest-tny-send-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
31 #include <modest-tny-send-queue.h>
32 #include <tny-simple-list.h>
33 #include <tny-iterator.h>
34 #include <tny-folder.h>
35 #include <tny-error.h>
36 #include <tny-camel-msg.h>
37 #include <tny-folder-change.h>
38 #include <tny-folder-observer.h>
39 #include <modest-tny-account.h>
40 #include <modest-runtime.h>
41 #include <modest-platform.h>
42 #include <widgets/modest-window-mgr.h>
43 #include <modest-marshal.h>
44 #include <modest-debug.h>
45 #include <string.h> /* strcmp */
46
47 /* 'private'/'protected' functions */
48 static void modest_tny_send_queue_class_init (ModestTnySendQueueClass *klass);
49 static void modest_tny_send_queue_finalize   (GObject *obj);
50 static void modest_tny_send_queue_instance_init (GTypeInstance *instance, gpointer g_class);
51
52 /* Signal handlers */ 
53 static void _on_msg_start_sending (TnySendQueue *self, 
54                                    TnyHeader *header, 
55                                    TnyMsg *msg, 
56                                    int done, 
57                                    int total, 
58                                    gpointer user_data);
59
60 static void _on_msg_has_been_sent (TnySendQueue *self, 
61                                    TnyHeader *header, 
62                                    TnyMsg *msg, 
63                                    int done, 
64                                    int total, 
65                                    gpointer user_data);
66
67 static void _on_msg_error_happened (TnySendQueue *self, 
68                                     TnyHeader *header, 
69                                     TnyMsg *msg, 
70                                     GError *err, 
71                                     gpointer user_data);
72
73 static void _on_queue_start        (TnySendQueue *self, 
74                                     gpointer user_data);
75
76 static void _on_queue_stop         (TnySendQueue *self,
77                                     gpointer data);
78
79 static void modest_tny_send_queue_add_async (TnySendQueue *self, 
80                                              TnyMsg *msg, 
81                                              TnySendQueueAddCallback callback, 
82                                              TnyStatusCallback status_callback, 
83                                              gpointer user_data);
84
85 static TnyFolder* modest_tny_send_queue_get_outbox  (TnySendQueue *self);
86 static TnyFolder* modest_tny_send_queue_get_sentbox (TnySendQueue *self);
87
88 /* list my signals  */
89 enum {
90         STATUS_CHANGED_SIGNAL,
91         LAST_SIGNAL
92 };
93
94 typedef struct _SendInfo SendInfo;
95 struct _SendInfo {
96         gchar* msg_id;
97         ModestTnySendQueueStatus status;
98 };
99
100 typedef struct _ModestTnySendQueuePrivate ModestTnySendQueuePrivate;
101 struct _ModestTnySendQueuePrivate {
102         /* Queued infos */
103         GQueue* queue;
104
105         /* The info that is currently being sent */
106         GList* current;
107
108         /* Special folders */
109         TnyFolder *outbox;
110         TnyFolder *sentbox;
111
112         /* last was send receive operation?*/
113         gboolean requested_send_receive;
114
115         gboolean sending;
116 };
117
118 #define MODEST_TNY_SEND_QUEUE_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
119                                                    MODEST_TYPE_TNY_SEND_QUEUE, \
120                                                    ModestTnySendQueuePrivate))
121
122 /* globals */
123 static TnyCamelSendQueueClass *parent_class = NULL;
124
125 /* uncomment the following if you have defined any signals */
126 static guint signals[LAST_SIGNAL] = {0};
127
128 /*
129  * this thread actually tries to send all the mails in the outbox and keeps
130  * track of their state.
131  */
132
133 static int
134 on_modest_tny_send_queue_compare_id (gconstpointer info, gconstpointer msg_id)
135 {
136         g_return_val_if_fail (info && ((SendInfo*)info)->msg_id && msg_id, -1);
137         
138         return strcmp( ((SendInfo*)info)->msg_id, msg_id);
139 }
140
141 static void
142 modest_tny_send_queue_info_free (SendInfo *info)
143 {
144         g_free(info->msg_id);
145         g_slice_free(SendInfo, info);
146 }
147
148 static GList*
149 modest_tny_send_queue_lookup_info (ModestTnySendQueue *self, const gchar *msg_id)
150 {
151         ModestTnySendQueuePrivate *priv;
152         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
153         
154         return g_queue_find_custom (priv->queue, msg_id, on_modest_tny_send_queue_compare_id);
155 }
156
157
158 static void
159 queue_item_to_string (gpointer data, gchar **user_data)
160 {
161         SendInfo *info = (SendInfo*)data;
162         const gchar *status;
163         gchar *tmp;
164         
165         if (!(user_data && *user_data))
166                 return;
167         
168         switch (info->status) {
169         case MODEST_TNY_SEND_QUEUE_UNKNOWN: status = "UNKNOWN"; break;
170         case MODEST_TNY_SEND_QUEUE_WAITING: status = "WAITING"; break;
171         case MODEST_TNY_SEND_QUEUE_SUSPENDED: status = "SUSPENDED"; break;
172         case MODEST_TNY_SEND_QUEUE_SENDING: status = "SENDING"; break;
173         case MODEST_TNY_SEND_QUEUE_FAILED: status = "FAILED"; break;
174         default: status= "UNEXPECTED"; break;
175         }
176
177         tmp = g_strdup_printf ("%s\"%s\" => [%s]\n",
178                                *user_data, info->msg_id, status);
179         g_free (*user_data);
180         *user_data = tmp;
181 }
182
183 gchar*
184 modest_tny_send_queue_to_string (ModestTnySendQueue *self)
185 {
186         gchar *str;
187         ModestTnySendQueuePrivate *priv;
188
189         g_return_val_if_fail (MODEST_IS_TNY_SEND_QUEUE(self), NULL);
190         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
191
192         str = g_strdup_printf ("items in the send queue: %d\n",
193                                g_queue_get_length (priv->queue));
194         
195         g_queue_foreach (priv->queue, (GFunc)queue_item_to_string, &str);
196
197         return str;
198 }
199
200 typedef struct {
201         TnySendQueueAddCallback callback;
202         gpointer user_data;
203 } AddAsyncHelper;
204
205 static void
206 _on_added_to_outbox (TnySendQueue *self, 
207                      gboolean cancelled, 
208                      TnyMsg *msg, 
209                      GError *err,
210                      gpointer user_data) 
211 {
212         ModestTnySendQueuePrivate *priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE(self);
213         TnyHeader *header = NULL;
214         SendInfo *info = NULL;
215         GList* existing = NULL;
216         gchar* msg_id = NULL;
217         AddAsyncHelper *helper;
218
219         g_return_if_fail (TNY_IS_SEND_QUEUE(self));
220         g_return_if_fail (TNY_IS_CAMEL_MSG(msg));
221
222         header = tny_msg_get_header (msg);
223         msg_id = modest_tny_send_queue_get_msg_id (header);
224         if (!msg_id) {
225                 g_warning ("%s: No msg_id returned for header", __FUNCTION__);
226                 goto end;
227         }
228
229         /* Put newly added message in WAITING state */
230         existing = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE(self), msg_id);
231         if(existing != NULL) {
232                 info = existing->data;
233                 info->status = MODEST_TNY_SEND_QUEUE_WAITING;
234         } else {
235                 info = g_slice_new (SendInfo);
236                 info->msg_id = msg_id;
237                 info->status = MODEST_TNY_SEND_QUEUE_WAITING;
238                 g_queue_push_tail (priv->queue, info);
239         }
240
241         g_signal_emit (self, signals[STATUS_CHANGED_SIGNAL], 0, info->msg_id, info->status);
242
243  end:
244         g_object_unref (G_OBJECT(header));
245
246         /* Call the user callback */
247         helper = (AddAsyncHelper *) user_data;
248         if (helper->callback)
249                 helper->callback (self, cancelled, msg, err, helper->user_data);
250         g_slice_free (AddAsyncHelper, helper);
251 }
252
253 static void
254 _add_message (ModestTnySendQueue *self, TnyHeader *header)
255 {
256         ModestWindowMgr *mgr = NULL;
257         ModestTnySendQueuePrivate *priv;
258         SendInfo *info = NULL;
259         GList* existing = NULL;
260         gchar* msg_uid = NULL;
261         ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
262         gboolean editing = FALSE;
263
264         g_return_if_fail (TNY_IS_SEND_QUEUE(self));
265         g_return_if_fail (TNY_IS_HEADER(header));
266         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
267         
268         /* Check whether the mail is already in the queue */
269         msg_uid = modest_tny_send_queue_get_msg_id (header);
270         status = modest_tny_send_queue_get_msg_status (self, msg_uid);
271         switch (status) {
272         case MODEST_TNY_SEND_QUEUE_UNKNOWN:
273         case MODEST_TNY_SEND_QUEUE_SUSPENDED:
274         case MODEST_TNY_SEND_QUEUE_FAILED:
275
276                 /* Check if it already exists on queue */
277                 existing = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE(self), msg_uid);
278                 if(existing != NULL)
279                         break;
280                 
281                 /* Check if its being edited */
282                 mgr = modest_runtime_get_window_mgr ();
283                 editing = modest_window_mgr_find_registered_header (mgr, header, NULL);
284                 if (editing)
285                         break;
286                 
287                 /* Add new meesage info */
288                 info = g_slice_new0 (SendInfo);
289                 info->msg_id = strdup(msg_uid);
290                 info->status = MODEST_TNY_SEND_QUEUE_WAITING;
291                 g_queue_push_tail (priv->queue, info);
292                 break;
293         default:
294                 break;
295         }
296
297         /* Free */
298         g_free(msg_uid);
299 }
300
301 static void 
302 modest_tny_send_queue_add_async (TnySendQueue *self, 
303                                  TnyMsg *msg, 
304                                  TnySendQueueAddCallback callback, 
305                                  TnyStatusCallback status_callback, 
306                                  gpointer user_data)
307 {
308         AddAsyncHelper *helper = g_slice_new0 (AddAsyncHelper);
309         helper->callback = callback;
310         helper->user_data = user_data;
311
312         /* Call the superclass passing our own callback */
313         TNY_CAMEL_SEND_QUEUE_CLASS(parent_class)->add_async (self, msg, 
314                                                              _on_added_to_outbox, 
315                                                              status_callback, 
316                                                              helper);
317 }
318
319
320 static TnyFolder*
321 modest_tny_send_queue_get_sentbox (TnySendQueue *self)
322 {
323         ModestTnySendQueuePrivate *priv;
324
325         g_return_val_if_fail (self, NULL);
326
327         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
328
329         return g_object_ref (priv->sentbox);
330 }
331
332
333 static TnyFolder*
334 modest_tny_send_queue_get_outbox (TnySendQueue *self)
335 {
336         ModestTnySendQueuePrivate *priv;
337
338         g_return_val_if_fail (self, NULL);
339
340         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
341
342         return g_object_ref (priv->outbox);
343 }
344
345 GType
346 modest_tny_send_queue_get_type (void)
347 {
348         static GType my_type = 0;
349
350         if (my_type == 0) {
351                 static const GTypeInfo my_info = {
352                         sizeof(ModestTnySendQueueClass),
353                         NULL,           /* base init */
354                         NULL,           /* base finalize */
355                         (GClassInitFunc) modest_tny_send_queue_class_init,
356                         NULL,           /* class finalize */
357                         NULL,           /* class data */
358                         sizeof(ModestTnySendQueue),
359                         0,              /* n_preallocs */
360                         (GInstanceInitFunc) modest_tny_send_queue_instance_init,
361                         NULL
362                 };
363                 
364                 my_type = g_type_register_static (TNY_TYPE_CAMEL_SEND_QUEUE,
365                                                   "ModestTnySendQueue",
366                                                   &my_info, 0);
367         }
368         return my_type;
369 }
370
371
372 static void
373 modest_tny_send_queue_class_init (ModestTnySendQueueClass *klass)
374 {
375         GObjectClass *gobject_class;
376
377         gobject_class = (GObjectClass*) klass;
378         
379         parent_class            = g_type_class_peek_parent (klass);
380         gobject_class->finalize = modest_tny_send_queue_finalize;
381
382         TNY_CAMEL_SEND_QUEUE_CLASS(klass)->add_async   = modest_tny_send_queue_add_async;
383         TNY_CAMEL_SEND_QUEUE_CLASS(klass)->get_outbox  = modest_tny_send_queue_get_outbox;
384         TNY_CAMEL_SEND_QUEUE_CLASS(klass)->get_sentbox = modest_tny_send_queue_get_sentbox;
385         klass->status_changed   = NULL;
386
387         signals[STATUS_CHANGED_SIGNAL] =
388                 g_signal_new ("status_changed",
389                               G_TYPE_FROM_CLASS (gobject_class),
390                               G_SIGNAL_RUN_FIRST,
391                               G_STRUCT_OFFSET (ModestTnySendQueueClass, status_changed),
392                               NULL, NULL,
393                               modest_marshal_VOID__STRING_INT,
394                               G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);
395
396         g_type_class_add_private (gobject_class, sizeof(ModestTnySendQueuePrivate));
397 }
398
399 static void
400 modest_tny_send_queue_instance_init (GTypeInstance *instance, gpointer g_class)
401 {
402         ModestTnySendQueuePrivate *priv;
403
404         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (instance);
405         priv->queue = g_queue_new();
406         priv->current = NULL;
407         priv->outbox = NULL;
408         priv->sentbox = NULL;
409         priv->sending = FALSE;
410 }
411
412 static void
413 modest_tny_send_queue_finalize (GObject *obj)
414 {
415         ModestTnySendQueuePrivate *priv;
416
417         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (obj);
418
419         g_queue_foreach (priv->queue, (GFunc)modest_tny_send_queue_info_free, NULL);
420         g_queue_free (priv->queue);
421
422         G_OBJECT_CLASS(parent_class)->finalize (obj);
423         g_object_unref (priv->outbox);
424         g_object_unref (priv->sentbox);
425 }
426
427 typedef struct {
428         TnyCamelTransportAccount *account;
429         ModestTnySendQueue *queue;
430 } GetHeadersInfo;
431
432 static void
433 new_queue_get_headers_async_cb (TnyFolder *folder, 
434                                 gboolean cancelled, 
435                                 TnyList *headers, 
436                                 GError *err, 
437                                 gpointer user_data)
438 {
439         ModestTnySendQueue *self;
440         TnyIterator *iter;
441         GetHeadersInfo *info;
442         ModestMailOperation *wakeup_op;
443
444         info = (GetHeadersInfo *) user_data;
445         self = MODEST_TNY_SEND_QUEUE (info->queue);
446
447         /* In case of error set the transport account anyway */
448         if (cancelled || err)
449                 goto set_transport;
450
451         /* Add messages to our internal queue */
452         iter = tny_list_create_iterator (headers);
453         while (!tny_iterator_is_done (iter)) {
454                 TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iter));
455                 _add_message (self, header);
456                 g_object_unref (header);        
457                 tny_iterator_next (iter);
458         }
459
460         /* Reenable suspended items */
461         wakeup_op = modest_mail_operation_new (NULL);
462         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
463                                          wakeup_op);
464         modest_mail_operation_queue_wakeup (wakeup_op, MODEST_TNY_SEND_QUEUE (self));
465
466         /* Frees */
467         g_object_unref (iter);
468         g_object_unref (headers);
469
470  set_transport:
471         /* Do this at the end, because it'll call tny_send_queue_flush
472            which will call tny_send_queue_get_outbox and
473            tny_send_queue_get_sentbox */
474         tny_camel_send_queue_set_transport_account (TNY_CAMEL_SEND_QUEUE(self),
475                                                     info->account);
476
477         /* Frees */
478         g_object_unref (info->account); 
479         g_object_unref (info->queue); 
480         g_slice_free (GetHeadersInfo, info);
481 }
482
483 ModestTnySendQueue*
484 modest_tny_send_queue_new (TnyCamelTransportAccount *account)
485 {
486         ModestTnySendQueue *self = NULL;
487         ModestTnySendQueuePrivate *priv = NULL;
488         TnyList *headers = NULL;
489         GetHeadersInfo *info;
490         
491         g_return_val_if_fail (TNY_IS_CAMEL_TRANSPORT_ACCOUNT(account), NULL);
492         
493         self = MODEST_TNY_SEND_QUEUE(g_object_new(MODEST_TYPE_TNY_SEND_QUEUE, NULL));
494
495         /* Set outbox and sentbox */
496         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
497         priv->outbox  = modest_tny_account_get_special_folder (TNY_ACCOUNT(account),
498                                                                TNY_FOLDER_TYPE_OUTBOX);
499         priv->sentbox = modest_tny_account_get_special_folder (TNY_ACCOUNT(account),
500                                                                TNY_FOLDER_TYPE_SENT);
501
502         /* NOTE that this could happen if there was not enough disk
503            space when the account was created */
504         if (!priv->outbox || !priv->sentbox) {
505                 g_object_unref (self);
506                 return NULL;
507         }
508
509         /* Connect signals to control when a msg is being or has been sent */
510         g_signal_connect (G_OBJECT(self), "msg-sending",
511                           G_CALLBACK(_on_msg_start_sending),
512                           NULL);                          
513         g_signal_connect (G_OBJECT(self), "msg-sent",
514                           G_CALLBACK(_on_msg_has_been_sent), 
515                           NULL);
516         g_signal_connect (G_OBJECT(self), "error-happened",
517                           G_CALLBACK(_on_msg_error_happened),
518                           NULL);
519
520         g_signal_connect (G_OBJECT (self), "queue-start",
521                           G_CALLBACK (_on_queue_start),
522                           NULL);
523
524         g_signal_connect (G_OBJECT (self), "queue-stop",
525                           G_CALLBACK (_on_queue_stop),
526                           NULL);
527
528         priv->requested_send_receive = FALSE;
529
530         headers = tny_simple_list_new ();
531         info = g_slice_new0 (GetHeadersInfo);
532         info->account = g_object_ref (account);
533         info->queue = g_object_ref (self);
534         tny_folder_get_headers_async (priv->outbox, headers, TRUE, 
535                                       new_queue_get_headers_async_cb, 
536                                       NULL, info);
537
538         return self;
539 }
540
541 gboolean
542 modest_tny_send_queue_msg_is_being_sent (ModestTnySendQueue* self,
543                                          const gchar *msg_id)
544 {       
545         ModestTnySendQueueStatus status;
546         
547         g_return_val_if_fail (msg_id != NULL, FALSE); 
548         
549         status = modest_tny_send_queue_get_msg_status (self, msg_id);
550         return status == MODEST_TNY_SEND_QUEUE_SENDING;
551 }
552
553 gboolean
554 modest_tny_send_queue_sending_in_progress (ModestTnySendQueue* self)
555 {       
556         ModestTnySendQueuePrivate *priv;
557
558         g_return_val_if_fail (MODEST_IS_TNY_SEND_QUEUE(self), FALSE);
559         
560         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
561         
562         return priv->sending;
563 }
564
565 ModestTnySendQueueStatus
566 modest_tny_send_queue_get_msg_status (ModestTnySendQueue *self, const gchar *msg_id)
567 {
568         GList *item;
569
570         g_return_val_if_fail (MODEST_IS_TNY_SEND_QUEUE(self), MODEST_TNY_SEND_QUEUE_UNKNOWN);
571         g_return_val_if_fail (msg_id, MODEST_TNY_SEND_QUEUE_UNKNOWN);
572
573         item = modest_tny_send_queue_lookup_info (self, msg_id);
574         if (!item)
575                 return MODEST_TNY_SEND_QUEUE_UNKNOWN;
576         else
577                 return ((SendInfo*)item->data)->status;
578 }
579
580 gchar *
581 modest_tny_send_queue_get_msg_id (TnyHeader *header)
582 {
583         gchar* msg_uid = NULL;
584         gchar *subject;
585         time_t date_received;
586                 
587         g_return_val_if_fail (header && TNY_IS_HEADER(header), NULL);
588
589         /* Get message uid */
590         subject = tny_header_dup_subject (header);
591         date_received = tny_header_get_date_received (header);
592
593         msg_uid = g_strdup_printf ("%s %d", subject, (int) date_received);
594         g_free (subject);
595
596         return msg_uid;
597 }
598
599
600 static void
601 _on_msg_start_sending (TnySendQueue *self, TnyHeader *header,
602                        TnyMsg *msg, int done, int total, gpointer user_data)
603 {
604         ModestTnySendQueuePrivate *priv = NULL;
605         GList *item = NULL;
606         SendInfo *info = NULL;
607         gchar *msg_id = NULL;
608
609         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
610         
611         /* Get message uid */
612         msg_id = modest_tny_send_queue_get_msg_id (header);
613         if (msg_id) 
614                 item = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE (self), msg_id);
615         else
616                 g_warning ("%s: could not get msg-id for header", __FUNCTION__);
617         
618         if (item) {
619                 /* Set current status item */
620                 info = item->data;
621                 info->status = MODEST_TNY_SEND_QUEUE_SENDING;
622                 g_signal_emit (self, signals[STATUS_CHANGED_SIGNAL], 0, info->msg_id, info->status);
623                 priv->current = item;
624         } else
625                 g_warning ("%s: could not find item with id '%s'", __FUNCTION__, msg_id);
626         
627         /* free */
628         g_free (msg_id);
629 }
630
631 static void 
632 _on_msg_has_been_sent (TnySendQueue *self,
633                        TnyHeader *header,
634                        TnyMsg *msg, 
635                        int done, 
636                        int total,
637                        gpointer user_data)
638 {
639         ModestTnySendQueuePrivate *priv;
640         gchar *msg_id = NULL;
641         GList *item;
642
643         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
644
645         /* Get message uid */
646         msg_id = modest_tny_send_queue_get_msg_id (header);
647
648         tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
649
650         tny_folder_sync_async (priv->sentbox, FALSE, NULL, NULL, NULL);
651
652         /* Get status info */
653         item = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE (self), msg_id);
654
655
656         /* TODO: note that item=NULL must not happen, but I found that
657            tinymail is issuing the message-sent signal twice, because
658            tny_camel_send_queue_update is called twice for each
659            message sent. This must be fixed in tinymail. Sergio */
660         if (item) {
661                 /* Remove status info */
662                 modest_tny_send_queue_info_free (item->data);
663                 g_queue_delete_link (priv->queue, item);
664                 priv->current = NULL;
665                 
666                 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_sent"));
667         }
668
669         /* free */
670         g_free(msg_id);
671 }
672
673 static void 
674 _on_msg_error_happened (TnySendQueue *self,
675                         TnyHeader *header,
676                         TnyMsg *msg,
677                         GError *err,
678                         gpointer user_data)
679 {
680         ModestTnySendQueuePrivate *priv = NULL;
681         
682         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
683
684         /* Note that header could be NULL. Tinymail notifies about
685            generic send queue errors with this signal as well, and
686            those notifications are not bound to any particular header
687            or message */
688         if (header && TNY_IS_HEADER (header)) {
689                 SendInfo *info = NULL;
690                 GList *item = NULL;
691                 gchar* msg_uid = NULL;
692
693                 /* Get sending info (create new if it doesn not exist) */
694                 msg_uid = modest_tny_send_queue_get_msg_id (header);
695                 item = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE (self), 
696                                                           msg_uid);
697
698                 /* TODO: this should not happen (but it does), so the
699                    problem should be located in the way we generate
700                    the message uids */
701                 if (!item) {
702                         g_warning ("%s: could not find item with id '%s'", __FUNCTION__, msg_uid);
703                         g_free(msg_uid);
704                         return;
705                 }
706
707                 info = item->data;
708
709                 /* Keep in queue so that we remember that the opertion has failed */
710                 /* and was not just cancelled */
711                 if (err->code == TNY_SYSTEM_ERROR_CANCEL) {
712                         info->status = MODEST_TNY_SEND_QUEUE_SUSPENDED;
713                 } else {
714                         info->status = MODEST_TNY_SEND_QUEUE_FAILED;
715                 }
716                 priv->current = NULL;
717
718                 /* Notify status has changed */
719                 g_signal_emit (self, signals[STATUS_CHANGED_SIGNAL], 0, info->msg_id, info->status);
720
721                 /* free */
722                 g_free(msg_uid);
723         }
724 }
725
726 static void 
727 _on_queue_start (TnySendQueue *self,
728                  gpointer data)
729 {
730         ModestTnySendQueuePrivate *priv;
731         ModestMailOperation *mail_op;
732
733         mail_op = modest_mail_operation_new (NULL);
734         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
735                                          mail_op);
736         modest_mail_operation_run_queue (mail_op, MODEST_TNY_SEND_QUEUE (self));
737         g_object_unref (mail_op);
738
739         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
740         priv->sending = TRUE;
741 }
742
743 static void 
744 _on_queue_stop (TnySendQueue *self,
745                 gpointer data)
746 {
747         ModestTnySendQueuePrivate *priv;
748
749         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
750         priv->sending = FALSE;
751 }
752
753 static void 
754 fill_list_of_caches (gpointer key, gpointer value, gpointer userdata)
755 {
756         GSList **send_queues = (GSList **) userdata;
757         *send_queues = g_slist_prepend (*send_queues, value);
758 }
759
760 /* This function shouldn't be here. Move it to another place. Sergio */
761 ModestTnySendQueueStatus
762 modest_tny_all_send_queues_get_msg_status (TnyHeader *header)
763 {
764         ModestCacheMgr *cache_mgr = NULL;
765         GHashTable     *send_queue_cache = NULL;
766         ModestTnyAccountStore *accounts_store = NULL;
767         TnyList *accounts = NULL;
768         TnyIterator *iter = NULL;
769         TnyTransportAccount *account = NULL;
770         GSList *send_queues = NULL, *node;
771         /* get_msg_status returns suspended by default, so we want to detect changes */
772         ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
773         ModestTnySendQueueStatus queue_status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
774         gchar *msg_uid = NULL;
775         ModestTnySendQueue *send_queue = NULL;
776         
777         g_return_val_if_fail (TNY_IS_HEADER(header), MODEST_TNY_SEND_QUEUE_UNKNOWN);
778
779         msg_uid = modest_tny_send_queue_get_msg_id (header);
780         cache_mgr = modest_runtime_get_cache_mgr ();
781         send_queue_cache = modest_cache_mgr_get_cache (cache_mgr,
782                                                        MODEST_CACHE_MGR_CACHE_TYPE_SEND_QUEUE);
783         
784         g_hash_table_foreach (send_queue_cache, (GHFunc) fill_list_of_caches, &send_queues);
785         if (send_queues == NULL) {
786                 accounts = tny_simple_list_new (); 
787                 accounts_store = modest_runtime_get_account_store ();
788                 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(accounts_store), 
789                                                 accounts, 
790                                                 TNY_ACCOUNT_STORE_TRANSPORT_ACCOUNTS);
791                 
792                 iter = tny_list_create_iterator (accounts);
793                 while (!tny_iterator_is_done (iter)) {                  
794                         account = TNY_TRANSPORT_ACCOUNT(tny_iterator_get_current (iter));
795                         send_queue = modest_runtime_get_send_queue(TNY_TRANSPORT_ACCOUNT(account), TRUE);
796                         g_object_unref(account);
797                         if (TNY_IS_SEND_QUEUE (send_queue)) {
798                                 queue_status = modest_tny_send_queue_get_msg_status (send_queue, msg_uid);
799                                 if (queue_status != MODEST_TNY_SEND_QUEUE_UNKNOWN) {
800                                         status = queue_status;
801                                         break;
802                                 }
803                         }
804                         tny_iterator_next (iter);
805                 }
806                 g_object_unref (iter);
807                 g_object_unref (accounts);
808         }
809         else {
810                 for (node = send_queues; node != NULL; node = g_slist_next (node)) {
811                         send_queue = MODEST_TNY_SEND_QUEUE (node->data);
812                         
813                         queue_status = modest_tny_send_queue_get_msg_status (send_queue, msg_uid);
814                         if (queue_status != MODEST_TNY_SEND_QUEUE_UNKNOWN) {
815                                 status = queue_status;
816                                 break;
817                         }
818                 }
819         }
820
821         g_free(msg_uid);
822         g_slist_free (send_queues);
823         return status;
824 }
825
826 typedef struct _WakeupHelper {
827         ModestTnySendQueue *self;
828         ModestTnySendQueueWakeupFunc callback;
829         gpointer userdata;
830 } WakeupHelper;
831
832 static void
833 wakeup_sync_cb (TnyFolder *self, gboolean cancelled, GError *err, gpointer userdata)
834 {
835         WakeupHelper *helper = (WakeupHelper *) userdata;
836
837         if (helper->callback) {
838                 helper->callback (helper->self, cancelled, err, helper->userdata);
839         }
840         g_object_unref (helper->self);
841         g_slice_free (WakeupHelper, helper);
842 }
843
844 static void
845 wakeup_get_headers_async_cb (TnyFolder *folder, 
846                              gboolean cancelled, 
847                              TnyList *headers, 
848                              GError *err, 
849                              gpointer user_data)
850 {
851         ModestTnySendQueue *self;
852         ModestTnySendQueuePrivate *priv;
853         TnyIterator *iter;
854         WakeupHelper *helper = (WakeupHelper *) user_data;
855
856         self = MODEST_TNY_SEND_QUEUE (helper->self);
857         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
858
859         if (cancelled || err) {
860                 g_debug ("Failed to wake up the headers of the send queue");
861                 g_object_unref (self);
862                 if (helper->callback) {
863                         helper->callback (helper->self, cancelled, err, helper->userdata);
864                 }
865                 g_object_unref (helper->self);
866                 g_slice_free (WakeupHelper, helper);
867                 return;
868         }
869
870         /* Wake up every single suspended header */
871         iter = tny_list_create_iterator (headers);
872         while (!tny_iterator_is_done (iter)) {
873                 TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iter));
874
875                 if (tny_header_get_flags (header) & TNY_HEADER_FLAG_SUSPENDED) {
876                         gchar *msg_id;
877                         GList *item;
878                         SendInfo *info;
879
880                         /* Unset the suspended flag */
881                         tny_header_unset_flag (header, TNY_HEADER_FLAG_SUSPENDED);
882
883                         /* Notify view */
884                         msg_id = modest_tny_send_queue_get_msg_id (header);                     
885                         item = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE (self), msg_id);
886                         if (!item) {
887                                 info = g_slice_new (SendInfo);
888                                 info->msg_id = msg_id;
889                                 g_queue_push_tail (priv->queue, info);
890                         } else {
891                                 info = (SendInfo *) item->data;
892                                 g_free (msg_id);
893                         }
894                         info->status = MODEST_TNY_SEND_QUEUE_WAITING;
895                         g_signal_emit (self, signals[STATUS_CHANGED_SIGNAL], 0, info->msg_id, info->status);            
896                 }
897
898                 /* Frees */
899                 g_object_unref (header);
900                 tny_iterator_next (iter);
901         }
902
903         /* Make changes persistent on disk */
904         tny_folder_sync_async (priv->outbox, FALSE, wakeup_sync_cb, NULL, helper);
905
906         /* Frees */
907         g_object_unref (iter);
908         g_object_unref (headers);
909 }
910
911 void   
912 modest_tny_send_queue_wakeup (ModestTnySendQueue *self, 
913                               ModestTnySendQueueWakeupFunc callback,
914                               gpointer userdata)
915 {
916         ModestTnySendQueuePrivate *priv;
917         TnyList *headers;
918         WakeupHelper *helper;
919
920         g_return_if_fail (MODEST_IS_TNY_SEND_QUEUE (self));
921         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
922
923         helper = g_slice_new (WakeupHelper);
924         helper->self = g_object_ref (self);
925         helper->callback = callback;
926         helper->userdata = userdata;
927
928         headers = tny_simple_list_new ();
929         tny_folder_get_headers_async (priv->outbox, headers, TRUE, 
930                                       wakeup_get_headers_async_cb, 
931                                       NULL, helper);
932 }
933
934 gboolean 
935 modest_tny_send_queue_get_requested_send_receive (ModestTnySendQueue *self)
936 {
937         ModestTnySendQueuePrivate *priv;
938
939         g_return_val_if_fail (MODEST_IS_TNY_SEND_QUEUE (self), FALSE);
940         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
941
942         return priv->requested_send_receive;
943 }
944
945 void 
946 modest_tny_send_queue_set_requested_send_receive (ModestTnySendQueue *self, gboolean requested_send_receive)
947 {
948         ModestTnySendQueuePrivate *priv;
949
950         g_return_if_fail (MODEST_IS_TNY_SEND_QUEUE (self));
951         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
952
953         priv->requested_send_receive = requested_send_receive;
954 }