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