* remove some g_asserts, add warnings instead
[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-camel-msg.h>
36 #include <tny-folder-change.h>
37 #include <tny-folder-observer.h>
38 #include <modest-tny-account.h>
39 #include <modest-runtime.h>
40 #include <modest-platform.h>
41 #include <widgets/modest-window-mgr.h>
42 #include <modest-marshal.h>
43 #include <string.h> /* strcmp */
44
45 /* 'private'/'protected' functions */
46 static void modest_tny_send_queue_class_init (ModestTnySendQueueClass *klass);
47 static void modest_tny_send_queue_finalize   (GObject *obj);
48 static void modest_tny_send_queue_instance_init (GTypeInstance *instance, gpointer g_class);
49
50 /* Signal handlers */ 
51 static void _on_msg_start_sending (TnySendQueue *self, 
52                                    TnyHeader *header, 
53                                    TnyMsg *msg, 
54                                    int done, 
55                                    int total, 
56                                    gpointer user_data);
57
58 static void _on_msg_has_been_sent (TnySendQueue *self, 
59                                    TnyHeader *header, 
60                                    TnyMsg *msg, 
61                                    int done, 
62                                    int total, 
63                                    gpointer user_data);
64
65 static void _on_msg_error_happened (TnySendQueue *self, 
66                                     TnyHeader *header, 
67                                     TnyMsg *msg, 
68                                     GError *err, 
69                                     gpointer user_data);
70
71 static TnyFolder* modest_tny_send_queue_get_outbox  (TnySendQueue *self);
72 static TnyFolder* modest_tny_send_queue_get_sentbox (TnySendQueue *self);
73
74 /* list my signals  */
75 enum {
76         STATUS_CHANGED,
77         /* MY_SIGNAL_1, */
78         /* MY_SIGNAL_2, */
79         LAST_SIGNAL
80 };
81
82 typedef struct _SendInfo SendInfo;
83 struct _SendInfo {
84         gchar* msg_id;
85         ModestTnySendQueueStatus status;
86 };
87
88 typedef struct _ModestTnySendQueuePrivate ModestTnySendQueuePrivate;
89 struct _ModestTnySendQueuePrivate {
90         /* Queued infos */
91         GQueue* queue;
92
93         /* The info that is currently being sent */
94         GList* current;
95 };
96
97 #define MODEST_TNY_SEND_QUEUE_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
98                                                    MODEST_TYPE_TNY_SEND_QUEUE, \
99                                                    ModestTnySendQueuePrivate))
100
101 /* globals */
102 static TnyCamelSendQueueClass *parent_class = NULL;
103
104 /* uncomment the following if you have defined any signals */
105 static guint signals[LAST_SIGNAL] = {0};
106
107 /*
108  * this thread actually tries to send all the mails in the outbox and keeps
109  * track of their state.
110  */
111
112 static int
113 on_modest_tny_send_queue_compare_id(gconstpointer info, gconstpointer msg_id)
114 {
115         g_return_val_if_fail (info && ((SendInfo*)info)->msg_id && msg_id, -1);
116         
117         return strcmp( ((SendInfo*)info)->msg_id, msg_id);
118 }
119
120 static void
121 modest_tny_send_queue_info_free(SendInfo *info)
122 {
123         g_free(info->msg_id);
124         g_slice_free(SendInfo, info);
125 }
126
127 static GList*
128 modest_tny_send_queue_lookup_info (ModestTnySendQueue *self, const gchar *msg_id)
129 {
130         ModestTnySendQueuePrivate *priv;
131         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
132
133         return g_queue_find_custom (priv->queue, msg_id, on_modest_tny_send_queue_compare_id);
134 }
135
136 static void
137 modest_tny_send_queue_cancel (TnySendQueue *self, gboolean remove, GError **err)
138 {
139         ModestTnySendQueuePrivate *priv;
140         SendInfo *info;
141         TnyIterator *iter = NULL;
142         TnyFolder *outbox = NULL;
143         TnyList *headers = tny_simple_list_new ();
144         TnyHeader *header = NULL;
145
146         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
147         if(priv->current != NULL)
148         {
149                 info = priv->current->data;
150                 priv->current = NULL;
151
152                 /* Keep in list until retry, so that we know that this was suspended
153                  * by the user and not by an error. */
154                 info->status = MODEST_TNY_SEND_QUEUE_SUSPENDED;
155
156                 g_signal_emit (self, signals[STATUS_CHANGED], 0, info->msg_id, info->status);
157         }
158
159         /* Set flags to supend sending operaiton (if removed, this is not necessary) */
160         if (!remove) {          
161                 outbox = modest_tny_send_queue_get_outbox (TNY_SEND_QUEUE(self));
162                 tny_folder_get_headers (outbox, headers, TRUE, err);
163                 if (err != NULL) goto frees;
164                 iter = tny_list_create_iterator (headers);
165                 while (!tny_iterator_is_done (iter)) {
166                         header = TNY_HEADER (tny_iterator_get_current (iter));
167                         if (header) {
168                                 tny_header_set_flag (header, TNY_HEADER_FLAG_SUSPENDED);
169                                 tny_iterator_next (iter);
170                                 g_object_unref (header);
171                         }
172                 }
173                 
174                 g_queue_foreach (priv->queue, (GFunc)modest_tny_send_queue_info_free, NULL);
175                 g_queue_free (priv->queue);
176                 priv->queue = g_queue_new();
177         }
178                 
179         /* Dont call super class implementaiton, becasue camel removes messages from outbox */
180         TNY_CAMEL_SEND_QUEUE_CLASS(parent_class)->cancel_func (self, remove, err); /* FIXME */
181
182  frees:
183         if (headers != NULL)
184                 g_object_unref (G_OBJECT (headers));
185         if (outbox != NULL) 
186                 g_object_unref (G_OBJECT (outbox));
187         if (iter != NULL) 
188                 g_object_unref (iter);
189 }
190
191 static void
192 _on_added_to_outbox (TnySendQueue *self, gboolean cancelled, TnyMsg *msg, GError *err, gpointer user_data) 
193 {
194         ModestTnySendQueuePrivate *priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE(self);
195         TnyHeader *header = NULL;
196         SendInfo *info = NULL;
197         GList* existing = NULL;
198         const gchar* msg_id = NULL;
199
200         g_return_if_fail (TNY_IS_SEND_QUEUE(self));
201         g_return_if_fail (TNY_IS_CAMEL_MSG(msg));
202
203         header = tny_msg_get_header (msg);
204         msg_id = modest_tny_send_queue_get_msg_id (header);
205 /*      msg_id = tny_header_get_message_id (header); */
206         g_return_if_fail(msg_id != NULL);
207
208         /* Put newly added message in WAITING state */
209         existing = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE(self), msg_id);
210         if(existing != NULL)
211         {
212                 info = existing->data;
213                 info->status = MODEST_TNY_SEND_QUEUE_WAITING;
214         }
215         else
216         {
217                 
218                 info = g_slice_new (SendInfo);
219
220                 info->msg_id = strdup(msg_id);
221                 info->status = MODEST_TNY_SEND_QUEUE_WAITING;
222                 g_queue_push_tail (priv->queue, info);
223         }
224
225         g_signal_emit (self, signals[STATUS_CHANGED], 0, info->msg_id, info->status);
226
227         g_object_unref(G_OBJECT(header));
228 }
229
230 void
231 modest_tny_send_queue_add (ModestTnySendQueue *self, TnyMsg *msg, GError **err)
232 {
233         g_return_if_fail (MODEST_IS_TNY_SEND_QUEUE(self));
234         g_return_if_fail (TNY_IS_CAMEL_MSG(msg));
235
236         tny_camel_send_queue_add_async (TNY_CAMEL_SEND_QUEUE(self), 
237                                         msg, 
238                                         _on_added_to_outbox, 
239                                         NULL, 
240                                         NULL);
241 }
242
243
244 static void
245 _add_message (ModestTnySendQueue *self, TnyHeader *header)
246 {
247         ModestWindowMgr *mgr = NULL;
248         ModestTnySendQueuePrivate *priv;
249         SendInfo *info = NULL;
250         GList* existing = NULL;
251         gchar* msg_uid = NULL;
252         ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
253         gboolean editing = FALSE;
254
255         g_return_if_fail (TNY_IS_SEND_QUEUE(self));
256         g_return_if_fail (TNY_IS_HEADER(header));
257         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
258         
259         /* Check whether the mail is already in the queue */
260         msg_uid = modest_tny_send_queue_get_msg_id (header);
261         status = modest_tny_send_queue_get_msg_status (self, msg_uid);
262         switch (status) {
263         case MODEST_TNY_SEND_QUEUE_UNKNOWN:
264         case MODEST_TNY_SEND_QUEUE_SUSPENDED:
265         case MODEST_TNY_SEND_QUEUE_FAILED:
266
267                 /* Check if it already exists on queue */
268                 existing = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE(self), msg_uid);
269                 if(existing != NULL)
270                         break;
271                 
272                 /* Check if its being edited */
273                 mgr = modest_runtime_get_window_mgr ();
274                 editing = modest_window_mgr_find_registered_header (mgr, header, NULL);
275                 if (editing)
276                         break;
277                 
278                 /* Add new meesage info */
279                 info = g_slice_new (SendInfo);
280                 info->msg_id = strdup(msg_uid);
281                 info->status = MODEST_TNY_SEND_QUEUE_WAITING;
282                 g_queue_push_tail (priv->queue, info);
283                 break;
284         default:
285                 break;
286         }
287
288         /* Free */
289         g_free(msg_uid);
290 }
291
292 static TnyFolder*
293 modest_tny_send_queue_get_sentbox (TnySendQueue *self)
294 {
295         TnyFolder *folder;
296         TnyCamelTransportAccount *account;
297
298         g_return_val_if_fail (self, NULL);
299
300         account = tny_camel_send_queue_get_transport_account (TNY_CAMEL_SEND_QUEUE(self));
301         if (!account) {
302                 g_printerr ("modest: no account for send queue\n");
303                 return NULL;
304         }
305         folder  = modest_tny_account_get_special_folder (TNY_ACCOUNT(account),
306                                                          TNY_FOLDER_TYPE_SENT);
307         g_object_unref (G_OBJECT(account));
308
309         return folder;
310 }
311
312
313 static TnyFolder*
314 modest_tny_send_queue_get_outbox (TnySendQueue *self)
315 {
316         TnyFolder *folder;
317         TnyCamelTransportAccount *account;
318
319         g_return_val_if_fail (self, NULL);
320
321         account = tny_camel_send_queue_get_transport_account (TNY_CAMEL_SEND_QUEUE(self));
322         if (!account) {
323                 g_printerr ("modest: no account for send queue\n");
324                 return NULL;
325         }
326         folder  = modest_tny_account_get_special_folder (TNY_ACCOUNT(account),
327                                                          TNY_FOLDER_TYPE_OUTBOX);
328
329         /* This vfunc's tinymail contract does not allow it to return NULL. */
330         if (!folder) {
331                 g_warning("%s: Returning NULL.\n", __FUNCTION__);
332         }
333
334         g_object_unref (G_OBJECT(account));
335
336         return folder;
337 }
338
339 GType
340 modest_tny_send_queue_get_type (void)
341 {
342         static GType my_type = 0;
343
344         if (my_type == 0) {
345                 static const GTypeInfo my_info = {
346                         sizeof(ModestTnySendQueueClass),
347                         NULL,           /* base init */
348                         NULL,           /* base finalize */
349                         (GClassInitFunc) modest_tny_send_queue_class_init,
350                         NULL,           /* class finalize */
351                         NULL,           /* class data */
352                         sizeof(ModestTnySendQueue),
353                         0,              /* n_preallocs */
354                         (GInstanceInitFunc) modest_tny_send_queue_instance_init,
355                         NULL
356                 };
357                 
358                 my_type = g_type_register_static (TNY_TYPE_CAMEL_SEND_QUEUE,
359                                                   "ModestTnySendQueue",
360                                                   &my_info, 0);
361         }
362         return my_type;
363 }
364
365
366 static void
367 modest_tny_send_queue_class_init (ModestTnySendQueueClass *klass)
368 {
369         GObjectClass *gobject_class;
370
371         gobject_class = (GObjectClass*) klass;
372         
373         parent_class            = g_type_class_peek_parent (klass);
374         gobject_class->finalize = modest_tny_send_queue_finalize;
375
376         TNY_CAMEL_SEND_QUEUE_CLASS(klass)->get_outbox_func  = modest_tny_send_queue_get_outbox;
377         TNY_CAMEL_SEND_QUEUE_CLASS(klass)->get_sentbox_func = modest_tny_send_queue_get_sentbox;
378         TNY_CAMEL_SEND_QUEUE_CLASS(klass)->cancel_func      = modest_tny_send_queue_cancel;
379         klass->status_changed   = NULL;
380
381         signals[STATUS_CHANGED] =
382                 g_signal_new ("status_changed",
383                               G_TYPE_FROM_CLASS (gobject_class),
384                               G_SIGNAL_RUN_FIRST,
385                               G_STRUCT_OFFSET (ModestTnySendQueueClass, status_changed),
386                               NULL, NULL,
387                               modest_marshal_VOID__STRING_INT,
388                               G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);
389
390         g_type_class_add_private (gobject_class, sizeof(ModestTnySendQueuePrivate));
391 }
392
393 static void
394 modest_tny_send_queue_instance_init (GTypeInstance *instance, gpointer g_class)
395 {
396         ModestTnySendQueuePrivate *priv;
397
398         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (instance);
399         priv->queue = g_queue_new();
400         priv->current = NULL;
401 }
402
403 static void
404 modest_tny_send_queue_finalize (GObject *obj)
405 {
406         ModestTnySendQueuePrivate *priv;
407                 
408         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (obj);
409
410         g_queue_foreach (priv->queue, (GFunc)modest_tny_send_queue_info_free, NULL);
411         g_queue_free (priv->queue);
412
413         G_OBJECT_CLASS(parent_class)->finalize (obj);
414 }
415
416 ModestTnySendQueue*
417 modest_tny_send_queue_new (TnyCamelTransportAccount *account)
418 {
419         ModestTnySendQueue *self;
420         
421         g_return_val_if_fail (TNY_IS_CAMEL_TRANSPORT_ACCOUNT(account), NULL);
422         
423         self = MODEST_TNY_SEND_QUEUE(g_object_new(MODEST_TYPE_TNY_SEND_QUEUE, NULL));
424         
425         tny_camel_send_queue_set_transport_account (TNY_CAMEL_SEND_QUEUE(self),
426                                                     account); 
427
428         /* Connect signals to control when a msg is being or has been sent */
429         g_signal_connect (G_OBJECT(self), "msg-sending",
430                           G_CALLBACK(_on_msg_start_sending),
431                           NULL);                          
432         g_signal_connect (G_OBJECT(self), "msg-sent",
433                           G_CALLBACK(_on_msg_has_been_sent), 
434                           NULL);
435         g_signal_connect (G_OBJECT(self), "error-happened",
436                           G_CALLBACK(_on_msg_error_happened),
437                           NULL);
438         return self;
439 }
440
441
442
443 void
444 modest_tny_send_queue_try_to_send (ModestTnySendQueue* self)
445 {
446         TnyIterator *iter = NULL;
447         TnyFolder *outbox = NULL;
448         TnyList *headers = tny_simple_list_new ();
449         TnyHeader *header = NULL;
450         GError *err = NULL;
451
452         outbox = modest_tny_send_queue_get_outbox (TNY_SEND_QUEUE(self));
453         if (!outbox)
454                 return;
455
456         tny_folder_get_headers (outbox, headers, TRUE, &err);
457         if (err != NULL) 
458                 goto frees;
459
460         iter = tny_list_create_iterator (headers);
461         while (!tny_iterator_is_done (iter)) {
462                 header = TNY_HEADER (tny_iterator_get_current (iter));
463                 if (header) {   
464                         _add_message (self, header); 
465                         g_object_unref (header);
466                 }
467
468                 tny_iterator_next (iter);
469         }
470         
471         /* Flush send queue */
472         tny_camel_send_queue_flush (TNY_CAMEL_SEND_QUEUE(self));
473
474  frees:
475         if (headers != NULL)
476                 g_object_unref (G_OBJECT (headers));
477         if (outbox != NULL) 
478                 g_object_unref (G_OBJECT (outbox));
479         if (iter != NULL) 
480                 g_object_unref (iter);
481 }
482
483 gboolean
484 modest_tny_send_queue_msg_is_being_sent (ModestTnySendQueue* self,
485                                          const gchar *msg_id)
486 {       
487         ModestTnySendQueueStatus status;
488         
489         g_return_val_if_fail (msg_id != NULL, FALSE); 
490         
491         status = modest_tny_send_queue_get_msg_status (self, msg_id);
492         return status == MODEST_TNY_SEND_QUEUE_SENDING;
493 }
494
495 gboolean
496 modest_tny_send_queue_sending_in_progress (ModestTnySendQueue* self)
497 {       
498         ModestTnySendQueuePrivate *priv;
499         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
500         
501         return priv->current != NULL;
502 }
503
504 ModestTnySendQueueStatus
505 modest_tny_send_queue_get_msg_status (ModestTnySendQueue *self, const gchar *msg_id)
506 {
507   GList *item;
508   item = modest_tny_send_queue_lookup_info (self, msg_id);
509   if(item == NULL) return MODEST_TNY_SEND_QUEUE_UNKNOWN;
510   return ((SendInfo*)item->data)->status;
511 }
512
513 gchar *
514 modest_tny_send_queue_get_msg_id (TnyHeader *header)
515 {
516         gchar* msg_uid = NULL;
517         const gchar *subject;
518         time_t date_received;
519                 
520         /* Get message uid */
521         subject = tny_header_get_subject (header);
522         date_received = tny_header_get_date_received (header);
523
524         msg_uid = g_strdup_printf ("%s %d", subject, (int) date_received);
525
526         return msg_uid;
527 }
528
529
530 static void
531 _on_msg_start_sending (TnySendQueue *self,
532                        TnyHeader *header,
533                        TnyMsg *msg,
534                        int done, 
535                        int total,
536                        gpointer user_data)
537 {
538         ModestTnySendQueuePrivate *priv = NULL;
539         GList *item = NULL;
540         SendInfo *info = NULL;
541         gchar *msg_id = NULL;
542
543         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
544         
545         /* Get message uid */
546         msg_id = modest_tny_send_queue_get_msg_id (header);
547
548         /* Get status info */
549         item = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE (self), msg_id);
550         if (!item) 
551                 g_warning  ("%s: item should not be NULL", __FUNCTION__);
552         else {
553                 info = item->data;
554                 info->status = MODEST_TNY_SEND_QUEUE_SENDING;
555                 
556                 /* Set current status item */
557                 g_signal_emit (self, signals[STATUS_CHANGED], 0, info->msg_id, info->status);
558                 priv->current = item;
559         }
560
561         /* free */
562         g_free (msg_id);
563 }
564
565 static void 
566 _on_msg_has_been_sent (TnySendQueue *self,
567                        TnyHeader *header,
568                        TnyMsg *msg, 
569                        int done, 
570                        int total,
571                        gpointer user_data)
572 {
573         ModestTnySendQueuePrivate *priv;
574         gchar *msg_id = NULL;
575         GList *item;
576         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
577
578         /* Get message uid */
579         msg_id = modest_tny_send_queue_get_msg_id (header);
580
581         tny_header_set_flag (header, TNY_HEADER_FLAG_SEEN);
582
583         /* Get status info */
584         item = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE (self), msg_id);
585         if (item) {
586                 
587                 /* Remove status info */
588                 modest_tny_send_queue_info_free (item->data);
589                 g_queue_delete_link (priv->queue, item);
590                 priv->current = NULL;
591                 
592                 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_sent"));
593         }
594
595         /* free */
596         g_free(msg_id);
597 }
598
599 static void 
600 _on_msg_error_happened (TnySendQueue *self,
601                         TnyHeader *header,
602                         TnyMsg *msg,
603                         GError *err,
604                         gpointer user_data)
605 {
606         ModestTnySendQueuePrivate *priv = NULL;
607         SendInfo *info = NULL;
608         GList *item = NULL;
609         gchar* msg_uid = NULL;
610         
611         priv = MODEST_TNY_SEND_QUEUE_GET_PRIVATE (self);
612         
613         /* Get sending info (create new if it doesn not exist) */
614         msg_uid = modest_tny_send_queue_get_msg_id (header);
615         item = modest_tny_send_queue_lookup_info (MODEST_TNY_SEND_QUEUE (self), 
616                                                   msg_uid);     
617         if (item == NULL) {
618                 info = g_slice_new (SendInfo);
619                 info->msg_id = strdup(msg_uid);
620                 g_queue_push_tail (priv->queue, info);
621         } else
622                 info = item->data;
623
624         /* Keep in queue so that we remember that the opertion has failed */
625         /* and was not just cancelled */
626         info->status = MODEST_TNY_SEND_QUEUE_FAILED;
627         
628         /* Notify status has changed */
629         g_signal_emit (self, signals[STATUS_CHANGED], 0, info->msg_id, info->status);
630
631         /* free */
632         g_free(msg_uid);
633 }