* implement TODO item i001:
[modest] / src / modest-mail-operation.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 #include "modest-mail-operation.h"
31 /* include other impl specific header files */
32 #include <string.h>
33 #include <stdarg.h>
34 #include <tny-mime-part.h>
35 #include <tny-store-account.h>
36 #include <tny-folder-store.h>
37 #include <tny-folder-store-query.h>
38 #include <tny-camel-stream.h>
39 #include <tny-simple-list.h>
40 #include <camel/camel-stream-mem.h>
41 #include <glib/gi18n.h>
42 #include <modest-tny-account.h>
43 #include <modest-tny-send-queue.h>
44 #include <modest-runtime.h>
45 #include "modest-text-utils.h"
46 #include "modest-tny-msg.h"
47 #include "modest-tny-platform-factory.h"
48 #include "modest-marshal.h"
49 #include "modest-formatter.h"
50 #include "modest-error.h"
51
52 /* 'private'/'protected' functions */
53 static void modest_mail_operation_class_init (ModestMailOperationClass *klass);
54 static void modest_mail_operation_init       (ModestMailOperation *obj);
55 static void modest_mail_operation_finalize   (GObject *obj);
56
57 static void     status_update_cb     (TnyFolder *folder, 
58                                       const gchar *what, 
59                                       gint status, 
60                                       gint oftotal,
61                                       gpointer user_data);
62 static void     folder_refresh_cb    (TnyFolder *folder, 
63                                       gboolean canceled,
64                                       GError **err,
65                                       gpointer user_data);
66 static void     update_folders_cb    (TnyFolderStore *self, 
67                                       TnyList *list, 
68                                       GError **err, 
69                                       gpointer user_data);
70 static void     add_attachments      (TnyMsg *msg, 
71                                       GList *attachments_list);
72
73
74 static TnyMimePart *         add_body_part    (TnyMsg *msg, 
75                                                const gchar *body,
76                                                const gchar *content_type, 
77                                                gboolean has_attachments);
78
79
80 static void        modest_mail_operation_xfer_folder       (ModestMailOperation *self,
81                                                             TnyFolder *folder,
82                                                             TnyFolderStore *parent,
83                                                             gboolean delete_original);
84
85 static gboolean    modest_mail_operation_xfer_msg          (ModestMailOperation *self,
86                                                             TnyHeader *header, 
87                                                             TnyFolder *folder, 
88                                                             gboolean delete_original);
89
90 enum _ModestMailOperationSignals 
91 {
92         PROGRESS_CHANGED_SIGNAL,
93
94         NUM_SIGNALS
95 };
96
97 typedef struct _ModestMailOperationPrivate ModestMailOperationPrivate;
98 struct _ModestMailOperationPrivate {
99         guint                      done;
100         guint                      total;
101         ModestMailOperationStatus  status;
102         GError                    *error;
103 };
104 #define MODEST_MAIL_OPERATION_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
105                                                    MODEST_TYPE_MAIL_OPERATION, \
106                                                    ModestMailOperationPrivate))
107
108 typedef struct _RefreshFolderAsyncHelper
109 {
110         ModestMailOperation *mail_op;
111         TnyIterator *iter;
112         guint failed;
113         guint canceled;
114
115 } RefreshFolderAsyncHelper;
116
117 /* some utility functions */
118 static char * get_content_type(const gchar *s);
119 static gboolean is_ascii(const gchar *s);
120
121 /* globals */
122 static GObjectClass *parent_class = NULL;
123
124 static guint signals[NUM_SIGNALS] = {0};
125
126 GType
127 modest_mail_operation_get_type (void)
128 {
129         static GType my_type = 0;
130         if (!my_type) {
131                 static const GTypeInfo my_info = {
132                         sizeof(ModestMailOperationClass),
133                         NULL,           /* base init */
134                         NULL,           /* base finalize */
135                         (GClassInitFunc) modest_mail_operation_class_init,
136                         NULL,           /* class finalize */
137                         NULL,           /* class data */
138                         sizeof(ModestMailOperation),
139                         1,              /* n_preallocs */
140                         (GInstanceInitFunc) modest_mail_operation_init,
141                         NULL
142                 };
143                 my_type = g_type_register_static (G_TYPE_OBJECT,
144                                                   "ModestMailOperation",
145                                                   &my_info, 0);
146         }
147         return my_type;
148 }
149
150 static void
151 modest_mail_operation_class_init (ModestMailOperationClass *klass)
152 {
153         GObjectClass *gobject_class;
154         gobject_class = (GObjectClass*) klass;
155
156         parent_class            = g_type_class_peek_parent (klass);
157         gobject_class->finalize = modest_mail_operation_finalize;
158
159         g_type_class_add_private (gobject_class, sizeof(ModestMailOperationPrivate));
160
161         /**
162          * ModestMailOperation::progress-changed
163          * @self: the #MailOperation that emits the signal
164          * @user_data: user data set when the signal handler was connected
165          *
166          * Emitted when the progress of a mail operation changes
167          */
168         signals[PROGRESS_CHANGED_SIGNAL] = 
169                 g_signal_new ("progress_changed",
170                               G_TYPE_FROM_CLASS (gobject_class),
171                               G_SIGNAL_RUN_FIRST,
172                               G_STRUCT_OFFSET (ModestMailOperationClass, progress_changed),
173                               NULL, NULL,
174                               g_cclosure_marshal_VOID__VOID,
175                               G_TYPE_NONE, 0);
176 }
177
178 static void
179 modest_mail_operation_init (ModestMailOperation *obj)
180 {
181         ModestMailOperationPrivate *priv;
182
183         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(obj);
184
185         priv->status   = MODEST_MAIL_OPERATION_STATUS_INVALID;
186         priv->error    = NULL;
187         priv->done     = 0;
188         priv->total    = 0;
189 }
190
191 static void
192 modest_mail_operation_finalize (GObject *obj)
193 {
194         ModestMailOperationPrivate *priv;
195
196         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(obj);
197
198         if (priv->error) {
199                 g_error_free (priv->error);
200                 priv->error = NULL;
201         }
202
203         G_OBJECT_CLASS(parent_class)->finalize (obj);
204 }
205
206 ModestMailOperation*
207 modest_mail_operation_new (void)
208 {
209         return MODEST_MAIL_OPERATION(g_object_new(MODEST_TYPE_MAIL_OPERATION, NULL));
210 }
211
212
213 void
214 modest_mail_operation_send_mail (ModestMailOperation *self,
215                                  TnyTransportAccount *transport_account,
216                                  TnyMsg* msg)
217 {
218         TnySendQueue *send_queue;
219         
220         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
221         g_return_if_fail (TNY_IS_TRANSPORT_ACCOUNT (transport_account));
222
223         send_queue = TNY_SEND_QUEUE (modest_runtime_get_send_queue (transport_account));
224         if (!TNY_IS_SEND_QUEUE(send_queue))
225                 g_printerr ("modest: could not find send queue for account\n");
226         else
227                 tny_send_queue_add (send_queue, msg);
228 }
229
230 void
231 modest_mail_operation_send_new_mail (ModestMailOperation *self,
232                                      TnyTransportAccount *transport_account,
233                                      const gchar *from,
234                                      const gchar *to,
235                                      const gchar *cc,
236                                      const gchar *bcc,
237                                      const gchar *subject,
238                                      const gchar *body,
239                                      const GList *attachments_list)
240 {
241         TnyPlatformFactory *fact;
242         TnyMsg *new_msg;
243         TnyHeader *header;
244         gchar *content_type;
245         ModestMailOperationPrivate *priv = NULL;
246
247         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
248         g_return_if_fail (TNY_IS_TRANSPORT_ACCOUNT (transport_account));
249
250         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
251
252         /* Check parametters */
253         if (to == NULL) {
254                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
255                              MODEST_MAIL_OPERATION_ERROR_MISSING_PARAMETER,
256                              _("Error trying to send a mail. You need to set almost one a recipient"));
257                 return;
258         }
259
260         /* Create new msg */
261         fact    = modest_tny_platform_factory_get_instance ();
262         new_msg = tny_platform_factory_new_msg (fact);
263         header  = tny_platform_factory_new_header (fact);
264
265         /* WARNING: set the header before assign values to it */
266         tny_msg_set_header (new_msg, header);
267         tny_header_set_from (TNY_HEADER (header), from);
268         tny_header_set_replyto (TNY_HEADER (header), from);
269         tny_header_set_to (TNY_HEADER (header), to);
270         tny_header_set_cc (TNY_HEADER (header), cc);
271         tny_header_set_bcc (TNY_HEADER (header), bcc);
272         tny_header_set_subject (TNY_HEADER (header), subject);
273
274         content_type = get_content_type(body);
275
276         /* Add the body of the new mail */      
277         add_body_part (new_msg, body, (const gchar *) content_type,
278                        (attachments_list == NULL) ? FALSE : TRUE);
279
280         /* Add attachments */
281         add_attachments (new_msg, (GList*) attachments_list);
282
283         /* Send mail */
284         modest_mail_operation_send_mail (self, transport_account, new_msg);
285
286         /* Clean */
287         g_object_unref (header);
288         g_object_unref (new_msg);
289         g_free(content_type);
290 }
291
292 static void
293 add_if_attachment (gpointer data, gpointer user_data)
294 {
295         TnyMimePart *part;
296         GList *attachments_list;
297
298         part = TNY_MIME_PART (data);
299         attachments_list = (GList *) user_data;
300
301         if (tny_mime_part_is_attachment (part))
302                 attachments_list = g_list_prepend (attachments_list, part);
303 }
304
305
306 static TnyMsg *
307 create_reply_forward_mail (TnyMsg *msg, const gchar *from, gboolean is_reply, guint type)
308 {
309         TnyPlatformFactory *fact;
310         TnyMsg *new_msg;
311         TnyHeader *new_header, *header;
312         gchar *new_subject;
313         TnyMimePart *body;
314         ModestFormatter *formatter;
315
316         /* Get body from original msg. Always look for the text/plain
317            part of the message to create the reply/forwarded mail */
318         header = tny_msg_get_header (msg);
319         body   = modest_tny_msg_find_body_part (msg, FALSE);
320
321         /* TODO: select the formatter from account prefs */
322         formatter = modest_formatter_new ("text/plain");
323
324         /* Format message body */
325         if (is_reply) {
326                 switch (type) {
327                 case MODEST_MAIL_OPERATION_REPLY_TYPE_CITE:
328                 default:
329                         new_msg = modest_formatter_cite  (formatter, body, header);
330                         break;
331                 case MODEST_MAIL_OPERATION_REPLY_TYPE_QUOTE:
332                         new_msg = modest_formatter_quote (formatter, body, header);
333                         break;
334                 }
335         } else {
336                 switch (type) {
337                 case MODEST_MAIL_OPERATION_FORWARD_TYPE_INLINE:
338                 default:
339                         new_msg = modest_formatter_inline  (formatter, body, header);
340                         break;
341                 case MODEST_MAIL_OPERATION_FORWARD_TYPE_ATTACHMENT:
342                         new_msg = modest_formatter_attach (formatter, body, header);
343                         break;
344                 }
345         }
346         g_object_unref (G_OBJECT(formatter));
347         g_object_unref (G_OBJECT(body));
348         
349         /* Fill the header */
350         fact = modest_tny_platform_factory_get_instance ();
351         new_header = TNY_HEADER (tny_platform_factory_new_header (fact));
352         tny_msg_set_header (new_msg, new_header);
353         tny_header_set_from (new_header, from);
354         tny_header_set_replyto (new_header, from);
355
356         /* Change the subject */
357         new_subject =
358                 (gchar *) modest_text_utils_derived_subject (tny_header_get_subject(header),
359                                                              (is_reply) ? _("Re:") : _("Fwd:"));
360         tny_header_set_subject (new_header, (const gchar *) new_subject);
361         g_free (new_subject);
362
363         /* Clean */
364         g_object_unref (G_OBJECT (new_header));
365         g_object_unref (G_OBJECT (header));
366
367         return new_msg;
368 }
369
370 TnyMsg* 
371 modest_mail_operation_create_forward_mail (TnyMsg *msg, 
372                                            const gchar *from,
373                                            ModestMailOperationForwardType forward_type)
374 {
375         TnyMsg *new_msg;
376         TnyList *parts = NULL;
377         GList *attachments_list = NULL;
378
379         new_msg = create_reply_forward_mail (msg, from, FALSE, forward_type);
380
381         /* Add attachments */
382         parts = TNY_LIST (tny_simple_list_new());
383         tny_mime_part_get_parts (TNY_MIME_PART (msg), parts);
384         tny_list_foreach (parts, add_if_attachment, attachments_list);
385         add_attachments (new_msg, attachments_list);
386
387         /* Clean */
388         if (attachments_list) g_list_free (attachments_list);
389         g_object_unref (G_OBJECT (parts));
390
391         return new_msg;
392 }
393
394 TnyMsg* 
395 modest_mail_operation_create_reply_mail (TnyMsg *msg, 
396                                          const gchar *from,
397                                          ModestMailOperationReplyType reply_type,
398                                          ModestMailOperationReplyMode reply_mode)
399 {
400         TnyMsg *new_msg = NULL;
401         TnyHeader *new_header, *header;
402         const gchar* reply_to;
403         gchar *new_cc = NULL;
404         const gchar *cc = NULL, *bcc = NULL;
405         GString *tmp = NULL;
406
407         new_msg = create_reply_forward_mail (msg, from, TRUE, reply_type);
408
409         /* Fill the header */
410         header = tny_msg_get_header (msg);
411         new_header = tny_msg_get_header (new_msg);
412         reply_to = tny_header_get_replyto (header);
413
414         if (reply_to)
415                 tny_header_set_to (new_header, reply_to);
416         else
417                 tny_header_set_to (new_header, tny_header_get_from (header));
418
419         switch (reply_mode) {
420         case MODEST_MAIL_OPERATION_REPLY_MODE_SENDER:
421                 /* Do not fill neither cc nor bcc */
422                 break;
423         case MODEST_MAIL_OPERATION_REPLY_MODE_LIST:
424                 /* TODO */
425                 break;
426         case MODEST_MAIL_OPERATION_REPLY_MODE_ALL:
427                 /* Concatenate to, cc and bcc */
428                 cc = tny_header_get_cc (header);
429                 bcc = tny_header_get_bcc (header);
430
431                 tmp = g_string_new (tny_header_get_to (header));
432                 if (cc)  g_string_append_printf (tmp, ",%s",cc);
433                 if (bcc) g_string_append_printf (tmp, ",%s",bcc);
434
435                /* Remove my own address from the cc list. TODO:
436                   remove also the To: of the new message, needed due
437                   to the new reply_to feature */
438                 new_cc = (gchar *)
439                         modest_text_utils_remove_address ((const gchar *) tmp->str,
440                                                           from);
441                 /* FIXME: remove also the mails from the new To: */
442                 tny_header_set_cc (new_header, new_cc);
443
444                 /* Clean */
445                 g_string_free (tmp, TRUE);
446                 g_free (new_cc);
447                 break;
448         }
449
450         /* Clean */
451         g_object_unref (G_OBJECT (new_header));
452         g_object_unref (G_OBJECT (header));
453
454         return new_msg;
455 }
456
457 static void
458 status_update_cb (TnyFolder *folder, const gchar *what, gint status, gint oftotal, gpointer user_data) 
459 {
460         g_print ("%s status: %d, of total %d\n", what, status, oftotal);
461 }
462
463 static void
464 folder_refresh_cb (TnyFolder *folder, gboolean canceled, GError **err, gpointer user_data)
465 {
466         ModestMailOperation *self = NULL;
467         ModestMailOperationPrivate *priv = NULL;
468         RefreshFolderAsyncHelper *helper;
469
470         helper = (RefreshFolderAsyncHelper *) user_data;
471         self = MODEST_MAIL_OPERATION (helper->mail_op);
472         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
473
474         if ((canceled && *err) || *err) {
475                 priv->error = g_error_copy (*err);
476                 helper->failed++;
477         } else if (canceled) {
478                 helper->canceled++;
479                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
480                              MODEST_MAIL_OPERATION_ERROR_OPERATION_CANCELED,
481                              _("Error trying to refresh folder %s. Operation canceled"),
482                              tny_folder_get_name (folder));
483         } else {
484                 priv->done++;
485         }
486
487         if (priv->done == priv->total)
488                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
489         else if ((priv->done + helper->canceled + helper->failed) == priv->total) {
490                 if (helper->failed == priv->total)
491                         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
492                 else if (helper->failed == priv->total)
493                         priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
494                 else
495                         priv->status = MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS;
496         }
497         tny_iterator_next (helper->iter);
498         if (tny_iterator_is_done (helper->iter)) {
499                 TnyList *list;
500                 list = tny_iterator_get_list (helper->iter);
501                 g_object_unref (G_OBJECT (helper->iter));
502                 g_object_unref (G_OBJECT (list));
503                 g_slice_free (RefreshFolderAsyncHelper, helper);
504         } else {
505                 TnyFolder *folder = TNY_FOLDER (tny_iterator_get_current (helper->iter));
506                 tny_folder_refresh_async (folder, folder_refresh_cb,
507                                           status_update_cb, 
508                                           helper);
509                 g_object_unref (G_OBJECT(folder));
510         }
511         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 0, NULL);
512 }
513
514
515 static void
516 update_folders_cb (TnyFolderStore *folder_store, TnyList *list, GError **err, gpointer user_data)
517 {
518         ModestMailOperation *self;
519         ModestMailOperationPrivate *priv;
520         RefreshFolderAsyncHelper *helper;
521         TnyFolder *folder;
522         
523         self  = MODEST_MAIL_OPERATION (user_data);
524         priv  = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
525
526         if (*err) {
527                 priv->error = g_error_copy (*err);
528                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
529                 return;
530         }
531
532         priv->total = tny_list_get_length (list);
533         priv->done = 0;
534         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
535
536         helper = g_slice_new0 (RefreshFolderAsyncHelper);
537         helper->mail_op = self;
538         helper->iter = tny_list_create_iterator (list);
539         helper->failed = 0;
540         helper->canceled = 0;
541
542         /* Async refresh folders */
543         folder = TNY_FOLDER (tny_iterator_get_current (helper->iter));
544         tny_folder_refresh_async (folder, folder_refresh_cb,
545                                   status_update_cb, helper);
546         g_object_unref (G_OBJECT(folder));
547 }
548
549 gboolean
550 modest_mail_operation_update_account (ModestMailOperation *self,
551                                       TnyStoreAccount *store_account)
552 {
553         ModestMailOperationPrivate *priv;
554         TnyList *folders;
555         TnyFolderStoreQuery *query;
556
557         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION (self), FALSE);
558         g_return_val_if_fail (TNY_IS_STORE_ACCOUNT(store_account), FALSE);
559
560         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
561
562         /* Get subscribed folders & refresh them */
563         folders = TNY_LIST (tny_simple_list_new ());
564         query = tny_folder_store_query_new ();
565         tny_folder_store_query_add_item (query, NULL, TNY_FOLDER_STORE_QUERY_OPTION_SUBSCRIBED);
566         tny_folder_store_get_folders_async (TNY_FOLDER_STORE (store_account),
567                                             folders, update_folders_cb, query, self);
568         g_object_unref (query);
569
570         return TRUE;
571 }
572
573 ModestMailOperationStatus
574 modest_mail_operation_get_status (ModestMailOperation *self)
575 {
576         ModestMailOperationPrivate *priv;
577
578         g_return_val_if_fail (self, MODEST_MAIL_OPERATION_STATUS_INVALID);
579         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION (self),
580                               MODEST_MAIL_OPERATION_STATUS_INVALID);
581
582         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
583         return priv->status;
584 }
585
586 const GError *
587 modest_mail_operation_get_error (ModestMailOperation *self)
588 {
589         ModestMailOperationPrivate *priv;
590
591         g_return_val_if_fail (self, NULL);
592         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION (self), NULL);
593
594         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
595         return priv->error;
596 }
597
598 gboolean 
599 modest_mail_operation_cancel (ModestMailOperation *self)
600 {
601         /* TODO */
602         return TRUE;
603 }
604
605 guint 
606 modest_mail_operation_get_task_done (ModestMailOperation *self)
607 {
608         ModestMailOperationPrivate *priv;
609
610         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION (self), 0);
611
612         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
613         return priv->done;
614 }
615
616 guint 
617 modest_mail_operation_get_task_total (ModestMailOperation *self)
618 {
619         ModestMailOperationPrivate *priv;
620
621         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION (self), 0);
622
623         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
624         return priv->total;
625 }
626
627 gboolean
628 modest_mail_operation_is_finished (ModestMailOperation *self)
629 {
630         ModestMailOperationPrivate *priv;
631         gboolean retval = FALSE;
632
633         if (!MODEST_IS_MAIL_OPERATION (self)) {
634                 g_warning ("%s: invalid parametter", G_GNUC_FUNCTION);
635                 return retval;
636         }
637
638         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
639
640         if (priv->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS   ||
641             priv->status == MODEST_MAIL_OPERATION_STATUS_FAILED    ||
642             priv->status == MODEST_MAIL_OPERATION_STATUS_CANCELED  ||
643             priv->status == MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS) {
644                 retval = TRUE;
645         } else {
646                 retval = FALSE;
647         }
648
649         return retval;
650 }
651
652 /* ******************************************************************* */
653 /* ************************** STORE  ACTIONS ************************* */
654 /* ******************************************************************* */
655
656
657 TnyFolder *
658 modest_mail_operation_create_folder (ModestMailOperation *self,
659                                      TnyFolderStore *parent,
660                                      const gchar *name)
661 {
662         g_return_val_if_fail (TNY_IS_FOLDER_STORE (parent), NULL);
663         g_return_val_if_fail (name, NULL);
664
665         TnyFolder *new_folder = NULL;
666         TnyStoreAccount *store_account;
667
668         /* Create the folder */
669         new_folder = tny_folder_store_create_folder (parent, name, NULL); /* FIXME */
670         if (!new_folder) 
671                 return NULL;
672
673         /* Subscribe to folder */
674         if (!tny_folder_is_subscribed (new_folder)) {
675                 store_account = TNY_STORE_ACCOUNT (tny_folder_get_account (TNY_FOLDER (parent)));
676                 tny_store_account_subscribe (store_account, new_folder);
677                 g_object_unref (G_OBJECT (store_account));
678         }
679
680         return new_folder;
681 }
682
683 void
684 modest_mail_operation_remove_folder (ModestMailOperation *self,
685                                      TnyFolder *folder,
686                                      gboolean remove_to_trash)
687 {
688         TnyFolderStore *folder_store;
689
690         g_return_if_fail (TNY_IS_FOLDER (folder));
691
692         /* Get folder store */
693         folder_store = TNY_FOLDER_STORE (tny_folder_get_account (folder));
694
695         /* Delete folder or move to trash */
696         if (remove_to_trash) {
697                 TnyFolder *trash_folder;
698                 trash_folder = modest_tny_account_get_special_folder (TNY_ACCOUNT(folder_store),
699                                                                       TNY_FOLDER_TYPE_TRASH);
700                 /* TODO: error_handling */
701                 modest_mail_operation_move_folder (self, folder, 
702                                                    TNY_FOLDER_STORE (trash_folder));
703         } else {
704                 tny_folder_store_remove_folder (folder_store, folder, NULL); /* FIXME */
705                 g_object_unref (G_OBJECT (folder));
706         }
707
708         /* Free instances */
709         g_object_unref (G_OBJECT (folder_store));
710 }
711
712 void
713 modest_mail_operation_rename_folder (ModestMailOperation *self,
714                                      TnyFolder *folder,
715                                      const gchar *name)
716 {
717         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
718         g_return_if_fail (TNY_IS_FOLDER_STORE (folder));
719         g_return_if_fail (name);
720
721         /* FIXME: better error handling */
722         if (strrchr (name, '/') != NULL)
723                 return;
724
725         /* Rename. Camel handles folder subscription/unsubscription */
726         tny_folder_set_name (folder, name, NULL); /* FIXME */
727  }
728
729 void
730 modest_mail_operation_move_folder (ModestMailOperation *self,
731                                    TnyFolder *folder,
732                                    TnyFolderStore *parent)
733 {
734         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
735         g_return_if_fail (TNY_IS_FOLDER_STORE (parent));
736         g_return_if_fail (TNY_IS_FOLDER (folder));
737         
738         modest_mail_operation_xfer_folder (self, folder, parent, TRUE);
739 }
740
741 void
742 modest_mail_operation_copy_folder (ModestMailOperation *self,
743                                    TnyFolder *folder,
744                                    TnyFolderStore *parent)
745 {
746         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
747         g_return_if_fail (TNY_IS_FOLDER_STORE (parent));
748         g_return_if_fail (TNY_IS_FOLDER (folder));
749
750         modest_mail_operation_xfer_folder (self, folder, parent, FALSE);
751 }
752
753 static void
754 modest_mail_operation_xfer_folder (ModestMailOperation *self,
755                                    TnyFolder *folder,
756                                    TnyFolderStore *parent,
757                                    gboolean delete_original)
758 {
759         const gchar *folder_name;
760         TnyFolder *dest_folder, *child;
761         TnyIterator *iter;
762         TnyList *folders, *headers;
763
764         g_return_if_fail (TNY_IS_FOLDER (folder));
765         g_return_if_fail (TNY_IS_FOLDER_STORE (parent));
766
767         /* Create the destination folder */
768         folder_name = tny_folder_get_name (folder);
769         dest_folder = modest_mail_operation_create_folder (self, parent, folder_name);
770
771         /* Transfer messages */
772         headers = TNY_LIST (tny_simple_list_new ());
773         tny_folder_get_headers (folder, headers, FALSE, NULL); /* FIXME */
774         tny_folder_transfer_msgs (folder, headers, dest_folder, delete_original, NULL); /* FIXME */
775
776         /* Recurse children */
777         folders = TNY_LIST (tny_simple_list_new ());
778         tny_folder_store_get_folders (TNY_FOLDER_STORE (folder), folders, NULL, NULL ); /* FIXME */
779         iter = tny_list_create_iterator (folders);
780
781         while (!tny_iterator_is_done (iter)) {
782                 child = TNY_FOLDER (tny_iterator_get_current (iter));
783                 modest_mail_operation_xfer_folder (self, child, TNY_FOLDER_STORE (dest_folder),
784                                                    delete_original);
785                 tny_iterator_next (iter);
786                 g_object_unref (G_OBJECT(child));
787         }
788
789         /* Delete source folder (if needed) */
790         if (delete_original)
791                 modest_mail_operation_remove_folder (self, folder, FALSE);
792
793         /* Clean up */
794         g_object_unref (G_OBJECT (dest_folder));
795         g_object_unref (G_OBJECT (headers));
796         g_object_unref (G_OBJECT (folders));
797         g_object_unref (G_OBJECT (iter));
798 }
799
800
801 /* ******************************************************************* */
802 /* **************************  MSG  ACTIONS  ************************* */
803 /* ******************************************************************* */
804
805 gboolean 
806 modest_mail_operation_copy_msg (ModestMailOperation *self,
807                                 TnyHeader *header, 
808                                 TnyFolder *folder)
809 {
810         g_return_val_if_fail (TNY_IS_HEADER (header), FALSE);
811         g_return_val_if_fail (TNY_IS_FOLDER (folder), FALSE);
812
813         return modest_mail_operation_xfer_msg (self, header, folder, FALSE);
814 }
815
816 gboolean 
817 modest_mail_operation_move_msg (ModestMailOperation *self,
818                                 TnyHeader *header, 
819                                 TnyFolder *folder)
820 {
821         g_return_val_if_fail (TNY_IS_HEADER (header), FALSE);
822         g_return_val_if_fail (TNY_IS_FOLDER (folder), FALSE);
823
824         return modest_mail_operation_xfer_msg (self, header, folder, TRUE);
825 }
826
827 void 
828 modest_mail_operation_remove_msg (ModestMailOperation *self,
829                                   TnyHeader *header,
830                                   gboolean remove_to_trash)
831 {
832         TnyFolder *folder;
833
834         g_return_if_fail (TNY_IS_HEADER (header));
835
836         folder = tny_header_get_folder (header);
837
838         /* Delete or move to trash */
839         if (remove_to_trash) {
840                 TnyFolder *trash_folder;
841                 TnyStoreAccount *store_account;
842
843                 store_account = TNY_STORE_ACCOUNT (tny_folder_get_account (folder));
844                 trash_folder = modest_tny_account_get_special_folder (TNY_ACCOUNT(store_account),
845                                                                       TNY_FOLDER_TYPE_TRASH);
846                 if (trash_folder) {
847                         modest_mail_operation_move_msg (self, header, trash_folder);
848 /*                      g_object_unref (trash_folder); */
849                 } else {
850                         ModestMailOperationPrivate *priv;
851
852                         /* Set status failed and set an error */
853                         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
854                         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
855                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
856                                      MODEST_MAIL_OPERATION_ERROR_ITEM_NOT_FOUND,
857                                      _("Error trying to delete a message. Trash folder not found"));
858                 }
859
860                 g_object_unref (G_OBJECT (store_account));
861         } else {
862                 tny_folder_remove_msg (folder, header, NULL); /* FIXME */
863                 tny_folder_sync(folder, TRUE, NULL); /* FIXME */
864         }
865
866         /* Free */
867         g_object_unref (folder);
868 }
869
870 static void
871 transfer_msgs_cb (TnyFolder *folder, GError **err, gpointer user_data)
872 {
873         ModestMailOperationPrivate *priv;
874
875         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(user_data);
876         priv->done = 1;
877         priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
878
879         g_signal_emit (G_OBJECT (user_data), signals[PROGRESS_CHANGED_SIGNAL], 0, NULL);
880 }
881
882 static gboolean
883 modest_mail_operation_xfer_msg (ModestMailOperation *self,
884                                 TnyHeader *header, 
885                                 TnyFolder *folder, 
886                                 gboolean delete_original)
887 {
888         ModestMailOperationPrivate *priv;
889         TnyFolder *src_folder;
890         TnyList *headers;
891
892         src_folder = tny_header_get_folder (header);
893         headers = tny_simple_list_new ();
894
895         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
896         priv->total = 1;
897         priv->done = 0;
898         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
899
900         tny_list_prepend (headers, G_OBJECT (header));
901         tny_folder_transfer_msgs_async (src_folder, headers, folder, 
902                                         delete_original, transfer_msgs_cb, self);
903
904         /* Free */
905         g_object_unref (headers);
906         g_object_unref (folder);
907
908         return TRUE;
909 }
910
911
912 /* ******************************************************************* */
913 /* ************************* UTILIY FUNCTIONS ************************ */
914 /* ******************************************************************* */
915 static gboolean
916 is_ascii(const gchar *s)
917 {
918         while (s[0]) {
919                 if (s[0] & 128 || s[0] < 32)
920                         return FALSE;
921                 s++;
922         }
923         return TRUE;
924 }
925
926 static char *
927 get_content_type(const gchar *s)
928 {
929         GString *type;
930         
931         type = g_string_new("text/plain");
932         if (!is_ascii(s)) {
933                 if (g_utf8_validate(s, -1, NULL)) {
934                         g_string_append(type, "; charset=\"utf-8\"");
935                 } else {
936                         /* it should be impossible to reach this, but better safe than sorry */
937                         g_warning("invalid utf8 in message");
938                         g_string_append(type, "; charset=\"latin1\"");
939                 }
940         }
941         return g_string_free(type, FALSE);
942 }
943
944 static void
945 add_attachments (TnyMsg *msg, GList *attachments_list)
946 {
947         GList *pos;
948         TnyMimePart *attachment_part, *old_attachment;
949         const gchar *attachment_content_type;
950         const gchar *attachment_filename;
951         TnyStream *attachment_stream;
952         TnyPlatformFactory *fact;
953
954         fact = modest_tny_platform_factory_get_instance ();
955         for (pos = (GList *)attachments_list; pos; pos = pos->next) {
956
957                 old_attachment = pos->data;
958                 attachment_filename = tny_mime_part_get_filename (old_attachment);
959                 attachment_stream = tny_mime_part_get_stream (old_attachment);
960                 attachment_part = tny_platform_factory_new_mime_part (fact);
961                 
962                 attachment_content_type = tny_mime_part_get_content_type (old_attachment);
963                                  
964                 tny_mime_part_construct_from_stream (attachment_part,
965                                                      attachment_stream,
966                                                      attachment_content_type);
967                 tny_stream_reset (attachment_stream);
968                 
969                 tny_mime_part_set_filename (attachment_part, attachment_filename);
970                 
971                 tny_mime_part_add_part (TNY_MIME_PART (msg), attachment_part);
972 /*              g_object_unref (attachment_part); */
973         }
974 }
975
976
977 static TnyMimePart *
978 add_body_part (TnyMsg *msg, 
979                const gchar *body,
980                const gchar *content_type,
981                gboolean has_attachments)
982 {
983         TnyMimePart *text_body_part = NULL;
984         TnyStream *text_body_stream;
985         TnyPlatformFactory *fact;
986
987         fact = modest_tny_platform_factory_get_instance ();
988
989         /* Create the stream */
990         text_body_stream = TNY_STREAM (tny_camel_stream_new
991                                        (camel_stream_mem_new_with_buffer
992                                         (body, strlen(body))));
993
994         /* Create body part if needed */
995         if (has_attachments)
996                 text_body_part = tny_platform_factory_new_mime_part (fact);
997         else
998                 text_body_part = TNY_MIME_PART(msg);
999
1000         /* Construct MIME part */
1001         tny_stream_reset (text_body_stream);
1002         tny_mime_part_construct_from_stream (text_body_part,
1003                                              text_body_stream,
1004                                              content_type);
1005         tny_stream_reset (text_body_stream);
1006
1007         /* Add part if needed */
1008         if (has_attachments) {
1009                 tny_mime_part_add_part (TNY_MIME_PART (msg), text_body_part);
1010                 g_object_unref (G_OBJECT(text_body_part));
1011         }
1012
1013         /* Clean */
1014         g_object_unref (text_body_stream);
1015
1016         return text_body_part;
1017 }