Bug #606181 - Accepting bad SSL certificate applies to any hostname
[platform/upstream/evolution-data-server.git] / camel / camel-imapx-server.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <time.h>
25 #include <errno.h>
26 #include <glib/gstdio.h>
27 #include <glib/gi18n-lib.h>
28
29 // fixme, use own type funcs
30 #include <ctype.h>
31
32 #include <nspr.h>
33 #include <prio.h>
34 #include <prerror.h>
35 #include <prerr.h>
36
37 #include "camel-imapx-server.h"
38
39 #include "camel-imapx-command.h"
40 #include "camel-imapx-folder.h"
41 #include "camel-imapx-job.h"
42 #include "camel-imapx-settings.h"
43 #include "camel-imapx-store.h"
44 #include "camel-imapx-stream.h"
45 #include "camel-imapx-summary.h"
46 #include "camel-imapx-utils.h"
47
48 #ifdef G_OS_WIN32
49 #include <winsock2.h>
50 #include <ws2tcpip.h>
51 #endif
52
53 #define c(...) camel_imapx_debug(command, __VA_ARGS__)
54 #define e(...) camel_imapx_debug(extra, __VA_ARGS__)
55
56 #define CIF(x) ((CamelIMAPXFolder *)x)
57
58 #define QUEUE_LOCK(x) (g_static_rec_mutex_lock(&(x)->queue_lock))
59 #define QUEUE_UNLOCK(x) (g_static_rec_mutex_unlock(&(x)->queue_lock))
60
61 #define IDLE_LOCK(x) (g_mutex_lock((x)->idle_lock))
62 #define IDLE_UNLOCK(x) (g_mutex_unlock((x)->idle_lock))
63
64 /* Try pipelining fetch requests, 'in bits' */
65 #define MULTI_SIZE (20480)
66
67 /* How many outstanding commands do we allow before we just queue them? */
68 #define MAX_COMMANDS (10)
69
70 #define MAX_COMMAND_LEN 1000
71
72 extern gint camel_application_is_exiting;
73
74 /* Job-specific structs */
75 typedef struct _GetMessageData GetMessageData;
76 typedef struct _RefreshInfoData RefreshInfoData;
77 typedef struct _SyncChangesData SyncChangesData;
78 typedef struct _AppendMessageData AppendMessageData;
79 typedef struct _CopyMessagesData CopyMessagesData;
80 typedef struct _ListData ListData;
81 typedef struct _ManageSubscriptionsData ManageSubscriptionsData;
82 typedef struct _RenameFolderData RenameFolderData;
83 typedef struct _CreateFolderData CreateFolderData;
84 typedef struct _DeleteFolderData DeleteFolderData;
85
86 struct _GetMessageData {
87         /* in: uid requested */
88         gchar *uid;
89         /* in/out: message content stream output */
90         CamelStream *stream;
91         /* working variables */
92         gsize body_offset;
93         gssize body_len;
94         gsize fetch_offset;
95         gsize size;
96         gboolean use_multi_fetch;
97 };
98
99 struct _RefreshInfoData {
100         /* array of refresh info's */
101         GArray *infos;
102         /* used for building uidset stuff */
103         gint index;
104         gint last_index;
105         gint fetch_msg_limit;
106         CamelFetchType fetch_type;
107         gboolean update_unseen;
108         struct _uidset_state uidset;
109         /* changes during refresh */
110         CamelFolderChangeInfo *changes;
111 };
112
113 struct _SyncChangesData {
114         CamelFolder *folder;
115         GPtrArray *changed_uids;
116         guint32 on_set;
117         guint32 off_set;
118         GArray *on_user; /* imapx_flag_change */
119         GArray *off_user;
120         gint unread_change;
121 };
122
123 struct _AppendMessageData {
124         gchar *path;
125         CamelMessageInfo *info;
126         gchar *appended_uid;
127 };
128
129 struct _CopyMessagesData {
130         CamelFolder *dest;
131         GPtrArray *uids;
132         gboolean delete_originals;
133         gint index;
134         gint last_index;
135         struct _uidset_state uidset;
136 };
137
138 struct _ListData {
139         gchar *pattern;
140         guint32 flags;
141         gchar *ext;
142         GHashTable *folders;
143 };
144
145 struct _ManageSubscriptionsData {
146         gchar *folder_name;
147         gboolean subscribe;
148 };
149
150 struct _RenameFolderData {
151         gchar *old_folder_name;
152         gchar *new_folder_name;
153 };
154
155 struct _CreateFolderData {
156         gchar *folder_name;
157 };
158
159 struct _DeleteFolderData {
160         gchar *folder_name;
161 };
162
163 enum {
164         SELECT_CHANGED,
165         SHUTDOWN,
166         LAST_SIGNAL
167 };
168
169 static guint signals[LAST_SIGNAL];
170
171 void imapx_uidset_init (struct _uidset_state *ss, gint total, gint limit);
172 gint imapx_uidset_done (struct _uidset_state *ss, struct _CamelIMAPXCommand *ic);
173 gint imapx_uidset_add (struct _uidset_state *ss, struct _CamelIMAPXCommand *ic, const gchar *uid);
174 static gboolean imapx_command_idle_stop (CamelIMAPXServer *is, GError **error);
175 static gboolean imapx_continuation (CamelIMAPXServer *is, gboolean litplus, GCancellable *cancellable, GError **error);
176 static gboolean imapx_disconnect (CamelIMAPXServer *is);
177 static gint imapx_uid_cmp (gconstpointer ap, gconstpointer bp, gpointer data);
178
179 static gboolean imapx_is_command_queue_empty (CamelIMAPXServer *is);
180
181 /* states for the connection? */
182 enum {
183         IMAPX_DISCONNECTED,
184         IMAPX_SHUTDOWN,
185         IMAPX_CONNECTED,
186         IMAPX_AUTHENTICATED,
187         IMAPX_INITIALISED,
188         IMAPX_SELECTED
189 };
190
191 struct _refresh_info {
192         gchar *uid;
193         gboolean exists;
194         guint32 server_flags;
195         CamelFlag *server_user_flags;
196 };
197
198 enum {
199         IMAPX_JOB_GET_MESSAGE = 1 << 0,
200         IMAPX_JOB_APPEND_MESSAGE = 1 << 1,
201         IMAPX_JOB_COPY_MESSAGE = 1 << 2,
202         IMAPX_JOB_FETCH_NEW_MESSAGES = 1 << 3,
203         IMAPX_JOB_REFRESH_INFO = 1 << 4,
204         IMAPX_JOB_SYNC_CHANGES = 1 << 5,
205         IMAPX_JOB_EXPUNGE = 1 << 6,
206         IMAPX_JOB_NOOP = 1 << 7,
207         IMAPX_JOB_IDLE = 1 << 8,
208         IMAPX_JOB_LIST = 1 << 9,
209         IMAPX_JOB_MANAGE_SUBSCRIPTION = 1 << 10,
210         IMAPX_JOB_CREATE_FOLDER = 1 << 11,
211         IMAPX_JOB_DELETE_FOLDER = 1 << 12,
212         IMAPX_JOB_RENAME_FOLDER = 1 << 13,
213         IMAPX_JOB_FETCH_MESSAGES = 1 << 14,
214 };
215
216 /* Operations on the store (folder_tree) will have highest priority as we know for sure they are sync
217  * and user triggered. */
218 enum {
219         IMAPX_PRIORITY_CREATE_FOLDER = 200,
220         IMAPX_PRIORITY_DELETE_FOLDER = 200,
221         IMAPX_PRIORITY_RENAME_FOLDER = 200,
222         IMAPX_PRIORITY_MANAGE_SUBSCRIPTION = 200,
223         IMAPX_PRIORITY_SYNC_CHANGES = 150,
224         IMAPX_PRIORITY_EXPUNGE = 150,
225         IMAPX_PRIORITY_GET_MESSAGE = 100,
226         IMAPX_PRIORITY_REFRESH_INFO = 0,
227         IMAPX_PRIORITY_NOOP = 0,
228         IMAPX_PRIORITY_NEW_MESSAGES = 0,
229         IMAPX_PRIORITY_APPEND_MESSAGE = -60,
230         IMAPX_PRIIORITY_COPY_MESSAGE = -60,
231         IMAPX_PRIORITY_LIST = -80,
232         IMAPX_PRIORITY_IDLE = -100,
233         IMAPX_PRIORITY_SYNC_MESSAGE = -120
234 };
235
236 struct _imapx_flag_change {
237         GPtrArray *infos;
238         gchar *name;
239 };
240
241 static CamelIMAPXJob *imapx_match_active_job (CamelIMAPXServer *is, guint32 type, const gchar *uid);
242 static void imapx_job_fetch_new_messages_start (CamelIMAPXJob *job, CamelIMAPXServer *is);
243 static gint imapx_refresh_info_uid_cmp (gconstpointer ap, gconstpointer bp, gboolean ascending);
244 static gint imapx_uids_array_cmp (gconstpointer ap, gconstpointer bp);
245 static gboolean imapx_server_sync_changes (CamelIMAPXServer *is, CamelFolder *folder, gint pri, GCancellable *cancellable, GError **error);
246 static void imapx_sync_free_user (GArray *user_set);
247
248 static void     imapx_command_copy_messages_step_start
249                                                 (CamelIMAPXServer *is,
250                                                  CamelIMAPXJob *job,
251                                                  gint index);
252
253 enum _idle_state {
254         IMAPX_IDLE_OFF,
255         IMAPX_IDLE_PENDING,     /* Queue is idle; waiting to send IDLE command
256                                    soon if nothing more interesting happens */
257         IMAPX_IDLE_ISSUED,      /* Sent IDLE command; waiting for response */
258         IMAPX_IDLE_STARTED,     /* IDLE continuation received; IDLE active */
259         IMAPX_IDLE_CANCEL,      /* Cancelled from ISSUED state; need to send
260                                    DONE as soon as we receive continuation */
261 };
262 #define IMAPX_IDLE_DWELL_TIME   2 /* Number of seconds to remain in PENDING
263                                      state waiting for other commands to be
264                                      queued, before actually sending IDLE */
265
266 struct _CamelIMAPXIdle {
267         GMutex *idle_lock;
268         GThread *idle_thread;
269
270         GCond *start_watch_cond;
271         GMutex *start_watch_mutex;
272         gboolean start_watch_is_set;
273
274         time_t started;
275         enum _idle_state state;
276         gboolean idle_exit;
277 };
278
279 static gboolean imapx_in_idle (CamelIMAPXServer *is);
280 static gboolean imapx_idle_supported (CamelIMAPXServer *is);
281 static void imapx_start_idle (CamelIMAPXServer *is);
282 static void imapx_exit_idle (CamelIMAPXServer *is);
283 static void imapx_init_idle (CamelIMAPXServer *is);
284 static gboolean imapx_stop_idle (CamelIMAPXServer *is, GError **error);
285 static gboolean camel_imapx_server_idle (CamelIMAPXServer *is, CamelFolder *folder, GCancellable *cancellable, GError **error);
286
287 enum {
288         USE_SSL_NEVER,
289         USE_SSL_ALWAYS,
290         USE_SSL_WHEN_POSSIBLE
291 };
292
293 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
294 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
295
296 static gboolean imapx_select (CamelIMAPXServer *is, CamelFolder *folder, gboolean force, GCancellable *cancellable, GError **error);
297
298 G_DEFINE_TYPE (CamelIMAPXServer, camel_imapx_server, CAMEL_TYPE_OBJECT)
299
300 static void
301 get_message_data_free (GetMessageData *data)
302 {
303         g_free (data->uid);
304
305         if (data->stream != NULL)
306                 g_object_unref (data->stream);
307
308         g_slice_free (GetMessageData, data);
309 }
310
311 static void
312 refresh_info_data_free (RefreshInfoData *data)
313 {
314         camel_folder_change_info_free (data->changes);
315
316         g_slice_free (RefreshInfoData, data);
317 }
318
319 static void
320 sync_changes_data_free (SyncChangesData *data)
321 {
322         if (data->folder != NULL) {
323                 camel_folder_free_uids (data->folder, data->changed_uids);
324                 g_object_unref (data->folder);
325         }
326
327         imapx_sync_free_user (data->on_user);
328         imapx_sync_free_user (data->off_user);
329
330         g_slice_free (SyncChangesData, data);
331 }
332
333 static void
334 append_message_data_free (AppendMessageData *data)
335 {
336         g_free (data->path);
337         g_free (data->appended_uid);
338
339         camel_message_info_free (data->info);
340
341         g_slice_free (AppendMessageData, data);
342 }
343
344 static void
345 copy_messages_data_free (CopyMessagesData *data)
346 {
347         if (data->dest != NULL)
348                 g_object_unref (data->dest);
349
350         if (data->uids != NULL) {
351                 g_ptr_array_foreach (data->uids, (GFunc) g_free, NULL);
352                 g_ptr_array_free (data->uids, TRUE);
353         }
354
355         g_slice_free (CopyMessagesData, data);
356 }
357
358 static void
359 list_data_free (ListData *data)
360 {
361         g_free (data->pattern);
362         g_free (data->ext);
363
364         g_hash_table_destroy (data->folders);
365
366         g_slice_free (ListData, data);
367 }
368
369 static void
370 manage_subscriptions_data_free (ManageSubscriptionsData *data)
371 {
372         g_free (data->folder_name);
373
374         g_slice_free (ManageSubscriptionsData, data);
375 }
376
377 static void
378 rename_folder_data_free (RenameFolderData *data)
379 {
380         g_free (data->old_folder_name);
381         g_free (data->new_folder_name);
382
383         g_slice_free (RenameFolderData, data);
384 }
385
386 static void
387 create_folder_data_free (CreateFolderData *data)
388 {
389         g_free (data->folder_name);
390
391         g_slice_free (CreateFolderData, data);
392 }
393
394 static void
395 delete_folder_data_free (DeleteFolderData *data)
396 {
397         g_free (data->folder_name);
398
399         g_slice_free (DeleteFolderData, data);
400 }
401
402 /*
403   this creates a uid (or sequence number) set directly into a command,
404   if total is set, then we break it up into total uids. (i.e. command time)
405   if limit is set, then we break it up into limit entries (i.e. command length)
406 */
407 void
408 imapx_uidset_init (struct _uidset_state *ss,
409                    gint total,
410                    gint limit)
411 {
412         ss->uids = 0;
413         ss->entries = 0;
414         ss->start = 0;
415         ss->last = 0;
416         ss->total = total;
417         ss->limit = limit;
418 }
419
420 gboolean
421 imapx_uidset_done (struct _uidset_state *ss,
422                    CamelIMAPXCommand *ic)
423 {
424         gint ret = FALSE;
425
426         if (ss->last != 0 && ss->last != ss->start) {
427                 camel_imapx_command_add (ic, ":%d", ss->last);
428         }
429
430         ret = ss->last != 0;
431
432         ss->start = 0;
433         ss->last = 0;
434         ss->uids = 0;
435         ss->entries = 0;
436
437         return ret;
438 }
439
440 gint
441 imapx_uidset_add (struct _uidset_state *ss,
442                   CamelIMAPXCommand *ic,
443                   const gchar *uid)
444 {
445         guint32 uidn;
446
447         uidn = strtoul (uid, NULL, 10);
448         if (uidn == 0)
449                 return -1;
450
451         ss->uids++;
452
453         e(ic->is->tagprefix, "uidset add '%s'\n", uid);
454
455         if (ss->last == 0) {
456                 e(ic->is->tagprefix, " start\n");
457                 camel_imapx_command_add (ic, "%d", uidn);
458                 ss->entries++;
459                 ss->start = uidn;
460         } else {
461                 if (ss->last != uidn - 1) {
462                         if (ss->last == ss->start) {
463                                 e(ic->is->tagprefix, " ,next\n");
464                                 camel_imapx_command_add (ic, ",%d", uidn);
465                                 ss->entries++;
466                         } else {
467                                 e(ic->is->tagprefix, " :range\n");
468                                 camel_imapx_command_add (ic, ":%d,%d", ss->last, uidn);
469                                 ss->entries+=2;
470                         }
471                         ss->start = uidn;
472                 }
473         }
474
475         ss->last = uidn;
476
477         if ((ss->limit && ss->entries >= ss->limit)
478             || (ss->total && ss->uids >= ss->total)) {
479                 e(ic->is->tagprefix, " done, %d entries, %d uids\n", ss->entries, ss->uids);
480                 if (!imapx_uidset_done (ss, ic))
481                         return -1;
482                 return 1;
483         }
484
485         return 0;
486 }
487
488 /* Must hold QUEUE_LOCK */
489 static gboolean
490 imapx_command_start (CamelIMAPXServer *is,
491                      CamelIMAPXCommand *ic,
492                      GCancellable *cancellable,
493                      GError **error)
494 {
495         CamelIMAPXCommandPart *cp;
496         gboolean cp_continuation;
497         gboolean cp_literal_plus;
498         GList *head;
499         gint retval;
500
501         camel_imapx_command_close (ic);
502
503         head = g_queue_peek_head_link (&ic->parts);
504         g_return_val_if_fail (head != NULL, FALSE);
505         cp = (CamelIMAPXCommandPart *) head->data;
506         ic->current_part = head;
507
508         cp_continuation = ((cp->type & CAMEL_IMAPX_COMMAND_CONTINUATION) != 0);
509         cp_literal_plus = ((cp->type & CAMEL_IMAPX_COMMAND_LITERAL_PLUS) != 0);
510
511         /* TODO: If we support literal+ we should be able to write the whole command out
512          * at this point .... >here< */
513
514         if (cp_continuation || cp_literal_plus)
515                 is->literal = ic;
516
517         camel_imapx_command_queue_push_tail (is->active, ic);
518
519         g_static_rec_mutex_lock (&is->ostream_lock);
520
521         c(is->tagprefix, "Starting command (active=%d,%s) %c%05u %s\r\n", camel_imapx_command_queue_get_length (is->active), is->literal?" literal":"", is->tagprefix, ic->tag, cp->data && g_str_has_prefix (cp->data, "LOGIN") ? "LOGIN..." : cp->data);
522         if (is->stream != NULL) {
523                 gchar *string;
524
525                 string = g_strdup_printf ("%c%05u %s\r\n", is->tagprefix, ic->tag, cp->data);
526                 retval = camel_stream_write_string ((CamelStream *) is->stream, string, cancellable, NULL);
527                 g_free (string);
528         } else
529                 retval = -1;
530         if (retval == -1) {
531                 g_set_error (
532                         error, CAMEL_IMAPX_ERROR, 1,
533                         "Failed to issue the command");
534                 goto err;
535         }
536         while (is->literal == ic && cp_literal_plus) {
537                 /* Sent LITERAL+ continuation immediately */
538                 if (!imapx_continuation (is, TRUE, cancellable, error))
539                         goto err;
540         }
541
542         g_static_rec_mutex_unlock (&is->ostream_lock);
543
544         return TRUE;
545
546 err:
547         g_static_rec_mutex_unlock (&is->ostream_lock);
548
549         camel_imapx_command_queue_remove (is->active, ic);
550
551         /* HACK: Since we're failing, make sure the command has a status
552          *       structure and the result code indicates failure, so the
553          *       ic->complete() callback does not start a new command. */
554         if (ic->status == NULL)
555                 ic->status = g_malloc0 (sizeof (struct _status_info));
556         if (ic->status->result == IMAPX_OK)
557                 ic->status->result = IMAPX_UNKNOWN;
558
559         /* Send a NULL GError since we've already set a
560          * GError to get here, and we're not interested
561          * in individual command errors. */
562         if (ic != NULL && ic->complete != NULL)
563                 ic->complete (is, ic, NULL);
564
565         return FALSE;
566 }
567
568 static gboolean
569 duplicate_fetch_or_refresh (CamelIMAPXServer *is,
570                             CamelIMAPXCommand *ic)
571 {
572         CamelIMAPXJob *job;
573
574         job = camel_imapx_command_get_job (ic);
575
576         if (job == NULL)
577                 return FALSE;
578
579         if (!(job->type & (IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES)))
580                 return FALSE;
581
582         if (imapx_match_active_job (is, IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES, NULL)) {
583                 c(is->tagprefix, "Not yet sending duplicate fetch/refresh %s command\n", ic->name);
584                 return TRUE;
585         }
586
587         return FALSE;
588 }
589
590 /* See if we can start another task yet.
591  *
592  * If we're waiting for a literal, we cannot proceed.
593  *
594  * If we're about to change the folder we're
595  * looking at from user-direction, we dont proceed.
596  *
597  * If we have a folder selected, first see if any
598  * jobs are waiting on it, but only if they are
599  * at least as high priority as anything we
600  * have running.
601  *
602  * If we dont, select the first folder required,
603  * then queue all the outstanding jobs on it, that
604  * are at least as high priority as the first.
605  *
606  * must have QUEUE lock */
607
608 static void
609 imapx_command_start_next (CamelIMAPXServer *is,
610                           GCancellable *cancellable,
611                           GError **error)
612 {
613         CamelIMAPXCommand *first_ic;
614         gint min_pri = -128;
615
616         c(is->tagprefix, "** Starting next command\n");
617         if (is->literal) {
618                 c(is->tagprefix, "* no; waiting for literal '%s'\n", is->literal->name);
619                 return;
620         }
621
622         if (is->select_pending) {
623                 GQueue start = G_QUEUE_INIT;
624                 GList *head, *link;
625
626                 c(is->tagprefix, "-- Checking job queue for non-folder jobs\n");
627
628                 head = camel_imapx_command_queue_peek_head_link (is->queue);
629
630                 /* Tag which commands in the queue to start. */
631                 for (link = head; link != NULL; link = g_list_next (link)) {
632                         CamelIMAPXCommand *ic = link->data;
633
634                         if (ic->pri < min_pri)
635                                 break;
636
637                         c(is->tagprefix, "-- %3d '%s'?\n", (gint)ic->pri, ic->name);
638                         if (!ic->select) {
639                                 c(is->tagprefix, "--> starting '%s'\n", ic->name);
640                                 min_pri = ic->pri;
641                                 g_queue_push_tail (&start, link);
642                         }
643
644                         if (g_queue_get_length (&start) == MAX_COMMANDS)
645                                 break;
646                 }
647
648                 if (g_queue_is_empty (&start))
649                         c(is->tagprefix, "* no, waiting for pending select '%s'\n", camel_folder_get_full_name (is->select_pending));
650
651                 /* Start the tagged commands.
652                  *
653                  * Each command must be removed from 'is->queue' before
654                  * starting it, so we temporarily reference the command
655                  * to avoid accidentally finalizing it. */
656                 while ((link = g_queue_pop_head (&start)) != NULL) {
657                         CamelIMAPXCommand *ic;
658                         ic = camel_imapx_command_ref (link->data);
659                         camel_imapx_command_queue_delete_link (is->queue, link);
660                         imapx_command_start (is, ic, cancellable, error);
661                         camel_imapx_command_unref (ic);
662                 }
663
664                 return;
665         }
666
667         if (imapx_idle_supported (is) && is->state == IMAPX_SELECTED) {
668                 gboolean empty = imapx_is_command_queue_empty (is);
669
670                 if (imapx_in_idle (is) && !camel_imapx_command_queue_is_empty (is->queue)) {
671                         /* if imapx_stop_idle() returns FALSE, it was only
672                          * pending and we can go ahead and send a new command
673                          * immediately. If it returns TRUE, either it sent the
674                          * DONE to exit IDLE mode, or there was an error.
675                          * Either way, we do nothing more right now. */
676                         if (imapx_stop_idle (is, error)) {
677                                 c(is->tagprefix, "waiting for idle to stop \n");
678                                 return;
679                         }
680                 } else if (empty && !imapx_in_idle (is)) {
681                         imapx_start_idle (is);
682                         c(is->tagprefix, "starting idle \n");
683                         return;
684                 }
685         }
686
687         if (camel_imapx_command_queue_is_empty (is->queue)) {
688                 c(is->tagprefix, "* no, no jobs\n");
689                 return;
690         }
691
692         /* See if any queued jobs on this select first */
693         if (is->select_folder) {
694                 GQueue start = G_QUEUE_INIT;
695                 GList *head, *link;
696                 gboolean commands_started = FALSE;
697
698                 c(is->tagprefix, "- we're selected on '%s', current jobs?\n",
699                   camel_folder_get_full_name (is->select_folder));
700
701                 head = camel_imapx_command_queue_peek_head_link (is->active);
702
703                 /* Find the highest priority in the active queue. */
704                 for (link = head; link != NULL; link = g_list_next (link)) {
705                         CamelIMAPXCommand *ic = link->data;
706
707                         min_pri = MAX (min_pri, ic->pri);
708                         c(is->tagprefix, "-  %3d '%s'\n", (gint)ic->pri, ic->name);
709                 }
710
711                 if (camel_imapx_command_queue_get_length (is->active) >= MAX_COMMANDS) {
712                         c(is->tagprefix, "** too many jobs busy, waiting for results for now\n");
713                         return;
714                 }
715
716                 c(is->tagprefix, "-- Checking job queue\n");
717
718                 head = camel_imapx_command_queue_peek_head_link (is->queue);
719
720                 /* Tag which commands in the queue to start. */
721                 for (link = head; link != NULL; link = g_list_next (link)) {
722                         CamelIMAPXCommand *ic = link->data;
723
724                         if (is->literal != NULL)
725                                 break;
726
727                         if (ic->pri < min_pri)
728                                 break;
729
730                         c(is->tagprefix, "-- %3d '%s'?\n", (gint)ic->pri, ic->name);
731                         if (!ic->select || ((ic->select == is->select_folder) &&
732                                             !duplicate_fetch_or_refresh (is, ic))) {
733                                 c(is->tagprefix, "--> starting '%s'\n", ic->name);
734                                 min_pri = ic->pri;
735                                 g_queue_push_tail (&start, link);
736                         } else {
737                                 /* This job isn't for the selected folder, but we don't want to
738                                  * consider jobs with _lower_ priority than this, even if they
739                                  * are for the selected folder. */
740                                 min_pri = ic->pri;
741                         }
742
743                         if (g_queue_get_length (&start) == MAX_COMMANDS)
744                                 break;
745                 }
746
747                 /* Start the tagged commands.
748                  *
749                  * Each command must be removed from 'is->queue' before
750                  * starting it, so we temporarily reference the command
751                  * to avoid accidentally finalizing it. */
752                 while ((link = g_queue_pop_head (&start)) != NULL) {
753                         CamelIMAPXCommand *ic;
754                         ic = camel_imapx_command_ref (link->data);
755                         camel_imapx_command_queue_delete_link (is->queue, link);
756                         imapx_command_start (is, ic, cancellable, error);
757                         camel_imapx_command_unref (ic);
758                         commands_started = TRUE;
759                 }
760
761                 if (commands_started)
762                         return;
763         }
764
765         /* This won't be NULL because we checked for an empty queue above. */
766         first_ic = camel_imapx_command_queue_peek_head (is->queue);
767
768         /* If we need to select a folder for the first command, do it now,
769          * once it is complete it will re-call us if it succeeded. */
770         if (first_ic->select) {
771                 c(is->tagprefix, "Selecting folder '%s' for command '%s'(%p)\n",
772                   camel_folder_get_full_name (first_ic->select),
773                   first_ic->name, first_ic);
774                 imapx_select (is, first_ic->select, FALSE, cancellable, error);
775         } else {
776                 GQueue start = G_QUEUE_INIT;
777                 GList *head, *link;
778
779                 min_pri = first_ic->pri;
780
781                 head = camel_imapx_command_queue_peek_head_link (is->queue);
782
783                 /* Tag which commands in the queue to start. */
784                 for (link = head; link != NULL; link = g_list_next (link)) {
785                         CamelIMAPXCommand *ic = link->data;
786
787                         if (is->literal != NULL)
788                                 break;
789
790                         if (ic->pri < min_pri)
791                                 break;
792
793                         if (!ic->select || (ic->select == is->select_folder &&
794                                             !duplicate_fetch_or_refresh (is, ic))) {
795                                 c(is->tagprefix, "* queueing job %3d '%s'\n", (gint)ic->pri, ic->name);
796                                 min_pri = ic->pri;
797                                 g_queue_push_tail (&start, link);
798                         }
799
800                         if (g_queue_get_length (&start) == MAX_COMMANDS)
801                                 break;
802                 }
803
804                 /* Start the tagged commands.
805                  *
806                  * Each command must be removed from 'is->queue' before
807                  * starting it, so we temporarily reference the command
808                  * to avoid accidentally finalizing it. */
809                 while ((link = g_queue_pop_head (&start)) != NULL) {
810                         CamelIMAPXCommand *ic;
811                         ic = camel_imapx_command_ref (link->data);
812                         camel_imapx_command_queue_delete_link (is->queue, link);
813                         imapx_command_start (is, ic, cancellable, error);
814                         camel_imapx_command_unref (ic);
815                 }
816         }
817 }
818
819 static gboolean
820 imapx_is_command_queue_empty (CamelIMAPXServer *is)
821 {
822         if (!camel_imapx_command_queue_is_empty (is->queue))
823                 return FALSE;
824
825         if (!camel_imapx_command_queue_is_empty (is->active))
826                 return FALSE;
827
828         return TRUE;
829 }
830
831 static void
832 imapx_command_queue (CamelIMAPXServer *is,
833                      CamelIMAPXCommand *ic)
834 {
835         CamelIMAPXJob *job;
836
837         /* We enqueue in priority order, new messages have
838          * higher priority than older messages with the same priority */
839
840         job = camel_imapx_command_get_job (ic);
841         g_return_if_fail (CAMEL_IS_IMAPX_JOB (job));
842
843         camel_imapx_command_close (ic);
844
845         c(is->tagprefix, "enqueue job '%.*s'\n", ((CamelIMAPXCommandPart *)ic->parts.head->data)->data_size, ((CamelIMAPXCommandPart *)ic->parts.head->data)->data);
846
847         QUEUE_LOCK (is);
848
849         if (is->state == IMAPX_SHUTDOWN) {
850                 c(is->tagprefix, "refuse to queue job on disconnected server\n");
851                 if (job->error == NULL)
852                         g_set_error (
853                                 &job->error, CAMEL_IMAPX_ERROR, 1,
854                                 "%s", _("Server disconnected"));
855                 QUEUE_UNLOCK (is);
856
857                 /* Send a NULL GError since we've already set
858                  * the job's GError, and we're not interested
859                  * in individual command errors. */
860                 if (ic->complete != NULL)
861                         ic->complete (is, ic, NULL);
862                 return;
863         }
864
865         camel_imapx_command_queue_insert_sorted (is->queue, ic);
866
867         imapx_command_start_next (is, job->cancellable, NULL);
868
869         QUEUE_UNLOCK (is);
870
871         return;
872 }
873
874 /* Must have QUEUE lock */
875 static CamelIMAPXCommand *
876 imapx_find_command_tag (CamelIMAPXServer *is,
877                         guint tag)
878 {
879         CamelIMAPXCommand *ic = NULL;
880         GList *head, *link;
881
882         QUEUE_LOCK (is);
883
884         if (is->literal != NULL && is->literal->tag == tag) {
885                 ic = is->literal;
886                 goto exit;
887         }
888
889         head = camel_imapx_command_queue_peek_head_link (is->active);
890
891         for (link = head; link != NULL; link = g_list_next (link)) {
892                 CamelIMAPXCommand *candidate = link->data;
893
894                 if (candidate->tag == tag) {
895                         ic = candidate;
896                         break;
897                 }
898         }
899
900 exit:
901         QUEUE_UNLOCK (is);
902
903         return ic;
904 }
905
906 /* Must not have QUEUE lock */
907 static CamelIMAPXJob *
908 imapx_match_active_job (CamelIMAPXServer *is,
909                         guint32 type,
910                         const gchar *uid)
911 {
912         CamelIMAPXJob *match = NULL;
913         GList *head, *link;
914
915         QUEUE_LOCK (is);
916
917         head = camel_imapx_command_queue_peek_head_link (is->active);
918
919         for (link = head; link != NULL; link = g_list_next (link)) {
920                 CamelIMAPXCommand *ic = link->data;
921                 CamelIMAPXJob *job;
922
923                 job = camel_imapx_command_get_job (ic);
924
925                 if (job == NULL)
926                         continue;
927
928                 if (!(job->type & type))
929                         continue;
930
931                 if (camel_imapx_job_matches (job, is->select_folder, uid)) {
932                         match = job;
933                         break;
934                 }
935         }
936
937         QUEUE_UNLOCK (is);
938
939         return match;
940 }
941
942 static CamelIMAPXJob *
943 imapx_is_job_in_queue (CamelIMAPXServer *is,
944                        CamelFolder *folder,
945                        guint32 type,
946                        const gchar *uid)
947 {
948         GList *head, *link;
949         CamelIMAPXJob *job = NULL;
950         gboolean found = FALSE;
951
952         QUEUE_LOCK (is);
953
954         head = g_queue_peek_head_link (&is->jobs);
955
956         for (link = head; link != NULL; link = g_list_next (link)) {
957                 job = (CamelIMAPXJob *) link->data;
958
959                 if (!job || !(job->type & type))
960                         continue;
961
962                 if (camel_imapx_job_matches (job, folder, uid)) {
963                         found = TRUE;
964                         break;
965                 }
966         }
967
968         QUEUE_UNLOCK (is);
969
970         if (found)
971                 return job;
972         else
973                 return NULL;
974 }
975
976 static void
977 imapx_expunge_uid_from_summary (CamelIMAPXServer *is,
978                                 gchar *uid,
979                                 gboolean unsolicited)
980 {
981         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) is->select_folder;
982
983         if (unsolicited && ifolder->exists_on_server)
984                 ifolder->exists_on_server--;
985
986         if (is->changes == NULL)
987                 is->changes = camel_folder_change_info_new ();
988
989         camel_folder_summary_remove_uid (is->select_folder->summary, uid);
990         is->expunged = g_list_prepend (is->expunged, uid);
991
992         camel_folder_change_info_remove_uid (is->changes, uid);
993
994         if (imapx_idle_supported (is) && imapx_in_idle (is)) {
995                 camel_folder_summary_save_to_db (is->select_folder->summary, NULL);
996                 imapx_update_store_summary (is->select_folder);
997                 camel_folder_changed (is->select_folder, is->changes);
998
999                 g_list_free_full (is->expunged, (GDestroyNotify) g_free);
1000                 is->expunged = NULL;
1001
1002                 camel_folder_change_info_clear (is->changes);
1003         }
1004 }
1005
1006 static gchar *
1007 imapx_get_uid_from_index (CamelFolderSummary *summary,
1008                           guint id)
1009 {
1010         GPtrArray *array;
1011         gchar *uid = NULL;
1012
1013         g_return_val_if_fail (summary != NULL, NULL);
1014
1015         array = camel_folder_summary_get_array (summary);
1016         g_return_val_if_fail (array != NULL, NULL);
1017
1018         if (id < array->len) {
1019                 camel_folder_sort_uids (camel_folder_summary_get_folder (summary), array);
1020                 uid = g_strdup (g_ptr_array_index (array, id));
1021         }
1022
1023         camel_folder_summary_free_array (array);
1024
1025         return uid;
1026 }
1027
1028 static void
1029 invalidate_local_cache (CamelIMAPXFolder *ifolder,
1030                         guint64 new_uidvalidity)
1031 {
1032         CamelFolder *cfolder;
1033         CamelFolderChangeInfo *changes;
1034         GPtrArray *uids;
1035         gint ii;
1036
1037         g_return_if_fail (ifolder != NULL);
1038
1039         cfolder = CAMEL_FOLDER (ifolder);
1040         g_return_if_fail (cfolder != NULL);
1041
1042         changes = camel_folder_change_info_new ();
1043
1044         uids = camel_folder_summary_get_array (cfolder->summary);
1045         for (ii = 0; uids && ii < uids->len; ii++) {
1046                 const gchar *uid = uids->pdata[ii];
1047
1048                 if (uid)
1049                         camel_folder_change_info_change_uid (changes, uid);
1050         }
1051
1052         camel_folder_summary_free_array (uids);
1053
1054         CAMEL_IMAPX_SUMMARY (cfolder->summary)->validity = new_uidvalidity;
1055         camel_folder_summary_touch (cfolder->summary);
1056         camel_folder_summary_save_to_db (cfolder->summary, NULL);
1057
1058         camel_data_cache_clear (ifolder->cache, "cache");
1059         camel_data_cache_clear (ifolder->cache, "cur");
1060
1061         camel_folder_changed (cfolder, changes);
1062         camel_folder_change_info_free (changes);
1063 }
1064
1065 /* handle any untagged responses */
1066 static gboolean
1067 imapx_untagged (CamelIMAPXServer *is,
1068                 GCancellable *cancellable,
1069                 GError **error)
1070 {
1071         CamelService *service;
1072         CamelSettings *settings;
1073         CamelSortType fetch_order;
1074         guint id, len;
1075         guchar *token, *p, c;
1076         gint tok;
1077         gboolean lsub = FALSE;
1078         struct _status_info *sinfo;
1079
1080         service = CAMEL_SERVICE (is->store);
1081         settings = camel_service_get_settings (service);
1082
1083         fetch_order = camel_imapx_settings_get_fetch_order (
1084                 CAMEL_IMAPX_SETTINGS (settings));
1085
1086         e(is->tagprefix, "got untagged response\n");
1087         id = 0;
1088         tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
1089         if (tok < 0)
1090                 return FALSE;
1091
1092         if (tok == IMAPX_TOK_INT) {
1093                 id = strtoul ((gchar *) token, NULL, 10);
1094                 tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
1095                 if (tok < 0)
1096                         return FALSE;
1097         }
1098
1099         if (tok == '\n') {
1100                 g_set_error (
1101                         error, CAMEL_IMAPX_ERROR, 1,
1102                         "truncated server response");
1103                 return FALSE;
1104         }
1105
1106         e(is->tagprefix, "Have token '%s' id %d\n", token, id);
1107         p = token;
1108         while ((c = *p))
1109                 *p++ = toupper((gchar) c);
1110
1111         switch (imapx_tokenise ((const gchar *) token, len)) {
1112         case IMAPX_CAPABILITY:
1113                 if (is->cinfo)
1114                         imapx_free_capability (is->cinfo);
1115                 is->cinfo = imapx_parse_capability (is->stream, cancellable, error);
1116                 if (is->cinfo == NULL)
1117                         return FALSE;
1118                 c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
1119                 return TRUE;
1120         case IMAPX_EXPUNGE: {
1121                 guint32 expunge = id;
1122                 CamelIMAPXJob *job = imapx_match_active_job (is, IMAPX_JOB_EXPUNGE, NULL);
1123
1124                 /* If there is a job running, let it handle the deletion */
1125                 if (job)
1126                         break;
1127
1128                 c(is->tagprefix, "expunged: %d\n", id);
1129                 if (is->select_folder) {
1130                         gchar *uid = NULL;
1131
1132                         uid = imapx_get_uid_from_index (is->select_folder->summary, expunge - 1);
1133                         if (!uid)
1134                                 break;
1135
1136                         imapx_expunge_uid_from_summary (is, uid, TRUE);
1137                 }
1138
1139                 break;
1140         }
1141         case IMAPX_VANISHED: {
1142                 GPtrArray *uids;
1143                 gboolean unsolicited = TRUE;
1144                 gint i;
1145                 guint len;
1146                 guchar *token;
1147                 gint tok;
1148
1149                 tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
1150                 if (tok < 0)
1151                         return FALSE;
1152                 if (tok == '(') {
1153                         unsolicited = FALSE;
1154                         while (tok != ')') {
1155                                 /* We expect this to be 'EARLIER' */
1156                                 tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
1157                                 if (tok < 0)
1158                                         return FALSE;
1159                         }
1160                 } else
1161                         camel_imapx_stream_ungettoken (is->stream, tok, token, len);
1162
1163                 uids = imapx_parse_uids (is->stream, cancellable, error);
1164                 if (uids == NULL)
1165                         return FALSE;
1166                 for (i = 0; i < uids->len; i++) {
1167                         gchar *uid = g_strdup_printf("%u", GPOINTER_TO_UINT(g_ptr_array_index (uids, i)));
1168                         c(is->tagprefix, "vanished: %s\n", uid);
1169                         imapx_expunge_uid_from_summary (is, uid, unsolicited);
1170                 }
1171                 g_ptr_array_free (uids, FALSE);
1172                 break;
1173         }
1174         case IMAPX_NAMESPACE: {
1175                 CamelIMAPXNamespaceList *nsl = NULL;
1176
1177                 nsl = imapx_parse_namespace_list (is->stream, cancellable, error);
1178                 if (nsl != NULL) {
1179                         CamelIMAPXStore *imapx_store = (CamelIMAPXStore *) is->store;
1180                         CamelIMAPXStoreNamespace *ns;
1181
1182                         imapx_store->summary->namespaces = nsl;
1183                         camel_store_summary_touch ((CamelStoreSummary *) imapx_store->summary);
1184
1185                         /* TODO Need to remove imapx_store->dir_sep to support multiple namespaces */
1186                         ns = nsl->personal;
1187                         if (ns)
1188                                 imapx_store->dir_sep = ns->sep;
1189                 }
1190
1191                 return TRUE;
1192         }
1193         case IMAPX_EXISTS:
1194                 c(is->tagprefix, "exists: %d\n", id);
1195                 is->exists = id;
1196
1197                 if (is->select_folder)
1198                         ((CamelIMAPXFolder *) is->select_folder)->exists_on_server = id;
1199
1200                 if (imapx_idle_supported (is) && imapx_in_idle (is)) {
1201                         if (camel_folder_summary_count (is->select_folder->summary) < id)
1202                                 imapx_stop_idle (is, error);
1203                 }
1204
1205                 break;
1206         case IMAPX_FLAGS: {
1207                 guint32 flags;
1208
1209                 imapx_parse_flags (is->stream, &flags, NULL, cancellable, error);
1210
1211                 c(is->tagprefix, "flags: %08x\n", flags);
1212                 break;
1213         }
1214         case IMAPX_FETCH: {
1215                 struct _fetch_info *finfo;
1216
1217                 finfo = imapx_parse_fetch (is->stream, cancellable, error);
1218                 if (finfo == NULL) {
1219                         imapx_free_fetch (finfo);
1220                         return FALSE;
1221                 }
1222
1223                 if ((finfo->got & (FETCH_BODY | FETCH_UID)) == (FETCH_BODY | FETCH_UID)) {
1224                         CamelIMAPXJob *job = imapx_match_active_job (is, IMAPX_JOB_GET_MESSAGE, finfo->uid);
1225                         GetMessageData *data;
1226
1227                         data = camel_imapx_job_get_data (job);
1228                         g_return_val_if_fail (data != NULL, FALSE);
1229
1230                         /* This must've been a get-message request, fill out the body stream,
1231                          * in the right spot */
1232
1233                         if (job && job->error == NULL) {
1234                                 if (data->use_multi_fetch) {
1235                                         data->body_offset = finfo->offset;
1236                                         g_seekable_seek (G_SEEKABLE (data->stream), finfo->offset, G_SEEK_SET, NULL, NULL);
1237                                 }
1238
1239                                 data->body_len = camel_stream_write_to_stream (finfo->body, data->stream, job->cancellable, &job->error);
1240                                 if (data->body_len == -1)
1241                                         g_prefix_error (
1242                                                 &job->error,
1243                                                 _("Error writing to cache stream: "));
1244                         }
1245                 }
1246
1247                 if ((finfo->got & FETCH_FLAGS) && !(finfo->got & FETCH_HEADER)) {
1248                         CamelIMAPXJob *job = imapx_match_active_job (is, IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES, NULL);
1249                         /* This is either a refresh_info job, check to see if it is and update
1250                          * if so, otherwise it must've been an unsolicited response, so update
1251                          * the summary to match */
1252
1253                         if (job && (finfo->got & FETCH_UID)) {
1254                                 RefreshInfoData *data;
1255                                 struct _refresh_info r;
1256
1257                                 data = camel_imapx_job_get_data (job);
1258                                 g_return_val_if_fail (data != NULL, FALSE);
1259
1260                                 r.uid = finfo->uid;
1261                                 finfo->uid = NULL;
1262                                 r.server_flags = finfo->flags;
1263                                 r.server_user_flags = finfo->user_flags;
1264                                 finfo->user_flags = NULL;
1265                                 r.exists = FALSE;
1266                                 g_array_append_val (data->infos, r);
1267                         } else if (is->select_folder) {
1268                                 CamelFolder *folder;
1269                                 CamelMessageInfo *mi = NULL;
1270                                 gboolean changed = FALSE;
1271                                 gchar *uid = NULL;
1272
1273                                 g_object_ref (is->select_folder);
1274                                 folder = is->select_folder;
1275
1276                                 c(is->tagprefix, "flag changed: %d\n", id);
1277
1278                                 if (finfo->got & FETCH_UID) {
1279                                         uid = finfo->uid;
1280                                         finfo->uid = NULL;
1281                                 } else {
1282                                         uid = imapx_get_uid_from_index (folder->summary, id - 1);
1283                                 }
1284
1285                                 if (uid) {
1286                                         mi = camel_folder_summary_get (folder->summary, uid);
1287                                         if (mi) {
1288                                                 /* It's unsolicited _unless_ is->select_pending (i.e. during
1289                                                  * a QRESYNC SELECT */
1290                                                 changed = imapx_update_message_info_flags (mi, finfo->flags, finfo->user_flags, is->permanentflags, folder, !is->select_pending);
1291                                         } else {
1292                                                 /* This (UID + FLAGS for previously unknown message) might
1293                                                  * happen during a SELECT (QRESYNC). We should use it. */
1294                                                 c(is->tagprefix, "flags changed for unknown uid %s\n.", uid);
1295                                         }
1296                                         finfo->user_flags = NULL;
1297                                 }
1298
1299                                 if (changed) {
1300                                         if (is->changes == NULL)
1301                                                 is->changes = camel_folder_change_info_new ();
1302
1303                                         camel_folder_change_info_change_uid (is->changes, uid);
1304                                         g_free (uid);
1305                                 }
1306
1307                                 if (imapx_idle_supported (is) && changed && imapx_in_idle (is)) {
1308                                         camel_folder_summary_save_to_db (is->select_folder->summary, NULL);
1309                                         imapx_update_store_summary (is->select_folder);
1310                                         camel_folder_changed (is->select_folder, is->changes);
1311                                         camel_folder_change_info_clear (is->changes);
1312                                 }
1313
1314                                 if (mi)
1315                                         camel_message_info_free (mi);
1316                                 g_object_unref (folder);
1317                         }
1318                 }
1319
1320                 if ((finfo->got & (FETCH_HEADER | FETCH_UID)) == (FETCH_HEADER | FETCH_UID)) {
1321                         CamelIMAPXJob *job = imapx_match_active_job (is, IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES, NULL);
1322
1323                         /* This must be a refresh info job as well, but it has asked for
1324                          * new messages to be added to the index */
1325
1326                         if (job) {
1327                                 CamelMimeParser *mp;
1328                                 CamelMessageInfo *mi;
1329
1330                                 /* Do we want to save these headers for later too?  Do we care? */
1331
1332                                 mp = camel_mime_parser_new ();
1333                                 camel_mime_parser_init_with_stream (mp, finfo->header, NULL);
1334                                 mi = camel_folder_summary_info_new_from_parser (job->folder->summary, mp);
1335                                 g_object_unref (mp);
1336
1337                                 if (mi) {
1338                                         guint32 server_flags;
1339                                         CamelFlag *server_user_flags;
1340                                         CamelMessageInfoBase *binfo;
1341                                         gboolean free_user_flags = FALSE;
1342
1343                                         mi->uid = camel_pstring_strdup (finfo->uid);
1344
1345                                         if (!(finfo->got & FETCH_FLAGS)) {
1346                                                 RefreshInfoData *data;
1347                                                 struct _refresh_info *r = NULL;
1348                                                 gint min, max, mid;
1349                                                 gboolean found = FALSE;
1350
1351                                                 data = camel_imapx_job_get_data (job);
1352                                                 g_return_val_if_fail (data != NULL, FALSE);
1353
1354                                                 min = data->last_index;
1355                                                 max = data->index;
1356
1357                                                 /* array is sorted, so use a binary search */
1358                                                 do {
1359                                                         gint cmp = 0;
1360
1361                                                         mid = (min + max) / 2;
1362                                                         r = &g_array_index (data->infos, struct _refresh_info, mid);
1363                                                         cmp = imapx_refresh_info_uid_cmp (finfo->uid, r->uid, fetch_order == CAMEL_SORT_ASCENDING);
1364
1365                                                         if (cmp > 0)
1366                                                                 min = mid + 1;
1367                                                         else if (cmp < 0)
1368                                                                 max = mid - 1;
1369                                                         else
1370                                                                 found = TRUE;
1371
1372                                                 } while (!found && min <= max);
1373
1374                                                 if (!found)
1375                                                         g_assert_not_reached ();
1376
1377                                                 server_flags = r->server_flags;
1378                                                 server_user_flags = r->server_user_flags;
1379                                         } else {
1380                                                 server_flags = finfo->flags;
1381                                                 server_user_flags = finfo->user_flags;
1382                                                 /* free user_flags ? */
1383                                                 finfo->user_flags = NULL;
1384                                                 free_user_flags = TRUE;
1385                                         }
1386
1387                                         /* If the message is a really new one -- equal or higher than what
1388                                          * we know as UIDNEXT for the folder, then it came in since we last
1389                                          * fetched UIDNEXT and UNREAD count. We'll update UIDNEXT in the
1390                                          * command completion, but update UNREAD count now according to the
1391                                          * message SEEN flag */
1392                                         if (!(server_flags & CAMEL_MESSAGE_SEEN)) {
1393                                                 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
1394                                                 guint64 uidl = strtoull (mi->uid, NULL, 10);
1395
1396                                                 if (uidl >= ifolder->uidnext_on_server) {
1397                                                         c(is->tagprefix, "Updating unread count for new message %s\n", mi->uid);
1398                                                         ((CamelIMAPXFolder *) job->folder)->unread_on_server++;
1399                                                 } else {
1400                                                         c(is->tagprefix, "Not updating unread count for new message %s\n", mi->uid);
1401                                                 }
1402                                         }
1403
1404                                         binfo = (CamelMessageInfoBase *) mi;
1405                                         binfo->size = finfo->size;
1406
1407                                         if (!camel_folder_summary_check_uid (job->folder->summary, mi->uid)) {
1408                                                 RefreshInfoData *data;
1409                                                 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
1410                                                 gint cnt;
1411
1412                                                 data = camel_imapx_job_get_data (job);
1413                                                 g_return_val_if_fail (data != NULL, FALSE);
1414
1415                                                 imapx_set_message_info_flags_for_new_message (mi, server_flags, server_user_flags, job->folder);
1416                                                 camel_folder_summary_add (job->folder->summary, mi);
1417                                                 camel_folder_change_info_add_uid (data->changes, mi->uid);
1418
1419                                                 if (!g_hash_table_lookup (ifolder->ignore_recent, mi->uid)) {
1420                                                         camel_folder_change_info_recent_uid (data->changes, mi->uid);
1421                                                         g_hash_table_remove (ifolder->ignore_recent, mi->uid);
1422                                                 }
1423
1424                                                 cnt = (camel_folder_summary_count (job->folder->summary) * 100 ) / ifolder->exists_on_server;
1425                                                 camel_operation_progress (job->cancellable, cnt ? cnt : 1);
1426                                         }
1427
1428                                         if (free_user_flags && server_user_flags)
1429                                                 camel_flag_list_free (&server_user_flags);
1430
1431                                 }
1432                         }
1433                 }
1434
1435                 imapx_free_fetch (finfo);
1436                 break;
1437         }
1438         case IMAPX_LSUB:
1439                 lsub = TRUE;
1440         case IMAPX_LIST: {
1441                 struct _list_info *linfo = imapx_parse_list (is->stream, cancellable, error);
1442                 CamelIMAPXJob *job;
1443                 ListData *data;
1444
1445                 if (!linfo)
1446                         break;
1447
1448                 job = imapx_match_active_job (is, IMAPX_JOB_LIST, linfo->name);
1449
1450                 data = camel_imapx_job_get_data (job);
1451                 g_return_val_if_fail (data != NULL, FALSE);
1452
1453                 // TODO: we want to make sure the names match?
1454
1455                 if (data->flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) {
1456                         c(is->tagprefix, "lsub: '%s' (%c)\n", linfo->name, linfo->separator);
1457
1458                 } else {
1459                         c(is->tagprefix, "list: '%s' (%c)\n", linfo->name, linfo->separator);
1460                 }
1461
1462                 if (job && g_hash_table_lookup (data->folders, linfo->name) == NULL) {
1463                         if (lsub)
1464                                 linfo->flags |= CAMEL_FOLDER_SUBSCRIBED;
1465                         g_hash_table_insert (data->folders, linfo->name, linfo);
1466                 } else {
1467                         g_warning("got list response but no current listing job happening?\n");
1468                         imapx_free_list (linfo);
1469                 }
1470                 break;
1471         }
1472         case IMAPX_RECENT:
1473                 c(is->tagprefix, "recent: %d\n", id);
1474                 is->recent = id;
1475                 break;
1476         case IMAPX_STATUS: {
1477                 struct _state_info *sinfo = imapx_parse_status_info (is->stream, cancellable, error);
1478                 if (sinfo) {
1479                         CamelIMAPXStoreSummary *s = ((CamelIMAPXStore *) is->store)->summary;
1480                         CamelIMAPXStoreNamespace *ns;
1481                         CamelIMAPXFolder *ifolder = NULL;;
1482
1483                         ns = camel_imapx_store_summary_namespace_find_full (s, sinfo->name);
1484                         if (ns) {
1485                                 gchar *path_name;
1486
1487                                 path_name = camel_imapx_store_summary_full_to_path (s, sinfo->name, ns->sep);
1488                                 c(is->tagprefix, "Got folder path '%s' for full '%s'\n", path_name, sinfo->name);
1489                                 if (path_name) {
1490                                         ifolder = (gpointer) camel_store_get_folder_sync (is->store, path_name, 0, cancellable, error);
1491                                         g_free (path_name);
1492                                 }
1493                         }
1494                         if (ifolder) {
1495                                 CamelFolder *cfolder = CAMEL_FOLDER (ifolder);
1496
1497                                 ifolder->unread_on_server = sinfo->unseen;
1498                                 ifolder->exists_on_server = sinfo->messages;
1499                                 ifolder->modseq_on_server = sinfo->highestmodseq;
1500                                 ifolder->uidnext_on_server = sinfo->uidnext;
1501                                 ifolder->uidvalidity_on_server = sinfo->uidvalidity;
1502                                 if (sinfo->uidvalidity && sinfo->uidvalidity != ((CamelIMAPXSummary *) cfolder->summary)->validity)
1503                                         invalidate_local_cache (ifolder, sinfo->uidvalidity);
1504                         } else {
1505                                 c(is->tagprefix, "Received STATUS for unknown folder '%s'\n", sinfo->name);
1506                         }
1507
1508                         g_free (sinfo->name);
1509                         g_free (sinfo);
1510                 }
1511                 break;
1512         }
1513         case IMAPX_BYE: {
1514                 guchar *token;
1515
1516                 if (camel_imapx_stream_text (is->stream, &token, cancellable, NULL)) {
1517                         c(is->tagprefix, "BYE: %s\n", token);
1518                         g_set_error (
1519                                 error, CAMEL_IMAPX_ERROR, 1,
1520                                 "IMAP server said BYE: %s", token);
1521                 }
1522                 is->state = IMAPX_SHUTDOWN;
1523                 return FALSE;
1524         }
1525         case IMAPX_PREAUTH:
1526                 c(is->tagprefix, "preauthenticated\n");
1527                 if (is->state < IMAPX_AUTHENTICATED)
1528                         is->state = IMAPX_AUTHENTICATED;
1529                 /* fall through... */
1530         case IMAPX_OK: case IMAPX_NO: case IMAPX_BAD:
1531                 /* TODO: validate which ones of these can happen as unsolicited responses */
1532                 /* TODO: handle bye/preauth differently */
1533                 camel_imapx_stream_ungettoken (is->stream, tok, token, len);
1534                 sinfo = imapx_parse_status (is->stream, cancellable, error);
1535                 if (sinfo == NULL)
1536                         return FALSE;
1537                 switch (sinfo->condition) {
1538                 case IMAPX_CLOSED:
1539                         c(is->tagprefix, "previously selected folder is now closed\n");
1540                         if (is->select_pending && !is->select_folder) {
1541                                 is->select_folder = is->select_pending;
1542                         }
1543                         break;
1544                 case IMAPX_READ_WRITE:
1545                         is->mode = IMAPX_MODE_READ | IMAPX_MODE_WRITE;
1546                         c(is->tagprefix, "folder is read-write\n");
1547                         break;
1548                 case IMAPX_READ_ONLY:
1549                         is->mode = IMAPX_MODE_READ;
1550                         c(is->tagprefix, "folder is read-only\n");
1551                         break;
1552                 case IMAPX_UIDVALIDITY:
1553                         is->uidvalidity = sinfo->u.uidvalidity;
1554                         break;
1555                 case IMAPX_UNSEEN:
1556                         is->unseen = sinfo->u.unseen;
1557                         break;
1558                 case IMAPX_HIGHESTMODSEQ:
1559                         is->highestmodseq = sinfo->u.highestmodseq;
1560                         break;
1561                 case IMAPX_PERMANENTFLAGS:
1562                         is->permanentflags = sinfo->u.permanentflags;
1563                         break;
1564                 case IMAPX_UIDNEXT:
1565                         is->uidnext = sinfo->u.uidnext;
1566                         break;
1567                 case IMAPX_ALERT:
1568                         c(is->tagprefix, "ALERT!: %s\n", sinfo->text);
1569                         break;
1570                 case IMAPX_PARSE:
1571                         c(is->tagprefix, "PARSE: %s\n", sinfo->text);
1572                         break;
1573                 case IMAPX_CAPABILITY:
1574                         if (sinfo->u.cinfo) {
1575                                 struct _capability_info *cinfo = is->cinfo;
1576                                 is->cinfo = sinfo->u.cinfo;
1577                                 sinfo->u.cinfo = NULL;
1578                                 if (cinfo)
1579                                         imapx_free_capability (cinfo);
1580                                 c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
1581                         }
1582                         break;
1583                 default:
1584                         break;
1585                 }
1586                 imapx_free_status (sinfo);
1587                 return TRUE;
1588         default:
1589                 /* unknown response, just ignore it */
1590                 c(is->tagprefix, "unknown token: %s\n", token);
1591         }
1592
1593         return (camel_imapx_stream_skip (is->stream, cancellable, error) == 0);
1594 }
1595
1596 /* handle any continuation requests
1597  * either data continuations, or auth continuation */
1598 static gboolean
1599 imapx_continuation (CamelIMAPXServer *is,
1600                     gboolean litplus,
1601                     GCancellable *cancellable,
1602                     GError **error)
1603 {
1604         CamelIMAPXCommand *ic, *newliteral = NULL;
1605         CamelIMAPXCommandPart *cp;
1606         GList *link;
1607
1608         /* The 'literal' pointer is like a write-lock, nothing else
1609          * can write while we have it ... so we dont need any
1610          * ohter lock here.  All other writes go through
1611          * queue-lock */
1612         if (imapx_idle_supported (is) && imapx_in_idle (is)) {
1613                 camel_imapx_stream_skip (is->stream, cancellable, error);
1614
1615                 c(is->tagprefix, "Got continuation response for IDLE \n");
1616                 IDLE_LOCK (is->idle);
1617                 /* We might have actually sent the DONE already! */
1618                 if (is->idle->state == IMAPX_IDLE_ISSUED)
1619                         is->idle->state = IMAPX_IDLE_STARTED;
1620                 else if (is->idle->state == IMAPX_IDLE_CANCEL) {
1621                         /* IDLE got cancelled after we sent the command, while
1622                          * we were waiting for this continuation. Send DONE
1623                          * immediately. */
1624                         if (!imapx_command_idle_stop (is, error)) {
1625                                 IDLE_UNLOCK (is->idle);
1626                                 return FALSE;
1627                         }
1628                         is->idle->state = IMAPX_IDLE_OFF;
1629                 } else {
1630                         c(is->tagprefix, "idle starts in wrong state %d\n",
1631                                  is->idle->state);
1632                 }
1633                 IDLE_UNLOCK (is->idle);
1634
1635                 QUEUE_LOCK (is);
1636                 is->literal = NULL;
1637                 imapx_command_start_next (is, cancellable, error);
1638                 QUEUE_UNLOCK (is);
1639
1640                 return TRUE;
1641         }
1642
1643         ic = is->literal;
1644         if (!litplus) {
1645                 if (ic == NULL) {
1646                         camel_imapx_stream_skip (is->stream, cancellable, error);
1647                         c(is->tagprefix, "got continuation response with no outstanding continuation requests?\n");
1648                         return TRUE;
1649                 }
1650                 c(is->tagprefix, "got continuation response for data\n");
1651         } else {
1652                 c(is->tagprefix, "sending LITERAL+ continuation\n");
1653         }
1654
1655         link = ic->current_part;
1656         g_return_val_if_fail (link != NULL, FALSE);
1657         cp = (CamelIMAPXCommandPart *) link->data;
1658
1659         switch (cp->type & CAMEL_IMAPX_COMMAND_MASK) {
1660         case CAMEL_IMAPX_COMMAND_DATAWRAPPER:
1661                 c(is->tagprefix, "writing data wrapper to literal\n");
1662                 camel_data_wrapper_write_to_stream_sync ((CamelDataWrapper *) cp->ob, (CamelStream *) is->stream, cancellable, NULL);
1663                 break;
1664         case CAMEL_IMAPX_COMMAND_STREAM:
1665                 c(is->tagprefix, "writing stream to literal\n");
1666                 camel_stream_write_to_stream ((CamelStream *) cp->ob, (CamelStream *) is->stream, cancellable, NULL);
1667                 break;
1668         case CAMEL_IMAPX_COMMAND_AUTH: {
1669                 gchar *resp;
1670                 guchar *token;
1671
1672                 if (camel_imapx_stream_text (is->stream, &token, cancellable, error))
1673                         return FALSE;
1674
1675                 resp = camel_sasl_challenge_base64_sync (
1676                         (CamelSasl *) cp->ob, (const gchar *) token,
1677                         cancellable, error);
1678                 g_free (token);
1679                 if (resp == NULL)
1680                         return FALSE;
1681                 c(is->tagprefix, "got auth continuation, feeding token '%s' back to auth mech\n", resp);
1682
1683                 camel_stream_write ((CamelStream *) is->stream, resp, strlen (resp), cancellable, NULL);
1684                 g_free (resp);
1685                 /* we want to keep getting called until we get a status reponse from the server
1686                  * ignore what sasl tells us */
1687                 newliteral = ic;
1688                 /* We already ate the end of the input stream line */
1689                 goto noskip;
1690                 break; }
1691         case CAMEL_IMAPX_COMMAND_FILE: {
1692                 CamelStream *file;
1693
1694                 c(is->tagprefix, "writing file '%s' to literal\n", (gchar *)cp->ob);
1695
1696                 // FIXME: errors
1697                 if (cp->ob && (file = camel_stream_fs_new_with_name (cp->ob, O_RDONLY, 0, NULL))) {
1698                         camel_stream_write_to_stream (file, (CamelStream *) is->stream, cancellable, NULL);
1699                         g_object_unref (file);
1700                 } else if (cp->ob_size > 0) {
1701                         // Server is expecting data ... ummm, send it zeros?  abort?
1702                 }
1703                 break; }
1704         case CAMEL_IMAPX_COMMAND_STRING:
1705                 camel_stream_write ((CamelStream *) is->stream, cp->ob, cp->ob_size, cancellable, NULL);
1706                 break;
1707         default:
1708                 /* should we just ignore? */
1709                 is->literal = NULL;
1710                 g_set_error (
1711                         error, CAMEL_IMAPX_ERROR, 1,
1712                         "continuation response for non-continuation request");
1713                 return FALSE;
1714         }
1715
1716         if (!litplus)
1717                 camel_imapx_stream_skip (is->stream, cancellable, error);
1718
1719 noskip:
1720         link = g_list_next (link);
1721         if (link != NULL) {
1722                 ic->current_part = link;
1723                 cp = (CamelIMAPXCommandPart *) link->data;
1724
1725                 c(is->tagprefix, "next part of command \"%c%05u: %s\"\n", is->tagprefix, ic->tag, cp->data);
1726                 camel_stream_write_string ((CamelStream *) is->stream, cp->data, cancellable, NULL);
1727                 camel_stream_write_string ((CamelStream *) is->stream, "\r\n", cancellable, NULL);
1728                 if (cp->type & (CAMEL_IMAPX_COMMAND_CONTINUATION | CAMEL_IMAPX_COMMAND_LITERAL_PLUS)) {
1729                         newliteral = ic;
1730                 } else {
1731                         g_assert (g_list_next (link) == NULL);
1732                 }
1733         } else {
1734                 c(is->tagprefix, "%p: queueing continuation\n", ic);
1735                 camel_stream_write_string((CamelStream *)is->stream, "\r\n", cancellable, NULL);
1736         }
1737
1738         QUEUE_LOCK (is);
1739         is->literal = newliteral;
1740
1741         if (!litplus)
1742                 imapx_command_start_next (is, cancellable, error);
1743         QUEUE_UNLOCK (is);
1744
1745         return TRUE;
1746 }
1747
1748 /* handle a completion line */
1749 static gboolean
1750 imapx_completion (CamelIMAPXServer *is,
1751                   guchar *token,
1752                   gint len,
1753                   GCancellable *cancellable,
1754                   GError **error)
1755 {
1756         CamelIMAPXCommand *ic;
1757         guint tag;
1758
1759         /* Given "A0001 ...", 'A' = tag prefix, '0001' = tag. */
1760
1761         if (token[0] != is->tagprefix) {
1762                 g_set_error (
1763                         error, CAMEL_IMAPX_ERROR, 1,
1764                         "Server sent unexpected response: %s", token);
1765                 return FALSE;
1766         }
1767
1768         tag = strtoul ((gchar *) token + 1, NULL, 10);
1769
1770         if ((ic = imapx_find_command_tag (is, tag)) == NULL) {
1771                 g_set_error (
1772                         error, CAMEL_IMAPX_ERROR, 1,
1773                         "got response tag unexpectedly: %s", token);
1774                 return FALSE;
1775         }
1776
1777         c(is->tagprefix, "Got completion response for command %05u '%s'\n", ic->tag, ic->name);
1778
1779         if (camel_folder_change_info_changed (is->changes)) {
1780                 camel_folder_summary_save_to_db (is->select_folder->summary, NULL);
1781
1782                 g_list_free_full (is->expunged, (GDestroyNotify) g_free);
1783                 is->expunged = NULL;
1784
1785                 imapx_update_store_summary (is->select_folder);
1786                 camel_folder_changed (is->select_folder, is->changes);
1787                 camel_folder_change_info_clear (is->changes);
1788         }
1789
1790         QUEUE_LOCK (is);
1791
1792         camel_imapx_command_ref (ic);
1793         camel_imapx_command_queue_remove (is->active, ic);
1794         camel_imapx_command_queue_push_tail (is->done, ic);
1795         camel_imapx_command_unref (ic);
1796
1797         if (is->literal == ic)
1798                 is->literal = NULL;
1799
1800         if (g_list_next (ic->current_part) != NULL) {
1801                 QUEUE_UNLOCK (is);
1802                 g_set_error (
1803                         error, CAMEL_IMAPX_ERROR, 1,
1804                         "command still has unsent parts? %s", ic->name);
1805                 return FALSE;
1806         }
1807
1808         camel_imapx_command_queue_remove (is->done, ic);
1809
1810         QUEUE_UNLOCK (is);
1811
1812         ic->status = imapx_parse_status (is->stream, cancellable, error);
1813
1814         if (ic->status == NULL)
1815                 return FALSE;
1816
1817         if (ic->complete != NULL)
1818                 if (!ic->complete (is, ic, error))
1819                         return FALSE;
1820
1821         QUEUE_LOCK (is);
1822         imapx_command_start_next (is, cancellable, error);
1823         QUEUE_UNLOCK (is);
1824
1825         return TRUE;
1826 }
1827
1828 static gboolean
1829 imapx_step (CamelIMAPXServer *is,
1830             GCancellable *cancellable,
1831             GError **error)
1832 {
1833         guint len;
1834         guchar *token;
1835         gint tok;
1836
1837         // poll ?  wait for other stuff? loop?
1838         tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
1839         if (tok < 0)
1840                 return FALSE;
1841
1842         if (tok == '*')
1843                 return imapx_untagged (is, cancellable, error);
1844         else if (tok == IMAPX_TOK_TOKEN)
1845                 return imapx_completion (is, token, len, cancellable, error);
1846         else if (tok == '+')
1847                 return imapx_continuation (is, FALSE, cancellable, error);
1848
1849         g_set_error (
1850                 error, CAMEL_IMAPX_ERROR, 1,
1851                 "unexpected server response:");
1852
1853         return FALSE;
1854 }
1855
1856 /* Used to run 1 command synchronously,
1857  * use for capa, login, and namespaces only. */
1858 static gboolean
1859 imapx_command_run (CamelIMAPXServer *is,
1860                    CamelIMAPXCommand *ic,
1861                    GCancellable *cancellable,
1862                    GError **error)
1863 /* throws IO,PARSE exception */
1864 {
1865         gboolean success = TRUE;
1866
1867         camel_imapx_command_close (ic);
1868
1869         QUEUE_LOCK (is);
1870         imapx_command_start (is, ic, cancellable, error);
1871         QUEUE_UNLOCK (is);
1872
1873         while (success && ic->status == NULL)
1874                 success = imapx_step (is, cancellable, error);
1875
1876         if (is->literal == ic)
1877                 is->literal = NULL;
1878
1879         QUEUE_LOCK (is);
1880         camel_imapx_command_queue_remove (is->active, ic);
1881         QUEUE_UNLOCK (is);
1882
1883         return success;
1884 }
1885
1886 static gboolean
1887 imapx_command_complete (CamelIMAPXServer *is,
1888                         CamelIMAPXCommand *ic,
1889                         GError **error)
1890 {
1891         camel_imapx_command_done (ic);
1892         camel_imapx_command_unref (ic);
1893
1894         return TRUE;
1895 }
1896
1897 static void
1898 imapx_command_cancelled (GCancellable *cancellable,
1899                          CamelIMAPXCommand *ic)
1900 {
1901         /* Unblock imapx_command_run_sync() immediately.
1902          *
1903          * If camel_imapx_command_done() is called sometime later,
1904          * the GCond will broadcast but no one will be listening. */
1905
1906         camel_imapx_command_done (ic);
1907 }
1908
1909 /* The caller should free the command as well */
1910 static gboolean
1911 imapx_command_run_sync (CamelIMAPXServer *is,
1912                         CamelIMAPXCommand *ic,
1913                         GCancellable *cancellable,
1914                         GError **error)
1915 {
1916         guint cancel_id = 0;
1917
1918         /* FIXME The only caller of this function currently does not set
1919          *       a "complete" callback function, so we can get away with
1920          *       referencing the command here and dropping the reference
1921          *       in imapx_command_complete().  The queueing/dequeueing
1922          *       of these things is too complex for my little mind, so
1923          *       we may have to revisit the reference counting if this
1924          *       function gets another caller. */
1925
1926         g_warn_if_fail (ic->complete == NULL);
1927         ic->complete = imapx_command_complete;
1928
1929         if (G_IS_CANCELLABLE (cancellable))
1930                 cancel_id = g_cancellable_connect (
1931                         cancellable,
1932                         G_CALLBACK (imapx_command_cancelled),
1933                         camel_imapx_command_ref (ic),
1934                         (GDestroyNotify) camel_imapx_command_unref);
1935
1936         /* Unref'ed in imapx_command_complete(). */
1937         camel_imapx_command_ref (ic);
1938
1939         imapx_command_queue (is, ic);
1940
1941         camel_imapx_command_wait (ic);
1942
1943         if (cancel_id > 0)
1944                 g_cancellable_disconnect (cancellable, cancel_id);
1945
1946         if (g_cancellable_set_error_if_cancelled (cancellable, error))
1947                 return FALSE;
1948
1949         if (camel_imapx_command_set_error_if_failed (ic, error))
1950                 return FALSE;
1951
1952         return TRUE;
1953 }
1954
1955 static gboolean
1956 imapx_register_job (CamelIMAPXServer *is,
1957                     CamelIMAPXJob *job,
1958                     GError **error)
1959 {
1960         if (is->state >= IMAPX_INITIALISED) {
1961                 QUEUE_LOCK (is);
1962                 g_queue_push_head (&is->jobs, camel_imapx_job_ref (job));
1963                 QUEUE_UNLOCK (is);
1964
1965         } else {
1966                 e (is->tagprefix, "NO connection yet, maybe user cancelled jobs earlier ?");
1967                 g_set_error (
1968                         error, CAMEL_SERVICE_ERROR,
1969                         CAMEL_SERVICE_ERROR_NOT_CONNECTED,
1970                         _("Not authenticated"));
1971                 return FALSE;
1972         }
1973
1974         return TRUE;
1975 }
1976
1977 static void
1978 imapx_unregister_job (CamelIMAPXServer *is,
1979                       CamelIMAPXJob *job)
1980 {
1981         if (!job->noreply)
1982                 camel_imapx_job_done (job);
1983
1984         QUEUE_LOCK (is);
1985         if (g_queue_remove (&is->jobs, job))
1986                 camel_imapx_job_unref (job);
1987         QUEUE_UNLOCK (is);
1988 }
1989
1990 static gboolean
1991 imapx_submit_job (CamelIMAPXServer *is,
1992                   CamelIMAPXJob *job,
1993                   GError **error)
1994 {
1995         if (!imapx_register_job (is, job, error))
1996                 return FALSE;
1997
1998         return camel_imapx_job_run (job, is, error);
1999 }
2000
2001 /* ********************************************************************** */
2002 // IDLE support
2003
2004 /*TODO handle negative cases sanely */
2005 static gboolean
2006 imapx_command_idle_stop (CamelIMAPXServer *is,
2007                          GError **error)
2008 {
2009         if (!is->stream || camel_stream_write_string ((CamelStream *)is->stream, "DONE\r\n", NULL, NULL) == -1) {
2010                 g_set_error (
2011                         error, CAMEL_IMAPX_ERROR, 1,
2012                         "Unable to issue DONE");
2013                 c(is->tagprefix, "Failed to issue DONE to terminate IDLE\n");
2014                 is->state = IMAPX_SHUTDOWN;
2015                 is->parser_quit = TRUE;
2016                 if (is->cancellable)
2017                         g_cancellable_cancel (is->cancellable);
2018                 return FALSE;
2019         }
2020
2021         return TRUE;
2022 }
2023
2024 static gboolean
2025 imapx_command_idle_done (CamelIMAPXServer *is,
2026                          CamelIMAPXCommand *ic,
2027                          GError **error)
2028 {
2029         CamelIMAPXIdle *idle = is->idle;
2030         CamelIMAPXJob *job;
2031         gboolean success = TRUE;
2032
2033         job = camel_imapx_command_get_job (ic);
2034         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
2035
2036         if (camel_imapx_command_set_error_if_failed (ic, error)) {
2037                 g_prefix_error (
2038                         error, "%s: ",
2039                         _("Error performing IDLE"));
2040                 success = FALSE;
2041         }
2042
2043         IDLE_LOCK (idle);
2044         idle->state = IMAPX_IDLE_OFF;
2045         IDLE_UNLOCK (idle);
2046
2047         imapx_unregister_job (is, job);
2048         camel_imapx_command_unref (ic);
2049
2050         return success;
2051 }
2052
2053 static void
2054 imapx_job_idle_start (CamelIMAPXJob *job,
2055                       CamelIMAPXServer *is)
2056 {
2057         CamelIMAPXCommand *ic;
2058         CamelIMAPXCommandPart *cp;
2059
2060         ic = camel_imapx_command_new (
2061                 is, "IDLE", job->folder, "IDLE");
2062         camel_imapx_command_set_job (ic, job);
2063         ic->pri = job->pri;
2064         ic->complete = imapx_command_idle_done;
2065
2066         camel_imapx_command_close (ic);
2067         cp = g_queue_peek_head (&ic->parts);
2068         cp->type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
2069
2070         QUEUE_LOCK (is);
2071         IDLE_LOCK (is->idle);
2072         /* Don't issue it if the idle was cancelled already */
2073         if (is->idle->state == IMAPX_IDLE_PENDING) {
2074                 is->idle->state = IMAPX_IDLE_ISSUED;
2075                 imapx_command_start (is, ic, job->cancellable, &job->error);
2076         } else {
2077                 imapx_unregister_job (is, job);
2078                 camel_imapx_command_unref (ic);
2079         }
2080         IDLE_UNLOCK (is->idle);
2081         QUEUE_UNLOCK (is);
2082 }
2083
2084 static gboolean
2085 camel_imapx_server_idle (CamelIMAPXServer *is,
2086                          CamelFolder *folder,
2087                          GCancellable *cancellable,
2088                          GError **error)
2089 {
2090         CamelIMAPXJob *job;
2091         gboolean success;
2092
2093         job = camel_imapx_job_new (cancellable);
2094         job->type = IMAPX_JOB_IDLE;
2095         job->start = imapx_job_idle_start;
2096         job->folder = folder;
2097
2098         success = imapx_submit_job (is, job, error);
2099
2100         camel_imapx_job_unref (job);
2101
2102         return success;
2103 }
2104
2105 static gboolean
2106 imapx_job_fetch_new_messages_matches (CamelIMAPXJob *job,
2107                                       CamelFolder *folder,
2108                                       const gchar *uid)
2109 {
2110         return (folder == job->folder);
2111 }
2112
2113 static gboolean
2114 imapx_server_fetch_new_messages (CamelIMAPXServer *is,
2115                                  CamelFolder *folder,
2116                                  gboolean async,
2117                                  gboolean update_unseen,
2118                                  GCancellable *cancellable,
2119                                  GError **error)
2120 {
2121         CamelIMAPXJob *job;
2122         RefreshInfoData *data;
2123         gboolean success;
2124
2125         data = g_slice_new0 (RefreshInfoData);
2126         data->changes = camel_folder_change_info_new ();
2127         data->update_unseen = update_unseen;
2128         data->fetch_msg_limit = -1;
2129
2130         job = camel_imapx_job_new (cancellable);
2131         job->type = IMAPX_JOB_FETCH_NEW_MESSAGES;
2132         job->start = imapx_job_fetch_new_messages_start;
2133         job->matches = imapx_job_fetch_new_messages_matches;
2134         job->folder = folder;
2135         job->noreply = async;
2136
2137         camel_imapx_job_set_data (
2138                 job, data, (GDestroyNotify) refresh_info_data_free);
2139
2140         success = imapx_submit_job (is, job, error);
2141
2142         camel_imapx_job_unref (job);
2143
2144         return success;
2145 }
2146
2147 static gpointer
2148 imapx_idle_thread (gpointer data)
2149 {
2150         CamelIMAPXServer *is = (CamelIMAPXServer *) data;
2151         GError *local_error = NULL;
2152
2153         while (TRUE) {
2154                 CamelIMAPXFolder *ifolder;
2155
2156                 g_mutex_lock (is->idle->start_watch_mutex);
2157                 is->idle->start_watch_is_set = FALSE;
2158                 g_mutex_unlock (is->idle->start_watch_mutex);
2159
2160                 IDLE_LOCK (is->idle);
2161                 while ((ifolder = (CamelIMAPXFolder *) is->select_folder) &&
2162                        is->idle->state == IMAPX_IDLE_PENDING &&
2163                        !is->idle->idle_exit) {
2164                         time_t dwelled = time (NULL) - is->idle->started;
2165
2166                         if (dwelled < IMAPX_IDLE_DWELL_TIME) {
2167                                 IDLE_UNLOCK (is->idle);
2168                                 g_usleep ((IMAPX_IDLE_DWELL_TIME - dwelled) * G_USEC_PER_SEC);
2169                                 IDLE_LOCK (is->idle);
2170                                 continue;
2171                         }
2172                         IDLE_UNLOCK (is->idle);
2173
2174                         camel_imapx_server_idle (is, (gpointer) ifolder, is->cancellable, &local_error);
2175
2176                         if (local_error == NULL && ifolder->exists_on_server >
2177                             camel_folder_summary_count (((CamelFolder *) ifolder)->summary) && imapx_is_command_queue_empty (is))
2178                                 imapx_server_fetch_new_messages (is, is->select_folder, TRUE, TRUE, is->cancellable, &local_error);
2179
2180                         if (local_error != NULL) {
2181                                 e (is->tagprefix, "Caught exception in idle thread:  %s \n", local_error->message);
2182                                 /* No way to asyncronously notify UI ? */
2183                                 g_clear_error (&local_error);
2184                         }
2185                         IDLE_LOCK (is->idle);
2186                 }
2187                 IDLE_UNLOCK (is->idle);
2188
2189                 g_mutex_lock (is->idle->start_watch_mutex);
2190                 while (!is->idle->start_watch_is_set)
2191                         g_cond_wait (
2192                                 is->idle->start_watch_cond,
2193                                 is->idle->start_watch_mutex);
2194                 g_mutex_unlock (is->idle->start_watch_mutex);
2195
2196                 if (is->idle->idle_exit)
2197                         break;
2198         }
2199
2200         g_clear_error (&local_error);
2201         is->idle->idle_thread = NULL;
2202         return NULL;
2203 }
2204
2205 static gboolean
2206 imapx_stop_idle (CamelIMAPXServer *is,
2207                  GError **error)
2208 {
2209         CamelIMAPXIdle *idle = is->idle;
2210         gint stopped = FALSE;
2211         time_t now;
2212
2213         time (&now);
2214         IDLE_LOCK (idle);
2215
2216         switch (idle->state) {
2217         case IMAPX_IDLE_ISSUED:
2218                 idle->state = IMAPX_IDLE_CANCEL;
2219         case IMAPX_IDLE_CANCEL:
2220                 stopped = TRUE;
2221                 break;
2222
2223         case IMAPX_IDLE_STARTED:
2224                 /* We set 'stopped' even if sending DONE fails, to ensure that
2225                  * our caller doesn't try to submit its own command. */
2226                 stopped = TRUE;
2227                 if (!imapx_command_idle_stop (is, error))
2228                         break;
2229
2230                 idle->state = IMAPX_IDLE_OFF;
2231                 c(is->tagprefix, "Stopping idle after %ld seconds\n",
2232                   (long)(now - idle->started));
2233         case IMAPX_IDLE_PENDING:
2234                 idle->state = IMAPX_IDLE_OFF;
2235         case IMAPX_IDLE_OFF:
2236                 break;
2237         }
2238         IDLE_UNLOCK (idle);
2239
2240         return stopped;
2241 }
2242
2243 static void
2244 imapx_init_idle (CamelIMAPXServer *is)
2245 {
2246         is->idle = g_new0 (CamelIMAPXIdle, 1);
2247         is->idle->idle_lock = g_mutex_new ();
2248 }
2249
2250 static void
2251 imapx_exit_idle (CamelIMAPXServer *is)
2252 {
2253         CamelIMAPXIdle *idle = is->idle;
2254         GThread *thread = NULL;
2255
2256         if (!idle)
2257                 return;
2258
2259         IDLE_LOCK (idle);
2260
2261         if (idle->idle_thread) {
2262                 idle->idle_exit = TRUE;
2263
2264                 g_mutex_lock (idle->start_watch_mutex);
2265                 idle->start_watch_is_set = TRUE;
2266                 g_cond_broadcast (idle->start_watch_cond);
2267                 g_mutex_unlock (idle->start_watch_mutex);
2268
2269                 thread = idle->idle_thread;
2270                 idle->idle_thread = 0;
2271         }
2272
2273         idle->idle_thread = NULL;
2274         IDLE_UNLOCK (idle);
2275
2276         if (thread)
2277                 g_thread_join (thread);
2278
2279         g_mutex_free (idle->idle_lock);
2280
2281         if (idle->start_watch_cond != NULL)
2282                 g_cond_free (idle->start_watch_cond);
2283
2284         if (idle->start_watch_mutex != NULL)
2285                 g_mutex_free (idle->start_watch_mutex);
2286
2287         g_free (is->idle);
2288         is->idle = NULL;
2289 }
2290
2291 static void
2292 imapx_start_idle (CamelIMAPXServer *is)
2293 {
2294         CamelIMAPXIdle *idle = is->idle;
2295
2296         if (camel_application_is_exiting)
2297                 return;
2298
2299         IDLE_LOCK (idle);
2300
2301         g_assert (idle->state == IMAPX_IDLE_OFF);
2302         time (&idle->started);
2303         idle->state = IMAPX_IDLE_PENDING;
2304
2305         if (!idle->idle_thread) {
2306                 idle->start_watch_cond = g_cond_new ();
2307                 idle->start_watch_mutex = g_mutex_new ();
2308                 idle->start_watch_is_set = FALSE;
2309
2310                 idle->idle_thread = g_thread_create (
2311                         (GThreadFunc) imapx_idle_thread, is, TRUE, NULL);
2312         } else {
2313                 g_mutex_lock (idle->start_watch_mutex);
2314                 idle->start_watch_is_set = TRUE;
2315                 g_cond_broadcast (idle->start_watch_cond);
2316                 g_mutex_unlock (idle->start_watch_mutex);
2317         }
2318
2319         IDLE_UNLOCK (idle);
2320 }
2321
2322 static gboolean
2323 imapx_in_idle (CamelIMAPXServer *is)
2324 {
2325         gboolean ret = FALSE;
2326         CamelIMAPXIdle *idle = is->idle;
2327
2328         IDLE_LOCK (idle);
2329         ret = (idle->state > IMAPX_IDLE_OFF);
2330         IDLE_UNLOCK (idle);
2331
2332         return ret;
2333 }
2334
2335 static gboolean
2336 imapx_idle_supported (CamelIMAPXServer *is)
2337 {
2338         return (is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_IDLE) != 0 && is->use_idle);
2339 }
2340
2341 // end IDLE
2342 /* ********************************************************************** */
2343 static gboolean
2344 imapx_command_select_done (CamelIMAPXServer *is,
2345                            CamelIMAPXCommand *ic,
2346                            GError **error)
2347 {
2348         const gchar *selected_folder = NULL;
2349         gboolean success = TRUE;
2350         GError *local_error = NULL;
2351
2352         if (camel_imapx_command_set_error_if_failed (ic, &local_error)) {
2353                 GQueue failed = G_QUEUE_INIT;
2354                 GQueue trash = G_QUEUE_INIT;
2355                 GList *link;
2356
2357                 c(is->tagprefix, "Select failed\n");
2358
2359                 QUEUE_LOCK (is);
2360
2361                 if (is->select_pending) {
2362                         GList *head = camel_imapx_command_queue_peek_head_link (is->queue);
2363
2364                         for (link = head; link != NULL; link = g_list_next (link)) {
2365                                 CamelIMAPXCommand *cw = link->data;
2366
2367                                 if (cw->select && cw->select == is->select_pending) {
2368                                         c(is->tagprefix, "Cancelling command '%s'(%p) for folder '%s'\n",
2369                                           cw->name, cw, camel_folder_get_full_name (cw->select));
2370                                         g_queue_push_tail (&trash, link);
2371                                 }
2372                         }
2373                 }
2374
2375                 while ((link = g_queue_pop_head (&trash)) != NULL) {
2376                         CamelIMAPXCommand *cw = link->data;
2377                         camel_imapx_command_queue_delete_link (is->queue, link);
2378                         g_queue_push_tail (&failed, cw);
2379                 }
2380
2381                 QUEUE_UNLOCK (is);
2382
2383                 while (!g_queue_is_empty (&failed)) {
2384                         CamelIMAPXCommand *cw;
2385                         CamelIMAPXJob *job;
2386
2387                         cw = g_queue_pop_head (&failed);
2388                         job = camel_imapx_command_get_job (cw);
2389
2390                         if (!CAMEL_IS_IMAPX_JOB (job)) {
2391                                 g_warn_if_reached ();
2392                                 continue;
2393                         }
2394
2395                         if (ic->status)
2396                                 cw->status = imapx_copy_status (ic->status);
2397                         if (job->error == NULL) {
2398                                 if (ic->status == NULL)
2399                                         /* FIXME: why is ic->status == NULL here? It shouldn't happen. */
2400                                         g_debug ("imapx_command_select_done: ic->status is NULL.");
2401                                 g_set_error (
2402                                         &job->error,
2403                                         CAMEL_IMAPX_ERROR, 1,
2404                                         "SELECT %s failed: %s",
2405                                         camel_folder_get_full_name (cw->select),
2406                                         ic->status && ic->status->text? ic->status->text:"<unknown reason>");
2407                         }
2408                         cw->complete (is, cw, NULL);
2409                 }
2410
2411                 if (is->select_pending)
2412                         g_object_unref (is->select_pending);
2413
2414                 /* A [CLOSED] status may have caused us to assume that it had happened */
2415                 if (is->select_folder)
2416                         is->select_folder = NULL;
2417
2418                 is->state = IMAPX_INITIALISED;
2419
2420                 g_propagate_error (error, local_error);
2421                 success = FALSE;
2422
2423         } else {
2424                 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) is->select_pending;
2425                 CamelFolder *cfolder = is->select_pending;
2426
2427                 c(is->tagprefix, "Select ok!\n");
2428
2429                 if (!is->select_folder) {
2430                         /* This could have been done earlier by a [CLOSED] status */
2431                         is->select_folder = is->select_pending;
2432                 }
2433                 is->state = IMAPX_SELECTED;
2434                 ifolder->exists_on_server = is->exists;
2435                 ifolder->modseq_on_server = is->highestmodseq;
2436                 if (ifolder->uidnext_on_server < is->uidnext) {
2437                         imapx_server_fetch_new_messages (is, is->select_pending, TRUE, TRUE, NULL, NULL);
2438                         /* We don't do this right now because we want the new messages to
2439                          * update the unseen count. */
2440                         //ifolder->uidnext_on_server = is->uidnext;
2441                 }
2442                 ifolder->uidvalidity_on_server = is->uidvalidity;
2443                 selected_folder = camel_folder_get_full_name (is->select_folder);
2444
2445                 if (is->uidvalidity && is->uidvalidity != ((CamelIMAPXSummary *) cfolder->summary)->validity)
2446                         invalidate_local_cache (ifolder, is->uidvalidity);
2447
2448 #if 0
2449                 /* This should trigger a new messages scan */
2450                 if (is->exists != is->select_folder->summary->root_view->total_count)
2451                         g_warning("exists is %d our summary is %d and summary exists is %d\n", is->exists,
2452                                   is->select_folder->summary->root_view->total_count,
2453                                   ((CamelIMAPXSummary *) is->select_folder->summary)->exists);
2454 #endif
2455         }
2456
2457         is->select_pending = NULL;
2458         camel_imapx_command_unref (ic);
2459
2460         g_signal_emit (is, signals[SELECT_CHANGED], 0, selected_folder);
2461
2462         return success;
2463 }
2464
2465 /* Should have a queue lock. TODO Change the way select is written */
2466 static gboolean
2467 imapx_select (CamelIMAPXServer *is,
2468               CamelFolder *folder,
2469               gboolean forced,
2470               GCancellable *cancellable,
2471               GError **error)
2472 {
2473         CamelIMAPXCommand *ic;
2474
2475         /* Select is complicated by the fact we may have commands
2476          * active on the server for a different selection.
2477          *
2478          * So this waits for any commands to complete, selects the
2479          * new folder, and halts the queuing of any new commands.
2480          * It is assumed whomever called is us about to issue
2481          * a high-priority command anyway */
2482
2483         /* TODO check locking here, pending_select will do
2484          * most of the work for normal commands, but not
2485          * for another select */
2486
2487         if (is->select_pending)
2488                 return TRUE;
2489
2490         if (is->select_folder == folder && !forced)
2491                 return TRUE;
2492
2493         if (!camel_imapx_command_queue_is_empty (is->active))
2494                 return TRUE;
2495
2496         is->select_pending = folder;
2497         g_object_ref (folder);
2498         if (is->select_folder) {
2499                 g_object_unref (is->select_folder);
2500                 is->select_folder = NULL;
2501         } else {
2502                 /* If no folder was selected, we won't get a [CLOSED] status
2503                  * so just point select_folder at the new folder immediately */
2504                 is->select_folder = is->select_pending;
2505         }
2506
2507         is->uidvalidity = 0;
2508         is->unseen = 0;
2509         is->highestmodseq = 0;
2510         is->permanentflags = 0;
2511         is->exists = 0;
2512         is->recent = 0;
2513         is->mode = 0;
2514         is->uidnext = 0;
2515
2516         /* Hrm, what about reconnecting? */
2517         is->state = IMAPX_INITIALISED;
2518
2519         ic = camel_imapx_command_new (
2520                 is, "SELECT", NULL, "SELECT %f", folder);
2521
2522         if (is->use_qresync) {
2523                 CamelIMAPXSummary *isum = (CamelIMAPXSummary *) folder->summary;
2524                 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
2525                 gint total = camel_folder_summary_count (folder->summary);
2526                 gchar *firstuid, *lastuid;
2527
2528                 if (total && isum->modseq && ifolder->uidvalidity_on_server) {
2529
2530                         firstuid = imapx_get_uid_from_index (folder->summary, 0);
2531                         lastuid = imapx_get_uid_from_index (folder->summary, total - 1);
2532
2533                         c(is->tagprefix, "SELECT QRESYNC %" G_GUINT64_FORMAT
2534                           " %" G_GUINT64_FORMAT "\n",
2535                           ifolder->uidvalidity_on_server, isum->modseq);
2536
2537                         camel_imapx_command_add (
2538                                 ic, " (QRESYNC (%"
2539                                 G_GUINT64_FORMAT " %"
2540                                 G_GUINT64_FORMAT " %s:%s",
2541                                 ifolder->uidvalidity_on_server,
2542                                 isum->modseq,
2543                                 firstuid, lastuid);
2544
2545                         g_free (firstuid);
2546                         g_free (lastuid);
2547
2548                         if (total > 10) {
2549                                 gint i;
2550                                 GString *seqs, *uids;
2551
2552                                 seqs = g_string_new(" ");
2553                                 uids = g_string_new(")");
2554
2555                                 /* Include some seq/uid pairs to avoid a huge VANISHED list
2556                                  * (see RFC5162 Â§3.1). Work backwards exponentially from the
2557                                  * end of the mailbox, starting with the message 9 from the
2558                                  * end, then 27 from the end, then 81 from the end... */
2559                                 i = 3;
2560                                 do {
2561                                         gchar buf[10];
2562                                         gchar *uid;
2563                                         i *= 3;
2564                                         if (i > total)
2565                                                 i = total;
2566
2567                                         if (i != 9) { /* If not the first time */
2568                                                 g_string_prepend(seqs, ",");
2569                                                 g_string_prepend(uids, ",");
2570                                         }
2571
2572                                         /* IMAP sequence numbers are one higher than the corresponding
2573                                          * indices in our folder summary -- they start from one, while
2574                                          * the summary starts from zero. */
2575                                         sprintf(buf, "%d", total - i + 1);
2576                                         g_string_prepend (seqs, buf);
2577                                         uid = imapx_get_uid_from_index (folder->summary, total - i);
2578                                         g_string_prepend (uids, uid);
2579                                         g_free (uid);
2580                                 } while (i < total);
2581
2582                                 g_string_prepend(seqs, " (");
2583
2584                                 c(is->tagprefix, "adding QRESYNC seq/uidset %s%s\n", seqs->str, uids->str);
2585                                 camel_imapx_command_add (ic, seqs->str);
2586                                 camel_imapx_command_add (ic, uids->str);
2587
2588                                 g_string_free (seqs, TRUE);
2589                                 g_string_free (uids, TRUE);
2590
2591                         }
2592                         camel_imapx_command_add (ic, "))");
2593                 }
2594         }
2595
2596         ic->complete = imapx_command_select_done;
2597         imapx_command_start (is, ic, cancellable, error);
2598
2599         return TRUE;
2600 }
2601
2602 #ifndef G_OS_WIN32
2603
2604 /* Using custom commands to connect to IMAP servers is not supported on Win32 */
2605
2606 static gboolean
2607 connect_to_server_process (CamelIMAPXServer *is,
2608                            const gchar *cmd,
2609                            GError **error)
2610 {
2611         CamelNetworkSettings *network_settings;
2612         CamelProvider *provider;
2613         CamelSettings *settings;
2614         CamelStream *cmd_stream;
2615         CamelService *service;
2616         CamelURL url;
2617         gint ret, i = 0;
2618         gchar *buf;
2619         gchar *cmd_copy;
2620         gchar *full_cmd;
2621         gchar *child_env[7];
2622         const gchar *password;
2623         gchar *host;
2624         gchar *user;
2625         guint16 port;
2626
2627         memset (&url, 0, sizeof (CamelURL));
2628
2629         service = CAMEL_SERVICE (is->store);
2630         password = camel_service_get_password (service);
2631         provider = camel_service_get_provider (service);
2632         settings = camel_service_get_settings (service);
2633
2634         network_settings = CAMEL_NETWORK_SETTINGS (settings);
2635         host = camel_network_settings_dup_host (network_settings);
2636         port = camel_network_settings_get_port (network_settings);
2637         user = camel_network_settings_dup_user (network_settings);
2638
2639         /* Put full details in the environment, in case the connection
2640          * program needs them */
2641         camel_url_set_protocol (&url, provider->protocol);
2642         camel_url_set_host (&url, host);
2643         camel_url_set_port (&url, port);
2644         camel_url_set_user (&url, user);
2645         buf = camel_url_to_string (&url, 0);
2646         child_env[i++] = g_strdup_printf("URL=%s", buf);
2647         g_free (buf);
2648
2649         child_env[i++] = g_strdup_printf("URLHOST=%s", host);
2650         if (port)
2651                 child_env[i++] = g_strdup_printf("URLPORT=%u", port);
2652         if (user)
2653                 child_env[i++] = g_strdup_printf("URLUSER=%s", user);
2654         if (password)
2655                 child_env[i++] = g_strdup_printf("URLPASSWD=%s", password);
2656         child_env[i] = NULL;
2657
2658         /* Now do %h, %u, etc. substitution in cmd */
2659         buf = cmd_copy = g_strdup (cmd);
2660
2661         full_cmd = g_strdup("");
2662
2663         for (;;) {
2664                 gchar *pc;
2665                 gchar *tmp;
2666                 const gchar *var;
2667                 gint len;
2668
2669                 pc = strchr (buf, '%');
2670         ignore:
2671                 if (!pc) {
2672                         tmp = g_strdup_printf("%s%s", full_cmd, buf);
2673                         g_free (full_cmd);
2674                         full_cmd = tmp;
2675                         break;
2676                 }
2677
2678                 len = pc - buf;
2679
2680                 var = NULL;
2681
2682                 switch (pc[1]) {
2683                 case 'h':
2684                         var = host;
2685                         break;
2686                 case 'u':
2687                         var = user;
2688                         break;
2689                 }
2690                 if (!var) {
2691                         /* If there wasn't a valid %-code, with an actual
2692                          * variable to insert, pretend we didn't see the % */
2693                         pc = strchr (pc + 1, '%');
2694                         goto ignore;
2695                 }
2696                 tmp = g_strdup_printf("%s%.*s%s", full_cmd, len, buf, var);
2697                 g_free (full_cmd);
2698                 full_cmd = tmp;
2699                 buf = pc + 2;
2700         }
2701
2702         g_free (cmd_copy);
2703
2704         g_free (host);
2705         g_free (user);
2706
2707         cmd_stream = camel_stream_process_new ();
2708
2709         ret = camel_stream_process_connect (
2710                 CAMEL_STREAM_PROCESS (cmd_stream),
2711                 full_cmd, (const gchar **) child_env, error);
2712
2713         while (i)
2714                 g_free (child_env[--i]);
2715
2716         if (ret == -1) {
2717                 g_object_unref (cmd_stream);
2718                 g_free (full_cmd);
2719                 return FALSE;
2720         }
2721
2722         g_free (full_cmd);
2723
2724         is->stream = (CamelIMAPXStream *) camel_imapx_stream_new (cmd_stream);
2725         g_object_unref (cmd_stream);
2726         is->is_process_stream = TRUE;
2727
2728         return TRUE;
2729 }
2730 #endif /* G_OS_WIN32 */
2731
2732 gboolean
2733 imapx_connect_to_server (CamelIMAPXServer *is,
2734                          GCancellable *cancellable,
2735                          GError **error)
2736 {
2737         CamelNetworkSettings *network_settings;
2738         CamelNetworkSecurityMethod method;
2739         CamelStream * tcp_stream = NULL;
2740         CamelSockOptData sockopt;
2741         CamelSettings *settings;
2742         CamelService *service;
2743         guint len;
2744         guchar *token;
2745         gint tok;
2746         CamelIMAPXCommand *ic;
2747         gboolean success = TRUE;
2748         gchar *host;
2749         GError *local_error = NULL;
2750
2751 #ifndef G_OS_WIN32
2752         gboolean use_shell_command;
2753         gchar *shell_command = NULL;
2754 #endif
2755
2756         service = CAMEL_SERVICE (is->store);
2757         settings = camel_service_get_settings (service);
2758
2759         network_settings = CAMEL_NETWORK_SETTINGS (settings);
2760         host = camel_network_settings_dup_host (network_settings);
2761         method = camel_network_settings_get_security_method (network_settings);
2762
2763 #ifndef G_OS_WIN32
2764         use_shell_command = camel_imapx_settings_get_use_shell_command (
2765                 CAMEL_IMAPX_SETTINGS (settings));
2766
2767         if (use_shell_command)
2768                 shell_command = camel_imapx_settings_dup_shell_command (
2769                         CAMEL_IMAPX_SETTINGS (settings));
2770
2771         if (shell_command != NULL) {
2772                 gboolean success;
2773
2774                 success = connect_to_server_process (
2775                         is, shell_command, &local_error);
2776
2777                 g_free (shell_command);
2778
2779                 if (success)
2780                         goto connected;
2781                 else
2782                         goto exit;
2783         }
2784 #endif
2785
2786         tcp_stream = camel_network_service_connect_sync (
2787                 CAMEL_NETWORK_SERVICE (is->store), cancellable, error);
2788
2789         if (tcp_stream == NULL) {
2790                 success = FALSE;
2791                 goto exit;
2792         }
2793
2794         is->stream = (CamelIMAPXStream *) camel_imapx_stream_new (tcp_stream);
2795         g_object_unref (tcp_stream);
2796
2797         /* Disable Nagle - we send a lot of small requests which nagle slows down */
2798         sockopt.option = CAMEL_SOCKOPT_NODELAY;
2799         sockopt.value.no_delay = TRUE;
2800         camel_tcp_stream_setsockopt ((CamelTcpStream *) tcp_stream, &sockopt);
2801
2802         /* Set keepalive - needed for some hosts/router configurations, we're idle a lot */
2803         sockopt.option = CAMEL_SOCKOPT_KEEPALIVE;
2804         sockopt.value.keep_alive = TRUE;
2805         camel_tcp_stream_setsockopt ((CamelTcpStream *) tcp_stream, &sockopt);
2806
2807  connected:
2808         is->stream->tagprefix = is->tagprefix;
2809         while (1) {
2810                 // poll ?  wait for other stuff? loop?
2811                 if (camel_application_is_exiting || is->parser_quit) {
2812                         g_set_error (
2813                                 error, G_IO_ERROR,
2814                                 G_IO_ERROR_CANCELLED,
2815                                 "Connection to server cancelled\n");
2816                         success = FALSE;
2817                         goto exit;
2818                 }
2819
2820                 tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
2821                 if (tok < 0) {
2822                         success = FALSE;
2823                         goto exit;
2824                 }
2825
2826                 if (tok == '*') {
2827                         imapx_untagged (is, cancellable, error);
2828                         break;
2829                 }
2830                 camel_imapx_stream_ungettoken (is->stream, tok, token, len);
2831                 if (camel_imapx_stream_text (is->stream, &token, cancellable, error)) {
2832                         success = FALSE;
2833                         goto exit;
2834                 }
2835                 e(is->tagprefix, "Got unexpected line before greeting:  '%s'\n", token);
2836                 g_free (token);
2837         }
2838
2839         if (!is->cinfo) {
2840                 ic = camel_imapx_command_new (
2841                         is, "CAPABILITY", NULL, "CAPABILITY");
2842                 if (!imapx_command_run (is, ic, cancellable, error)) {
2843                         camel_imapx_command_unref (ic);
2844                         success = FALSE;
2845                         goto exit;
2846                 }
2847
2848                 /* Server reported error. */
2849                 if (ic->status->result != IMAPX_OK) {
2850                         g_set_error (
2851                                 error, CAMEL_ERROR,
2852                                 CAMEL_ERROR_GENERIC,
2853                                 "%s", ic->status->text);
2854                         camel_imapx_command_unref (ic);
2855                         success = FALSE;
2856                         goto exit;
2857                 }
2858
2859                 camel_imapx_command_unref (ic);
2860         }
2861
2862         if (method == CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT) {
2863
2864                 if (!(is->cinfo->capa & IMAPX_CAPABILITY_STARTTLS)) {
2865                         g_set_error (
2866                                 &local_error, CAMEL_ERROR,
2867                                 CAMEL_ERROR_GENERIC,
2868                                 _("Failed to connect to IMAP server %s in secure mode: %s"),
2869                                 host, _("STARTTLS not supported"));
2870                         goto exit;
2871                 }
2872
2873                 ic = camel_imapx_command_new (
2874                         is, "STARTTLS", NULL, "STARTTLS");
2875                 if (!imapx_command_run (is, ic, cancellable, error)) {
2876                         camel_imapx_command_unref (ic);
2877                         success = FALSE;
2878                         goto exit;
2879                 }
2880
2881                 /* Server reported error. */
2882                 if (ic->status->result != IMAPX_OK) {
2883                         g_set_error (
2884                                 error, CAMEL_ERROR,
2885                                 CAMEL_ERROR_GENERIC,
2886                                 "%s", ic->status->text);
2887                         camel_imapx_command_unref (ic);
2888                         success = FALSE;
2889                         goto exit;
2890                 }
2891
2892                 /* See if we got new capabilities in the STARTTLS response */
2893                 imapx_free_capability (is->cinfo);
2894                 is->cinfo = NULL;
2895                 if (ic->status->condition == IMAPX_CAPABILITY) {
2896                         is->cinfo = ic->status->u.cinfo;
2897                         ic->status->u.cinfo = NULL;
2898                         c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
2899                 }
2900
2901                 camel_imapx_command_unref (ic);
2902
2903                 if (camel_tcp_stream_ssl_enable_ssl (
2904                         CAMEL_TCP_STREAM_SSL (tcp_stream),
2905                         cancellable, &local_error) == -1) {
2906                         g_prefix_error (
2907                                 &local_error,
2908                                 _("Failed to connect to IMAP server %s in secure mode: "),
2909                                 host);
2910                         goto exit;
2911                 }
2912                 /* Get new capabilities if they weren't already given */
2913                 if (!is->cinfo) {
2914                         ic = camel_imapx_command_new (
2915                                 is, "CAPABILITY", NULL, "CAPABILITY");
2916                         if (!imapx_command_run (is, ic, cancellable, error)) {
2917                                 camel_imapx_command_unref (ic);
2918                                 success = FALSE;
2919                                 goto exit;
2920                         }
2921
2922                         camel_imapx_command_unref (ic);
2923                 }
2924         }
2925
2926 exit:
2927         if (!success) {
2928                 if (is->stream != NULL) {
2929                         g_object_unref (is->stream);
2930                         is->stream = NULL;
2931                 }
2932
2933                 if (is->cinfo != NULL) {
2934                         imapx_free_capability (is->cinfo);
2935                         is->cinfo = NULL;
2936                 }
2937         }
2938
2939         g_free (host);
2940
2941         return success;
2942 }
2943
2944 CamelAuthenticationResult
2945 camel_imapx_server_authenticate (CamelIMAPXServer *is,
2946                                  const gchar *mechanism,
2947                                  GCancellable *cancellable,
2948                                  GError **error)
2949 {
2950         CamelNetworkSettings *network_settings;
2951         CamelSettings *settings;
2952         CamelAuthenticationResult result;
2953         CamelIMAPXCommand *ic;
2954         CamelService *service;
2955         CamelSasl *sasl = NULL;
2956         gchar *host;
2957         gchar *user;
2958
2959         g_return_val_if_fail (
2960                 CAMEL_IS_IMAPX_SERVER (is),
2961                 CAMEL_AUTHENTICATION_REJECTED);
2962
2963         service = CAMEL_SERVICE (is->store);
2964         settings = camel_service_get_settings (service);
2965
2966         network_settings = CAMEL_NETWORK_SETTINGS (settings);
2967         host = camel_network_settings_dup_host (network_settings);
2968         user = camel_network_settings_dup_user (network_settings);
2969
2970         if (mechanism != NULL) {
2971                 if (!g_hash_table_lookup (is->cinfo->auth_types, mechanism)) {
2972                         g_set_error (
2973                                 error, CAMEL_SERVICE_ERROR,
2974                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
2975                                 _("IMAP server %s does not support %s "
2976                                   "authentication"), host, mechanism);
2977                         result = CAMEL_AUTHENTICATION_ERROR;
2978                         goto exit;
2979                 }
2980
2981                 sasl = camel_sasl_new ("imap", mechanism, service);
2982                 if (sasl == NULL) {
2983                         g_set_error (
2984                                 error, CAMEL_SERVICE_ERROR,
2985                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
2986                                 _("No support for %s authentication"),
2987                                 mechanism);
2988                         result = CAMEL_AUTHENTICATION_ERROR;
2989                         goto exit;
2990                 }
2991         }
2992
2993         if (sasl != NULL) {
2994                 ic = camel_imapx_command_new (
2995                         is, "AUTHENTICATE", NULL, "AUTHENTICATE %A", sasl);
2996         } else {
2997                 const gchar *password;
2998
2999                 password = camel_service_get_password (service);
3000
3001                 if (user == NULL) {
3002                         g_set_error_literal (
3003                                 error, CAMEL_SERVICE_ERROR,
3004                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
3005                                 _("Cannot authenticate without a username"));
3006                         result = CAMEL_AUTHENTICATION_ERROR;
3007                         goto exit;
3008                 }
3009
3010                 if (password == NULL) {
3011                         g_set_error_literal (
3012                                 error, CAMEL_SERVICE_ERROR,
3013                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
3014                                 _("Authentication password not available"));
3015                         result = CAMEL_AUTHENTICATION_ERROR;
3016                         goto exit;
3017                 }
3018
3019                 ic = camel_imapx_command_new (
3020                         is, "LOGIN", NULL, "LOGIN %s %s", user, password);
3021         }
3022
3023         if (!imapx_command_run (is, ic, cancellable, error))
3024                 result = CAMEL_AUTHENTICATION_ERROR;
3025         else if (ic->status->result == IMAPX_OK)
3026                 result = CAMEL_AUTHENTICATION_ACCEPTED;
3027         else
3028                 result = CAMEL_AUTHENTICATION_REJECTED;
3029
3030         /* Forget old capabilities after login. */
3031         if (result == CAMEL_AUTHENTICATION_ACCEPTED) {
3032                 if (is->cinfo) {
3033                         imapx_free_capability (is->cinfo);
3034                         is->cinfo = NULL;
3035                 }
3036
3037                 if (ic->status->condition == IMAPX_CAPABILITY) {
3038                         is->cinfo = ic->status->u.cinfo;
3039                         ic->status->u.cinfo = NULL;
3040                         c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
3041                 }
3042         }
3043
3044         camel_imapx_command_unref (ic);
3045
3046         if (sasl != NULL)
3047                 g_object_unref (sasl);
3048
3049 exit:
3050         g_free (host);
3051         g_free (user);
3052
3053         return result;
3054 }
3055
3056 static gboolean
3057 imapx_reconnect (CamelIMAPXServer *is,
3058                  GCancellable *cancellable,
3059                  GError **error)
3060 {
3061         CamelIMAPXCommand *ic;
3062         CamelService *service;
3063         CamelSession *session;
3064         CamelSettings *settings;
3065         gchar *mechanism;
3066         gboolean use_idle;
3067         gboolean use_qresync;
3068
3069         service = CAMEL_SERVICE (is->store);
3070         session = camel_service_get_session (service);
3071         settings = camel_service_get_settings (service);
3072
3073         mechanism = camel_network_settings_dup_auth_mechanism (
3074                 CAMEL_NETWORK_SETTINGS (settings));
3075
3076         use_idle = camel_imapx_settings_get_use_idle (
3077                 CAMEL_IMAPX_SETTINGS (settings));
3078
3079         use_qresync = camel_imapx_settings_get_use_qresync (
3080                 CAMEL_IMAPX_SETTINGS (settings));
3081
3082         if (!imapx_connect_to_server (is, cancellable, error))
3083                 goto exception;
3084
3085         if (is->state == IMAPX_AUTHENTICATED)
3086                 goto preauthed;
3087
3088         if (!camel_session_authenticate_sync (
3089                 session, service, mechanism, cancellable, error))
3090                 goto exception;
3091
3092         /* After login we re-capa unless the server already told us */
3093         if (!is->cinfo) {
3094                 ic = camel_imapx_command_new (
3095                         is, "CAPABILITY", NULL, "CAPABILITY");
3096                 if (!imapx_command_run (is, ic, cancellable, error)) {
3097                         camel_imapx_command_unref (ic);
3098                         goto exception;
3099                 }
3100
3101                 camel_imapx_command_unref (ic);
3102         }
3103
3104         is->state = IMAPX_AUTHENTICATED;
3105
3106  preauthed:
3107         is->use_idle = use_idle;
3108
3109         if (imapx_idle_supported (is))
3110                 imapx_init_idle (is);
3111
3112         /* Fetch namespaces */
3113         if (is->cinfo->capa & IMAPX_CAPABILITY_NAMESPACE) {
3114                 ic = camel_imapx_command_new (
3115                         is, "NAMESPACE", NULL, "NAMESPACE");
3116                 if (!imapx_command_run (is, ic, cancellable, error)) {
3117                         camel_imapx_command_unref (ic);
3118                         goto exception;
3119                 }
3120
3121                 camel_imapx_command_unref (ic);
3122         }
3123
3124         if (use_qresync && is->cinfo->capa & IMAPX_CAPABILITY_QRESYNC) {
3125                 ic = camel_imapx_command_new (
3126                         is, "ENABLE", NULL, "ENABLE CONDSTORE QRESYNC");
3127                 if (!imapx_command_run (is, ic, cancellable, error)) {
3128                         camel_imapx_command_unref (ic);
3129                         goto exception;
3130                 }
3131
3132                 camel_imapx_command_unref (ic);
3133
3134                 is->use_qresync = TRUE;
3135         } else
3136                 is->use_qresync = FALSE;
3137
3138         if (((CamelIMAPXStore *) is->store)->summary->namespaces == NULL) {
3139                 CamelIMAPXNamespaceList *nsl = NULL;
3140                 CamelIMAPXStoreNamespace *ns = NULL;
3141                 CamelIMAPXStore *imapx_store = (CamelIMAPXStore *) is->store;
3142
3143                 /* set a default namespace */
3144                 nsl = g_malloc0 (sizeof (CamelIMAPXNamespaceList));
3145                 ns = g_new0 (CamelIMAPXStoreNamespace, 1);
3146                 ns->next = NULL;
3147                 ns->path = g_strdup ("");
3148                 ns->full_name = g_strdup ("");
3149                 ns->sep = '/';
3150                 nsl->personal = ns;
3151                 imapx_store->summary->namespaces = nsl;
3152                 /* FIXME needs to be identified from list response */
3153                 imapx_store->dir_sep = ns->sep;
3154         }
3155
3156         is->state = IMAPX_INITIALISED;
3157
3158         g_free (mechanism);
3159
3160         return TRUE;
3161
3162 exception:
3163
3164         imapx_disconnect (is);
3165
3166         if (is->cinfo) {
3167                 imapx_free_capability (is->cinfo);
3168                 is->cinfo = NULL;
3169         }
3170
3171         g_free (mechanism);
3172
3173         return FALSE;
3174 }
3175
3176 /* ********************************************************************** */
3177
3178 static gboolean
3179 imapx_command_fetch_message_done (CamelIMAPXServer *is,
3180                                   CamelIMAPXCommand *ic,
3181                                   GError **error)
3182 {
3183         CamelIMAPXJob *job;
3184         GetMessageData *data;
3185         gboolean success = TRUE;
3186         GError *local_error = NULL;
3187
3188         job = camel_imapx_command_get_job (ic);
3189         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3190
3191         data = camel_imapx_job_get_data (job);
3192         g_return_val_if_fail (data != NULL, FALSE);
3193
3194         /* We either have more to fetch (partial mode?), we are complete,
3195          * or we failed.  Failure is handled in the fetch code, so
3196          * we just return the job, or keep it alive with more requests */
3197
3198         job->commands--;
3199
3200         if (camel_imapx_command_set_error_if_failed (ic, &local_error)) {
3201                 g_prefix_error (
3202                         &local_error, "%s: ",
3203                         _("Error fetching message"));
3204                 data->body_len = -1;
3205
3206         } else if (data->use_multi_fetch) {
3207                 gsize really_fetched = g_seekable_tell (G_SEEKABLE (data->stream));
3208                 /* Don't automatically stop when we reach the reported message
3209                  * size -- some crappy servers (like Microsoft Exchange) have
3210                  * a tendency to lie about it. Keep going (one request at a
3211                  * time) until the data actually stop coming. */
3212                 if (data->fetch_offset < data->size ||
3213                     data->fetch_offset == really_fetched) {
3214                         CamelIMAPXCommand *new_ic;
3215
3216                         camel_operation_progress (
3217                                 job->cancellable,
3218                                 (data->fetch_offset *100) / data->size);
3219
3220                         new_ic = camel_imapx_command_new (
3221                                 is, "FETCH", job->folder,
3222                                 "UID FETCH %t (BODY.PEEK[]",
3223                                 data->uid);
3224                         camel_imapx_command_add (new_ic, "<%u.%u>", data->fetch_offset, MULTI_SIZE);
3225                         camel_imapx_command_add (new_ic, ")");
3226                         new_ic->complete = imapx_command_fetch_message_done;
3227                         camel_imapx_command_set_job (new_ic, job);
3228                         new_ic->pri = job->pri - 1;
3229                         data->fetch_offset += MULTI_SIZE;
3230                         job->commands++;
3231                         imapx_command_queue (is, new_ic);
3232
3233                         camel_imapx_command_unref (ic);
3234
3235                         return TRUE;
3236                 }
3237         }
3238
3239         if (job->commands == 0) {
3240                 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
3241                 CamelStream *stream = data->stream;
3242
3243                 /* return the exception from last command */
3244                 if (local_error != NULL) {
3245                         if (stream)
3246                                 g_object_unref (stream);
3247                         data->stream = NULL;
3248
3249                         g_propagate_error (error, local_error);
3250                         local_error = NULL;
3251                         success = FALSE;
3252
3253                 } else {
3254                         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
3255
3256                         if (stream) {
3257                                 gchar *tmp = camel_data_cache_get_filename (ifolder->cache, "tmp", data->uid, NULL);
3258
3259                                 if (camel_stream_flush (stream, job->cancellable, &job->error) == 0 && camel_stream_close (stream, job->cancellable, &job->error) == 0) {
3260                                         gchar *cache_file = camel_data_cache_get_filename  (ifolder->cache, "cur", data->uid, NULL);
3261                                         gchar *temp = g_strrstr (cache_file, "/"), *dir;
3262
3263                                         dir = g_strndup (cache_file, temp - cache_file);
3264                                         g_mkdir_with_parents (dir, 0700);
3265                                         g_free (dir);
3266
3267                                         if (g_rename (tmp, cache_file) != 0)
3268                                                 g_set_error (
3269                                                         &job->error, CAMEL_IMAPX_ERROR, 1,
3270                                                         "failed to copy the tmp file");
3271                                         g_free (cache_file);
3272                                 } else
3273                                         g_prefix_error (
3274                                                 &job->error,
3275                                                 _("Closing tmp stream failed: "));
3276
3277                                 g_free (tmp);
3278                                 g_object_unref (data->stream);
3279                                 data->stream = camel_data_cache_get (ifolder->cache, "cur", data->uid, NULL);
3280                         }
3281                 }
3282
3283                 camel_data_cache_remove (ifolder->cache, "tmp", data->uid, NULL);
3284                 imapx_unregister_job (is, job);
3285         }
3286
3287         camel_imapx_command_unref (ic);
3288
3289         g_clear_error (&local_error);
3290
3291         return success;
3292 }
3293
3294 static void
3295 imapx_job_get_message_start (CamelIMAPXJob *job,
3296                              CamelIMAPXServer *is)
3297 {
3298         CamelIMAPXCommand *ic;
3299         GetMessageData *data;
3300         gint i;
3301
3302         data = camel_imapx_job_get_data (job);
3303         g_return_if_fail (data != NULL);
3304
3305         if (data->use_multi_fetch) {
3306                 for (i = 0; i < 3 && data->fetch_offset < data->size; i++) {
3307                         ic = camel_imapx_command_new (
3308                                 is, "FETCH", job->folder,
3309                                 "UID FETCH %t (BODY.PEEK[]",
3310                                 data->uid);
3311                         camel_imapx_command_add (ic, "<%u.%u>", data->fetch_offset, MULTI_SIZE);
3312                         camel_imapx_command_add (ic, ")");
3313                         ic->complete = imapx_command_fetch_message_done;
3314                         camel_imapx_command_set_job (ic, job);
3315                         ic->pri = job->pri;
3316                         data->fetch_offset += MULTI_SIZE;
3317                         job->commands++;
3318                         imapx_command_queue (is, ic);
3319                 }
3320         } else {
3321                 ic = camel_imapx_command_new (
3322                         is, "FETCH", job->folder,
3323                         "UID FETCH %t (BODY.PEEK[])",
3324                         data->uid);
3325                 ic->complete = imapx_command_fetch_message_done;
3326                 camel_imapx_command_set_job (ic, job);
3327                 ic->pri = job->pri;
3328                 job->commands++;
3329                 imapx_command_queue (is, ic);
3330         }
3331 }
3332
3333 static gboolean
3334 imapx_job_get_message_matches (CamelIMAPXJob *job,
3335                                CamelFolder *folder,
3336                                const gchar *uid)
3337 {
3338         GetMessageData *data;
3339
3340         data = camel_imapx_job_get_data (job);
3341         g_return_val_if_fail (data != NULL, FALSE);
3342
3343         if (folder != job->folder)
3344                 return FALSE;
3345
3346         if (g_strcmp0 (uid, data->uid) != 0)
3347                 return FALSE;
3348
3349         return TRUE;
3350 }
3351
3352 /* ********************************************************************** */
3353
3354 static gboolean
3355 imapx_command_copy_messages_step_done (CamelIMAPXServer *is,
3356                                        CamelIMAPXCommand *ic,
3357                                        GError **error)
3358 {
3359         CamelIMAPXJob *job;
3360         CopyMessagesData *data;
3361         GPtrArray *uids;
3362         gint i;
3363         gboolean success = TRUE;
3364
3365         job = camel_imapx_command_get_job (ic);
3366         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3367
3368         data = camel_imapx_job_get_data (job);
3369         g_return_val_if_fail (data != NULL, FALSE);
3370
3371         uids = data->uids;
3372         i = data->index;
3373
3374         if (camel_imapx_command_set_error_if_failed (ic, error)) {
3375                 g_prefix_error (
3376                         &job->error, "%s: ",
3377                         _("Error copying messages"));
3378                 success = FALSE;
3379                 goto cleanup;
3380         }
3381
3382         if (data->delete_originals) {
3383                 gint j;
3384
3385                 for (j = data->last_index; j < i; j++)
3386                         camel_folder_delete_message (job->folder, uids->pdata[j]);
3387         }
3388
3389         /* TODO copy the summary and cached messages to the new folder. We might need a sorted insert to avoid refreshing the dest folder */
3390         if (ic->status->condition == IMAPX_COPYUID) {
3391                 gint i;
3392
3393                 for (i = 0; i < ic->status->u.copyuid.copied_uids->len; i++) {
3394                         guint32 uid = GPOINTER_TO_UINT (g_ptr_array_index (ic->status->u.copyuid.copied_uids, i));
3395                         gchar *str = g_strdup_printf ("%d",uid);
3396                         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) data->dest;
3397
3398                         g_hash_table_insert (ifolder->ignore_recent, str, GINT_TO_POINTER (1));
3399                 }
3400
3401         }
3402
3403         if (i < uids->len) {
3404                 camel_imapx_command_unref (ic);
3405                 imapx_command_copy_messages_step_start (is, job, i);
3406                 return TRUE;
3407         }
3408
3409 cleanup:
3410         g_object_unref (job->folder);
3411
3412         imapx_unregister_job (is, job);
3413         camel_imapx_command_unref (ic);
3414
3415         return success;
3416 }
3417
3418 static void
3419 imapx_command_copy_messages_step_start (CamelIMAPXServer *is,
3420                                         CamelIMAPXJob *job,
3421                                         gint index)
3422 {
3423         CamelIMAPXCommand *ic;
3424         CopyMessagesData *data;
3425         GPtrArray *uids;
3426         gint i = index;
3427
3428         data = camel_imapx_job_get_data (job);
3429         g_return_if_fail (data != NULL);
3430
3431         uids = data->uids;
3432
3433         ic = camel_imapx_command_new (
3434                 is, "COPY", job->folder, "UID COPY ");
3435         ic->complete = imapx_command_copy_messages_step_done;
3436         camel_imapx_command_set_job (ic, job);
3437         ic->pri = job->pri;
3438         data->last_index = i;
3439
3440         for (; i < uids->len; i++) {
3441                 gint res;
3442                 const gchar *uid = (gchar *) g_ptr_array_index (uids, i);
3443
3444                 res = imapx_uidset_add (&data->uidset, ic, uid);
3445                 if (res == 1) {
3446                         camel_imapx_command_add (ic, " %f", data->dest);
3447                         data->index = i;
3448                         imapx_command_queue (is, ic);
3449                         return;
3450                 }
3451         }
3452
3453         data->index = i;
3454         if (imapx_uidset_done (&data->uidset, ic)) {
3455                 camel_imapx_command_add (ic, " %f", data->dest);
3456                 imapx_command_queue (is, ic);
3457                 return;
3458         }
3459 }
3460
3461 static void
3462 imapx_job_copy_messages_start (CamelIMAPXJob *job,
3463                                CamelIMAPXServer *is)
3464 {
3465         CopyMessagesData *data;
3466
3467         data = camel_imapx_job_get_data (job);
3468         g_return_if_fail (data != NULL);
3469
3470         if (!imapx_server_sync_changes (
3471                 is, job->folder, job->pri, job->cancellable, &job->error))
3472                 imapx_unregister_job (is, job);
3473
3474         g_ptr_array_sort (data->uids, (GCompareFunc) imapx_uids_array_cmp);
3475         imapx_uidset_init (&data->uidset, 0, MAX_COMMAND_LEN);
3476         imapx_command_copy_messages_step_start (is, job, 0);
3477 }
3478
3479 /* ********************************************************************** */
3480
3481 static gboolean
3482 imapx_command_append_message_done (CamelIMAPXServer *is,
3483                                    CamelIMAPXCommand *ic,
3484                                    GError **error)
3485 {
3486         CamelIMAPXJob *job;
3487         CamelIMAPXFolder *ifolder;
3488         CamelMessageInfo *mi;
3489         AppendMessageData *data;
3490         gchar *cur, *old_uid;
3491         gboolean success = TRUE;
3492
3493         job = camel_imapx_command_get_job (ic);
3494         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3495
3496         data = camel_imapx_job_get_data (job);
3497         g_return_val_if_fail (data != NULL, FALSE);
3498
3499         ifolder = (CamelIMAPXFolder *) job->folder;
3500
3501         /* Append done.  If we the server supports UIDPLUS we will get an APPENDUID response
3502          * with the new uid.  This lets us move the message we have directly to the cache
3503          * and also create a correctly numbered MessageInfo, without losing any information.
3504          * Otherwise we have to wait for the server to less us know it was appended. */
3505
3506         mi = camel_message_info_clone (data->info);
3507         old_uid = g_strdup (data->info->uid);
3508
3509         if (camel_imapx_command_set_error_if_failed (ic, error)) {
3510                 g_prefix_error (
3511                         error, "%s: ",
3512                         _("Error appending message"));
3513                 success = FALSE;
3514
3515         } else if (ic->status->condition == IMAPX_APPENDUID) {
3516                 c(is->tagprefix, "Got appenduid %d %d\n", (gint)ic->status->u.appenduid.uidvalidity, (gint)ic->status->u.appenduid.uid);
3517                 if (ic->status->u.appenduid.uidvalidity == ifolder->uidvalidity_on_server) {
3518                         CamelFolderChangeInfo *changes;
3519
3520                         data->appended_uid = g_strdup_printf ("%u", (guint) ic->status->u.appenduid.uid);
3521                         mi->uid = camel_pstring_add (data->appended_uid, FALSE);
3522
3523                         cur = camel_data_cache_get_filename  (ifolder->cache, "cur", mi->uid, NULL);
3524                         g_rename (data->path, cur);
3525
3526                         /* should we update the message count ? */
3527                         imapx_set_message_info_flags_for_new_message (mi,
3528                                                                       ((CamelMessageInfoBase *) data->info)->flags,
3529                                                                       ((CamelMessageInfoBase *) data->info)->user_flags,
3530                                                                       job->folder);
3531                         camel_folder_summary_add (job->folder->summary, mi);
3532                         changes = camel_folder_change_info_new ();
3533                         camel_folder_change_info_add_uid (changes, mi->uid);
3534                         camel_folder_changed (job->folder, changes);
3535                         camel_folder_change_info_free (changes);
3536
3537                         g_free (cur);
3538                 } else {
3539                         g_message ("but uidvalidity changed \n");
3540                 }
3541         }
3542
3543         camel_data_cache_remove (ifolder->cache, "new", old_uid, NULL);
3544         g_free (old_uid);
3545         g_object_unref (job->folder);
3546
3547         imapx_unregister_job (is, job);
3548         camel_imapx_command_unref (ic);
3549
3550         return success;
3551 }
3552
3553 static void
3554 imapx_job_append_message_start (CamelIMAPXJob *job,
3555                                 CamelIMAPXServer *is)
3556 {
3557         CamelIMAPXCommand *ic;
3558         AppendMessageData *data;
3559
3560         data = camel_imapx_job_get_data (job);
3561         g_return_if_fail (data != NULL);
3562
3563         /* TODO: we could supply the original append date from the file timestamp */
3564         ic = camel_imapx_command_new (
3565                 is, "APPEND", NULL,
3566                 "APPEND %f %F %P", job->folder,
3567                 ((CamelMessageInfoBase *) data->info)->flags,
3568                 ((CamelMessageInfoBase *) data->info)->user_flags,
3569                 data->path);
3570         ic->complete = imapx_command_append_message_done;
3571         camel_imapx_command_set_job (ic, job);
3572         ic->pri = job->pri;
3573         job->commands++;
3574         imapx_command_queue (is, ic);
3575 }
3576
3577 /* ********************************************************************** */
3578
3579 static gint
3580 imapx_refresh_info_uid_cmp (gconstpointer ap,
3581                             gconstpointer bp,
3582                             gboolean ascending)
3583 {
3584         guint av, bv;
3585
3586         av = g_ascii_strtoull ((const gchar *) ap, NULL, 10);
3587         bv = g_ascii_strtoull ((const gchar *) bp, NULL, 10);
3588
3589         if (av < bv)
3590                 return ascending ? -1 : 1;
3591         else if (av > bv)
3592                 return ascending ? 1 : -1;
3593         else
3594                 return 0;
3595 }
3596
3597 static gint
3598 imapx_uids_array_cmp (gconstpointer ap,
3599                       gconstpointer bp)
3600 {
3601         const gchar **a = (const gchar **) ap;
3602         const gchar **b = (const gchar **) bp;
3603
3604         return imapx_refresh_info_uid_cmp (*a, *b, TRUE);
3605 }
3606
3607 static gint
3608 imapx_refresh_info_cmp (gconstpointer ap,
3609                         gconstpointer bp)
3610 {
3611         const struct _refresh_info *a = ap;
3612         const struct _refresh_info *b = bp;
3613
3614         return imapx_refresh_info_uid_cmp (a->uid, b->uid, TRUE);
3615 }
3616
3617 static gint
3618 imapx_refresh_info_cmp_descending (gconstpointer ap,
3619                                    gconstpointer bp)
3620 {
3621         const struct _refresh_info *a = ap;
3622         const struct _refresh_info *b = bp;
3623
3624         return imapx_refresh_info_uid_cmp (a->uid, b->uid, FALSE);
3625
3626 }
3627
3628 /* skips over non-server uids (pending appends) */
3629 static guint
3630 imapx_index_next (GPtrArray *uids,
3631                   CamelFolderSummary *s,
3632                   guint index)
3633 {
3634
3635         while (index < uids->len) {
3636                 CamelMessageInfo *info;
3637
3638                 index++;
3639                 if (index >= uids->len)
3640                         break;
3641
3642                 info = camel_folder_summary_get (s, g_ptr_array_index (uids, index));
3643                 if (!info)
3644                         continue;
3645
3646                 if (info && (strchr (camel_message_info_uid (info), '-') != NULL)) {
3647                         camel_message_info_free (info);
3648                         e('?', "Ignoring offline uid '%s'\n", camel_message_info_uid(info));
3649                 } else {
3650                         camel_message_info_free (info);
3651                         break;
3652                 }
3653         }
3654
3655         return index;
3656 }
3657
3658 static gboolean
3659 imapx_command_step_fetch_done (CamelIMAPXServer *is,
3660                                CamelIMAPXCommand *ic,
3661                                GError **error)
3662 {
3663         CamelIMAPXFolder *ifolder;
3664         CamelIMAPXSummary *isum;
3665         CamelIMAPXJob *job;
3666         RefreshInfoData *data;
3667         gint i;
3668         gboolean success = TRUE;
3669         CamelSettings *settings;
3670         CamelService *service;
3671         guint batch_count;
3672         gboolean mobile_mode;
3673
3674         job = camel_imapx_command_get_job (ic);
3675         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3676
3677         data = camel_imapx_job_get_data (job);
3678         g_return_val_if_fail (data != NULL, FALSE);
3679
3680         ifolder = (CamelIMAPXFolder *) job->folder;
3681         isum = (CamelIMAPXSummary *) job->folder->summary;
3682
3683         service = CAMEL_SERVICE (is->store);
3684         settings = camel_service_get_settings (service);
3685
3686         batch_count = camel_imapx_settings_get_batch_fetch_count (
3687                 CAMEL_IMAPX_SETTINGS (settings));
3688         mobile_mode = camel_imapx_settings_get_mobile_mode (
3689                 CAMEL_IMAPX_SETTINGS (settings));
3690
3691         i = data->index;
3692
3693         //printf("%s: Mobile mode: %d Fetch Count %d\n", camel_folder_get_display_name (job->folder), mobile_mode, batch_count);
3694         if (camel_imapx_command_set_error_if_failed (ic, error)) {
3695                 g_prefix_error (
3696                         error, "%s: ",
3697                         _("Error fetching message headers"));
3698                 success = FALSE;
3699                 goto cleanup;
3700         }
3701
3702         if (camel_folder_change_info_changed (data->changes)) {
3703                 imapx_update_store_summary (job->folder);
3704                 camel_folder_summary_save_to_db (job->folder->summary, NULL);
3705                 camel_folder_changed (job->folder, data->changes);
3706         }
3707
3708         camel_folder_change_info_clear (data->changes);
3709
3710         if (i < data->infos->len) {
3711                 gint total = camel_folder_summary_count (job->folder->summary);
3712                 gint fetch_limit = data->fetch_msg_limit;
3713
3714                 camel_imapx_command_unref (ic);
3715
3716                 ic = camel_imapx_command_new (
3717                         is, "FETCH", job->folder, "UID FETCH ");
3718                 ic->complete = imapx_command_step_fetch_done;
3719                 camel_imapx_command_set_job (ic, job);
3720                 ic->pri = job->pri - 1;
3721
3722                 //printf("Total: %d: %d, %d, %d\n", total, fetch_limit, i, data->last_index);   
3723                 data->last_index = i;
3724
3725                 /* If its mobile client and  when total=0 (new account setup) fetch only one batch of mails,
3726                  * on futher attempts download all new mails as per the limit. */
3727                 //printf("Total: %d: %d\n", total, fetch_limit);
3728                 for (; i < data->infos->len && (!mobile_mode || (total && i == 0) || ((fetch_limit != -1 && i < fetch_limit) || (fetch_limit == -1 && i < batch_count))); i++) {
3729
3730                         gint res;
3731                         struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
3732
3733                         if (!r->exists) {
3734                                 res = imapx_uidset_add (&data->uidset, ic, r->uid);
3735                                 if (res == 1) {
3736                                         camel_imapx_command_add (ic, " (RFC822.SIZE RFC822.HEADER)");
3737                                         data->index = i;
3738                                         imapx_command_queue (is, ic);
3739                                         return TRUE;
3740                                 }
3741                         }
3742                 }
3743
3744                 //printf("Existing : %d Gonna fetch in %s for %d/%d\n", total, camel_folder_get_full_name(job->folder), i, data->infos->len);
3745                 data->index = data->infos->len;
3746                 if (imapx_uidset_done (&data->uidset, ic)) {
3747                         camel_imapx_command_add (ic, " (RFC822.SIZE RFC822.HEADER)");
3748
3749                         imapx_command_queue (is, ic);
3750                         return TRUE;
3751                 }
3752         }
3753
3754         if (camel_folder_summary_count (job->folder->summary)) {
3755                 gchar *uid = imapx_get_uid_from_index (job->folder->summary,
3756                                                        camel_folder_summary_count (job->folder->summary) - 1);
3757                 guint64 uidl = strtoull (uid, NULL, 10);
3758                 g_free (uid);
3759
3760                 uidl++;
3761
3762                 if (uidl > ifolder->uidnext_on_server) {
3763                         c(is->tagprefix, "Updating uidnext_on_server for '%s' to %" G_GUINT64_FORMAT "\n",
3764                           camel_folder_get_full_name (job->folder), uidl);
3765                         ifolder->uidnext_on_server = uidl;
3766                 }
3767         }
3768         isum->uidnext = ifolder->uidnext_on_server;
3769
3770  cleanup:
3771         for (i = 0; i < data->infos->len; i++) {
3772                 struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
3773
3774                 camel_flag_list_free (&r->server_user_flags);
3775                 g_free (r->uid);
3776         }
3777         g_array_free (data->infos, TRUE);
3778
3779         imapx_unregister_job (is, job);
3780         camel_imapx_command_unref (ic);
3781
3782         return success;
3783 }
3784
3785 static gint
3786 imapx_uid_cmp (gconstpointer ap,
3787                gconstpointer bp,
3788                gpointer data)
3789 {
3790         const gchar *a = ap, *b = bp;
3791         gchar *ae, *be;
3792         gulong av, bv;
3793
3794         av = strtoul (a, &ae, 10);
3795         bv = strtoul (b, &be, 10);
3796
3797         if (av < bv)
3798                 return -1;
3799         else if (av > bv)
3800                 return 1;
3801
3802         if (*ae == '-')
3803                 ae++;
3804         if (*be == '-')
3805                 be++;
3806
3807         return strcmp (ae, be);
3808 }
3809
3810 static gboolean
3811 imapx_job_scan_changes_done (CamelIMAPXServer *is,
3812                              CamelIMAPXCommand *ic,
3813                              GError **error)
3814 {
3815         CamelIMAPXJob *job;
3816         CamelService *service;
3817         CamelSettings *settings;
3818         RefreshInfoData *data;
3819         guint uidset_size;
3820         gint i;
3821         gboolean success = TRUE;
3822         gboolean mobile_mode;
3823
3824         job = camel_imapx_command_get_job (ic);
3825         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3826
3827         data = camel_imapx_job_get_data (job);
3828         g_return_val_if_fail (data != NULL, FALSE);
3829
3830         service = CAMEL_SERVICE (is->store);
3831         settings = camel_service_get_settings (service);
3832
3833         uidset_size = camel_imapx_settings_get_batch_fetch_count (
3834                 CAMEL_IMAPX_SETTINGS (settings));
3835         mobile_mode = camel_imapx_settings_get_mobile_mode (
3836                 CAMEL_IMAPX_SETTINGS (settings));
3837
3838         if (camel_imapx_command_set_error_if_failed (ic, error)) {
3839                 g_prefix_error (
3840                         error, "%s: ",
3841                         _("Error retrieving message"));
3842                 success = FALSE;
3843
3844         } else {
3845                 GCompareDataFunc uid_cmp = imapx_uid_cmp;
3846                 CamelMessageInfo *s_minfo = NULL;
3847                 CamelIMAPXMessageInfo *info;
3848                 CamelFolderSummary *s = job->folder->summary;
3849                 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
3850                 GList *removed = NULL, *l;
3851                 gboolean fetch_new = FALSE;
3852                 gint i;
3853                 guint j = 0;
3854                 GPtrArray *uids;
3855
3856                 /* Actually we wanted to do this after the SELECT but before the
3857                  * FETCH command was issued. But this should suffice. */
3858                 ((CamelIMAPXSummary *) s)->uidnext = ifolder->uidnext_on_server;
3859                 ((CamelIMAPXSummary *) s)->modseq = ifolder->modseq_on_server;
3860
3861                 /* Here we do the typical sort/iterate/merge loop.
3862                  * If the server flags dont match what we had, we modify our
3863                  * flags to pick up what the server now has - but we merge
3864                  * not overwrite */
3865
3866                 /* FIXME: We also have to check the offline directory for
3867                  * anything missing in our summary, and also queue up jobs
3868                  * for all outstanding messages to be uploaded */
3869
3870                 /* obtain a copy to be thread safe */
3871                 uids = camel_folder_summary_get_array (s);
3872
3873                 qsort (data->infos->data, data->infos->len, sizeof (struct _refresh_info), imapx_refresh_info_cmp);
3874                 g_ptr_array_sort (uids, (GCompareFunc) imapx_uids_array_cmp);
3875
3876                 if (uids->len)
3877                         s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, 0));
3878
3879                 for (i = 0; i < data->infos->len; i++) {
3880                         struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
3881
3882                         while (s_minfo && uid_cmp (camel_message_info_uid (s_minfo), r->uid, s) < 0) {
3883                                 const gchar *uid = camel_message_info_uid (s_minfo);
3884
3885                                 camel_folder_change_info_remove_uid (data->changes, uid);
3886                                 removed = g_list_prepend (removed, (gpointer ) g_strdup (uid));
3887                                 camel_message_info_free (s_minfo);
3888                                 s_minfo = NULL;
3889
3890                                 j = imapx_index_next (uids, s, j);
3891                                 if (j < uids->len)
3892                                         s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
3893                         }
3894
3895                         info = NULL;
3896                         if (s_minfo && uid_cmp (s_minfo->uid, r->uid, s) == 0) {
3897                                 info = (CamelIMAPXMessageInfo *) s_minfo;
3898
3899                                 if (imapx_update_message_info_flags ((CamelMessageInfo *) info, r->server_flags, r->server_user_flags, is->permanentflags, job->folder, FALSE))
3900                                         camel_folder_change_info_change_uid (data->changes, camel_message_info_uid (s_minfo));
3901                                 r->exists = TRUE;
3902                         } else
3903                                 fetch_new = TRUE;
3904
3905                         if (s_minfo) {
3906                                 camel_message_info_free (s_minfo);
3907                                 s_minfo = NULL;
3908                         }
3909
3910                         if (j >= uids->len)
3911                                 break;
3912
3913                         j = imapx_index_next (uids, s, j);
3914                         if (j < uids->len)
3915                                 s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
3916                 }
3917
3918                 if (s_minfo)
3919                         camel_message_info_free (s_minfo);
3920
3921                 while (j < uids->len) {
3922                         s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
3923
3924                         if (!s_minfo) {
3925                                 j++;
3926                                 continue;
3927                         }
3928
3929                         e(is->tagprefix, "Message %s vanished\n", s_minfo->uid);
3930                         removed = g_list_prepend (removed, (gpointer) g_strdup (s_minfo->uid));
3931                         camel_message_info_free (s_minfo);
3932                         j++;
3933                 }
3934
3935                 for (l = removed; l != NULL; l = g_list_next (l)) {
3936                         gchar *uid = (gchar *) l->data;
3937
3938                         camel_folder_change_info_remove_uid (data->changes, uid);
3939                         camel_folder_summary_remove_uid (s, uid);
3940                 }
3941
3942                 if (removed != NULL) {
3943                         camel_folder_summary_touch (s);
3944
3945                         g_list_free_full (removed, (GDestroyNotify) g_free);
3946                 }
3947
3948                 camel_folder_summary_save_to_db (s, NULL);
3949                 imapx_update_store_summary (job->folder);
3950
3951                 if (camel_folder_change_info_changed (data->changes))
3952                         camel_folder_changed (job->folder, data->changes);
3953                 camel_folder_change_info_clear (data->changes);
3954
3955                 camel_folder_summary_free_array (uids);
3956
3957                 /* If we have any new messages, download their headers, but only a few (100?) at a time */
3958                 if (fetch_new) {
3959                         job->pop_operation_msg = TRUE;
3960
3961                         camel_operation_push_message (
3962                                 job->cancellable,
3963                                 _("Fetching summary information for new messages in %s"),
3964                                 camel_folder_get_display_name (job->folder));
3965
3966                         imapx_uidset_init (&data->uidset, uidset_size, 0);
3967                         /* These are new messages which arrived since we last knew the unseen count;
3968                          * update it as they arrive. */
3969                         data->update_unseen = TRUE;
3970                         return imapx_command_step_fetch_done (is, ic, error);
3971                 }
3972         }
3973
3974         for (i = 0; i < data->infos->len; i++) {
3975                 struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
3976
3977                 camel_flag_list_free (&r->server_user_flags);
3978                 g_free (r->uid);
3979         }
3980
3981         /* There's no sane way to get the server-side unseen count on the
3982          * select mailbox. So just work it out from the flags if its not in
3983          * mobile mode. In mobile mode we would have this filled up already
3984          * with a STATUS command. 
3985          **/
3986         if (!mobile_mode)
3987                 ((CamelIMAPXFolder *) job->folder)->unread_on_server = camel_folder_summary_get_unread_count (job->folder->summary);
3988
3989         g_array_free (data->infos, TRUE);
3990         imapx_unregister_job (is, job);
3991         camel_imapx_command_unref (ic);
3992
3993         return success;
3994 }
3995
3996 static void
3997 imapx_job_scan_changes_start (CamelIMAPXJob *job,
3998                               CamelIMAPXServer *is)
3999 {
4000         CamelIMAPXCommand *ic;
4001         RefreshInfoData *data;
4002         gchar *uid = imapx_get_uid_from_index (job->folder->summary, 0);
4003
4004         data = camel_imapx_job_get_data (job);
4005         g_return_if_fail (data != NULL);
4006
4007         job->pop_operation_msg = TRUE;
4008
4009         camel_operation_push_message (
4010                 job->cancellable,
4011                 _("Scanning for changed messages in %s"),
4012                 camel_folder_get_display_name (job->folder));
4013
4014         e('E', "Scanning from %s in %s\n", uid, camel_folder_get_full_name (job->folder));      
4015         ic = camel_imapx_command_new (
4016                 is, "FETCH", job->folder,
4017                 "UID FETCH %s:* (UID FLAGS)", uid ? uid : "1");
4018         camel_imapx_command_set_job (ic, job);
4019         ic->complete = imapx_job_scan_changes_done;
4020         ic->pri = job->pri;
4021         data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
4022         imapx_command_queue (is, ic);
4023         g_free (uid);
4024 }
4025
4026 static gboolean
4027 imapx_command_fetch_new_messages_done (CamelIMAPXServer *is,
4028                                        CamelIMAPXCommand *ic,
4029                                        GError **error)
4030 {
4031         CamelIMAPXJob *job;
4032         CamelIMAPXSummary *isum;
4033         CamelIMAPXFolder *ifolder;
4034         RefreshInfoData *data;
4035         gboolean success = TRUE;
4036
4037         job = camel_imapx_command_get_job (ic);
4038         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4039
4040         data = camel_imapx_job_get_data (job);
4041         g_return_val_if_fail (data != NULL, FALSE);
4042
4043         ifolder = (CamelIMAPXFolder *) job->folder;
4044         isum = (CamelIMAPXSummary *) job->folder->summary;
4045
4046         if (camel_imapx_command_set_error_if_failed (ic, error)) {
4047                 g_prefix_error (
4048                         error, "%s: ",
4049                         _("Error fetching new messages"));
4050                 success = FALSE;
4051                 goto exception;
4052         }
4053
4054         if (camel_folder_change_info_changed (data->changes)) {
4055                 camel_folder_summary_save_to_db (job->folder->summary, NULL);
4056                 imapx_update_store_summary (job->folder);
4057                 camel_folder_changed (job->folder, data->changes);
4058                 camel_folder_change_info_clear (data->changes);
4059         }
4060
4061         if (camel_folder_summary_count (job->folder->summary)) {
4062                 gchar *uid = imapx_get_uid_from_index (job->folder->summary,
4063                                                        camel_folder_summary_count (job->folder->summary) - 1);
4064                 guint64 uidl = strtoull (uid, NULL, 10);
4065                 g_free (uid);
4066
4067                 uidl++;
4068
4069                 if (uidl > ifolder->uidnext_on_server) {
4070                         c(is->tagprefix, "Updating uidnext_on_server for '%s' to %" G_GUINT64_FORMAT "\n",
4071                           camel_folder_get_full_name (job->folder), uidl);
4072                         ifolder->uidnext_on_server = uidl;
4073                 }
4074         }
4075
4076         isum->uidnext = ifolder->uidnext_on_server;
4077
4078 exception:
4079         imapx_unregister_job (is, job);
4080         camel_imapx_command_unref (ic);
4081
4082         return success;
4083 }
4084
4085 static gboolean
4086 imapx_command_fetch_new_uids_done (CamelIMAPXServer *is,
4087                                    CamelIMAPXCommand *ic,
4088                                    GError **error)
4089 {
4090         CamelIMAPXJob *job;
4091         RefreshInfoData *data;
4092
4093         job = camel_imapx_command_get_job (ic);
4094         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4095
4096         data = camel_imapx_job_get_data (job);
4097         g_return_val_if_fail (data != NULL, FALSE);
4098
4099         qsort (
4100                 data->infos->data,
4101                 data->infos->len,
4102                 sizeof (struct _refresh_info),
4103                 imapx_refresh_info_cmp_descending);
4104
4105         return imapx_command_step_fetch_done (is, ic, error);
4106 }
4107
4108 static void
4109 imapx_job_fetch_new_messages_start (CamelIMAPXJob *job,
4110                                     CamelIMAPXServer *is)
4111 {
4112         CamelIMAPXCommand *ic;
4113         CamelFolder *folder = job->folder;
4114         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
4115         CamelService *service;
4116         CamelSettings *settings;
4117         CamelSortType fetch_order;
4118         RefreshInfoData *data;
4119         guint32 total, diff;
4120         guint uidset_size;
4121         gchar *uid = NULL;
4122
4123         data = camel_imapx_job_get_data (job);
4124         g_return_if_fail (data != NULL);
4125
4126         service = CAMEL_SERVICE (is->store);
4127         settings = camel_service_get_settings (service);
4128
4129         fetch_order = camel_imapx_settings_get_fetch_order (
4130                 CAMEL_IMAPX_SETTINGS (settings));
4131
4132         uidset_size = camel_imapx_settings_get_batch_fetch_count (
4133                 CAMEL_IMAPX_SETTINGS (settings));
4134
4135         total = camel_folder_summary_count (folder->summary);
4136         diff = ifolder->exists_on_server - total;
4137
4138         if (total > 0) {
4139                 guint64 uidl;
4140                 uid = imapx_get_uid_from_index (folder->summary, total - 1);
4141                 uidl = strtoull (uid, NULL, 10);
4142                 g_free (uid);
4143                 uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uidl+1);
4144         } else
4145                 uid = g_strdup ("1");
4146
4147         job->pop_operation_msg = TRUE;
4148
4149         camel_operation_push_message (
4150                 job->cancellable,
4151                 _("Fetching summary information for new messages in %s"),
4152                 camel_folder_get_display_name (folder));
4153
4154         //printf("Fetch order: %d/%d\n", fetch_order, CAMEL_SORT_DESCENDING);
4155         if (diff > uidset_size || fetch_order == CAMEL_SORT_DESCENDING) {
4156                 ic = camel_imapx_command_new (
4157                         is, "FETCH", job->folder,
4158                         "UID FETCH %s:* (UID FLAGS)", uid);
4159                 imapx_uidset_init (&data->uidset, uidset_size, 0);
4160                 data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
4161                 ic->pri = job->pri;
4162
4163                 if (fetch_order == CAMEL_SORT_DESCENDING)
4164                         ic->complete = imapx_command_fetch_new_uids_done;
4165                 else
4166                         ic->complete = imapx_command_step_fetch_done;
4167         } else {
4168                 ic = camel_imapx_command_new (
4169                         is, "FETCH", job->folder,
4170                         "UID FETCH %s:* (RFC822.SIZE RFC822.HEADER FLAGS)", uid);
4171                 ic->pri = job->pri;
4172                 ic->complete = imapx_command_fetch_new_messages_done;
4173         }
4174
4175         g_free (uid);
4176         camel_imapx_command_set_job (ic, job);
4177         imapx_command_queue (is, ic);
4178 }
4179
4180 static void
4181 imapx_job_fetch_messages_start (CamelIMAPXJob *job,
4182                                 CamelIMAPXServer *is)
4183 {
4184         CamelIMAPXCommand *ic;
4185         CamelFolder *folder = job->folder;
4186         guint32 total;
4187         gchar *start_uid = NULL, *end_uid = NULL;
4188         CamelFetchType ftype;
4189         gint fetch_limit;
4190         CamelSortType fetch_order;
4191         CamelService *service;
4192         CamelSettings *settings;
4193         guint uidset_size;
4194         RefreshInfoData *data;
4195
4196         data = camel_imapx_job_get_data (job);
4197         g_return_if_fail (data != NULL);
4198
4199         service = CAMEL_SERVICE (is->store);
4200         settings = camel_service_get_settings (service);
4201
4202         fetch_order = camel_imapx_settings_get_fetch_order (
4203                 CAMEL_IMAPX_SETTINGS (settings));
4204
4205         total = camel_folder_summary_count (folder->summary);
4206
4207         ftype = data->fetch_type;
4208         fetch_limit = data->fetch_msg_limit;
4209
4210         uidset_size = camel_imapx_settings_get_batch_fetch_count (
4211                 CAMEL_IMAPX_SETTINGS (settings));
4212
4213         if (ftype == CAMEL_FETCH_NEW_MESSAGES ||
4214                 (ftype ==  CAMEL_FETCH_OLD_MESSAGES && total <=0 )) {
4215
4216                 gchar *uid;
4217
4218                 if (total > 0) {
4219                         /* This means that we are fetching limited number of new mails */
4220                         uid = g_strdup_printf("%d", total);
4221                 } else {
4222                         /* For empty accounts, we always fetch the specified number of new mails independent of 
4223                          * being asked to fetch old or new.
4224                          */
4225                         uid = g_strdup ("1"); 
4226                 }
4227
4228                 if (ftype == CAMEL_FETCH_NEW_MESSAGES) {
4229                         /* We need to issue Status command to get the total unread count */
4230                         ic = camel_imapx_command_new (
4231                                 is, "STATUS", NULL, 
4232                                 "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
4233                         camel_imapx_command_set_job (ic, job);
4234                         ic->pri = job->pri;
4235
4236                         imapx_command_run_sync (is, ic, job->cancellable, &job->error);
4237
4238                         job = camel_imapx_command_get_job (ic);
4239                         g_return_if_fail (CAMEL_IS_IMAPX_JOB (job));
4240
4241                         if (job->error != NULL || camel_imapx_command_set_error_if_failed (ic, &job->error)) {
4242                                 g_prefix_error (
4243                                         &job->error, "%s: ",
4244                                         _("Error while fetching messages"));
4245                         }
4246
4247                         camel_imapx_command_unref (ic);
4248                 }
4249
4250                 camel_operation_push_message (
4251                         job->cancellable,
4252                         ngettext ("Fetching summary information for %d message in %s",
4253                                   "Fetching summary information for %d messages in %s", data->fetch_msg_limit),
4254                         data->fetch_msg_limit, camel_folder_get_full_name (folder));
4255                 /* New account and fetching old messages, we would return just the limited number of newest messages */
4256                 ic = camel_imapx_command_new (
4257                         is, "FETCH", job->folder, 
4258                         "UID FETCH %s:* (UID FLAGS)", uid);
4259
4260                 imapx_uidset_init (&data->uidset, uidset_size, 0);
4261                 data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
4262                 ic->pri = job->pri;
4263
4264                 if (fetch_order == CAMEL_SORT_DESCENDING)
4265                         ic->complete = imapx_command_fetch_new_uids_done;
4266                 else
4267                         ic->complete = imapx_command_step_fetch_done;
4268
4269                 g_free (uid);
4270
4271                 camel_imapx_command_set_job (ic, job);
4272                 imapx_command_queue (is, ic);
4273
4274         } else if (ftype == CAMEL_FETCH_OLD_MESSAGES && total > 0) {
4275                         guint64 uidl;
4276                         start_uid = imapx_get_uid_from_index (folder->summary, 0);
4277                         uidl = strtoull (start_uid, NULL, 10);
4278                         end_uid = g_strdup_printf ("%" G_GINT64_MODIFIER "d", (((int)uidl)-fetch_limit > 0) ? (uidl-fetch_limit) : 1);
4279
4280                         camel_operation_push_message (
4281                                 job->cancellable,
4282                                 ngettext ("Fetching summary information for %d message in %s",
4283                                           "Fetching summary information for %d messages in %s", data->fetch_msg_limit),
4284                                 data->fetch_msg_limit, camel_folder_get_full_name (folder));
4285
4286                         ic = camel_imapx_command_new (is, "FETCH", job->folder,
4287                                                 "UID FETCH %s:%s (RFC822.SIZE RFC822.HEADER FLAGS)", start_uid, end_uid);
4288                         ic->pri = job->pri;
4289                         ic->complete = imapx_command_fetch_new_messages_done;
4290
4291                         g_free (start_uid);
4292                         g_free (end_uid);
4293
4294                         camel_imapx_command_set_job (ic, job);
4295                         imapx_command_queue (is, ic);
4296
4297         } else {
4298                 g_error ("Shouldn't reach here. Incorrect fetch type");
4299         }
4300 }
4301
4302 static void
4303 imapx_job_refresh_info_start (CamelIMAPXJob *job,
4304                               CamelIMAPXServer *is)
4305 {
4306         guint32 total;
4307         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
4308         CamelIMAPXSummary *isum = (CamelIMAPXSummary *) job->folder->summary;
4309         CamelFolder *folder = job->folder;
4310         const gchar *full_name;
4311         gboolean need_rescan = FALSE;
4312         gboolean is_selected = FALSE;
4313         gboolean can_qresync = FALSE;
4314         CamelService *service;
4315         CamelSettings *settings;
4316         gboolean mobile_mode;
4317
4318         service = CAMEL_SERVICE (is->store);
4319         settings = camel_service_get_settings (service);
4320         mobile_mode = camel_imapx_settings_get_mobile_mode (
4321                 CAMEL_IMAPX_SETTINGS (settings));
4322
4323         full_name = camel_folder_get_full_name (folder);
4324
4325         /* Sync changes first, else unread count will not
4326          * match. Need to think about better ways for this */
4327         if (!imapx_server_sync_changes (
4328                 is, folder, job->pri,
4329                 job->cancellable, &job->error))
4330                 goto done;
4331
4332 #if 0 /* There are issues with this still; continue with the buggy behaviour
4333          where we issue STATUS on the current folder, for now...*/
4334         if (is->select_folder == folder)
4335                 is_selected = TRUE;
4336 #endif
4337         total = camel_folder_summary_count (folder->summary);
4338
4339         if (ifolder->uidvalidity_on_server && isum->validity && isum->validity != ifolder->uidvalidity_on_server) {
4340                 invalidate_local_cache (ifolder, ifolder->uidvalidity_on_server);
4341                 need_rescan = TRUE;
4342         }
4343
4344         /* We don't have valid unread count or modseq for currently-selected server
4345          * (unless we want to re-SELECT it). We fake unread count when fetching
4346          * message flags, but don't depend on modseq for the selected folder */
4347         if (total != ifolder->exists_on_server ||
4348             isum->uidnext != ifolder->uidnext_on_server ||
4349             camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
4350             (!is_selected && isum->modseq != ifolder->modseq_on_server))
4351                 need_rescan = TRUE;
4352
4353         /* This is probably the first check of this folder after startup;
4354          * use STATUS to check whether the cached summary is valid, rather
4355          * than blindly updating. Only for servers which support CONDSTORE
4356          * though. */
4357         if ((isum->modseq && !ifolder->modseq_on_server))
4358                 need_rescan = FALSE;
4359
4360         /* If we don't think there's anything to do, poke it to check */
4361         if (!need_rescan) {
4362                 CamelIMAPXCommand *ic;
4363
4364                 #if 0
4365                 if (is_selected) {
4366                         /* We may not issue STATUS on the current folder. Use SELECT or NOOP instead. */
4367                         if (0 /* server needs SELECT not just NOOP */) {
4368                                 if (imapx_idle_supported (is) && imapx_in_idle (is))
4369                                         if (!imapx_stop_idle (is, &job->error))
4370                                                 goto done;
4371                                 /* This doesn't work -- this is an immediate command, not queued */
4372                                 if (!imapx_select (
4373                                         is, folder, TRUE,
4374                                         job->cancellable, &job->error))
4375                                         goto done;
4376                         } else {
4377                                 /* Or maybe just NOOP, unless we're in IDLE in which case do nothing */
4378                                 if (!imapx_idle_supported (is) || !imapx_in_idle (is)) {
4379                                         if (!camel_imapx_server_noop (is, folder, job->cancellable, &job->error))
4380                                                 goto done;
4381                                 }
4382                         }
4383                 } else
4384                 #endif
4385                 {
4386                         if (is->cinfo->capa & IMAPX_CAPABILITY_CONDSTORE)
4387                                 ic = camel_imapx_command_new (
4388                                         is, "STATUS", NULL,
4389                                         "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT HIGHESTMODSEQ)", folder);
4390                         else
4391                                 ic = camel_imapx_command_new (
4392                                         is, "STATUS", NULL,
4393                                         "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
4394
4395                         camel_imapx_command_set_job (ic, job);
4396                         ic->pri = job->pri;
4397
4398                         imapx_command_run_sync (
4399                                 is, ic, job->cancellable, &job->error);
4400
4401                         job = camel_imapx_command_get_job (ic);
4402                         g_return_if_fail (CAMEL_IS_IMAPX_JOB (job));
4403
4404                         if (job->error != NULL || camel_imapx_command_set_error_if_failed (ic, &job->error)) {
4405                                 g_prefix_error (
4406                                         &job->error, "%s: ",
4407                                         _("Error refreshing folder"));
4408                         }
4409
4410                         if (job->error != NULL) {
4411                                 camel_imapx_command_unref (ic);
4412                                 goto done;
4413                         }
4414
4415                         camel_imapx_command_unref (ic);
4416                 }
4417
4418                 /* Recalulate need_rescan */
4419                 if (total != ifolder->exists_on_server ||
4420                     isum->uidnext != ifolder->uidnext_on_server ||
4421                     camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
4422                     (!is_selected && isum->modseq != ifolder->modseq_on_server))
4423                         need_rescan = TRUE;
4424
4425         } else if (mobile_mode) {
4426                 /* We need to issue Status command to get the total unread count */
4427                 CamelIMAPXCommand *ic;
4428
4429                 ic = camel_imapx_command_new (
4430                         is, "STATUS", NULL, 
4431                         "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
4432                 camel_imapx_command_set_job (ic, job);
4433                 ic->pri = job->pri;
4434
4435                 imapx_command_run_sync (is, ic, job->cancellable, &job->error);
4436
4437                 job = camel_imapx_command_get_job (ic);
4438                 g_return_if_fail (CAMEL_IS_IMAPX_JOB (job));
4439
4440                 if (job->error != NULL || camel_imapx_command_set_error_if_failed (ic, &job->error)) {
4441                         g_prefix_error (
4442                                 &job->error, "%s: ",
4443                                 _("Error refreshing folder"));
4444                 }
4445
4446                 if (job->error != NULL) {
4447                         camel_imapx_command_unref (ic);
4448                         goto done;
4449                 }
4450                 camel_imapx_command_unref (ic);
4451         }
4452
4453         if (is->use_qresync && isum->modseq && ifolder->uidvalidity_on_server)
4454                 can_qresync = TRUE;
4455
4456         e(is->tagprefix, "folder %s is %sselected, total %u / %u, unread %u / %u, modseq %" G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT ", uidnext %u / %u: will %srescan\n",
4457           full_name, is_selected?"": "not ", total, ifolder->exists_on_server,
4458           camel_folder_summary_get_unread_count (folder->summary), ifolder->unread_on_server,
4459           (guint64) isum->modseq, (guint64) ifolder->modseq_on_server,
4460           isum->uidnext, ifolder->uidnext_on_server,
4461           need_rescan?"":"not ");
4462
4463         /* Fetch new messages first, so that they appear to the user ASAP */
4464         if (ifolder->exists_on_server > total ||
4465             ifolder->uidnext_on_server > isum->uidnext)
4466         {
4467                 if (!total)
4468                         need_rescan = FALSE;
4469
4470                 if (!imapx_server_fetch_new_messages (
4471                         is, folder, FALSE, FALSE,
4472                         job->cancellable, &job->error))
4473                         goto done;
4474
4475                 /* If QRESYNC-capable we'll have got all flags changes in SELECT */
4476                 if (can_qresync)
4477                         goto qresync_done;
4478         }
4479
4480         if (!need_rescan)
4481                 goto done;
4482
4483         if (can_qresync) {
4484                 /* Actually we only want to select it; no need for the NOOP */
4485                 camel_imapx_server_noop (is, folder, job->cancellable, &job->error);
4486         qresync_done:
4487                 isum->modseq = ifolder->modseq_on_server;
4488                 total = camel_folder_summary_count (job->folder->summary);
4489                 if (total != ifolder->exists_on_server ||
4490                     camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
4491                     (isum->modseq != ifolder->modseq_on_server)) {
4492                         c(is->tagprefix, "Eep, after QRESYNC we're out of sync. total %u / %u, unread %u / %u, modseq %" G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT "\n",
4493                           total, ifolder->exists_on_server,
4494                           camel_folder_summary_get_unread_count (folder->summary), ifolder->unread_on_server,
4495                           isum->modseq, ifolder->modseq_on_server);
4496                 } else {
4497                         c(is->tagprefix, "OK, after QRESYNC we're still in sync. total %u / %u, unread %u / %u, modseq %" G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT "\n",
4498                           total, ifolder->exists_on_server,
4499                           camel_folder_summary_get_unread_count (folder->summary), ifolder->unread_on_server,
4500                           isum->modseq, ifolder->modseq_on_server);
4501                         goto done;
4502                 }
4503         }
4504
4505         imapx_job_scan_changes_start (job, is);
4506         return;
4507
4508 done:
4509         imapx_unregister_job (is, job);
4510 }
4511
4512 static gboolean
4513 imapx_job_refresh_info_matches (CamelIMAPXJob *job,
4514                                 CamelFolder *folder,
4515                                 const gchar *uid)
4516 {
4517         return (folder == job->folder);
4518 }
4519
4520 /* ********************************************************************** */
4521
4522 static gboolean
4523 imapx_command_expunge_done (CamelIMAPXServer *is,
4524                             CamelIMAPXCommand *ic,
4525                             GError **error)
4526 {
4527         CamelIMAPXJob *job;
4528         gboolean success = TRUE;
4529
4530         job = camel_imapx_command_get_job (ic);
4531         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4532
4533         if (camel_imapx_command_set_error_if_failed (ic, error)) {
4534                 g_prefix_error (
4535                         error, "%s: ",
4536                         _("Error expunging message"));
4537                 success = FALSE;
4538
4539         } else {
4540                 GPtrArray *uids;
4541                 CamelFolder *folder = job->folder;
4542                 CamelStore *parent_store;
4543                 const gchar *full_name;
4544
4545                 full_name = camel_folder_get_full_name (folder);
4546                 parent_store = camel_folder_get_parent_store (folder);
4547
4548                 camel_folder_summary_save_to_db (folder->summary, NULL);
4549                 uids = camel_db_get_folder_deleted_uids (parent_store->cdb_r, full_name, &job->error);
4550
4551                 if (uids && uids->len)  {
4552                         CamelFolderChangeInfo *changes;
4553                         GList *removed = NULL;
4554                         gint i;
4555
4556                         changes = camel_folder_change_info_new ();
4557                         for (i = 0; i < uids->len; i++) {
4558                                 gchar *uid = uids->pdata[i];
4559
4560                                 camel_folder_summary_remove_uid (folder->summary, uid);
4561                                 camel_folder_change_info_remove_uid (changes, uids->pdata[i]);
4562                                 removed = g_list_prepend (removed, (gpointer) uids->pdata[i]);
4563                         }
4564
4565                         camel_folder_summary_save_to_db (folder->summary, NULL);
4566                         camel_folder_changed (folder, changes);
4567                         camel_folder_change_info_free (changes);
4568
4569                         g_list_free (removed);
4570                         g_ptr_array_foreach (uids, (GFunc) camel_pstring_free, NULL);
4571                         g_ptr_array_free (uids, TRUE);
4572                 }
4573         }
4574
4575         imapx_unregister_job (is, job);
4576         camel_imapx_command_unref (ic);
4577
4578         return success;
4579 }
4580
4581 static void
4582 imapx_job_expunge_start (CamelIMAPXJob *job,
4583                          CamelIMAPXServer *is)
4584 {
4585         CamelIMAPXCommand *ic;
4586
4587         imapx_server_sync_changes (
4588                 is, job->folder, job->pri,
4589                 job->cancellable, &job->error);
4590
4591         /* TODO handle UIDPLUS capability */
4592         ic = camel_imapx_command_new (
4593                 is, "EXPUNGE", job->folder, "EXPUNGE");
4594         camel_imapx_command_set_job (ic, job);
4595         ic->pri = job->pri;
4596         ic->complete = imapx_command_expunge_done;
4597
4598         imapx_command_queue (is, ic);
4599 }
4600
4601 static gboolean
4602 imapx_job_expunge_matches (CamelIMAPXJob *job,
4603                            CamelFolder *folder,
4604                            const gchar *uid)
4605 {
4606         return (folder == job->folder);
4607 }
4608
4609 /* ********************************************************************** */
4610
4611 static gboolean
4612 imapx_command_list_done (CamelIMAPXServer *is,
4613                          CamelIMAPXCommand *ic,
4614                          GError **error)
4615 {
4616         CamelIMAPXJob *job;
4617         gboolean success = TRUE;
4618
4619         job = camel_imapx_command_get_job (ic);
4620         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4621
4622         if (camel_imapx_command_set_error_if_failed (ic, error)) {
4623                 g_prefix_error (
4624                         error, "%s: ",
4625                         _("Error fetching folders"));
4626                 success = FALSE;
4627         }
4628
4629         e (is->tagprefix, "==== list or lsub completed ==== \n");
4630         imapx_unregister_job (is, job);
4631         camel_imapx_command_unref (ic);
4632
4633         return success;
4634 }
4635
4636 static void
4637 imapx_job_list_start (CamelIMAPXJob *job,
4638                       CamelIMAPXServer *is)
4639 {
4640         CamelIMAPXCommand *ic;
4641         ListData *data;
4642
4643         data = camel_imapx_job_get_data (job);
4644         g_return_if_fail (data != NULL);
4645
4646         ic = camel_imapx_command_new (
4647                 is, "LIST", NULL,
4648                 "%s \"\" %s",
4649                 (data->flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) ?
4650                         "LSUB" : "LIST",
4651                 data->pattern);
4652         if (data->ext) {
4653                 /* Hm, we need a way to add atoms _without_ quoting or using literals */
4654                 camel_imapx_command_add (ic, " ");
4655                 camel_imapx_command_add (ic, data->ext);
4656         }
4657         ic->pri = job->pri;
4658         camel_imapx_command_set_job (ic, job);
4659         ic->complete = imapx_command_list_done;
4660
4661         imapx_command_queue (is, ic);
4662 }
4663
4664 static gboolean
4665 imapx_job_list_matches (CamelIMAPXJob *job,
4666                         CamelFolder *folder,
4667                         const gchar *uid)
4668 {
4669         return TRUE;  /* matches everything */
4670 }
4671
4672 /* ********************************************************************** */
4673
4674 static gchar *
4675 imapx_encode_folder_name (CamelIMAPXStore *istore,
4676                           const gchar *folder_name)
4677 {
4678         gchar *fname, *encoded;
4679
4680         fname = camel_imapx_store_summary_full_from_path (istore->summary, folder_name);
4681         if (fname) {
4682                 encoded = camel_utf8_utf7 (fname);
4683                 g_free (fname);
4684         } else
4685                 encoded = camel_utf8_utf7 (folder_name);
4686
4687         return encoded;
4688 }
4689
4690 static gboolean
4691 imapx_command_subscription_done (CamelIMAPXServer *is,
4692                                  CamelIMAPXCommand *ic,
4693                                  GError **error)
4694 {
4695         CamelIMAPXJob *job;
4696         gboolean success = TRUE;
4697
4698         job = camel_imapx_command_get_job (ic);
4699         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4700
4701         if (camel_imapx_command_set_error_if_failed (ic, error)) {
4702                 g_prefix_error (
4703                         error, "%s: ",
4704                         _("Error subscribing to folder"));
4705                 success = FALSE;
4706         }
4707
4708         imapx_unregister_job (is, job);
4709         camel_imapx_command_unref (ic);
4710
4711         return success;
4712 }
4713
4714 static void
4715 imapx_job_manage_subscription_start (CamelIMAPXJob *job,
4716                                      CamelIMAPXServer *is)
4717 {
4718         CamelIMAPXCommand *ic;
4719         ManageSubscriptionsData *data;
4720         gchar *encoded_fname = NULL;
4721
4722         data = camel_imapx_job_get_data (job);
4723         g_return_if_fail (data != NULL);
4724
4725         encoded_fname = imapx_encode_folder_name (
4726                 (CamelIMAPXStore *) is->store,
4727                 data->folder_name);
4728         if (data->subscribe)
4729                 ic = camel_imapx_command_new (
4730                         is, "SUBSCRIBE", NULL,
4731                         "SUBSCRIBE %s", encoded_fname);
4732         else
4733                 ic = camel_imapx_command_new (
4734                         is, "UNSUBSCRIBE", NULL,
4735                         "UNSUBSCRIBE %s", encoded_fname);
4736
4737         ic->pri = job->pri;
4738         camel_imapx_command_set_job (ic, job);
4739         ic->complete = imapx_command_subscription_done;
4740         imapx_command_queue (is, ic);
4741
4742         g_free (encoded_fname);
4743 }
4744
4745 /* ********************************************************************** */
4746
4747 static gboolean
4748 imapx_command_create_folder_done (CamelIMAPXServer *is,
4749                                   CamelIMAPXCommand *ic,
4750                                   GError **error)
4751 {
4752         CamelIMAPXJob *job;
4753         gboolean success = TRUE;
4754
4755         job = camel_imapx_command_get_job (ic);
4756         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4757
4758         if (camel_imapx_command_set_error_if_failed (ic, error)) {
4759                 g_prefix_error (
4760                         error, "%s: ",
4761                         _("Error creating folder"));
4762                 success = FALSE;
4763         }
4764
4765         imapx_unregister_job (is, job);
4766         camel_imapx_command_unref (ic);
4767
4768         return success;
4769 }
4770
4771 static void
4772 imapx_job_create_folder_start (CamelIMAPXJob *job,
4773                                CamelIMAPXServer *is)
4774 {
4775         CamelIMAPXCommand *ic;
4776         CreateFolderData *data;
4777         gchar *encoded_fname = NULL;
4778
4779         data = camel_imapx_job_get_data (job);
4780         g_return_if_fail (data != NULL);
4781
4782         encoded_fname = camel_utf8_utf7 (data->folder_name);
4783         ic = camel_imapx_command_new (
4784                 is, "CREATE", NULL,
4785                 "CREATE %s", encoded_fname);
4786         ic->pri = job->pri;
4787         camel_imapx_command_set_job (ic, job);
4788         ic->complete = imapx_command_create_folder_done;
4789         imapx_command_queue (is, ic);
4790
4791         g_free (encoded_fname);
4792 }
4793
4794 /* ********************************************************************** */
4795
4796 static gboolean
4797 imapx_command_delete_folder_done (CamelIMAPXServer *is,
4798                                   CamelIMAPXCommand *ic,
4799                                   GError **error)
4800 {
4801         CamelIMAPXJob *job;
4802         gboolean success;
4803
4804         job = camel_imapx_command_get_job (ic);
4805         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4806
4807         if (camel_imapx_command_set_error_if_failed (ic, error)) {
4808                 g_prefix_error (
4809                         error, "%s: ",
4810                         _("Error deleting folder"));
4811                 success = FALSE;
4812         }
4813
4814         imapx_unregister_job (is, job);
4815         camel_imapx_command_unref (ic);
4816
4817         return success;
4818 }
4819
4820 static void
4821 imapx_job_delete_folder_start (CamelIMAPXJob *job,
4822                                CamelIMAPXServer *is)
4823 {
4824         CamelIMAPXCommand *ic;
4825         DeleteFolderData *data;
4826         gchar *encoded_fname = NULL;
4827
4828         data = camel_imapx_job_get_data (job);
4829         g_return_if_fail (data != NULL);
4830
4831         encoded_fname = imapx_encode_folder_name ((CamelIMAPXStore *) is->store, data->folder_name);
4832
4833         job->folder = camel_store_get_folder_sync (
4834                 is->store, "INBOX", 0, job->cancellable, &job->error);
4835
4836         /* make sure to-be-deleted folder is not selected by selecting INBOX for this operation */
4837         ic = camel_imapx_command_new (
4838                 is, "DELETE", job->folder,
4839                 "DELETE %s", encoded_fname);
4840         ic->pri = job->pri;
4841         camel_imapx_command_set_job (ic, job);
4842         ic->complete = imapx_command_delete_folder_done;
4843         imapx_command_queue (is, ic);
4844
4845         g_free (encoded_fname);
4846 }
4847
4848 /* ********************************************************************** */
4849
4850 static gboolean
4851 imapx_command_rename_folder_done (CamelIMAPXServer *is,
4852                                   CamelIMAPXCommand *ic,
4853                                   GError **error)
4854 {
4855         CamelIMAPXJob *job;
4856         gboolean success = TRUE;
4857
4858         job = camel_imapx_command_get_job (ic);
4859         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4860
4861         if (camel_imapx_command_set_error_if_failed (ic, error)) {
4862                 g_prefix_error (
4863                         error, "%s: ",
4864                         _("Error renaming folder"));
4865                 success = FALSE;
4866         }
4867
4868         imapx_unregister_job (is, job);
4869         camel_imapx_command_unref (ic);
4870
4871         return success;
4872 }
4873
4874 static void
4875 imapx_job_rename_folder_start (CamelIMAPXJob *job,
4876                                CamelIMAPXServer *is)
4877 {
4878         CamelIMAPXCommand *ic;
4879         RenameFolderData *data;
4880         gchar *en_ofname = NULL, *en_nfname = NULL;
4881
4882         data = camel_imapx_job_get_data (job);
4883         g_return_if_fail (data != NULL);
4884
4885         job->folder = camel_store_get_folder_sync (
4886                 is->store, "INBOX", 0, job->cancellable, &job->error);
4887
4888         en_ofname = imapx_encode_folder_name ((CamelIMAPXStore *) is->store, data->old_folder_name);
4889         en_nfname = imapx_encode_folder_name ((CamelIMAPXStore *) is->store, data->new_folder_name);
4890
4891         ic = camel_imapx_command_new (
4892                 is, "RENAME", job->folder,
4893                 "RENAME %s %s", en_ofname, en_nfname);
4894         ic->pri = job->pri;
4895         camel_imapx_command_set_job (ic, job);
4896         ic->complete = imapx_command_rename_folder_done;
4897         imapx_command_queue (is, ic);
4898
4899         g_free (en_ofname);
4900         g_free (en_nfname);
4901 }
4902
4903 /* ********************************************************************** */
4904
4905 static gboolean
4906 imapx_command_noop_done (CamelIMAPXServer *is,
4907                          CamelIMAPXCommand *ic,
4908                          GError **error)
4909 {
4910         CamelIMAPXJob *job;
4911         gboolean success = TRUE;
4912
4913         job = camel_imapx_command_get_job (ic);
4914         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4915
4916         if (camel_imapx_command_set_error_if_failed (ic, error)) {
4917                 g_prefix_error (
4918                         error, "%s: ",
4919                         _("Error performing NOOP"));
4920                 success = FALSE;
4921         }
4922
4923         imapx_unregister_job (is, job);
4924         camel_imapx_command_unref (ic);
4925
4926         return success;
4927 }
4928
4929 static void
4930 imapx_job_noop_start (CamelIMAPXJob *job,
4931                       CamelIMAPXServer *is)
4932 {
4933         CamelIMAPXCommand *ic;
4934
4935         ic = camel_imapx_command_new (
4936                 is, "NOOP", job->folder, "NOOP");
4937
4938         camel_imapx_command_set_job (ic, job);
4939         ic->complete = imapx_command_noop_done;
4940         if (job->folder)
4941                 ic->pri = IMAPX_PRIORITY_REFRESH_INFO;
4942         else
4943                 ic->pri = IMAPX_PRIORITY_NOOP;
4944         imapx_command_queue (is, ic);
4945 }
4946
4947 /* ********************************************************************** */
4948
4949 /* FIXME: this is basically a copy of the same in camel-imapx-utils.c */
4950 static struct {
4951         const gchar *name;
4952         guint32 flag;
4953 } flags_table[] = {
4954         { "\\ANSWERED", CAMEL_MESSAGE_ANSWERED },
4955         { "\\DELETED", CAMEL_MESSAGE_DELETED },
4956         { "\\DRAFT", CAMEL_MESSAGE_DRAFT },
4957         { "\\FLAGGED", CAMEL_MESSAGE_FLAGGED },
4958         { "\\SEEN", CAMEL_MESSAGE_SEEN },
4959         { "\\RECENT", CAMEL_IMAPX_MESSAGE_RECENT },
4960         { "JUNK", CAMEL_MESSAGE_JUNK },
4961         { "NOTJUNK", CAMEL_MESSAGE_NOTJUNK }
4962 };
4963
4964 /*
4965  *  flags 00101000
4966  * sflags 01001000
4967  * ^      01100000
4968  * ~flags 11010111
4969  * &      01000000
4970  *
4971  * &flags 00100000
4972  */
4973
4974 static gboolean
4975 imapx_command_sync_changes_done (CamelIMAPXServer *is,
4976                                  CamelIMAPXCommand *ic,
4977                                  GError **error)
4978 {
4979         CamelIMAPXJob *job;
4980         CamelStore *parent_store;
4981         SyncChangesData *data;
4982         const gchar *full_name;
4983         CamelService *service;
4984         CamelSettings *settings;
4985         gboolean mobile_mode;
4986         gboolean success = TRUE;
4987
4988         job = camel_imapx_command_get_job (ic);
4989         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4990
4991         data = camel_imapx_job_get_data (job);
4992         g_return_val_if_fail (data != NULL, FALSE);
4993
4994         service = CAMEL_SERVICE (is->store);
4995         settings = camel_service_get_settings (service);
4996         mobile_mode = camel_imapx_settings_get_mobile_mode (
4997                 CAMEL_IMAPX_SETTINGS (settings));
4998
4999         job->commands--;
5000
5001         full_name = camel_folder_get_full_name (job->folder);
5002         parent_store = camel_folder_get_parent_store (job->folder);
5003
5004         /* If this worked, we should really just update the changes that we
5005          * sucessfully stored, so we dont have to worry about sending them
5006          * again ...
5007          * But then we'd have to track which uid's we actually updated, so
5008          * its easier just to refresh all of the ones we got.
5009          *
5010          * Not that ... given all the asynchronicity going on, we're guaranteed
5011          * that what we just set is actually what is on the server now .. but
5012          * if it isn't, i guess we'll fix up next refresh */
5013
5014         if (camel_imapx_command_set_error_if_failed (ic, error)) {
5015                 g_prefix_error (
5016                         error, "%s: ",
5017                         _("Error syncing changes"));
5018                 success = FALSE;
5019
5020         /* lock cache ? */
5021         } else {
5022                 gint i;
5023
5024                 for (i = 0; i < data->changed_uids->len; i++) {
5025                         CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) camel_folder_summary_get (job->folder->summary,
5026                                         data->changed_uids->pdata[i]);
5027
5028                         if (!xinfo)
5029                                 continue;
5030
5031                         xinfo->server_flags = ((CamelMessageInfoBase *) xinfo)->flags & CAMEL_IMAPX_SERVER_FLAGS;
5032                         xinfo->info.flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
5033                         xinfo->info.dirty = TRUE;
5034                         camel_flag_list_copy (&xinfo->server_user_flags, &xinfo->info.user_flags);
5035
5036                         camel_folder_summary_touch (job->folder->summary);
5037                         camel_message_info_free (xinfo);
5038                 }
5039                 /* Apply the changes to server-side unread count; it won't tell
5040                  * us of these changes, of course. */
5041                 ((CamelIMAPXFolder *) job->folder)->unread_on_server += data->unread_change;
5042         }
5043
5044         if (job->commands == 0) {
5045                 if (job->folder->summary && (job->folder->summary->flags & CAMEL_SUMMARY_DIRTY) != 0) {
5046                         CamelStoreInfo *si;
5047
5048                         /* ... and store's summary when folder's summary is dirty */
5049                         si = camel_store_summary_path ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary, full_name);
5050                         if (si) {
5051                                 if (si->total != camel_folder_summary_get_saved_count (job->folder->summary) ||
5052                                     si->unread != camel_folder_summary_get_unread_count (job->folder->summary)) {
5053                                         si->total = camel_folder_summary_get_saved_count (job->folder->summary);
5054                                         if (!mobile_mode) /* Don't mess with server's unread count in mobile mode, as what we have downloaded is little */
5055                                                 si->unread = camel_folder_summary_get_unread_count (job->folder->summary);
5056                                         camel_store_summary_touch ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
5057                                 }
5058
5059                                 camel_store_summary_info_free ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary, si);
5060                         }
5061                 }
5062
5063                 camel_folder_summary_save_to_db (job->folder->summary, &job->error);
5064                 camel_store_summary_save ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
5065
5066                 imapx_unregister_job (is, job);
5067         }
5068
5069         camel_imapx_command_unref (ic);
5070
5071         return success;
5072 }
5073
5074 static void
5075 imapx_job_sync_changes_start (CamelIMAPXJob *job,
5076                               CamelIMAPXServer *is)
5077 {
5078         SyncChangesData *data;
5079         guint32 i, j;
5080         struct _uidset_state ss;
5081         GPtrArray *uids;
5082         gint on;
5083
5084         data = camel_imapx_job_get_data (job);
5085         g_return_if_fail (data != NULL);
5086
5087         uids = data->changed_uids;
5088
5089         for (on = 0; on < 2; on++) {
5090                 guint32 orset = on ? data->on_set : data->off_set;
5091                 GArray *user_set = on ? data->on_user : data->off_user;
5092
5093                 for (j = 0; j < G_N_ELEMENTS (flags_table); j++) {
5094                         guint32 flag = flags_table[j].flag;
5095                         CamelIMAPXCommand *ic = NULL;
5096
5097                         if ((orset & flag) == 0)
5098                                 continue;
5099
5100                         c(is->tagprefix, "checking/storing %s flags '%s'\n", on?"on":"off", flags_table[j].name);
5101                         imapx_uidset_init (&ss, 0, 100);
5102                         for (i = 0; i < uids->len; i++) {
5103                                 CamelIMAPXMessageInfo *info = (CamelIMAPXMessageInfo *) camel_folder_summary_get
5104                                                                                 (job->folder->summary, uids->pdata[i]);
5105                                 guint32 flags;
5106                                 guint32 sflags;
5107                                 gint send;
5108
5109                                 if (!info)
5110                                         continue;
5111
5112                                 flags = ((CamelMessageInfoBase *) info)->flags & CAMEL_IMAPX_SERVER_FLAGS;
5113                                 sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
5114                                 send = 0;
5115
5116                                 if ( (on && (((flags ^ sflags) & flags) & flag))
5117                                      || (!on && (((flags ^ sflags) & ~flags) & flag))) {
5118                                         if (ic == NULL) {
5119                                                 ic = camel_imapx_command_new (
5120                                                         is, "STORE", job->folder,
5121                                                         "UID STORE ");
5122                                                 ic->complete = imapx_command_sync_changes_done;
5123                                                 camel_imapx_command_set_job (ic, job);
5124                                                 ic->pri = job->pri;
5125                                         }
5126                                         send = imapx_uidset_add (&ss, ic, camel_message_info_uid (info));
5127                                 }
5128                                 if (send == 1 || (i == uids->len - 1 && imapx_uidset_done (&ss, ic))) {
5129                                         job->commands++;
5130                                         camel_imapx_command_add (ic, " %tFLAGS.SILENT (%t)", on?"+":"-", flags_table[j].name);
5131                                         imapx_command_queue (is, ic);
5132                                         ic = NULL;
5133                                 }
5134                                 if (flag == CAMEL_MESSAGE_SEEN) {
5135                                         /* Remember how the server's unread count will change if this
5136                                          * command succeeds */
5137                                         if (on)
5138                                                 data->unread_change--;
5139                                         else
5140                                                 data->unread_change++;
5141                                 }
5142                                 camel_message_info_free (info);
5143                         }
5144                 }
5145
5146                 if (user_set) {
5147                         CamelIMAPXCommand *ic = NULL;
5148
5149                         for (j = 0; j < user_set->len; j++) {
5150                                 struct _imapx_flag_change *c = &g_array_index (user_set, struct _imapx_flag_change, j);
5151
5152                                 imapx_uidset_init (&ss, 0, 100);
5153                                 for (i = 0; i < c->infos->len; i++) {
5154                                         CamelIMAPXMessageInfo *info = c->infos->pdata[i];
5155
5156                                         if (ic == NULL) {
5157                                                 ic = camel_imapx_command_new (
5158                                                         is, "STORE", job->folder,
5159                                                         "UID STORE ");
5160                                                 ic->complete = imapx_command_sync_changes_done;
5161                                                 camel_imapx_command_set_job (ic, job);
5162                                                 ic->pri = job->pri;
5163                                         }
5164
5165                                         if (imapx_uidset_add (&ss, ic, camel_message_info_uid (info)) == 1
5166                                             || (i == c->infos->len - 1 && imapx_uidset_done (&ss, ic))) {
5167                                                 job->commands++;
5168                                                 camel_imapx_command_add (ic, " %tFLAGS.SILENT (%t)", on?"+":"-", c->name);
5169                                                 imapx_command_queue (is, ic);
5170                                                 ic = NULL;
5171                                         }
5172                                 }
5173                         }
5174                 }
5175         }
5176
5177         /* Since this may start in another thread ... we need to
5178          * lock the commands count, ho hum */
5179
5180         if (job->commands == 0) {
5181                 imapx_unregister_job (is, job);
5182         }
5183 }
5184
5185 static gboolean
5186 imapx_job_sync_changes_matches (CamelIMAPXJob *job,
5187                                 CamelFolder *folder,
5188                                 const gchar *uid)
5189 {
5190         return (folder == job->folder);
5191 }
5192
5193 /* we cancel all the commands and their jobs, so associated jobs will be notified */
5194 static void
5195 cancel_all_jobs (CamelIMAPXServer *is,
5196                  GError *error)
5197 {
5198         CamelIMAPXCommandQueue *queue;
5199         GList *head, *link;
5200
5201         /* Transfer all pending and active commands to a separate
5202          * command queue to complete them without holding QUEUE_LOCK. */
5203
5204         queue = camel_imapx_command_queue_new ();
5205
5206         QUEUE_LOCK (is);
5207
5208         camel_imapx_command_queue_transfer (is->queue, queue);
5209         camel_imapx_command_queue_transfer (is->active, queue);
5210
5211         QUEUE_UNLOCK (is);
5212
5213         head = camel_imapx_command_queue_peek_head_link (queue);
5214
5215         for (link = head; link != NULL; link = g_list_next (link)) {
5216                 CamelIMAPXCommand *ic = link->data;
5217                 CamelIMAPXJob *job;
5218
5219                 /* Sanity check the CamelIMAPXCommand before proceeding.
5220                  * XXX We are actually getting reports of crashes here...
5221                  *     not sure how this is happening but it's happening. */
5222                 if (ic == NULL)
5223                         continue;
5224
5225                 /* Similarly with the CamelIMAPXJob contained within. */
5226                 job = camel_imapx_command_get_job (ic);
5227                 if (!CAMEL_IS_IMAPX_JOB (job))
5228                         continue;
5229
5230                 if (job->error == NULL)
5231                         job->error = g_error_copy (error);
5232
5233                 /* Send a NULL GError since we've already set
5234                  * the job's GError, and we're not interested
5235                  * in individual command errors. */
5236                 ic->complete (is, ic, NULL);
5237         }
5238
5239         camel_imapx_command_queue_free (queue);
5240 }
5241
5242 /* ********************************************************************** */
5243
5244 static void
5245 parse_contents (CamelIMAPXServer *is,
5246                 GCancellable *cancellable,
5247                 GError **error)
5248 {
5249         while (imapx_step (is, cancellable, error))
5250                 if (camel_imapx_stream_buffered (is->stream) == 0)
5251                         break;
5252 }
5253
5254 /*
5255  * The main processing (reading) loop.
5256  *
5257  * Main area of locking required is command_queue
5258  * and command_start_next, the 'literal' command,
5259  * the jobs queue, the active queue, the queue
5260  * queue. */
5261 static gpointer
5262 imapx_parser_thread (gpointer d)
5263 {
5264         CamelIMAPXServer *is = d;
5265         GCancellable *cancellable;
5266         GError *local_error = NULL;
5267
5268         QUEUE_LOCK (is);
5269         cancellable = camel_operation_new ();
5270         is->cancellable = g_object_ref (cancellable);
5271         QUEUE_UNLOCK (is);
5272
5273         while (local_error == NULL && is->stream) {
5274                 g_cancellable_reset (cancellable);
5275
5276 #ifndef G_OS_WIN32
5277                 if (is->is_process_stream)      {
5278                         GPollFD fds[2] = { {0, 0, 0}, {0, 0, 0} };
5279                         gint res;
5280
5281                         fds[0].fd = ((CamelStreamProcess *) is->stream->source)->sockfd;
5282                         fds[0].events = G_IO_IN;
5283                         fds[1].fd = g_cancellable_get_fd (cancellable);
5284                         fds[1].events = G_IO_IN;
5285                         res = g_poll (fds, 2, -1);
5286                         if (res == -1)
5287                                 g_usleep (1) /* ?? */ ;
5288                         else if (res == 0)
5289                                 /* timed out */;
5290                         else if (fds[0].revents & G_IO_IN)
5291                                 parse_contents (is, cancellable, &local_error);
5292                         g_cancellable_release_fd (cancellable);
5293                 } else
5294 #endif
5295                 {
5296                         parse_contents (is, cancellable, &local_error);
5297                 }
5298
5299                 if (is->parser_quit)
5300                         g_cancellable_cancel (cancellable);
5301                 else if (g_cancellable_is_cancelled (cancellable)) {
5302                         gint is_empty;
5303
5304                         QUEUE_LOCK (is);
5305                         is_empty = camel_imapx_command_queue_is_empty (is->active);
5306                         QUEUE_UNLOCK (is);
5307
5308                         if (is_empty || (imapx_idle_supported (is) && imapx_in_idle (is))) {
5309                                 g_cancellable_reset (cancellable);
5310                                 g_clear_error (&local_error);
5311                         } else {
5312                                 /* Cancelled error should be set. */
5313                                 g_warn_if_fail (local_error != NULL);
5314                         }
5315                 }
5316
5317                 /* Jump out of the loop if an error occurred. */
5318                 if (local_error != NULL)
5319                         break;
5320         }
5321
5322         QUEUE_LOCK (is);
5323         is->state = IMAPX_SHUTDOWN;
5324         QUEUE_UNLOCK (is);
5325
5326         cancel_all_jobs (is, local_error);
5327
5328         g_clear_error (&local_error);
5329
5330         QUEUE_LOCK (is);
5331         if (is->cancellable != NULL) {
5332                 g_object_unref (is->cancellable);
5333                 is->cancellable = NULL;
5334         }
5335         g_object_unref (cancellable);
5336         QUEUE_UNLOCK (is);
5337
5338         is->parser_quit = FALSE;
5339         if (is->state != IMAPX_SHUTDOWN)
5340                 g_signal_emit (is, signals[SHUTDOWN], 0);
5341
5342         return NULL;
5343 }
5344
5345 static gboolean
5346 join_helper (gpointer thread)
5347 {
5348         g_thread_join (thread);
5349         return FALSE;
5350 }
5351
5352 static void
5353 imapx_server_dispose (GObject *object)
5354 {
5355         CamelIMAPXServer *server = CAMEL_IMAPX_SERVER (object);
5356
5357         QUEUE_LOCK (server);
5358         server->state = IMAPX_SHUTDOWN;
5359
5360         server->parser_quit = TRUE;
5361
5362         if (server->cancellable != NULL) {
5363                 g_cancellable_cancel (server->cancellable);
5364                 g_object_unref (server->cancellable);
5365                 server->cancellable = NULL;
5366         }
5367         QUEUE_UNLOCK (server);
5368
5369         if (server->parser_thread) {
5370                 if (server->parser_thread == g_thread_self ())
5371                         g_idle_add (&join_helper, server->parser_thread);
5372                 else
5373                         g_thread_join (server->parser_thread);
5374                 server->parser_thread = NULL;
5375         }
5376
5377         if (server->cinfo && imapx_idle_supported (server))
5378                 imapx_exit_idle (server);
5379
5380         imapx_disconnect (server);
5381
5382         if (server->session != NULL) {
5383                 g_object_unref (server->session);
5384                 server->session = NULL;
5385         }
5386
5387         /* Chain up to parent's dispose() method. */
5388         G_OBJECT_CLASS (camel_imapx_server_parent_class)->dispose (object);
5389 }
5390
5391 static void
5392 imapx_server_finalize (GObject *object)
5393 {
5394         CamelIMAPXServer *is = CAMEL_IMAPX_SERVER (object);
5395
5396         g_static_rec_mutex_free (&is->queue_lock);
5397         g_static_rec_mutex_free (&is->ostream_lock);
5398         g_mutex_free (is->fetch_mutex);
5399         g_cond_free (is->fetch_cond);
5400
5401         camel_folder_change_info_free (is->changes);
5402
5403         /* Chain up to parent's finalize() method. */
5404         G_OBJECT_CLASS (camel_imapx_server_parent_class)->finalize (object);
5405 }
5406
5407 static void
5408 imapx_server_constructed (GObject *object)
5409 {
5410         CamelIMAPXServer *server;
5411         CamelIMAPXServerClass *class;
5412
5413         server = CAMEL_IMAPX_SERVER (object);
5414         class = CAMEL_IMAPX_SERVER_GET_CLASS (server);
5415
5416         server->tagprefix = class->tagprefix;
5417         class->tagprefix++;
5418         if (class->tagprefix > 'Z')
5419                 class->tagprefix = 'A';
5420 }
5421
5422 static void
5423 camel_imapx_server_class_init (CamelIMAPXServerClass *class)
5424 {
5425         GObjectClass *object_class;
5426
5427         object_class = G_OBJECT_CLASS (class);
5428         object_class->finalize = imapx_server_finalize;
5429         object_class->constructed = imapx_server_constructed;
5430         object_class->dispose = imapx_server_dispose;
5431
5432         class->select_changed = NULL;
5433         class->shutdown = NULL;
5434
5435         /**
5436          * CamelIMAPXServer::select_changed
5437          * @server: the #CamelIMAPXServer which emitted the signal
5438          **/
5439         signals[SELECT_CHANGED] = g_signal_new (
5440                 "select_changed",
5441                 G_OBJECT_CLASS_TYPE (class),
5442                 G_SIGNAL_RUN_FIRST,
5443                 G_STRUCT_OFFSET (CamelIMAPXServerClass, select_changed),
5444                 NULL, NULL,
5445                 g_cclosure_marshal_VOID__STRING,
5446                 G_TYPE_NONE, 1, G_TYPE_STRING);
5447
5448         /**
5449          * CamelIMAPXServer::shutdown
5450          * @server: the #CamelIMAPXServer which emitted the signal
5451          **/
5452         signals[SHUTDOWN] = g_signal_new (
5453                 "shutdown",
5454                 G_OBJECT_CLASS_TYPE (class),
5455                 G_SIGNAL_RUN_FIRST,
5456                 G_STRUCT_OFFSET (CamelIMAPXServerClass, shutdown),
5457                 NULL, NULL,
5458                 g_cclosure_marshal_VOID__VOID,
5459                 G_TYPE_NONE, 0);
5460
5461         class->tagprefix = 'A';
5462 }
5463
5464 static void
5465 camel_imapx_server_init (CamelIMAPXServer *is)
5466 {
5467         is->queue = camel_imapx_command_queue_new ();
5468         is->active = camel_imapx_command_queue_new ();
5469         is->done = camel_imapx_command_queue_new ();
5470
5471         g_queue_init (&is->jobs);
5472
5473         /* not used at the moment. Use it in future */
5474         is->job_timeout = 29 * 60 * 1000 * 1000;
5475
5476         g_static_rec_mutex_init (&is->queue_lock);
5477         g_static_rec_mutex_init (&is->ostream_lock);
5478
5479         is->state = IMAPX_DISCONNECTED;
5480
5481         is->expunged = NULL;
5482         is->changes = camel_folder_change_info_new ();
5483         is->parser_quit = FALSE;
5484
5485         is->fetch_mutex = g_mutex_new ();
5486         is->fetch_cond = g_cond_new ();
5487 }
5488
5489 CamelIMAPXServer *
5490 camel_imapx_server_new (CamelStore *store)
5491 {
5492         CamelService *service;
5493         CamelSession *session;
5494         CamelIMAPXServer *is;
5495
5496         service = CAMEL_SERVICE (store);
5497         session = camel_service_get_session (service);
5498
5499         is = g_object_new (CAMEL_TYPE_IMAPX_SERVER, NULL);
5500         is->session = g_object_ref (session);
5501         is->store = store;
5502
5503         return is;
5504 }
5505
5506 static gboolean
5507 imapx_disconnect (CamelIMAPXServer *is)
5508 {
5509         gboolean ret = TRUE;
5510
5511         g_static_rec_mutex_lock (&is->ostream_lock);
5512
5513         if (is->stream) {
5514                 if (camel_stream_close (is->stream->source, NULL, NULL) == -1)
5515                         ret = FALSE;
5516
5517                 g_object_unref (is->stream);
5518                 is->stream = NULL;
5519         }
5520
5521         /* TODO need a select lock */
5522         if (is->select_folder) {
5523                 g_object_unref (is->select_folder);
5524                 is->select_folder = NULL;
5525         }
5526
5527         if (is->select_pending) {
5528                 g_object_unref (is->select_pending);
5529                 is->select_pending = NULL;
5530         }
5531
5532         if (is->cinfo) {
5533                 imapx_free_capability (is->cinfo);
5534                 is->cinfo = NULL;
5535         }
5536
5537         is->state = IMAPX_DISCONNECTED;
5538
5539         g_static_rec_mutex_unlock (&is->ostream_lock);
5540
5541         return ret;
5542 }
5543
5544 /* Client commands */
5545 gboolean
5546 camel_imapx_server_connect (CamelIMAPXServer *is,
5547                             GCancellable *cancellable,
5548                             GError **error)
5549 {
5550         gboolean success;
5551
5552         if (is->state == IMAPX_SHUTDOWN) {
5553                 g_set_error (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE, "Shutting down");
5554                 return FALSE;
5555         }
5556
5557         if (is->state >= IMAPX_INITIALISED)
5558                 return TRUE;
5559
5560         g_static_rec_mutex_lock (&is->ostream_lock);
5561         success = imapx_reconnect (is, cancellable, error);
5562         g_static_rec_mutex_unlock (&is->ostream_lock);
5563
5564         if (!success)
5565                 return FALSE;
5566
5567         is->parser_thread = g_thread_create ((GThreadFunc) imapx_parser_thread, is, TRUE, NULL);
5568         return TRUE;
5569 }
5570
5571 static CamelStream *
5572 imapx_server_get_message (CamelIMAPXServer *is,
5573                           CamelFolder *folder,
5574                           const gchar *uid,
5575                           gint pri,
5576                           GCancellable *cancellable,
5577                           GError **error)
5578 {
5579         CamelStream *stream = NULL;
5580         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
5581         CamelIMAPXJob *job;
5582         CamelMessageInfo *mi;
5583         GetMessageData *data;
5584         gboolean registered;
5585         gboolean success;
5586
5587         QUEUE_LOCK (is);
5588
5589         if ((job = imapx_is_job_in_queue (is, folder, IMAPX_JOB_GET_MESSAGE, uid))) {
5590                 if (pri > job->pri)
5591                         job->pri = pri;
5592
5593                 /* Wait for the job to finish. This would be so much nicer if
5594                  * we could just use the queue lock with a GCond, but instead
5595                  * we have to use a GMutex. I miss the kernel waitqueues. */
5596                 do {
5597                         gint this;
5598
5599                         g_mutex_lock (is->fetch_mutex);
5600                         this = is->fetch_count;
5601
5602                         QUEUE_UNLOCK (is);
5603
5604                         while (is->fetch_count == this)
5605                                 g_cond_wait (is->fetch_cond, is->fetch_mutex);
5606
5607                         g_mutex_unlock (is->fetch_mutex);
5608
5609                         QUEUE_LOCK (is);
5610
5611                 } while (imapx_is_job_in_queue (is, folder,
5612                                                 IMAPX_JOB_GET_MESSAGE, uid));
5613
5614                 QUEUE_UNLOCK (is);
5615
5616                 stream = camel_data_cache_get (
5617                         ifolder->cache, "cur", uid, error);
5618                 if (stream == NULL)
5619                         g_prefix_error (
5620                                 error, "Could not retrieve the message: ");
5621                 return stream;
5622         }
5623
5624         mi = camel_folder_summary_get (folder->summary, uid);
5625         if (!mi) {
5626                 g_set_error (
5627                         error, CAMEL_FOLDER_ERROR,
5628                         CAMEL_FOLDER_ERROR_INVALID_UID,
5629                         _("Cannot get message with message ID %s: %s"),
5630                         uid, _("No such message available."));
5631                 QUEUE_UNLOCK (is);
5632                 return NULL;
5633         }
5634
5635         data = g_slice_new0 (GetMessageData);
5636         data->uid = g_strdup (uid);
5637         data->stream = camel_data_cache_add (ifolder->cache, "tmp", uid, NULL);
5638         data->size = ((CamelMessageInfoBase *) mi)->size;
5639         if (data->size > MULTI_SIZE)
5640                 data->use_multi_fetch = TRUE;
5641
5642         job = camel_imapx_job_new (cancellable);
5643         job->pri = pri;
5644         job->type = IMAPX_JOB_GET_MESSAGE;
5645         job->start = imapx_job_get_message_start;
5646         job->matches = imapx_job_get_message_matches;
5647         job->folder = folder;
5648
5649         camel_imapx_job_set_data (
5650                 job, data, (GDestroyNotify) get_message_data_free);
5651
5652         camel_message_info_free (mi);
5653         registered = imapx_register_job (is, job, error);
5654
5655         QUEUE_UNLOCK (is);
5656
5657         success = registered && camel_imapx_job_run (job, is, error);
5658
5659         if (success)
5660                 stream = g_object_ref (data->stream);
5661
5662         camel_imapx_job_unref (job);
5663
5664         g_mutex_lock (is->fetch_mutex);
5665         is->fetch_count++;
5666         g_cond_broadcast (is->fetch_cond);
5667         g_mutex_unlock (is->fetch_mutex);
5668
5669         return stream;
5670 }
5671
5672 CamelStream *
5673 camel_imapx_server_get_message (CamelIMAPXServer *is,
5674                                 CamelFolder *folder,
5675                                 const gchar *uid,
5676                                 GCancellable *cancellable,
5677                                 GError **error)
5678 {
5679         CamelStream *stream;
5680
5681         stream = imapx_server_get_message (
5682                 is, folder, uid,
5683                 IMAPX_PRIORITY_GET_MESSAGE,
5684                 cancellable, error);
5685
5686         return stream;
5687 }
5688
5689 gboolean
5690 camel_imapx_server_sync_message (CamelIMAPXServer *is,
5691                                  CamelFolder *folder,
5692                                  const gchar *uid,
5693                                  GCancellable *cancellable,
5694                                  GError **error)
5695 {
5696         gchar *cache_file = NULL;
5697         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
5698         CamelStream *stream;
5699         gboolean is_cached;
5700         struct stat st;
5701
5702         /* Check if the cache file already exists and is non-empty. */
5703         cache_file = camel_data_cache_get_filename (
5704                 ifolder->cache, "cur", uid, NULL);
5705         is_cached = (g_stat (cache_file, &st) == 0 && st.st_size > 0);
5706         g_free (cache_file);
5707
5708         if (is_cached)
5709                 return TRUE;
5710
5711         stream = imapx_server_get_message (
5712                 is, folder, uid,
5713                 IMAPX_PRIORITY_SYNC_MESSAGE,
5714                 cancellable, error);
5715
5716         if (stream == NULL)
5717                 return FALSE;
5718
5719         g_object_unref (stream);
5720
5721         return TRUE;
5722 }
5723
5724 gboolean
5725 camel_imapx_server_copy_message (CamelIMAPXServer *is,
5726                                  CamelFolder *source,
5727                                  CamelFolder *dest,
5728                                  GPtrArray *uids,
5729                                  gboolean delete_originals,
5730                                  GCancellable *cancellable,
5731                                  GError **error)
5732 {
5733         CamelIMAPXJob *job;
5734         CopyMessagesData *data;
5735         gint ii;
5736
5737         data = g_slice_new0 (CopyMessagesData);
5738         data->dest = g_object_ref (dest);
5739         data->uids = g_ptr_array_new ();
5740         data->delete_originals = delete_originals;
5741
5742         for (ii = 0; ii < uids->len; ii++)
5743                 g_ptr_array_add (data->uids, g_strdup (uids->pdata[ii]));
5744
5745         job = camel_imapx_job_new (cancellable);
5746         job->pri = IMAPX_PRIORITY_APPEND_MESSAGE;
5747         job->type = IMAPX_JOB_COPY_MESSAGE;
5748         job->start = imapx_job_copy_messages_start;
5749         job->folder = g_object_ref (source);
5750
5751         camel_imapx_job_set_data (
5752                 job, data, (GDestroyNotify) copy_messages_data_free);
5753
5754         return imapx_submit_job (is, job, error);
5755 }
5756
5757 gboolean
5758 camel_imapx_server_append_message (CamelIMAPXServer *is,
5759                                    CamelFolder *folder,
5760                                    CamelMimeMessage *message,
5761                                    const CamelMessageInfo *mi,
5762                                    gchar **appended_uid,
5763                                    GCancellable *cancellable,
5764                                    GError **error)
5765 {
5766         gchar *uid = NULL, *path = NULL;
5767         CamelStream *stream, *filter;
5768         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
5769         CamelMimeFilter *canon;
5770         CamelIMAPXJob *job;
5771         CamelMessageInfo *info;
5772         AppendMessageData *data;
5773         gint res;
5774         gboolean success;
5775
5776         /* Append just assumes we have no/a dodgy connection.  We dump stuff into the 'new'
5777          * directory, and let the summary know it's there.  Then we fire off a no-reply
5778          * job which will asynchronously upload the message at some point in the future,
5779          * and fix up the summary to match */
5780
5781         /* chen cleanup this later */
5782         uid = imapx_get_temp_uid ();
5783         stream = camel_data_cache_add (ifolder->cache, "new", uid, error);
5784         if (stream == NULL) {
5785                 g_prefix_error (error, _("Cannot create spool file: "));
5786                 g_free (uid);
5787                 return FALSE;
5788         }
5789
5790         filter = camel_stream_filter_new (stream);
5791         g_object_unref (stream);
5792         canon = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_CRLF);
5793         camel_stream_filter_add ((CamelStreamFilter *) filter, canon);
5794         res = camel_data_wrapper_write_to_stream_sync (
5795                 (CamelDataWrapper *) message, filter, cancellable, error);
5796         g_object_unref (canon);
5797         g_object_unref (filter);
5798
5799         if (res == -1) {
5800                 g_prefix_error (error, _("Cannot create spool file: "));
5801                 camel_data_cache_remove (ifolder->cache, "new", uid, NULL);
5802                 g_free (uid);
5803                 return FALSE;
5804         }
5805
5806         path = camel_data_cache_get_filename (ifolder->cache, "new", uid, NULL);
5807         info = camel_folder_summary_info_new_from_message ((CamelFolderSummary *) folder->summary, message, NULL);
5808         info->uid = camel_pstring_strdup (uid);
5809         if (mi)
5810                 ((CamelMessageInfoBase *) info)->flags = ((CamelMessageInfoBase *) mi)->flags;
5811         g_free (uid);
5812
5813         /* So, we actually just want to let the server loop that
5814          * messages need appending, i think.  This is so the same
5815          * mechanism is used for normal uploading as well as
5816          * offline re-syncing when we go back online */
5817
5818         data = g_slice_new0 (AppendMessageData);
5819         data->info = info;  /* takes ownership */
5820         data->path = path;  /* takes ownership */
5821         data->appended_uid = NULL;
5822
5823         job = camel_imapx_job_new (cancellable);
5824         job->pri = IMAPX_PRIORITY_APPEND_MESSAGE;
5825         job->type = IMAPX_JOB_APPEND_MESSAGE;
5826         job->start = imapx_job_append_message_start;
5827         job->folder = g_object_ref (folder);
5828         job->noreply = FALSE;
5829
5830         camel_imapx_job_set_data (
5831                 job, data, (GDestroyNotify) append_message_data_free);
5832
5833         success = imapx_submit_job (is, job, error);
5834
5835         if (appended_uid) {
5836                 *appended_uid = data->appended_uid;
5837                 data->appended_uid = NULL;
5838         }
5839
5840         camel_imapx_job_unref (job);
5841
5842         return success;
5843 }
5844
5845 gboolean
5846 camel_imapx_server_noop (CamelIMAPXServer *is,
5847                          CamelFolder *folder,
5848                          GCancellable *cancellable,
5849                          GError **error)
5850 {
5851         CamelIMAPXJob *job;
5852         gboolean success;
5853
5854         job = camel_imapx_job_new (cancellable);
5855         job->type = IMAPX_JOB_NOOP;
5856         job->start = imapx_job_noop_start;
5857         job->folder = folder;
5858         job->pri = IMAPX_PRIORITY_NOOP;
5859
5860         success = imapx_submit_job (is, job, error);
5861
5862         camel_imapx_job_unref (job);
5863
5864         return success;
5865 }
5866
5867 gboolean
5868 camel_imapx_server_refresh_info (CamelIMAPXServer *is,
5869                                  CamelFolder *folder,
5870                                  GCancellable *cancellable,
5871                                  GError **error)
5872 {
5873         CamelIMAPXJob *job;
5874         RefreshInfoData *data;
5875         gboolean registered = TRUE;
5876         const gchar *full_name;
5877         gboolean success = TRUE;
5878
5879         full_name = camel_folder_get_full_name (folder);
5880
5881         QUEUE_LOCK (is);
5882
5883         /* Both RefreshInfo and Fetch messages can't operate simultaneously */
5884         if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_REFRESH_INFO, NULL) ||
5885                 imapx_is_job_in_queue (is, folder, IMAPX_JOB_FETCH_MESSAGES, NULL)) {
5886                 QUEUE_UNLOCK (is);
5887                 return TRUE;
5888         }
5889
5890         data = g_slice_new0 (RefreshInfoData);
5891         data->changes = camel_folder_change_info_new ();
5892
5893         job = camel_imapx_job_new (cancellable);
5894         job->type = IMAPX_JOB_REFRESH_INFO;
5895         job->start = imapx_job_refresh_info_start;
5896         job->matches = imapx_job_refresh_info_matches;
5897         job->folder = folder;
5898         job->pri = IMAPX_PRIORITY_REFRESH_INFO;
5899
5900         if (g_ascii_strcasecmp(full_name, "INBOX") == 0)
5901                 job->pri += 10;
5902
5903         camel_imapx_job_set_data (
5904                 job, data, (GDestroyNotify) refresh_info_data_free);
5905
5906         registered = imapx_register_job (is, job, error);
5907
5908         QUEUE_UNLOCK (is);
5909
5910         success = registered && camel_imapx_job_run (job, is, error);
5911
5912         if (success && camel_folder_change_info_changed (data->changes))
5913                 camel_folder_changed (folder, data->changes);
5914
5915         camel_imapx_job_unref (job);
5916
5917         return success;
5918 }
5919
5920 static void
5921 imapx_sync_free_user (GArray *user_set)
5922 {
5923         gint i;
5924
5925         if (user_set == NULL)
5926                 return;
5927
5928         for (i = 0; i < user_set->len; i++) {
5929                 struct _imapx_flag_change *flag_change = &g_array_index (user_set, struct _imapx_flag_change, i);
5930                 GPtrArray *infos = flag_change->infos;
5931                 gint j;
5932
5933                 for (j = 0; j < infos->len; j++) {
5934                         CamelMessageInfo *info = g_ptr_array_index (infos, j);
5935                         camel_message_info_free (info);
5936                 }
5937
5938                 g_ptr_array_free (infos, TRUE);
5939                 g_free (flag_change->name);
5940         }
5941         g_array_free (user_set, TRUE);
5942 }
5943
5944 static gboolean
5945 imapx_server_sync_changes (CamelIMAPXServer *is,
5946                            CamelFolder *folder,
5947                            gint pri,
5948                            GCancellable *cancellable,
5949                            GError **error)
5950 {
5951         guint i, on_orset, off_orset;
5952         GPtrArray *uids;
5953         GArray *on_user = NULL, *off_user = NULL;
5954         CamelIMAPXMessageInfo *info;
5955         CamelIMAPXJob *job;
5956         SyncChangesData *data;
5957         gboolean registered;
5958         gboolean success = TRUE;
5959
5960         /* We calculate two masks, a mask of all flags which have been
5961          * turned off and a mask of all flags which have been turned
5962          * on. If either of these aren't 0, then we have work to do,
5963          * and we fire off a job to do it.
5964  *
5965          * User flags are a bit more tricky, we rely on the user
5966          * flags being sorted, and then we create a bunch of lists;
5967          * one for each flag being turned off, including each
5968          * info being turned off, and one for each flag being turned on.
5969         */
5970         uids = camel_folder_summary_get_changed (folder->summary);
5971
5972         if (uids->len == 0) {
5973                 camel_folder_free_uids (folder, uids);
5974                 return TRUE;
5975         }
5976
5977         off_orset = on_orset = 0;
5978         for (i = 0; i < uids->len; i++) {
5979                 guint32 flags, sflags;
5980                 CamelFlag *uflags, *suflags;
5981                 guint j = 0;
5982
5983                 info = (CamelIMAPXMessageInfo *) camel_folder_summary_get (folder->summary, uids->pdata[i]);
5984
5985                 if (!info)
5986                         continue;
5987
5988                 if (!(info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) {
5989                         camel_message_info_free (info);
5990                         continue;
5991                 }
5992
5993                 flags = ((CamelMessageInfoBase *) info)->flags & CAMEL_IMAPX_SERVER_FLAGS;
5994                 sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
5995                 if (flags != sflags) {
5996                         off_orset |= ( flags ^ sflags ) & ~flags;
5997                         on_orset |= (flags ^ sflags) & flags;
5998                 }
5999
6000                 uflags = ((CamelMessageInfoBase *) info)->user_flags;
6001                 suflags = info->server_user_flags;
6002                 while (uflags || suflags) {
6003                         gint res;
6004
6005                         if (uflags) {
6006                                 if (suflags)
6007                                         res = strcmp (uflags->name, suflags->name);
6008                                 else if (*uflags->name)
6009                                         res = -1;
6010                                 else {
6011                                         uflags = uflags->next;
6012                                         continue;
6013                                 }
6014                         } else {
6015                                 res = 1;
6016                         }
6017
6018                         if (res == 0) {
6019                                 uflags = uflags->next;
6020                                 suflags = suflags->next;
6021                         } else {
6022                                 GArray *user_set;
6023                                 CamelFlag *user_flag;
6024                                 struct _imapx_flag_change *change = NULL, add = { 0 };
6025
6026                                 if (res < 0) {
6027                                         if (on_user == NULL)
6028                                                 on_user = g_array_new (FALSE, FALSE, sizeof (struct _imapx_flag_change));
6029                                         user_set = on_user;
6030                                         user_flag = uflags;
6031                                         uflags = uflags->next;
6032                                 } else {
6033                                         if (off_user == NULL)
6034                                                 off_user = g_array_new (FALSE, FALSE, sizeof (struct _imapx_flag_change));
6035                                         user_set = off_user;
6036                                         user_flag = suflags;
6037                                         suflags = suflags->next;
6038                                 }
6039
6040                                 /* Could sort this and binary search */
6041                                 for (j = 0; j < user_set->len; j++) {
6042                                         change = &g_array_index (user_set, struct _imapx_flag_change, j);
6043                                         if (strcmp (change->name, user_flag->name) == 0)
6044                                                 goto found;
6045                                 }
6046                                 add.name = g_strdup (user_flag->name);
6047                                 add.infos = g_ptr_array_new ();
6048                                 g_array_append_val (user_set, add);
6049                                 change = &add;
6050                         found:
6051                                 camel_message_info_ref (info);
6052                                 g_ptr_array_add (change->infos, info);
6053                         }
6054                 }
6055                 camel_message_info_free (info);
6056         }
6057
6058         if ((on_orset | off_orset) == 0 && on_user == NULL && off_user == NULL) {
6059                 imapx_sync_free_user (on_user);
6060                 imapx_sync_free_user (off_user);
6061                 camel_folder_free_uids (folder, uids);
6062
6063                 return TRUE;
6064         }
6065
6066         /* TODO above code should go into changes_start */
6067
6068         QUEUE_LOCK (is);
6069
6070         if ((job = imapx_is_job_in_queue (is, folder, IMAPX_JOB_SYNC_CHANGES, NULL))) {
6071                 if (pri > job->pri)
6072                         job->pri = pri;
6073
6074                 QUEUE_UNLOCK (is);
6075
6076                 imapx_sync_free_user (on_user);
6077                 imapx_sync_free_user (off_user);
6078                 camel_folder_free_uids (folder, uids);
6079
6080                 return TRUE;
6081         }
6082
6083         data = g_slice_new0 (SyncChangesData);
6084         data->folder = g_object_ref (folder);
6085         data->changed_uids = uids;  /* takes ownership */
6086         data->on_set = on_orset;
6087         data->off_set = off_orset;
6088         data->on_user = on_user;  /* takes ownership */
6089         data->off_user = off_user;  /* takes ownership */
6090
6091         job = camel_imapx_job_new (cancellable);
6092         job->type = IMAPX_JOB_SYNC_CHANGES;
6093         job->start = imapx_job_sync_changes_start;
6094         job->matches = imapx_job_sync_changes_matches;
6095         job->pri = pri;
6096         job->folder = folder;
6097
6098         camel_imapx_job_set_data (
6099                 job, data, (GDestroyNotify) sync_changes_data_free);
6100
6101         registered = imapx_register_job (is, job, error);
6102
6103         QUEUE_UNLOCK (is);
6104
6105         success = registered && camel_imapx_job_run (job, is, error);
6106
6107         camel_imapx_job_unref (job);
6108
6109         return success;
6110 }
6111
6112 gboolean
6113 camel_imapx_server_sync_changes (CamelIMAPXServer *is,
6114                                  CamelFolder *folder,
6115                                  GCancellable *cancellable,
6116                                  GError **error)
6117 {
6118         return imapx_server_sync_changes (
6119                 is, folder, IMAPX_PRIORITY_SYNC_CHANGES,
6120                 cancellable, error);
6121 }
6122
6123 /* expunge-uids? */
6124 gboolean
6125 camel_imapx_server_expunge (CamelIMAPXServer *is,
6126                             CamelFolder *folder,
6127                             GCancellable *cancellable,
6128                             GError **error)
6129 {
6130         CamelIMAPXJob *job;
6131         gboolean registered;
6132         gboolean success;
6133
6134         /* Do we really care to wait for this one to finish? */
6135         QUEUE_LOCK (is);
6136
6137         if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_EXPUNGE, NULL)) {
6138                 QUEUE_UNLOCK (is);
6139                 return TRUE;
6140         }
6141
6142         job = camel_imapx_job_new (cancellable);
6143         job->type = IMAPX_JOB_EXPUNGE;
6144         job->start = imapx_job_expunge_start;
6145         job->matches = imapx_job_expunge_matches;
6146         job->pri = IMAPX_PRIORITY_EXPUNGE;
6147         job->folder = folder;
6148
6149         registered = imapx_register_job (is, job, error);
6150
6151         QUEUE_UNLOCK (is);
6152
6153         success = registered && camel_imapx_job_run (job, is, error);
6154
6155         camel_imapx_job_unref (job);
6156
6157         return success;
6158 }
6159
6160 static guint
6161 imapx_name_hash (gconstpointer key)
6162 {
6163         if (g_ascii_strcasecmp(key, "INBOX") == 0)
6164                 return g_str_hash("INBOX");
6165         else
6166                 return g_str_hash (key);
6167 }
6168
6169 static gint
6170 imapx_name_equal (gconstpointer a,
6171                   gconstpointer b)
6172 {
6173         gconstpointer aname = a, bname = b;
6174
6175         if (g_ascii_strcasecmp(a, "INBOX") == 0)
6176                 aname = "INBOX";
6177         if (g_ascii_strcasecmp(b, "INBOX") == 0)
6178                 bname = "INBOX";
6179         return g_str_equal (aname, bname);
6180 }
6181
6182 static void
6183 imapx_list_flatten (gpointer k,
6184                     gpointer v,
6185                     gpointer d)
6186 {
6187         GPtrArray *folders = d;
6188
6189         g_ptr_array_add (folders, v);
6190 }
6191
6192 static gint
6193 imapx_list_cmp (gconstpointer ap,
6194                 gconstpointer bp)
6195 {
6196         struct _list_info *a = ((struct _list_info **) ap)[0];
6197         struct _list_info *b = ((struct _list_info **) bp)[0];
6198
6199         return strcmp (a->name, b->name);
6200 }
6201
6202 GPtrArray *
6203 camel_imapx_server_list (CamelIMAPXServer *is,
6204                          const gchar *top,
6205                          guint32 flags,
6206                          const gchar *ext,
6207                          GCancellable *cancellable,
6208                          GError **error)
6209 {
6210         CamelIMAPXJob *job;
6211         GPtrArray *folders = NULL;
6212         ListData *data;
6213         gchar *encoded_name;
6214
6215         encoded_name = camel_utf8_utf7 (top);
6216
6217         data = g_slice_new0 (ListData);
6218         data->flags = flags;
6219         data->ext = g_strdup (ext);
6220         data->folders = g_hash_table_new (imapx_name_hash, imapx_name_equal);
6221
6222         if (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)
6223                 data->pattern = g_strdup_printf ("%s*", encoded_name);
6224         else
6225                 data->pattern = g_strdup (encoded_name);
6226
6227         job = camel_imapx_job_new (cancellable);
6228         job->type = IMAPX_JOB_LIST;
6229         job->start = imapx_job_list_start;
6230         job->matches = imapx_job_list_matches;
6231         job->pri = IMAPX_PRIORITY_LIST;
6232
6233         camel_imapx_job_set_data (
6234                 job, data, (GDestroyNotify) list_data_free);
6235
6236         /* sync operation which is triggered by user */
6237         if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST)
6238                 job->pri += 300;
6239
6240         if (imapx_submit_job (is, job, error)) {
6241                 folders = g_ptr_array_new ();
6242                 g_hash_table_foreach (data->folders, imapx_list_flatten, folders);
6243                 qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), imapx_list_cmp);
6244         }
6245
6246         g_free (encoded_name);
6247         camel_imapx_job_unref (job);
6248
6249         return folders;
6250 }
6251
6252 gboolean
6253 camel_imapx_server_manage_subscription (CamelIMAPXServer *is,
6254                                         const gchar *folder_name,
6255                                         gboolean subscribe,
6256                                         GCancellable *cancellable,
6257                                         GError **error)
6258 {
6259         CamelIMAPXJob *job;
6260         ManageSubscriptionsData *data;
6261         gboolean success;
6262
6263         data = g_slice_new0 (ManageSubscriptionsData);
6264         data->folder_name = g_strdup (folder_name);
6265         data->subscribe = subscribe;
6266
6267         job = camel_imapx_job_new (cancellable);
6268         job->type = IMAPX_JOB_MANAGE_SUBSCRIPTION;
6269         job->start = imapx_job_manage_subscription_start;
6270         job->pri = IMAPX_PRIORITY_MANAGE_SUBSCRIPTION;
6271
6272         camel_imapx_job_set_data (
6273                 job, data, (GDestroyNotify) manage_subscriptions_data_free);
6274
6275         success = imapx_submit_job (is, job, error);
6276
6277         camel_imapx_job_unref (job);
6278
6279         return success;
6280 }
6281
6282 gboolean
6283 camel_imapx_server_create_folder (CamelIMAPXServer *is,
6284                                   const gchar *folder_name,
6285                                   GCancellable *cancellable,
6286                                   GError **error)
6287 {
6288         CamelIMAPXJob *job;
6289         CreateFolderData *data;
6290         gboolean success;
6291
6292         data = g_slice_new0 (CreateFolderData);
6293         data->folder_name = g_strdup (folder_name);
6294
6295         job = camel_imapx_job_new (cancellable);
6296         job->type = IMAPX_JOB_CREATE_FOLDER;
6297         job->start = imapx_job_create_folder_start;
6298         job->pri = IMAPX_PRIORITY_CREATE_FOLDER;
6299
6300         camel_imapx_job_set_data (
6301                 job, data, (GDestroyNotify) create_folder_data_free);
6302
6303         success = imapx_submit_job (is, job, error);
6304
6305         camel_imapx_job_unref (job);
6306
6307         return success;
6308 }
6309
6310 gboolean
6311 camel_imapx_server_delete_folder (CamelIMAPXServer *is,
6312                                   const gchar *folder_name,
6313                                   GCancellable *cancellable,
6314                                   GError **error)
6315 {
6316         CamelIMAPXJob *job;
6317         DeleteFolderData *data;
6318         gboolean success;
6319
6320         data = g_slice_new0 (DeleteFolderData);
6321         data->folder_name = g_strdup (folder_name);
6322
6323         job = camel_imapx_job_new (cancellable);
6324         job->type = IMAPX_JOB_DELETE_FOLDER;
6325         job->start = imapx_job_delete_folder_start;
6326         job->pri = IMAPX_PRIORITY_DELETE_FOLDER;
6327
6328         camel_imapx_job_set_data (
6329                 job, data, (GDestroyNotify) delete_folder_data_free);
6330
6331         success = imapx_submit_job (is, job, error);
6332
6333         camel_imapx_job_unref (job);
6334
6335         return success;
6336 }
6337
6338 static gboolean
6339 imapx_job_fetch_messages_matches (CamelIMAPXJob *job,
6340                                       CamelFolder *folder,
6341                                       const gchar *uid)
6342 {
6343         return (folder == job->folder);
6344 }
6345
6346 gboolean
6347 camel_imapx_server_fetch_messages (CamelIMAPXServer *is,
6348                                    CamelFolder *folder,
6349                                    CamelFetchType type,
6350                                    gint limit,
6351                                    GCancellable *cancellable,
6352                                    GError **error)
6353 {
6354         CamelIMAPXJob *job;
6355         RefreshInfoData *data;
6356         gboolean registered = TRUE;
6357         const gchar *full_name;
6358         gboolean success = TRUE;
6359         guint64 firstuid, newfirstuid;
6360         gchar *uid;
6361         gint old_len;
6362
6363         old_len = camel_folder_summary_count (folder->summary);
6364         uid = imapx_get_uid_from_index (folder->summary, 0);
6365         firstuid = strtoull (uid, NULL, 10);
6366         g_free (uid);
6367
6368         QUEUE_LOCK (is);
6369
6370         /* Both RefreshInfo and Fetch messages can't operate simultaneously */
6371         if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_REFRESH_INFO, NULL) ||
6372                 imapx_is_job_in_queue (is, folder, IMAPX_JOB_FETCH_MESSAGES, NULL)) {
6373                 QUEUE_UNLOCK (is);
6374                 return TRUE;
6375         }
6376
6377         data = g_slice_new0 (RefreshInfoData);
6378         data->changes = camel_folder_change_info_new ();
6379         data->fetch_msg_limit = limit;
6380         data->fetch_type = type;
6381
6382         job = camel_imapx_job_new (cancellable);
6383         job->type = IMAPX_JOB_FETCH_MESSAGES;
6384         job->start = imapx_job_fetch_messages_start;
6385         job->matches = imapx_job_fetch_messages_matches;
6386         job->folder = folder;
6387         job->pri = IMAPX_PRIORITY_NEW_MESSAGES;
6388
6389         full_name = camel_folder_get_full_name (folder);
6390
6391         if (g_ascii_strcasecmp(full_name, "INBOX") == 0)
6392                 job->pri += 10;
6393
6394         camel_imapx_job_set_data (
6395                 job, data, (GDestroyNotify) refresh_info_data_free);
6396
6397         registered = imapx_register_job (is, job, error);
6398
6399         QUEUE_UNLOCK (is);
6400
6401         success = registered && camel_imapx_job_run (job, is, error);
6402
6403         if (success && camel_folder_change_info_changed (data->changes) && camel_folder_change_info_changed (data->changes))
6404                 camel_folder_changed (folder, data->changes);
6405
6406         uid = imapx_get_uid_from_index (folder->summary, 0);
6407         newfirstuid = strtoull (uid, NULL, 10);
6408         g_free (uid);
6409
6410         camel_imapx_job_unref (job);
6411
6412         if (type == CAMEL_FETCH_OLD_MESSAGES && firstuid == newfirstuid)
6413                 return FALSE; /* No more old messages */
6414         else if (type == CAMEL_FETCH_NEW_MESSAGES &&
6415                         old_len == camel_folder_summary_count (folder->summary))
6416                 return FALSE; /* No more new messages */
6417
6418         return TRUE;
6419 }
6420
6421 gboolean
6422 camel_imapx_server_rename_folder (CamelIMAPXServer *is,
6423                                   const gchar *old_name,
6424                                   const gchar *new_name,
6425                                   GCancellable *cancellable,
6426                                   GError **error)
6427 {
6428         CamelIMAPXJob *job;
6429         RenameFolderData *data;
6430         gboolean success;
6431
6432         data = g_slice_new0 (RenameFolderData);
6433         data->old_folder_name = g_strdup (old_name);
6434         data->new_folder_name = g_strdup (new_name);
6435
6436         job = camel_imapx_job_new (cancellable);
6437         job->type = IMAPX_JOB_RENAME_FOLDER;
6438         job->start = imapx_job_rename_folder_start;
6439         job->pri = IMAPX_PRIORITY_RENAME_FOLDER;
6440
6441         camel_imapx_job_set_data (
6442                 job, data, (GDestroyNotify) rename_folder_data_free);
6443
6444         success = imapx_submit_job (is, job, error);
6445
6446         camel_imapx_job_unref (job);
6447
6448         return success;
6449 }
6450
6451 IMAPXJobQueueInfo *
6452 camel_imapx_server_get_job_queue_info (CamelIMAPXServer *is)
6453 {
6454         IMAPXJobQueueInfo *jinfo = g_new0 (IMAPXJobQueueInfo, 1);
6455         CamelIMAPXJob *job = NULL;
6456         GList *head, *link;
6457
6458         QUEUE_LOCK (is);
6459
6460         jinfo->queue_len = g_queue_get_length (&is->jobs);
6461         jinfo->folders = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
6462
6463         head = g_queue_peek_head_link (&is->jobs);
6464
6465         for (link = head; link != NULL; link = g_list_next (link)) {
6466                 job = (CamelIMAPXJob *) link->data;
6467
6468                 if (job->folder) {
6469                         const gchar *full_name = camel_folder_get_full_name (job->folder);
6470                         g_hash_table_insert (jinfo->folders, g_strdup (full_name), GINT_TO_POINTER (1));
6471                 }
6472         }
6473
6474         if (is->select_folder)
6475                 g_hash_table_insert (jinfo->folders, g_strdup (camel_folder_get_full_name (is->select_folder)), GINT_TO_POINTER (1));
6476
6477         QUEUE_UNLOCK (is);
6478
6479         return jinfo;
6480 }