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