Fix plugin api, so that it includes all the new required headers.
[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 <string.h>
31 #include <stdarg.h>
32 #include <tny-mime-part.h>
33 #include <tny-store-account.h>
34 #include <tny-folder-store.h>
35 #include <tny-folder-store-query.h>
36 #include <tny-camel-stream.h>
37 #include <tny-camel-pop-store-account.h>
38 #include <tny-camel-pop-folder.h>
39 #include <tny-camel-imap-folder.h>
40 #include <tny-camel-mem-stream.h>
41 #include <tny-simple-list.h>
42 #include <tny-send-queue.h>
43 #include <tny-status.h>
44 #include <tny-error.h>
45 #include <tny-folder-observer.h>
46 #include <camel/camel-stream-mem.h>
47 #include <glib/gi18n.h>
48 #include <modest-defs.h>
49 #include "modest-platform.h"
50 #include "modest-account-mgr-helpers.h"
51 #include <modest-tny-account.h>
52 #include <modest-tny-send-queue.h>
53 #include <modest-runtime.h>
54 #include "modest-text-utils.h"
55 #include "modest-tny-msg.h"
56 #include "modest-tny-folder.h"
57 #include "modest-tny-account-store.h"
58 #include "modest-tny-platform-factory.h"
59 #include "modest-marshal.h"
60 #include "modest-error.h"
61 #include "modest-mail-operation.h"
62 #include <modest-count-stream.h>
63 #include <libgnomevfs/gnome-vfs.h>
64 #include "modest-utils.h"
65 #include "modest-debug.h"
66
67 #define KB 1024
68
69 /* 
70  * Remove all these #ifdef stuff when the tinymail's idle calls become
71  * locked
72  */
73 #define TINYMAIL_IDLES_NOT_LOCKED_YET 1
74
75 /* 'private'/'protected' functions */
76 static void modest_mail_operation_class_init (ModestMailOperationClass *klass);
77 static void modest_mail_operation_init       (ModestMailOperation *obj);
78 static void modest_mail_operation_finalize   (GObject *obj);
79
80 static void     get_msg_async_cb (TnyFolder *folder, 
81                                   gboolean cancelled, 
82                                   TnyMsg *msg, 
83                                   GError *rr, 
84                                   gpointer user_data);
85
86 static void     get_msg_status_cb (GObject *obj,
87                                    TnyStatus *status,  
88                                    gpointer user_data);
89
90 static void     modest_mail_operation_notify_start (ModestMailOperation *self);
91 static void     modest_mail_operation_notify_end (ModestMailOperation *self);
92
93 static void     notify_progress_of_multiple_messages (ModestMailOperation *self,
94                                                       TnyStatus *status,
95                                                       gint *last_total_bytes,
96                                                       gint *sum_total_bytes,
97                                                       gint total_bytes,
98                                                       gboolean increment_done);
99
100 static guint    compute_message_list_size (TnyList *headers, guint num_elements);
101
102 static int      compare_headers_by_date   (gconstpointer a,
103                                            gconstpointer b);
104
105 static void     sync_folder_finish_callback (TnyFolder *self, 
106                                              gboolean cancelled, 
107                                              GError *err, 
108                                              gpointer user_data);
109
110 static gboolean _check_memory_low         (ModestMailOperation *mail_op);
111
112
113 typedef struct {
114         ModestTnySendQueue *queue;
115         ModestMailOperation *self;
116         guint error_handler;
117         guint start_handler;
118         guint stop_handler;
119 } RunQueueHelper;
120
121 static void run_queue_notify_and_destroy (RunQueueHelper *helper,
122                                           ModestMailOperationStatus status);
123
124 /* Helpers for the update account operation (send & receive)*/
125 typedef struct 
126 {
127         ModestMailOperation *mail_op;
128         gchar *account_name;
129         UpdateAccountCallback callback;
130         gpointer user_data;
131         TnyList *folders;
132         gint pending_calls;
133         gboolean poke_all;
134         TnyFolderObserver *inbox_observer;
135         RetrieveAllCallback retrieve_all_cb;
136         gboolean interactive;
137         gboolean msg_readed;
138 } UpdateAccountInfo;
139
140 static void destroy_update_account_info         (UpdateAccountInfo *info);
141
142 static void update_account_send_mail            (UpdateAccountInfo *info);
143
144 static void update_account_get_msg_async_cb     (TnyFolder *folder, 
145                                                  gboolean canceled, 
146                                                  TnyMsg *msg, 
147                                                  GError *err, 
148                                                  gpointer user_data);
149
150 static void update_account_notify_user_and_free (UpdateAccountInfo *info, 
151                                                  TnyList *new_headers);
152
153 enum _ModestMailOperationSignals 
154 {
155         PROGRESS_CHANGED_SIGNAL,
156         OPERATION_STARTED_SIGNAL,
157         OPERATION_FINISHED_SIGNAL,
158         NUM_SIGNALS
159 };
160
161 typedef struct _ModestMailOperationPrivate ModestMailOperationPrivate;
162 struct _ModestMailOperationPrivate {
163         TnyAccount                *account;
164         guint                      done;
165         guint                      total;
166         GObject                   *source;
167         GError                    *error;
168         ErrorCheckingUserCallback  error_checking;
169         gpointer                   error_checking_user_data;
170         ErrorCheckingUserDataDestroyer error_checking_user_data_destroyer;
171         ModestMailOperationStatus  status;      
172         ModestMailOperationTypeOperation op_type;
173 };
174
175 #define MODEST_MAIL_OPERATION_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
176                                                    MODEST_TYPE_MAIL_OPERATION, \
177                                                    ModestMailOperationPrivate))
178
179 #define CHECK_EXCEPTION(priv, new_status)  if (priv->error) {\
180                                                    priv->status = new_status;\
181                                                }
182
183
184 typedef struct {
185         GetMsgAsyncUserCallback user_callback;
186         TnyHeader *header;
187         TnyIterator *more_msgs;
188         gpointer user_data;
189         ModestMailOperation *mail_op;
190         GDestroyNotify destroy_notify;
191         gint last_total_bytes;
192         gint sum_total_bytes;
193         gint total_bytes;
194 } GetMsgInfo;
195
196 typedef struct _RefreshAsyncHelper {    
197         ModestMailOperation *mail_op;
198         RefreshAsyncUserCallback user_callback; 
199         gpointer user_data;
200 } RefreshAsyncHelper;
201
202 typedef struct _XFerMsgsAsyncHelper
203 {
204         ModestMailOperation *mail_op;
205         TnyList *headers;
206         TnyIterator *more_msgs;
207         TnyFolder *dest_folder;
208         XferMsgsAsyncUserCallback user_callback;        
209         gboolean delete;
210         gpointer user_data;
211         gint last_total_bytes;
212         gint sum_total_bytes;
213         gint total_bytes;
214 } XFerMsgsAsyncHelper;
215
216 typedef struct _XFerFolderAsyncHelper
217 {
218         ModestMailOperation *mail_op;
219         XferFolderAsyncUserCallback user_callback;      
220         gpointer user_data;
221 } XFerFolderAsyncHelper;
222
223 typedef void (*ModestMailOperationCreateMsgCallback) (ModestMailOperation *mail_op,
224                                                       TnyMsg *msg,
225                                                       gpointer userdata);
226
227 static void          modest_mail_operation_create_msg (ModestMailOperation *self,
228                                                        const gchar *from, const gchar *to,
229                                                        const gchar *cc, const gchar *bcc,
230                                                        const gchar *subject, const gchar *plain_body,
231                                                        const gchar *html_body, const GList *attachments_list,
232                                                        const GList *images_list,
233                                                        TnyHeaderFlags priority_flags,
234                                                        ModestMailOperationCreateMsgCallback callback,
235                                                        gpointer userdata);
236
237 static gboolean      idle_notify_queue (gpointer data);
238 typedef struct
239 {
240         ModestMailOperation *mail_op;
241         gchar *from;
242         gchar *to;
243         gchar *cc;
244         gchar *bcc;
245         gchar *subject;
246         gchar *plain_body;
247         gchar *html_body;
248         GList *attachments_list;
249         GList *images_list;
250         TnyHeaderFlags priority_flags;
251         ModestMailOperationCreateMsgCallback callback;
252         gpointer userdata;
253 } CreateMsgInfo;
254
255 typedef struct
256 {
257         ModestMailOperation *mail_op;
258         TnyMsg *msg;
259         ModestMailOperationCreateMsgCallback callback;
260         gpointer userdata;
261 } CreateMsgIdleInfo;
262
263 /* globals */
264 static GObjectClass *parent_class = NULL;
265
266 static guint signals[NUM_SIGNALS] = {0};
267
268 GType
269 modest_mail_operation_get_type (void)
270 {
271         static GType my_type = 0;
272         if (!my_type) {
273                 static const GTypeInfo my_info = {
274                         sizeof(ModestMailOperationClass),
275                         NULL,           /* base init */
276                         NULL,           /* base finalize */
277                         (GClassInitFunc) modest_mail_operation_class_init,
278                         NULL,           /* class finalize */
279                         NULL,           /* class data */
280                         sizeof(ModestMailOperation),
281                         1,              /* n_preallocs */
282                         (GInstanceInitFunc) modest_mail_operation_init,
283                         NULL
284                 };
285                 my_type = g_type_register_static (G_TYPE_OBJECT,
286                                                   "ModestMailOperation",
287                                                   &my_info, 0);
288         }
289         return my_type;
290 }
291
292 static void
293 modest_mail_operation_class_init (ModestMailOperationClass *klass)
294 {
295         GObjectClass *gobject_class;
296         gobject_class = (GObjectClass*) klass;
297
298         parent_class            = g_type_class_peek_parent (klass);
299         gobject_class->finalize = modest_mail_operation_finalize;
300
301         g_type_class_add_private (gobject_class, sizeof(ModestMailOperationPrivate));
302
303         /**
304          * ModestMailOperation::progress-changed
305          * @self: the #MailOperation that emits the signal
306          * @user_data: user data set when the signal handler was connected
307          *
308          * Emitted when the progress of a mail operation changes
309          */
310         signals[PROGRESS_CHANGED_SIGNAL] = 
311                 g_signal_new ("progress-changed",
312                               G_TYPE_FROM_CLASS (gobject_class),
313                               G_SIGNAL_RUN_FIRST,
314                               G_STRUCT_OFFSET (ModestMailOperationClass, progress_changed),
315                               NULL, NULL,
316                               g_cclosure_marshal_VOID__POINTER,
317                               G_TYPE_NONE, 1, G_TYPE_POINTER);
318         /**
319          * operation-started
320          *
321          * This signal is issued whenever a mail operation starts, and
322          * starts mean when the tinymail operation is issued. This
323          * means that it could happen that something wrong happens and
324          * the tinymail function is never called. In this situation a
325          * operation-finished will be issued without any
326          * operation-started
327          */
328         signals[OPERATION_STARTED_SIGNAL] =
329                 g_signal_new ("operation-started",
330                               G_TYPE_FROM_CLASS (gobject_class),
331                               G_SIGNAL_RUN_FIRST,
332                               G_STRUCT_OFFSET (ModestMailOperationClass, operation_started),
333                               NULL, NULL,
334                               g_cclosure_marshal_VOID__VOID,
335                               G_TYPE_NONE, 0);
336         /**
337          * operation-started
338          *
339          * This signal is issued whenever a mail operation
340          * finishes. Note that this signal could be issued without any
341          * previous "operation-started" signal, because this last one
342          * is only issued when the tinymail operation is successfully
343          * started
344          */
345         signals[OPERATION_FINISHED_SIGNAL] =
346                 g_signal_new ("operation-finished",
347                               G_TYPE_FROM_CLASS (gobject_class),
348                               G_SIGNAL_RUN_FIRST,
349                               G_STRUCT_OFFSET (ModestMailOperationClass, operation_finished),
350                               NULL, NULL,
351                               g_cclosure_marshal_VOID__VOID,
352                               G_TYPE_NONE, 0);
353 }
354
355 static void
356 modest_mail_operation_init (ModestMailOperation *obj)
357 {
358         ModestMailOperationPrivate *priv;
359
360         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(obj);
361
362         priv->account        = NULL;
363         priv->status         = MODEST_MAIL_OPERATION_STATUS_INVALID;
364         priv->op_type        = MODEST_MAIL_OPERATION_TYPE_UNKNOWN;
365         priv->error          = NULL;
366         priv->done           = 0;
367         priv->total          = 0;
368         priv->source         = NULL;
369         priv->error_checking = NULL;
370         priv->error_checking_user_data = NULL;
371 }
372
373 static void
374 modest_mail_operation_finalize (GObject *obj)
375 {
376         ModestMailOperationPrivate *priv;
377
378         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(obj);
379
380         
381         
382         if (priv->error) {
383                 g_error_free (priv->error);
384                 priv->error = NULL;
385         }
386         if (priv->source) {
387                 g_object_unref (priv->source);
388                 priv->source = NULL;
389         }
390         if (priv->account) {
391                 g_object_unref (priv->account);
392                 priv->account = NULL;
393         }
394
395
396         G_OBJECT_CLASS(parent_class)->finalize (obj);
397 }
398
399 ModestMailOperation*
400 modest_mail_operation_new (GObject *source)
401 {
402         ModestMailOperation *obj;
403         ModestMailOperationPrivate *priv;
404                 
405         obj = MODEST_MAIL_OPERATION(g_object_new(MODEST_TYPE_MAIL_OPERATION, NULL));
406         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(obj);
407
408         if (source != NULL)
409                 priv->source = g_object_ref(source);
410
411         return obj;
412 }
413
414 ModestMailOperation*
415 modest_mail_operation_new_with_error_handling (GObject *source,
416                                                ErrorCheckingUserCallback error_handler,
417                                                gpointer user_data,
418                                                ErrorCheckingUserDataDestroyer error_handler_destroyer)
419 {
420         ModestMailOperation *obj;
421         ModestMailOperationPrivate *priv;
422                 
423         obj = modest_mail_operation_new (source);
424         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(obj);
425         
426         g_return_val_if_fail (error_handler != NULL, obj);
427         priv->error_checking = error_handler;
428         priv->error_checking_user_data = user_data;
429         priv->error_checking_user_data_destroyer = error_handler_destroyer;
430
431         return obj;
432 }
433
434 void
435 modest_mail_operation_execute_error_handler (ModestMailOperation *self)
436 {
437         ModestMailOperationPrivate *priv;
438
439         g_return_if_fail (self && MODEST_IS_MAIL_OPERATION(self));
440         
441         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
442         g_return_if_fail(priv->status != MODEST_MAIL_OPERATION_STATUS_SUCCESS);     
443
444         /* Call the user callback */
445         if (priv->error_checking != NULL)
446                 priv->error_checking (self, priv->error_checking_user_data);
447 }
448
449
450 ModestMailOperationTypeOperation
451 modest_mail_operation_get_type_operation (ModestMailOperation *self)
452 {
453         ModestMailOperationPrivate *priv;
454
455         g_return_val_if_fail (self && MODEST_IS_MAIL_OPERATION(self),
456                               MODEST_MAIL_OPERATION_TYPE_UNKNOWN);
457         
458         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
459         
460         return priv->op_type;
461 }
462
463 gboolean 
464 modest_mail_operation_is_mine (ModestMailOperation *self, 
465                                GObject *me)
466 {
467         ModestMailOperationPrivate *priv;
468
469         g_return_val_if_fail (self && MODEST_IS_MAIL_OPERATION(self),
470                               FALSE);
471         
472         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
473         if (priv->source == NULL) return FALSE;
474
475         return priv->source == me;
476 }
477
478 GObject *
479 modest_mail_operation_get_source (ModestMailOperation *self)
480 {
481         ModestMailOperationPrivate *priv;
482
483         g_return_val_if_fail (self && MODEST_IS_MAIL_OPERATION(self),
484                               NULL);
485         
486         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
487         if (!priv) {
488                 g_warning ("BUG: %s: priv == NULL", __FUNCTION__);
489                 return NULL;
490         }
491         
492         return (priv->source) ? g_object_ref (priv->source) : NULL;
493 }
494
495 ModestMailOperationStatus
496 modest_mail_operation_get_status (ModestMailOperation *self)
497 {
498         ModestMailOperationPrivate *priv;
499
500         g_return_val_if_fail (self, MODEST_MAIL_OPERATION_STATUS_INVALID);
501         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION (self),
502                               MODEST_MAIL_OPERATION_STATUS_INVALID);
503
504         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
505         if (!priv) {
506                 g_warning ("BUG: %s: priv == NULL", __FUNCTION__);
507                 return MODEST_MAIL_OPERATION_STATUS_INVALID;
508         }
509         
510         return priv->status;
511 }
512
513 const GError *
514 modest_mail_operation_get_error (ModestMailOperation *self)
515 {
516         ModestMailOperationPrivate *priv;
517
518         g_return_val_if_fail (self, NULL);
519         g_return_val_if_fail (MODEST_IS_MAIL_OPERATION (self), NULL);
520
521         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
522
523         if (!priv) {
524                 g_warning ("BUG: %s: priv == NULL", __FUNCTION__);
525                 return NULL;
526         }
527
528         return priv->error;
529 }
530
531 gboolean 
532 modest_mail_operation_cancel (ModestMailOperation *self)
533 {
534         ModestMailOperationPrivate *priv;
535         gboolean canceled = FALSE;
536         
537         g_return_val_if_fail (self && MODEST_IS_MAIL_OPERATION (self), FALSE);
538
539         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
540
541         /* Set new status */
542         priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
543         
544         /* Cancel the mail operation */
545         g_return_val_if_fail (priv->account, FALSE);
546         tny_account_cancel (priv->account);
547
548         if (priv->op_type == MODEST_MAIL_OPERATION_TYPE_SEND) {
549                 ModestTnySendQueue *queue;
550                 queue = modest_runtime_get_send_queue (TNY_TRANSPORT_ACCOUNT (priv->account),
551                                                        TRUE);
552
553                 /* Cancel the sending of the following next messages */
554                 if (TNY_IS_SEND_QUEUE (queue))
555                         tny_send_queue_cancel (TNY_SEND_QUEUE (queue), TNY_SEND_QUEUE_CANCEL_ACTION_SUSPEND, NULL);
556         }
557         
558         return canceled;
559 }
560
561 guint 
562 modest_mail_operation_get_task_done (ModestMailOperation *self)
563 {
564         ModestMailOperationPrivate *priv;
565
566         g_return_val_if_fail (self && MODEST_IS_MAIL_OPERATION(self),
567                               0);
568         
569         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
570         return priv->done;
571 }
572
573 guint 
574 modest_mail_operation_get_task_total (ModestMailOperation *self)
575 {
576         ModestMailOperationPrivate *priv;
577
578         g_return_val_if_fail (self && MODEST_IS_MAIL_OPERATION(self),
579                               0);
580
581         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
582         return priv->total;
583 }
584
585 gboolean
586 modest_mail_operation_is_finished (ModestMailOperation *self)
587 {
588         ModestMailOperationPrivate *priv;
589         gboolean retval = FALSE;
590
591         g_return_val_if_fail (self && MODEST_IS_MAIL_OPERATION(self),
592                               FALSE);
593         
594         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
595
596         if (priv->status == MODEST_MAIL_OPERATION_STATUS_SUCCESS   ||
597             priv->status == MODEST_MAIL_OPERATION_STATUS_FAILED    ||
598             priv->status == MODEST_MAIL_OPERATION_STATUS_CANCELED  ||
599             priv->status == MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS) {
600                 retval = TRUE;
601         } else {
602                 retval = FALSE;
603         }
604
605         return retval;
606 }
607
608 /*
609  * Creates an image of the current state of a mail operation, the
610  * caller must free it
611  */
612 static ModestMailOperationState *
613 modest_mail_operation_clone_state (ModestMailOperation *self)
614 {
615         ModestMailOperationState *state;
616         ModestMailOperationPrivate *priv;
617
618         g_return_val_if_fail (self, NULL);
619         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
620         g_return_val_if_fail (priv, NULL);
621
622         if (!priv)
623                 return NULL;
624
625         state = g_slice_new (ModestMailOperationState);
626
627         state->status = priv->status;
628         state->op_type = priv->op_type;
629         state->done = priv->done;
630         state->total = priv->total;
631         state->finished = modest_mail_operation_is_finished (self);
632         state->bytes_done = 0;
633         state->bytes_total = 0;
634
635         return state;
636 }
637
638 /* ******************************************************************* */
639 /* ************************** SEND   ACTIONS ************************* */
640 /* ******************************************************************* */
641
642 typedef struct 
643 {
644         ModestMailOperation *mail_op;
645         gboolean notify;
646 } SendNewMailHelper;
647
648 static void
649 send_mail_on_sync_async_cb (TnyFolder *folder, 
650                             gboolean cancelled, 
651                             GError *err, 
652                             gpointer user_data)
653 {
654         ModestMailOperationPrivate *priv;
655         ModestMailOperation *self;
656         SendNewMailHelper *helper;
657
658         helper = (SendNewMailHelper *) user_data;
659         self = helper->mail_op;
660         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
661
662         if (cancelled || err)
663                 goto end;
664
665         if (err) {
666                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
667                              MODEST_MAIL_OPERATION_ERROR_SEND_QUEUE_ADD_ERROR,
668                              "Error adding a msg to the send queue\n");
669                 priv->status = MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS;
670         } else {
671                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
672         }
673
674  end:
675         if (helper->notify)
676                 modest_mail_operation_notify_end (self);
677
678         g_object_unref (helper->mail_op);
679         g_slice_free (SendNewMailHelper, helper);
680 }
681
682 static void
683 run_queue_start (TnySendQueue *self,
684                  gpointer user_data)
685 {
686         RunQueueHelper *helper = (RunQueueHelper *) user_data;
687         ModestMailOperation *mail_op;
688                         
689         g_debug ("%s sending queue successfully started", __FUNCTION__);
690
691         /* Wait for the message to be sent */
692         mail_op = modest_mail_operation_new (NULL);
693         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
694                                          mail_op);
695         modest_mail_operation_run_queue (mail_op, helper->queue);
696         g_object_unref (mail_op);
697
698         /* Free the helper and end operation */
699         run_queue_notify_and_destroy (helper, MODEST_MAIL_OPERATION_STATUS_SUCCESS);
700 }
701
702 static void 
703 run_queue_error_happened (TnySendQueue *queue, 
704                           TnyHeader *header, 
705                           TnyMsg *msg, 
706                           GError *error, 
707                           gpointer user_data)
708 {
709         RunQueueHelper *helper = (RunQueueHelper *) user_data;
710         ModestMailOperationPrivate *priv;
711
712         /* If we are here this means that the send queue could not
713            start to send emails. Shouldn't happen as this means that
714            we could not create the thread */
715         g_debug ("%s sending queue failed to create the thread", __FUNCTION__);
716
717         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (helper->self);
718         priv->error = g_error_copy ((const GError *) error);
719
720         if (error->code != TNY_SYSTEM_ERROR_UNKNOWN) {
721                 /* This code is here for safety reasons. It should
722                    never be called, because that would mean that we
723                    are not controlling some error case */
724                 g_warning ("%s Error %s should not happen", 
725                            __FUNCTION__, error->message);
726         }
727
728         /* Free helper and end operation */
729         run_queue_notify_and_destroy (helper, MODEST_MAIL_OPERATION_STATUS_FAILED);
730 }
731
732 static void
733 send_mail_on_added_to_outbox (TnySendQueue *send_queue, 
734                               gboolean cancelled, 
735                               TnyMsg *msg, 
736                               GError *err,
737                               gpointer user_data)
738 {
739         ModestMailOperationPrivate *priv;
740         ModestMailOperation *self;
741         SendNewMailHelper *helper;
742
743         helper = (SendNewMailHelper *) user_data;
744         self = helper->mail_op;
745         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
746
747         if (cancelled || err)
748                 goto end;
749
750         if (err) {
751                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
752                              MODEST_MAIL_OPERATION_ERROR_SEND_QUEUE_ADD_ERROR,
753                              "Error adding a msg to the send queue\n");
754                 priv->status = MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS;
755         } else {
756                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
757         }
758
759  end:
760         if (helper->notify) {
761                 TnyTransportAccount *trans_account;
762                 ModestTnySendQueue *queue;
763
764                 trans_account = (TnyTransportAccount *) modest_mail_operation_get_account (self);
765                 queue = modest_runtime_get_send_queue (trans_account, TRUE);
766                 if (queue) {
767                         RunQueueHelper *helper;
768
769                         /* Create the helper */
770                         helper = g_slice_new0 (RunQueueHelper);
771                         helper->queue = g_object_ref (queue);
772                         helper->self = g_object_ref (self);
773
774                         /* if sending is ongoing wait for the queue to
775                            stop. Otherwise wait for the queue-start
776                            signal. It could happen that the queue
777                            could not start, then check also the error
778                            happened signal */
779                         if (modest_tny_send_queue_sending_in_progress (queue)) {
780                                 run_queue_start (TNY_SEND_QUEUE (queue), helper);
781                         } else {
782                                 helper->start_handler = g_signal_connect (queue, "queue-start", 
783                                                                           G_CALLBACK (run_queue_start), 
784                                                                           helper);
785                                 helper->error_handler = g_signal_connect (queue, "error-happened", 
786                                                                           G_CALLBACK (run_queue_error_happened), 
787                                                                           helper);
788                         }
789                 } else {
790                         /* Finalize this mail operation */
791                         modest_mail_operation_notify_end (self);
792                 }
793                 g_object_unref (trans_account);
794         }
795
796         g_object_unref (helper->mail_op);
797         g_slice_free (SendNewMailHelper, helper);
798 }
799
800 static gboolean
801 idle_create_msg_cb (gpointer idle_data)
802 {
803         CreateMsgIdleInfo *info = (CreateMsgIdleInfo *) idle_data;
804
805         /* This is a GDK lock because we are an idle callback and
806          * info->callback can contain Gtk+ code */
807
808         gdk_threads_enter (); /* CHECKED */
809         info->callback (info->mail_op, info->msg, info->userdata);
810
811         g_object_unref (info->mail_op);
812         if (info->msg)
813                 g_object_unref (info->msg);
814         g_slice_free (CreateMsgIdleInfo, info);
815         gdk_threads_leave (); /* CHECKED */
816
817         return FALSE;
818 }
819
820 static gpointer 
821 create_msg_thread (gpointer thread_data)
822 {
823         CreateMsgInfo *info = (CreateMsgInfo *) thread_data;
824         TnyMsg *new_msg = NULL;
825         ModestMailOperationPrivate *priv;
826         gint attached = 0;
827
828         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(info->mail_op);
829         if (info->html_body == NULL) {
830                 new_msg = modest_tny_msg_new (info->to, info->from, info->cc, 
831                                               info->bcc, info->subject, info->plain_body, 
832                                               info->attachments_list, &attached,
833                                               &(priv->error));
834         } else {
835                 new_msg = modest_tny_msg_new_html_plain (info->to, info->from, info->cc,
836                                                          info->bcc, info->subject, info->html_body,
837                                                          info->plain_body, info->attachments_list,
838                                                          info->images_list, &attached,
839                                                          &(priv->error));
840         }
841
842         if (new_msg) {
843                 TnyHeader *header;
844
845                 /* Set priority flags in message */
846                 header = tny_msg_get_header (new_msg);
847                 tny_header_set_flag (header, info->priority_flags);
848
849                 /* Set attachment flags in message */
850                 if (info->attachments_list != NULL && attached > 0)
851                         tny_header_set_flag (header, TNY_HEADER_FLAG_ATTACHMENTS);
852
853                 g_object_unref (G_OBJECT(header));
854         } else {
855                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
856                 if (!priv->error)
857                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
858                                      MODEST_MAIL_OPERATION_ERROR_INSTANCE_CREATION_FAILED,
859                                      "modest: failed to create a new msg\n");
860         }
861
862
863         g_free (info->to);
864         g_free (info->from);
865         g_free (info->cc);
866         g_free (info->bcc);
867         g_free (info->plain_body);
868         g_free (info->html_body);
869         g_free (info->subject);
870         g_list_foreach (info->attachments_list, (GFunc) g_object_unref, NULL);
871         g_list_free (info->attachments_list);
872         g_list_foreach (info->images_list, (GFunc) g_object_unref, NULL);
873         g_list_free (info->images_list);
874
875         if (info->callback) {
876                 CreateMsgIdleInfo *idle_info;
877                 idle_info = g_slice_new0 (CreateMsgIdleInfo);
878                 idle_info->mail_op = g_object_ref (info->mail_op);
879                 idle_info->msg = (new_msg) ? g_object_ref (new_msg) : NULL;
880                 idle_info->callback = info->callback;
881                 idle_info->userdata = info->userdata;
882                 g_idle_add (idle_create_msg_cb, idle_info);
883         } else {
884                 g_idle_add (idle_notify_queue, g_object_ref (info->mail_op));
885         }
886
887         g_object_unref (info->mail_op);
888         g_slice_free (CreateMsgInfo, info);
889         if (new_msg) g_object_unref(new_msg);
890         return NULL;
891 }
892
893
894 void
895 modest_mail_operation_create_msg (ModestMailOperation *self,
896                                   const gchar *from, const gchar *to,
897                                   const gchar *cc, const gchar *bcc,
898                                   const gchar *subject, const gchar *plain_body,
899                                   const gchar *html_body,
900                                   const GList *attachments_list,
901                                   const GList *images_list,
902                                   TnyHeaderFlags priority_flags,
903                                   ModestMailOperationCreateMsgCallback callback,
904                                   gpointer userdata)
905 {
906         CreateMsgInfo *info = NULL;
907
908         info = g_slice_new0 (CreateMsgInfo);
909         info->mail_op = g_object_ref (self);
910
911         info->from = g_strdup (from);
912         info->to = g_strdup (to);
913         info->cc = g_strdup (cc);
914         info->bcc  = g_strdup (bcc);
915         info->subject = g_strdup (subject);
916         info->plain_body = g_strdup (plain_body);
917         info->html_body = g_strdup (html_body);
918         info->attachments_list = g_list_copy ((GList *) attachments_list);
919         g_list_foreach (info->attachments_list, (GFunc) g_object_ref, NULL);
920         info->images_list = g_list_copy ((GList *) images_list);
921         g_list_foreach (info->images_list, (GFunc) g_object_ref, NULL);
922         info->priority_flags = priority_flags;
923
924         info->callback = callback;
925         info->userdata = userdata;
926
927         g_thread_create (create_msg_thread, info, FALSE, NULL);
928 }
929
930 typedef struct
931 {
932         TnyTransportAccount *transport_account;
933         TnyMsg *draft_msg;
934 } SendNewMailInfo;
935
936 static void
937 modest_mail_operation_send_new_mail_cb (ModestMailOperation *self,
938                                         TnyMsg *msg,
939                                         gpointer userdata)
940 {
941         TnySendQueue *send_queue = NULL;
942         ModestMailOperationPrivate *priv = NULL;
943         SendNewMailInfo *info = (SendNewMailInfo *) userdata;
944         TnyFolder *draft_folder = NULL;
945         TnyFolder *outbox_folder = NULL;
946         TnyHeader *header = NULL;
947
948         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
949
950         if (!msg) {
951                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
952                 modest_mail_operation_notify_end (self);
953                 goto end;
954         }
955
956         if (priv->error && priv->error->code != MODEST_MAIL_OPERATION_ERROR_FILE_IO) {
957                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
958                 modest_mail_operation_notify_end (self);
959                 goto end;
960         }
961
962         /* Add message to send queue */
963         send_queue = TNY_SEND_QUEUE (modest_runtime_get_send_queue (info->transport_account, TRUE));
964         if (!TNY_IS_SEND_QUEUE(send_queue)) {
965                 if (priv->error) {
966                         g_error_free (priv->error);
967                         priv->error = NULL;
968                 }
969                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
970                              MODEST_MAIL_OPERATION_ERROR_ITEM_NOT_FOUND,
971                              "modest: could not find send queue for account\n");
972                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
973                 modest_mail_operation_notify_end (self);
974                 goto end;
975         } else {
976                 SendNewMailHelper *helper = g_slice_new (SendNewMailHelper);
977                 helper->mail_op = g_object_ref (self);
978                 helper->notify = (info->draft_msg == NULL);
979
980                 /* Add the msg to the queue. The callback will free
981                    the helper */
982                 modest_tny_send_queue_set_requested_send_receive (MODEST_TNY_SEND_QUEUE (send_queue), 
983                                                                   FALSE);
984                 tny_send_queue_add_async (send_queue, msg, send_mail_on_added_to_outbox, 
985                                           NULL, helper);
986         }
987
988         if (info->draft_msg != NULL) {
989                 TnyList *tmp_headers = NULL;
990                 TnyFolder *folder = NULL;
991                 TnyFolder *src_folder = NULL;
992                 TnyFolderType folder_type;              
993                 TnyTransportAccount *transport_account = NULL;
994                 SendNewMailHelper *helper = NULL;
995
996                 /* To remove the old mail from its source folder, we need to get the
997                  * transport account of the original draft message (the transport account
998                  * might have been changed by the user) */
999                 header = tny_msg_get_header (info->draft_msg);
1000                 transport_account = modest_tny_account_store_get_transport_account_from_outbox_header(
1001                         modest_runtime_get_account_store(), header);
1002                 if (transport_account == NULL)
1003                         transport_account = g_object_ref(info->transport_account);
1004                 draft_folder = modest_tny_account_get_special_folder (TNY_ACCOUNT (transport_account),
1005                                                                       TNY_FOLDER_TYPE_DRAFTS);
1006                 outbox_folder = modest_tny_account_get_special_folder (TNY_ACCOUNT (transport_account),
1007                                                                        TNY_FOLDER_TYPE_OUTBOX);
1008                 g_object_unref(transport_account);
1009
1010                 if (!draft_folder) {
1011                         g_warning ("%s: modest_tny_account_get_special_folder(..) returned a NULL drafts folder",
1012                                    __FUNCTION__);
1013                         modest_mail_operation_notify_end (self);
1014                         goto end;
1015                 }
1016                 if (!outbox_folder) {
1017                         g_warning ("%s: modest_tny_account_get_special_folder(..) returned a NULL outbox folder",
1018                                    __FUNCTION__);
1019                         modest_mail_operation_notify_end (self);
1020                         goto end;
1021                 }
1022
1023                 folder = tny_msg_get_folder (info->draft_msg);          
1024                 if (folder == NULL) {
1025                         modest_mail_operation_notify_end (self);
1026                         goto end;
1027                 }
1028                 folder_type = modest_tny_folder_guess_folder_type (folder);
1029
1030                 if (folder_type == TNY_FOLDER_TYPE_INVALID)
1031                         g_warning ("%s: BUG: folder of type TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1032                 
1033                 if (folder_type == TNY_FOLDER_TYPE_OUTBOX) 
1034                         src_folder = outbox_folder;
1035                 else 
1036                         src_folder = draft_folder;
1037
1038                 /* Note: This can fail (with a warning) if the message is not really already in a folder,
1039                  * because this function requires it to have a UID. */
1040                 helper = g_slice_new (SendNewMailHelper);
1041                 helper->mail_op = g_object_ref (self);
1042                 helper->notify = TRUE;
1043
1044                 tmp_headers = tny_simple_list_new ();
1045                 tny_list_append (tmp_headers, (GObject*) header);
1046                 tny_folder_remove_msgs_async (src_folder, tmp_headers, NULL, NULL, NULL);
1047                 g_object_unref (tmp_headers);
1048                 tny_folder_sync_async (src_folder, TRUE, send_mail_on_sync_async_cb, 
1049                                        NULL, helper);
1050                 g_object_unref (folder);
1051         }
1052
1053 end:
1054         if (header)
1055                 g_object_unref (header);
1056         if (info->draft_msg)
1057                 g_object_unref (info->draft_msg);
1058         if (draft_folder)
1059                 g_object_unref (draft_folder);
1060         if (outbox_folder)
1061                 g_object_unref (outbox_folder);
1062         if (info->transport_account)
1063                 g_object_unref (info->transport_account);
1064         g_slice_free (SendNewMailInfo, info);
1065 }
1066
1067 void
1068 modest_mail_operation_send_new_mail (ModestMailOperation *self,
1069                                      TnyTransportAccount *transport_account,
1070                                      TnyMsg *draft_msg,
1071                                      const gchar *from,  const gchar *to,
1072                                      const gchar *cc,  const gchar *bcc,
1073                                      const gchar *subject, const gchar *plain_body,
1074                                      const gchar *html_body,
1075                                      const GList *attachments_list,
1076                                      const GList *images_list,
1077                                      TnyHeaderFlags priority_flags)
1078 {
1079         ModestMailOperationPrivate *priv = NULL;
1080         SendNewMailInfo *info;
1081
1082         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
1083         g_return_if_fail (TNY_IS_TRANSPORT_ACCOUNT (transport_account));
1084
1085         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
1086         priv->op_type = MODEST_MAIL_OPERATION_TYPE_SEND;
1087         priv->account = TNY_ACCOUNT (g_object_ref (transport_account));
1088         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
1089
1090         modest_mail_operation_notify_start (self);
1091
1092         /* Check parametters */
1093         if (to == NULL) {
1094                 /* Set status failed and set an error */
1095                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
1096                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
1097                              MODEST_MAIL_OPERATION_ERROR_BAD_PARAMETER,
1098                              _("Error trying to send a mail. You need to set at least one recipient"));
1099                 modest_mail_operation_notify_end (self);
1100                 return;
1101         }
1102         info = g_slice_new0 (SendNewMailInfo);
1103         info->transport_account = transport_account;
1104         if (transport_account)
1105                 g_object_ref (transport_account);
1106         info->draft_msg = draft_msg;
1107         if (draft_msg)
1108                 g_object_ref (draft_msg);
1109
1110
1111         modest_mail_operation_create_msg (self, from, to, cc, bcc, subject, plain_body, html_body,
1112                                           attachments_list, images_list, priority_flags,
1113                                           modest_mail_operation_send_new_mail_cb, info);
1114
1115 }
1116
1117 typedef struct
1118 {
1119         TnyTransportAccount *transport_account;
1120         TnyMsg *draft_msg;
1121         SaveToDraftstCallback callback;
1122         gpointer user_data;
1123         TnyFolder *drafts;
1124         TnyMsg *msg;
1125         ModestMailOperation *mailop;
1126 } SaveToDraftsAddMsgInfo;
1127
1128 static void
1129 modest_mail_operation_save_to_drafts_add_msg_cb(TnyFolder *self,
1130                                                 gboolean canceled,
1131                                                 GError *err,
1132                                                 gpointer userdata)
1133 {
1134         ModestMailOperationPrivate *priv = NULL;
1135         SaveToDraftsAddMsgInfo *info = (SaveToDraftsAddMsgInfo *) userdata;
1136         GError *io_error = NULL;
1137
1138         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(info->mailop);
1139
1140         if (priv->error && priv->error->code == MODEST_MAIL_OPERATION_ERROR_FILE_IO) {
1141                 io_error = priv->error;
1142                 priv->error = NULL;
1143         }
1144         if (priv->error) {
1145                 g_warning ("%s: priv->error != NULL", __FUNCTION__);
1146                 g_error_free(priv->error);
1147         }
1148
1149         priv->error = (err == NULL) ? NULL : g_error_copy(err);
1150
1151         if ((!priv->error) && (info->draft_msg != NULL)) {
1152                 TnyHeader *header = tny_msg_get_header (info->draft_msg);
1153                 TnyFolder *src_folder = tny_header_get_folder (header);
1154
1155                 g_debug ("--- REMOVE AND SYNC");
1156                 /* Remove the old draft */
1157                 tny_folder_remove_msg (src_folder, header, NULL);
1158
1159                 /* Synchronize to expunge and to update the msg counts */
1160                 tny_folder_sync_async (info->drafts, TRUE, NULL, NULL, NULL);
1161                 tny_folder_sync_async (src_folder, TRUE, NULL, NULL, NULL);
1162                 g_debug ("--- REMOVED - SYNCED");
1163
1164                 g_object_unref (G_OBJECT(header));
1165                 g_object_unref (G_OBJECT(src_folder));
1166         }
1167
1168         if (priv->error) {
1169                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
1170                 if (io_error) {
1171                         g_error_free (io_error);
1172                         io_error = NULL;
1173                 }
1174         } else if (io_error) {
1175                 priv->error = io_error;
1176                 priv->status = MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS;
1177         } else {
1178                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
1179         }
1180
1181         /* Call the user callback */
1182         if (info->callback)
1183                 info->callback (info->mailop, info->msg, info->user_data);
1184
1185         if (info->transport_account)
1186                 g_object_unref (G_OBJECT(info->transport_account));
1187         if (info->draft_msg)
1188                 g_object_unref (G_OBJECT (info->draft_msg));
1189         if (info->drafts)
1190                 g_object_unref (G_OBJECT(info->drafts));
1191         if (info->msg)
1192                 g_object_unref (G_OBJECT (info->msg));
1193
1194         modest_mail_operation_notify_end (info->mailop);
1195         g_object_unref(info->mailop);
1196         g_slice_free (SaveToDraftsAddMsgInfo, info);
1197 }
1198
1199 typedef struct
1200 {
1201         TnyTransportAccount *transport_account;
1202         TnyMsg *draft_msg;
1203         SaveToDraftstCallback callback;
1204         gpointer user_data;
1205 } SaveToDraftsInfo;
1206
1207 static void
1208 modest_mail_operation_save_to_drafts_cb (ModestMailOperation *self,
1209                                          TnyMsg *msg,
1210                                          gpointer userdata)
1211 {
1212         TnyFolder *drafts = NULL;
1213         ModestMailOperationPrivate *priv = NULL;
1214         SaveToDraftsInfo *info = (SaveToDraftsInfo *) userdata;
1215
1216         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
1217
1218         if (!msg) {
1219                 if (!(priv->error)) {
1220                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
1221                                      MODEST_MAIL_OPERATION_ERROR_INSTANCE_CREATION_FAILED,
1222                                      "modest: failed to create a new msg\n");
1223                 }
1224         } else {
1225                 drafts = modest_tny_account_get_special_folder (TNY_ACCOUNT (info->transport_account),
1226                                                                 TNY_FOLDER_TYPE_DRAFTS);
1227                 if (!drafts && !(priv->error)) {
1228                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
1229                                      MODEST_MAIL_OPERATION_ERROR_ITEM_NOT_FOUND,
1230                                      "modest: failed to create a new msg\n");
1231                 }
1232         }
1233
1234         if (!priv->error || priv->error->code == MODEST_MAIL_OPERATION_ERROR_FILE_IO) {
1235                 SaveToDraftsAddMsgInfo *cb_info = g_slice_new(SaveToDraftsAddMsgInfo);
1236                 cb_info->transport_account = g_object_ref(info->transport_account);
1237                 cb_info->draft_msg = info->draft_msg ? g_object_ref(info->draft_msg) : NULL;
1238                 cb_info->callback = info->callback;
1239                 cb_info->user_data = info->user_data;
1240                 cb_info->drafts = g_object_ref(drafts);
1241                 cb_info->msg = g_object_ref(msg);
1242                 cb_info->mailop = g_object_ref(self);
1243                 tny_folder_add_msg_async(drafts, msg, modest_mail_operation_save_to_drafts_add_msg_cb,
1244                                          NULL, cb_info);
1245         } else {
1246                 /* Call the user callback */
1247                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
1248                 if (info->callback)
1249                         info->callback (self, msg, info->user_data);
1250                 modest_mail_operation_notify_end (self);
1251         }
1252
1253         if (drafts)
1254                 g_object_unref (G_OBJECT(drafts));
1255         if (info->draft_msg)
1256                 g_object_unref (G_OBJECT (info->draft_msg));
1257         if (info->transport_account)
1258                 g_object_unref (G_OBJECT(info->transport_account));
1259         g_slice_free (SaveToDraftsInfo, info);
1260 }
1261
1262 void
1263 modest_mail_operation_save_to_drafts (ModestMailOperation *self,
1264                                       TnyTransportAccount *transport_account,
1265                                       TnyMsg *draft_msg,
1266                                       const gchar *from,  const gchar *to,
1267                                       const gchar *cc,  const gchar *bcc,
1268                                       const gchar *subject, const gchar *plain_body,
1269                                       const gchar *html_body,
1270                                       const GList *attachments_list,
1271                                       const GList *images_list,
1272                                       TnyHeaderFlags priority_flags,
1273                                       SaveToDraftstCallback callback,
1274                                       gpointer user_data)
1275 {
1276         ModestMailOperationPrivate *priv = NULL;
1277         SaveToDraftsInfo *info = NULL;
1278
1279         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
1280         g_return_if_fail (TNY_IS_TRANSPORT_ACCOUNT (transport_account));
1281
1282         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
1283
1284         /* Get account and set it into mail_operation */
1285         priv->account = g_object_ref (transport_account);
1286         priv->op_type = MODEST_MAIL_OPERATION_TYPE_INFO;
1287
1288         info = g_slice_new0 (SaveToDraftsInfo);
1289         info->transport_account = g_object_ref (transport_account);
1290         info->draft_msg = (draft_msg) ? g_object_ref (draft_msg) : NULL;
1291         info->callback = callback;
1292         info->user_data = user_data;
1293
1294         g_debug ("--- CREATE MESSAGE");
1295         modest_mail_operation_notify_start (self);
1296         modest_mail_operation_create_msg (self, from, to, cc, bcc, subject, plain_body, html_body,
1297                                           attachments_list, images_list, priority_flags,
1298                                           modest_mail_operation_save_to_drafts_cb, info);
1299 }
1300
1301 typedef struct
1302 {
1303         ModestMailOperation *mail_op;
1304         TnyMimePart *mime_part;
1305         gssize size;
1306         GetMimePartSizeCallback callback;
1307         gpointer userdata;
1308 } GetMimePartSizeInfo;
1309
1310 /***** I N T E R N A L    F O L D E R    O B S E R V E R *****/
1311 /* We use this folder observer to track the headers that have been
1312  * added to a folder */
1313 typedef struct {
1314         GObject parent;
1315         TnyList *new_headers;
1316 } InternalFolderObserver;
1317
1318 typedef struct {
1319         GObjectClass parent;
1320 } InternalFolderObserverClass;
1321
1322 static void tny_folder_observer_init (TnyFolderObserverIface *idace);
1323
1324 G_DEFINE_TYPE_WITH_CODE (InternalFolderObserver,
1325                          internal_folder_observer,
1326                          G_TYPE_OBJECT,
1327                          G_IMPLEMENT_INTERFACE(TNY_TYPE_FOLDER_OBSERVER, tny_folder_observer_init));
1328
1329
1330 static void
1331 foreach_add_item (gpointer header, gpointer user_data)
1332 {
1333         tny_list_append (TNY_LIST (user_data), G_OBJECT (header));
1334 }
1335
1336 /* This is the method that looks for new messages in a folder */
1337 static void
1338 internal_folder_observer_update (TnyFolderObserver *self, TnyFolderChange *change)
1339 {
1340         InternalFolderObserver *derived = (InternalFolderObserver *)self;
1341         
1342         TnyFolderChangeChanged changed;
1343
1344         changed = tny_folder_change_get_changed (change);
1345
1346         if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) {
1347                 TnyList *list;
1348
1349                 /* Get added headers */
1350                 list = tny_simple_list_new ();
1351                 tny_folder_change_get_added_headers (change, list);
1352
1353                 /* Add them to the folder observer */
1354                 tny_list_foreach (list, foreach_add_item, 
1355                                   derived->new_headers);
1356
1357                 g_object_unref (G_OBJECT (list));
1358         }
1359 }
1360
1361 static void
1362 internal_folder_observer_init (InternalFolderObserver *self) 
1363 {
1364         self->new_headers = tny_simple_list_new ();
1365 }
1366 static void
1367 internal_folder_observer_finalize (GObject *object) 
1368 {
1369         InternalFolderObserver *self;
1370
1371         self = (InternalFolderObserver *) object;
1372         g_object_unref (self->new_headers);
1373
1374         G_OBJECT_CLASS (internal_folder_observer_parent_class)->finalize (object);
1375 }
1376 static void
1377 tny_folder_observer_init (TnyFolderObserverIface *iface) 
1378 {
1379         iface->update = internal_folder_observer_update;
1380 }
1381 static void
1382 internal_folder_observer_class_init (InternalFolderObserverClass *klass) 
1383 {
1384         GObjectClass *object_class;
1385
1386         internal_folder_observer_parent_class = g_type_class_peek_parent (klass);
1387         object_class = (GObjectClass*) klass;
1388         object_class->finalize = internal_folder_observer_finalize;
1389 }
1390
1391 static void
1392 destroy_update_account_info (UpdateAccountInfo *info)
1393 {
1394         g_free (info->account_name);
1395         g_object_unref (info->folders);
1396         g_object_unref (info->mail_op);
1397         g_slice_free (UpdateAccountInfo, info);
1398 }
1399
1400
1401 static void
1402 update_account_send_mail (UpdateAccountInfo *info)
1403 {
1404         TnyTransportAccount *transport_account = NULL;
1405         ModestTnyAccountStore *account_store;
1406
1407         account_store = modest_runtime_get_account_store ();
1408
1409         /* We don't try to send messages while sending mails is blocked */
1410         if (modest_tny_account_store_is_send_mail_blocked (account_store))
1411                 return;
1412
1413         /* Get the transport account */
1414         transport_account = (TnyTransportAccount *) 
1415                 modest_tny_account_store_get_server_account (account_store, info->account_name, 
1416                                                              TNY_ACCOUNT_TYPE_TRANSPORT);
1417
1418         if (transport_account) {
1419                 ModestTnySendQueue *send_queue;
1420                 TnyFolder *outbox;
1421                 guint num_messages;
1422
1423                 send_queue = modest_runtime_get_send_queue (transport_account, TRUE);
1424                 g_object_unref (transport_account);
1425
1426                 if (TNY_IS_SEND_QUEUE (send_queue)) {
1427                         /* Get outbox folder */
1428                         outbox = tny_send_queue_get_outbox (TNY_SEND_QUEUE (send_queue));
1429                         if (outbox) { /* this could fail in some cases */
1430                                 num_messages = tny_folder_get_all_count (outbox);
1431                                 g_object_unref (outbox);
1432                         } else {
1433                                 g_warning ("%s: could not get outbox", __FUNCTION__);
1434                                 num_messages = 0;
1435                         }
1436                 
1437                         if (num_messages != 0) {
1438                                 /* Reenable suspended items */
1439                                 modest_tny_send_queue_wakeup (MODEST_TNY_SEND_QUEUE (send_queue));
1440                                 
1441                                 /* Try to send */
1442                                 tny_camel_send_queue_flush (TNY_CAMEL_SEND_QUEUE (send_queue));
1443                                 modest_tny_send_queue_set_requested_send_receive (MODEST_TNY_SEND_QUEUE (send_queue), 
1444                                                                                   info->interactive);
1445                         }
1446                 }
1447         }
1448 }
1449
1450 static void
1451 update_account_get_msg_async_cb (TnyFolder *folder, 
1452                                  gboolean canceled, 
1453                                  TnyMsg *msg, 
1454                                  GError *err, 
1455                                  gpointer user_data)
1456 {
1457         GetMsgInfo *msg_info = (GetMsgInfo *) user_data;
1458         ModestMailOperationPrivate *priv;
1459
1460         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (msg_info->mail_op);
1461         priv->done++;
1462
1463         if (TNY_IS_MSG (msg)) {
1464                 TnyHeader *header = tny_msg_get_header (msg);
1465
1466                 if (header) {
1467                         ModestMailOperationState *state;
1468                         state = modest_mail_operation_clone_state (msg_info->mail_op);
1469                         msg_info->sum_total_bytes += tny_header_get_message_size (header);
1470                         state->bytes_done = msg_info->sum_total_bytes;
1471                         state->bytes_total = msg_info->total_bytes;
1472
1473                         /* Notify the status change. Only notify about changes
1474                            referred to bytes */
1475                         g_signal_emit (G_OBJECT (msg_info->mail_op), 
1476                                        signals[PROGRESS_CHANGED_SIGNAL], 
1477                                        0, state, NULL);
1478
1479                         g_object_unref (header);
1480                         g_slice_free (ModestMailOperationState, state);
1481                 }
1482         }
1483
1484         if (priv->done == priv->total) {
1485                 TnyList *new_headers;
1486                 UpdateAccountInfo *info;
1487
1488                 /* After getting all the messages send the ones in the
1489                    outboxes */
1490                 info = (UpdateAccountInfo *) msg_info->user_data;
1491                 update_account_send_mail (info);
1492
1493                 /* Check if the operation was a success */
1494                 if (!priv->error)
1495                         priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
1496                 
1497                 /* Call the user callback and free */
1498                 new_headers = tny_iterator_get_list (msg_info->more_msgs);
1499                 update_account_notify_user_and_free (info, new_headers);
1500                 g_object_unref (new_headers);
1501
1502                 /* Delete the helper */
1503                 g_object_unref (msg_info->more_msgs);
1504                 g_object_unref (msg_info->mail_op);
1505                 g_slice_free (GetMsgInfo, msg_info);
1506         }
1507 }
1508
1509 static void
1510 update_account_notify_user_and_free (UpdateAccountInfo *info, 
1511                                      TnyList *new_headers)
1512 {
1513         /* Set the account back to not busy */
1514         modest_account_mgr_set_account_busy (modest_runtime_get_account_mgr (), 
1515                                              info->account_name, FALSE);
1516         
1517         /* User callback */
1518         if (info->callback)
1519                 info->callback (info->mail_op, new_headers, info->user_data);
1520         
1521         /* Mail operation end */
1522         modest_mail_operation_notify_end (info->mail_op);
1523
1524         /* Frees */
1525         if (new_headers)
1526                 g_object_unref (new_headers);
1527         destroy_update_account_info (info);
1528 }
1529
1530 static void
1531 inbox_refreshed_cb (TnyFolder *inbox, 
1532                     gboolean canceled, 
1533                     GError *err, 
1534                     gpointer user_data)
1535 {       
1536         UpdateAccountInfo *info;
1537         ModestMailOperationPrivate *priv;
1538         TnyIterator *new_headers_iter;
1539         GPtrArray *new_headers_array = NULL;
1540         gint max_size, retrieve_limit, i;
1541         ModestAccountMgr *mgr;
1542         ModestAccountRetrieveType retrieve_type;
1543         TnyList *new_headers = NULL;
1544         gboolean headers_only, ignore_limit;
1545
1546         info = (UpdateAccountInfo *) user_data;
1547         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (info->mail_op);
1548         mgr = modest_runtime_get_account_mgr ();
1549
1550         if (canceled || err) {
1551                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
1552                 if (err)
1553                         priv->error = g_error_copy (err);
1554                 else
1555                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
1556                                      MODEST_MAIL_OPERATION_ERROR_OPERATION_CANCELED,
1557                                      "canceled");
1558
1559                 tny_folder_remove_observer (inbox, info->inbox_observer);
1560                 g_object_unref (info->inbox_observer);
1561                 info->inbox_observer = NULL;
1562
1563                 /* Notify the user about the error and then exit */
1564                 update_account_notify_user_and_free (info, NULL);
1565                 return;
1566         }
1567
1568         if (!inbox) {
1569                 /* Try to send anyway */
1570                 goto send_mail;
1571         }
1572
1573         /* Set the last updated as the current time */
1574         modest_account_mgr_set_last_updated (mgr, tny_account_get_id (priv->account), time (NULL));
1575
1576         /* Get the message max size */
1577         max_size  = modest_conf_get_int (modest_runtime_get_conf (),
1578                                          MODEST_CONF_MSG_SIZE_LIMIT, NULL);
1579         if (max_size == 0)
1580                 max_size = G_MAXINT;
1581         else
1582                 max_size = max_size * KB;
1583
1584         /* Create the new headers array. We need it to sort the
1585            new headers by date */
1586         new_headers_array = g_ptr_array_new ();
1587         if (info->inbox_observer) {
1588                 new_headers_iter = tny_list_create_iterator (((InternalFolderObserver *) info->inbox_observer)->new_headers);
1589                 while (!tny_iterator_is_done (new_headers_iter)) {
1590                         TnyHeader *header = NULL;
1591
1592                         header = TNY_HEADER (tny_iterator_get_current (new_headers_iter));
1593                         /* Apply per-message size limits */
1594                         if (tny_header_get_message_size (header) < max_size)
1595                                 g_ptr_array_add (new_headers_array, g_object_ref (header));
1596                         
1597                         g_object_unref (header);
1598                         tny_iterator_next (new_headers_iter);
1599                 }
1600                 g_object_unref (new_headers_iter);
1601
1602                 tny_folder_remove_observer (inbox, info->inbox_observer);
1603                 g_object_unref (info->inbox_observer);
1604                 info->inbox_observer = NULL;
1605         }
1606
1607         if (new_headers_array->len == 0) {
1608                 g_ptr_array_free (new_headers_array, FALSE);
1609                 goto send_mail;
1610         }
1611
1612         /* Get per-account message amount retrieval limit */
1613         retrieve_limit = modest_account_mgr_get_retrieve_limit (mgr, info->account_name);
1614         if (retrieve_limit == 0)
1615                 retrieve_limit = G_MAXINT;
1616         
1617         /* Get per-account retrieval type */
1618         retrieve_type = modest_account_mgr_get_retrieve_type (mgr, info->account_name);
1619         headers_only = (retrieve_type == MODEST_ACCOUNT_RETRIEVE_HEADERS_ONLY);
1620
1621         /* Order by date */
1622         g_ptr_array_sort (new_headers_array, (GCompareFunc) compare_headers_by_date);
1623         
1624         /* Ask the users if they want to retrieve all the messages
1625            even though the limit was exceeded */
1626         ignore_limit = FALSE;
1627         if (new_headers_array->len > retrieve_limit) {
1628                 /* Ask the user if a callback has been specified and
1629                    if the mail operation has a source (this means that
1630                    was invoked by the user and not automatically by a
1631                    D-Bus method) */
1632                 if (info->retrieve_all_cb && priv->source)
1633                         ignore_limit = info->retrieve_all_cb (priv->source,
1634                                                               new_headers_array->len,
1635                                                               retrieve_limit);
1636         }
1637
1638         /* Copy the headers to a list and free the array */
1639         new_headers = tny_simple_list_new ();
1640         for (i=0; i < new_headers_array->len; i++) {
1641                 TnyHeader *header = TNY_HEADER (g_ptr_array_index (new_headers_array, i));
1642                 tny_list_append (new_headers, G_OBJECT (header));
1643         }
1644         g_ptr_array_foreach (new_headers_array, (GFunc) g_object_unref, NULL);
1645         g_ptr_array_free (new_headers_array, FALSE);
1646         
1647         if (!headers_only && (tny_list_get_length (new_headers) > 0)) {
1648                 gint msg_num = 0;
1649                 TnyIterator *iter;
1650                 GetMsgInfo *msg_info;
1651
1652                 priv->done = 0;
1653                 if (ignore_limit)
1654                         priv->total = tny_list_get_length (new_headers);
1655                 else
1656                         priv->total = MIN (tny_list_get_length (new_headers), retrieve_limit);
1657
1658                 iter = tny_list_create_iterator (new_headers);
1659
1660                 /* Create the message info */
1661                 msg_info = g_slice_new0 (GetMsgInfo);
1662                 msg_info->mail_op = g_object_ref (info->mail_op);
1663                 msg_info->total_bytes = compute_message_list_size (new_headers, priv->total);
1664                 msg_info->more_msgs = g_object_ref (iter);
1665                 msg_info->user_data = info;
1666
1667                 while ((msg_num < priv->total ) && !tny_iterator_is_done (iter)) {              
1668                         TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iter));
1669                         TnyFolder *folder = tny_header_get_folder (header);
1670
1671                         /* Get message in an async way */
1672                         tny_folder_get_msg_async (folder, header, update_account_get_msg_async_cb, 
1673                                                   NULL, msg_info);
1674
1675                         g_object_unref (folder);
1676                         
1677                         msg_num++;
1678                         tny_iterator_next (iter);
1679                 }
1680                 g_object_unref (iter);
1681
1682                 /* The mail operation will finish when the last
1683                    message is retrieved */
1684                 return;
1685         }
1686  send_mail:
1687         /* If we don't have to retrieve the new messages then
1688            simply send mail */
1689         update_account_send_mail (info);
1690         
1691         /* Check if the operation was a success */
1692         if (!priv->error)
1693                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
1694         
1695         /* Call the user callback and free */
1696         update_account_notify_user_and_free (info, new_headers);
1697 }
1698
1699 static void
1700 inbox_refresh_status_update (GObject *obj,
1701                              TnyStatus *status,
1702                              gpointer user_data)
1703 {
1704         UpdateAccountInfo *info = NULL;
1705         ModestMailOperation *self = NULL;
1706         ModestMailOperationPrivate *priv = NULL;
1707         ModestMailOperationState *state;
1708
1709         g_return_if_fail (user_data != NULL);
1710         g_return_if_fail (status != NULL);
1711
1712         /* Show only the status information we want */
1713         if (status->code != TNY_FOLDER_STATUS_CODE_REFRESH)
1714                 return;
1715
1716         info = (UpdateAccountInfo *) user_data;
1717         self = info->mail_op;
1718         g_return_if_fail (MODEST_IS_MAIL_OPERATION(self));
1719
1720         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
1721
1722         priv->done = status->position;
1723         priv->total = status->of_total;
1724
1725         state = modest_mail_operation_clone_state (self);
1726
1727         /* This is not a GDK lock because we are a Tinymail callback and
1728          * Tinymail already acquires the Gdk lock */
1729         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 0, state, NULL);
1730
1731         g_slice_free (ModestMailOperationState, state);
1732 }
1733
1734 static void 
1735 recurse_folders_async_cb (TnyFolderStore *folder_store, 
1736                           gboolean canceled,
1737                           TnyList *list, 
1738                           GError *err, 
1739                           gpointer user_data)
1740 {
1741         UpdateAccountInfo *info;
1742         ModestMailOperationPrivate *priv;
1743     
1744         info = (UpdateAccountInfo *) user_data;
1745         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (info->mail_op);
1746
1747         if (err || canceled) {
1748                 /* If the error was previosly set by another callback
1749                    don't set it again */
1750                 if (!priv->error) {
1751                         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
1752                         if (err)
1753                                 priv->error = g_error_copy (err);
1754                         else
1755                                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
1756                                              MODEST_MAIL_OPERATION_ERROR_OPERATION_CANCELED,
1757                                              "canceled");
1758                 }
1759         } else { 
1760                 /* We're not getting INBOX children if we don't want to poke all */
1761                 TnyIterator *iter = tny_list_create_iterator (list);
1762                 while (!tny_iterator_is_done (iter)) {
1763                         TnyFolderStore *folder = (TnyFolderStore*) tny_iterator_get_current (iter);
1764
1765                         /* Add to the list of all folders */
1766                         tny_list_append (info->folders, (GObject *) folder);
1767                         
1768                         if (info->poke_all) {
1769                                 TnyList *folders = tny_simple_list_new ();
1770                                 /* Add pending call */
1771                                 info->pending_calls++;
1772                                 
1773                                 tny_folder_store_get_folders_async (folder, folders, NULL, FALSE,
1774                                                                     recurse_folders_async_cb, 
1775                                                                     NULL, info);
1776                                 g_object_unref (folders);
1777                         }
1778                         
1779                         g_object_unref (G_OBJECT (folder));
1780                         
1781                         tny_iterator_next (iter);           
1782                 }
1783                 g_object_unref (G_OBJECT (iter));
1784         }
1785
1786         /* Remove my own pending call */
1787         info->pending_calls--;
1788
1789         /* This means that we have all the folders */
1790         if (info->pending_calls == 0) {
1791                 TnyIterator *iter_all_folders;
1792                 TnyFolder *inbox = NULL;
1793
1794                 /* If there was any error do not continue */
1795                 if (priv->error) {
1796                         update_account_notify_user_and_free (info, NULL);
1797                         return;
1798                 }
1799
1800                 iter_all_folders = tny_list_create_iterator (info->folders);
1801
1802                 /* Do a poke status over all folders */
1803                 while (!tny_iterator_is_done (iter_all_folders) &&
1804                        priv->status == MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS) {
1805                         TnyFolder *folder = NULL;
1806
1807                         folder = TNY_FOLDER (tny_iterator_get_current (iter_all_folders));
1808
1809                         if (tny_folder_get_folder_type (folder) == TNY_FOLDER_TYPE_INBOX) {
1810                                 /* Get a reference to the INBOX */
1811                                 inbox = g_object_ref (folder);
1812                         } else {
1813                                 /* Issue a poke status over the folder */
1814                                 if (info->poke_all)
1815                                         tny_folder_poke_status (folder);
1816                         }
1817
1818                         /* Free and go to next */
1819                         g_object_unref (folder);
1820                         tny_iterator_next (iter_all_folders);
1821                 }
1822                 g_object_unref (iter_all_folders);
1823
1824                 /* Refresh the INBOX */
1825                 if (inbox) {
1826                         /* Refresh the folder. Our observer receives
1827                          * the new emails during folder refreshes, so
1828                          * we can use observer->new_headers
1829                          */
1830                         info->inbox_observer = g_object_new (internal_folder_observer_get_type (), NULL);
1831                         tny_folder_add_observer (inbox, info->inbox_observer);
1832
1833                         /* Refresh the INBOX */
1834                         tny_folder_refresh_async (inbox, inbox_refreshed_cb, inbox_refresh_status_update, info);
1835                         g_object_unref (inbox);
1836                 } else {
1837                         /* We could not perform the inbox refresh but
1838                            we'll try to send mails anyway */
1839                         inbox_refreshed_cb (inbox, FALSE, NULL, info);
1840                 }
1841         }
1842 }
1843
1844 void
1845 modest_mail_operation_update_account (ModestMailOperation *self,
1846                                       const gchar *account_name,
1847                                       gboolean poke_all,
1848                                       gboolean interactive,
1849                                       RetrieveAllCallback retrieve_all_cb,
1850                                       UpdateAccountCallback callback,
1851                                       gpointer user_data)
1852 {
1853         UpdateAccountInfo *info = NULL;
1854         ModestMailOperationPrivate *priv = NULL;
1855         ModestTnyAccountStore *account_store = NULL;
1856         TnyList *folders;
1857         ModestMailOperationState *state;
1858
1859         /* Init mail operation */
1860         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
1861         priv->total = 0;
1862         priv->done  = 0;
1863         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
1864         priv->op_type = MODEST_MAIL_OPERATION_TYPE_SEND_AND_RECEIVE;
1865
1866         /* Get the store account */
1867         account_store = modest_runtime_get_account_store ();
1868         priv->account =
1869                 modest_tny_account_store_get_server_account (account_store,
1870                                                              account_name,
1871                                                              TNY_ACCOUNT_TYPE_STORE);
1872
1873         /* The above function could return NULL */
1874         if (!priv->account) {
1875                 /* Check if the operation was a success */
1876                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
1877                              MODEST_MAIL_OPERATION_ERROR_ITEM_NOT_FOUND,
1878                              "no account");
1879                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
1880
1881                 /* Call the user callback */
1882                 if (callback)
1883                         callback (self, NULL, user_data);
1884
1885                 /* Notify about operation end */
1886                 modest_mail_operation_notify_end (self);
1887
1888                 return;
1889         }
1890         
1891         /* We have once seen priv->account getting finalized during this code,
1892          * therefore adding a reference (bug #82296) */
1893         
1894         g_object_ref (priv->account);
1895
1896         /* Create the helper object */
1897         info = g_slice_new0 (UpdateAccountInfo);
1898         info->pending_calls = 1;
1899         info->folders = tny_simple_list_new ();
1900         info->mail_op = g_object_ref (self);
1901         info->poke_all = poke_all;
1902         info->interactive = interactive;
1903         info->account_name = g_strdup (account_name);
1904         info->callback = callback;
1905         info->user_data = user_data;
1906         info->retrieve_all_cb = retrieve_all_cb;
1907
1908         /* Set account busy */
1909         modest_account_mgr_set_account_busy (modest_runtime_get_account_mgr (), account_name, TRUE);
1910         modest_mail_operation_notify_start (self);
1911
1912         /* notify about the start of the operation */ 
1913         state = modest_mail_operation_clone_state (self);
1914         state->done = 0;
1915         state->total = 0;
1916
1917         /* Start notifying progress */
1918         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 0, state, NULL);
1919         g_slice_free (ModestMailOperationState, state);
1920         
1921         /* Get all folders and continue in the callback */ 
1922         folders = tny_simple_list_new ();
1923         tny_folder_store_get_folders_async (TNY_FOLDER_STORE (priv->account),
1924                                             folders, NULL, FALSE,
1925                                             recurse_folders_async_cb, 
1926                                             NULL, info);
1927         g_object_unref (folders);
1928         
1929         g_object_unref (priv->account);
1930         
1931 }
1932
1933 /*
1934  * Used to notify the queue from the main
1935  * loop. We call it inside an idle call to achieve that
1936  */
1937 static gboolean
1938 idle_notify_queue (gpointer data)
1939 {
1940         ModestMailOperation *mail_op = MODEST_MAIL_OPERATION (data);
1941
1942         gdk_threads_enter ();
1943         modest_mail_operation_notify_end (mail_op);
1944         gdk_threads_leave ();
1945         g_object_unref (mail_op);
1946
1947         return FALSE;
1948 }
1949
1950 static int
1951 compare_headers_by_date (gconstpointer a,
1952                          gconstpointer b)
1953 {
1954         TnyHeader **header1, **header2;
1955         time_t sent1, sent2;
1956
1957         header1 = (TnyHeader **) a;
1958         header2 = (TnyHeader **) b;
1959
1960         sent1 = tny_header_get_date_sent (*header1);
1961         sent2 = tny_header_get_date_sent (*header2);
1962
1963         /* We want the most recent ones (greater time_t) at the
1964            beginning */
1965         if (sent1 < sent2)
1966                 return 1;
1967         else
1968                 return -1;
1969 }
1970
1971
1972 /* ******************************************************************* */
1973 /* ************************** STORE  ACTIONS ************************* */
1974 /* ******************************************************************* */
1975
1976 typedef struct {
1977         ModestMailOperation *mail_op;
1978         CreateFolderUserCallback callback;
1979         gpointer user_data;
1980 } CreateFolderInfo;
1981
1982
1983 static void
1984 create_folder_cb (TnyFolderStore *parent_folder, 
1985                   gboolean canceled, 
1986                   TnyFolder *new_folder, 
1987                   GError *err, 
1988                   gpointer user_data)
1989 {
1990         ModestMailOperationPrivate *priv;
1991         CreateFolderInfo *info;
1992
1993         info = (CreateFolderInfo *) user_data;
1994         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (info->mail_op);
1995
1996         if (canceled || err) {
1997                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
1998                 if (err)
1999                         priv->error = g_error_copy (err);
2000                 else
2001                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2002                                      MODEST_MAIL_OPERATION_ERROR_OPERATION_CANCELED,
2003                                      "canceled");               
2004         } else {
2005                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
2006         }
2007
2008         /* The user will unref the new_folder */
2009         if (info->callback)
2010                 info->callback (info->mail_op, parent_folder, 
2011                                 new_folder, info->user_data);
2012         
2013         /* Notify about operation end */
2014         modest_mail_operation_notify_end (info->mail_op);
2015
2016         /* Frees */
2017         g_object_unref (info->mail_op);
2018         g_slice_free (CreateFolderInfo, info);
2019 }
2020
2021 void
2022 modest_mail_operation_create_folder (ModestMailOperation *self,
2023                                      TnyFolderStore *parent,
2024                                      const gchar *name,
2025                                      CreateFolderUserCallback callback,
2026                                      gpointer user_data)
2027 {
2028         ModestMailOperationPrivate *priv;
2029
2030         g_return_if_fail (TNY_IS_FOLDER_STORE (parent));
2031         g_return_if_fail (name);
2032         
2033         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2034         priv->op_type = MODEST_MAIL_OPERATION_TYPE_INFO;
2035         priv->account = (TNY_IS_ACCOUNT (parent)) ? 
2036                 g_object_ref (parent) : 
2037                 modest_tny_folder_get_account (TNY_FOLDER (parent));
2038
2039         /* Check for already existing folder */
2040         if (modest_tny_folder_has_subfolder_with_name (parent, name, TRUE)) {
2041                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2042                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2043                              MODEST_MAIL_OPERATION_ERROR_FOLDER_EXISTS,
2044                              _CS("ckdg_ib_folder_already_exists"));
2045         }
2046
2047         /* Check parent */
2048         if (TNY_IS_FOLDER (parent)) {
2049                 /* Check folder rules */
2050                 ModestTnyFolderRules rules = modest_tny_folder_get_rules (TNY_FOLDER (parent));
2051                 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2052                         /* Set status failed and set an error */
2053                         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2054                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2055                                      MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
2056                                      _("mail_in_ui_folder_create_error"));
2057                 }
2058         }
2059
2060         if (!strcmp (name, " ") || strchr (name, '/')) {
2061                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2062                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2063                              MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
2064                              _("mail_in_ui_folder_create_error"));
2065         }
2066
2067         if (!priv->error) {
2068                 CreateFolderInfo *info;
2069
2070                 info = g_slice_new0 (CreateFolderInfo);
2071                 info->mail_op = g_object_ref (self);
2072                 info->callback = callback;
2073                 info->user_data = user_data;
2074
2075                 modest_mail_operation_notify_start (self);
2076
2077                 /* Create the folder */
2078                 tny_folder_store_create_folder_async (parent, name, create_folder_cb, 
2079                                                       NULL, info);
2080         } else {
2081                 /* Call the user callback anyway */
2082                 if (callback)
2083                         callback (self, parent, NULL, user_data);
2084                 /* Notify about operation end */
2085                 modest_mail_operation_notify_end (self);
2086         }
2087 }
2088
2089 void
2090 modest_mail_operation_remove_folder (ModestMailOperation *self,
2091                                      TnyFolder           *folder,
2092                                      gboolean             remove_to_trash)
2093 {
2094         ModestMailOperationPrivate *priv;
2095         ModestTnyFolderRules rules;
2096
2097         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2098         g_return_if_fail (TNY_IS_FOLDER (folder));
2099         
2100         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2101         
2102         /* Check folder rules */
2103         rules = modest_tny_folder_get_rules (TNY_FOLDER (folder));
2104         if (rules & MODEST_FOLDER_RULES_FOLDER_NON_DELETABLE) {
2105                 /* Set status failed and set an error */
2106                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2107                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2108                              MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
2109                              _("mail_in_ui_folder_delete_error"));
2110                 goto end;
2111         }
2112
2113         /* Get the account */
2114         priv->account = modest_tny_folder_get_account (folder);
2115         priv->op_type = MODEST_MAIL_OPERATION_TYPE_DELETE;
2116
2117         /* Delete folder or move to trash */
2118         if (remove_to_trash) {
2119                 TnyFolder *trash_folder = NULL;
2120                 trash_folder = modest_tny_account_get_special_folder (priv->account,
2121                                                                       TNY_FOLDER_TYPE_TRASH);
2122                 /* TODO: error_handling */
2123                 if (trash_folder) {
2124                         modest_mail_operation_notify_start (self);
2125                         modest_mail_operation_xfer_folder (self, folder,
2126                                                     TNY_FOLDER_STORE (trash_folder), 
2127                                                     TRUE, NULL, NULL);
2128                         g_object_unref (trash_folder);
2129                 } else {
2130                         g_warning ("%s: modest_tny_account_get_special_folder(..) returned a NULL trash folder", __FUNCTION__);
2131                 }
2132         } else {
2133                 TnyFolderStore *parent = tny_folder_get_folder_store (folder);
2134                 if (parent) {
2135                         modest_mail_operation_notify_start (self);
2136                         tny_folder_store_remove_folder (parent, folder, &(priv->error));
2137                         CHECK_EXCEPTION (priv, MODEST_MAIL_OPERATION_STATUS_FAILED);
2138                         
2139                         if (!priv->error)
2140                                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
2141
2142                         g_object_unref (parent);
2143                 } else
2144                         g_warning ("%s: could not get parent folder", __FUNCTION__);
2145         }
2146
2147  end:
2148         /* Notify about operation end */
2149         modest_mail_operation_notify_end (self);
2150 }
2151
2152 static void
2153 transfer_folder_status_cb (GObject *obj,
2154                            TnyStatus *status,
2155                            gpointer user_data)
2156 {
2157         ModestMailOperation *self;
2158         ModestMailOperationPrivate *priv;
2159         ModestMailOperationState *state;
2160         XFerFolderAsyncHelper *helper;
2161
2162         g_return_if_fail (status != NULL);
2163
2164         /* Show only the status information we want */
2165         if (status->code != TNY_FOLDER_STATUS_CODE_COPY_FOLDER)
2166                 return;
2167
2168         helper = (XFerFolderAsyncHelper *) user_data;
2169         g_return_if_fail (helper != NULL);
2170
2171         self = helper->mail_op;
2172         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
2173
2174         priv->done = status->position;
2175         priv->total = status->of_total;
2176
2177         state = modest_mail_operation_clone_state (self);
2178
2179         /* This is not a GDK lock because we are a Tinymail callback
2180          * which is already GDK locked by Tinymail */
2181
2182         /* no gdk_threads_enter (), CHECKED */
2183
2184         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 0, state, NULL); 
2185
2186         /* no gdk_threads_leave (), CHECKED */
2187
2188         g_slice_free (ModestMailOperationState, state);
2189 }
2190
2191
2192 static void
2193 transfer_folder_cb (TnyFolder *folder, 
2194                     gboolean cancelled, 
2195                     TnyFolderStore *into, 
2196                     TnyFolder *new_folder, 
2197                     GError *err, 
2198                     gpointer user_data)
2199 {
2200         XFerFolderAsyncHelper *helper;
2201         ModestMailOperation *self = NULL;
2202         ModestMailOperationPrivate *priv = NULL;
2203
2204         helper = (XFerFolderAsyncHelper *) user_data;
2205         g_return_if_fail (helper != NULL);       
2206
2207         self = helper->mail_op;
2208         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
2209
2210         if (err) {
2211                 priv->error = g_error_copy (err);
2212                 priv->done = 0;
2213                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2214         } else if (cancelled) {
2215                 priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
2216                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2217                              MODEST_MAIL_OPERATION_ERROR_OPERATION_CANCELED,
2218                              _("Transference of %s was cancelled."),
2219                              tny_folder_get_name (folder));
2220         } else {
2221                 priv->done = 1;
2222                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
2223         }
2224                 
2225         /* Notify about operation end */
2226         modest_mail_operation_notify_end (self);
2227
2228         /* If user defined callback function was defined, call it */
2229         if (helper->user_callback) {
2230
2231                 /* This is not a GDK lock because we are a Tinymail callback
2232                  * which is already GDK locked by Tinymail */
2233
2234                 /* no gdk_threads_enter (), CHECKED */
2235                 helper->user_callback (self, new_folder, helper->user_data);
2236                 /* no gdk_threads_leave () , CHECKED */
2237         }
2238
2239         /* Free */
2240         g_object_unref (helper->mail_op);
2241         g_slice_free   (XFerFolderAsyncHelper, helper);
2242 }
2243
2244 /**
2245  *
2246  * This function checks if the new name is a valid name for our local
2247  * folders account. The new name could not be the same than then name
2248  * of any of the mandatory local folders
2249  *
2250  * We can not rely on tinymail because tinymail does not check the
2251  * name of the virtual folders that the account could have in the case
2252  * that we're doing a rename (because it directly calls Camel which
2253  * knows nothing about our virtual folders). 
2254  *
2255  * In the case of an actual copy/move (i.e. move/copy a folder between
2256  * accounts) tinymail uses the tny_folder_store_create_account which
2257  * is reimplemented by our ModestTnyLocalFoldersAccount that indeed
2258  * checks the new name of the folder, so this call in that case
2259  * wouldn't be needed. *But* NOTE that if tinymail changes its
2260  * implementation (if folder transfers within the same account is no
2261  * longer implemented as a rename) this call will allow Modest to work
2262  * perfectly
2263  *
2264  * If the new name is not valid, this function will set the status to
2265  * failed and will set also an error in the mail operation
2266  */
2267 static gboolean
2268 new_name_valid_if_local_account (ModestMailOperationPrivate *priv,
2269                                  TnyFolderStore *into,
2270                                  const gchar *new_name)
2271 {
2272         if (TNY_IS_ACCOUNT (into) && 
2273             modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (into)) &&
2274             modest_tny_local_folders_account_folder_name_in_use (MODEST_TNY_LOCAL_FOLDERS_ACCOUNT (into),
2275                                                                  new_name)) {
2276                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2277                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2278                              MODEST_MAIL_OPERATION_ERROR_FOLDER_EXISTS,
2279                              _CS("ckdg_ib_folder_already_exists"));
2280                 return FALSE;
2281         } else
2282                 return TRUE;
2283 }
2284
2285 void
2286 modest_mail_operation_xfer_folder (ModestMailOperation *self,
2287                                    TnyFolder *folder,
2288                                    TnyFolderStore *parent,
2289                                    gboolean delete_original,
2290                                    XferFolderAsyncUserCallback user_callback,
2291                                    gpointer user_data)
2292 {
2293         ModestMailOperationPrivate *priv = NULL;
2294         ModestTnyFolderRules parent_rules = 0, rules; 
2295         XFerFolderAsyncHelper *helper = NULL;
2296         const gchar *folder_name = NULL;
2297         const gchar *error_msg;
2298
2299         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2300         g_return_if_fail (TNY_IS_FOLDER (folder));
2301         g_return_if_fail (TNY_IS_FOLDER_STORE (parent));
2302
2303         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2304         folder_name = tny_folder_get_name (folder);
2305
2306         /* Set the error msg */
2307         error_msg = _("mail_in_ui_folder_move_target_error");
2308
2309         /* Get account and set it into mail_operation */
2310         priv->op_type = MODEST_MAIL_OPERATION_TYPE_RECEIVE;
2311         priv->account = modest_tny_folder_get_account (TNY_FOLDER(folder));
2312         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
2313
2314         /* Get folder rules */
2315         rules = modest_tny_folder_get_rules (TNY_FOLDER (folder));
2316         if (TNY_IS_FOLDER (parent))
2317                 parent_rules = modest_tny_folder_get_rules (TNY_FOLDER (parent));
2318         
2319         /* Apply operation constraints */
2320         if ((gpointer) parent == (gpointer) folder ||
2321             (!TNY_IS_FOLDER_STORE (parent)) || 
2322             (rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE)) {
2323                 /* Folder rules */
2324                 goto error;
2325         } else if (TNY_IS_FOLDER (parent) && 
2326                    (parent_rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE)) {
2327                 /* Folder rules */
2328                 goto error;
2329
2330         } else if (TNY_IS_FOLDER (parent) &&
2331                    TNY_IS_FOLDER_STORE (folder) &&
2332                    modest_tny_folder_is_ancestor (TNY_FOLDER (parent), 
2333                                                   TNY_FOLDER_STORE (folder))) {
2334                 /* Do not move a parent into a child */
2335                 goto error;
2336         } else if (TNY_IS_FOLDER_STORE (parent) &&
2337                    modest_tny_folder_has_subfolder_with_name (parent, folder_name, TRUE)) {
2338                 /* Check that the new folder name is not used by any
2339                    parent subfolder */
2340                 goto error;     
2341         } else if (!(new_name_valid_if_local_account (priv, parent, folder_name))) {
2342                 /* Check that the new folder name is not used by any
2343                    special local folder */
2344                 goto error;
2345         } else {
2346                 /* Create the helper */
2347                 helper = g_slice_new0 (XFerFolderAsyncHelper);
2348                 helper->mail_op = g_object_ref (self);
2349                 helper->user_callback = user_callback;
2350                 helper->user_data = user_data;
2351                 
2352                 /* Move/Copy folder */
2353                 modest_mail_operation_notify_start (self);
2354                 tny_folder_copy_async (folder,
2355                                        parent,
2356                                        tny_folder_get_name (folder),
2357                                        delete_original,
2358                                        transfer_folder_cb,
2359                                        transfer_folder_status_cb,
2360                                        helper);
2361                 return;
2362         }
2363
2364  error:
2365         /* Set status failed and set an error */
2366         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2367         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2368                      MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
2369                      error_msg);
2370
2371         /* Call the user callback if exists */
2372         if (user_callback)
2373                 user_callback (self, NULL, user_data);
2374
2375         /* Notify the queue */
2376         modest_mail_operation_notify_end (self);
2377 }
2378
2379 void
2380 modest_mail_operation_rename_folder (ModestMailOperation *self,
2381                                      TnyFolder *folder,
2382                                      const gchar *name,
2383                                      XferFolderAsyncUserCallback user_callback,
2384                                      gpointer user_data)
2385 {
2386         ModestMailOperationPrivate *priv;
2387         ModestTnyFolderRules rules;
2388         XFerFolderAsyncHelper *helper;
2389
2390         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2391         g_return_if_fail (TNY_IS_FOLDER_STORE (folder));
2392         g_return_if_fail (name);
2393         
2394         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2395
2396         /* Get account and set it into mail_operation */
2397         priv->account = modest_tny_folder_get_account (TNY_FOLDER(folder));
2398         priv->op_type = MODEST_MAIL_OPERATION_TYPE_INFO;
2399
2400         /* Check folder rules */
2401         rules = modest_tny_folder_get_rules (TNY_FOLDER (folder));
2402         if (rules & MODEST_FOLDER_RULES_FOLDER_NON_RENAMEABLE) {
2403                 goto error;
2404         } else if (!strcmp (name, " ") || strchr (name, '/')) {
2405                 goto error;
2406         } else {
2407                 TnyFolderStore *into;
2408
2409                 into = tny_folder_get_folder_store (folder);    
2410
2411                 /* Check that the new folder name is not used by any
2412                    special local folder */
2413                 if (new_name_valid_if_local_account (priv, into, name)) {
2414                         /* Create the helper */
2415                         helper = g_slice_new0 (XFerFolderAsyncHelper);
2416                         helper->mail_op = g_object_ref(self);
2417                         helper->user_callback = user_callback;
2418                         helper->user_data = user_data;
2419                 
2420                         /* Rename. Camel handles folder subscription/unsubscription */
2421                         modest_mail_operation_notify_start (self);
2422                         tny_folder_copy_async (folder, into, name, TRUE,
2423                                                transfer_folder_cb,
2424                                                transfer_folder_status_cb,
2425                                                helper);
2426                         g_object_unref (into);
2427                 } else {
2428                         g_object_unref (into);
2429                         goto error;
2430                 }
2431
2432                 return;
2433         }
2434  error:
2435         /* Set status failed and set an error */
2436         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2437         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2438                      MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
2439                      _("FIXME: unable to rename"));
2440         
2441         if (user_callback)
2442                 user_callback (self, NULL, user_data);
2443
2444         /* Notify about operation end */
2445         modest_mail_operation_notify_end (self);
2446 }
2447
2448 /* ******************************************************************* */
2449 /* **************************  MSG  ACTIONS  ************************* */
2450 /* ******************************************************************* */
2451
2452 void 
2453 modest_mail_operation_get_msg (ModestMailOperation *self,
2454                                TnyHeader *header,
2455                                gboolean progress_feedback,
2456                                GetMsgAsyncUserCallback user_callback,
2457                                gpointer user_data)
2458 {
2459         GetMsgInfo *helper = NULL;
2460         TnyFolder *folder;
2461         ModestMailOperationPrivate *priv;
2462         
2463         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2464         g_return_if_fail (TNY_IS_HEADER (header));
2465         
2466         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2467         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
2468         priv->total = 1;
2469         priv->done = 0;
2470
2471         /* Check memory low */
2472         if (_check_memory_low (self)) {
2473                 if (user_callback)
2474                         user_callback (self, header, FALSE, NULL, priv->error, user_data);
2475                 modest_mail_operation_notify_end (self);
2476                 return;
2477         }
2478
2479         /* Get account and set it into mail_operation */
2480         folder = tny_header_get_folder (header);
2481         priv->account = modest_tny_folder_get_account (TNY_FOLDER(folder));
2482         
2483         /* Check for cached messages */
2484         if (progress_feedback) {
2485                 if (tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED)
2486                         priv->op_type = MODEST_MAIL_OPERATION_TYPE_OPEN;
2487                 else 
2488                         priv->op_type = MODEST_MAIL_OPERATION_TYPE_RECEIVE;
2489         } else {
2490                 priv->op_type = MODEST_MAIL_OPERATION_TYPE_UNKNOWN;
2491         }
2492         
2493         /* Create the helper */
2494         helper = g_slice_new0 (GetMsgInfo);
2495         helper->header = g_object_ref (header);
2496         helper->mail_op = g_object_ref (self);
2497         helper->user_callback = user_callback;
2498         helper->user_data = user_data;
2499         helper->destroy_notify = NULL;
2500         helper->last_total_bytes = 0;
2501         helper->sum_total_bytes = 0;
2502         helper->total_bytes = tny_header_get_message_size (header);
2503         helper->more_msgs = NULL;
2504
2505         modest_mail_operation_notify_start (self);
2506         
2507         /* notify about the start of the operation */ 
2508         ModestMailOperationState *state;
2509         state = modest_mail_operation_clone_state (self);
2510         state->done = 0;
2511         state->total = 0;
2512         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 
2513                                 0, state, NULL);
2514         g_slice_free (ModestMailOperationState, state);
2515         
2516         tny_folder_get_msg_async (folder, header, get_msg_async_cb, get_msg_status_cb, helper);
2517
2518         g_object_unref (G_OBJECT (folder));
2519 }
2520
2521 static void     
2522 get_msg_status_cb (GObject *obj,
2523                    TnyStatus *status,  
2524                    gpointer user_data)
2525 {
2526         GetMsgInfo *helper = NULL;
2527
2528         g_return_if_fail (status != NULL);
2529
2530         /* Show only the status information we want */
2531         if (status->code != TNY_FOLDER_STATUS_CODE_GET_MSG)
2532                 return;
2533
2534         helper = (GetMsgInfo *) user_data;
2535         g_return_if_fail (helper != NULL);       
2536
2537         /* Notify progress */
2538         notify_progress_of_multiple_messages (helper->mail_op, status, &(helper->last_total_bytes), 
2539                                               &(helper->sum_total_bytes), helper->total_bytes, FALSE);
2540 }
2541
2542 static void
2543 get_msg_async_cb (TnyFolder *folder, 
2544                   gboolean canceled, 
2545                   TnyMsg *msg, 
2546                   GError *err, 
2547                   gpointer user_data)
2548 {
2549         GetMsgInfo *info = NULL;
2550         ModestMailOperationPrivate *priv = NULL;
2551         gboolean finished;
2552
2553         info = (GetMsgInfo *) user_data;
2554
2555         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (info->mail_op);
2556         priv->done++;
2557
2558         if (info->more_msgs) {
2559                 tny_iterator_next (info->more_msgs);
2560                 finished = (tny_iterator_is_done (info->more_msgs));
2561         } else {
2562                 finished = (priv->done == priv->total) ? TRUE : FALSE;
2563         }
2564
2565         /* If canceled by the user, ignore the error given by Tinymail */
2566         if (canceled) {
2567                 canceled = TRUE;
2568                 finished = TRUE;
2569                 priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
2570         } else if (err) {
2571                 priv->status = MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS;
2572                 if (err) {
2573                         priv->error = g_error_copy ((const GError *) err);
2574                         priv->error->domain = MODEST_MAIL_OPERATION_ERROR;
2575                 }
2576                 if (!priv->error) {
2577                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2578                                      MODEST_MAIL_OPERATION_ERROR_ITEM_NOT_FOUND,
2579                                      err->message);
2580                 }
2581         } else if (finished && priv->status == MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS) {
2582                 /* Set the success status before calling the user callback */
2583                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
2584         }
2585
2586
2587         /* Call the user callback */
2588         if (info->user_callback)
2589                 info->user_callback (info->mail_op, info->header, canceled, 
2590                                      msg, err, info->user_data);
2591
2592         /* Notify about operation end if this is the last callback */
2593         if (finished) {
2594                 /* Free user data */
2595                 if (info->destroy_notify)
2596                         info->destroy_notify (info->user_data);
2597
2598                 /* Notify about operation end */
2599                 modest_mail_operation_notify_end (info->mail_op);
2600
2601                 /* Clean */
2602                 if (info->more_msgs)
2603                         g_object_unref (info->more_msgs);
2604                 g_object_unref (info->header);
2605                 g_object_unref (info->mail_op);
2606                 g_slice_free (GetMsgInfo, info);
2607         } else if (info->more_msgs) {
2608                 TnyHeader *header = TNY_HEADER (tny_iterator_get_current (info->more_msgs));
2609                 TnyFolder *folder = tny_header_get_folder (header);
2610
2611                 g_object_unref (info->header);
2612                 info->header = g_object_ref (header);
2613
2614                 /* Retrieve the next message */
2615                 tny_folder_get_msg_async (folder, header, get_msg_async_cb, get_msg_status_cb, info);
2616
2617                 g_object_unref (header);
2618                 g_object_unref (folder);
2619         } else {
2620                 g_warning ("%s: finished != TRUE but no messages left", __FUNCTION__);
2621         }
2622 }
2623
2624 void 
2625 modest_mail_operation_get_msgs_full (ModestMailOperation *self,
2626                                      TnyList *header_list, 
2627                                      GetMsgAsyncUserCallback user_callback,
2628                                      gpointer user_data,
2629                                      GDestroyNotify notify)
2630 {
2631         ModestMailOperationPrivate *priv = NULL;
2632         gint msg_list_size;
2633         TnyIterator *iter = NULL;
2634         gboolean has_uncached_messages;
2635         
2636         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2637
2638         /* Init mail operation */
2639         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2640         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
2641         priv->done = 0;
2642         priv->total = tny_list_get_length(header_list);
2643
2644         /* Check memory low */
2645         if (_check_memory_low (self)) {
2646                 if (user_callback) {
2647                         TnyHeader *header = NULL;
2648                         TnyIterator *iter;
2649
2650                         if (tny_list_get_length (header_list) > 0) {
2651                                 iter = tny_list_create_iterator (header_list);
2652                                 header = (TnyHeader *) tny_iterator_get_current (iter);
2653                                 g_object_unref (iter);
2654                         }
2655                         user_callback (self, header, FALSE, NULL, priv->error, user_data);
2656                         if (header)
2657                                 g_object_unref (header);
2658                 }
2659                 if (notify)
2660                         notify (user_data);
2661                 /* Notify about operation end */
2662                 modest_mail_operation_notify_end (self);
2663                 return;
2664         }
2665
2666         /* Check uncached messages */
2667         for (iter = tny_list_create_iterator (header_list), has_uncached_messages = FALSE;
2668              !has_uncached_messages && !tny_iterator_is_done (iter); 
2669              tny_iterator_next (iter)) {
2670                 TnyHeader *header;
2671
2672                 header = (TnyHeader *) tny_iterator_get_current (iter);
2673                 if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED))
2674                         has_uncached_messages = TRUE;
2675                 g_object_unref (header);
2676         }       
2677         g_object_unref (iter);
2678         priv->op_type = has_uncached_messages?MODEST_MAIL_OPERATION_TYPE_RECEIVE:MODEST_MAIL_OPERATION_TYPE_OPEN;
2679
2680         /* Get account and set it into mail_operation */
2681         if (tny_list_get_length (header_list) >= 1) {
2682                 TnyIterator *iterator = tny_list_create_iterator (header_list);
2683                 TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iterator));
2684                 if (header) {
2685                         TnyFolder *folder = tny_header_get_folder (header);
2686                         if (folder) {           
2687                                 priv->account = modest_tny_folder_get_account (TNY_FOLDER(folder));
2688                                 g_object_unref (folder);
2689                         }
2690                         g_object_unref (header);
2691                 }
2692                 g_object_unref (iterator);
2693         }
2694
2695         msg_list_size = compute_message_list_size (header_list, 0);
2696
2697         modest_mail_operation_notify_start (self);
2698         iter = tny_list_create_iterator (header_list);
2699         if (!tny_iterator_is_done (iter)) {
2700                 /* notify about the start of the operation */
2701                 ModestMailOperationState *state;
2702                 state = modest_mail_operation_clone_state (self);
2703                 state->done = 0;
2704                 state->total = 0;
2705                 g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL],
2706                                0, state, NULL);
2707
2708                 GetMsgInfo *msg_info = NULL;
2709                 TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iter));
2710                 TnyFolder *folder = tny_header_get_folder (header);
2711
2712                 /* Create the message info */
2713                 msg_info = g_slice_new0 (GetMsgInfo);
2714                 msg_info->mail_op = g_object_ref (self);
2715                 msg_info->header = g_object_ref (header);
2716                 msg_info->more_msgs = g_object_ref (iter);
2717                 msg_info->user_callback = user_callback;
2718                 msg_info->user_data = user_data;
2719                 msg_info->destroy_notify = notify;
2720                 msg_info->last_total_bytes = 0;
2721                 msg_info->sum_total_bytes = 0;
2722                 msg_info->total_bytes = msg_list_size;
2723
2724                 /* The callback will call it per each header */
2725                 tny_folder_get_msg_async (folder, header, get_msg_async_cb, get_msg_status_cb, msg_info);
2726
2727                 /* Free and go on */
2728                 g_object_unref (header);
2729                 g_object_unref (folder);
2730                 g_slice_free (ModestMailOperationState, state);
2731         }
2732         g_object_unref (iter);
2733 }
2734
2735
2736 static void
2737 remove_msgs_async_cb (TnyFolder *folder, 
2738                       gboolean canceled, 
2739                       GError *err, 
2740                       gpointer user_data)
2741 {
2742         gboolean expunge, leave_on_server;
2743         const gchar *account_name;
2744         TnyAccount *account;
2745         ModestProtocolType account_proto = MODEST_PROTOCOL_REGISTRY_TYPE_INVALID;
2746         ModestMailOperation *self;
2747         ModestMailOperationPrivate *priv;
2748         ModestProtocolRegistry *protocol_registry;
2749
2750         self = (ModestMailOperation *) user_data;
2751         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2752         protocol_registry = modest_runtime_get_protocol_registry ();
2753
2754         if (canceled || err) {
2755                 /* If canceled by the user, ignore the error given by Tinymail */
2756                 if (canceled) {
2757                         priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
2758                 } else if (err) {
2759                         priv->status = MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS;
2760                         priv->error = g_error_copy ((const GError *) err);
2761                         priv->error->domain = MODEST_MAIL_OPERATION_ERROR;
2762                 }
2763                 /* Exit */
2764                 modest_mail_operation_notify_end (self);
2765                 g_object_unref (self);
2766                 return;
2767         }
2768
2769         account = tny_folder_get_account (folder);
2770         account_name = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2771         leave_on_server =
2772                 modest_account_mgr_get_leave_on_server (modest_runtime_get_account_mgr (),
2773                                                         account_name);  
2774         account_proto = modest_tny_account_get_protocol_type (account);
2775         g_object_unref (account);
2776
2777         if ((modest_protocol_registry_protocol_type_has_leave_on_server (protocol_registry, account_proto) && 
2778              !leave_on_server) ||
2779             !modest_tny_folder_is_remote_folder (folder))
2780                 expunge = TRUE;
2781         else
2782                 expunge = FALSE;
2783
2784         /* Sync folder */
2785         tny_folder_sync_async(folder, expunge, sync_folder_finish_callback, 
2786                               NULL, self);
2787 }
2788
2789 void 
2790 modest_mail_operation_remove_msgs (ModestMailOperation *self,  
2791                                    TnyList *headers,
2792                                    gboolean remove_to_trash /*ignored*/)
2793 {
2794         TnyFolder *folder = NULL;
2795         ModestMailOperationPrivate *priv;
2796         TnyIterator *iter = NULL;
2797         TnyHeader *header = NULL;
2798         TnyList *remove_headers = NULL;
2799         TnyFolderType folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2800
2801         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2802         g_return_if_fail (TNY_IS_LIST (headers));
2803
2804         if (remove_to_trash)
2805                 g_warning ("remove to trash is not implemented");
2806
2807         if (tny_list_get_length(headers) == 0) {
2808                 g_warning ("%s: list of headers is empty\n", __FUNCTION__);
2809                 goto cleanup; /* nothing to do */
2810         }
2811         
2812         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2813         remove_headers = g_object_ref(headers);
2814
2815         /* Get folder from first header and sync it */
2816         iter = tny_list_create_iterator (headers);      
2817         header = TNY_HEADER (tny_iterator_get_current (iter));
2818
2819         folder = tny_header_get_folder (header);        
2820         if (!TNY_IS_FOLDER(folder)) {
2821                 g_warning ("%s: could not get folder for header\n", __FUNCTION__);
2822                 goto cleanup;
2823         }
2824
2825         /* Don't remove messages that are being sent */
2826         if (modest_tny_folder_is_local_folder (folder)) {
2827                 folder_type = modest_tny_folder_get_local_or_mmc_folder_type (folder);
2828         }
2829         if (folder_type == TNY_FOLDER_TYPE_OUTBOX) {
2830                 TnyTransportAccount *traccount = NULL;
2831                 ModestTnyAccountStore *accstore = modest_runtime_get_account_store();
2832                 traccount = modest_tny_account_store_get_transport_account_from_outbox_header(accstore, header);
2833                 if (traccount) {
2834                         ModestTnySendQueueStatus status;
2835                         ModestTnySendQueue *send_queue = modest_runtime_get_send_queue(traccount, TRUE);
2836
2837                         if (TNY_IS_SEND_QUEUE (send_queue)) {
2838                                 TnyIterator *iter = tny_list_create_iterator(headers);
2839                                 g_object_unref(remove_headers);
2840                                 remove_headers = TNY_LIST(tny_simple_list_new());
2841                                 while (!tny_iterator_is_done(iter)) {
2842                                         char *msg_id;
2843                                         TnyHeader *hdr = TNY_HEADER(tny_iterator_get_current(iter));
2844                                         msg_id = modest_tny_send_queue_get_msg_id (hdr);
2845                                         status = modest_tny_send_queue_get_msg_status(send_queue, msg_id);
2846                                         if (status != MODEST_TNY_SEND_QUEUE_SENDING) {
2847                                                 tny_list_append(remove_headers, G_OBJECT(hdr));
2848                                         }
2849                                         g_object_unref(hdr);
2850                                         g_free(msg_id);
2851                                         tny_iterator_next(iter);
2852                                 }
2853                                 g_object_unref(iter);
2854                         }
2855                         g_object_unref(traccount);
2856                 }
2857         }
2858
2859         /* Get account and set it into mail_operation */
2860         priv->account = modest_tny_folder_get_account (TNY_FOLDER(folder));
2861         priv->op_type = MODEST_MAIL_OPERATION_TYPE_DELETE;
2862         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
2863
2864         /* remove message from folder */
2865         modest_mail_operation_notify_start (self);
2866         tny_folder_remove_msgs_async (folder, remove_headers, remove_msgs_async_cb, 
2867                                       NULL, g_object_ref (self));
2868
2869 cleanup:
2870         if (remove_headers)
2871                 g_object_unref (remove_headers);
2872         if (header)
2873                 g_object_unref (header);
2874         if (iter)
2875                 g_object_unref (iter);
2876         if (folder)
2877                 g_object_unref (folder);
2878 }
2879
2880 static void
2881 notify_progress_of_multiple_messages (ModestMailOperation *self,
2882                                       TnyStatus *status,
2883                                       gint *last_total_bytes,
2884                                       gint *sum_total_bytes,
2885                                       gint total_bytes, 
2886                                       gboolean increment_done)
2887 {
2888         ModestMailOperationPrivate *priv;
2889         ModestMailOperationState *state;
2890         gboolean is_num_bytes = FALSE;
2891
2892         priv =  MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2893
2894         /* We know that tinymail sends us information about
2895          *  transferred bytes with this particular message
2896          */
2897         if (status->message)
2898                 is_num_bytes = (g_ascii_strcasecmp (status->message, "Retrieving message") == 0);
2899
2900         state = modest_mail_operation_clone_state (self);
2901         if (is_num_bytes && !((status->position == 1) && (status->of_total == 100))) {
2902                 /* We know that we're in a different message when the
2903                    total number of bytes to transfer is different. Of
2904                    course it could fail if we're transferring messages
2905                    of the same size, but this is a workarround */
2906                 if (status->of_total != *last_total_bytes) {
2907                         /* We need to increment the done when there is
2908                            no information about each individual
2909                            message, we need to do this in message
2910                            transfers, and we don't do it for getting
2911                            messages */
2912                         if (increment_done)
2913                                 priv->done++;
2914                         *sum_total_bytes += *last_total_bytes;
2915                         *last_total_bytes = status->of_total;
2916                 }
2917                 state->bytes_done += status->position + *sum_total_bytes;
2918                 state->bytes_total = total_bytes;
2919
2920                 /* Notify the status change. Only notify about changes
2921                    referred to bytes */
2922                 g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 
2923                                0, state, NULL);
2924         }
2925
2926         g_slice_free (ModestMailOperationState, state);
2927 }
2928
2929 static void
2930 transfer_msgs_status_cb (GObject *obj,
2931                          TnyStatus *status,  
2932                          gpointer user_data)
2933 {
2934         XFerMsgsAsyncHelper *helper;
2935
2936         g_return_if_fail (status != NULL);
2937
2938         /* Show only the status information we want */
2939         if (status->code != TNY_FOLDER_STATUS_CODE_XFER_MSGS)
2940                 return;
2941
2942         helper = (XFerMsgsAsyncHelper *) user_data;
2943         g_return_if_fail (helper != NULL);       
2944
2945         /* Notify progress */
2946         notify_progress_of_multiple_messages (helper->mail_op, status, &(helper->last_total_bytes), 
2947                                               &(helper->sum_total_bytes), helper->total_bytes, TRUE);
2948 }
2949
2950 static void
2951 transfer_msgs_sync_folder_cb (TnyFolder *self, 
2952                               gboolean cancelled, 
2953                               GError *err, 
2954                               gpointer user_data)
2955 {
2956         XFerMsgsAsyncHelper *helper;
2957         /* We don't care here about the results of the
2958            synchronization */
2959         helper = (XFerMsgsAsyncHelper *) user_data;
2960
2961         /* Notify about operation end */
2962         modest_mail_operation_notify_end (helper->mail_op);
2963
2964         /* If user defined callback function was defined, call it */
2965         if (helper->user_callback)
2966                 helper->user_callback (helper->mail_op, helper->user_data);
2967         
2968         /* Free */
2969         if (helper->more_msgs)
2970                 g_object_unref (helper->more_msgs);
2971         if (helper->headers)
2972                 g_object_unref (helper->headers);
2973         if (helper->dest_folder)
2974                 g_object_unref (helper->dest_folder);
2975         if (helper->mail_op)
2976                 g_object_unref (helper->mail_op);
2977         g_slice_free (XFerMsgsAsyncHelper, helper);
2978 }
2979
2980 static void
2981 transfer_msgs_cb (TnyFolder *folder, gboolean cancelled, GError *err, gpointer user_data)
2982 {
2983         XFerMsgsAsyncHelper *helper;
2984         ModestMailOperation *self;
2985         ModestMailOperationPrivate *priv;
2986         gboolean finished = TRUE;
2987
2988         helper = (XFerMsgsAsyncHelper *) user_data;
2989         self = helper->mail_op;
2990
2991         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2992
2993         if (cancelled) {
2994                 priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
2995         } else if (err) {
2996                 priv->error = g_error_copy (err);
2997                 priv->done = 0;
2998                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;     
2999         } else if (priv->status != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
3000                 if (helper->more_msgs) {
3001                         /* We'll transfer the next message in the list */
3002                         tny_iterator_next (helper->more_msgs);
3003                         if (!tny_iterator_is_done (helper->more_msgs)) {
3004                                 GObject *next_header;
3005                                 g_object_unref (helper->headers);
3006                                 helper->headers = tny_simple_list_new ();
3007                                 next_header = tny_iterator_get_current (helper->more_msgs);
3008                                 tny_list_append (helper->headers, next_header);
3009                                 g_object_unref (next_header);
3010                                 finished = FALSE;
3011                         }
3012                 }
3013                 if (finished) {
3014                         priv->done = 1;
3015                         priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
3016                 }
3017         }
3018
3019         if (finished) {
3020                 /* Synchronize the source folder contents. This should
3021                    be done by tinymail but the camel_folder_sync it's
3022                    actually disabled in transfer_msgs_thread_clean
3023                    because it's supposed to cause hangs */
3024                 tny_folder_sync_async (folder, helper->delete, 
3025                                        transfer_msgs_sync_folder_cb, 
3026                                        NULL, helper);
3027         } else {
3028                 /* Transfer more messages */
3029                 tny_folder_transfer_msgs_async (folder,
3030                                                 helper->headers,
3031                                                 helper->dest_folder,
3032                                                 helper->delete,
3033                                                 transfer_msgs_cb,
3034                                                 transfer_msgs_status_cb,
3035                                                 helper);
3036         }
3037 }
3038
3039 /* Computes the size of the messages the headers in the list belongs
3040    to. If num_elements is different from 0 then it only takes into
3041    account the first num_elements for the calculation */
3042 static guint
3043 compute_message_list_size (TnyList *headers, 
3044                            guint num_elements)
3045 {
3046         TnyIterator *iter;
3047         guint size = 0, element = 0;
3048
3049         /* If num_elements is not valid then take all into account */
3050         if ((num_elements <= 0) || (num_elements > tny_list_get_length (headers)))
3051                 num_elements = tny_list_get_length (headers);
3052
3053         iter = tny_list_create_iterator (headers);
3054         while (!tny_iterator_is_done (iter) && element < num_elements) {
3055                 TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iter));
3056                 size += tny_header_get_message_size (header);
3057                 g_object_unref (header);
3058                 tny_iterator_next (iter);
3059                 element++;
3060         }
3061         g_object_unref (iter);
3062
3063         return size;
3064 }
3065
3066 void
3067 modest_mail_operation_xfer_msgs (ModestMailOperation *self,
3068                                  TnyList *headers, 
3069                                  TnyFolder *folder, 
3070                                  gboolean delete_original,
3071                                  XferMsgsAsyncUserCallback user_callback,
3072                                  gpointer user_data)
3073 {
3074         ModestMailOperationPrivate *priv = NULL;
3075         TnyIterator *iter = NULL;
3076         TnyFolder *src_folder = NULL;
3077         XFerMsgsAsyncHelper *helper = NULL;
3078         TnyHeader *header = NULL;
3079         ModestTnyFolderRules rules = 0;
3080         TnyAccount *dst_account = NULL;
3081         gboolean leave_on_server;
3082         ModestMailOperationState *state;
3083         ModestProtocolRegistry *protocol_registry;
3084         ModestProtocolType account_protocol;
3085
3086         g_return_if_fail (self && MODEST_IS_MAIL_OPERATION (self));
3087         g_return_if_fail (headers && TNY_IS_LIST (headers));
3088         g_return_if_fail (folder && TNY_IS_FOLDER (folder));
3089
3090         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3091         protocol_registry = modest_runtime_get_protocol_registry ();
3092
3093         priv->total = tny_list_get_length (headers);
3094         priv->done = 0;
3095         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
3096         priv->op_type = MODEST_MAIL_OPERATION_TYPE_RECEIVE;
3097
3098         /* Apply folder rules */
3099         rules = modest_tny_folder_get_rules (TNY_FOLDER (folder));
3100         if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
3101                 /* Set status failed and set an error */
3102                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
3103                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
3104                              MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
3105                              _CS("ckct_ib_unable_to_paste_here"));
3106                 /* Notify the queue */
3107                 modest_mail_operation_notify_end (self);
3108                 return;
3109         }
3110                 
3111         /* Get source folder */
3112         iter = tny_list_create_iterator (headers);
3113         header = TNY_HEADER (tny_iterator_get_current (iter));
3114         if (header) {
3115                 src_folder = tny_header_get_folder (header);
3116                 g_object_unref (header);
3117         }
3118         g_object_unref (iter);
3119
3120         if (src_folder == NULL) {
3121                 /* Notify the queue */
3122                 modest_mail_operation_notify_end (self);
3123
3124                 g_warning ("%s: cannot find folder from header", __FUNCTION__);
3125                 return;
3126         }
3127
3128         
3129         /* Check folder source and destination */
3130         if (src_folder == folder) {
3131                 /* Set status failed and set an error */
3132                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
3133                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
3134                              MODEST_MAIL_OPERATION_ERROR_BAD_PARAMETER,
3135                              _("mail_in_ui_folder_copy_target_error"));
3136                 
3137                 /* Notify the queue */
3138                 modest_mail_operation_notify_end (self);
3139                 
3140                 /* Free */
3141                 g_object_unref (src_folder);            
3142                 return;
3143         }
3144
3145         /* Create the helper */
3146         helper = g_slice_new0 (XFerMsgsAsyncHelper);
3147         helper->mail_op = g_object_ref(self);
3148         helper->dest_folder = g_object_ref(folder);
3149         helper->user_callback = user_callback;
3150         helper->user_data = user_data;
3151         helper->last_total_bytes = 0;
3152         helper->sum_total_bytes = 0;
3153         helper->total_bytes = compute_message_list_size (headers, 0);
3154
3155         /* Get account and set it into mail_operation */
3156         priv->account = modest_tny_folder_get_account (src_folder);
3157         dst_account = modest_tny_folder_get_account (folder);
3158
3159         if (priv->account == dst_account) {
3160                 /* Transfer all messages at once using the fast
3161                  * method. Note that depending on the server this
3162                  * might not be that fast, and might not be
3163                  * user-cancellable either */
3164                 helper->headers = g_object_ref (headers);
3165                 helper->more_msgs = NULL;
3166         } else {
3167                 /* Transfer messages one by one so the user can cancel
3168                  * the operation */
3169                 GObject *hdr;
3170                 helper->headers = tny_simple_list_new ();
3171                 helper->more_msgs = tny_list_create_iterator (headers);
3172                 hdr = tny_iterator_get_current (helper->more_msgs);
3173                 tny_list_append (helper->headers, hdr);
3174                 g_object_unref (hdr);
3175         }
3176
3177         /* If leave_on_server is set to TRUE then don't use
3178            delete_original, we always pass FALSE. This is because
3179            otherwise tinymail will try to sync the source folder and
3180            this could cause an error if we're offline while
3181            transferring an already downloaded message from a POP
3182            account */
3183         account_protocol = modest_tny_account_get_protocol_type (priv->account);
3184         if (modest_protocol_registry_protocol_type_has_leave_on_server (protocol_registry, account_protocol)) {
3185                 const gchar *account_name;
3186
3187                 account_name = modest_tny_account_get_parent_modest_account_name_for_server_account (priv->account);
3188                 leave_on_server = modest_account_mgr_get_leave_on_server (modest_runtime_get_account_mgr (),
3189                                                                           account_name);
3190         } else {
3191                 leave_on_server = FALSE;
3192         }
3193
3194         /* Do not delete messages if leave on server is TRUE */
3195         helper->delete = (leave_on_server) ? FALSE : delete_original;
3196
3197         modest_mail_operation_notify_start (self);
3198
3199         /* Start notifying progress */
3200         state = modest_mail_operation_clone_state (self);
3201         state->done = 0;
3202         state->total = 0;
3203         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 0, state, NULL);
3204         g_slice_free (ModestMailOperationState, state);
3205
3206         tny_folder_transfer_msgs_async (src_folder, 
3207                                         helper->headers, 
3208                                         folder, 
3209                                         helper->delete, 
3210                                         transfer_msgs_cb, 
3211                                         transfer_msgs_status_cb,
3212                                         helper);
3213         g_object_unref (src_folder);
3214         g_object_unref (dst_account);
3215 }
3216
3217
3218 static void
3219 on_refresh_folder (TnyFolder   *folder, 
3220                    gboolean     cancelled, 
3221                    GError     *error,
3222                    gpointer     user_data)
3223 {
3224         RefreshAsyncHelper *helper = NULL;
3225         ModestMailOperation *self = NULL;
3226         ModestMailOperationPrivate *priv = NULL;
3227
3228         helper = (RefreshAsyncHelper *) user_data;
3229         self = helper->mail_op;
3230         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3231
3232         g_return_if_fail(priv!=NULL);
3233
3234         if (error) {
3235                 priv->error = g_error_copy (error);
3236                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
3237                 goto out;
3238         }
3239
3240         if (cancelled) {
3241                 priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
3242                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
3243                              MODEST_MAIL_OPERATION_ERROR_ITEM_NOT_FOUND,
3244                              _("Error trying to refresh the contents of %s"),
3245                              tny_folder_get_name (folder));
3246                 goto out;
3247         }
3248
3249         priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
3250  out:
3251
3252         /* Call user defined callback, if it exists */
3253         if (helper->user_callback) {
3254
3255                 /* This is not a GDK lock because we are a Tinymail callback and
3256                  * Tinymail already acquires the Gdk lock */
3257                 helper->user_callback (self, folder, helper->user_data);
3258         }
3259
3260         /* Free */
3261         g_slice_free (RefreshAsyncHelper, helper);
3262
3263         /* Notify about operation end */
3264         modest_mail_operation_notify_end (self);
3265         g_object_unref(self);
3266 }
3267
3268 static void
3269 on_refresh_folder_status_update (GObject *obj,
3270                                  TnyStatus *status,
3271                                  gpointer user_data)
3272 {
3273         RefreshAsyncHelper *helper = NULL;
3274         ModestMailOperation *self = NULL;
3275         ModestMailOperationPrivate *priv = NULL;
3276         ModestMailOperationState *state;
3277
3278         g_return_if_fail (user_data != NULL);
3279         g_return_if_fail (status != NULL);
3280
3281         /* Show only the status information we want */
3282         if (status->code != TNY_FOLDER_STATUS_CODE_REFRESH)
3283                 return;
3284
3285         helper = (RefreshAsyncHelper *) user_data;
3286         self = helper->mail_op;
3287         g_return_if_fail (MODEST_IS_MAIL_OPERATION(self));
3288
3289         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3290
3291         priv->done = status->position;
3292         priv->total = status->of_total;
3293
3294         state = modest_mail_operation_clone_state (self);
3295
3296         /* This is not a GDK lock because we are a Tinymail callback and
3297          * Tinymail already acquires the Gdk lock */
3298         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 0, state, NULL);
3299
3300         g_slice_free (ModestMailOperationState, state);
3301 }
3302
3303 void 
3304 modest_mail_operation_refresh_folder  (ModestMailOperation *self,
3305                                        TnyFolder *folder,
3306                                        RefreshAsyncUserCallback user_callback,
3307                                        gpointer user_data)
3308 {
3309         ModestMailOperationPrivate *priv = NULL;
3310         RefreshAsyncHelper *helper = NULL;
3311
3312         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3313
3314         /* Check memory low */
3315         if (_check_memory_low (self)) {
3316                 if (user_callback)
3317                         user_callback (self, folder, user_data);
3318                 /* Notify about operation end */
3319                 modest_mail_operation_notify_end (self);
3320                 return;
3321         }
3322
3323         /* Get account and set it into mail_operation */
3324         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
3325         priv->account = modest_tny_folder_get_account  (folder);
3326         priv->op_type = MODEST_MAIL_OPERATION_TYPE_RECEIVE;
3327
3328         /* Create the helper */
3329         helper = g_slice_new0 (RefreshAsyncHelper);
3330         helper->mail_op = g_object_ref(self);
3331         helper->user_callback = user_callback;
3332         helper->user_data = user_data;
3333
3334         modest_mail_operation_notify_start (self);
3335         
3336         /* notify that the operation was started */
3337         ModestMailOperationState *state;
3338         state = modest_mail_operation_clone_state (self);
3339         state->done = 0;
3340         state->total = 0;
3341         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 
3342                         0, state, NULL);
3343         g_slice_free (ModestMailOperationState, state);
3344         
3345         tny_folder_refresh_async (folder,
3346                                   on_refresh_folder,
3347                                   on_refresh_folder_status_update,
3348                                   helper);
3349 }
3350
3351 static void
3352 run_queue_notify_and_destroy (RunQueueHelper *helper,
3353                               ModestMailOperationStatus status)
3354 {
3355         ModestMailOperationPrivate *priv;
3356
3357         /* Disconnect */
3358         if (helper->error_handler &&
3359             g_signal_handler_is_connected (helper->queue, helper->error_handler))
3360                 g_signal_handler_disconnect (helper->queue, helper->error_handler);
3361         if (helper->start_handler &&
3362             g_signal_handler_is_connected (helper->queue, helper->start_handler))
3363                 g_signal_handler_disconnect (helper->queue, helper->start_handler);
3364         if (helper->stop_handler &&
3365             g_signal_handler_is_connected (helper->queue, helper->stop_handler))
3366                 g_signal_handler_disconnect (helper->queue, helper->stop_handler);
3367
3368         /* Set status */
3369         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (helper->self);
3370         priv->status = status;
3371
3372         /* Notify end */
3373         modest_mail_operation_notify_end (helper->self);
3374
3375         /* Free data */
3376         g_object_unref (helper->queue);
3377         g_object_unref (helper->self);
3378         g_slice_free (RunQueueHelper, helper);
3379 }
3380
3381 static void
3382 run_queue_stop (ModestTnySendQueue *queue,
3383                 gpointer user_data)
3384 {
3385         RunQueueHelper *helper;
3386
3387         g_debug ("%s sending queue stopped", __FUNCTION__);
3388
3389         helper = (RunQueueHelper *) user_data;
3390         run_queue_notify_and_destroy (helper, MODEST_MAIL_OPERATION_STATUS_SUCCESS);
3391 }
3392
3393 void
3394 modest_mail_operation_run_queue (ModestMailOperation *self,
3395                                  ModestTnySendQueue *queue)
3396 {
3397         ModestMailOperationPrivate *priv;
3398         RunQueueHelper *helper;
3399
3400         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
3401         g_return_if_fail (MODEST_IS_TNY_SEND_QUEUE (queue));
3402         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
3403
3404         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
3405         priv->account = TNY_ACCOUNT (tny_camel_send_queue_get_transport_account (TNY_CAMEL_SEND_QUEUE (queue)));
3406         priv->op_type = MODEST_MAIL_OPERATION_TYPE_RUN_QUEUE;
3407
3408         /* Create the helper */
3409         helper = g_slice_new0 (RunQueueHelper);
3410         helper->queue = g_object_ref (queue);
3411         helper->self = g_object_ref (self);
3412         helper->stop_handler = g_signal_connect (queue, "queue-stop", 
3413                                                  G_CALLBACK (run_queue_stop), 
3414                                                  helper);
3415
3416         /* Notify operation has started */
3417         modest_mail_operation_notify_start (self);
3418         g_debug ("%s, run queue started", __FUNCTION__);
3419 }
3420
3421 static void
3422 shutdown_callback (ModestTnyAccountStore *account_store, gpointer userdata)
3423 {
3424         ModestMailOperation *self = (ModestMailOperation *) userdata;
3425         ModestMailOperationPrivate *priv;
3426
3427         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
3428         g_return_if_fail (MODEST_IS_TNY_ACCOUNT_STORE (account_store));
3429         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
3430
3431         priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
3432
3433         modest_mail_operation_notify_end (self);
3434         g_object_unref (self);
3435 }
3436
3437 void
3438 modest_mail_operation_shutdown (ModestMailOperation *self, ModestTnyAccountStore *account_store)
3439 {
3440         ModestMailOperationPrivate *priv;
3441
3442         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
3443         g_return_if_fail (MODEST_IS_TNY_ACCOUNT_STORE (account_store));
3444         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
3445
3446         modest_mail_operation_queue_set_running_shutdown (modest_runtime_get_mail_operation_queue ());
3447
3448         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
3449         priv->account = NULL;
3450         priv->op_type = MODEST_MAIL_OPERATION_TYPE_SHUTDOWN;
3451
3452         modest_mail_operation_notify_start (self);
3453         g_object_ref (self);
3454         modest_tny_account_store_shutdown (account_store, shutdown_callback, self);
3455 }
3456
3457 static void
3458 sync_folder_finish_callback (TnyFolder *self, 
3459                              gboolean cancelled, 
3460                              GError *err, 
3461                              gpointer user_data)
3462
3463 {
3464         ModestMailOperation *mail_op;
3465         ModestMailOperationPrivate *priv;
3466
3467         mail_op = (ModestMailOperation *) user_data;
3468         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (mail_op);
3469
3470         /* If canceled by the user, ignore the error given by Tinymail */
3471         if (cancelled) {
3472                 priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
3473         } else if (err) {
3474                 /* If the operation was a sync then the status is
3475                    failed, but if it's part of another operation then
3476                    just set it as finished with errors */
3477                 if (priv->op_type == MODEST_MAIL_OPERATION_TYPE_SYNC_FOLDER)
3478                         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
3479                 else
3480                         priv->status = MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS;
3481                 priv->error = g_error_copy ((const GError *) err);
3482                 priv->error->domain = MODEST_MAIL_OPERATION_ERROR;
3483         } else {
3484                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
3485         }
3486
3487         modest_mail_operation_notify_end (mail_op);
3488         g_object_unref (mail_op);
3489 }
3490
3491 void
3492 modest_mail_operation_sync_folder (ModestMailOperation *self,
3493                                    TnyFolder *folder, gboolean expunge)
3494 {
3495         ModestMailOperationPrivate *priv;
3496
3497         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
3498         g_return_if_fail (TNY_IS_FOLDER (folder));
3499         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
3500
3501         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
3502         priv->account = modest_tny_folder_get_account (folder);
3503         priv->op_type = MODEST_MAIL_OPERATION_TYPE_SYNC_FOLDER;
3504
3505         modest_mail_operation_notify_start (self);
3506         g_object_ref (self);
3507         tny_folder_sync_async (folder, expunge, 
3508                                (TnyFolderCallback) sync_folder_finish_callback, 
3509                                NULL, self);
3510 }
3511
3512 static void
3513 modest_mail_operation_notify_start (ModestMailOperation *self)
3514 {
3515         ModestMailOperationPrivate *priv = NULL;
3516
3517         g_return_if_fail (self);
3518
3519         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3520
3521         /* Ensure that all the fields are filled correctly */
3522         g_return_if_fail (priv->op_type != MODEST_MAIL_OPERATION_TYPE_UNKNOWN);
3523
3524         /* Notify the observers about the mail operation. We do not
3525            wrapp this emission because we assume that this function is
3526            always called from within the main lock */
3527         g_signal_emit (G_OBJECT (self), signals[OPERATION_STARTED_SIGNAL], 0, NULL);
3528 }
3529
3530 /**
3531  *
3532  * It's used by the mail operation queue to notify the observers
3533  * attached to that signal that the operation finished. We need to use
3534  * that because tinymail does not give us the progress of a given
3535  * operation when it finishes (it directly calls the operation
3536  * callback).
3537  */
3538 static void
3539 modest_mail_operation_notify_end (ModestMailOperation *self)
3540 {
3541         ModestMailOperationPrivate *priv = NULL;
3542
3543         g_return_if_fail (self);
3544
3545         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3546
3547         /* Notify the observers about the mail operation end. We do
3548            not wrapp this emission because we assume that this
3549            function is always called from within the main lock */
3550         g_signal_emit (G_OBJECT (self), signals[OPERATION_FINISHED_SIGNAL], 0, NULL);
3551
3552         /* Remove the error user data */
3553         if (priv->error_checking_user_data && priv->error_checking_user_data_destroyer)
3554                 priv->error_checking_user_data_destroyer (priv->error_checking_user_data);
3555 }
3556
3557 TnyAccount *
3558 modest_mail_operation_get_account (ModestMailOperation *self)
3559 {
3560         ModestMailOperationPrivate *priv = NULL;
3561
3562         g_return_val_if_fail (self, NULL);
3563
3564         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3565
3566         return (priv->account) ? g_object_ref (priv->account) : NULL;
3567 }
3568
3569 void
3570 modest_mail_operation_noop (ModestMailOperation *self)
3571 {
3572         ModestMailOperationPrivate *priv = NULL;
3573
3574         g_return_if_fail (self);
3575
3576         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3577         priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
3578         priv->op_type = MODEST_MAIL_OPERATION_TYPE_INFO;
3579         priv->done = 0;
3580         priv->total = 0;
3581
3582         /* This mail operation does nothing actually */
3583         modest_mail_operation_notify_start (self);
3584         modest_mail_operation_notify_end (self);
3585 }
3586
3587
3588 gchar*
3589 modest_mail_operation_to_string (ModestMailOperation *self)
3590 {
3591         const gchar *type, *status, *account_id;
3592         ModestMailOperationPrivate *priv = NULL;
3593         
3594         g_return_val_if_fail (self, NULL);
3595
3596         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3597
3598         /* new operations don't have anything interesting */
3599         if (priv->op_type == MODEST_MAIL_OPERATION_TYPE_UNKNOWN)
3600                 return g_strdup_printf ("%p <new operation>", self);
3601         
3602         switch (priv->op_type) {
3603         case MODEST_MAIL_OPERATION_TYPE_SEND:    type= "SEND";    break;
3604         case MODEST_MAIL_OPERATION_TYPE_RECEIVE: type= "RECEIVE"; break;
3605         case MODEST_MAIL_OPERATION_TYPE_OPEN:    type= "OPEN";    break;
3606         case MODEST_MAIL_OPERATION_TYPE_DELETE:  type= "DELETE";  break;
3607         case MODEST_MAIL_OPERATION_TYPE_INFO:    type= "INFO";    break;
3608         case MODEST_MAIL_OPERATION_TYPE_RUN_QUEUE: type= "RUN-QUEUE"; break;
3609         case MODEST_MAIL_OPERATION_TYPE_SYNC_FOLDER: type= "SYNC-FOLDER"; break;
3610         case MODEST_MAIL_OPERATION_TYPE_UNKNOWN: type= "UNKNOWN"; break;
3611         default: type = "UNEXPECTED"; break;
3612         }
3613
3614         switch (priv->status) {
3615         case MODEST_MAIL_OPERATION_STATUS_INVALID:              status= "INVALID"; break;
3616         case MODEST_MAIL_OPERATION_STATUS_SUCCESS:              status= "SUCCESS"; break;
3617         case MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS: status= "FINISHED-WITH-ERRORS"; break;
3618         case MODEST_MAIL_OPERATION_STATUS_FAILED:               status= "FAILED"; break;
3619         case MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS:          status= "IN-PROGRESS"; break;
3620         case MODEST_MAIL_OPERATION_STATUS_CANCELED:             status= "CANCELLED"; break;
3621         default:                                                status= "UNEXPECTED"; break;
3622         } 
3623
3624         account_id = priv->account ? tny_account_get_id (priv->account) : "";
3625
3626         return g_strdup_printf ("%p \"%s\" (%s) [%s] {%d/%d} '%s'", self, account_id,type, status,
3627                                 priv->done, priv->total,
3628                                 priv->error && priv->error->message ? priv->error->message : "");
3629 }
3630
3631 /* 
3632  * Once the mail operations were objects this will be no longer
3633  * needed. I don't like it, but we need it for the moment
3634  */
3635 static gboolean
3636 _check_memory_low (ModestMailOperation *mail_op)
3637 {
3638         if (modest_platform_check_memory_low (NULL, FALSE)) {
3639                 ModestMailOperationPrivate *priv;
3640
3641                 priv = MODEST_MAIL_OPERATION_GET_PRIVATE (mail_op);
3642                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
3643                 g_set_error (&(priv->error),
3644                              MODEST_MAIL_OPERATION_ERROR,
3645                              MODEST_MAIL_OPERATION_ERROR_LOW_MEMORY,
3646                              "Not enough memory to complete the operation");
3647                 return TRUE;
3648         } else {
3649                 return FALSE;
3650         }
3651 }