Force to have at least first row selected in edit modes (fixes NB#98788).
[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_prepend (TNY_LIST (user_data), 
1333                           G_OBJECT (header));
1334 }
1335
1336 /* This is the method that looks for new messages in a folder */
1337 static void
1338 internal_folder_observer_update (TnyFolderObserver *self, TnyFolderChange *change)
1339 {
1340         InternalFolderObserver *derived = (InternalFolderObserver *)self;
1341         
1342         TnyFolderChangeChanged changed;
1343
1344         changed = tny_folder_change_get_changed (change);
1345
1346         if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) {
1347                 TnyList *list;
1348
1349                 /* Get added headers */
1350                 list = tny_simple_list_new ();
1351                 tny_folder_change_get_added_headers (change, list);
1352
1353                 /* Add them to the folder observer */
1354                 tny_list_foreach (list, foreach_add_item, 
1355                                   derived->new_headers);
1356
1357                 g_object_unref (G_OBJECT (list));
1358         }
1359 }
1360
1361 static void
1362 internal_folder_observer_init (InternalFolderObserver *self) 
1363 {
1364         self->new_headers = tny_simple_list_new ();
1365 }
1366 static void
1367 internal_folder_observer_finalize (GObject *object) 
1368 {
1369         InternalFolderObserver *self;
1370
1371         self = (InternalFolderObserver *) object;
1372         g_object_unref (self->new_headers);
1373
1374         G_OBJECT_CLASS (internal_folder_observer_parent_class)->finalize (object);
1375 }
1376 static void
1377 tny_folder_observer_init (TnyFolderObserverIface *iface) 
1378 {
1379         iface->update = internal_folder_observer_update;
1380 }
1381 static void
1382 internal_folder_observer_class_init (InternalFolderObserverClass *klass) 
1383 {
1384         GObjectClass *object_class;
1385
1386         internal_folder_observer_parent_class = g_type_class_peek_parent (klass);
1387         object_class = (GObjectClass*) klass;
1388         object_class->finalize = internal_folder_observer_finalize;
1389 }
1390
1391 static void
1392 destroy_update_account_info (UpdateAccountInfo *info)
1393 {
1394         g_free (info->account_name);
1395         g_object_unref (info->folders);
1396         g_object_unref (info->mail_op);
1397         g_slice_free (UpdateAccountInfo, info);
1398 }
1399
1400
1401 static void
1402 update_account_send_mail (UpdateAccountInfo *info)
1403 {
1404         TnyTransportAccount *transport_account = NULL;
1405         ModestTnyAccountStore *account_store;
1406
1407         account_store = modest_runtime_get_account_store ();
1408
1409         /* We don't try to send messages while sending mails is blocked */
1410         if (modest_tny_account_store_is_send_mail_blocked (account_store))
1411                 return;
1412
1413         /* Get the transport account */
1414         transport_account = (TnyTransportAccount *)
1415                 modest_tny_account_store_get_transport_account_for_open_connection (account_store,
1416                                                                                     info->account_name);
1417
1418         if (transport_account) {
1419                 ModestTnySendQueue *send_queue;
1420                 TnyFolder *outbox;
1421                 guint num_messages;
1422
1423                 send_queue = modest_runtime_get_send_queue (transport_account, TRUE);
1424                 g_object_unref (transport_account);
1425
1426                 if (TNY_IS_SEND_QUEUE (send_queue)) {
1427                         /* Get outbox folder */
1428                         outbox = tny_send_queue_get_outbox (TNY_SEND_QUEUE (send_queue));
1429                         if (outbox) { /* this could fail in some cases */
1430                                 num_messages = tny_folder_get_all_count (outbox);
1431                                 g_object_unref (outbox);
1432                         } else {
1433                                 g_warning ("%s: could not get outbox", __FUNCTION__);
1434                                 num_messages = 0;
1435                         }
1436                 
1437                         if (num_messages != 0) {
1438                                 /* Reenable suspended items */
1439                                 modest_tny_send_queue_wakeup (MODEST_TNY_SEND_QUEUE (send_queue));
1440                                 
1441                                 /* Try to send */
1442                                 tny_camel_send_queue_flush (TNY_CAMEL_SEND_QUEUE (send_queue));
1443                                 modest_tny_send_queue_set_requested_send_receive (MODEST_TNY_SEND_QUEUE (send_queue), 
1444                                                                                   info->interactive);
1445                         }
1446                 }
1447         }
1448 }
1449
1450 static void
1451 update_account_get_msg_async_cb (TnyFolder *folder, 
1452                                  gboolean canceled, 
1453                                  TnyMsg *msg, 
1454                                  GError *err, 
1455                                  gpointer user_data)
1456 {
1457         GetMsgInfo *msg_info = (GetMsgInfo *) user_data;
1458         ModestMailOperationPrivate *priv;
1459
1460         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (msg_info->mail_op);
1461         priv->done++;
1462
1463         if (TNY_IS_MSG (msg)) {
1464                 TnyHeader *header = tny_msg_get_header (msg);
1465
1466                 if (header) {
1467                         ModestMailOperationState *state;
1468                         state = modest_mail_operation_clone_state (msg_info->mail_op);
1469                         msg_info->sum_total_bytes += tny_header_get_message_size (header);
1470                         state->bytes_done = msg_info->sum_total_bytes;
1471                         state->bytes_total = msg_info->total_bytes;
1472
1473                         /* Notify the status change. Only notify about changes
1474                            referred to bytes */
1475                         g_signal_emit (G_OBJECT (msg_info->mail_op), 
1476                                        signals[PROGRESS_CHANGED_SIGNAL], 
1477                                        0, state, NULL);
1478
1479                         g_object_unref (header);
1480                         g_slice_free (ModestMailOperationState, state);
1481                 }
1482         }
1483
1484         if (priv->done == priv->total) {
1485                 TnyList *new_headers;
1486                 UpdateAccountInfo *info;
1487
1488                 /* After getting all the messages send the ones in the
1489                    outboxes */
1490                 info = (UpdateAccountInfo *) msg_info->user_data;
1491                 update_account_send_mail (info);
1492
1493                 /* Check if the operation was a success */
1494                 if (!priv->error)
1495                         priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
1496                 
1497                 /* Call the user callback and free */
1498                 new_headers = tny_iterator_get_list (msg_info->more_msgs);
1499                 update_account_notify_user_and_free (info, new_headers);
1500                 g_object_unref (new_headers);
1501
1502                 /* Delete the helper */
1503                 g_object_unref (msg_info->more_msgs);
1504                 g_object_unref (msg_info->mail_op);
1505                 g_slice_free (GetMsgInfo, msg_info);
1506         }
1507 }
1508
1509 static void
1510 update_account_notify_user_and_free (UpdateAccountInfo *info, 
1511                                      TnyList *new_headers)
1512 {
1513         /* Set the account back to not busy */
1514         modest_account_mgr_set_account_busy (modest_runtime_get_account_mgr (), 
1515                                              info->account_name, FALSE);
1516         
1517         /* User callback */
1518         if (info->callback)
1519                 info->callback (info->mail_op, new_headers, info->user_data);
1520         
1521         /* Mail operation end */
1522         modest_mail_operation_notify_end (info->mail_op);
1523
1524         /* Frees */
1525         if (new_headers)
1526                 g_object_unref (new_headers);
1527         destroy_update_account_info (info);
1528 }
1529
1530 static void
1531 inbox_refreshed_cb (TnyFolder *inbox, 
1532                     gboolean canceled, 
1533                     GError *err, 
1534                     gpointer user_data)
1535 {       
1536         UpdateAccountInfo *info;
1537         ModestMailOperationPrivate *priv;
1538         TnyIterator *new_headers_iter;
1539         GPtrArray *new_headers_array = NULL;   
1540         gint max_size, retrieve_limit, i;
1541         ModestAccountMgr *mgr;
1542         ModestAccountRetrieveType retrieve_type;
1543         TnyList *new_headers = NULL;
1544         gboolean headers_only, ignore_limit;
1545
1546         info = (UpdateAccountInfo *) user_data;
1547         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (info->mail_op);
1548         mgr = modest_runtime_get_account_mgr ();
1549
1550         /* Set the last updated as the current time, do it even if the inbox refresh failed */
1551         modest_account_mgr_set_last_updated (mgr, tny_account_get_id (priv->account), time (NULL));
1552
1553         if (canceled || err) {
1554                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
1555                 if (err)
1556                         priv->error = g_error_copy (err);
1557                 else
1558                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
1559                                      MODEST_MAIL_OPERATION_ERROR_OPERATION_CANCELED,
1560                                      "canceled");
1561
1562                 tny_folder_remove_observer (inbox, info->inbox_observer);
1563                 g_object_unref (info->inbox_observer);
1564                 info->inbox_observer = NULL;
1565
1566                 /* Notify the user about the error and then exit */
1567                 update_account_notify_user_and_free (info, NULL);
1568                 return;
1569         }
1570
1571         if (!inbox) {
1572                 /* Try to send anyway */
1573                 goto send_mail;
1574         }
1575
1576         /* Get the message max size */
1577         max_size  = modest_conf_get_int (modest_runtime_get_conf (),
1578                                          MODEST_CONF_MSG_SIZE_LIMIT, NULL);
1579         if (max_size == 0)
1580                 max_size = G_MAXINT;
1581         else
1582                 max_size = max_size * KB;
1583
1584         /* Create the new headers array. We need it to sort the
1585            new headers by date */
1586         new_headers_array = g_ptr_array_new ();
1587         if (info->inbox_observer) {
1588                 new_headers_iter = tny_list_create_iterator (((InternalFolderObserver *) info->inbox_observer)->new_headers);
1589                 while (!tny_iterator_is_done (new_headers_iter)) {
1590                         TnyHeader *header = NULL;
1591
1592                         header = TNY_HEADER (tny_iterator_get_current (new_headers_iter));
1593                         /* Apply per-message size limits */
1594                         if (tny_header_get_message_size (header) < max_size)
1595                                 g_ptr_array_add (new_headers_array, g_object_ref (header));
1596                         
1597                         g_object_unref (header);
1598                         tny_iterator_next (new_headers_iter);
1599                 }
1600                 g_object_unref (new_headers_iter);
1601
1602                 tny_folder_remove_observer (inbox, info->inbox_observer);
1603                 g_object_unref (info->inbox_observer);
1604                 info->inbox_observer = NULL;
1605         }
1606
1607         if (new_headers_array->len == 0) {
1608                 g_ptr_array_free (new_headers_array, FALSE);
1609                 goto send_mail;
1610         }
1611
1612         /* Get per-account message amount retrieval limit */
1613         retrieve_limit = modest_account_mgr_get_retrieve_limit (mgr, info->account_name);
1614         if (retrieve_limit == 0)
1615                 retrieve_limit = G_MAXINT;
1616         
1617         /* Get per-account retrieval type */
1618         retrieve_type = modest_account_mgr_get_retrieve_type (mgr, info->account_name);
1619         headers_only = (retrieve_type == MODEST_ACCOUNT_RETRIEVE_HEADERS_ONLY);
1620
1621         /* Order by date */
1622         g_ptr_array_sort (new_headers_array, (GCompareFunc) compare_headers_by_date);
1623         
1624         /* Ask the users if they want to retrieve all the messages
1625            even though the limit was exceeded */
1626         ignore_limit = FALSE;
1627         if (new_headers_array->len > retrieve_limit) {
1628                 /* Ask the user if a callback has been specified and
1629                    if the mail operation has a source (this means that
1630                    was invoked by the user and not automatically by a
1631                    D-Bus method) */
1632                 if (info->retrieve_all_cb && priv->source)
1633                         ignore_limit = info->retrieve_all_cb (priv->source,
1634                                                               new_headers_array->len,
1635                                                               retrieve_limit);
1636         }
1637
1638         /* Copy the headers to a list and free the array */
1639         new_headers = tny_simple_list_new ();
1640         for (i=0; i < new_headers_array->len; i++) {
1641                 TnyHeader *header = TNY_HEADER (g_ptr_array_index (new_headers_array, i));
1642                 tny_list_append (new_headers, G_OBJECT (header));
1643         }
1644         g_ptr_array_foreach (new_headers_array, (GFunc) g_object_unref, NULL);
1645         g_ptr_array_free (new_headers_array, FALSE);
1646         
1647         if (!headers_only && (tny_list_get_length (new_headers) > 0)) {
1648                 gint msg_num = 0;
1649                 TnyIterator *iter;
1650                 GetMsgInfo *msg_info;
1651
1652                 priv->done = 0;
1653                 if (ignore_limit)
1654                         priv->total = tny_list_get_length (new_headers);
1655                 else
1656                         priv->total = MIN (tny_list_get_length (new_headers), retrieve_limit);
1657
1658                 iter = tny_list_create_iterator (new_headers);
1659
1660                 /* Create the message info */
1661                 msg_info = g_slice_new0 (GetMsgInfo);
1662                 msg_info->mail_op = g_object_ref (info->mail_op);
1663                 msg_info->total_bytes = compute_message_list_size (new_headers, priv->total);
1664                 msg_info->more_msgs = g_object_ref (iter);
1665                 msg_info->user_data = info;
1666
1667                 while ((msg_num < priv->total ) && !tny_iterator_is_done (iter)) {              
1668                         TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iter));
1669                         TnyFolder *folder = tny_header_get_folder (header);
1670
1671                         /* Get message in an async way */
1672                         tny_folder_get_msg_async (folder, header, update_account_get_msg_async_cb, 
1673                                                   NULL, msg_info);
1674
1675                         g_object_unref (folder);
1676                         
1677                         msg_num++;
1678                         tny_iterator_next (iter);
1679                 }
1680                 g_object_unref (iter);
1681
1682                 /* The mail operation will finish when the last
1683                    message is retrieved */
1684                 return;
1685         }
1686  send_mail:
1687         /* If we don't have to retrieve the new messages then
1688            simply send mail */
1689         update_account_send_mail (info);
1690         
1691         /* Check if the operation was a success */
1692         if (!priv->error)
1693                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
1694         
1695         /* Call the user callback and free */
1696         update_account_notify_user_and_free (info, new_headers);
1697 }
1698
1699 static void
1700 inbox_refresh_status_update (GObject *obj,
1701                              TnyStatus *status,
1702                              gpointer user_data)
1703 {
1704         UpdateAccountInfo *info = NULL;
1705         ModestMailOperation *self = NULL;
1706         ModestMailOperationPrivate *priv = NULL;
1707         ModestMailOperationState *state;
1708
1709         g_return_if_fail (user_data != NULL);
1710         g_return_if_fail (status != NULL);
1711
1712         /* Show only the status information we want */
1713         if (status->code != TNY_FOLDER_STATUS_CODE_REFRESH)
1714                 return;
1715
1716         info = (UpdateAccountInfo *) user_data;
1717         self = info->mail_op;
1718         g_return_if_fail (MODEST_IS_MAIL_OPERATION(self));
1719
1720         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
1721
1722         priv->done = status->position;
1723         priv->total = status->of_total;
1724
1725         state = modest_mail_operation_clone_state (self);
1726
1727         /* This is not a GDK lock because we are a Tinymail callback and
1728          * Tinymail already acquires the Gdk lock */
1729         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 0, state, NULL);
1730
1731         g_slice_free (ModestMailOperationState, state);
1732 }
1733
1734 static void 
1735 recurse_folders_async_cb (TnyFolderStore *folder_store, 
1736                           gboolean canceled,
1737                           TnyList *list, 
1738                           GError *err, 
1739                           gpointer user_data)
1740 {
1741         UpdateAccountInfo *info;
1742         ModestMailOperationPrivate *priv;
1743     
1744         info = (UpdateAccountInfo *) user_data;
1745         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (info->mail_op);
1746
1747         if (err || canceled) {
1748                 /* If the error was previosly set by another callback
1749                    don't set it again */
1750                 if (!priv->error) {
1751                         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
1752                         if (err)
1753                                 priv->error = g_error_copy (err);
1754                         else
1755                                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
1756                                              MODEST_MAIL_OPERATION_ERROR_OPERATION_CANCELED,
1757                                              "canceled");
1758                 }
1759         } else { 
1760                 /* We're not getting INBOX children if we don't want to poke all */
1761                 TnyIterator *iter = tny_list_create_iterator (list);
1762                 while (!tny_iterator_is_done (iter)) {
1763                         TnyFolderStore *folder = (TnyFolderStore*) tny_iterator_get_current (iter);
1764
1765                         /* Add to the list of all folders */
1766                         tny_list_append (info->folders, (GObject *) folder);
1767                         
1768                         if (info->poke_all) {
1769                                 TnyList *folders = tny_simple_list_new ();
1770                                 /* Add pending call */
1771                                 info->pending_calls++;
1772                                 
1773                                 tny_folder_store_get_folders_async (folder, folders, NULL, FALSE,
1774                                                                     recurse_folders_async_cb, 
1775                                                                     NULL, info);
1776                                 g_object_unref (folders);
1777                         }
1778                         
1779                         g_object_unref (G_OBJECT (folder));
1780                         
1781                         tny_iterator_next (iter);           
1782                 }
1783                 g_object_unref (G_OBJECT (iter));
1784         }
1785
1786         /* Remove my own pending call */
1787         info->pending_calls--;
1788
1789         /* This means that we have all the folders */
1790         if (info->pending_calls == 0) {
1791                 TnyIterator *iter_all_folders;
1792                 TnyFolder *inbox = NULL;
1793
1794                 /* If there was any error do not continue */
1795                 if (priv->error) {
1796                         update_account_notify_user_and_free (info, NULL);
1797                         return;
1798                 }
1799
1800                 iter_all_folders = tny_list_create_iterator (info->folders);
1801
1802                 /* Do a poke status over all folders */
1803                 while (!tny_iterator_is_done (iter_all_folders) &&
1804                        priv->status == MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS) {
1805                         TnyFolder *folder = NULL;
1806
1807                         folder = TNY_FOLDER (tny_iterator_get_current (iter_all_folders));
1808
1809                         if (tny_folder_get_folder_type (folder) == TNY_FOLDER_TYPE_INBOX) {
1810                                 /* Get a reference to the INBOX */
1811                                 inbox = g_object_ref (folder);
1812                         } else {
1813                                 /* Issue a poke status over the folder */
1814                                 if (info->poke_all)
1815                                         tny_folder_poke_status (folder);
1816                         }
1817
1818                         /* Free and go to next */
1819                         g_object_unref (folder);
1820                         tny_iterator_next (iter_all_folders);
1821                 }
1822                 g_object_unref (iter_all_folders);
1823
1824                 /* Refresh the INBOX */
1825                 if (inbox) {
1826                         /* Refresh the folder. Our observer receives
1827                          * the new emails during folder refreshes, so
1828                          * we can use observer->new_headers
1829                          */
1830                         info->inbox_observer = g_object_new (internal_folder_observer_get_type (), NULL);
1831                         tny_folder_add_observer (inbox, info->inbox_observer);
1832
1833                         /* Refresh the INBOX */
1834                         tny_folder_refresh_async (inbox, inbox_refreshed_cb, inbox_refresh_status_update, info);
1835                         g_object_unref (inbox);
1836                 } else {
1837                         /* We could not perform the inbox refresh but
1838                            we'll try to send mails anyway */
1839                         inbox_refreshed_cb (inbox, FALSE, NULL, info);
1840                 }
1841         }
1842 }
1843
1844 void
1845 modest_mail_operation_update_account (ModestMailOperation *self,
1846                                       const gchar *account_name,
1847                                       gboolean poke_all,
1848                                       gboolean interactive,
1849                                       RetrieveAllCallback retrieve_all_cb,
1850                                       UpdateAccountCallback callback,
1851                                       gpointer user_data)
1852 {
1853         UpdateAccountInfo *info = NULL;
1854         ModestMailOperationPrivate *priv = NULL;
1855         ModestTnyAccountStore *account_store = NULL;
1856         TnyList *folders;
1857         ModestMailOperationState *state;
1858
1859         /* Init mail operation */
1860         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
1861         priv->total = 0;
1862         priv->done  = 0;
1863         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
1864         priv->op_type = MODEST_MAIL_OPERATION_TYPE_SEND_AND_RECEIVE;
1865
1866         /* Get the store account */
1867         account_store = modest_runtime_get_account_store ();
1868         priv->account =
1869                 modest_tny_account_store_get_server_account (account_store,
1870                                                              account_name,
1871                                                              TNY_ACCOUNT_TYPE_STORE);
1872
1873         /* The above function could return NULL */
1874         if (!priv->account) {
1875                 /* Check if the operation was a success */
1876                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
1877                              MODEST_MAIL_OPERATION_ERROR_ITEM_NOT_FOUND,
1878                              "no account");
1879                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
1880
1881                 /* Call the user callback */
1882                 if (callback)
1883                         callback (self, NULL, user_data);
1884
1885                 /* Notify about operation end */
1886                 modest_mail_operation_notify_end (self);
1887
1888                 return;
1889         }
1890         
1891         /* We have once seen priv->account getting finalized during this code,
1892          * therefore adding a reference (bug #82296) */
1893         
1894         g_object_ref (priv->account);
1895
1896         /* Create the helper object */
1897         info = g_slice_new0 (UpdateAccountInfo);
1898         info->pending_calls = 1;
1899         info->folders = tny_simple_list_new ();
1900         info->mail_op = g_object_ref (self);
1901         info->poke_all = poke_all;
1902         info->interactive = interactive;
1903         info->account_name = g_strdup (account_name);
1904         info->callback = callback;
1905         info->user_data = user_data;
1906         info->retrieve_all_cb = retrieve_all_cb;
1907
1908         /* Set account busy */
1909         modest_account_mgr_set_account_busy (modest_runtime_get_account_mgr (), account_name, TRUE);
1910         modest_mail_operation_notify_start (self);
1911
1912         /* notify about the start of the operation */ 
1913         state = modest_mail_operation_clone_state (self);
1914         state->done = 0;
1915         state->total = 0;
1916
1917         /* Start notifying progress */
1918         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 0, state, NULL);
1919         g_slice_free (ModestMailOperationState, state);
1920         
1921         /* Get all folders and continue in the callback */ 
1922         folders = tny_simple_list_new ();
1923         tny_folder_store_get_folders_async (TNY_FOLDER_STORE (priv->account),
1924                                             folders, NULL, FALSE,
1925                                             recurse_folders_async_cb, 
1926                                             NULL, info);
1927         g_object_unref (folders);
1928         
1929         g_object_unref (priv->account);
1930         
1931 }
1932
1933 /*
1934  * Used to notify the queue from the main
1935  * loop. We call it inside an idle call to achieve that
1936  */
1937 static gboolean
1938 idle_notify_queue (gpointer data)
1939 {
1940         ModestMailOperation *mail_op = MODEST_MAIL_OPERATION (data);
1941
1942         gdk_threads_enter ();
1943         modest_mail_operation_notify_end (mail_op);
1944         gdk_threads_leave ();
1945         g_object_unref (mail_op);
1946
1947         return FALSE;
1948 }
1949
1950 static int
1951 compare_headers_by_date (gconstpointer a,
1952                          gconstpointer b)
1953 {
1954         TnyHeader **header1, **header2;
1955         time_t sent1, sent2;
1956
1957         header1 = (TnyHeader **) a;
1958         header2 = (TnyHeader **) b;
1959
1960         sent1 = tny_header_get_date_sent (*header1);
1961         sent2 = tny_header_get_date_sent (*header2);
1962
1963         /* We want the most recent ones (greater time_t) at the
1964            beginning */
1965         if (sent1 < sent2)
1966                 return 1;
1967         else
1968                 return -1;
1969 }
1970
1971
1972 /* ******************************************************************* */
1973 /* ************************** STORE  ACTIONS ************************* */
1974 /* ******************************************************************* */
1975
1976 typedef struct {
1977         ModestMailOperation *mail_op;
1978         CreateFolderUserCallback callback;
1979         gpointer user_data;
1980 } CreateFolderInfo;
1981
1982
1983 static void
1984 create_folder_cb (TnyFolderStore *parent_folder, 
1985                   gboolean canceled, 
1986                   TnyFolder *new_folder, 
1987                   GError *err, 
1988                   gpointer user_data)
1989 {
1990         ModestMailOperationPrivate *priv;
1991         CreateFolderInfo *info;
1992
1993         info = (CreateFolderInfo *) user_data;
1994         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (info->mail_op);
1995
1996         if (canceled || err) {
1997                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
1998                 if (err)
1999                         priv->error = g_error_copy (err);
2000                 else
2001                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2002                                      MODEST_MAIL_OPERATION_ERROR_OPERATION_CANCELED,
2003                                      "canceled");               
2004         } else {
2005                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
2006         }
2007
2008         /* The user will unref the new_folder */
2009         if (info->callback)
2010                 info->callback (info->mail_op, parent_folder, 
2011                                 new_folder, info->user_data);
2012         
2013         /* Notify about operation end */
2014         modest_mail_operation_notify_end (info->mail_op);
2015
2016         /* Frees */
2017         g_object_unref (info->mail_op);
2018         g_slice_free (CreateFolderInfo, info);
2019 }
2020
2021 void
2022 modest_mail_operation_create_folder (ModestMailOperation *self,
2023                                      TnyFolderStore *parent,
2024                                      const gchar *name,
2025                                      CreateFolderUserCallback callback,
2026                                      gpointer user_data)
2027 {
2028         ModestMailOperationPrivate *priv;
2029
2030         g_return_if_fail (TNY_IS_FOLDER_STORE (parent));
2031         g_return_if_fail (name);
2032         
2033         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2034         priv->op_type = MODEST_MAIL_OPERATION_TYPE_INFO;
2035         priv->account = (TNY_IS_ACCOUNT (parent)) ? 
2036                 g_object_ref (parent) : 
2037                 modest_tny_folder_get_account (TNY_FOLDER (parent));
2038
2039         /* Check for already existing folder */
2040         if (modest_tny_folder_has_subfolder_with_name (parent, name, TRUE)) {
2041                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2042                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2043                              MODEST_MAIL_OPERATION_ERROR_FOLDER_EXISTS,
2044                              _CS("ckdg_ib_folder_already_exists"));
2045         }
2046
2047         /* Check parent */
2048         if (TNY_IS_FOLDER (parent)) {
2049                 /* Check folder rules */
2050                 ModestTnyFolderRules rules = modest_tny_folder_get_rules (TNY_FOLDER (parent));
2051                 if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
2052                         /* Set status failed and set an error */
2053                         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2054                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2055                                      MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
2056                                      _("mail_in_ui_folder_create_error"));
2057                 }
2058         }
2059
2060         if (!strcmp (name, " ") || strchr (name, '/')) {
2061                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2062                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2063                              MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
2064                              _("mail_in_ui_folder_create_error"));
2065         }
2066
2067         if (!priv->error) {
2068                 CreateFolderInfo *info;
2069
2070                 info = g_slice_new0 (CreateFolderInfo);
2071                 info->mail_op = g_object_ref (self);
2072                 info->callback = callback;
2073                 info->user_data = user_data;
2074
2075                 modest_mail_operation_notify_start (self);
2076
2077                 /* Create the folder */
2078                 tny_folder_store_create_folder_async (parent, name, create_folder_cb, 
2079                                                       NULL, info);
2080         } else {
2081                 /* Call the user callback anyway */
2082                 if (callback)
2083                         callback (self, parent, NULL, user_data);
2084                 /* Notify about operation end */
2085                 modest_mail_operation_notify_end (self);
2086         }
2087 }
2088
2089 void
2090 modest_mail_operation_remove_folder (ModestMailOperation *self,
2091                                      TnyFolder           *folder,
2092                                      gboolean             remove_to_trash)
2093 {
2094         ModestMailOperationPrivate *priv;
2095         ModestTnyFolderRules rules;
2096
2097         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2098         g_return_if_fail (TNY_IS_FOLDER (folder));
2099         
2100         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2101         
2102         /* Check folder rules */
2103         rules = modest_tny_folder_get_rules (TNY_FOLDER (folder));
2104         if (rules & MODEST_FOLDER_RULES_FOLDER_NON_DELETABLE) {
2105                 /* Set status failed and set an error */
2106                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2107                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2108                              MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
2109                              _("mail_in_ui_folder_delete_error"));
2110                 goto end;
2111         }
2112
2113         /* Get the account */
2114         priv->account = modest_tny_folder_get_account (folder);
2115         priv->op_type = MODEST_MAIL_OPERATION_TYPE_DELETE;
2116
2117         /* Delete folder or move to trash */
2118         if (remove_to_trash) {
2119                 TnyFolder *trash_folder = NULL;
2120                 trash_folder = modest_tny_account_get_special_folder (priv->account,
2121                                                                       TNY_FOLDER_TYPE_TRASH);
2122                 /* TODO: error_handling */
2123                 if (trash_folder) {
2124                         modest_mail_operation_notify_start (self);
2125                         modest_mail_operation_xfer_folder (self, folder,
2126                                                     TNY_FOLDER_STORE (trash_folder), 
2127                                                     TRUE, NULL, NULL);
2128                         g_object_unref (trash_folder);
2129                 } else {
2130                         g_warning ("%s: modest_tny_account_get_special_folder(..) returned a NULL trash folder", __FUNCTION__);
2131                 }
2132         } else {
2133                 TnyFolderStore *parent = tny_folder_get_folder_store (folder);
2134                 if (parent) {
2135                         modest_mail_operation_notify_start (self);
2136                         tny_folder_store_remove_folder (parent, folder, &(priv->error));
2137                         CHECK_EXCEPTION (priv, MODEST_MAIL_OPERATION_STATUS_FAILED);
2138                         
2139                         if (!priv->error)
2140                                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
2141
2142                         g_object_unref (parent);
2143                 } else
2144                         g_warning ("%s: could not get parent folder", __FUNCTION__);
2145         }
2146
2147  end:
2148         /* Notify about operation end */
2149         modest_mail_operation_notify_end (self);
2150 }
2151
2152 static void
2153 transfer_folder_status_cb (GObject *obj,
2154                            TnyStatus *status,
2155                            gpointer user_data)
2156 {
2157         ModestMailOperation *self;
2158         ModestMailOperationPrivate *priv;
2159         ModestMailOperationState *state;
2160         XFerFolderAsyncHelper *helper;
2161
2162         g_return_if_fail (status != NULL);
2163
2164         /* Show only the status information we want */
2165         if (status->code != TNY_FOLDER_STATUS_CODE_COPY_FOLDER)
2166                 return;
2167
2168         helper = (XFerFolderAsyncHelper *) user_data;
2169         g_return_if_fail (helper != NULL);
2170
2171         self = helper->mail_op;
2172         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
2173
2174         priv->done = status->position;
2175         priv->total = status->of_total;
2176
2177         state = modest_mail_operation_clone_state (self);
2178
2179         /* This is not a GDK lock because we are a Tinymail callback
2180          * which is already GDK locked by Tinymail */
2181
2182         /* no gdk_threads_enter (), CHECKED */
2183
2184         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 0, state, NULL); 
2185
2186         /* no gdk_threads_leave (), CHECKED */
2187
2188         g_slice_free (ModestMailOperationState, state);
2189 }
2190
2191
2192 static void
2193 transfer_folder_cb (TnyFolder *folder, 
2194                     gboolean cancelled, 
2195                     TnyFolderStore *into, 
2196                     TnyFolder *new_folder, 
2197                     GError *err, 
2198                     gpointer user_data)
2199 {
2200         XFerFolderAsyncHelper *helper;
2201         ModestMailOperation *self = NULL;
2202         ModestMailOperationPrivate *priv = NULL;
2203
2204         helper = (XFerFolderAsyncHelper *) user_data;
2205         g_return_if_fail (helper != NULL);       
2206
2207         self = helper->mail_op;
2208         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
2209
2210         if (err) {
2211                 priv->error = g_error_copy (err);
2212                 priv->done = 0;
2213                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2214         } else if (cancelled) {
2215                 priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
2216                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2217                              MODEST_MAIL_OPERATION_ERROR_OPERATION_CANCELED,
2218                              _("Transference of %s was cancelled."),
2219                              tny_folder_get_name (folder));
2220         } else {
2221                 priv->done = 1;
2222                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
2223         }
2224                 
2225         /* Notify about operation end */
2226         modest_mail_operation_notify_end (self);
2227
2228         /* If user defined callback function was defined, call it */
2229         if (helper->user_callback) {
2230
2231                 /* This is not a GDK lock because we are a Tinymail callback
2232                  * which is already GDK locked by Tinymail */
2233
2234                 /* no gdk_threads_enter (), CHECKED */
2235                 helper->user_callback (self, new_folder, helper->user_data);
2236                 /* no gdk_threads_leave () , CHECKED */
2237         }
2238
2239         /* Free */
2240         g_object_unref (helper->mail_op);
2241         g_slice_free   (XFerFolderAsyncHelper, helper);
2242 }
2243
2244 /**
2245  *
2246  * This function checks if the new name is a valid name for our local
2247  * folders account. The new name could not be the same than then name
2248  * of any of the mandatory local folders
2249  *
2250  * We can not rely on tinymail because tinymail does not check the
2251  * name of the virtual folders that the account could have in the case
2252  * that we're doing a rename (because it directly calls Camel which
2253  * knows nothing about our virtual folders). 
2254  *
2255  * In the case of an actual copy/move (i.e. move/copy a folder between
2256  * accounts) tinymail uses the tny_folder_store_create_account which
2257  * is reimplemented by our ModestTnyLocalFoldersAccount that indeed
2258  * checks the new name of the folder, so this call in that case
2259  * wouldn't be needed. *But* NOTE that if tinymail changes its
2260  * implementation (if folder transfers within the same account is no
2261  * longer implemented as a rename) this call will allow Modest to work
2262  * perfectly
2263  *
2264  * If the new name is not valid, this function will set the status to
2265  * failed and will set also an error in the mail operation
2266  */
2267 static gboolean
2268 new_name_valid_if_local_account (ModestMailOperationPrivate *priv,
2269                                  TnyFolderStore *into,
2270                                  const gchar *new_name)
2271 {
2272         if (TNY_IS_ACCOUNT (into) && 
2273             modest_tny_account_is_virtual_local_folders (TNY_ACCOUNT (into)) &&
2274             modest_tny_local_folders_account_folder_name_in_use (MODEST_TNY_LOCAL_FOLDERS_ACCOUNT (into),
2275                                                                  new_name)) {
2276                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2277                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2278                              MODEST_MAIL_OPERATION_ERROR_FOLDER_EXISTS,
2279                              _CS("ckdg_ib_folder_already_exists"));
2280                 return FALSE;
2281         } else
2282                 return TRUE;
2283 }
2284
2285 void
2286 modest_mail_operation_xfer_folder (ModestMailOperation *self,
2287                                    TnyFolder *folder,
2288                                    TnyFolderStore *parent,
2289                                    gboolean delete_original,
2290                                    XferFolderAsyncUserCallback user_callback,
2291                                    gpointer user_data)
2292 {
2293         ModestMailOperationPrivate *priv = NULL;
2294         ModestTnyFolderRules parent_rules = 0, rules; 
2295         XFerFolderAsyncHelper *helper = NULL;
2296         const gchar *folder_name = NULL;
2297         const gchar *error_msg;
2298
2299         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2300         g_return_if_fail (TNY_IS_FOLDER (folder));
2301         g_return_if_fail (TNY_IS_FOLDER_STORE (parent));
2302
2303         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2304         folder_name = tny_folder_get_name (folder);
2305
2306         /* Set the error msg */
2307         error_msg = _("mail_in_ui_folder_move_target_error");
2308
2309         /* Get account and set it into mail_operation */
2310         priv->op_type = MODEST_MAIL_OPERATION_TYPE_RECEIVE;
2311         priv->account = modest_tny_folder_get_account (TNY_FOLDER(folder));
2312         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
2313
2314         /* Get folder rules */
2315         rules = modest_tny_folder_get_rules (TNY_FOLDER (folder));
2316         if (TNY_IS_FOLDER (parent))
2317                 parent_rules = modest_tny_folder_get_rules (TNY_FOLDER (parent));
2318         
2319         /* Apply operation constraints */
2320         if ((gpointer) parent == (gpointer) folder ||
2321             (!TNY_IS_FOLDER_STORE (parent)) || 
2322             (rules & MODEST_FOLDER_RULES_FOLDER_NON_MOVEABLE)) {
2323                 /* Folder rules */
2324                 goto error;
2325         } else if (TNY_IS_FOLDER (parent) && 
2326                    (parent_rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE)) {
2327                 /* Folder rules */
2328                 goto error;
2329
2330         } else if (TNY_IS_FOLDER (parent) &&
2331                    TNY_IS_FOLDER_STORE (folder) &&
2332                    modest_tny_folder_is_ancestor (TNY_FOLDER (parent), 
2333                                                   TNY_FOLDER_STORE (folder))) {
2334                 /* Do not move a parent into a child */
2335                 goto error;
2336         } else if (TNY_IS_FOLDER_STORE (parent) &&
2337                    modest_tny_folder_has_subfolder_with_name (parent, folder_name, TRUE)) {
2338                 /* Check that the new folder name is not used by any
2339                    parent subfolder */
2340                 goto error;     
2341         } else if (!(new_name_valid_if_local_account (priv, parent, folder_name))) {
2342                 /* Check that the new folder name is not used by any
2343                    special local folder */
2344                 goto error;
2345         } else {
2346                 /* Create the helper */
2347                 helper = g_slice_new0 (XFerFolderAsyncHelper);
2348                 helper->mail_op = g_object_ref (self);
2349                 helper->user_callback = user_callback;
2350                 helper->user_data = user_data;
2351                 
2352                 /* Move/Copy folder */
2353                 modest_mail_operation_notify_start (self);
2354                 tny_folder_copy_async (folder,
2355                                        parent,
2356                                        tny_folder_get_name (folder),
2357                                        delete_original,
2358                                        transfer_folder_cb,
2359                                        transfer_folder_status_cb,
2360                                        helper);
2361                 return;
2362         }
2363
2364  error:
2365         /* Set status failed and set an error */
2366         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2367         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2368                      MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
2369                      error_msg);
2370
2371         /* Call the user callback if exists */
2372         if (user_callback)
2373                 user_callback (self, NULL, user_data);
2374
2375         /* Notify the queue */
2376         modest_mail_operation_notify_end (self);
2377 }
2378
2379 void
2380 modest_mail_operation_rename_folder (ModestMailOperation *self,
2381                                      TnyFolder *folder,
2382                                      const gchar *name,
2383                                      XferFolderAsyncUserCallback user_callback,
2384                                      gpointer user_data)
2385 {
2386         ModestMailOperationPrivate *priv;
2387         ModestTnyFolderRules rules;
2388         XFerFolderAsyncHelper *helper;
2389
2390         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2391         g_return_if_fail (TNY_IS_FOLDER_STORE (folder));
2392         g_return_if_fail (name);
2393         
2394         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2395
2396         /* Get account and set it into mail_operation */
2397         priv->account = modest_tny_folder_get_account (TNY_FOLDER(folder));
2398         priv->op_type = MODEST_MAIL_OPERATION_TYPE_INFO;
2399
2400         /* Check folder rules */
2401         rules = modest_tny_folder_get_rules (TNY_FOLDER (folder));
2402         if (rules & MODEST_FOLDER_RULES_FOLDER_NON_RENAMEABLE) {
2403                 goto error;
2404         } else if (!strcmp (name, " ") || strchr (name, '/')) {
2405                 goto error;
2406         } else {
2407                 TnyFolderStore *into;
2408
2409                 into = tny_folder_get_folder_store (folder);    
2410
2411                 /* Check that the new folder name is not used by any
2412                    special local folder */
2413                 if (new_name_valid_if_local_account (priv, into, name)) {
2414                         /* Create the helper */
2415                         helper = g_slice_new0 (XFerFolderAsyncHelper);
2416                         helper->mail_op = g_object_ref(self);
2417                         helper->user_callback = user_callback;
2418                         helper->user_data = user_data;
2419                 
2420                         /* Rename. Camel handles folder subscription/unsubscription */
2421                         modest_mail_operation_notify_start (self);
2422                         tny_folder_copy_async (folder, into, name, TRUE,
2423                                                transfer_folder_cb,
2424                                                transfer_folder_status_cb,
2425                                                helper);
2426                         g_object_unref (into);
2427                 } else {
2428                         g_object_unref (into);
2429                         goto error;
2430                 }
2431
2432                 return;
2433         }
2434  error:
2435         /* Set status failed and set an error */
2436         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
2437         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2438                      MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
2439                      _("FIXME: unable to rename"));
2440         
2441         if (user_callback)
2442                 user_callback (self, NULL, user_data);
2443
2444         /* Notify about operation end */
2445         modest_mail_operation_notify_end (self);
2446 }
2447
2448 /* ******************************************************************* */
2449 /* **************************  MSG  ACTIONS  ************************* */
2450 /* ******************************************************************* */
2451
2452 void 
2453 modest_mail_operation_get_msg (ModestMailOperation *self,
2454                                TnyHeader *header,
2455                                gboolean progress_feedback,
2456                                GetMsgAsyncUserCallback user_callback,
2457                                gpointer user_data)
2458 {
2459         GetMsgInfo *helper = NULL;
2460         TnyFolder *folder;
2461         ModestMailOperationPrivate *priv;
2462         
2463         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2464         g_return_if_fail (TNY_IS_HEADER (header));
2465         
2466         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2467         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
2468         priv->total = 1;
2469         priv->done = 0;
2470
2471         /* Check memory low */
2472         if (_check_memory_low (self)) {
2473                 if (user_callback)
2474                         user_callback (self, header, FALSE, NULL, priv->error, user_data);
2475                 modest_mail_operation_notify_end (self);
2476                 return;
2477         }
2478
2479         /* Get account and set it into mail_operation */
2480         folder = tny_header_get_folder (header);
2481         priv->account = modest_tny_folder_get_account (TNY_FOLDER(folder));
2482         
2483         /* Check for cached messages */
2484         if (progress_feedback) {
2485                 if (tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED)
2486                         priv->op_type = MODEST_MAIL_OPERATION_TYPE_OPEN;
2487                 else 
2488                         priv->op_type = MODEST_MAIL_OPERATION_TYPE_RECEIVE;
2489         } else {
2490                 priv->op_type = MODEST_MAIL_OPERATION_TYPE_UNKNOWN;
2491         }
2492         
2493         /* Create the helper */
2494         helper = g_slice_new0 (GetMsgInfo);
2495         helper->header = g_object_ref (header);
2496         helper->mail_op = g_object_ref (self);
2497         helper->user_callback = user_callback;
2498         helper->user_data = user_data;
2499         helper->destroy_notify = NULL;
2500         helper->last_total_bytes = 0;
2501         helper->sum_total_bytes = 0;
2502         helper->total_bytes = tny_header_get_message_size (header);
2503         helper->more_msgs = NULL;
2504
2505         modest_mail_operation_notify_start (self);
2506         
2507         /* notify about the start of the operation */ 
2508         ModestMailOperationState *state;
2509         state = modest_mail_operation_clone_state (self);
2510         state->done = 0;
2511         state->total = 0;
2512         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 
2513                                 0, state, NULL);
2514         g_slice_free (ModestMailOperationState, state);
2515         
2516         tny_folder_get_msg_async (folder, header, get_msg_async_cb, get_msg_status_cb, helper);
2517
2518         g_object_unref (G_OBJECT (folder));
2519 }
2520
2521 static void     
2522 get_msg_status_cb (GObject *obj,
2523                    TnyStatus *status,  
2524                    gpointer user_data)
2525 {
2526         GetMsgInfo *helper = NULL;
2527
2528         g_return_if_fail (status != NULL);
2529
2530         /* Show only the status information we want */
2531         if (status->code != TNY_FOLDER_STATUS_CODE_GET_MSG)
2532                 return;
2533
2534         helper = (GetMsgInfo *) user_data;
2535         g_return_if_fail (helper != NULL);       
2536
2537         /* Notify progress */
2538         notify_progress_of_multiple_messages (helper->mail_op, status, &(helper->last_total_bytes), 
2539                                               &(helper->sum_total_bytes), helper->total_bytes, FALSE);
2540 }
2541
2542 static void
2543 get_msg_async_cb (TnyFolder *folder, 
2544                   gboolean canceled, 
2545                   TnyMsg *msg, 
2546                   GError *err, 
2547                   gpointer user_data)
2548 {
2549         GetMsgInfo *info = NULL;
2550         ModestMailOperationPrivate *priv = NULL;
2551         gboolean finished;
2552
2553         info = (GetMsgInfo *) user_data;
2554
2555         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (info->mail_op);
2556         priv->done++;
2557
2558         if (info->more_msgs) {
2559                 tny_iterator_next (info->more_msgs);
2560                 finished = (tny_iterator_is_done (info->more_msgs));
2561         } else {
2562                 finished = (priv->done == priv->total) ? TRUE : FALSE;
2563         }
2564
2565         /* If canceled by the user, ignore the error given by Tinymail */
2566         if (canceled) {
2567                 canceled = TRUE;
2568                 finished = TRUE;
2569                 priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
2570         } else if (err) {
2571                 priv->status = MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS;
2572                 if (err) {
2573                         priv->error = g_error_copy ((const GError *) err);
2574                         priv->error->domain = MODEST_MAIL_OPERATION_ERROR;
2575                 }
2576                 if (!priv->error) {
2577                         g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
2578                                      MODEST_MAIL_OPERATION_ERROR_ITEM_NOT_FOUND,
2579                                      err->message);
2580                 }
2581         } else if (finished && priv->status == MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS) {
2582                 /* Set the success status before calling the user callback */
2583                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
2584         }
2585
2586
2587         /* Call the user callback */
2588         if (info->user_callback)
2589                 info->user_callback (info->mail_op, info->header, canceled, 
2590                                      msg, err, info->user_data);
2591
2592         /* Notify about operation end if this is the last callback */
2593         if (finished) {
2594                 /* Free user data */
2595                 if (info->destroy_notify)
2596                         info->destroy_notify (info->user_data);
2597
2598                 /* Notify about operation end */
2599                 modest_mail_operation_notify_end (info->mail_op);
2600
2601                 /* Clean */
2602                 if (info->more_msgs)
2603                         g_object_unref (info->more_msgs);
2604                 g_object_unref (info->header);
2605                 g_object_unref (info->mail_op);
2606                 g_slice_free (GetMsgInfo, info);
2607         } else if (info->more_msgs) {
2608                 TnyHeader *header = TNY_HEADER (tny_iterator_get_current (info->more_msgs));
2609                 TnyFolder *folder = tny_header_get_folder (header);
2610
2611                 g_object_unref (info->header);
2612                 info->header = g_object_ref (header);
2613
2614                 /* Retrieve the next message */
2615                 tny_folder_get_msg_async (folder, header, get_msg_async_cb, get_msg_status_cb, info);
2616
2617                 g_object_unref (header);
2618                 g_object_unref (folder);
2619         } else {
2620                 g_warning ("%s: finished != TRUE but no messages left", __FUNCTION__);
2621         }
2622 }
2623
2624 void 
2625 modest_mail_operation_get_msgs_full (ModestMailOperation *self,
2626                                      TnyList *header_list, 
2627                                      GetMsgAsyncUserCallback user_callback,
2628                                      gpointer user_data,
2629                                      GDestroyNotify notify)
2630 {
2631         ModestMailOperationPrivate *priv = NULL;
2632         gint msg_list_size;
2633         TnyIterator *iter = NULL;
2634         gboolean has_uncached_messages;
2635         
2636         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2637
2638         /* Init mail operation */
2639         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2640         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
2641         priv->done = 0;
2642         priv->total = tny_list_get_length(header_list);
2643
2644         /* Check memory low */
2645         if (_check_memory_low (self)) {
2646                 if (user_callback) {
2647                         TnyHeader *header = NULL;
2648                         TnyIterator *iter;
2649
2650                         if (tny_list_get_length (header_list) > 0) {
2651                                 iter = tny_list_create_iterator (header_list);
2652                                 header = (TnyHeader *) tny_iterator_get_current (iter);
2653                                 g_object_unref (iter);
2654                         }
2655                         user_callback (self, header, FALSE, NULL, priv->error, user_data);
2656                         if (header)
2657                                 g_object_unref (header);
2658                 }
2659                 if (notify)
2660                         notify (user_data);
2661                 /* Notify about operation end */
2662                 modest_mail_operation_notify_end (self);
2663                 return;
2664         }
2665
2666         /* Check uncached messages */
2667         for (iter = tny_list_create_iterator (header_list), has_uncached_messages = FALSE;
2668              !has_uncached_messages && !tny_iterator_is_done (iter); 
2669              tny_iterator_next (iter)) {
2670                 TnyHeader *header;
2671
2672                 header = (TnyHeader *) tny_iterator_get_current (iter);
2673                 if (!(tny_header_get_flags (header) & TNY_HEADER_FLAG_CACHED))
2674                         has_uncached_messages = TRUE;
2675                 g_object_unref (header);
2676         }       
2677         g_object_unref (iter);
2678         priv->op_type = has_uncached_messages?MODEST_MAIL_OPERATION_TYPE_RECEIVE:MODEST_MAIL_OPERATION_TYPE_OPEN;
2679
2680         /* Get account and set it into mail_operation */
2681         if (tny_list_get_length (header_list) >= 1) {
2682                 TnyIterator *iterator = tny_list_create_iterator (header_list);
2683                 TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iterator));
2684                 if (header) {
2685                         TnyFolder *folder = tny_header_get_folder (header);
2686                         if (folder) {           
2687                                 priv->account = modest_tny_folder_get_account (TNY_FOLDER(folder));
2688                                 g_object_unref (folder);
2689                         }
2690                         g_object_unref (header);
2691                 }
2692                 g_object_unref (iterator);
2693         }
2694
2695         msg_list_size = compute_message_list_size (header_list, 0);
2696
2697         modest_mail_operation_notify_start (self);
2698         iter = tny_list_create_iterator (header_list);
2699         if (!tny_iterator_is_done (iter)) {
2700                 /* notify about the start of the operation */
2701                 ModestMailOperationState *state;
2702                 state = modest_mail_operation_clone_state (self);
2703                 state->done = 0;
2704                 state->total = 0;
2705                 g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL],
2706                                0, state, NULL);
2707
2708                 GetMsgInfo *msg_info = NULL;
2709                 TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iter));
2710                 TnyFolder *folder = tny_header_get_folder (header);
2711
2712                 /* Create the message info */
2713                 msg_info = g_slice_new0 (GetMsgInfo);
2714                 msg_info->mail_op = g_object_ref (self);
2715                 msg_info->header = g_object_ref (header);
2716                 msg_info->more_msgs = g_object_ref (iter);
2717                 msg_info->user_callback = user_callback;
2718                 msg_info->user_data = user_data;
2719                 msg_info->destroy_notify = notify;
2720                 msg_info->last_total_bytes = 0;
2721                 msg_info->sum_total_bytes = 0;
2722                 msg_info->total_bytes = msg_list_size;
2723
2724                 /* The callback will call it per each header */
2725                 tny_folder_get_msg_async (folder, header, get_msg_async_cb, get_msg_status_cb, msg_info);
2726
2727                 /* Free and go on */
2728                 g_object_unref (header);
2729                 g_object_unref (folder);
2730                 g_slice_free (ModestMailOperationState, state);
2731         }
2732         g_object_unref (iter);
2733 }
2734
2735
2736 static void
2737 remove_msgs_async_cb (TnyFolder *folder, 
2738                       gboolean canceled, 
2739                       GError *err, 
2740                       gpointer user_data)
2741 {
2742         gboolean expunge, leave_on_server;
2743         const gchar *account_name;
2744         TnyAccount *account;
2745         ModestProtocolType account_proto = MODEST_PROTOCOL_REGISTRY_TYPE_INVALID;
2746         ModestMailOperation *self;
2747         ModestMailOperationPrivate *priv;
2748         ModestProtocolRegistry *protocol_registry;
2749
2750         self = (ModestMailOperation *) user_data;
2751         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2752         protocol_registry = modest_runtime_get_protocol_registry ();
2753
2754         if (canceled || err) {
2755                 /* If canceled by the user, ignore the error given by Tinymail */
2756                 if (canceled) {
2757                         priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
2758                 } else if (err) {
2759                         priv->status = MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS;
2760                         priv->error = g_error_copy ((const GError *) err);
2761                         priv->error->domain = MODEST_MAIL_OPERATION_ERROR;
2762                 }
2763                 /* Exit */
2764                 modest_mail_operation_notify_end (self);
2765                 g_object_unref (self);
2766                 return;
2767         }
2768
2769         account = tny_folder_get_account (folder);
2770         account_name = modest_tny_account_get_parent_modest_account_name_for_server_account (account);
2771         leave_on_server =
2772                 modest_account_mgr_get_leave_on_server (modest_runtime_get_account_mgr (),
2773                                                         account_name);  
2774         account_proto = modest_tny_account_get_protocol_type (account);
2775         g_object_unref (account);
2776
2777         if ((modest_protocol_registry_protocol_type_has_leave_on_server (protocol_registry, account_proto) && 
2778              !leave_on_server) ||
2779             !modest_tny_folder_is_remote_folder (folder))
2780                 expunge = TRUE;
2781         else
2782                 expunge = FALSE;
2783
2784         /* Sync folder */
2785         tny_folder_sync_async(folder, expunge, sync_folder_finish_callback, 
2786                               NULL, self);
2787 }
2788
2789 void 
2790 modest_mail_operation_remove_msgs (ModestMailOperation *self,  
2791                                    TnyList *headers,
2792                                    gboolean remove_to_trash /*ignored*/)
2793 {
2794         TnyFolder *folder = NULL;
2795         ModestMailOperationPrivate *priv;
2796         TnyIterator *iter = NULL;
2797         TnyHeader *header = NULL;
2798         TnyList *remove_headers = NULL;
2799         TnyFolderType folder_type = TNY_FOLDER_TYPE_UNKNOWN;
2800
2801         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
2802         g_return_if_fail (TNY_IS_LIST (headers));
2803
2804         if (remove_to_trash)
2805                 g_warning ("remove to trash is not implemented");
2806
2807         if (tny_list_get_length(headers) == 0) {
2808                 g_warning ("%s: list of headers is empty\n", __FUNCTION__);
2809                 goto cleanup; /* nothing to do */
2810         }
2811         
2812         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2813         remove_headers = g_object_ref(headers);
2814
2815         /* Get folder from first header and sync it */
2816         iter = tny_list_create_iterator (headers);      
2817         header = TNY_HEADER (tny_iterator_get_current (iter));
2818
2819         folder = tny_header_get_folder (header);        
2820         if (!TNY_IS_FOLDER(folder)) {
2821                 g_warning ("%s: could not get folder for header\n", __FUNCTION__);
2822                 goto cleanup;
2823         }
2824
2825         /* Don't remove messages that are being sent */
2826         if (modest_tny_folder_is_local_folder (folder)) {
2827                 folder_type = modest_tny_folder_get_local_or_mmc_folder_type (folder);
2828         }
2829         if (folder_type == TNY_FOLDER_TYPE_OUTBOX) {
2830                 TnyTransportAccount *traccount = NULL;
2831                 ModestTnyAccountStore *accstore = modest_runtime_get_account_store();
2832                 traccount = modest_tny_account_store_get_transport_account_from_outbox_header(accstore, header);
2833                 if (traccount) {
2834                         ModestTnySendQueueStatus status;
2835                         ModestTnySendQueue *send_queue = modest_runtime_get_send_queue(traccount, TRUE);
2836
2837                         if (TNY_IS_SEND_QUEUE (send_queue)) {
2838                                 TnyIterator *iter = tny_list_create_iterator(headers);
2839                                 g_object_unref(remove_headers);
2840                                 remove_headers = TNY_LIST(tny_simple_list_new());
2841                                 while (!tny_iterator_is_done(iter)) {
2842                                         char *msg_id;
2843                                         TnyHeader *hdr = TNY_HEADER(tny_iterator_get_current(iter));
2844                                         msg_id = modest_tny_send_queue_get_msg_id (hdr);
2845                                         status = modest_tny_send_queue_get_msg_status(send_queue, msg_id);
2846                                         if (status != MODEST_TNY_SEND_QUEUE_SENDING) {
2847                                                 tny_list_append(remove_headers, G_OBJECT(hdr));
2848                                         }
2849                                         g_object_unref(hdr);
2850                                         g_free(msg_id);
2851                                         tny_iterator_next(iter);
2852                                 }
2853                                 g_object_unref(iter);
2854                         }
2855                         g_object_unref(traccount);
2856                 }
2857         }
2858
2859         /* Get account and set it into mail_operation */
2860         priv->account = modest_tny_folder_get_account (TNY_FOLDER(folder));
2861         priv->op_type = MODEST_MAIL_OPERATION_TYPE_DELETE;
2862         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
2863
2864         /* remove message from folder */
2865         modest_mail_operation_notify_start (self);
2866         tny_folder_remove_msgs_async (folder, remove_headers, remove_msgs_async_cb, 
2867                                       NULL, g_object_ref (self));
2868
2869 cleanup:
2870         if (remove_headers)
2871                 g_object_unref (remove_headers);
2872         if (header)
2873                 g_object_unref (header);
2874         if (iter)
2875                 g_object_unref (iter);
2876         if (folder)
2877                 g_object_unref (folder);
2878 }
2879
2880 static void
2881 notify_progress_of_multiple_messages (ModestMailOperation *self,
2882                                       TnyStatus *status,
2883                                       gint *last_total_bytes,
2884                                       gint *sum_total_bytes,
2885                                       gint total_bytes, 
2886                                       gboolean increment_done)
2887 {
2888         ModestMailOperationPrivate *priv;
2889         ModestMailOperationState *state;
2890         gboolean is_num_bytes = FALSE;
2891
2892         priv =  MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2893
2894         /* We know that tinymail sends us information about
2895          *  transferred bytes with this particular message
2896          */
2897         if (status->message)
2898                 is_num_bytes = (g_ascii_strcasecmp (status->message, "Retrieving message") == 0);
2899
2900         state = modest_mail_operation_clone_state (self);
2901         if (is_num_bytes && !((status->position == 1) && (status->of_total == 100))) {
2902                 /* We know that we're in a different message when the
2903                    total number of bytes to transfer is different. Of
2904                    course it could fail if we're transferring messages
2905                    of the same size, but this is a workarround */
2906                 if (status->of_total != *last_total_bytes) {
2907                         /* We need to increment the done when there is
2908                            no information about each individual
2909                            message, we need to do this in message
2910                            transfers, and we don't do it for getting
2911                            messages */
2912                         if (increment_done)
2913                                 priv->done++;
2914                         *sum_total_bytes += *last_total_bytes;
2915                         *last_total_bytes = status->of_total;
2916                 }
2917                 state->bytes_done += status->position + *sum_total_bytes;
2918                 state->bytes_total = total_bytes;
2919
2920                 /* Notify the status change. Only notify about changes
2921                    referred to bytes */
2922                 g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 
2923                                0, state, NULL);
2924         }
2925
2926         g_slice_free (ModestMailOperationState, state);
2927 }
2928
2929 static void
2930 transfer_msgs_status_cb (GObject *obj,
2931                          TnyStatus *status,  
2932                          gpointer user_data)
2933 {
2934         XFerMsgsAsyncHelper *helper;
2935
2936         g_return_if_fail (status != NULL);
2937
2938         /* Show only the status information we want */
2939         if (status->code != TNY_FOLDER_STATUS_CODE_XFER_MSGS)
2940                 return;
2941
2942         helper = (XFerMsgsAsyncHelper *) user_data;
2943         g_return_if_fail (helper != NULL);       
2944
2945         /* Notify progress */
2946         notify_progress_of_multiple_messages (helper->mail_op, status, &(helper->last_total_bytes), 
2947                                               &(helper->sum_total_bytes), helper->total_bytes, TRUE);
2948 }
2949
2950 static void
2951 transfer_msgs_sync_folder_cb (TnyFolder *self, 
2952                               gboolean cancelled, 
2953                               GError *err, 
2954                               gpointer user_data)
2955 {
2956         XFerMsgsAsyncHelper *helper;
2957         /* We don't care here about the results of the
2958            synchronization */
2959         helper = (XFerMsgsAsyncHelper *) user_data;
2960
2961         /* Notify about operation end */
2962         modest_mail_operation_notify_end (helper->mail_op);
2963
2964         /* If user defined callback function was defined, call it */
2965         if (helper->user_callback)
2966                 helper->user_callback (helper->mail_op, helper->user_data);
2967         
2968         /* Free */
2969         if (helper->more_msgs)
2970                 g_object_unref (helper->more_msgs);
2971         if (helper->headers)
2972                 g_object_unref (helper->headers);
2973         if (helper->dest_folder)
2974                 g_object_unref (helper->dest_folder);
2975         if (helper->mail_op)
2976                 g_object_unref (helper->mail_op);
2977         g_slice_free (XFerMsgsAsyncHelper, helper);
2978 }
2979
2980 static void
2981 transfer_msgs_cb (TnyFolder *folder, gboolean cancelled, GError *err, gpointer user_data)
2982 {
2983         XFerMsgsAsyncHelper *helper;
2984         ModestMailOperation *self;
2985         ModestMailOperationPrivate *priv;
2986         gboolean finished = TRUE;
2987
2988         helper = (XFerMsgsAsyncHelper *) user_data;
2989         self = helper->mail_op;
2990
2991         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
2992
2993         if (cancelled) {
2994                 priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
2995         } else if (err) {
2996                 priv->error = g_error_copy (err);
2997                 priv->done = 0;
2998                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;     
2999         } else if (priv->status != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
3000                 if (helper->more_msgs) {
3001                         /* We'll transfer the next message in the list */
3002                         tny_iterator_next (helper->more_msgs);
3003                         if (!tny_iterator_is_done (helper->more_msgs)) {
3004                                 GObject *next_header;
3005                                 g_object_unref (helper->headers);
3006                                 helper->headers = tny_simple_list_new ();
3007                                 next_header = tny_iterator_get_current (helper->more_msgs);
3008                                 tny_list_append (helper->headers, next_header);
3009                                 g_object_unref (next_header);
3010                                 finished = FALSE;
3011                         }
3012                 }
3013                 if (finished) {
3014                         priv->done = 1;
3015                         priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
3016                 }
3017         }
3018
3019         if (finished) {
3020                 /* Synchronize the source folder contents. This should
3021                    be done by tinymail but the camel_folder_sync it's
3022                    actually disabled in transfer_msgs_thread_clean
3023                    because it's supposed to cause hangs */
3024                 tny_folder_sync_async (folder, helper->delete, 
3025                                        transfer_msgs_sync_folder_cb, 
3026                                        NULL, helper);
3027         } else {
3028                 /* Transfer more messages */
3029                 tny_folder_transfer_msgs_async (folder,
3030                                                 helper->headers,
3031                                                 helper->dest_folder,
3032                                                 helper->delete,
3033                                                 transfer_msgs_cb,
3034                                                 transfer_msgs_status_cb,
3035                                                 helper);
3036         }
3037 }
3038
3039 /* Computes the size of the messages the headers in the list belongs
3040    to. If num_elements is different from 0 then it only takes into
3041    account the first num_elements for the calculation */
3042 static guint
3043 compute_message_list_size (TnyList *headers, 
3044                            guint num_elements)
3045 {
3046         TnyIterator *iter;
3047         guint size = 0, element = 0;
3048
3049         /* If num_elements is not valid then take all into account */
3050         if ((num_elements <= 0) || (num_elements > tny_list_get_length (headers)))
3051                 num_elements = tny_list_get_length (headers);
3052
3053         iter = tny_list_create_iterator (headers);
3054         while (!tny_iterator_is_done (iter) && element < num_elements) {
3055                 TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iter));
3056                 size += tny_header_get_message_size (header);
3057                 g_object_unref (header);
3058                 tny_iterator_next (iter);
3059                 element++;
3060         }
3061         g_object_unref (iter);
3062
3063         return size;
3064 }
3065
3066 void
3067 modest_mail_operation_xfer_msgs (ModestMailOperation *self,
3068                                  TnyList *headers, 
3069                                  TnyFolder *folder, 
3070                                  gboolean delete_original,
3071                                  XferMsgsAsyncUserCallback user_callback,
3072                                  gpointer user_data)
3073 {
3074         ModestMailOperationPrivate *priv = NULL;
3075         TnyIterator *iter = NULL;
3076         TnyFolder *src_folder = NULL;
3077         XFerMsgsAsyncHelper *helper = NULL;
3078         TnyHeader *header = NULL;
3079         ModestTnyFolderRules rules = 0;
3080         TnyAccount *dst_account = NULL;
3081         gboolean leave_on_server;
3082         ModestMailOperationState *state;
3083         ModestProtocolRegistry *protocol_registry;
3084         ModestProtocolType account_protocol;
3085
3086         g_return_if_fail (self && MODEST_IS_MAIL_OPERATION (self));
3087         g_return_if_fail (headers && TNY_IS_LIST (headers));
3088         g_return_if_fail (folder && TNY_IS_FOLDER (folder));
3089
3090         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3091         protocol_registry = modest_runtime_get_protocol_registry ();
3092
3093         priv->total = tny_list_get_length (headers);
3094         priv->done = 0;
3095         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
3096         priv->op_type = MODEST_MAIL_OPERATION_TYPE_RECEIVE;
3097
3098         /* Apply folder rules */
3099         rules = modest_tny_folder_get_rules (TNY_FOLDER (folder));
3100         if (rules & MODEST_FOLDER_RULES_FOLDER_NON_WRITEABLE) {
3101                 /* Set status failed and set an error */
3102                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
3103                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
3104                              MODEST_MAIL_OPERATION_ERROR_FOLDER_RULES,
3105                              _CS("ckct_ib_unable_to_paste_here"));
3106                 /* Notify the queue */
3107                 modest_mail_operation_notify_end (self);
3108                 return;
3109         }
3110                 
3111         /* Get source folder */
3112         iter = tny_list_create_iterator (headers);
3113         header = TNY_HEADER (tny_iterator_get_current (iter));
3114         if (header) {
3115                 src_folder = tny_header_get_folder (header);
3116                 g_object_unref (header);
3117         }
3118         g_object_unref (iter);
3119
3120         if (src_folder == NULL) {
3121                 /* Notify the queue */
3122                 modest_mail_operation_notify_end (self);
3123
3124                 g_warning ("%s: cannot find folder from header", __FUNCTION__);
3125                 return;
3126         }
3127
3128         
3129         /* Check folder source and destination */
3130         if (src_folder == folder) {
3131                 /* Set status failed and set an error */
3132                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
3133                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
3134                              MODEST_MAIL_OPERATION_ERROR_BAD_PARAMETER,
3135                              _("mail_in_ui_folder_copy_target_error"));
3136                 
3137                 /* Notify the queue */
3138                 modest_mail_operation_notify_end (self);
3139                 
3140                 /* Free */
3141                 g_object_unref (src_folder);            
3142                 return;
3143         }
3144
3145         /* Create the helper */
3146         helper = g_slice_new0 (XFerMsgsAsyncHelper);
3147         helper->mail_op = g_object_ref(self);
3148         helper->dest_folder = g_object_ref(folder);
3149         helper->user_callback = user_callback;
3150         helper->user_data = user_data;
3151         helper->last_total_bytes = 0;
3152         helper->sum_total_bytes = 0;
3153         helper->total_bytes = compute_message_list_size (headers, 0);
3154
3155         /* Get account and set it into mail_operation */
3156         priv->account = modest_tny_folder_get_account (src_folder);
3157         dst_account = modest_tny_folder_get_account (folder);
3158
3159         if (priv->account == dst_account) {
3160                 /* Transfer all messages at once using the fast
3161                  * method. Note that depending on the server this
3162                  * might not be that fast, and might not be
3163                  * user-cancellable either */
3164                 helper->headers = g_object_ref (headers);
3165                 helper->more_msgs = NULL;
3166         } else {
3167                 /* Transfer messages one by one so the user can cancel
3168                  * the operation */
3169                 GObject *hdr;
3170                 helper->headers = tny_simple_list_new ();
3171                 helper->more_msgs = tny_list_create_iterator (headers);
3172                 hdr = tny_iterator_get_current (helper->more_msgs);
3173                 tny_list_append (helper->headers, hdr);
3174                 g_object_unref (hdr);
3175         }
3176
3177         /* If leave_on_server is set to TRUE then don't use
3178            delete_original, we always pass FALSE. This is because
3179            otherwise tinymail will try to sync the source folder and
3180            this could cause an error if we're offline while
3181            transferring an already downloaded message from a POP
3182            account */
3183         account_protocol = modest_tny_account_get_protocol_type (priv->account);
3184         if (modest_protocol_registry_protocol_type_has_leave_on_server (protocol_registry, account_protocol)) {
3185                 const gchar *account_name;
3186
3187                 account_name = modest_tny_account_get_parent_modest_account_name_for_server_account (priv->account);
3188                 leave_on_server = modest_account_mgr_get_leave_on_server (modest_runtime_get_account_mgr (),
3189                                                                           account_name);
3190         } else {
3191                 leave_on_server = FALSE;
3192         }
3193
3194         /* Do not delete messages if leave on server is TRUE */
3195         helper->delete = (leave_on_server) ? FALSE : delete_original;
3196
3197         modest_mail_operation_notify_start (self);
3198
3199         /* Start notifying progress */
3200         state = modest_mail_operation_clone_state (self);
3201         state->done = 0;
3202         state->total = 0;
3203         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 0, state, NULL);
3204         g_slice_free (ModestMailOperationState, state);
3205
3206         tny_folder_transfer_msgs_async (src_folder, 
3207                                         helper->headers, 
3208                                         folder, 
3209                                         helper->delete, 
3210                                         transfer_msgs_cb, 
3211                                         transfer_msgs_status_cb,
3212                                         helper);
3213         g_object_unref (src_folder);
3214         g_object_unref (dst_account);
3215 }
3216
3217
3218 static void
3219 on_refresh_folder (TnyFolder   *folder, 
3220                    gboolean     cancelled, 
3221                    GError     *error,
3222                    gpointer     user_data)
3223 {
3224         RefreshAsyncHelper *helper = NULL;
3225         ModestMailOperation *self = NULL;
3226         ModestMailOperationPrivate *priv = NULL;
3227
3228         helper = (RefreshAsyncHelper *) user_data;
3229         self = helper->mail_op;
3230         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3231
3232         g_return_if_fail(priv!=NULL);
3233
3234         if (error) {
3235                 priv->error = g_error_copy (error);
3236                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
3237                 goto out;
3238         }
3239
3240         if (cancelled) {
3241                 priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
3242                 g_set_error (&(priv->error), MODEST_MAIL_OPERATION_ERROR,
3243                              MODEST_MAIL_OPERATION_ERROR_ITEM_NOT_FOUND,
3244                              _("Error trying to refresh the contents of %s"),
3245                              tny_folder_get_name (folder));
3246                 goto out;
3247         }
3248
3249         priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
3250  out:
3251
3252         /* Call user defined callback, if it exists */
3253         if (helper->user_callback) {
3254
3255                 /* This is not a GDK lock because we are a Tinymail callback and
3256                  * Tinymail already acquires the Gdk lock */
3257                 helper->user_callback (self, folder, helper->user_data);
3258         }
3259
3260         /* Free */
3261         g_slice_free (RefreshAsyncHelper, helper);
3262
3263         /* Notify about operation end */
3264         modest_mail_operation_notify_end (self);
3265         g_object_unref(self);
3266 }
3267
3268 static void
3269 on_refresh_folder_status_update (GObject *obj,
3270                                  TnyStatus *status,
3271                                  gpointer user_data)
3272 {
3273         RefreshAsyncHelper *helper = NULL;
3274         ModestMailOperation *self = NULL;
3275         ModestMailOperationPrivate *priv = NULL;
3276         ModestMailOperationState *state;
3277
3278         g_return_if_fail (user_data != NULL);
3279         g_return_if_fail (status != NULL);
3280
3281         /* Show only the status information we want */
3282         if (status->code != TNY_FOLDER_STATUS_CODE_REFRESH)
3283                 return;
3284
3285         helper = (RefreshAsyncHelper *) user_data;
3286         self = helper->mail_op;
3287         g_return_if_fail (MODEST_IS_MAIL_OPERATION(self));
3288
3289         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3290
3291         priv->done = status->position;
3292         priv->total = status->of_total;
3293
3294         state = modest_mail_operation_clone_state (self);
3295
3296         /* This is not a GDK lock because we are a Tinymail callback and
3297          * Tinymail already acquires the Gdk lock */
3298         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 0, state, NULL);
3299
3300         g_slice_free (ModestMailOperationState, state);
3301 }
3302
3303 void 
3304 modest_mail_operation_refresh_folder  (ModestMailOperation *self,
3305                                        TnyFolder *folder,
3306                                        RefreshAsyncUserCallback user_callback,
3307                                        gpointer user_data)
3308 {
3309         ModestMailOperationPrivate *priv = NULL;
3310         RefreshAsyncHelper *helper = NULL;
3311
3312         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3313
3314         /* Check memory low */
3315         if (_check_memory_low (self)) {
3316                 if (user_callback)
3317                         user_callback (self, folder, user_data);
3318                 /* Notify about operation end */
3319                 modest_mail_operation_notify_end (self);
3320                 return;
3321         }
3322
3323         /* Get account and set it into mail_operation */
3324         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
3325         priv->account = modest_tny_folder_get_account  (folder);
3326         priv->op_type = MODEST_MAIL_OPERATION_TYPE_RECEIVE;
3327
3328         /* Create the helper */
3329         helper = g_slice_new0 (RefreshAsyncHelper);
3330         helper->mail_op = g_object_ref(self);
3331         helper->user_callback = user_callback;
3332         helper->user_data = user_data;
3333
3334         modest_mail_operation_notify_start (self);
3335         
3336         /* notify that the operation was started */
3337         ModestMailOperationState *state;
3338         state = modest_mail_operation_clone_state (self);
3339         state->done = 0;
3340         state->total = 0;
3341         g_signal_emit (G_OBJECT (self), signals[PROGRESS_CHANGED_SIGNAL], 
3342                         0, state, NULL);
3343         g_slice_free (ModestMailOperationState, state);
3344         
3345         tny_folder_refresh_async (folder,
3346                                   on_refresh_folder,
3347                                   on_refresh_folder_status_update,
3348                                   helper);
3349 }
3350
3351 static void
3352 run_queue_notify_and_destroy (RunQueueHelper *helper,
3353                               ModestMailOperationStatus status)
3354 {
3355         ModestMailOperationPrivate *priv;
3356
3357         /* Disconnect */
3358         if (helper->error_handler &&
3359             g_signal_handler_is_connected (helper->queue, helper->error_handler))
3360                 g_signal_handler_disconnect (helper->queue, helper->error_handler);
3361         if (helper->start_handler &&
3362             g_signal_handler_is_connected (helper->queue, helper->start_handler))
3363                 g_signal_handler_disconnect (helper->queue, helper->start_handler);
3364         if (helper->stop_handler &&
3365             g_signal_handler_is_connected (helper->queue, helper->stop_handler))
3366                 g_signal_handler_disconnect (helper->queue, helper->stop_handler);
3367
3368         /* Set status */
3369         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (helper->self);
3370         priv->status = status;
3371
3372         /* Notify end */
3373         modest_mail_operation_notify_end (helper->self);
3374
3375         /* Free data */
3376         g_object_unref (helper->queue);
3377         g_object_unref (helper->self);
3378         g_slice_free (RunQueueHelper, helper);
3379 }
3380
3381 static void
3382 run_queue_stop (ModestTnySendQueue *queue,
3383                 gpointer user_data)
3384 {
3385         RunQueueHelper *helper;
3386
3387         g_debug ("%s sending queue stopped", __FUNCTION__);
3388
3389         helper = (RunQueueHelper *) user_data;
3390         run_queue_notify_and_destroy (helper, MODEST_MAIL_OPERATION_STATUS_SUCCESS);
3391 }
3392
3393 void
3394 modest_mail_operation_run_queue (ModestMailOperation *self,
3395                                  ModestTnySendQueue *queue)
3396 {
3397         ModestMailOperationPrivate *priv;
3398         RunQueueHelper *helper;
3399
3400         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
3401         g_return_if_fail (MODEST_IS_TNY_SEND_QUEUE (queue));
3402         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
3403
3404         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
3405         priv->account = TNY_ACCOUNT (tny_camel_send_queue_get_transport_account (TNY_CAMEL_SEND_QUEUE (queue)));
3406         priv->op_type = MODEST_MAIL_OPERATION_TYPE_RUN_QUEUE;
3407
3408         /* Create the helper */
3409         helper = g_slice_new0 (RunQueueHelper);
3410         helper->queue = g_object_ref (queue);
3411         helper->self = g_object_ref (self);
3412         helper->stop_handler = g_signal_connect (queue, "queue-stop", 
3413                                                  G_CALLBACK (run_queue_stop), 
3414                                                  helper);
3415
3416         /* Notify operation has started */
3417         modest_mail_operation_notify_start (self);
3418         g_debug ("%s, run queue started", __FUNCTION__);
3419 }
3420
3421 static void
3422 shutdown_callback (ModestTnyAccountStore *account_store, gpointer userdata)
3423 {
3424         ModestMailOperation *self = (ModestMailOperation *) userdata;
3425         ModestMailOperationPrivate *priv;
3426
3427         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
3428         g_return_if_fail (MODEST_IS_TNY_ACCOUNT_STORE (account_store));
3429         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
3430
3431         priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
3432
3433         modest_mail_operation_notify_end (self);
3434         g_object_unref (self);
3435 }
3436
3437 void
3438 modest_mail_operation_shutdown (ModestMailOperation *self, ModestTnyAccountStore *account_store)
3439 {
3440         ModestMailOperationPrivate *priv;
3441
3442         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
3443         g_return_if_fail (MODEST_IS_TNY_ACCOUNT_STORE (account_store));
3444         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
3445
3446         modest_mail_operation_queue_set_running_shutdown (modest_runtime_get_mail_operation_queue ());
3447
3448         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
3449         priv->account = NULL;
3450         priv->op_type = MODEST_MAIL_OPERATION_TYPE_SHUTDOWN;
3451
3452         modest_mail_operation_notify_start (self);
3453         g_object_ref (self);
3454         modest_tny_account_store_shutdown (account_store, shutdown_callback, self);
3455 }
3456
3457 static void
3458 sync_folder_finish_callback (TnyFolder *self, 
3459                              gboolean cancelled, 
3460                              GError *err, 
3461                              gpointer user_data)
3462
3463 {
3464         ModestMailOperation *mail_op;
3465         ModestMailOperationPrivate *priv;
3466
3467         mail_op = (ModestMailOperation *) user_data;
3468         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (mail_op);
3469
3470         /* If canceled by the user, ignore the error given by Tinymail */
3471         if (cancelled) {
3472                 priv->status = MODEST_MAIL_OPERATION_STATUS_CANCELED;
3473         } else if (err) {
3474                 /* If the operation was a sync then the status is
3475                    failed, but if it's part of another operation then
3476                    just set it as finished with errors */
3477                 if (priv->op_type == MODEST_MAIL_OPERATION_TYPE_SYNC_FOLDER)
3478                         priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
3479                 else
3480                         priv->status = MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS;
3481                 priv->error = g_error_copy ((const GError *) err);
3482                 priv->error->domain = MODEST_MAIL_OPERATION_ERROR;
3483         } else {
3484                 priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
3485         }
3486
3487         modest_mail_operation_notify_end (mail_op);
3488         g_object_unref (mail_op);
3489 }
3490
3491 void
3492 modest_mail_operation_sync_folder (ModestMailOperation *self,
3493                                    TnyFolder *folder, gboolean expunge)
3494 {
3495         ModestMailOperationPrivate *priv;
3496
3497         g_return_if_fail (MODEST_IS_MAIL_OPERATION (self));
3498         g_return_if_fail (TNY_IS_FOLDER (folder));
3499         priv = MODEST_MAIL_OPERATION_GET_PRIVATE (self);
3500
3501         priv->status = MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS;
3502         priv->account = modest_tny_folder_get_account (folder);
3503         priv->op_type = MODEST_MAIL_OPERATION_TYPE_SYNC_FOLDER;
3504
3505         modest_mail_operation_notify_start (self);
3506         g_object_ref (self);
3507         tny_folder_sync_async (folder, expunge, 
3508                                (TnyFolderCallback) sync_folder_finish_callback, 
3509                                NULL, self);
3510 }
3511
3512 static void
3513 modest_mail_operation_notify_start (ModestMailOperation *self)
3514 {
3515         ModestMailOperationPrivate *priv = NULL;
3516
3517         g_return_if_fail (self);
3518
3519         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3520
3521         /* Ensure that all the fields are filled correctly */
3522         g_return_if_fail (priv->op_type != MODEST_MAIL_OPERATION_TYPE_UNKNOWN);
3523
3524         /* Notify the observers about the mail operation. We do not
3525            wrapp this emission because we assume that this function is
3526            always called from within the main lock */
3527         g_signal_emit (G_OBJECT (self), signals[OPERATION_STARTED_SIGNAL], 0, NULL);
3528 }
3529
3530 /**
3531  *
3532  * It's used by the mail operation queue to notify the observers
3533  * attached to that signal that the operation finished. We need to use
3534  * that because tinymail does not give us the progress of a given
3535  * operation when it finishes (it directly calls the operation
3536  * callback).
3537  */
3538 static void
3539 modest_mail_operation_notify_end (ModestMailOperation *self)
3540 {
3541         ModestMailOperationPrivate *priv = NULL;
3542
3543         g_return_if_fail (self);
3544
3545         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3546
3547         /* Notify the observers about the mail operation end. We do
3548            not wrapp this emission because we assume that this
3549            function is always called from within the main lock */
3550         g_signal_emit (G_OBJECT (self), signals[OPERATION_FINISHED_SIGNAL], 0, NULL);
3551
3552         /* Remove the error user data */
3553         if (priv->error_checking_user_data && priv->error_checking_user_data_destroyer)
3554                 priv->error_checking_user_data_destroyer (priv->error_checking_user_data);
3555 }
3556
3557 TnyAccount *
3558 modest_mail_operation_get_account (ModestMailOperation *self)
3559 {
3560         ModestMailOperationPrivate *priv = NULL;
3561
3562         g_return_val_if_fail (self, NULL);
3563
3564         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3565
3566         return (priv->account) ? g_object_ref (priv->account) : NULL;
3567 }
3568
3569 void
3570 modest_mail_operation_noop (ModestMailOperation *self)
3571 {
3572         ModestMailOperationPrivate *priv = NULL;
3573
3574         g_return_if_fail (self);
3575
3576         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3577         priv->status = MODEST_MAIL_OPERATION_STATUS_SUCCESS;
3578         priv->op_type = MODEST_MAIL_OPERATION_TYPE_INFO;
3579         priv->done = 0;
3580         priv->total = 0;
3581
3582         /* This mail operation does nothing actually */
3583         modest_mail_operation_notify_start (self);
3584         modest_mail_operation_notify_end (self);
3585 }
3586
3587
3588 gchar*
3589 modest_mail_operation_to_string (ModestMailOperation *self)
3590 {
3591         const gchar *type, *status, *account_id;
3592         ModestMailOperationPrivate *priv = NULL;
3593         
3594         g_return_val_if_fail (self, NULL);
3595
3596         priv = MODEST_MAIL_OPERATION_GET_PRIVATE(self);
3597
3598         /* new operations don't have anything interesting */
3599         if (priv->op_type == MODEST_MAIL_OPERATION_TYPE_UNKNOWN)
3600                 return g_strdup_printf ("%p <new operation>", self);
3601         
3602         switch (priv->op_type) {
3603         case MODEST_MAIL_OPERATION_TYPE_SEND:    type= "SEND";    break;
3604         case MODEST_MAIL_OPERATION_TYPE_RECEIVE: type= "RECEIVE"; break;
3605         case MODEST_MAIL_OPERATION_TYPE_OPEN:    type= "OPEN";    break;
3606         case MODEST_MAIL_OPERATION_TYPE_DELETE:  type= "DELETE";  break;
3607         case MODEST_MAIL_OPERATION_TYPE_INFO:    type= "INFO";    break;
3608         case MODEST_MAIL_OPERATION_TYPE_RUN_QUEUE: type= "RUN-QUEUE"; break;
3609         case MODEST_MAIL_OPERATION_TYPE_SYNC_FOLDER: type= "SYNC-FOLDER"; break;
3610         case MODEST_MAIL_OPERATION_TYPE_UNKNOWN: type= "UNKNOWN"; break;
3611         default: type = "UNEXPECTED"; break;
3612         }
3613
3614         switch (priv->status) {
3615         case MODEST_MAIL_OPERATION_STATUS_INVALID:              status= "INVALID"; break;
3616         case MODEST_MAIL_OPERATION_STATUS_SUCCESS:              status= "SUCCESS"; break;
3617         case MODEST_MAIL_OPERATION_STATUS_FINISHED_WITH_ERRORS: status= "FINISHED-WITH-ERRORS"; break;
3618         case MODEST_MAIL_OPERATION_STATUS_FAILED:               status= "FAILED"; break;
3619         case MODEST_MAIL_OPERATION_STATUS_IN_PROGRESS:          status= "IN-PROGRESS"; break;
3620         case MODEST_MAIL_OPERATION_STATUS_CANCELED:             status= "CANCELLED"; break;
3621         default:                                                status= "UNEXPECTED"; break;
3622         } 
3623
3624         account_id = priv->account ? tny_account_get_id (priv->account) : "";
3625
3626         return g_strdup_printf ("%p \"%s\" (%s) [%s] {%d/%d} '%s'", self, account_id,type, status,
3627                                 priv->done, priv->total,
3628                                 priv->error && priv->error->message ? priv->error->message : "");
3629 }
3630
3631 /* 
3632  * Once the mail operations were objects this will be no longer
3633  * needed. I don't like it, but we need it for the moment
3634  */
3635 static gboolean
3636 _check_memory_low (ModestMailOperation *mail_op)
3637 {
3638         if (modest_platform_check_memory_low (NULL, FALSE)) {
3639                 ModestMailOperationPrivate *priv;
3640
3641                 priv = MODEST_MAIL_OPERATION_GET_PRIVATE (mail_op);
3642                 priv->status = MODEST_MAIL_OPERATION_STATUS_FAILED;
3643                 g_set_error (&(priv->error),
3644                              MODEST_MAIL_OPERATION_ERROR,
3645                              MODEST_MAIL_OPERATION_ERROR_LOW_MEMORY,
3646                              "Not enough memory to complete the operation");
3647                 return TRUE;
3648         } else {
3649                 return FALSE;
3650         }
3651 }