62c75c678ccaaa4bfefd9f9a715c16a006e8ebe0
[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                         if (err->code == TNY_SERVICE_ERROR_CONNECT) {
715                                 TnyCamelTransportAccount* transport;
716                                 TnyTransportAccount *conn_specific;
717
718                                 transport = tny_camel_send_queue_get_transport_account (TNY_CAMEL_SEND_QUEUE (self));
719                                 if (transport) {
720                                         gchar *message;
721                                         ModestTnyAccountStore *acc_store;
722                                         const gchar *acc_name;
723
724                                         acc_store = modest_runtime_get_account_store();
725                                         acc_name = modest_tny_account_get_parent_modest_account_name_for_server_account (TNY_ACCOUNT (transport));
726                                         conn_specific = (TnyTransportAccount *)
727                                                 modest_tny_account_store_get_transport_account_for_open_connection (acc_store, acc_name);
728                                         if (conn_specific) {
729                                                 message = g_strdup_printf (_("emev_ib_ui_smtp_server_invalid"), 
730                                                                            tny_account_get_hostname (TNY_ACCOUNT (conn_specific)));
731                                                 g_object_unref (conn_specific);
732                                         } else {
733                                                 message = g_strdup_printf (_("emev_ib_ui_smtp_server_invalid"), 
734                                                                            tny_account_get_hostname (TNY_ACCOUNT (transport)));
735                                         }
736                                         modest_platform_run_alert_dialog (message, FALSE);
737                                         g_free (message);
738                                         g_object_unref (transport);
739                                 }
740                         }
741                         info->status = MODEST_TNY_SEND_QUEUE_FAILED;
742                 }
743                 priv->current = NULL;
744                 
745                 /* Notify status has changed */
746                 g_signal_emit (self, signals[STATUS_CHANGED_SIGNAL], 0, info->msg_id, info->status);
747
748                 /* free */
749                 g_free(msg_uid);
750         }
751 }
752
753 static void 
754 _on_queue_start (TnySendQueue *self,
755                  gpointer data)
756 {
757         ModestTnySendQueuePrivate *priv;
758         ModestMailOperation *mail_op;
759
760         mail_op = modest_mail_operation_new (NULL);
761         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
762                                          mail_op);
763         modest_mail_operation_run_queue (mail_op, MODEST_TNY_SEND_QUEUE (self));
764         g_object_unref (mail_op);
765
766         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
767         priv->sending = TRUE;
768 }
769
770 static void 
771 _on_queue_stop (TnySendQueue *self,
772                 gpointer data)
773 {
774         ModestTnySendQueuePrivate *priv;
775
776         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
777         priv->sending = FALSE;
778 }
779
780 static void 
781 fill_list_of_caches (gpointer key, gpointer value, gpointer userdata)
782 {
783         GSList **send_queues = (GSList **) userdata;
784         *send_queues = g_slist_prepend (*send_queues, value);
785 }
786
787 /* This function shouldn't be here. Move it to another place. Sergio */
788 ModestTnySendQueueStatus
789 modest_tny_all_send_queues_get_msg_status (TnyHeader *header)
790 {
791         ModestCacheMgr *cache_mgr = NULL;
792         GHashTable     *send_queue_cache = NULL;
793         ModestTnyAccountStore *accounts_store = NULL;
794         TnyList *accounts = NULL;
795         TnyIterator *iter = NULL;
796         TnyTransportAccount *account = NULL;
797         GSList *send_queues = NULL, *node;
798         /* get_msg_status returns suspended by default, so we want to detect changes */
799         ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
800         ModestTnySendQueueStatus queue_status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
801         gchar *msg_uid = NULL;
802         ModestTnySendQueue *send_queue = NULL;
803         
804         g_return_val_if_fail (TNY_IS_HEADER(header), MODEST_TNY_SEND_QUEUE_UNKNOWN);
805
806         msg_uid = modest_tny_send_queue_get_msg_id (header);
807         cache_mgr = modest_runtime_get_cache_mgr ();
808         send_queue_cache = modest_cache_mgr_get_cache (cache_mgr,
809                                                        MODEST_CACHE_MGR_CACHE_TYPE_SEND_QUEUE);
810         
811         g_hash_table_foreach (send_queue_cache, (GHFunc) fill_list_of_caches, &send_queues);
812         if (send_queues == NULL) {
813                 accounts = tny_simple_list_new (); 
814                 accounts_store = modest_runtime_get_account_store ();
815                 tny_account_store_get_accounts (TNY_ACCOUNT_STORE(accounts_store), 
816                                                 accounts, 
817                                                 TNY_ACCOUNT_STORE_TRANSPORT_ACCOUNTS);
818                 
819                 iter = tny_list_create_iterator (accounts);
820                 while (!tny_iterator_is_done (iter)) {                  
821                         account = TNY_TRANSPORT_ACCOUNT(tny_iterator_get_current (iter));
822                         send_queue = modest_runtime_get_send_queue(TNY_TRANSPORT_ACCOUNT(account), TRUE);
823                         g_object_unref(account);
824                         if (TNY_IS_SEND_QUEUE (send_queue)) {
825                                 queue_status = modest_tny_send_queue_get_msg_status (send_queue, msg_uid);
826                                 if (queue_status != MODEST_TNY_SEND_QUEUE_UNKNOWN) {
827                                         status = queue_status;
828                                         break;
829                                 }
830                         }
831                         tny_iterator_next (iter);
832                 }
833                 g_object_unref (iter);
834                 g_object_unref (accounts);
835         }
836         else {
837                 for (node = send_queues; node != NULL; node = g_slist_next (node)) {
838                         send_queue = MODEST_TNY_SEND_QUEUE (node->data);
839                         
840                         queue_status = modest_tny_send_queue_get_msg_status (send_queue, msg_uid);
841                         if (queue_status != MODEST_TNY_SEND_QUEUE_UNKNOWN) {
842                                 status = queue_status;
843                                 break;
844                         }
845                 }
846         }
847
848         g_free(msg_uid);
849         g_slist_free (send_queues);
850         return status;
851 }
852
853 typedef struct _WakeupHelper {
854         ModestTnySendQueue *self;
855         ModestTnySendQueueWakeupFunc callback;
856         gpointer userdata;
857 } WakeupHelper;
858
859 static void
860 wakeup_sync_cb (TnyFolder *self, gboolean cancelled, GError *err, gpointer userdata)
861 {
862         WakeupHelper *helper = (WakeupHelper *) userdata;
863
864         if (helper->callback) {
865                 helper->callback (helper->self, cancelled, err, helper->userdata);
866         }
867         g_object_unref (helper->self);
868         g_slice_free (WakeupHelper, helper);
869 }
870
871 static void
872 wakeup_get_headers_async_cb (TnyFolder *folder, 
873                              gboolean cancelled, 
874                              TnyList *headers, 
875                              GError *err, 
876                              gpointer user_data)
877 {
878         ModestTnySendQueue *self;
879         ModestTnySendQueuePrivate *priv;
880         TnyIterator *iter;
881         WakeupHelper *helper = (WakeupHelper *) user_data;
882
883         self = MODEST_TNY_SEND_QUEUE (helper->self);
884         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
885
886         if (cancelled || err) {
887                 g_debug ("Failed to wake up the headers of the send queue");
888                 g_object_unref (self);
889                 if (helper->callback) {
890                         helper->callback (helper->self, cancelled, err, helper->userdata);
891                 }
892                 g_object_unref (helper->self);
893                 g_slice_free (WakeupHelper, helper);
894                 return;
895         }
896
897         /* Wake up every single suspended header */
898         iter = tny_list_create_iterator (headers);
899         while (!tny_iterator_is_done (iter)) {
900                 TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iter));
901
902                 if (tny_header_get_flags (header) & TNY_HEADER_FLAG_SUSPENDED) {
903                         gchar *msg_id;
904                         GList *item;
905                         SendInfo *info;
906
907                         /* Unset the suspended flag */
908                         tny_header_unset_flag (header, TNY_HEADER_FLAG_SUSPENDED);
909
910                         /* Notify view */
911                         msg_id = modest_tny_send_queue_get_msg_id (header);                     
912                         item = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE (self), msg_id);
913                         if (!item) {
914                                 info = g_slice_new (SendInfo);
915                                 info->msg_id = msg_id;
916                                 g_queue_push_tail (priv->queue, info);
917                         } else {
918                                 info = (SendInfo *) item->data;
919                                 g_free (msg_id);
920                         }
921                         info->status = MODEST_TNY_SEND_QUEUE_WAITING;
922                         g_signal_emit (self, signals[STATUS_CHANGED_SIGNAL], 0, info->msg_id, info->status);            
923                 }
924
925                 /* Frees */
926                 g_object_unref (header);
927                 tny_iterator_next (iter);
928         }
929
930         /* Make changes persistent on disk */
931         tny_folder_sync_async (priv->outbox, FALSE, wakeup_sync_cb, NULL, helper);
932
933         /* Frees */
934         g_object_unref (iter);
935         g_object_unref (headers);
936         g_object_unref (self);
937 }
938
939 void   
940 modest_tny_send_queue_wakeup (ModestTnySendQueue *self, 
941                               ModestTnySendQueueWakeupFunc callback,
942                               gpointer userdata)
943 {
944         ModestTnySendQueuePrivate *priv;
945         TnyList *headers;
946         WakeupHelper *helper;
947
948         g_return_if_fail (MODEST_IS_TNY_SEND_QUEUE (self));
949         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
950
951         helper = g_slice_new (WakeupHelper);
952         helper->self = g_object_ref (self);
953         helper->callback = callback;
954         helper->userdata = userdata;
955
956         headers = tny_simple_list_new ();
957         tny_folder_get_headers_async (priv->outbox, headers, TRUE, 
958                                       wakeup_get_headers_async_cb, 
959                                       NULL, helper);
960 }
961
962 gboolean 
963 modest_tny_send_queue_get_requested_send_receive (ModestTnySendQueue *self)
964 {
965         ModestTnySendQueuePrivate *priv;
966
967         g_return_val_if_fail (MODEST_IS_TNY_SEND_QUEUE (self), FALSE);
968         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
969
970         return priv->requested_send_receive;
971 }
972
973 void 
974 modest_tny_send_queue_set_requested_send_receive (ModestTnySendQueue *self, gboolean requested_send_receive)
975 {
976         ModestTnySendQueuePrivate *priv;
977
978         g_return_if_fail (MODEST_IS_TNY_SEND_QUEUE (self));
979         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
980
981         priv->requested_send_receive = requested_send_receive;
982 }