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