[IMAPX] Fix a memory leak around imapx_untagged_vanished()
[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 CAMEL_IMAPX_SERVER_GET_PRIVATE(obj) \
54         (G_TYPE_INSTANCE_GET_PRIVATE \
55         ((obj), CAMEL_TYPE_IMAPX_SERVER, CamelIMAPXServerPrivate))
56
57 #define c(...) camel_imapx_debug(command, __VA_ARGS__)
58 #define e(...) camel_imapx_debug(extra, __VA_ARGS__)
59
60 #define CIF(x) ((CamelIMAPXFolder *)x)
61
62 #define QUEUE_LOCK(x) (g_rec_mutex_lock(&(x)->queue_lock))
63 #define QUEUE_UNLOCK(x) (g_rec_mutex_unlock(&(x)->queue_lock))
64
65 #define IDLE_LOCK(x) (g_mutex_lock(&(x)->idle_lock))
66 #define IDLE_UNLOCK(x) (g_mutex_unlock(&(x)->idle_lock))
67
68 /* Try pipelining fetch requests, 'in bits' */
69 #define MULTI_SIZE (20480)
70
71 /* How many outstanding commands do we allow before we just queue them? */
72 #define MAX_COMMANDS (10)
73
74 #define MAX_COMMAND_LEN 1000
75
76 extern gint camel_application_is_exiting;
77
78 /* Job-specific structs */
79 typedef struct _GetMessageData GetMessageData;
80 typedef struct _RefreshInfoData RefreshInfoData;
81 typedef struct _SyncChangesData SyncChangesData;
82 typedef struct _AppendMessageData AppendMessageData;
83 typedef struct _CopyMessagesData CopyMessagesData;
84 typedef struct _ListData ListData;
85 typedef struct _ManageSubscriptionsData ManageSubscriptionsData;
86 typedef struct _RenameFolderData RenameFolderData;
87 typedef struct _CreateFolderData CreateFolderData;
88 typedef struct _DeleteFolderData DeleteFolderData;
89 typedef struct _QuotaData QuotaData;
90 typedef struct _SearchData SearchData;
91
92 struct _GetMessageData {
93         /* in: uid requested */
94         gchar *uid;
95         /* in/out: message content stream output */
96         CamelStream *stream;
97         /* working variables */
98         gsize body_offset;
99         gssize body_len;
100         gsize fetch_offset;
101         gsize size;
102         gboolean use_multi_fetch;
103 };
104
105 struct _RefreshInfoData {
106         /* array of refresh info's */
107         GArray *infos;
108         /* used for building uidset stuff */
109         gint index;
110         gint last_index;
111         gint fetch_msg_limit;
112         CamelFetchType fetch_type;
113         gboolean update_unseen;
114         gboolean scan_changes;
115         struct _uidset_state uidset;
116         /* changes during refresh */
117         CamelFolderChangeInfo *changes;
118 };
119
120 struct _SyncChangesData {
121         CamelFolder *folder;
122         GPtrArray *changed_uids;
123         guint32 on_set;
124         guint32 off_set;
125         GArray *on_user; /* imapx_flag_change */
126         GArray *off_user;
127         gint unread_change;
128
129         /* Remove recently set DELETED flags before synchronizing.
130          * This is only set when using a real Trash folder and NOT
131          * about to expunge the folder. */
132         gboolean remove_deleted_flags;
133 };
134
135 struct _AppendMessageData {
136         gchar *path;
137         CamelMessageInfo *info;
138         gchar *appended_uid;
139 };
140
141 struct _CopyMessagesData {
142         CamelFolder *dest;
143         GPtrArray *uids;
144         gboolean delete_originals;
145         gint index;
146         gint last_index;
147         struct _uidset_state uidset;
148 };
149
150 struct _ListData {
151         gchar *pattern;
152         guint32 flags;
153         gchar *ext;
154         GHashTable *folders;
155 };
156
157 struct _ManageSubscriptionsData {
158         gchar *folder_name;
159         gboolean subscribe;
160 };
161
162 struct _RenameFolderData {
163         gchar *old_folder_name;
164         gchar *new_folder_name;
165 };
166
167 struct _CreateFolderData {
168         gchar *folder_name;
169 };
170
171 struct _DeleteFolderData {
172         gchar *folder_name;
173 };
174
175 struct _SearchData {
176         gchar *criteria;
177         GArray *results;
178 };
179
180 struct _QuotaData {
181         gchar *folder_name;
182 };
183
184 /* untagged response handling */
185
186 /* May need to turn this into separate,
187  * subclassable GObject with proper getter/setter
188  * functions so derived implementations can
189  * supply their own context information.
190  * The context supplied here, however, should
191  * not be exposed outside CamelIMAPXServer.
192  * An instance is created in imapx_untagged()
193  * with a lifetime of one run of this function.
194  * In order to supply a derived context instance,
195  * we would need to register a derived _new()
196  * function for it which will be called inside
197  * imapx_untagged().
198  *
199  * TODO: rethink this construct.
200  */
201 typedef struct _CamelIMAPXServerUntaggedContext CamelIMAPXServerUntaggedContext;
202
203 struct _CamelIMAPXServerUntaggedContext {
204         CamelSortType fetch_order;
205         guint id;
206         guint len;
207         guchar *token;
208         gint tok;
209         gboolean lsub;
210         struct _status_info *sinfo;
211 };
212
213 /* internal untagged handler prototypes */
214 static gboolean imapx_untagged_bye              (CamelIMAPXServer *is,
215                                                  CamelIMAPXStream *stream,
216                                                  GCancellable *cancellable,
217                                                  GError **error);
218 static gboolean imapx_untagged_capability       (CamelIMAPXServer *is,
219                                                  CamelIMAPXStream *stream,
220                                                  GCancellable *cancellable,
221                                                  GError **error);
222 static gboolean imapx_untagged_exists           (CamelIMAPXServer *is,
223                                                  CamelIMAPXStream *stream,
224                                                  GCancellable *cancellable,
225                                                  GError **error);
226 static gboolean imapx_untagged_expunge          (CamelIMAPXServer *is,
227                                                  CamelIMAPXStream *stream,
228                                                  GCancellable *cancellable,
229                                                  GError **error);
230 static gboolean imapx_untagged_fetch            (CamelIMAPXServer *is,
231                                                  CamelIMAPXStream *stream,
232                                                  GCancellable *cancellable,
233                                                  GError **error);
234 static gboolean imapx_untagged_flags            (CamelIMAPXServer *is,
235                                                  CamelIMAPXStream *stream,
236                                                  GCancellable *cancellable,
237                                                  GError **error);
238 static gboolean imapx_untagged_list             (CamelIMAPXServer *is,
239                                                  CamelIMAPXStream *stream,
240                                                  GCancellable *cancellable,
241                                                  GError **error);
242 static gboolean imapx_untagged_lsub             (CamelIMAPXServer *is,
243                                                  CamelIMAPXStream *stream,
244                                                  GCancellable *cancellable,
245                                                  GError **error);
246 static gboolean imapx_untagged_namespace        (CamelIMAPXServer *is,
247                                                  CamelIMAPXStream *stream,
248                                                  GCancellable *cancellable,
249                                                  GError **error);
250 static gboolean imapx_untagged_ok_no_bad        (CamelIMAPXServer *is,
251                                                  CamelIMAPXStream *stream,
252                                                  GCancellable *cancellable,
253                                                  GError **error);
254 static gboolean imapx_untagged_preauth          (CamelIMAPXServer *is,
255                                                  CamelIMAPXStream *stream,
256                                                  GCancellable *cancellable,
257                                                  GError **error);
258 static gboolean imapx_untagged_quota            (CamelIMAPXServer *is,
259                                                  CamelIMAPXStream *stream,
260                                                  GCancellable *cancellable,
261                                                  GError **error);
262 static gboolean imapx_untagged_quotaroot        (CamelIMAPXServer *is,
263                                                  CamelIMAPXStream *stream,
264                                                  GCancellable *cancellable,
265                                                  GError **error);
266 static gboolean imapx_untagged_recent           (CamelIMAPXServer *is,
267                                                  CamelIMAPXStream *stream,
268                                                  GCancellable *cancellable,
269                                                  GError **error);
270 static gboolean imapx_untagged_search           (CamelIMAPXServer *is,
271                                                  CamelIMAPXStream *stream,
272                                                  GCancellable *cancellable,
273                                                  GError **error);
274 static gboolean imapx_untagged_status           (CamelIMAPXServer *is,
275                                                  CamelIMAPXStream *stream,
276                                                  GCancellable *cancellable,
277                                                  GError **error);
278 static gboolean imapx_untagged_vanished         (CamelIMAPXServer *is,
279                                                  CamelIMAPXStream *stream,
280                                                  GCancellable *cancellable,
281                                                  GError **error);
282
283 enum {
284         IMAPX_UNTAGGED_ID_BAD = 0,
285         IMAPX_UNTAGGED_ID_BYE,
286         IMAPX_UNTAGGED_ID_CAPABILITY,
287         IMAPX_UNTAGGED_ID_EXISTS,
288         IMAPX_UNTAGGED_ID_EXPUNGE,
289         IMAPX_UNTAGGED_ID_FETCH,
290         IMAPX_UNTAGGED_ID_FLAGS,
291         IMAPX_UNTAGGED_ID_LIST,
292         IMAPX_UNTAGGED_ID_LSUB,
293         IMAPX_UNTAGGED_ID_NAMESPACE,
294         IMAPX_UNTAGGED_ID_NO,
295         IMAPX_UNTAGGED_ID_OK,
296         IMAPX_UNTAGGED_ID_PREAUTH,
297         IMAPX_UNTAGGED_ID_QUOTA,
298         IMAPX_UNTAGGED_ID_QUOTAROOT,
299         IMAPX_UNTAGGED_ID_RECENT,
300         IMAPX_UNTAGGED_ID_SEARCH,
301         IMAPX_UNTAGGED_ID_STATUS,
302         IMAPX_UNTAGGED_ID_VANISHED,
303         IMAPX_UNTAGGED_LAST_ID
304 };
305
306 static const CamelIMAPXUntaggedRespHandlerDesc _untagged_descr[] = {
307         {CAMEL_IMAPX_UNTAGGED_BAD, imapx_untagged_ok_no_bad, NULL, FALSE},
308         {CAMEL_IMAPX_UNTAGGED_BYE, imapx_untagged_bye, NULL, FALSE},
309         {CAMEL_IMAPX_UNTAGGED_CAPABILITY, imapx_untagged_capability, NULL, FALSE},
310         {CAMEL_IMAPX_UNTAGGED_EXISTS, imapx_untagged_exists, NULL, TRUE},
311         {CAMEL_IMAPX_UNTAGGED_EXPUNGE, imapx_untagged_expunge, NULL, TRUE},
312         {CAMEL_IMAPX_UNTAGGED_FETCH, imapx_untagged_fetch, NULL, TRUE},
313         {CAMEL_IMAPX_UNTAGGED_FLAGS, imapx_untagged_flags, NULL, TRUE},
314         {CAMEL_IMAPX_UNTAGGED_LIST, imapx_untagged_list, NULL, TRUE},
315         {CAMEL_IMAPX_UNTAGGED_LSUB, imapx_untagged_lsub, CAMEL_IMAPX_UNTAGGED_LIST, TRUE /*overridden */ },
316         {CAMEL_IMAPX_UNTAGGED_NAMESPACE, imapx_untagged_namespace, NULL, FALSE},
317         {CAMEL_IMAPX_UNTAGGED_NO, imapx_untagged_ok_no_bad, NULL, FALSE},
318         {CAMEL_IMAPX_UNTAGGED_OK, imapx_untagged_ok_no_bad, NULL, FALSE},
319         {CAMEL_IMAPX_UNTAGGED_PREAUTH, imapx_untagged_preauth, CAMEL_IMAPX_UNTAGGED_OK, TRUE /*overridden */ },
320         {CAMEL_IMAPX_UNTAGGED_QUOTA, imapx_untagged_quota, NULL, FALSE},
321         {CAMEL_IMAPX_UNTAGGED_QUOTAROOT, imapx_untagged_quotaroot, NULL, FALSE},
322         {CAMEL_IMAPX_UNTAGGED_RECENT, imapx_untagged_recent, NULL, TRUE},
323         {CAMEL_IMAPX_UNTAGGED_SEARCH, imapx_untagged_search, NULL, FALSE},
324         {CAMEL_IMAPX_UNTAGGED_STATUS, imapx_untagged_status, NULL, TRUE},
325         {CAMEL_IMAPX_UNTAGGED_VANISHED, imapx_untagged_vanished, NULL, TRUE},
326 };
327
328 struct _CamelIMAPXServerPrivate {
329         GWeakRef store;
330
331         CamelIMAPXServerUntaggedContext *context;
332         GHashTable *untagged_handlers;
333
334         CamelIMAPXStream *stream;
335         GMutex stream_lock;
336
337         /* Untagged SEARCH data gets deposited here.
338          * The search command should claim the results
339          * when finished and reset the pointer to NULL. */
340         GArray *search_results;
341         GMutex search_results_lock;
342 };
343
344 enum {
345         PROP_0,
346         PROP_STREAM,
347         PROP_STORE
348 };
349
350 enum {
351         SELECT_CHANGED,
352         SHUTDOWN,
353         LAST_SIGNAL
354 };
355
356 static guint signals[LAST_SIGNAL];
357
358 static void     imapx_uidset_init               (struct _uidset_state *ss,
359                                                  gint total,
360                                                  gint limit);
361 static gint     imapx_uidset_done               (struct _uidset_state *ss,
362                                                  CamelIMAPXCommand *ic);
363 static gint     imapx_uidset_add                (struct _uidset_state *ss,
364                                                  CamelIMAPXCommand *ic,
365                                                  const gchar *uid);
366
367 static gboolean imapx_command_idle_stop         (CamelIMAPXServer *is,
368                                                  CamelIMAPXStream *stream,
369                                                  GCancellable *cancellable,
370                                                  GError **error);
371 static gboolean imapx_continuation              (CamelIMAPXServer *is,
372                                                  CamelIMAPXStream *stream,
373                                                  gboolean litplus,
374                                                  GCancellable *cancellable,
375                                                  GError **error);
376 static gboolean imapx_disconnect                (CamelIMAPXServer *is);
377 static gboolean imapx_is_command_queue_empty    (CamelIMAPXServer *is);
378 static gint     imapx_uid_cmp                   (gconstpointer ap,
379                                                  gconstpointer bp,
380                                                  gpointer data);
381
382 /* states for the connection? */
383 enum {
384         IMAPX_DISCONNECTED,
385         IMAPX_SHUTDOWN,
386         IMAPX_CONNECTED,
387         IMAPX_AUTHENTICATED,
388         IMAPX_INITIALISED,
389         IMAPX_SELECTED
390 };
391
392 struct _refresh_info {
393         gchar *uid;
394         gboolean exists;
395         guint32 server_flags;
396         CamelFlag *server_user_flags;
397 };
398
399 enum {
400         IMAPX_JOB_GET_MESSAGE = 1 << 0,
401         IMAPX_JOB_APPEND_MESSAGE = 1 << 1,
402         IMAPX_JOB_COPY_MESSAGE = 1 << 2,
403         IMAPX_JOB_FETCH_NEW_MESSAGES = 1 << 3,
404         IMAPX_JOB_REFRESH_INFO = 1 << 4,
405         IMAPX_JOB_SYNC_CHANGES = 1 << 5,
406         IMAPX_JOB_EXPUNGE = 1 << 6,
407         IMAPX_JOB_NOOP = 1 << 7,
408         IMAPX_JOB_IDLE = 1 << 8,
409         IMAPX_JOB_LIST = 1 << 9,
410         IMAPX_JOB_MANAGE_SUBSCRIPTION = 1 << 10,
411         IMAPX_JOB_CREATE_FOLDER = 1 << 11,
412         IMAPX_JOB_DELETE_FOLDER = 1 << 12,
413         IMAPX_JOB_RENAME_FOLDER = 1 << 13,
414         IMAPX_JOB_FETCH_MESSAGES = 1 << 14,
415         IMAPX_JOB_UPDATE_QUOTA_INFO = 1 << 15,
416         IMAPX_JOB_UID_SEARCH = 1 << 16
417 };
418
419 /* Operations on the store (folder_tree) will have highest priority as we know for sure they are sync
420  * and user triggered. */
421 enum {
422         IMAPX_PRIORITY_CREATE_FOLDER = 200,
423         IMAPX_PRIORITY_DELETE_FOLDER = 200,
424         IMAPX_PRIORITY_RENAME_FOLDER = 200,
425         IMAPX_PRIORITY_MANAGE_SUBSCRIPTION = 200,
426         IMAPX_PRIORITY_SYNC_CHANGES = 150,
427         IMAPX_PRIORITY_EXPUNGE = 150,
428         IMAPX_PRIORITY_SEARCH = 150,
429         IMAPX_PRIORITY_GET_MESSAGE = 100,
430         IMAPX_PRIORITY_REFRESH_INFO = 0,
431         IMAPX_PRIORITY_NOOP = 0,
432         IMAPX_PRIORITY_NEW_MESSAGES = 0,
433         IMAPX_PRIORITY_APPEND_MESSAGE = -60,
434         IMAPX_PRIIORITY_COPY_MESSAGE = -60,
435         IMAPX_PRIORITY_LIST = -80,
436         IMAPX_PRIORITY_IDLE = -100,
437         IMAPX_PRIORITY_SYNC_MESSAGE = -120,
438         IMAPX_PRIORITY_UPDATE_QUOTA_INFO = -80
439 };
440
441 struct _imapx_flag_change {
442         GPtrArray *infos;
443         gchar *name;
444 };
445
446 static CamelIMAPXJob *
447                 imapx_match_active_job          (CamelIMAPXServer *is,
448                                                  guint32 type,
449                                                  const gchar *uid);
450 static gboolean imapx_job_fetch_new_messages_start
451                                                 (CamelIMAPXJob *job,
452                                                  CamelIMAPXServer *is,
453                                                  GCancellable *cancellable,
454                                                  GError **error);
455 static gint     imapx_refresh_info_uid_cmp      (gconstpointer ap,
456                                                  gconstpointer bp,
457                                                  gboolean ascending);
458 static gint     imapx_uids_array_cmp            (gconstpointer ap,
459                                                  gconstpointer bp);
460 static gboolean imapx_server_sync_changes       (CamelIMAPXServer *is,
461                                                  CamelFolder *folder,
462                                                  guint32 job_type,
463                                                  gint pri,
464                                                  GCancellable *cancellable,
465                                                  GError **error);
466 static void     imapx_sync_free_user            (GArray *user_set);
467
468 static gboolean imapx_command_copy_messages_step_start
469                                                 (CamelIMAPXServer *is,
470                                                  CamelIMAPXJob *job,
471                                                  gint index,
472                                                  GCancellable *cancellable,
473                                                  GError **error);
474
475 enum _idle_state {
476         IMAPX_IDLE_OFF,
477         IMAPX_IDLE_PENDING,     /* Queue is idle; waiting to send IDLE command
478                                    soon if nothing more interesting happens */
479         IMAPX_IDLE_ISSUED,      /* Sent IDLE command; waiting for response */
480         IMAPX_IDLE_STARTED,     /* IDLE continuation received; IDLE active */
481         IMAPX_IDLE_CANCEL,      /* Cancelled from ISSUED state; need to send
482                                    DONE as soon as we receive continuation */
483 };
484 #define IMAPX_IDLE_DWELL_TIME   2 /* Number of seconds to remain in PENDING
485                                      state waiting for other commands to be
486                                      queued, before actually sending IDLE */
487
488 struct _CamelIMAPXIdle {
489         GMutex idle_lock;
490         GThread *idle_thread;
491
492         GCond start_watch_cond;
493         GMutex start_watch_mutex;
494         gboolean start_watch_is_set;
495
496         time_t started;
497         enum _idle_state state;
498         gboolean idle_exit;
499 };
500
501 typedef enum {
502         IMAPX_IDLE_STOP_NOOP,
503         IMAPX_IDLE_STOP_SUCCESS,
504         IMAPX_IDLE_STOP_ERROR
505 } CamelIMAPXIdleStopResult;
506
507 static gboolean imapx_in_idle                   (CamelIMAPXServer *is);
508 static gboolean imapx_idle_supported            (CamelIMAPXServer *is);
509 static void     imapx_start_idle                (CamelIMAPXServer *is);
510 static void     imapx_exit_idle                 (CamelIMAPXServer *is);
511 static void     imapx_init_idle                 (CamelIMAPXServer *is);
512 static CamelIMAPXIdleStopResult
513                 imapx_stop_idle                 (CamelIMAPXServer *is,
514                                                  CamelIMAPXStream *stream,
515                                                  GCancellable *cancellable,
516                                                  GError **error);
517 static gboolean camel_imapx_server_idle         (CamelIMAPXServer *is,
518                                                  CamelFolder *folder,
519                                                  GCancellable *cancellable,
520                                                  GError **error);
521
522 enum {
523         USE_SSL_NEVER,
524         USE_SSL_ALWAYS,
525         USE_SSL_WHEN_POSSIBLE
526 };
527
528 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
529 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
530
531 static gboolean imapx_select                    (CamelIMAPXServer *is,
532                                                  CamelFolder *folder,
533                                                  gboolean force,
534                                                  GCancellable *cancellable,
535                                                  GError **error);
536
537 G_DEFINE_TYPE (CamelIMAPXServer, camel_imapx_server, CAMEL_TYPE_OBJECT)
538
539 static const CamelIMAPXUntaggedRespHandlerDesc *
540 replace_untagged_descriptor (GHashTable *untagged_handlers,
541                              const gchar *key,
542                              const CamelIMAPXUntaggedRespHandlerDesc *descr)
543 {
544         const CamelIMAPXUntaggedRespHandlerDesc *prev = NULL;
545
546         g_return_val_if_fail (untagged_handlers != NULL, NULL);
547         g_return_val_if_fail (key != NULL, NULL);
548         /* descr may be NULL (to delete a handler) */
549
550         prev = g_hash_table_lookup (untagged_handlers, key);
551         g_hash_table_replace (
552                 untagged_handlers,
553                 g_strdup (key),
554                 (gpointer) descr);
555         return prev;
556 }
557
558 static void
559 add_initial_untagged_descriptor (GHashTable *untagged_handlers,
560                                  guint untagged_id)
561 {
562         const CamelIMAPXUntaggedRespHandlerDesc *prev = NULL;
563         const CamelIMAPXUntaggedRespHandlerDesc *cur  = NULL;
564
565         g_return_if_fail (untagged_handlers != NULL);
566         g_return_if_fail (untagged_id < IMAPX_UNTAGGED_LAST_ID);
567
568         cur =  &(_untagged_descr[untagged_id]);
569         prev = replace_untagged_descriptor (
570                 untagged_handlers,
571                 cur->untagged_response,
572                 cur);
573         /* there must not be any previous handler here */
574         g_return_if_fail (prev == NULL);
575 }
576
577 static GHashTable *
578 create_initial_untagged_handler_table (void)
579 {
580         GHashTable *uh = g_hash_table_new_full (
581                 g_str_hash,
582                 g_str_equal,
583                 g_free,
584                 NULL);
585         guint32 ii = 0;
586
587         /* CamelIMAPXServer predefined handlers*/
588         for (ii = 0; ii < IMAPX_UNTAGGED_LAST_ID; ii++)
589                 add_initial_untagged_descriptor (uh, ii);
590
591         g_return_val_if_fail (g_hash_table_size (uh) == IMAPX_UNTAGGED_LAST_ID, NULL);
592
593         return uh;
594 }
595
596 static void
597 get_message_data_free (GetMessageData *data)
598 {
599         g_free (data->uid);
600
601         if (data->stream != NULL)
602                 g_object_unref (data->stream);
603
604         g_slice_free (GetMessageData, data);
605 }
606
607 static void
608 refresh_info_data_infos_free (RefreshInfoData *data)
609 {
610         gint ii;
611
612         if (!data || !data->infos)
613                 return;
614
615         for (ii = 0; ii < data->infos->len; ii++) {
616                 struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, ii);
617
618                 camel_flag_list_free (&r->server_user_flags);
619                 g_free (r->uid);
620         }
621
622         g_array_free (data->infos, TRUE);
623         data->infos = NULL;
624 }
625
626 static void
627 refresh_info_data_free (RefreshInfoData *data)
628 {
629         camel_folder_change_info_free (data->changes);
630         refresh_info_data_infos_free (data);
631
632         g_slice_free (RefreshInfoData, data);
633 }
634
635 static void
636 sync_changes_data_free (SyncChangesData *data)
637 {
638         if (data->folder != NULL) {
639                 camel_folder_free_uids (data->folder, data->changed_uids);
640                 g_object_unref (data->folder);
641         }
642
643         imapx_sync_free_user (data->on_user);
644         imapx_sync_free_user (data->off_user);
645
646         g_slice_free (SyncChangesData, data);
647 }
648
649 static void
650 append_message_data_free (AppendMessageData *data)
651 {
652         g_free (data->path);
653         g_free (data->appended_uid);
654
655         camel_message_info_free (data->info);
656
657         g_slice_free (AppendMessageData, data);
658 }
659
660 static void
661 copy_messages_data_free (CopyMessagesData *data)
662 {
663         if (data->dest != NULL)
664                 g_object_unref (data->dest);
665
666         if (data->uids != NULL) {
667                 g_ptr_array_foreach (data->uids, (GFunc) g_free, NULL);
668                 g_ptr_array_free (data->uids, TRUE);
669         }
670
671         g_slice_free (CopyMessagesData, data);
672 }
673
674 static void
675 list_data_free (ListData *data)
676 {
677         g_free (data->pattern);
678         g_free (data->ext);
679
680         g_hash_table_destroy (data->folders);
681
682         g_slice_free (ListData, data);
683 }
684
685 static void
686 manage_subscriptions_data_free (ManageSubscriptionsData *data)
687 {
688         g_free (data->folder_name);
689
690         g_slice_free (ManageSubscriptionsData, data);
691 }
692
693 static void
694 rename_folder_data_free (RenameFolderData *data)
695 {
696         g_free (data->old_folder_name);
697         g_free (data->new_folder_name);
698
699         g_slice_free (RenameFolderData, data);
700 }
701
702 static void
703 create_folder_data_free (CreateFolderData *data)
704 {
705         g_free (data->folder_name);
706
707         g_slice_free (CreateFolderData, data);
708 }
709
710 static void
711 delete_folder_data_free (DeleteFolderData *data)
712 {
713         g_free (data->folder_name);
714
715         g_slice_free (DeleteFolderData, data);
716 }
717
718 static void
719 search_data_free (SearchData *data)
720 {
721         g_free (data->criteria);
722
723         if (data->results != NULL)
724                 g_array_unref (data->results);
725
726         g_slice_free (SearchData, data);
727 }
728
729 static void
730 quota_data_free (QuotaData *data)
731 {
732         g_free (data->folder_name);
733
734         g_slice_free (QuotaData, data);
735 }
736
737 /*
738   this creates a uid (or sequence number) set directly into a command,
739   if total is set, then we break it up into total uids. (i.e. command time)
740   if limit is set, then we break it up into limit entries (i.e. command length)
741 */
742 void
743 imapx_uidset_init (struct _uidset_state *ss,
744                    gint total,
745                    gint limit)
746 {
747         ss->uids = 0;
748         ss->entries = 0;
749         ss->start = 0;
750         ss->last = 0;
751         ss->total = total;
752         ss->limit = limit;
753 }
754
755 gboolean
756 imapx_uidset_done (struct _uidset_state *ss,
757                    CamelIMAPXCommand *ic)
758 {
759         gint ret = FALSE;
760
761         if (ss->last != 0 && ss->last != ss->start) {
762                 camel_imapx_command_add (ic, ":%d", ss->last);
763         }
764
765         ret = ss->last != 0;
766
767         ss->start = 0;
768         ss->last = 0;
769         ss->uids = 0;
770         ss->entries = 0;
771
772         return ret;
773 }
774
775 gint
776 imapx_uidset_add (struct _uidset_state *ss,
777                   CamelIMAPXCommand *ic,
778                   const gchar *uid)
779 {
780         guint32 uidn;
781
782         uidn = strtoul (uid, NULL, 10);
783         if (uidn == 0)
784                 return -1;
785
786         ss->uids++;
787
788         e (ic->is->tagprefix, "uidset add '%s'\n", uid);
789
790         if (ss->last == 0) {
791                 e (ic->is->tagprefix, " start\n");
792                 camel_imapx_command_add (ic, "%d", uidn);
793                 ss->entries++;
794                 ss->start = uidn;
795         } else {
796                 if (ss->last != uidn - 1) {
797                         if (ss->last == ss->start) {
798                                 e (ic->is->tagprefix, " ,next\n");
799                                 camel_imapx_command_add (ic, ",%d", uidn);
800                                 ss->entries++;
801                         } else {
802                                 e (ic->is->tagprefix, " :range\n");
803                                 camel_imapx_command_add (ic, ":%d,%d", ss->last, uidn);
804                                 ss->entries+=2;
805                         }
806                         ss->start = uidn;
807                 }
808         }
809
810         ss->last = uidn;
811
812         if ((ss->limit && ss->entries >= ss->limit)
813             || (ss->total && ss->uids >= ss->total)) {
814                 e (ic->is->tagprefix, " done, %d entries, %d uids\n", ss->entries, ss->uids);
815                 if (!imapx_uidset_done (ss, ic))
816                         return -1;
817                 return 1;
818         }
819
820         return 0;
821 }
822
823 /* Must hold QUEUE_LOCK */
824 static gboolean
825 imapx_command_start (CamelIMAPXServer *is,
826                      CamelIMAPXCommand *ic,
827                      GCancellable *cancellable,
828                      GError **error)
829 {
830         CamelIMAPXStream *stream = NULL;
831         CamelIMAPXCommandPart *cp;
832         gboolean cp_continuation;
833         gboolean cp_literal_plus;
834         GList *head;
835         gboolean success = FALSE;
836         gchar *string;
837         gint retval;
838
839         camel_imapx_command_close (ic);
840
841         head = g_queue_peek_head_link (&ic->parts);
842         g_return_val_if_fail (head != NULL, FALSE);
843         cp = (CamelIMAPXCommandPart *) head->data;
844         ic->current_part = head;
845
846         cp_continuation = ((cp->type & CAMEL_IMAPX_COMMAND_CONTINUATION) != 0);
847         cp_literal_plus = ((cp->type & CAMEL_IMAPX_COMMAND_LITERAL_PLUS) != 0);
848
849         /* TODO: If we support literal+ we should be able to write the whole command out
850          * at this point .... >here< */
851
852         if (cp_continuation || cp_literal_plus)
853                 is->literal = ic;
854
855         camel_imapx_command_queue_push_tail (is->active, ic);
856
857         stream = camel_imapx_server_ref_stream (is);
858
859         if (stream == NULL) {
860                 g_set_error (
861                         error, CAMEL_IMAPX_ERROR, 1,
862                         "Cannot issue command, no stream available");
863                 goto err;
864         }
865
866         c (
867                 is->tagprefix,
868                 "Starting command (active=%d,%s) %c%05u %s\r\n",
869                 camel_imapx_command_queue_get_length (is->active),
870                 is->literal ? " literal" : "",
871                 is->tagprefix,
872                 ic->tag,
873                 cp->data && g_str_has_prefix (cp->data, "LOGIN") ?
874                         "LOGIN..." : cp->data);
875
876         string = g_strdup_printf (
877                 "%c%05u %s\r\n", is->tagprefix, ic->tag, cp->data);
878         retval = camel_stream_write_string (
879                 CAMEL_STREAM (stream), string, cancellable, error);
880         g_free (string);
881
882         if (retval == -1)
883                 goto err;
884
885         while (is->literal == ic && cp_literal_plus) {
886                 /* Sent LITERAL+ continuation immediately */
887                 if (!imapx_continuation (is, stream, TRUE, cancellable, error))
888                         goto err;
889         }
890
891         success = TRUE;
892
893         goto exit;
894
895 err:
896         camel_imapx_command_queue_remove (is->active, ic);
897
898         /* HACK: Since we're failing, make sure the command has a status
899          *       structure and the result code indicates failure, so the
900          *       ic->complete() callback does not start a new command. */
901         if (ic->status == NULL)
902                 ic->status = g_malloc0 (sizeof (struct _status_info));
903         if (ic->status->result == IMAPX_OK)
904                 ic->status->result = IMAPX_UNKNOWN;
905
906         /* Send a NULL GError since we've already set a
907          * GError to get here, and we're not interested
908          * in individual command errors. */
909         if (ic != NULL && ic->complete != NULL)
910                 ic->complete (is, ic, NULL, NULL);
911
912 exit:
913         if (stream != NULL)
914                 g_object_unref (stream);
915
916         return success;
917 }
918
919 static gboolean
920 duplicate_fetch_or_refresh (CamelIMAPXServer *is,
921                             CamelIMAPXCommand *ic)
922 {
923         CamelIMAPXJob *job;
924
925         job = camel_imapx_command_get_job (ic);
926
927         if (job == NULL)
928                 return FALSE;
929
930         if (!(job->type & (IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES)))
931                 return FALSE;
932
933         if (imapx_match_active_job (is, IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES, NULL)) {
934                 c (is->tagprefix, "Not yet sending duplicate fetch/refresh %s command\n", ic->name);
935                 return TRUE;
936         }
937
938         return FALSE;
939 }
940
941 /* See if we can start another task yet.
942  *
943  * If we're waiting for a literal, we cannot proceed.
944  *
945  * If we're about to change the folder we're
946  * looking at from user-direction, we dont proceed.
947  *
948  * If we have a folder selected, first see if any
949  * jobs are waiting on it, but only if they are
950  * at least as high priority as anything we
951  * have running.
952  *
953  * If we dont, select the first folder required,
954  * then queue all the outstanding jobs on it, that
955  * are at least as high priority as the first.
956  *
957  * must have QUEUE lock */
958
959 static gboolean
960 imapx_command_start_next (CamelIMAPXServer *is,
961                           GCancellable *cancellable,
962                           GError **error)
963 {
964         CamelIMAPXCommand *first_ic;
965         CamelFolder *folder;
966         gint min_pri = -128;
967         gboolean success = TRUE;
968
969         c (is->tagprefix, "** Starting next command\n");
970         if (is->literal) {
971                 c (is->tagprefix, "* no; waiting for literal '%s'\n", is->literal->name);
972                 return success;
973         }
974
975         folder = g_weak_ref_get (&is->select_pending);
976         if (folder != NULL) {
977                 GQueue start = G_QUEUE_INIT;
978                 GList *head, *link;
979
980                 c (is->tagprefix, "-- Checking job queue for non-folder jobs\n");
981
982                 head = camel_imapx_command_queue_peek_head_link (is->queue);
983
984                 /* Tag which commands in the queue to start. */
985                 for (link = head; link != NULL; link = g_list_next (link)) {
986                         CamelIMAPXCommand *ic = link->data;
987
988                         if (ic->pri < min_pri)
989                                 break;
990
991                         c (is->tagprefix, "-- %3d '%s'?\n", (gint) ic->pri, ic->name);
992                         if (!ic->select) {
993                                 c (is->tagprefix, "--> starting '%s'\n", ic->name);
994                                 min_pri = ic->pri;
995                                 g_queue_push_tail (&start, link);
996                         }
997
998                         if (g_queue_get_length (&start) == MAX_COMMANDS)
999                                 break;
1000                 }
1001
1002                 if (g_queue_is_empty (&start))
1003                         c (is->tagprefix, "* no, waiting for pending select '%s'\n", camel_folder_get_full_name (folder));
1004
1005                 /* Start the tagged commands.
1006                  *
1007                  * Each command must be removed from 'is->queue' before
1008                  * starting it, so we temporarily reference the command
1009                  * to avoid accidentally finalizing it. */
1010                 while ((link = g_queue_pop_head (&start)) != NULL) {
1011                         CamelIMAPXCommand *ic;
1012
1013                         ic = camel_imapx_command_ref (link->data);
1014                         camel_imapx_command_queue_delete_link (is->queue, link);
1015
1016                         success = imapx_command_start (
1017                                 is, ic, cancellable, error);
1018
1019                         camel_imapx_command_unref (ic);
1020
1021                         if (!success) {
1022                                 g_queue_clear (&start);
1023                                 break;
1024                         }
1025                 }
1026
1027                 g_clear_object (&folder);
1028
1029                 return success;
1030         }
1031
1032         if (imapx_idle_supported (is) && is->state == IMAPX_SELECTED) {
1033                 gboolean empty = imapx_is_command_queue_empty (is);
1034
1035                 if (imapx_in_idle (is) && !camel_imapx_command_queue_is_empty (is->queue)) {
1036                         CamelIMAPXIdleStopResult stop_result;
1037                         CamelIMAPXStream *stream;
1038
1039                         stop_result = IMAPX_IDLE_STOP_NOOP;
1040                         stream = camel_imapx_server_ref_stream (is);
1041
1042                         if (stream != NULL) {
1043                                 stop_result = imapx_stop_idle (
1044                                         is, stream, cancellable, error);
1045                                 g_object_unref (stream);
1046                         }
1047
1048                         switch (stop_result) {
1049                                 /* Proceed with the next queued command. */
1050                                 case IMAPX_IDLE_STOP_NOOP:
1051                                         break;
1052
1053                                 case IMAPX_IDLE_STOP_SUCCESS:
1054                                         c (
1055                                                 is->tagprefix,
1056                                                 "waiting for idle to stop \n");
1057                                         /* if there are more pending commands,
1058                                          * then they should be processed too */
1059                                         break;
1060
1061                                 case IMAPX_IDLE_STOP_ERROR:
1062                                         return FALSE;
1063                         }
1064
1065                 } else if (empty && !imapx_in_idle (is)) {
1066                         imapx_start_idle (is);
1067                         c (is->tagprefix, "starting idle \n");
1068                         return TRUE;
1069                 }
1070         }
1071
1072         if (camel_imapx_command_queue_is_empty (is->queue)) {
1073                 c (is->tagprefix, "* no, no jobs\n");
1074                 return TRUE;
1075         }
1076
1077         /* See if any queued jobs on this select first */
1078         folder = g_weak_ref_get (&is->select_folder);
1079         if (folder != NULL) {
1080                 GQueue start = G_QUEUE_INIT;
1081                 GList *head, *link;
1082                 gboolean commands_started = FALSE;
1083
1084                 c (
1085                         is->tagprefix, "- we're selected on '%s', current jobs?\n",
1086                         camel_folder_get_full_name (folder));
1087
1088                 head = camel_imapx_command_queue_peek_head_link (is->active);
1089
1090                 /* Find the highest priority in the active queue. */
1091                 for (link = head; link != NULL; link = g_list_next (link)) {
1092                         CamelIMAPXCommand *ic = link->data;
1093
1094                         min_pri = MAX (min_pri, ic->pri);
1095                         c (is->tagprefix, "-  %3d '%s'\n", (gint) ic->pri, ic->name);
1096                 }
1097
1098                 if (camel_imapx_command_queue_get_length (is->active) >= MAX_COMMANDS) {
1099                         c (is->tagprefix, "** too many jobs busy, waiting for results for now\n");
1100                         g_object_unref (folder);
1101                         return TRUE;
1102                 }
1103
1104                 c (is->tagprefix, "-- Checking job queue\n");
1105
1106                 head = camel_imapx_command_queue_peek_head_link (is->queue);
1107
1108                 /* Tag which commands in the queue to start. */
1109                 for (link = head; link != NULL; link = g_list_next (link)) {
1110                         CamelIMAPXCommand *ic = link->data;
1111
1112                         if (is->literal != NULL)
1113                                 break;
1114
1115                         if (ic->pri < min_pri)
1116                                 break;
1117
1118                         c (is->tagprefix, "-- %3d '%s'?\n", (gint) ic->pri, ic->name);
1119
1120                         if (!ic->select || ((ic->select == folder) &&
1121                                             !duplicate_fetch_or_refresh (is, ic))) {
1122                                 c (is->tagprefix, "--> starting '%s'\n", ic->name);
1123                                 min_pri = ic->pri;
1124                                 g_queue_push_tail (&start, link);
1125                         } else {
1126                                 /* This job isn't for the selected folder, but we don't want to
1127                                  * consider jobs with _lower_ priority than this, even if they
1128                                  * are for the selected folder. */
1129                                 min_pri = ic->pri;
1130                         }
1131
1132                         if (g_queue_get_length (&start) == MAX_COMMANDS)
1133                                 break;
1134                 }
1135
1136                 g_clear_object (&folder);
1137
1138                 /* Start the tagged commands.
1139                  *
1140                  * Each command must be removed from 'is->queue' before
1141                  * starting it, so we temporarily reference the command
1142                  * to avoid accidentally finalizing it. */
1143                 while ((link = g_queue_pop_head (&start)) != NULL) {
1144                         CamelIMAPXCommand *ic;
1145                         gboolean success;
1146
1147                         ic = camel_imapx_command_ref (link->data);
1148                         camel_imapx_command_queue_delete_link (is->queue, link);
1149
1150                         success = imapx_command_start (
1151                                 is, ic, cancellable, error);
1152
1153                         camel_imapx_command_unref (ic);
1154
1155                         if (!success) {
1156                                 g_queue_clear (&start);
1157                                 return FALSE;
1158                         }
1159
1160                         commands_started = TRUE;
1161                 }
1162
1163                 if (commands_started)
1164                         return TRUE;
1165         }
1166
1167         /* This won't be NULL because we checked for an empty queue above. */
1168         first_ic = camel_imapx_command_queue_peek_head (is->queue);
1169
1170         /* If we need to select a folder for the first command, do it now,
1171          * once it is complete it will re-call us if it succeeded. */
1172         if (first_ic->select) {
1173                 c (
1174                         is->tagprefix, "Selecting folder '%s' for command '%s'(%p)\n",
1175                         camel_folder_get_full_name (first_ic->select),
1176                         first_ic->name, first_ic);
1177                 imapx_select (is, first_ic->select, FALSE, cancellable, error);
1178         } else {
1179                 GQueue start = G_QUEUE_INIT;
1180                 GList *head, *link;
1181
1182                 min_pri = first_ic->pri;
1183
1184                 folder = g_weak_ref_get (&is->select_folder);
1185
1186                 head = camel_imapx_command_queue_peek_head_link (is->queue);
1187
1188                 /* Tag which commands in the queue to start. */
1189                 for (link = head; link != NULL; link = g_list_next (link)) {
1190                         CamelIMAPXCommand *ic = link->data;
1191
1192                         if (is->literal != NULL)
1193                                 break;
1194
1195                         if (ic->pri < min_pri)
1196                                 break;
1197
1198                         if (!ic->select || (ic->select == folder &&
1199                                             !duplicate_fetch_or_refresh (is, ic))) {
1200                                 c (is->tagprefix, "* queueing job %3d '%s'\n", (gint) ic->pri, ic->name);
1201                                 min_pri = ic->pri;
1202                                 g_queue_push_tail (&start, link);
1203                         }
1204
1205                         if (g_queue_get_length (&start) == MAX_COMMANDS)
1206                                 break;
1207                 }
1208
1209                 g_clear_object (&folder);
1210
1211                 /* Start the tagged commands.
1212                  *
1213                  * Each command must be removed from 'is->queue' before
1214                  * starting it, so we temporarily reference the command
1215                  * to avoid accidentally finalizing it. */
1216                 while ((link = g_queue_pop_head (&start)) != NULL) {
1217                         CamelIMAPXCommand *ic;
1218                         gboolean success;
1219
1220                         ic = camel_imapx_command_ref (link->data);
1221                         camel_imapx_command_queue_delete_link (is->queue, link);
1222
1223                         success = imapx_command_start (
1224                                 is, ic, cancellable, error);
1225
1226                         camel_imapx_command_unref (ic);
1227
1228                         if (!success) {
1229                                 g_queue_clear (&start);
1230                                 return FALSE;
1231                         }
1232                 }
1233         }
1234
1235         return TRUE;
1236 }
1237
1238 static gboolean
1239 imapx_is_command_queue_empty (CamelIMAPXServer *is)
1240 {
1241         if (!camel_imapx_command_queue_is_empty (is->queue))
1242                 return FALSE;
1243
1244         if (!camel_imapx_command_queue_is_empty (is->active))
1245                 return FALSE;
1246
1247         return TRUE;
1248 }
1249
1250 static gboolean
1251 imapx_command_queue (CamelIMAPXServer *is,
1252                      CamelIMAPXCommand *ic,
1253                      GCancellable *cancellable,
1254                      GError **error)
1255 {
1256         CamelIMAPXJob *job;
1257         gboolean success;
1258
1259         /* We enqueue in priority order, new messages have
1260          * higher priority than older messages with the same priority */
1261
1262         job = camel_imapx_command_get_job (ic);
1263         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
1264
1265         camel_imapx_command_close (ic);
1266
1267         c (
1268                 is->tagprefix,
1269                 "enqueue job '%.*s'\n",
1270                 ((CamelIMAPXCommandPart *) ic->parts.head->data)->data_size,
1271                 ((CamelIMAPXCommandPart *) ic->parts.head->data)->data);
1272
1273         QUEUE_LOCK (is);
1274
1275         if (is->state == IMAPX_SHUTDOWN) {
1276                 c (is->tagprefix, "refuse to queue job on disconnected server\n");
1277                 g_set_error (
1278                         error, CAMEL_IMAPX_ERROR, 1,
1279                         "%s", _("Server disconnected"));
1280
1281                 QUEUE_UNLOCK (is);
1282
1283                 /* Send a NULL GError since we've already set the
1284                  * GError, and we're not interested in individual
1285                  * command errors at this point. */
1286                 if (ic->complete != NULL)
1287                         ic->complete (is, ic, NULL, NULL);
1288
1289                 return FALSE;
1290         }
1291
1292         camel_imapx_command_queue_insert_sorted (is->queue, ic);
1293
1294         success = imapx_command_start_next (is, cancellable, error);
1295
1296         QUEUE_UNLOCK (is);
1297
1298         return success;
1299 }
1300
1301 /* Must have QUEUE lock */
1302 static CamelIMAPXCommand *
1303 imapx_find_command_tag (CamelIMAPXServer *is,
1304                         guint tag)
1305 {
1306         CamelIMAPXCommand *ic = NULL;
1307         GList *head, *link;
1308
1309         QUEUE_LOCK (is);
1310
1311         if (is->literal != NULL && is->literal->tag == tag) {
1312                 ic = is->literal;
1313                 goto exit;
1314         }
1315
1316         head = camel_imapx_command_queue_peek_head_link (is->active);
1317
1318         for (link = head; link != NULL; link = g_list_next (link)) {
1319                 CamelIMAPXCommand *candidate = link->data;
1320
1321                 if (candidate->tag == tag) {
1322                         ic = candidate;
1323                         break;
1324                 }
1325         }
1326
1327 exit:
1328         QUEUE_UNLOCK (is);
1329
1330         return ic;
1331 }
1332
1333 /* Must not have QUEUE lock */
1334 static CamelIMAPXJob *
1335 imapx_match_active_job (CamelIMAPXServer *is,
1336                         guint32 type,
1337                         const gchar *uid)
1338 {
1339         CamelIMAPXJob *match = NULL;
1340         GList *head, *link;
1341
1342         QUEUE_LOCK (is);
1343
1344         head = camel_imapx_command_queue_peek_head_link (is->active);
1345
1346         for (link = head; link != NULL; link = g_list_next (link)) {
1347                 CamelIMAPXCommand *ic = link->data;
1348                 CamelFolder *folder;
1349                 CamelIMAPXJob *job;
1350                 gboolean job_matches;
1351
1352                 job = camel_imapx_command_get_job (ic);
1353
1354                 if (job == NULL)
1355                         continue;
1356
1357                 if (!(job->type & type))
1358                         continue;
1359
1360                 folder = g_weak_ref_get (&is->select_folder);
1361                 job_matches = camel_imapx_job_matches (job, folder, uid);
1362                 g_clear_object (&folder);
1363
1364                 if (job_matches) {
1365                         match = job;
1366                         break;
1367                 }
1368         }
1369
1370         QUEUE_UNLOCK (is);
1371
1372         return match;
1373 }
1374
1375 static CamelIMAPXJob *
1376 imapx_is_job_in_queue (CamelIMAPXServer *is,
1377                        CamelFolder *folder,
1378                        guint32 type,
1379                        const gchar *uid)
1380 {
1381         GList *head, *link;
1382         CamelIMAPXJob *job = NULL;
1383         gboolean found = FALSE;
1384
1385         QUEUE_LOCK (is);
1386
1387         head = g_queue_peek_head_link (&is->jobs);
1388
1389         for (link = head; link != NULL; link = g_list_next (link)) {
1390                 job = (CamelIMAPXJob *) link->data;
1391
1392                 if (!job || !(job->type & type))
1393                         continue;
1394
1395                 if (camel_imapx_job_matches (job, folder, uid)) {
1396                         found = TRUE;
1397                         break;
1398                 }
1399         }
1400
1401         QUEUE_UNLOCK (is);
1402
1403         if (found)
1404                 return job;
1405         else
1406                 return NULL;
1407 }
1408
1409 static void
1410 imapx_expunge_uid_from_summary (CamelIMAPXServer *is,
1411                                 gchar *uid,
1412                                 gboolean unsolicited)
1413 {
1414         CamelFolder *folder;
1415         CamelIMAPXFolder *ifolder;
1416         CamelMessageInfo *mi;
1417
1418         folder = g_weak_ref_get (&is->select_folder);
1419         g_return_if_fail (folder != NULL);
1420
1421         ifolder = CAMEL_IMAPX_FOLDER (folder);
1422
1423         if (unsolicited && ifolder->exists_on_server)
1424                 ifolder->exists_on_server--;
1425
1426         if (is->changes == NULL)
1427                 is->changes = camel_folder_change_info_new ();
1428
1429         mi = camel_folder_summary_peek_loaded (folder->summary, uid);
1430         if (mi) {
1431                 camel_folder_summary_remove (folder->summary, mi);
1432                 camel_message_info_free (mi);
1433         } else {
1434                 camel_folder_summary_remove_uid (folder->summary, uid);
1435         }
1436
1437         is->expunged = g_list_prepend (is->expunged, uid);
1438
1439         camel_folder_change_info_remove_uid (is->changes, uid);
1440
1441         if (imapx_idle_supported (is) && imapx_in_idle (is)) {
1442                 camel_folder_summary_save_to_db (folder->summary, NULL);
1443                 imapx_update_store_summary (folder);
1444                 camel_folder_changed (folder, is->changes);
1445
1446                 g_list_free_full (is->expunged, (GDestroyNotify) g_free);
1447                 is->expunged = NULL;
1448
1449                 camel_folder_change_info_clear (is->changes);
1450         }
1451
1452         g_object_unref (folder);
1453 }
1454
1455 static gchar *
1456 imapx_get_uid_from_index (CamelFolderSummary *summary,
1457                           guint id)
1458 {
1459         GPtrArray *array;
1460         gchar *uid = NULL;
1461
1462         g_return_val_if_fail (summary != NULL, NULL);
1463
1464         array = camel_folder_summary_get_array (summary);
1465         g_return_val_if_fail (array != NULL, NULL);
1466
1467         if (id < array->len) {
1468                 camel_folder_sort_uids (camel_folder_summary_get_folder (summary), array);
1469                 uid = g_strdup (g_ptr_array_index (array, id));
1470         }
1471
1472         camel_folder_summary_free_array (array);
1473
1474         return uid;
1475 }
1476
1477 static void
1478 invalidate_local_cache (CamelIMAPXFolder *ifolder,
1479                         guint64 new_uidvalidity)
1480 {
1481         CamelFolder *cfolder;
1482         CamelFolderChangeInfo *changes;
1483         GPtrArray *uids;
1484         gint ii;
1485
1486         g_return_if_fail (ifolder != NULL);
1487
1488         cfolder = CAMEL_FOLDER (ifolder);
1489         g_return_if_fail (cfolder != NULL);
1490
1491         changes = camel_folder_change_info_new ();
1492
1493         uids = camel_folder_summary_get_array (cfolder->summary);
1494         for (ii = 0; uids && ii < uids->len; ii++) {
1495                 const gchar *uid = uids->pdata[ii];
1496
1497                 if (uid)
1498                         camel_folder_change_info_change_uid (changes, uid);
1499         }
1500
1501         camel_folder_summary_free_array (uids);
1502
1503         CAMEL_IMAPX_SUMMARY (cfolder->summary)->validity = new_uidvalidity;
1504         camel_folder_summary_touch (cfolder->summary);
1505         camel_folder_summary_save_to_db (cfolder->summary, NULL);
1506
1507         camel_data_cache_clear (ifolder->cache, "cache");
1508         camel_data_cache_clear (ifolder->cache, "cur");
1509
1510         camel_folder_changed (cfolder, changes);
1511         camel_folder_change_info_free (changes);
1512 }
1513
1514 /* untagged response handler functions */
1515
1516 static gboolean
1517 imapx_untagged_capability (CamelIMAPXServer *is,
1518                            CamelIMAPXStream *stream,
1519                            GCancellable *cancellable,
1520                            GError **error)
1521 {
1522         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1523         /* cancellable may be NULL */
1524         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1525
1526         if (is->cinfo)
1527                 imapx_free_capability (is->cinfo);
1528         is->cinfo = imapx_parse_capability (stream, cancellable, error);
1529         if (is->cinfo == NULL)
1530                 return FALSE;
1531         c (is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
1532         return TRUE;
1533 }
1534
1535 static gboolean
1536 imapx_untagged_expunge (CamelIMAPXServer *is,
1537                         CamelIMAPXStream *stream,
1538                         GCancellable *cancellable,
1539                         GError **error)
1540 {
1541         CamelFolder *folder;
1542         CamelIMAPXJob *job = NULL;
1543         guint32 expunge = 0;
1544
1545         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1546         /* cancellable may be NULL */
1547         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1548
1549         expunge = is->priv->context->id;
1550         job = imapx_match_active_job (is, IMAPX_JOB_EXPUNGE, NULL);
1551
1552         /* If there is a job running, let it handle the deletion */
1553         if (job)
1554                 return TRUE;
1555
1556         c (is->tagprefix, "expunged: %d\n", is->priv->context->id);
1557
1558         folder = g_weak_ref_get (&is->select_folder);
1559
1560         if (folder != NULL) {
1561                 gchar *uid;
1562
1563                 uid = imapx_get_uid_from_index (folder->summary, expunge - 1);
1564
1565                 if (uid != NULL)
1566                         imapx_expunge_uid_from_summary (is, uid, TRUE);
1567
1568                 g_object_unref (folder);
1569         }
1570
1571         return TRUE;
1572 }
1573
1574 static gboolean
1575 imapx_untagged_vanished (CamelIMAPXServer *is,
1576                          CamelIMAPXStream *stream,
1577                          GCancellable *cancellable,
1578                          GError **error)
1579 {
1580         CamelFolder *folder;
1581         GPtrArray *uids = NULL;
1582         GList *uid_list = NULL;
1583         gboolean unsolicited = TRUE;
1584         gint i = 0;
1585         guint len = 0;
1586         guchar *token = NULL;
1587         gint tok = 0;
1588
1589         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1590         /* cancellable may be NULL */
1591         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1592
1593         tok = camel_imapx_stream_token (stream, &token, &len, cancellable, error);
1594         if (tok < 0)
1595                 return FALSE;
1596         if (tok == '(') {
1597                 unsolicited = FALSE;
1598                 while (tok != ')') {
1599                         /* We expect this to be 'EARLIER' */
1600                         tok = camel_imapx_stream_token (stream, &token, &len, cancellable, error);
1601                         if (tok < 0)
1602                                 return FALSE;
1603                 }
1604         } else
1605                 camel_imapx_stream_ungettoken (stream, tok, token, len);
1606
1607         uids = imapx_parse_uids (stream, cancellable, error);
1608         if (uids == NULL)
1609                 return FALSE;
1610
1611         folder = g_weak_ref_get (&is->select_folder);
1612         g_return_val_if_fail (folder != NULL, FALSE);
1613
1614         if (unsolicited) {
1615                 CamelIMAPXFolder *ifolder = CAMEL_IMAPX_FOLDER (folder);
1616
1617                 if (ifolder->exists_on_server < uids->len) {
1618                         c (
1619                                 is->tagprefix, "Error: exists_on_folder %d is fewer than vanished %d\n",
1620                                 ifolder->exists_on_server, uids->len);
1621                         ifolder->exists_on_server = 0;
1622                 } else
1623                         ifolder->exists_on_server -= uids->len;
1624         }
1625         if (is->changes == NULL)
1626                 is->changes = camel_folder_change_info_new ();
1627
1628         for (i = 0; i < uids->len; i++) {
1629                 gchar *uid = g_strdup_printf ("%u", GPOINTER_TO_UINT (g_ptr_array_index (uids, i)));
1630
1631                 c (is->tagprefix, "vanished: %s\n", uid);
1632
1633                 uid_list = g_list_prepend (uid_list, uid);
1634                 camel_folder_change_info_remove_uid (is->changes, uid);
1635         }
1636         uid_list = g_list_reverse (uid_list);
1637         camel_folder_summary_remove_uids (folder->summary, uid_list);
1638         is->expunged = g_list_concat (is->expunged, uid_list);
1639         g_ptr_array_free (uids, TRUE);
1640
1641         g_object_unref (folder);
1642
1643         return TRUE;
1644 }
1645
1646 static gboolean
1647 imapx_untagged_namespace (CamelIMAPXServer *is,
1648                           CamelIMAPXStream *stream,
1649                           GCancellable *cancellable,
1650                           GError **error)
1651 {
1652         CamelIMAPXNamespaceList *nsl = NULL;
1653         CamelIMAPXStoreNamespace *ns = NULL;
1654         CamelIMAPXStore *store;
1655
1656         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1657         /* cancellable may be NULL */
1658         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1659
1660         nsl = imapx_parse_namespace_list (stream, cancellable, error);
1661         if (nsl == NULL)
1662                 return FALSE;
1663
1664         store = camel_imapx_server_ref_store (is);
1665
1666         if (store->summary->namespaces)
1667                 camel_imapx_namespace_list_clear (store->summary->namespaces);
1668         store->summary->namespaces = nsl;
1669         camel_store_summary_touch (CAMEL_STORE_SUMMARY (store->summary));
1670
1671         /* TODO Need to remove store->dir_sep to support multiple namespaces */
1672         ns = nsl->personal;
1673         if (ns) {
1674                 store->dir_sep = ns->sep;
1675                 if (!store->dir_sep)
1676                         store->dir_sep = '/';
1677         }
1678
1679         g_object_unref (store);
1680
1681         return TRUE;
1682 }
1683
1684 static gboolean
1685 imapx_untagged_exists (CamelIMAPXServer *is,
1686                        CamelIMAPXStream *stream,
1687                        GCancellable *cancellable,
1688                        GError **error)
1689 {
1690         CamelFolder *folder;
1691         gboolean success = TRUE;
1692
1693         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1694
1695         c (is->tagprefix, "exists: %d\n", is->priv->context->id);
1696         is->exists = is->priv->context->id;
1697
1698         folder = g_weak_ref_get (&is->select_folder);
1699
1700         if (folder != NULL) {
1701                 CAMEL_IMAPX_FOLDER (folder)->exists_on_server =
1702                         is->priv->context->id;
1703
1704                 if (imapx_idle_supported (is) && imapx_in_idle (is)) {
1705                         guint count;
1706
1707                         count = camel_folder_summary_count (folder->summary);
1708                         if (count < is->priv->context->id) {
1709                                 CamelIMAPXIdleStopResult stop_result;
1710
1711                                 stop_result = imapx_stop_idle (
1712                                         is, stream, cancellable, error);
1713                                 success = (stop_result != IMAPX_IDLE_STOP_ERROR);
1714                         }
1715                 }
1716
1717                 g_object_unref (folder);
1718         }
1719
1720         return success;
1721 }
1722
1723 static gboolean
1724 imapx_untagged_flags (CamelIMAPXServer *is,
1725                       CamelIMAPXStream *stream,
1726                       GCancellable *cancellable,
1727                       GError **error)
1728 {
1729         guint32 flags;
1730
1731         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1732         /* cancellable may be NULL */
1733         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1734
1735         imapx_parse_flags (stream, &flags, NULL, cancellable, error);
1736         c (is->tagprefix, "flags: %08x\n", flags);
1737
1738         return TRUE;
1739 }
1740
1741 static gboolean
1742 imapx_untagged_fetch (CamelIMAPXServer *is,
1743                       CamelIMAPXStream *stream,
1744                       GCancellable *cancellable,
1745                       GError **error)
1746 {
1747         struct _fetch_info *finfo;
1748
1749         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1750         /* cancellable may be NULL */
1751         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1752
1753         finfo = imapx_parse_fetch (stream, cancellable, error);
1754         if (finfo == NULL) {
1755                 imapx_free_fetch (finfo);
1756                 return FALSE;
1757         }
1758
1759         if ((finfo->got & (FETCH_BODY | FETCH_UID)) == (FETCH_BODY | FETCH_UID)) {
1760                 CamelIMAPXJob *job;
1761                 GetMessageData *data;
1762
1763                 job = imapx_match_active_job (
1764                         is, IMAPX_JOB_GET_MESSAGE, finfo->uid);
1765                 g_return_val_if_fail (job != NULL, FALSE);
1766
1767                 data = camel_imapx_job_get_data (job);
1768                 g_return_val_if_fail (data != NULL, FALSE);
1769
1770                 /* This must've been a get-message request,
1771                  * fill out the body stream, in the right spot. */
1772
1773                 if (job != NULL) {
1774                         if (data->use_multi_fetch) {
1775                                 data->body_offset = finfo->offset;
1776                                 g_seekable_seek (
1777                                         G_SEEKABLE (data->stream),
1778                                         finfo->offset, G_SEEK_SET,
1779                                         NULL, NULL);
1780                         }
1781
1782                         data->body_len = camel_stream_write_to_stream (
1783                                 finfo->body, data->stream, cancellable, error);
1784                         if (data->body_len == -1) {
1785                                 g_prefix_error (
1786                                         error, "%s: ",
1787                                         _("Error writing to cache stream"));
1788                                 return FALSE;
1789                         }
1790                 }
1791         }
1792
1793         if ((finfo->got & FETCH_FLAGS) && !(finfo->got & FETCH_HEADER)) {
1794                 CamelIMAPXJob *job;
1795                 CamelFolder *select_folder;
1796                 CamelFolder *select_pending;
1797                 RefreshInfoData *data = NULL;
1798
1799                 job = imapx_match_active_job (
1800                         is, IMAPX_JOB_FETCH_NEW_MESSAGES |
1801                         IMAPX_JOB_REFRESH_INFO |
1802                         IMAPX_JOB_FETCH_MESSAGES, NULL);
1803
1804                 if (job != NULL) {
1805                         data = camel_imapx_job_get_data (job);
1806                         g_return_val_if_fail (data != NULL, FALSE);
1807                 }
1808
1809                 g_mutex_lock (&is->select_lock);
1810                 select_folder = g_weak_ref_get (&is->select_folder);
1811                 select_pending = g_weak_ref_get (&is->select_pending);
1812                 g_mutex_unlock (&is->select_lock);
1813
1814                 /* This is either a refresh_info job, check to see if it is
1815                  * and update if so, otherwise it must've been an unsolicited
1816                  * response, so update the summary to match. */
1817                 if (data && (finfo->got & FETCH_UID) && data->scan_changes) {
1818                         struct _refresh_info r;
1819
1820                         r.uid = finfo->uid;
1821                         finfo->uid = NULL;
1822                         r.server_flags = finfo->flags;
1823                         r.server_user_flags = finfo->user_flags;
1824                         finfo->user_flags = NULL;
1825                         r.exists = FALSE;
1826                         g_array_append_val (data->infos, r);
1827
1828                 } else if (select_folder != NULL) {
1829                         CamelMessageInfo *mi = NULL;
1830                         gboolean changed = FALSE;
1831                         gchar *uid = NULL;
1832
1833                         c (is->tagprefix, "flag changed: %d\n", is->priv->context->id);
1834
1835                         if (finfo->got & FETCH_UID) {
1836                                 uid = finfo->uid;
1837                                 finfo->uid = NULL;
1838                         } else {
1839                                 uid = imapx_get_uid_from_index (
1840                                         select_folder->summary,
1841                                         is->priv->context->id - 1);
1842                         }
1843
1844                         if (uid) {
1845                                 mi = camel_folder_summary_get (
1846                                         select_folder->summary, uid);
1847                                 if (mi) {
1848                                         /* It's unsolicited _unless_ is->select_pending (i.e. during
1849                                          * a QRESYNC SELECT */
1850                                         changed = imapx_update_message_info_flags (
1851                                                 mi, finfo->flags,
1852                                                 finfo->user_flags,
1853                                                 is->permanentflags,
1854                                                 select_folder,
1855                                                 (select_pending == NULL));
1856                                 } else {
1857                                         /* This (UID + FLAGS for previously unknown message) might
1858                                          * happen during a SELECT (QRESYNC). We should use it. */
1859                                         c (is->tagprefix, "flags changed for unknown uid %s\n.", uid);
1860                                 }
1861                                 finfo->user_flags = NULL;
1862                         }
1863
1864                         if (changed) {
1865                                 if (is->changes == NULL)
1866                                         is->changes = camel_folder_change_info_new ();
1867
1868                                 camel_folder_change_info_change_uid (is->changes, uid);
1869                                 g_free (uid);
1870                         }
1871
1872                         if (imapx_idle_supported (is) && changed && imapx_in_idle (is)) {
1873                                 camel_folder_summary_save_to_db (
1874                                         select_folder->summary, NULL);
1875                                 imapx_update_store_summary (select_folder);
1876                                 camel_folder_changed (
1877                                         select_folder, is->changes);
1878                                 camel_folder_change_info_clear (is->changes);
1879                         }
1880
1881                         if (mi)
1882                                 camel_message_info_free (mi);
1883                 }
1884
1885                 g_clear_object (&select_folder);
1886                 g_clear_object (&select_pending);
1887         }
1888
1889         if ((finfo->got & (FETCH_HEADER | FETCH_UID)) == (FETCH_HEADER | FETCH_UID)) {
1890                 CamelIMAPXJob *job;
1891
1892                 /* This must be a refresh info job as well, but it has
1893                  * asked for new messages to be added to the index. */
1894
1895                 job = imapx_match_active_job (
1896                         is, IMAPX_JOB_FETCH_NEW_MESSAGES |
1897                         IMAPX_JOB_REFRESH_INFO |
1898                         IMAPX_JOB_FETCH_MESSAGES, NULL);
1899
1900                 if (job != NULL) {
1901                         CamelFolder *folder;
1902                         CamelMimeParser *mp;
1903                         CamelMessageInfo *mi;
1904
1905                         folder = camel_imapx_job_ref_folder (job);
1906                         g_return_val_if_fail (folder != NULL, FALSE);
1907
1908                         /* Do we want to save these headers for later too?  Do we care? */
1909
1910                         mp = camel_mime_parser_new ();
1911                         camel_mime_parser_init_with_stream (mp, finfo->header, NULL);
1912                         mi = camel_folder_summary_info_new_from_parser (folder->summary, mp);
1913                         g_object_unref (mp);
1914
1915                         if (mi != NULL) {
1916                                 guint32 server_flags;
1917                                 CamelFlag *server_user_flags;
1918                                 CamelMessageInfoBase *binfo;
1919                                 gboolean free_user_flags = FALSE;
1920
1921                                 mi->uid = camel_pstring_strdup (finfo->uid);
1922
1923                                 if (!(finfo->got & FETCH_FLAGS)) {
1924                                         RefreshInfoData *data;
1925                                         struct _refresh_info *r = NULL;
1926                                         gint min, max, mid;
1927                                         gboolean found = FALSE;
1928
1929                                         data = camel_imapx_job_get_data (job);
1930                                         g_return_val_if_fail (data != NULL, FALSE);
1931
1932                                         min = data->last_index;
1933                                         max = data->index - 1;
1934
1935                                         /* array is sorted, so use a binary search */
1936                                         do {
1937                                                 gint cmp = 0;
1938
1939                                                 mid = (min + max) / 2;
1940                                                 r = &g_array_index (data->infos, struct _refresh_info, mid);
1941                                                 cmp = imapx_refresh_info_uid_cmp (
1942                                                         finfo->uid,
1943                                                         r->uid,
1944                                                         is->priv->context->fetch_order == CAMEL_SORT_ASCENDING);
1945
1946                                                 if (cmp > 0)
1947                                                         min = mid + 1;
1948                                                 else if (cmp < 0)
1949                                                         max = mid - 1;
1950                                                 else
1951                                                         found = TRUE;
1952
1953                                         } while (!found && min <= max);
1954
1955                                         if (!found)
1956                                                 g_assert_not_reached ();
1957
1958                                         server_flags = r->server_flags;
1959                                         server_user_flags = r->server_user_flags;
1960                                 } else {
1961                                         server_flags = finfo->flags;
1962                                         server_user_flags = finfo->user_flags;
1963                                         /* free user_flags ? */
1964                                         finfo->user_flags = NULL;
1965                                         free_user_flags = TRUE;
1966                                 }
1967
1968                                 /* If the message is a really new one -- equal or higher than what
1969                                  * we know as UIDNEXT for the folder, then it came in since we last
1970                                  * fetched UIDNEXT and UNREAD count. We'll update UIDNEXT in the
1971                                  * command completion, but update UNREAD count now according to the
1972                                  * message SEEN flag */
1973                                 if (!(server_flags & CAMEL_MESSAGE_SEEN)) {
1974                                         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
1975                                         guint64 uidl = strtoull (mi->uid, NULL, 10);
1976
1977                                         if (uidl >= ifolder->uidnext_on_server) {
1978                                                 c (is->tagprefix, "Updating unread count for new message %s\n", mi->uid);
1979                                                 ((CamelIMAPXFolder *) folder)->unread_on_server++;
1980                                         } else {
1981                                                 c (is->tagprefix, "Not updating unread count for new message %s\n", mi->uid);
1982                                         }
1983                                 }
1984
1985                                 binfo = (CamelMessageInfoBase *) mi;
1986                                 binfo->size = finfo->size;
1987
1988                                 if (!camel_folder_summary_check_uid (folder->summary, mi->uid)) {
1989                                         RefreshInfoData *data;
1990                                         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
1991                                         gint cnt;
1992
1993                                         data = camel_imapx_job_get_data (job);
1994                                         g_return_val_if_fail (data != NULL, FALSE);
1995
1996                                         imapx_set_message_info_flags_for_new_message (mi, server_flags, server_user_flags, folder);
1997                                         camel_folder_summary_add (folder->summary, mi);
1998                                         camel_folder_change_info_add_uid (data->changes, mi->uid);
1999
2000                                         if (!g_hash_table_lookup (ifolder->ignore_recent, mi->uid)) {
2001                                                 camel_folder_change_info_recent_uid (data->changes, mi->uid);
2002                                                 g_hash_table_remove (ifolder->ignore_recent, mi->uid);
2003                                         }
2004
2005                                         cnt = (camel_folder_summary_count (folder->summary) * 100 ) / ifolder->exists_on_server;
2006                                         camel_operation_progress (cancellable, cnt ? cnt : 1);
2007                                 } else {
2008                                         camel_message_info_free (mi);
2009                                 }
2010
2011                                 if (free_user_flags && server_user_flags)
2012                                         camel_flag_list_free (&server_user_flags);
2013
2014                         }
2015
2016                         g_object_unref (folder);
2017                 }
2018         }
2019
2020         imapx_free_fetch (finfo);
2021
2022         return TRUE;
2023 }
2024
2025 static gboolean
2026 imapx_untagged_lsub (CamelIMAPXServer *is,
2027                      CamelIMAPXStream *stream,
2028                      GCancellable *cancellable,
2029                      GError **error)
2030 {
2031         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2032         /* cancellable may be NULL */
2033         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2034
2035         is->priv->context->lsub = TRUE;
2036
2037         return TRUE;
2038 }
2039
2040 static gboolean
2041 imapx_untagged_list (CamelIMAPXServer *is,
2042                      CamelIMAPXStream *stream,
2043                      GCancellable *cancellable,
2044                      GError **error)
2045 {
2046         struct _list_info *linfo = NULL;
2047         CamelIMAPXJob *job = NULL;
2048         ListData *data = NULL;
2049
2050         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2051         /* cancellable may be NULL */
2052         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2053
2054         linfo = imapx_parse_list (stream, cancellable, error);
2055         if (!linfo)
2056                 return TRUE;
2057
2058         job = imapx_match_active_job (is, IMAPX_JOB_LIST, linfo->name);
2059
2060         data = camel_imapx_job_get_data (job);
2061         g_return_val_if_fail (data != NULL, FALSE);
2062
2063         // TODO: we want to make sure the names match?
2064
2065         if (data->flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) {
2066                 c (is->tagprefix, "lsub: '%s' (%c)\n", linfo->name, linfo->separator);
2067         } else {
2068                 c (is->tagprefix, "list: '%s' (%c)\n", linfo->name, linfo->separator);
2069         }
2070
2071         if (job && g_hash_table_lookup (data->folders, linfo->name) == NULL) {
2072                 if (is->priv->context->lsub)
2073                         linfo->flags |= CAMEL_FOLDER_SUBSCRIBED;
2074                 g_hash_table_insert (data->folders, linfo->name, linfo);
2075         } else {
2076                 g_warning ("got list response but no current listing job happening?\n");
2077                 imapx_free_list (linfo);
2078         }
2079
2080         return TRUE;
2081 }
2082
2083 static gboolean
2084 imapx_untagged_quota (CamelIMAPXServer *is,
2085                       CamelIMAPXStream *stream,
2086                       GCancellable *cancellable,
2087                       GError **error)
2088 {
2089         gchar *quota_root_name = NULL;
2090         CamelFolderQuotaInfo *quota_info = NULL;
2091         gboolean success;
2092
2093         success = camel_imapx_parse_quota (
2094                 stream, cancellable, &quota_root_name, &quota_info, error);
2095
2096         /* Sanity check */
2097         g_return_val_if_fail (
2098                 (success && (quota_root_name != NULL)) ||
2099                 (!success && (quota_root_name == NULL)), FALSE);
2100
2101         if (success) {
2102                 CamelIMAPXStore *store;
2103
2104                 store = camel_imapx_server_ref_store (is);
2105                 camel_imapx_store_set_quota_info (
2106                         store, quota_root_name, quota_info);
2107                 g_object_unref (store);
2108
2109                 g_free (quota_root_name);
2110                 camel_folder_quota_info_free (quota_info);
2111         }
2112
2113         return success;
2114 }
2115
2116 static gboolean
2117 imapx_untagged_quotaroot (CamelIMAPXServer *is,
2118                           CamelIMAPXStream *stream,
2119                           GCancellable *cancellable,
2120                           GError **error)
2121 {
2122         CamelIMAPXStore *store;
2123         CamelIMAPXStoreNamespace *ns;
2124         CamelFolder *folder = NULL;
2125         gchar *mailbox_name = NULL;
2126         gchar **quota_root_names = NULL;
2127         gboolean success;
2128         GError *local_error = NULL;
2129
2130         success = camel_imapx_parse_quotaroot (
2131                 stream, cancellable, &mailbox_name, &quota_root_names, error);
2132
2133         /* Sanity check */
2134         g_return_val_if_fail (
2135                 (success && (mailbox_name != NULL)) ||
2136                 (!success && (mailbox_name == NULL)), FALSE);
2137
2138         if (!success)
2139                 return FALSE;
2140
2141         store = camel_imapx_server_ref_store (is);
2142
2143         ns = camel_imapx_store_summary_namespace_find_full (
2144                 store->summary, mailbox_name);
2145         if (ns != NULL) {
2146                 gchar *folder_path;
2147
2148                 folder_path = camel_imapx_store_summary_full_to_path (
2149                         store->summary, mailbox_name, ns->sep);
2150                 if (folder_path != NULL) {
2151                         folder = camel_store_get_folder_sync (
2152                                 CAMEL_STORE (store), folder_path, 0,
2153                                 cancellable, &local_error);
2154                         g_free (folder_path);
2155                 }
2156         }
2157
2158         if (folder != NULL) {
2159                 camel_imapx_folder_set_quota_root_names (
2160                         CAMEL_IMAPX_FOLDER (folder),
2161                         (const gchar **) quota_root_names);
2162                 g_object_unref (folder);
2163         }
2164
2165         if (local_error != NULL) {
2166                 g_warning (
2167                         "%s: Failed to get folder '%s': %s",
2168                         G_STRFUNC, mailbox_name, local_error->message);
2169                 g_error_free (local_error);
2170         }
2171
2172         g_free (mailbox_name);
2173         g_strfreev (quota_root_names);
2174
2175         return TRUE;
2176 }
2177
2178 static gboolean
2179 imapx_untagged_recent (CamelIMAPXServer *is,
2180                        CamelIMAPXStream *stream,
2181                        GCancellable *cancellable,
2182                        GError **error)
2183 {
2184         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2185         /* cancellable may be NULL */
2186         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2187
2188         c (is->tagprefix, "recent: %d\n", is->priv->context->id);
2189         is->recent = is->priv->context->id;
2190
2191         return TRUE;
2192 }
2193
2194 static gboolean
2195 imapx_untagged_search (CamelIMAPXServer *is,
2196                        CamelIMAPXStream *stream,
2197                        GCancellable *cancellable,
2198                        GError **error)
2199 {
2200         GArray *search_results;
2201         gint tok;
2202         guint len;
2203         guchar *token;
2204         guint64 number;
2205         gboolean success = FALSE;
2206         GError *local_error = NULL;
2207
2208         search_results = g_array_new (FALSE, FALSE, sizeof (guint64));
2209
2210         while (TRUE) {
2211                 /* Peek at the next token, and break
2212                  * out of the loop if we get a newline. */
2213                 tok = camel_imapx_stream_token (
2214                         stream, &token, &len, cancellable, error);
2215                 if (tok == '\n')
2216                         break;
2217                 if (tok == IMAPX_TOK_ERROR || tok == IMAPX_TOK_PROTOCOL)
2218                         goto exit;
2219                 camel_imapx_stream_ungettoken (stream, tok, token, len);
2220
2221                 /* XXX camel_imapx_stream_number() should return the
2222                  *     number as an out parameter, so we can more easily
2223                  *     distinguish between a real '0' and an error. */
2224                 number = camel_imapx_stream_number (
2225                         stream, cancellable, &local_error);
2226                 if (local_error == NULL) {
2227                         g_array_append_val (search_results, number);
2228                 } else {
2229                         g_propagate_error (error, local_error);
2230                         goto exit;
2231                 }
2232         }
2233
2234         g_mutex_lock (&is->priv->search_results_lock);
2235
2236         if (is->priv->search_results == NULL)
2237                 is->priv->search_results = g_array_ref (search_results);
2238         else
2239                 g_warning ("%s: Conflicting search results", G_STRFUNC);
2240
2241         g_mutex_unlock (&is->priv->search_results_lock);
2242
2243         success = TRUE;
2244
2245 exit:
2246         g_array_unref (search_results);
2247
2248         return success;
2249 }
2250
2251 static gboolean
2252 imapx_untagged_status (CamelIMAPXServer *is,
2253                        CamelIMAPXStream *stream,
2254                        GCancellable *cancellable,
2255                        GError **error)
2256 {
2257         CamelIMAPXStore *store;
2258         struct _state_info *sinfo = NULL;
2259
2260         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2261         /* cancellable may be NULL */
2262         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2263
2264         store = camel_imapx_server_ref_store (is);
2265
2266         sinfo = imapx_parse_status_info (stream, cancellable, error);
2267
2268         if (sinfo) {
2269                 CamelIMAPXStoreSummary *s = store->summary;
2270                 CamelIMAPXStoreNamespace *ns;
2271                 CamelFolder *folder = NULL;
2272
2273                 ns = camel_imapx_store_summary_namespace_find_full (s, sinfo->name);
2274                 if (ns) {
2275                         gchar *path_name;
2276
2277                         path_name = camel_imapx_store_summary_full_to_path (s, sinfo->name, ns->sep);
2278                         c (is->tagprefix, "Got folder path '%s' for full '%s'\n", path_name, sinfo->name);
2279                         if (path_name) {
2280                                 folder = camel_store_get_folder_sync (
2281                                         CAMEL_STORE (store),
2282                                         path_name, 0, cancellable, error);
2283                                 g_free (path_name);
2284                         }
2285                 }
2286                 if (folder != NULL) {
2287                         CamelIMAPXFolder *ifolder;
2288
2289                         ifolder = CAMEL_IMAPX_FOLDER (folder);
2290                         ifolder->unread_on_server = sinfo->unseen;
2291                         ifolder->exists_on_server = sinfo->messages;
2292                         ifolder->modseq_on_server = sinfo->highestmodseq;
2293                         ifolder->uidnext_on_server = sinfo->uidnext;
2294                         ifolder->uidvalidity_on_server = sinfo->uidvalidity;
2295                         if (sinfo->uidvalidity && sinfo->uidvalidity != ((CamelIMAPXSummary *) folder->summary)->validity)
2296                                 invalidate_local_cache (ifolder, sinfo->uidvalidity);
2297                 } else {
2298                         c (is->tagprefix, "Received STATUS for unknown folder '%s'\n", sinfo->name);
2299                 }
2300
2301                 g_free (sinfo->name);
2302                 g_free (sinfo);
2303         }
2304
2305         g_object_unref (store);
2306
2307         return TRUE;
2308 }
2309
2310 static gboolean
2311 imapx_untagged_bye (CamelIMAPXServer *is,
2312                     CamelIMAPXStream *stream,
2313                     GCancellable *cancellable,
2314                     GError **error)
2315 {
2316         guchar *token = NULL;
2317
2318         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2319         /* cancellable may be NULL */
2320         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2321
2322         if (camel_imapx_stream_text (stream, &token, cancellable, NULL)) {
2323                 c (is->tagprefix, "BYE: %s\n", token);
2324                 g_set_error (
2325                         error, CAMEL_IMAPX_ERROR, 1,
2326                         "IMAP server said BYE: %s", token);
2327         }
2328         is->state = IMAPX_SHUTDOWN;
2329
2330         return FALSE;
2331 }
2332
2333 static gboolean
2334 imapx_untagged_preauth (CamelIMAPXServer *is,
2335                         CamelIMAPXStream *stream,
2336                         GCancellable *cancellable,
2337                         GError **error)
2338 {
2339         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2340         /* cancellable may be NULL */
2341         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2342
2343         c (is->tagprefix, "preauthenticated\n");
2344         if (is->state < IMAPX_AUTHENTICATED)
2345                 is->state = IMAPX_AUTHENTICATED;
2346
2347         return TRUE;
2348 }
2349
2350 static gboolean
2351 imapx_untagged_ok_no_bad (CamelIMAPXServer *is,
2352                           CamelIMAPXStream *stream,
2353                           GCancellable *cancellable,
2354                           GError **error)
2355 {
2356         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2357         /* cancellable may be NULL */
2358         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2359
2360         /* TODO: validate which ones of these can happen as unsolicited responses */
2361         /* TODO: handle bye/preauth differently */
2362         camel_imapx_stream_ungettoken (
2363                 stream,
2364                 is->priv->context->tok,
2365                 is->priv->context->token,
2366                 is->priv->context->len);
2367         is->priv->context->sinfo =
2368                 imapx_parse_status (stream, cancellable, error);
2369         if (is->priv->context->sinfo == NULL)
2370                 return FALSE;
2371         switch (is->priv->context->sinfo->condition) {
2372         case IMAPX_CLOSED:
2373                 c (is->tagprefix, "previously selected folder is now closed\n");
2374                 {
2375                         CamelFolder *select_folder;
2376                         CamelFolder *select_pending;
2377
2378                         g_mutex_lock (&is->select_lock);
2379
2380                         select_folder = g_weak_ref_get (&is->select_folder);
2381                         select_pending = g_weak_ref_get (&is->select_pending);
2382
2383                         if (select_folder == NULL)
2384                                 g_weak_ref_set (
2385                                         &is->select_folder,
2386                                         select_pending);
2387
2388                         g_clear_object (&select_folder);
2389                         g_clear_object (&select_pending);
2390
2391                         g_mutex_unlock (&is->select_lock);
2392                 }
2393                 break;
2394         case IMAPX_READ_WRITE:
2395                 is->mode = IMAPX_MODE_READ | IMAPX_MODE_WRITE;
2396                 c (is->tagprefix, "folder is read-write\n");
2397                 break;
2398         case IMAPX_READ_ONLY:
2399                 is->mode = IMAPX_MODE_READ;
2400                 c (is->tagprefix, "folder is read-only\n");
2401                 break;
2402         case IMAPX_UIDVALIDITY:
2403                 is->uidvalidity = is->priv->context->sinfo->u.uidvalidity;
2404                 break;
2405         case IMAPX_UNSEEN:
2406                 is->unseen = is->priv->context->sinfo->u.unseen;
2407                 break;
2408         case IMAPX_HIGHESTMODSEQ:
2409                 is->highestmodseq = is->priv->context->sinfo->u.highestmodseq;
2410                 break;
2411         case IMAPX_PERMANENTFLAGS:
2412                 is->permanentflags = is->priv->context->sinfo->u.permanentflags;
2413                 break;
2414         case IMAPX_UIDNEXT:
2415                 is->uidnext = is->priv->context->sinfo->u.uidnext;
2416                 break;
2417         case IMAPX_ALERT:
2418                 c (is->tagprefix, "ALERT!: %s\n", is->priv->context->sinfo->text);
2419                 break;
2420         case IMAPX_PARSE:
2421                 c (is->tagprefix, "PARSE: %s\n", is->priv->context->sinfo->text);
2422                 break;
2423         case IMAPX_CAPABILITY:
2424                 if (is->priv->context->sinfo->u.cinfo) {
2425                         struct _capability_info *cinfo = is->cinfo;
2426                         is->cinfo = is->priv->context->sinfo->u.cinfo;
2427                         is->priv->context->sinfo->u.cinfo = NULL;
2428                         if (cinfo)
2429                                 imapx_free_capability (cinfo);
2430                         c (is->tagprefix, "got capability flags %08x\n", is->cinfo ? is->cinfo->capa : 0xFFFFFFFF);
2431                 }
2432                 break;
2433         default:
2434                 break;
2435         }
2436         imapx_free_status (is->priv->context->sinfo);
2437
2438         return TRUE;
2439 }
2440
2441 /* handle any untagged responses */
2442 static gboolean
2443 imapx_untagged (CamelIMAPXServer *is,
2444                 CamelIMAPXStream *stream,
2445                 GCancellable *cancellable,
2446                 GError **error)
2447 {
2448         CamelIMAPXSettings *settings;
2449         CamelSortType fetch_order;
2450         guchar *p = NULL, c;
2451         const gchar *token = NULL;
2452         gboolean ok = FALSE;
2453
2454         /* If is->priv->context is not NULL here, it basically means
2455          * that imapx_untagged() got called concurrently for the same
2456          * CamelIMAPXServer instance. Should this ever happen, then
2457          * we will need to protect this data structure with locks
2458          */
2459         g_return_val_if_fail (is->priv->context == NULL, FALSE);
2460         is->priv->context = g_new0 (CamelIMAPXServerUntaggedContext, 1);
2461
2462         settings = camel_imapx_server_ref_settings (is);
2463         fetch_order = camel_imapx_settings_get_fetch_order (settings);
2464         g_object_unref (settings);
2465
2466         is->priv->context->lsub = FALSE;
2467         is->priv->context->fetch_order = fetch_order;
2468
2469         e (is->tagprefix, "got untagged response\n");
2470         is->priv->context->id = 0;
2471         is->priv->context->tok = camel_imapx_stream_token (
2472                 stream,
2473                 &(is->priv->context->token),
2474                 &(is->priv->context->len),
2475                 cancellable, error);
2476         if (is->priv->context->tok < 0)
2477                 goto exit;
2478
2479         if (is->priv->context->tok == IMAPX_TOK_INT) {
2480                 is->priv->context->id = strtoul (
2481                         (gchar *) is->priv->context->token, NULL, 10);
2482                 is->priv->context->tok = camel_imapx_stream_token (
2483                         stream,
2484                         &(is->priv->context->token),
2485                         &(is->priv->context->len),
2486                         cancellable, error);
2487                 if (is->priv->context->tok < 0)
2488                         goto exit;
2489         }
2490
2491         if (is->priv->context->tok == '\n') {
2492                 g_set_error (
2493                         error, CAMEL_IMAPX_ERROR, 1,
2494                         "truncated server response");
2495                 goto exit;
2496         }
2497
2498         e (is->tagprefix, "Have token '%s' id %d\n", is->priv->context->token, is->priv->context->id);
2499         p = is->priv->context->token;
2500         while ((c = *p))
2501                 *p++ = toupper((gchar) c);
2502
2503         token = (const gchar *) is->priv->context->token; /* FIXME need 'guchar *token' here */
2504         while (token != NULL) {
2505                 CamelIMAPXUntaggedRespHandlerDesc *desc = NULL;
2506
2507                 desc = g_hash_table_lookup (is->priv->untagged_handlers, token);
2508                 if (desc == NULL) {
2509                         /* unknown response, just ignore it */
2510                         c (is->tagprefix, "unknown token: %s\n", is->priv->context->token);
2511                         break;
2512                 }
2513                 if (desc->handler == NULL) {
2514                         /* no handler function, ignore token */
2515                         c (is->tagprefix, "no handler for token: %s\n", is->priv->context->token);
2516                         break;
2517                 }
2518
2519                 /* call the handler function */
2520                 ok = desc->handler (is, stream, cancellable, error);
2521                 if (!ok)
2522                         goto exit;
2523
2524                 /* is there another handler next-in-line? */
2525                 token = desc->next_response;
2526                 if (token != NULL) {
2527                         /* TODO do we need to update 'priv->context->token'
2528                          *      to the value of 'token' here, before
2529                          *      calling the handler next-in-line for this
2530                          *      specific run of imapx_untagged()?
2531                          *      It has not been done in the original code
2532                          *      in the "fall through" situation in the
2533                          *      token switch statement, which is what
2534                          *      we're mimicking here
2535                          */
2536                         continue;
2537                 }
2538
2539                 if (!desc->skip_stream_when_done)
2540                         goto exit;
2541         }
2542
2543         ok = (camel_imapx_stream_skip (stream, cancellable, error) == 0);
2544  exit:
2545         g_free (is->priv->context);
2546         is->priv->context = NULL;
2547
2548         return ok;
2549 }
2550
2551 /* handle any continuation requests
2552  * either data continuations, or auth continuation */
2553 static gboolean
2554 imapx_continuation (CamelIMAPXServer *is,
2555                     CamelIMAPXStream *stream,
2556                     gboolean litplus,
2557                     GCancellable *cancellable,
2558                     GError **error)
2559 {
2560         CamelIMAPXCommand *ic, *newliteral = NULL;
2561         CamelIMAPXCommandPart *cp;
2562         GList *link;
2563         gssize n_bytes_written;
2564         gboolean success = TRUE;
2565
2566         /* The 'literal' pointer is like a write-lock, nothing else
2567          * can write while we have it ... so we dont need any
2568          * ohter lock here.  All other writes go through
2569          * queue-lock */
2570         if (imapx_idle_supported (is) && imapx_in_idle (is)) {
2571                 camel_imapx_stream_skip (stream, cancellable, error);
2572
2573                 c (is->tagprefix, "Got continuation response for IDLE \n");
2574                 IDLE_LOCK (is->idle);
2575                 /* We might have actually sent the DONE already! */
2576                 if (is->idle->state == IMAPX_IDLE_ISSUED)
2577                         is->idle->state = IMAPX_IDLE_STARTED;
2578                 else if (is->idle->state == IMAPX_IDLE_CANCEL) {
2579                         /* IDLE got cancelled after we sent the command, while
2580                          * we were waiting for this continuation. Send DONE
2581                          * immediately. */
2582                         success = imapx_command_idle_stop (
2583                                 is, stream, cancellable, error);
2584                         if (!success) {
2585                                 IDLE_UNLOCK (is->idle);
2586                                 return FALSE;
2587                         }
2588                         is->idle->state = IMAPX_IDLE_OFF;
2589                 } else {
2590                         c (
2591                                 is->tagprefix, "idle starts in wrong state %d\n",
2592                                 is->idle->state);
2593                 }
2594                 IDLE_UNLOCK (is->idle);
2595
2596                 QUEUE_LOCK (is);
2597                 is->literal = NULL;
2598                 success = imapx_command_start_next (is, cancellable, error);
2599                 QUEUE_UNLOCK (is);
2600
2601                 return success;
2602         }
2603
2604         ic = is->literal;
2605         if (!litplus) {
2606                 if (ic == NULL) {
2607                         camel_imapx_stream_skip (stream, cancellable, error);
2608                         c (is->tagprefix, "got continuation response with no outstanding continuation requests?\n");
2609                         return TRUE;
2610                 }
2611                 c (is->tagprefix, "got continuation response for data\n");
2612         } else {
2613                 c (is->tagprefix, "sending LITERAL+ continuation\n");
2614         }
2615
2616         link = ic->current_part;
2617         g_return_val_if_fail (link != NULL, FALSE);
2618         cp = (CamelIMAPXCommandPart *) link->data;
2619
2620         switch (cp->type & CAMEL_IMAPX_COMMAND_MASK) {
2621         case CAMEL_IMAPX_COMMAND_DATAWRAPPER:
2622                 c (is->tagprefix, "writing data wrapper to literal\n");
2623                 n_bytes_written = camel_data_wrapper_write_to_stream_sync (
2624                         CAMEL_DATA_WRAPPER (cp->ob),
2625                         CAMEL_STREAM (stream),
2626                         cancellable, error);
2627                 if (n_bytes_written < 0)
2628                         return FALSE;
2629                 break;
2630         case CAMEL_IMAPX_COMMAND_STREAM:
2631                 c (is->tagprefix, "writing stream to literal\n");
2632                 n_bytes_written = camel_stream_write_to_stream (
2633                         CAMEL_STREAM (cp->ob),
2634                         CAMEL_STREAM (stream),
2635                         cancellable, error);
2636                 if (n_bytes_written < 0)
2637                         return FALSE;
2638                 break;
2639         case CAMEL_IMAPX_COMMAND_AUTH: {
2640                 gchar *resp;
2641                 guchar *token;
2642
2643                 if (camel_imapx_stream_text (stream, &token, cancellable, error))
2644                         return FALSE;
2645
2646                 resp = camel_sasl_challenge_base64_sync (
2647                         (CamelSasl *) cp->ob, (const gchar *) token,
2648                         cancellable, error);
2649                 g_free (token);
2650                 if (resp == NULL)
2651                         return FALSE;
2652                 c (is->tagprefix, "got auth continuation, feeding token '%s' back to auth mech\n", resp);
2653
2654                 n_bytes_written = camel_stream_write (
2655                         CAMEL_STREAM (stream),
2656                         resp, strlen (resp),
2657                         cancellable, error);
2658                 g_free (resp);
2659
2660                 if (n_bytes_written < 0)
2661                         return FALSE;
2662
2663                 /* we want to keep getting called until we get a status reponse from the server
2664                  * ignore what sasl tells us */
2665                 newliteral = ic;
2666                 /* We already ate the end of the input stream line */
2667                 goto noskip;
2668                 break; }
2669         case CAMEL_IMAPX_COMMAND_FILE: {
2670                 CamelStream *file;
2671
2672                 c (is->tagprefix, "writing file '%s' to literal\n", (gchar *) cp->ob);
2673
2674                 // FIXME: errors
2675                 if (cp->ob && (file = camel_stream_fs_new_with_name (cp->ob, O_RDONLY, 0, NULL))) {
2676                         n_bytes_written = camel_stream_write_to_stream (
2677                                 file, CAMEL_STREAM (stream),
2678                                 cancellable, error);
2679                         g_object_unref (file);
2680
2681                         if (n_bytes_written < 0)
2682                                 return FALSE;
2683                 } else if (cp->ob_size > 0) {
2684                         // Server is expecting data ... ummm, send it zeros?  abort?
2685                 }
2686                 break; }
2687         case CAMEL_IMAPX_COMMAND_STRING:
2688                 n_bytes_written = camel_stream_write (
2689                         CAMEL_STREAM (stream),
2690                         cp->ob, cp->ob_size,
2691                         cancellable, error);
2692                 if (n_bytes_written < 0)
2693                         return FALSE;
2694                 break;
2695         default:
2696                 /* should we just ignore? */
2697                 is->literal = NULL;
2698                 g_set_error (
2699                         error, CAMEL_IMAPX_ERROR, 1,
2700                         "continuation response for non-continuation request");
2701                 return FALSE;
2702         }
2703
2704         if (!litplus) {
2705                 if (camel_imapx_stream_skip (stream, cancellable, error) == -1)
2706                         return FALSE;
2707         }
2708
2709 noskip:
2710         link = g_list_next (link);
2711         if (link != NULL) {
2712                 ic->current_part = link;
2713                 cp = (CamelIMAPXCommandPart *) link->data;
2714
2715                 c (is->tagprefix, "next part of command \"%c%05u: %s\"\n", is->tagprefix, ic->tag, cp->data);
2716
2717                 n_bytes_written = camel_stream_write_string (
2718                         CAMEL_STREAM (stream), cp->data, cancellable, error);
2719                 if (n_bytes_written < 0)
2720                         return FALSE;
2721
2722                 if (cp->type & (CAMEL_IMAPX_COMMAND_CONTINUATION | CAMEL_IMAPX_COMMAND_LITERAL_PLUS)) {
2723                         newliteral = ic;
2724                 } else {
2725                         g_assert (g_list_next (link) == NULL);
2726                 }
2727         } else {
2728                 c (is->tagprefix, "%p: queueing continuation\n", ic);
2729         }
2730
2731         n_bytes_written = camel_stream_write_string (
2732                 CAMEL_STREAM (stream), "\r\n", cancellable, error);
2733         if (n_bytes_written < 0)
2734                 return FALSE;
2735
2736         QUEUE_LOCK (is);
2737         is->literal = newliteral;
2738
2739         if (!litplus)
2740                 success = imapx_command_start_next (is, cancellable, error);
2741         QUEUE_UNLOCK (is);
2742
2743         return success;
2744 }
2745
2746 /* handle a completion line */
2747 static gboolean
2748 imapx_completion (CamelIMAPXServer *is,
2749                   CamelIMAPXStream *stream,
2750                   guchar *token,
2751                   gint len,
2752                   GCancellable *cancellable,
2753                   GError **error)
2754 {
2755         CamelIMAPXCommand *ic;
2756         gboolean success;
2757         guint tag;
2758
2759         /* Given "A0001 ...", 'A' = tag prefix, '0001' = tag. */
2760
2761         if (token[0] != is->tagprefix) {
2762                 g_set_error (
2763                         error, CAMEL_IMAPX_ERROR, 1,
2764                         "Server sent unexpected response: %s", token);
2765                 return FALSE;
2766         }
2767
2768         tag = strtoul ((gchar *) token + 1, NULL, 10);
2769
2770         if ((ic = imapx_find_command_tag (is, tag)) == NULL) {
2771                 g_set_error (
2772                         error, CAMEL_IMAPX_ERROR, 1,
2773                         "got response tag unexpectedly: %s", token);
2774                 return FALSE;
2775         }
2776
2777         c (is->tagprefix, "Got completion response for command %05u '%s'\n", ic->tag, ic->name);
2778
2779         if (camel_folder_change_info_changed (is->changes)) {
2780                 CamelFolder *folder;
2781
2782                 folder = g_weak_ref_get (&is->select_folder);
2783                 g_return_val_if_fail (folder != NULL, FALSE);
2784
2785                 camel_folder_summary_save_to_db (folder->summary, NULL);
2786
2787                 g_list_free_full (is->expunged, (GDestroyNotify) g_free);
2788                 is->expunged = NULL;
2789
2790                 imapx_update_store_summary (folder);
2791                 camel_folder_changed (folder, is->changes);
2792                 camel_folder_change_info_clear (is->changes);
2793
2794                 g_object_unref (folder);
2795         }
2796
2797         QUEUE_LOCK (is);
2798
2799         camel_imapx_command_ref (ic);
2800         camel_imapx_command_queue_remove (is->active, ic);
2801         camel_imapx_command_queue_push_tail (is->done, ic);
2802         camel_imapx_command_unref (ic);
2803
2804         if (is->literal == ic)
2805                 is->literal = NULL;
2806
2807         if (g_list_next (ic->current_part) != NULL) {
2808                 QUEUE_UNLOCK (is);
2809                 g_set_error (
2810                         error, CAMEL_IMAPX_ERROR, 1,
2811                         "command still has unsent parts? %s", ic->name);
2812                 return FALSE;
2813         }
2814
2815         camel_imapx_command_queue_remove (is->done, ic);
2816
2817         QUEUE_UNLOCK (is);
2818
2819         ic->status = imapx_parse_status (stream, cancellable, error);
2820
2821         if (ic->status == NULL)
2822                 return FALSE;
2823
2824         if (ic->complete != NULL)
2825                 if (!ic->complete (is, ic, cancellable, error))
2826                         return FALSE;
2827
2828         QUEUE_LOCK (is);
2829         success = imapx_command_start_next (is, cancellable, error);
2830         QUEUE_UNLOCK (is);
2831
2832         return success;
2833 }
2834
2835 static gboolean
2836 imapx_step (CamelIMAPXServer *is,
2837             GCancellable *cancellable,
2838             GError **error)
2839 {
2840         CamelIMAPXStream *stream;
2841         guint len;
2842         guchar *token;
2843         gint tok;
2844         gboolean success = FALSE;
2845
2846         stream = camel_imapx_server_ref_stream (is);
2847
2848         // poll ?  wait for other stuff? loop?
2849         tok = camel_imapx_stream_token (
2850                 stream, &token, &len, cancellable, error);
2851
2852         switch (tok) {
2853                 case IMAPX_TOK_PROTOCOL:
2854                 case IMAPX_TOK_ERROR:
2855                         /* GError is already set. */
2856                         break;
2857                 case '*':
2858                         success = imapx_untagged (
2859                                 is, stream, cancellable, error);
2860                         break;
2861                 case IMAPX_TOK_TOKEN:
2862                         success = imapx_completion (
2863                                 is, stream, token, len, cancellable, error);
2864                         break;
2865                 case '+':
2866                         success = imapx_continuation (
2867                                 is, stream, FALSE, cancellable, error);
2868                         break;
2869                 default:
2870                         g_set_error (
2871                                 error, CAMEL_IMAPX_ERROR, 1,
2872                                 "unexpected server response:");
2873                         break;
2874         }
2875
2876         g_object_unref (stream);
2877
2878         return success;
2879 }
2880
2881 /* Used to run 1 command synchronously,
2882  * use for capa, login, and namespaces only. */
2883 static gboolean
2884 imapx_command_run (CamelIMAPXServer *is,
2885                    CamelIMAPXCommand *ic,
2886                    GCancellable *cancellable,
2887                    GError **error)
2888 /* throws IO,PARSE exception */
2889 {
2890         gboolean success = TRUE;
2891
2892         camel_imapx_command_close (ic);
2893
2894         QUEUE_LOCK (is);
2895         imapx_command_start (is, ic, cancellable, error);
2896         QUEUE_UNLOCK (is);
2897
2898         while (success && ic->status == NULL)
2899                 success = imapx_step (is, cancellable, error);
2900
2901         if (is->literal == ic)
2902                 is->literal = NULL;
2903
2904         QUEUE_LOCK (is);
2905         camel_imapx_command_queue_remove (is->active, ic);
2906         QUEUE_UNLOCK (is);
2907
2908         return success;
2909 }
2910
2911 static gboolean
2912 imapx_command_complete (CamelIMAPXServer *is,
2913                         CamelIMAPXCommand *ic,
2914                         GCancellable *cancellable,
2915                         GError **error)
2916 {
2917         camel_imapx_command_done (ic);
2918         camel_imapx_command_unref (ic);
2919
2920         return TRUE;
2921 }
2922
2923 static void
2924 imapx_command_cancelled (GCancellable *cancellable,
2925                          CamelIMAPXCommand *ic)
2926 {
2927         /* Unblock imapx_command_run_sync() immediately.
2928          *
2929          * If camel_imapx_command_done() is called sometime later,
2930          * the GCond will broadcast but no one will be listening. */
2931
2932         camel_imapx_command_done (ic);
2933 }
2934
2935 /* The caller should free the command as well */
2936 static gboolean
2937 imapx_command_run_sync (CamelIMAPXServer *is,
2938                         CamelIMAPXCommand *ic,
2939                         GCancellable *cancellable,
2940                         GError **error)
2941 {
2942         guint cancel_id = 0;
2943         gboolean success;
2944
2945         /* FIXME The only caller of this function currently does not set
2946          *       a "complete" callback function, so we can get away with
2947          *       referencing the command here and dropping the reference
2948          *       in imapx_command_complete().  The queueing/dequeueing
2949          *       of these things is too complex for my little mind, so
2950          *       we may have to revisit the reference counting if this
2951          *       function gets another caller. */
2952
2953         g_warn_if_fail (ic->complete == NULL);
2954         ic->complete = imapx_command_complete;
2955
2956         if (G_IS_CANCELLABLE (cancellable))
2957                 cancel_id = g_cancellable_connect (
2958                         cancellable,
2959                         G_CALLBACK (imapx_command_cancelled),
2960                         camel_imapx_command_ref (ic),
2961                         (GDestroyNotify) camel_imapx_command_unref);
2962
2963         /* Unref'ed in imapx_command_complete(). */
2964         camel_imapx_command_ref (ic);
2965
2966         success = imapx_command_queue (is, ic, cancellable, error);
2967
2968         if (success)
2969                 camel_imapx_command_wait (ic);
2970
2971         if (cancel_id > 0)
2972                 g_cancellable_disconnect (cancellable, cancel_id);
2973
2974         if (camel_imapx_command_set_error_if_failed (ic, error))
2975                 return FALSE;
2976
2977         return success;
2978 }
2979
2980 static gboolean
2981 imapx_register_job (CamelIMAPXServer *is,
2982                     CamelIMAPXJob *job,
2983                     GError **error)
2984 {
2985         if (is->state >= IMAPX_INITIALISED) {
2986                 QUEUE_LOCK (is);
2987                 g_queue_push_head (&is->jobs, camel_imapx_job_ref (job));
2988                 QUEUE_UNLOCK (is);
2989
2990         } else {
2991                 e (is->tagprefix, "NO connection yet, maybe user cancelled jobs earlier ?");
2992                 g_set_error (
2993                         error, CAMEL_SERVICE_ERROR,
2994                         CAMEL_SERVICE_ERROR_NOT_CONNECTED,
2995                         _("Not authenticated"));
2996                 return FALSE;
2997         }
2998
2999         return TRUE;
3000 }
3001
3002 static void
3003 imapx_unregister_job (CamelIMAPXServer *is,
3004                       CamelIMAPXJob *job)
3005 {
3006         if (!job->noreply)
3007                 camel_imapx_job_done (job);
3008
3009         QUEUE_LOCK (is);
3010         if (g_queue_remove (&is->jobs, job))
3011                 camel_imapx_job_unref (job);
3012         QUEUE_UNLOCK (is);
3013 }
3014
3015 static gboolean
3016 imapx_submit_job (CamelIMAPXServer *is,
3017                   CamelIMAPXJob *job,
3018                   GError **error)
3019 {
3020         if (!imapx_register_job (is, job, error))
3021                 return FALSE;
3022
3023         return camel_imapx_job_run (job, is, error);
3024 }
3025
3026 /* ********************************************************************** */
3027 // IDLE support
3028
3029 /*TODO handle negative cases sanely */
3030 static gboolean
3031 imapx_command_idle_stop (CamelIMAPXServer *is,
3032                          CamelIMAPXStream *stream,
3033                          GCancellable *cancellable,
3034                          GError **error)
3035 {
3036         gboolean success;
3037
3038         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
3039         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (stream), FALSE);
3040
3041         success = (camel_stream_write_string (
3042                 CAMEL_STREAM (stream),
3043                 "DONE\r\n", cancellable, error) != -1);
3044
3045         if (!success) {
3046                 g_prefix_error (error, "Unable to issue DONE: ");
3047                 c (is->tagprefix, "Failed to issue DONE to terminate IDLE\n");
3048                 is->state = IMAPX_SHUTDOWN;
3049                 is->parser_quit = TRUE;
3050                 if (is->cancellable)
3051                         g_cancellable_cancel (is->cancellable);
3052         }
3053
3054         return success;
3055 }
3056
3057 static gboolean
3058 imapx_command_idle_done (CamelIMAPXServer *is,
3059                          CamelIMAPXCommand *ic,
3060                          GCancellable *cancellable,
3061                          GError **error)
3062 {
3063         CamelIMAPXIdle *idle = is->idle;
3064         CamelIMAPXJob *job;
3065         gboolean success = TRUE;
3066
3067         job = camel_imapx_command_get_job (ic);
3068         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3069
3070         if (camel_imapx_command_set_error_if_failed (ic, error)) {
3071                 g_prefix_error (
3072                         error, "%s: ",
3073                         _("Error performing IDLE"));
3074                 success = FALSE;
3075         }
3076
3077         IDLE_LOCK (idle);
3078         idle->state = IMAPX_IDLE_OFF;
3079         IDLE_UNLOCK (idle);
3080
3081         imapx_unregister_job (is, job);
3082         camel_imapx_command_unref (ic);
3083
3084         return success;
3085 }
3086
3087 static gboolean
3088 imapx_job_idle_start (CamelIMAPXJob *job,
3089                       CamelIMAPXServer *is,
3090                       GCancellable *cancellable,
3091                       GError **error)
3092 {
3093         CamelFolder *folder;
3094         CamelIMAPXCommand *ic;
3095         CamelIMAPXCommandPart *cp;
3096         gboolean success = TRUE;
3097
3098         folder = camel_imapx_job_ref_folder (job);
3099         g_return_val_if_fail (folder != NULL, FALSE);
3100
3101         ic = camel_imapx_command_new (
3102                 is, "IDLE", folder, "IDLE");
3103         camel_imapx_command_set_job (ic, job);
3104         ic->pri = job->pri;
3105         ic->complete = imapx_command_idle_done;
3106
3107         camel_imapx_command_close (ic);
3108         cp = g_queue_peek_head (&ic->parts);
3109         cp->type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
3110
3111         QUEUE_LOCK (is);
3112         IDLE_LOCK (is->idle);
3113         /* Don't issue it if the idle was cancelled already */
3114         if (is->idle->state == IMAPX_IDLE_PENDING) {
3115                 is->idle->state = IMAPX_IDLE_ISSUED;
3116                 success = imapx_command_start (is, ic, cancellable, error);
3117         } else {
3118                 imapx_unregister_job (is, job);
3119                 camel_imapx_command_unref (ic);
3120         }
3121         IDLE_UNLOCK (is->idle);
3122         QUEUE_UNLOCK (is);
3123
3124         g_object_unref (folder);
3125
3126         return success;
3127 }
3128
3129 static gboolean
3130 camel_imapx_server_idle (CamelIMAPXServer *is,
3131                          CamelFolder *folder,
3132                          GCancellable *cancellable,
3133                          GError **error)
3134 {
3135         CamelIMAPXJob *job;
3136         gboolean success;
3137
3138         job = camel_imapx_job_new (cancellable);
3139         job->type = IMAPX_JOB_IDLE;
3140         job->start = imapx_job_idle_start;
3141
3142         camel_imapx_job_set_folder (job, folder);
3143
3144         success = imapx_submit_job (is, job, error);
3145
3146         camel_imapx_job_unref (job);
3147
3148         return success;
3149 }
3150
3151 static gboolean
3152 imapx_job_fetch_new_messages_matches (CamelIMAPXJob *job,
3153                                       CamelFolder *folder,
3154                                       const gchar *uid)
3155 {
3156         return camel_imapx_job_has_folder (job, folder);
3157 }
3158
3159 static gboolean
3160 imapx_server_fetch_new_messages (CamelIMAPXServer *is,
3161                                  CamelFolder *folder,
3162                                  gboolean async,
3163                                  gboolean update_unseen,
3164                                  GCancellable *cancellable,
3165                                  GError **error)
3166 {
3167         CamelIMAPXJob *job;
3168         RefreshInfoData *data;
3169         gboolean success;
3170
3171         data = g_slice_new0 (RefreshInfoData);
3172         data->changes = camel_folder_change_info_new ();
3173         data->update_unseen = update_unseen;
3174         data->fetch_msg_limit = -1;
3175
3176         job = camel_imapx_job_new (cancellable);
3177         job->type = IMAPX_JOB_FETCH_NEW_MESSAGES;
3178         job->start = imapx_job_fetch_new_messages_start;
3179         job->matches = imapx_job_fetch_new_messages_matches;
3180         job->noreply = async;
3181
3182         camel_imapx_job_set_folder (job, folder);
3183
3184         camel_imapx_job_set_data (
3185                 job, data, (GDestroyNotify) refresh_info_data_free);
3186
3187         success = imapx_submit_job (is, job, error);
3188
3189         camel_imapx_job_unref (job);
3190
3191         return success;
3192 }
3193
3194 static gpointer
3195 imapx_idle_thread (gpointer data)
3196 {
3197         CamelIMAPXServer *is = (CamelIMAPXServer *) data;
3198         GError *local_error = NULL;
3199
3200         while (TRUE) {
3201                 g_mutex_lock (&is->idle->start_watch_mutex);
3202                 is->idle->start_watch_is_set = FALSE;
3203                 g_mutex_unlock (&is->idle->start_watch_mutex);
3204
3205                 IDLE_LOCK (is->idle);
3206
3207                 while (TRUE) {
3208                         CamelFolder *folder;
3209                         gboolean new_messages_on_server;
3210                         time_t dwelled;
3211
3212                         if (is->idle->state != IMAPX_IDLE_PENDING)
3213                                 break;
3214
3215                         if (is->idle->idle_exit)
3216                                 break;
3217
3218                         folder = g_weak_ref_get (&is->select_folder);
3219                         if (folder == NULL)
3220                                 break;
3221
3222                         dwelled = time (NULL) - is->idle->started;
3223                         if (dwelled < IMAPX_IDLE_DWELL_TIME) {
3224                                 gulong seconds;
3225
3226                                 g_object_unref (folder);
3227
3228                                 IDLE_UNLOCK (is->idle);
3229                                 seconds = IMAPX_IDLE_DWELL_TIME - dwelled;
3230                                 g_usleep (seconds * G_USEC_PER_SEC);
3231                                 IDLE_LOCK (is->idle);
3232
3233                                 continue;
3234                         }
3235
3236                         IDLE_UNLOCK (is->idle);
3237
3238                         camel_imapx_server_idle (
3239                                 is, folder, is->cancellable, &local_error);
3240
3241                         new_messages_on_server =
3242                                 CAMEL_IMAPX_FOLDER (folder)->exists_on_server >
3243                                 camel_folder_summary_count (folder->summary);
3244
3245                         if (local_error == NULL &&
3246                             new_messages_on_server &&
3247                             imapx_is_command_queue_empty (is)) {
3248                                 imapx_server_fetch_new_messages (
3249                                         is, folder, TRUE, TRUE,
3250                                         is->cancellable, &local_error);
3251                         }
3252
3253                         if (local_error != NULL) {
3254                                 e (is->tagprefix, "Caught exception in idle thread:  %s \n", local_error->message);
3255                                 /* No way to asyncronously notify UI ? */
3256                                 g_clear_error (&local_error);
3257                         }
3258
3259                         g_object_unref (folder);
3260
3261                         IDLE_LOCK (is->idle);
3262                 }
3263
3264                 IDLE_UNLOCK (is->idle);
3265
3266                 g_mutex_lock (&is->idle->start_watch_mutex);
3267                 while (!is->idle->start_watch_is_set)
3268                         g_cond_wait (
3269                                 &is->idle->start_watch_cond,
3270                                 &is->idle->start_watch_mutex);
3271                 g_mutex_unlock (&is->idle->start_watch_mutex);
3272
3273                 if (is->idle->idle_exit)
3274                         break;
3275         }
3276
3277         g_clear_error (&local_error);
3278         is->idle->idle_thread = NULL;
3279         return NULL;
3280 }
3281
3282 static CamelIMAPXIdleStopResult
3283 imapx_stop_idle (CamelIMAPXServer *is,
3284                  CamelIMAPXStream *stream,
3285                  GCancellable *cancellable,
3286                  GError **error)
3287 {
3288         CamelIMAPXIdleStopResult result = IMAPX_IDLE_STOP_NOOP;
3289         gboolean success;
3290         time_t now;
3291
3292         time (&now);
3293
3294         IDLE_LOCK (is->idle);
3295
3296         switch (is->idle->state) {
3297                 case IMAPX_IDLE_ISSUED:
3298                         is->idle->state = IMAPX_IDLE_CANCEL;
3299                         /* fall through */
3300
3301                 case IMAPX_IDLE_CANCEL:
3302                         result = IMAPX_IDLE_STOP_SUCCESS;
3303                         break;
3304
3305                 case IMAPX_IDLE_STARTED:
3306                         success = imapx_command_idle_stop (
3307                                 is, stream, cancellable, error);
3308                         if (success) {
3309                                 result = IMAPX_IDLE_STOP_SUCCESS;
3310                         } else {
3311                                 result = IMAPX_IDLE_STOP_ERROR;
3312                                 goto exit;
3313                         }
3314
3315                         c (
3316                                 is->tagprefix,
3317                                 "Stopping idle after %ld seconds\n",
3318                                 (glong) (now - is->idle->started));
3319                         /* fall through */
3320
3321                 case IMAPX_IDLE_PENDING:
3322                         is->idle->state = IMAPX_IDLE_OFF;
3323                         /* fall through */
3324
3325                 case IMAPX_IDLE_OFF:
3326                         break;
3327         }
3328
3329 exit:
3330         IDLE_UNLOCK (is->idle);
3331
3332         return result;
3333 }
3334
3335 static void
3336 imapx_init_idle (CamelIMAPXServer *is)
3337 {
3338         is->idle = g_new0 (CamelIMAPXIdle, 1);
3339         g_mutex_init (&is->idle->idle_lock);
3340
3341         g_cond_init (&is->idle->start_watch_cond);
3342         g_mutex_init (&is->idle->start_watch_mutex);
3343 }
3344
3345 static void
3346 imapx_exit_idle (CamelIMAPXServer *is)
3347 {
3348         CamelIMAPXIdle *idle = is->idle;
3349         GThread *thread = NULL;
3350
3351         if (!idle)
3352                 return;
3353
3354         IDLE_LOCK (idle);
3355
3356         if (idle->idle_thread) {
3357                 idle->idle_exit = TRUE;
3358
3359                 g_mutex_lock (&idle->start_watch_mutex);
3360                 idle->start_watch_is_set = TRUE;
3361                 g_cond_broadcast (&idle->start_watch_cond);
3362                 g_mutex_unlock (&idle->start_watch_mutex);
3363
3364                 thread = idle->idle_thread;
3365                 idle->idle_thread = NULL;
3366         }
3367
3368         idle->idle_thread = NULL;
3369         IDLE_UNLOCK (idle);
3370
3371         if (thread)
3372                 g_thread_join (thread);
3373
3374         g_mutex_clear (&idle->idle_lock);
3375         g_cond_clear (&idle->start_watch_cond);
3376         g_mutex_clear (&idle->start_watch_mutex);
3377
3378         g_free (is->idle);
3379         is->idle = NULL;
3380 }
3381
3382 static void
3383 imapx_start_idle (CamelIMAPXServer *is)
3384 {
3385         CamelIMAPXIdle *idle = is->idle;
3386
3387         if (camel_application_is_exiting)
3388                 return;
3389
3390         IDLE_LOCK (idle);
3391
3392         g_assert (idle->state == IMAPX_IDLE_OFF);
3393         time (&idle->started);
3394         idle->state = IMAPX_IDLE_PENDING;
3395
3396         if (!idle->idle_thread) {
3397                 idle->start_watch_is_set = FALSE;
3398
3399                 idle->idle_thread = g_thread_new (
3400                         NULL, (GThreadFunc) imapx_idle_thread, is);
3401         } else {
3402                 g_mutex_lock (&idle->start_watch_mutex);
3403                 idle->start_watch_is_set = TRUE;
3404                 g_cond_broadcast (&idle->start_watch_cond);
3405                 g_mutex_unlock (&idle->start_watch_mutex);
3406         }
3407
3408         IDLE_UNLOCK (idle);
3409 }
3410
3411 static gboolean
3412 imapx_in_idle (CamelIMAPXServer *is)
3413 {
3414         gboolean ret = FALSE;
3415         CamelIMAPXIdle *idle = is->idle;
3416
3417         IDLE_LOCK (idle);
3418         ret = (idle->state > IMAPX_IDLE_OFF);
3419         IDLE_UNLOCK (idle);
3420
3421         return ret;
3422 }
3423
3424 static gboolean
3425 imapx_idle_supported (CamelIMAPXServer *is)
3426 {
3427         return (is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_IDLE) != 0 && is->use_idle);
3428 }
3429
3430 // end IDLE
3431 /* ********************************************************************** */
3432 static gboolean
3433 imapx_command_select_done (CamelIMAPXServer *is,
3434                            CamelIMAPXCommand *ic,
3435                            GCancellable *cancellable,
3436                            GError **error)
3437 {
3438         const gchar *selected_folder = NULL;
3439         gboolean success = TRUE;
3440         GError *local_error = NULL;
3441
3442         if (camel_imapx_command_set_error_if_failed (ic, &local_error)) {
3443                 GQueue failed = G_QUEUE_INIT;
3444                 GQueue trash = G_QUEUE_INIT;
3445                 CamelFolder *folder;
3446                 GList *link;
3447
3448                 c (is->tagprefix, "Select failed\n");
3449
3450                 g_mutex_lock (&is->select_lock);
3451                 folder = g_weak_ref_get (&is->select_pending);
3452                 g_weak_ref_set (&is->select_folder, NULL);
3453                 g_weak_ref_set (&is->select_pending, NULL);
3454                 is->state = IMAPX_INITIALISED;
3455                 g_mutex_unlock (&is->select_lock);
3456
3457                 QUEUE_LOCK (is);
3458
3459                 if (folder != NULL) {
3460                         GList *head = camel_imapx_command_queue_peek_head_link (is->queue);
3461
3462                         for (link = head; link != NULL; link = g_list_next (link)) {
3463                                 CamelIMAPXCommand *cw = link->data;
3464
3465                                 if (cw->select && cw->select == folder) {
3466                                         c (
3467                                                 is->tagprefix, "Cancelling command '%s'(%p) for folder '%s'\n",
3468                                                 cw->name, cw, camel_folder_get_full_name (cw->select));
3469                                         g_queue_push_tail (&trash, link);
3470                                 }
3471                         }
3472
3473                         g_object_unref (folder);
3474                 }
3475
3476                 while ((link = g_queue_pop_head (&trash)) != NULL) {
3477                         CamelIMAPXCommand *cw = link->data;
3478                         camel_imapx_command_queue_delete_link (is->queue, link);
3479                         g_queue_push_tail (&failed, cw);
3480                 }
3481
3482                 QUEUE_UNLOCK (is);
3483
3484                 while (!g_queue_is_empty (&failed)) {
3485                         CamelIMAPXCommand *cw;
3486                         CamelIMAPXJob *job;
3487
3488                         cw = g_queue_pop_head (&failed);
3489                         job = camel_imapx_command_get_job (cw);
3490
3491                         if (!CAMEL_IS_IMAPX_JOB (job)) {
3492                                 g_warn_if_reached ();
3493                                 continue;
3494                         }
3495
3496                         camel_imapx_job_cancel (job);
3497
3498                         if (ic->status)
3499                                 cw->status = imapx_copy_status (ic->status);
3500
3501                         cw->complete (is, cw, NULL, NULL);
3502                 }
3503
3504                 g_propagate_error (error, local_error);
3505                 success = FALSE;
3506
3507         } else {
3508                 CamelFolder *folder;
3509                 CamelIMAPXFolder *ifolder;
3510
3511                 c (is->tagprefix, "Select ok!\n");
3512
3513                 g_mutex_lock (&is->select_lock);
3514                 folder = g_weak_ref_get (&is->select_pending);
3515                 g_weak_ref_set (&is->select_folder, folder);
3516                 g_weak_ref_set (&is->select_pending, NULL);
3517                 is->state = IMAPX_SELECTED;
3518                 g_mutex_unlock (&is->select_lock);
3519
3520                 /* We should have a strong reference
3521                  * on the newly-selected CamelFolder. */
3522                 g_return_val_if_fail (folder != NULL, FALSE);
3523                 ifolder = CAMEL_IMAPX_FOLDER (folder);
3524
3525                 ifolder->exists_on_server = is->exists;
3526                 ifolder->modseq_on_server = is->highestmodseq;
3527                 if (ifolder->uidnext_on_server < is->uidnext) {
3528                         /* We don't want to fetch new messages if the command we selected this
3529                          * folder for is *already* fetching all messages (i.e. scan_changes).
3530                          * Bug #667725. */
3531                         CamelIMAPXJob *job = imapx_is_job_in_queue (
3532                                 is, folder, IMAPX_JOB_REFRESH_INFO, NULL);
3533                         if (job) {
3534                                 RefreshInfoData *data = camel_imapx_job_get_data (job);
3535
3536                                 if (data->scan_changes) {
3537                                         c (is->tagprefix, "Will not fetch_new_messages when already in scan_changes\n");
3538                                         goto no_fetch_new;
3539                                 }
3540                         }
3541                         imapx_server_fetch_new_messages (is, folder, TRUE, TRUE, NULL, NULL);
3542                         /* We don't do this right now because we want the new messages to
3543                          * update the unseen count. */
3544                         //ifolder->uidnext_on_server = is->uidnext;
3545                 no_fetch_new:
3546                         ;
3547                 }
3548                 ifolder->uidvalidity_on_server = is->uidvalidity;
3549                 selected_folder = camel_folder_get_full_name (folder);
3550
3551                 if (is->uidvalidity && is->uidvalidity != ((CamelIMAPXSummary *) folder->summary)->validity)
3552                         invalidate_local_cache (ifolder, is->uidvalidity);
3553
3554 #if 0  /* see comment for disabled bits in imapx_job_refresh_info_start() */
3555                 /* This should trigger a new messages scan */
3556                 if (is->exists != folder->summary->root_view->total_count)
3557                         g_warning (
3558                                 "exists is %d our summary is %d and summary exists is %d\n", is->exists,
3559                                 folder->summary->root_view->total_count,
3560                                 ((CamelIMAPXSummary *) folder->summary)->exists);
3561 #endif
3562
3563                 g_object_unref (folder);
3564         }
3565
3566         camel_imapx_command_unref (ic);
3567
3568         g_signal_emit (is, signals[SELECT_CHANGED], 0, selected_folder);
3569
3570         return success;
3571 }
3572
3573 /* Should have a queue lock. TODO Change the way select is written */
3574 static gboolean
3575 imapx_select (CamelIMAPXServer *is,
3576               CamelFolder *folder,
3577               gboolean forced,
3578               GCancellable *cancellable,
3579               GError **error)
3580 {
3581         CamelIMAPXCommand *ic;
3582         CamelFolder *select_folder;
3583         CamelFolder *select_pending;
3584         gboolean nothing_to_do = FALSE;
3585
3586         /* Select is complicated by the fact we may have commands
3587          * active on the server for a different selection.
3588          *
3589          * So this waits for any commands to complete, selects the
3590          * new folder, and halts the queuing of any new commands.
3591          * It is assumed whomever called is us about to issue
3592          * a high-priority command anyway */
3593
3594         /* TODO check locking here, pending_select will do
3595          * most of the work for normal commands, but not
3596          * for another select */
3597
3598         g_mutex_lock (&is->select_lock);
3599
3600         select_folder = g_weak_ref_get (&is->select_folder);
3601         select_pending = g_weak_ref_get (&is->select_pending);
3602
3603         if (select_pending != NULL) {
3604                 nothing_to_do = TRUE;
3605         } else if (select_folder == folder && !forced) {
3606                 nothing_to_do = TRUE;
3607         } else if (!camel_imapx_command_queue_is_empty (is->active)) {
3608                 nothing_to_do = TRUE;
3609         } else {
3610                 g_weak_ref_set (&is->select_pending, folder);
3611
3612                 if (select_folder != NULL) {
3613                         g_weak_ref_set (&is->select_folder, NULL);
3614                 } else {
3615                         /* If no folder was selected, we won't get a
3616                          * [CLOSED] status so just point select_folder
3617                          * at the newly-selected folder immediately. */
3618                         g_weak_ref_set (&is->select_folder, folder);
3619                 }
3620
3621                 is->uidvalidity = 0;
3622                 is->unseen = 0;
3623                 is->highestmodseq = 0;
3624                 is->permanentflags = 0;
3625                 is->exists = 0;
3626                 is->recent = 0;
3627                 is->mode = 0;
3628                 is->uidnext = 0;
3629
3630                 /* Hrm, what about reconnecting? */
3631                 is->state = IMAPX_INITIALISED;
3632         }
3633
3634         g_clear_object (&select_folder);
3635         g_clear_object (&select_pending);
3636
3637         g_mutex_unlock (&is->select_lock);
3638
3639         if (nothing_to_do)
3640                 return TRUE;
3641
3642         ic = camel_imapx_command_new (
3643                 is, "SELECT", NULL, "SELECT %f", folder);
3644
3645         if (is->use_qresync) {
3646                 CamelIMAPXSummary *isum = (CamelIMAPXSummary *) folder->summary;
3647                 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
3648                 gint total = camel_folder_summary_count (folder->summary);
3649                 gchar *firstuid, *lastuid;
3650
3651                 if (total && isum->modseq && ifolder->uidvalidity_on_server) {
3652
3653                         firstuid = imapx_get_uid_from_index (folder->summary, 0);
3654                         lastuid = imapx_get_uid_from_index (folder->summary, total - 1);
3655
3656                         c (
3657                                 is->tagprefix, "SELECT QRESYNC %" G_GUINT64_FORMAT
3658                                 " %" G_GUINT64_FORMAT "\n",
3659                                 ifolder->uidvalidity_on_server, isum->modseq);
3660
3661                         camel_imapx_command_add (
3662                                 ic, " (QRESYNC (%"
3663                                 G_GUINT64_FORMAT " %"
3664                                 G_GUINT64_FORMAT " %s:%s",
3665                                 ifolder->uidvalidity_on_server,
3666                                 isum->modseq,
3667                                 firstuid, lastuid);
3668
3669                         g_free (firstuid);
3670                         g_free (lastuid);
3671
3672                         if (total > 10) {
3673                                 gint i;
3674                                 GString *seqs, *uids;
3675
3676                                 seqs = g_string_new (" ");
3677                                 uids = g_string_new (")");
3678
3679                                 /* Include some seq/uid pairs to avoid a huge VANISHED list
3680                                  * (see RFC5162 Â§3.1). Work backwards exponentially from the
3681                                  * end of the mailbox, starting with the message 9 from the
3682                                  * end, then 27 from the end, then 81 from the end... */
3683                                 i = 3;
3684                                 do {
3685                                         gchar buf[10];
3686                                         gchar *uid;
3687                                         i *= 3;
3688                                         if (i > total)
3689                                                 i = total;
3690
3691                                         if (i != 9) { /* If not the first time */
3692                                                 g_string_prepend (seqs, ",");
3693                                                 g_string_prepend (uids, ",");
3694                                         }
3695
3696                                         /* IMAP sequence numbers are one higher than the corresponding
3697                                          * indices in our folder summary -- they start from one, while
3698                                          * the summary starts from zero. */
3699                                         sprintf (buf, "%d", total - i + 1);
3700                                         g_string_prepend (seqs, buf);
3701                                         uid = imapx_get_uid_from_index (folder->summary, total - i);
3702                                         g_string_prepend (uids, uid);
3703                                         g_free (uid);
3704                                 } while (i < total);
3705
3706                                 g_string_prepend (seqs, " (");
3707
3708                                 c (is->tagprefix, "adding QRESYNC seq/uidset %s%s\n", seqs->str, uids->str);
3709                                 camel_imapx_command_add (ic, seqs->str);
3710                                 camel_imapx_command_add (ic, uids->str);
3711
3712                                 g_string_free (seqs, TRUE);
3713                                 g_string_free (uids, TRUE);
3714
3715                         }
3716                         camel_imapx_command_add (ic, "))");
3717                 }
3718         }
3719
3720         ic->complete = imapx_command_select_done;
3721         imapx_command_start (is, ic, cancellable, error);
3722
3723         return TRUE;
3724 }
3725
3726 #ifndef G_OS_WIN32
3727
3728 /* Using custom commands to connect to IMAP servers is not supported on Win32 */
3729
3730 static CamelStream *
3731 connect_to_server_process (CamelIMAPXServer *is,
3732                            const gchar *cmd,
3733                            GError **error)
3734 {
3735         CamelNetworkSettings *network_settings;
3736         CamelProvider *provider;
3737         CamelSettings *settings;
3738         CamelStream *cmd_stream;
3739         CamelStream *imapx_stream;
3740         CamelIMAPXStore *store;
3741         CamelURL url;
3742         gint ret, i = 0;
3743         gchar *buf;
3744         gchar *cmd_copy;
3745         gchar *full_cmd;
3746         gchar *child_env[7];
3747         const gchar *password;
3748         gchar *host;
3749         gchar *user;
3750         guint16 port;
3751
3752         memset (&url, 0, sizeof (CamelURL));
3753
3754         store = camel_imapx_server_ref_store (is);
3755
3756         password = camel_service_get_password (CAMEL_SERVICE (store));
3757         provider = camel_service_get_provider (CAMEL_SERVICE (store));
3758         settings = camel_service_ref_settings (CAMEL_SERVICE (store));
3759
3760         network_settings = CAMEL_NETWORK_SETTINGS (settings);
3761         host = camel_network_settings_dup_host (network_settings);
3762         port = camel_network_settings_get_port (network_settings);
3763         user = camel_network_settings_dup_user (network_settings);
3764
3765         /* Put full details in the environment, in case the connection
3766          * program needs them */
3767         camel_url_set_protocol (&url, provider->protocol);
3768         camel_url_set_host (&url, host);
3769         camel_url_set_port (&url, port);
3770         camel_url_set_user (&url, user);
3771         buf = camel_url_to_string (&url, 0);
3772         child_env[i++] = g_strdup_printf ("URL=%s", buf);
3773         g_free (buf);
3774
3775         child_env[i++] = g_strdup_printf ("URLHOST=%s", host);
3776         if (port)
3777                 child_env[i++] = g_strdup_printf ("URLPORT=%u", port);
3778         if (user)
3779                 child_env[i++] = g_strdup_printf ("URLUSER=%s", user);
3780         if (password)
3781                 child_env[i++] = g_strdup_printf ("URLPASSWD=%s", password);
3782         child_env[i] = NULL;
3783
3784         g_object_unref (settings);
3785         g_object_unref (store);
3786
3787         /* Now do %h, %u, etc. substitution in cmd */
3788         buf = cmd_copy = g_strdup (cmd);
3789
3790         full_cmd = g_strdup ("");
3791
3792         for (;;) {
3793                 gchar *pc;
3794                 gchar *tmp;
3795                 const gchar *var;
3796                 gint len;
3797
3798                 pc = strchr (buf, '%');
3799         ignore:
3800                 if (!pc) {
3801                         tmp = g_strdup_printf ("%s%s", full_cmd, buf);
3802                         g_free (full_cmd);
3803                         full_cmd = tmp;
3804                         break;
3805                 }
3806
3807                 len = pc - buf;
3808
3809                 var = NULL;
3810
3811                 switch (pc[1]) {
3812                 case 'h':
3813                         var = host;
3814                         break;
3815                 case 'u':
3816                         var = user;
3817                         break;
3818                 }
3819                 if (!var) {
3820                         /* If there wasn't a valid %-code, with an actual
3821                          * variable to insert, pretend we didn't see the % */
3822                         pc = strchr (pc + 1, '%');
3823                         goto ignore;
3824                 }
3825                 tmp = g_strdup_printf ("%s%.*s%s", full_cmd, len, buf, var);
3826                 g_free (full_cmd);
3827                 full_cmd = tmp;
3828                 buf = pc + 2;
3829         }
3830
3831         g_free (cmd_copy);
3832
3833         g_free (host);
3834         g_free (user);
3835
3836         cmd_stream = camel_stream_process_new ();
3837
3838         ret = camel_stream_process_connect (
3839                 CAMEL_STREAM_PROCESS (cmd_stream),
3840                 full_cmd, (const gchar **) child_env, error);
3841
3842         while (i)
3843                 g_free (child_env[--i]);
3844
3845         if (ret == -1) {
3846                 g_object_unref (cmd_stream);
3847                 g_free (full_cmd);
3848                 return NULL;
3849         }
3850
3851         g_free (full_cmd);
3852
3853         imapx_stream = camel_imapx_stream_new (cmd_stream);
3854
3855         g_object_unref (cmd_stream);
3856
3857         /* Server takes ownership of the IMAPX stream. */
3858         g_mutex_lock (&is->priv->stream_lock);
3859         g_warn_if_fail (is->priv->stream == NULL);
3860         is->priv->stream = CAMEL_IMAPX_STREAM (imapx_stream);
3861         is->is_process_stream = TRUE;
3862         g_mutex_unlock (&is->priv->stream_lock);
3863
3864         g_object_notify (G_OBJECT (is), "stream");
3865
3866         return imapx_stream;
3867 }
3868 #endif /* G_OS_WIN32 */
3869
3870 gboolean
3871 imapx_connect_to_server (CamelIMAPXServer *is,
3872                          GCancellable *cancellable,
3873                          GError **error)
3874 {
3875         CamelNetworkSettings *network_settings;
3876         CamelNetworkSecurityMethod method;
3877         CamelStream *tcp_stream = NULL;
3878         CamelStream *imapx_stream = NULL;
3879         CamelSockOptData sockopt;
3880         CamelIMAPXStore *store;
3881         CamelSettings *settings;
3882         guint len;
3883         guchar *token;
3884         gint tok;
3885         CamelIMAPXCommand *ic;
3886         gboolean success = TRUE;
3887         gchar *host;
3888         GError *local_error = NULL;
3889
3890 #ifndef G_OS_WIN32
3891         gboolean use_shell_command;
3892         gchar *shell_command = NULL;
3893 #endif
3894
3895         store = camel_imapx_server_ref_store (is);
3896
3897         settings = camel_service_ref_settings (CAMEL_SERVICE (store));
3898
3899         network_settings = CAMEL_NETWORK_SETTINGS (settings);
3900         host = camel_network_settings_dup_host (network_settings);
3901         method = camel_network_settings_get_security_method (network_settings);
3902
3903 #ifndef G_OS_WIN32
3904         use_shell_command = camel_imapx_settings_get_use_shell_command (
3905                 CAMEL_IMAPX_SETTINGS (settings));
3906
3907         if (use_shell_command)
3908                 shell_command = camel_imapx_settings_dup_shell_command (
3909                         CAMEL_IMAPX_SETTINGS (settings));
3910 #endif
3911
3912         g_object_unref (settings);
3913
3914 #ifndef G_OS_WIN32
3915         if (shell_command != NULL) {
3916                 imapx_stream = connect_to_server_process (
3917                         is, shell_command, &local_error);
3918
3919                 g_free (shell_command);
3920
3921                 if (imapx_stream != NULL)
3922                         goto connected;
3923                 else
3924                         goto exit;
3925         }
3926 #endif
3927
3928         tcp_stream = camel_network_service_connect_sync (
3929                 CAMEL_NETWORK_SERVICE (store), cancellable, error);
3930
3931         if (tcp_stream == NULL) {
3932                 success = FALSE;
3933                 goto exit;
3934         }
3935
3936         /* Disable Nagle
3937          * We send a lot of small requests which nagle slows down. */
3938         sockopt.option = CAMEL_SOCKOPT_NODELAY;
3939         sockopt.value.no_delay = TRUE;
3940         camel_tcp_stream_setsockopt (CAMEL_TCP_STREAM (tcp_stream), &sockopt);
3941
3942         /* Set Keepalive
3943          * Needed for some hosts/router configurations, we're idle a lot. */
3944         sockopt.option = CAMEL_SOCKOPT_KEEPALIVE;
3945         sockopt.value.keep_alive = TRUE;
3946         camel_tcp_stream_setsockopt (CAMEL_TCP_STREAM (tcp_stream), &sockopt);
3947
3948         imapx_stream = camel_imapx_stream_new (tcp_stream);
3949
3950         /* CamelIMAPXServer takes ownership of the IMAPX stream.
3951          * We need to set this right away for imapx_command_run()
3952          * to work, but we delay emitting a "notify" signal until
3953          * we're fully connected. */
3954         g_mutex_lock (&is->priv->stream_lock);
3955         g_warn_if_fail (is->priv->stream == NULL);
3956         is->priv->stream = CAMEL_IMAPX_STREAM (imapx_stream);
3957         g_mutex_unlock (&is->priv->stream_lock);
3958
3959         g_object_unref (tcp_stream);
3960
3961  connected:
3962         CAMEL_IMAPX_STREAM (imapx_stream)->tagprefix = is->tagprefix;
3963
3964         while (1) {
3965                 // poll ?  wait for other stuff? loop?
3966                 if (camel_application_is_exiting || is->parser_quit) {
3967                         g_set_error (
3968                                 error, G_IO_ERROR,
3969                                 G_IO_ERROR_CANCELLED,
3970                                 "Connection to server cancelled\n");
3971                         success = FALSE;
3972                         goto exit;
3973                 }
3974
3975                 tok = camel_imapx_stream_token (
3976                         CAMEL_IMAPX_STREAM (imapx_stream),
3977                         &token, &len, cancellable, error);
3978                 if (tok < 0) {
3979                         success = FALSE;
3980                         goto exit;
3981                 }
3982
3983                 if (tok == '*') {
3984                         imapx_untagged (
3985                                 is, CAMEL_IMAPX_STREAM (imapx_stream),
3986                                 cancellable, error);
3987                         break;
3988                 }
3989                 camel_imapx_stream_ungettoken (
3990                         CAMEL_IMAPX_STREAM (imapx_stream), tok, token, len);
3991
3992                 success = camel_imapx_stream_text (
3993                         CAMEL_IMAPX_STREAM (imapx_stream),
3994                         &token, cancellable, error);
3995                 if (!success)
3996                         goto exit;
3997                 e (is->tagprefix, "Got unexpected line before greeting:  '%s'\n", token);
3998                 g_free (token);
3999         }
4000
4001         if (!is->cinfo) {
4002                 ic = camel_imapx_command_new (
4003                         is, "CAPABILITY", NULL, "CAPABILITY");
4004                 if (!imapx_command_run (is, ic, cancellable, error)) {
4005                         camel_imapx_command_unref (ic);
4006                         success = FALSE;
4007                         goto exit;
4008                 }
4009
4010                 /* Server reported error. */
4011                 if (ic->status->result != IMAPX_OK) {
4012                         g_set_error (
4013                                 error, CAMEL_ERROR,
4014                                 CAMEL_ERROR_GENERIC,
4015                                 "%s", ic->status->text);
4016                         camel_imapx_command_unref (ic);
4017                         success = FALSE;
4018                         goto exit;
4019                 }
4020
4021                 camel_imapx_command_unref (ic);
4022         }
4023
4024         if (method == CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT) {
4025
4026                 if (is->cinfo && !(is->cinfo->capa & IMAPX_CAPABILITY_STARTTLS)) {
4027                         g_set_error (
4028                                 &local_error, CAMEL_ERROR,
4029                                 CAMEL_ERROR_GENERIC,
4030                                 _("Failed to connect to IMAP server %s in secure mode: %s"),
4031                                 host, _("STARTTLS not supported"));
4032                         goto exit;
4033                 }
4034
4035                 ic = camel_imapx_command_new (
4036                         is, "STARTTLS", NULL, "STARTTLS");
4037                 if (!imapx_command_run (is, ic, cancellable, error)) {
4038                         camel_imapx_command_unref (ic);
4039                         success = FALSE;
4040                         goto exit;
4041                 }
4042
4043                 /* Server reported error. */
4044                 if (ic->status->result != IMAPX_OK) {
4045                         g_set_error (
4046                                 error, CAMEL_ERROR,
4047                                 CAMEL_ERROR_GENERIC,
4048                                 "%s", ic->status->text);
4049                         camel_imapx_command_unref (ic);
4050                         success = FALSE;
4051                         goto exit;
4052                 }
4053
4054                 /* See if we got new capabilities in the STARTTLS response */
4055                 imapx_free_capability (is->cinfo);
4056                 is->cinfo = NULL;
4057                 if (ic->status->condition == IMAPX_CAPABILITY) {
4058                         is->cinfo = ic->status->u.cinfo;
4059                         ic->status->u.cinfo = NULL;
4060                         c (is->tagprefix, "got capability flags %08x\n", is->cinfo ? is->cinfo->capa : 0xFFFFFFFF);
4061                 }
4062
4063                 camel_imapx_command_unref (ic);
4064
4065                 if (camel_tcp_stream_ssl_enable_ssl (
4066                         CAMEL_TCP_STREAM_SSL (tcp_stream),
4067                         cancellable, &local_error) == -1) {
4068                         g_prefix_error (
4069                                 &local_error,
4070                                 _("Failed to connect to IMAP server %s in secure mode: "),
4071                                 host);
4072                         goto exit;
4073                 }
4074                 /* Get new capabilities if they weren't already given */
4075                 if (!is->cinfo) {
4076                         ic = camel_imapx_command_new (
4077                                 is, "CAPABILITY", NULL, "CAPABILITY");
4078                         if (!imapx_command_run (is, ic, cancellable, error)) {
4079                                 camel_imapx_command_unref (ic);
4080                                 success = FALSE;
4081                                 goto exit;
4082                         }
4083
4084                         camel_imapx_command_unref (ic);
4085                 }
4086         }
4087
4088 exit:
4089         if (success) {
4090                 g_object_notify (G_OBJECT (is), "stream");
4091         } else {
4092                 g_mutex_lock (&is->priv->stream_lock);
4093
4094                 if (is->priv->stream != NULL) {
4095                         g_object_unref (is->priv->stream);
4096                         is->priv->stream = NULL;
4097                 }
4098
4099                 if (is->cinfo != NULL) {
4100                         imapx_free_capability (is->cinfo);
4101                         is->cinfo = NULL;
4102                 }
4103
4104                 g_mutex_unlock (&is->priv->stream_lock);
4105         }
4106
4107         g_free (host);
4108
4109         g_object_unref (store);
4110
4111         return success;
4112 }
4113
4114 CamelAuthenticationResult
4115 camel_imapx_server_authenticate (CamelIMAPXServer *is,
4116                                  const gchar *mechanism,
4117                                  GCancellable *cancellable,
4118                                  GError **error)
4119 {
4120         CamelNetworkSettings *network_settings;
4121         CamelIMAPXStore *store;
4122         CamelService *service;
4123         CamelSettings *settings;
4124         CamelAuthenticationResult result;
4125         CamelIMAPXCommand *ic;
4126         CamelSasl *sasl = NULL;
4127         gchar *host;
4128         gchar *user;
4129
4130         g_return_val_if_fail (
4131                 CAMEL_IS_IMAPX_SERVER (is),
4132                 CAMEL_AUTHENTICATION_REJECTED);
4133
4134         store = camel_imapx_server_ref_store (is);
4135
4136         service = CAMEL_SERVICE (store);
4137         settings = camel_service_ref_settings (service);
4138
4139         network_settings = CAMEL_NETWORK_SETTINGS (settings);
4140         host = camel_network_settings_dup_host (network_settings);
4141         user = camel_network_settings_dup_user (network_settings);
4142
4143         g_object_unref (settings);
4144
4145         if (mechanism != NULL) {
4146                 if (is->cinfo && !g_hash_table_lookup (is->cinfo->auth_types, mechanism)) {
4147                         g_set_error (
4148                                 error, CAMEL_SERVICE_ERROR,
4149                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
4150                                 _("IMAP server %s does not support %s "
4151                                 "authentication"), host, mechanism);
4152                         result = CAMEL_AUTHENTICATION_ERROR;
4153                         goto exit;
4154                 }
4155
4156                 sasl = camel_sasl_new ("imap", mechanism, service);
4157                 if (sasl == NULL) {
4158                         g_set_error (
4159                                 error, CAMEL_SERVICE_ERROR,
4160                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
4161                                 _("No support for %s authentication"),
4162                                 mechanism);
4163                         result = CAMEL_AUTHENTICATION_ERROR;
4164                         goto exit;
4165                 }
4166         }
4167
4168         if (sasl != NULL) {
4169                 ic = camel_imapx_command_new (
4170                         is, "AUTHENTICATE", NULL, "AUTHENTICATE %A", sasl);
4171         } else {
4172                 const gchar *password;
4173
4174                 password = camel_service_get_password (service);
4175
4176                 if (user == NULL) {
4177                         g_set_error_literal (
4178                                 error, CAMEL_SERVICE_ERROR,
4179                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
4180                                 _("Cannot authenticate without a username"));
4181                         result = CAMEL_AUTHENTICATION_ERROR;
4182                         goto exit;
4183                 }
4184
4185                 if (password == NULL) {
4186                         g_set_error_literal (
4187                                 error, CAMEL_SERVICE_ERROR,
4188                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
4189                                 _("Authentication password not available"));
4190                         result = CAMEL_AUTHENTICATION_ERROR;
4191                         goto exit;
4192                 }
4193
4194                 ic = camel_imapx_command_new (
4195                         is, "LOGIN", NULL, "LOGIN %s %s", user, password);
4196         }
4197
4198         if (!imapx_command_run (is, ic, cancellable, error))
4199                 result = CAMEL_AUTHENTICATION_ERROR;
4200         else if (ic->status->result == IMAPX_OK)
4201                 result = CAMEL_AUTHENTICATION_ACCEPTED;
4202         else
4203                 result = CAMEL_AUTHENTICATION_REJECTED;
4204
4205         /* Forget old capabilities after login. */
4206         if (result == CAMEL_AUTHENTICATION_ACCEPTED) {
4207                 if (is->cinfo) {
4208                         imapx_free_capability (is->cinfo);
4209                         is->cinfo = NULL;
4210                 }
4211
4212                 if (ic->status->condition == IMAPX_CAPABILITY) {
4213                         is->cinfo = ic->status->u.cinfo;
4214                         ic->status->u.cinfo = NULL;
4215                         c (is->tagprefix, "got capability flags %08x\n", is->cinfo ? is->cinfo->capa : 0xFFFFFFFF);
4216                 }
4217         }
4218
4219         camel_imapx_command_unref (ic);
4220
4221         if (sasl != NULL)
4222                 g_object_unref (sasl);
4223
4224 exit:
4225         g_free (host);
4226         g_free (user);
4227
4228         g_object_unref (store);
4229
4230         return result;
4231 }
4232
4233 static gboolean
4234 imapx_reconnect (CamelIMAPXServer *is,
4235                  GCancellable *cancellable,
4236                  GError **error)
4237 {
4238         CamelIMAPXCommand *ic;
4239         CamelService *service;
4240         CamelSession *session;
4241         CamelIMAPXStore *store;
4242         CamelSettings *settings;
4243         gchar *mechanism;
4244         gboolean use_idle;
4245         gboolean use_qresync;
4246         gboolean success = FALSE;
4247
4248         store = camel_imapx_server_ref_store (is);
4249
4250         service = CAMEL_SERVICE (store);
4251         session = camel_service_ref_session (service);
4252
4253         settings = camel_service_ref_settings (service);
4254
4255         mechanism = camel_network_settings_dup_auth_mechanism (
4256                 CAMEL_NETWORK_SETTINGS (settings));
4257
4258         use_idle = camel_imapx_settings_get_use_idle (
4259                 CAMEL_IMAPX_SETTINGS (settings));
4260
4261         use_qresync = camel_imapx_settings_get_use_qresync (
4262                 CAMEL_IMAPX_SETTINGS (settings));
4263
4264         g_object_unref (settings);
4265
4266         if (!imapx_connect_to_server (is, cancellable, error))
4267                 goto exception;
4268
4269         if (is->state == IMAPX_AUTHENTICATED)
4270                 goto preauthed;
4271
4272         if (!camel_session_authenticate_sync (
4273                 session, service, mechanism, cancellable, error))
4274                 goto exception;
4275
4276         /* After login we re-capa unless the server already told us */
4277         if (!is->cinfo) {
4278                 ic = camel_imapx_command_new (
4279                         is, "CAPABILITY", NULL, "CAPABILITY");
4280                 if (!imapx_command_run (is, ic, cancellable, error)) {
4281                         camel_imapx_command_unref (ic);
4282                         goto exception;
4283                 }
4284
4285                 camel_imapx_command_unref (ic);
4286         }
4287
4288         is->state = IMAPX_AUTHENTICATED;
4289
4290  preauthed:
4291         is->use_idle = use_idle;
4292
4293         if (imapx_idle_supported (is))
4294                 imapx_init_idle (is);
4295
4296         /* Fetch namespaces */
4297         if (is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_NAMESPACE) != 0) {
4298                 ic = camel_imapx_command_new (
4299                         is, "NAMESPACE", NULL, "NAMESPACE");
4300                 if (!imapx_command_run (is, ic, cancellable, error)) {
4301                         camel_imapx_command_unref (ic);
4302                         goto exception;
4303                 }
4304
4305                 camel_imapx_command_unref (ic);
4306         }
4307
4308         if (use_qresync && is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_QRESYNC) != 0) {
4309                 ic = camel_imapx_command_new (
4310                         is, "ENABLE", NULL, "ENABLE CONDSTORE QRESYNC");
4311                 if (!imapx_command_run (is, ic, cancellable, error)) {
4312                         camel_imapx_command_unref (ic);
4313                         goto exception;
4314                 }
4315
4316                 camel_imapx_command_unref (ic);
4317
4318                 is->use_qresync = TRUE;
4319         } else
4320                 is->use_qresync = FALSE;
4321
4322         if (store->summary->namespaces == NULL) {
4323                 CamelIMAPXNamespaceList *nsl = NULL;
4324                 CamelIMAPXStoreNamespace *ns = NULL;
4325
4326                 /* set a default namespace */
4327                 nsl = g_malloc0 (sizeof (CamelIMAPXNamespaceList));
4328                 ns = g_new0 (CamelIMAPXStoreNamespace, 1);
4329                 ns->next = NULL;
4330                 ns->path = g_strdup ("");
4331                 ns->full_name = g_strdup ("");
4332                 ns->sep = '/';
4333                 nsl->personal = ns;
4334
4335                 store->summary->namespaces = nsl;
4336                 /* FIXME needs to be identified from list response */
4337                 store->dir_sep = ns->sep;
4338         }
4339
4340         is->state = IMAPX_INITIALISED;
4341
4342         success = TRUE;
4343
4344         goto exit;
4345
4346 exception:
4347
4348         imapx_disconnect (is);
4349
4350         if (is->cinfo) {
4351                 imapx_free_capability (is->cinfo);
4352                 is->cinfo = NULL;
4353         }
4354
4355 exit:
4356         g_free (mechanism);
4357
4358         g_object_unref (session);
4359         g_object_unref (store);
4360
4361         return success;
4362 }
4363
4364 /* ********************************************************************** */
4365
4366 static gboolean
4367 imapx_command_fetch_message_done (CamelIMAPXServer *is,
4368                                   CamelIMAPXCommand *ic,
4369                                   GCancellable *cancellable,
4370                                   GError **error)
4371 {
4372         CamelIMAPXJob *job;
4373         CamelFolder *folder;
4374         GetMessageData *data;
4375         CamelIMAPXFolder *ifolder;
4376         gboolean success = TRUE;
4377         GError *local_error = NULL;
4378
4379         job = camel_imapx_command_get_job (ic);
4380         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4381
4382         data = camel_imapx_job_get_data (job);
4383         g_return_val_if_fail (data != NULL, FALSE);
4384
4385         folder = camel_imapx_job_ref_folder (job);
4386         g_return_val_if_fail (folder != NULL, FALSE);
4387
4388         /* We either have more to fetch (partial mode?), we are complete,
4389          * or we failed.  Failure is handled in the fetch code, so
4390          * we just return the job, or keep it alive with more requests */
4391
4392         job->commands--;
4393
4394         if (camel_imapx_command_set_error_if_failed (ic, &local_error)) {
4395                 g_prefix_error (
4396                         &local_error, "%s: ",
4397                         _("Error fetching message"));
4398                 data->body_len = -1;
4399
4400         } else if (data->use_multi_fetch) {
4401                 gsize really_fetched = g_seekable_tell (G_SEEKABLE (data->stream));
4402                 /* Don't automatically stop when we reach the reported message
4403                  * size -- some crappy servers (like Microsoft Exchange) have
4404                  * a tendency to lie about it. Keep going (one request at a
4405                  * time) until the data actually stop coming. */
4406                 if (data->fetch_offset < data->size ||
4407                     data->fetch_offset == really_fetched) {
4408                         CamelIMAPXCommand *new_ic;
4409
4410                         camel_operation_progress (
4411                                 cancellable,
4412                                 (data->fetch_offset *100) / data->size);
4413
4414                         new_ic = camel_imapx_command_new (
4415                                 is, "FETCH", folder,
4416                                 "UID FETCH %t (BODY.PEEK[]",
4417                                 data->uid);
4418                         camel_imapx_command_add (new_ic, "<%u.%u>", data->fetch_offset, MULTI_SIZE);
4419                         camel_imapx_command_add (new_ic, ")");
4420                         new_ic->complete = imapx_command_fetch_message_done;
4421                         camel_imapx_command_set_job (new_ic, job);
4422                         new_ic->pri = job->pri - 1;
4423                         data->fetch_offset += MULTI_SIZE;
4424                         job->commands++;
4425
4426                         success = imapx_command_queue (
4427                                 is, new_ic, cancellable, error);
4428
4429                         goto exit;
4430                 }
4431         }
4432
4433         /* If we have more messages to fetch, skip the rest. */
4434         if (job->commands > 0)
4435                 goto exit;
4436
4437         /* No more messages to fetch, let's wrap things up. */
4438
4439         ifolder = CAMEL_IMAPX_FOLDER (folder);
4440
4441         /* return the exception from last command */
4442         if (local_error != NULL) {
4443                 if (data->stream != NULL) {
4444                         g_object_unref (data->stream);
4445                         data->stream = NULL;
4446                 }
4447
4448                 g_propagate_error (error, local_error);
4449                 local_error = NULL;
4450                 success = FALSE;
4451
4452         } else if (data->stream != NULL) {
4453                 success =
4454                         (camel_stream_flush (
4455                         data->stream, cancellable, error) == 0) &&
4456                         (camel_stream_close (
4457                         data->stream, cancellable, error) == 0);
4458
4459                 if (success) {
4460                         gchar *cur_filename;
4461                         gchar *tmp_filename;
4462                         gchar *dirname;
4463
4464                         cur_filename = camel_data_cache_get_filename (
4465                                 ifolder->cache, "cur", data->uid);
4466
4467                         tmp_filename = camel_data_cache_get_filename (
4468                                 ifolder->cache, "tmp", data->uid);
4469
4470                         dirname = g_path_get_dirname (cur_filename);
4471                         g_mkdir_with_parents (dirname, 0700);
4472                         g_free (dirname);
4473
4474                         if (g_rename (tmp_filename, cur_filename) != 0)
4475                                 g_set_error (
4476                                         error, G_FILE_ERROR,
4477                                         g_file_error_from_errno (errno),
4478                                         "%s: %s",
4479                                         _("Failed to copy the tmp file"),
4480                                         g_strerror (errno));
4481
4482                         g_free (cur_filename);
4483                         g_free (tmp_filename);
4484
4485                         /* Exchange the "tmp" stream for the "cur" stream. */
4486                         g_object_unref (data->stream);
4487                         data->stream = camel_data_cache_get (
4488                                 ifolder->cache, "cur", data->uid, error);
4489                         success = (data->stream != NULL);
4490                 } else {
4491                         g_prefix_error (
4492                                 error, "%s: ",
4493                                 _("Failed to close the tmp stream"));
4494                 }
4495         }
4496
4497         camel_data_cache_remove (ifolder->cache, "tmp", data->uid, NULL);
4498         imapx_unregister_job (is, job);
4499
4500 exit:
4501         g_object_unref (folder);
4502
4503         camel_imapx_command_unref (ic);
4504
4505         g_clear_error (&local_error);
4506
4507         return success;
4508 }
4509
4510 static gboolean
4511 imapx_job_get_message_start (CamelIMAPXJob *job,
4512                              CamelIMAPXServer *is,
4513                              GCancellable *cancellable,
4514                              GError **error)
4515 {
4516         CamelFolder *folder;
4517         CamelIMAPXCommand *ic;
4518         GetMessageData *data;
4519         gint i;
4520         gboolean success = TRUE;
4521
4522         data = camel_imapx_job_get_data (job);
4523         g_return_val_if_fail (data != NULL, FALSE);
4524
4525         folder = camel_imapx_job_ref_folder (job);
4526         g_return_val_if_fail (folder != NULL, FALSE);
4527
4528         if (data->use_multi_fetch) {
4529                 for (i = 0; i < 3 && data->fetch_offset < data->size; i++) {
4530                         ic = camel_imapx_command_new (
4531                                 is, "FETCH", folder,
4532                                 "UID FETCH %t (BODY.PEEK[]",
4533                                 data->uid);
4534                         camel_imapx_command_add (ic, "<%u.%u>", data->fetch_offset, MULTI_SIZE);
4535                         camel_imapx_command_add (ic, ")");
4536                         ic->complete = imapx_command_fetch_message_done;
4537                         camel_imapx_command_set_job (ic, job);
4538                         ic->pri = job->pri;
4539                         data->fetch_offset += MULTI_SIZE;
4540                         job->commands++;
4541
4542                         success = imapx_command_queue (
4543                                 is, ic, cancellable, error);
4544                         if (!success)
4545                                 break;
4546                 }
4547         } else {
4548                 ic = camel_imapx_command_new (
4549                         is, "FETCH", folder,
4550                         "UID FETCH %t (BODY.PEEK[])",
4551                         data->uid);
4552                 ic->complete = imapx_command_fetch_message_done;
4553                 camel_imapx_command_set_job (ic, job);
4554                 ic->pri = job->pri;
4555                 job->commands++;
4556
4557                 success = imapx_command_queue (is, ic, cancellable, error);
4558         }
4559
4560         g_object_unref (folder);
4561
4562         return success;
4563 }
4564
4565 static gboolean
4566 imapx_job_get_message_matches (CamelIMAPXJob *job,
4567                                CamelFolder *folder,
4568                                const gchar *uid)
4569 {
4570         GetMessageData *data;
4571
4572         data = camel_imapx_job_get_data (job);
4573         g_return_val_if_fail (data != NULL, FALSE);
4574
4575         if (!camel_imapx_job_has_folder (job, folder))
4576                 return FALSE;
4577
4578         if (g_strcmp0 (uid, data->uid) != 0)
4579                 return FALSE;
4580
4581         return TRUE;
4582 }
4583
4584 /* ********************************************************************** */
4585
4586 static gboolean
4587 imapx_command_copy_messages_step_done (CamelIMAPXServer *is,
4588                                        CamelIMAPXCommand *ic,
4589                                        GCancellable *cancellable,
4590                                        GError **error)
4591 {
4592         CamelIMAPXJob *job;
4593         CamelFolder *folder;
4594         CopyMessagesData *data;
4595         GPtrArray *uids;
4596         gint i;
4597         gboolean success = TRUE;
4598
4599         job = camel_imapx_command_get_job (ic);
4600         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4601
4602         data = camel_imapx_job_get_data (job);
4603         g_return_val_if_fail (data != NULL, FALSE);
4604
4605         folder = camel_imapx_job_ref_folder (job);
4606         g_return_val_if_fail (folder != NULL, FALSE);
4607
4608         uids = data->uids;
4609         i = data->index;
4610
4611         if (camel_imapx_command_set_error_if_failed (ic, error)) {
4612                 g_prefix_error (
4613                         error, "%s: ",
4614                         _("Error copying messages"));
4615                 success = FALSE;
4616                 goto exit;
4617         }
4618
4619         if (data->delete_originals) {
4620                 gint j;
4621
4622                 for (j = data->last_index; j < i; j++)
4623                         camel_folder_delete_message (folder, uids->pdata[j]);
4624         }
4625
4626         /* TODO Copy the summary and cached messages to the new folder.
4627          *      We might need a sorted insert to avoid refreshing the dest
4628          *      folder. */
4629         if (ic->status && ic->status->condition == IMAPX_COPYUID) {
4630                 gint i;
4631
4632                 for (i = 0; i < ic->status->u.copyuid.copied_uids->len; i++) {
4633                         guint32 uid = GPOINTER_TO_UINT (g_ptr_array_index (ic->status->u.copyuid.copied_uids, i));
4634                         gchar *str = g_strdup_printf ("%d",uid);
4635                         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) data->dest;
4636
4637                         g_hash_table_insert (ifolder->ignore_recent, str, GINT_TO_POINTER (1));
4638                 }
4639
4640         }
4641
4642         if (i < uids->len) {
4643                 g_object_unref (folder);
4644
4645                 camel_imapx_command_unref (ic);
4646
4647                 return imapx_command_copy_messages_step_start (
4648                         is, job, i, cancellable, error);
4649         }
4650
4651 exit:
4652         g_object_unref (folder);
4653
4654         imapx_unregister_job (is, job);
4655         camel_imapx_command_unref (ic);
4656
4657         return success;
4658 }
4659
4660 static gboolean
4661 imapx_command_copy_messages_step_start (CamelIMAPXServer *is,
4662                                         CamelIMAPXJob *job,
4663                                         gint index,
4664                                         GCancellable *cancellable,
4665                                         GError **error)
4666 {
4667         CamelFolder *folder;
4668         CamelIMAPXCommand *ic;
4669         CopyMessagesData *data;
4670         GPtrArray *uids;
4671         gint i = index;
4672
4673         data = camel_imapx_job_get_data (job);
4674         g_return_val_if_fail (data != NULL, FALSE);
4675
4676         folder = camel_imapx_job_ref_folder (job);
4677         g_return_val_if_fail (folder != NULL, FALSE);
4678
4679         uids = data->uids;
4680
4681         ic = camel_imapx_command_new (is, "COPY", folder, "UID COPY ");
4682         ic->complete = imapx_command_copy_messages_step_done;
4683         camel_imapx_command_set_job (ic, job);
4684         ic->pri = job->pri;
4685         data->last_index = i;
4686
4687         g_object_unref (folder);
4688
4689         for (; i < uids->len; i++) {
4690                 gint res;
4691                 const gchar *uid = (gchar *) g_ptr_array_index (uids, i);
4692
4693                 res = imapx_uidset_add (&data->uidset, ic, uid);
4694                 if (res == 1) {
4695                         camel_imapx_command_add (ic, " %f", data->dest);
4696                         data->index = i + 1;
4697                         return imapx_command_queue (is, ic, cancellable, error);
4698                 }
4699         }
4700
4701         data->index = i;
4702         if (imapx_uidset_done (&data->uidset, ic)) {
4703                 camel_imapx_command_add (ic, " %f", data->dest);
4704                 return imapx_command_queue (is, ic, cancellable, error);
4705         }
4706
4707         return TRUE;
4708 }
4709
4710 static gboolean
4711 imapx_job_copy_messages_start (CamelIMAPXJob *job,
4712                                CamelIMAPXServer *is,
4713                                GCancellable *cancellable,
4714                                GError **error)
4715 {
4716         CamelFolder *folder;
4717         CopyMessagesData *data;
4718         gboolean success;
4719
4720         data = camel_imapx_job_get_data (job);
4721         g_return_val_if_fail (data != NULL, FALSE);
4722
4723         folder = camel_imapx_job_ref_folder (job);
4724         g_return_val_if_fail (folder != NULL, FALSE);
4725
4726         success = imapx_server_sync_changes (
4727                 is, folder, job->type, job->pri, cancellable, error);
4728         if (!success)
4729                 imapx_unregister_job (is, job);
4730
4731         /* XXX Should we still do this even if a failure occurred? */
4732         g_ptr_array_sort (data->uids, (GCompareFunc) imapx_uids_array_cmp);
4733         imapx_uidset_init (&data->uidset, 0, MAX_COMMAND_LEN);
4734
4735         g_object_unref (folder);
4736
4737         return imapx_command_copy_messages_step_start (
4738                 is, job, 0, cancellable, error);
4739 }
4740
4741 /* ********************************************************************** */
4742
4743 static gboolean
4744 imapx_command_append_message_done (CamelIMAPXServer *is,
4745                                    CamelIMAPXCommand *ic,
4746                                    GCancellable *cancellable,
4747                                    GError **error)
4748 {
4749         CamelIMAPXJob *job;
4750         CamelIMAPXFolder *ifolder;
4751         CamelFolder *folder;
4752         CamelMessageInfo *mi;
4753         AppendMessageData *data;
4754         gchar *cur, *old_uid;
4755         gboolean success = TRUE;
4756
4757         job = camel_imapx_command_get_job (ic);
4758         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4759
4760         data = camel_imapx_job_get_data (job);
4761         g_return_val_if_fail (data != NULL, FALSE);
4762
4763         folder = camel_imapx_job_ref_folder (job);
4764         g_return_val_if_fail (folder != NULL, FALSE);
4765
4766         ifolder = CAMEL_IMAPX_FOLDER (folder);
4767
4768         /* Append done.  If we the server supports UIDPLUS we will get
4769          * an APPENDUID response with the new uid.  This lets us move the
4770          * message we have directly to the cache and also create a correctly
4771          * numbered MessageInfo, without losing any information.  Otherwise
4772          * we have to wait for the server to let us know it was appended. */
4773
4774         mi = camel_message_info_clone (data->info);
4775         old_uid = g_strdup (data->info->uid);
4776
4777         if (camel_imapx_command_set_error_if_failed (ic, error)) {
4778                 g_prefix_error (
4779                         error, "%s: ",
4780                         _("Error appending message"));
4781                 success = FALSE;
4782
4783         } else if (ic->status && ic->status->condition == IMAPX_APPENDUID) {
4784                 c (is->tagprefix, "Got appenduid %d %d\n", (gint) ic->status->u.appenduid.uidvalidity, (gint) ic->status->u.appenduid.uid);
4785                 if (ic->status->u.appenduid.uidvalidity == ifolder->uidvalidity_on_server) {
4786                         CamelFolderChangeInfo *changes;
4787
4788                         data->appended_uid = g_strdup_printf ("%u", (guint) ic->status->u.appenduid.uid);
4789                         mi->uid = camel_pstring_add (data->appended_uid, FALSE);
4790
4791                         cur = camel_data_cache_get_filename  (ifolder->cache, "cur", mi->uid);
4792                         g_rename (data->path, cur);
4793
4794                         /* should we update the message count ? */
4795                         imapx_set_message_info_flags_for_new_message (
4796                                 mi,
4797                                 ((CamelMessageInfoBase *) data->info)->flags,
4798                                 ((CamelMessageInfoBase *) data->info)->user_flags,
4799                                 folder);
4800                         camel_folder_summary_add (folder->summary, mi);
4801                         changes = camel_folder_change_info_new ();
4802                         camel_folder_change_info_add_uid (changes, mi->uid);
4803                         camel_folder_changed (folder, changes);
4804                         camel_folder_change_info_free (changes);
4805
4806                         g_free (cur);
4807                 } else {
4808                         g_message ("but uidvalidity changed \n");
4809                 }
4810         }
4811
4812         camel_data_cache_remove (ifolder->cache, "new", old_uid, NULL);
4813         g_free (old_uid);
4814
4815         g_object_unref (folder);
4816
4817         imapx_unregister_job (is, job);
4818         camel_imapx_command_unref (ic);
4819
4820         return success;
4821 }
4822
4823 static gboolean
4824 imapx_job_append_message_start (CamelIMAPXJob *job,
4825                                 CamelIMAPXServer *is,
4826                                 GCancellable *cancellable,
4827                                 GError **error)
4828 {
4829         CamelFolder *folder;
4830         CamelIMAPXCommand *ic;
4831         AppendMessageData *data;
4832
4833         data = camel_imapx_job_get_data (job);
4834         g_return_val_if_fail (data != NULL, FALSE);
4835
4836         folder = camel_imapx_job_ref_folder (job);
4837         g_return_val_if_fail (folder != NULL, FALSE);
4838
4839         /* TODO: we could supply the original append date from the file timestamp */
4840         ic = camel_imapx_command_new (
4841                 is, "APPEND", NULL,
4842                 "APPEND %f %F %P", folder,
4843                 ((CamelMessageInfoBase *) data->info)->flags,
4844                 ((CamelMessageInfoBase *) data->info)->user_flags,
4845                 data->path);
4846         ic->complete = imapx_command_append_message_done;
4847         camel_imapx_command_set_job (ic, job);
4848         ic->pri = job->pri;
4849         job->commands++;
4850
4851         g_object_unref (folder);
4852
4853         return imapx_command_queue (is, ic, cancellable, error);
4854 }
4855
4856 /* ********************************************************************** */
4857
4858 static gint
4859 imapx_refresh_info_uid_cmp (gconstpointer ap,
4860                             gconstpointer bp,
4861                             gboolean ascending)
4862 {
4863         guint av, bv;
4864
4865         av = g_ascii_strtoull ((const gchar *) ap, NULL, 10);
4866         bv = g_ascii_strtoull ((const gchar *) bp, NULL, 10);
4867
4868         if (av < bv)
4869                 return ascending ? -1 : 1;
4870         else if (av > bv)
4871                 return ascending ? 1 : -1;
4872         else
4873                 return 0;
4874 }
4875
4876 static gint
4877 imapx_uids_array_cmp (gconstpointer ap,
4878                       gconstpointer bp)
4879 {
4880         const gchar **a = (const gchar **) ap;
4881         const gchar **b = (const gchar **) bp;
4882
4883         return imapx_refresh_info_uid_cmp (*a, *b, TRUE);
4884 }
4885
4886 static gint
4887 imapx_refresh_info_cmp (gconstpointer ap,
4888                         gconstpointer bp)
4889 {
4890         const struct _refresh_info *a = ap;
4891         const struct _refresh_info *b = bp;
4892
4893         return imapx_refresh_info_uid_cmp (a->uid, b->uid, TRUE);
4894 }
4895
4896 static gint
4897 imapx_refresh_info_cmp_descending (gconstpointer ap,
4898                                    gconstpointer bp)
4899 {
4900         const struct _refresh_info *a = ap;
4901         const struct _refresh_info *b = bp;
4902
4903         return imapx_refresh_info_uid_cmp (a->uid, b->uid, FALSE);
4904
4905 }
4906
4907 /* skips over non-server uids (pending appends) */
4908 static guint
4909 imapx_index_next (GPtrArray *uids,
4910                   CamelFolderSummary *s,
4911                   guint index)
4912 {
4913
4914         while (index < uids->len) {
4915                 CamelMessageInfo *info;
4916
4917                 index++;
4918                 if (index >= uids->len)
4919                         break;
4920
4921                 info = camel_folder_summary_get (s, g_ptr_array_index (uids, index));
4922                 if (!info)
4923                         continue;
4924
4925                 if (info && (strchr (camel_message_info_uid (info), '-') != NULL)) {
4926                         camel_message_info_free (info);
4927                         e ('?', "Ignoring offline uid '%s'\n", camel_message_info_uid (info));
4928                 } else {
4929                         camel_message_info_free (info);
4930                         break;
4931                 }
4932         }
4933
4934         return index;
4935 }
4936
4937 static gboolean
4938 imapx_command_step_fetch_done (CamelIMAPXServer *is,
4939                                CamelIMAPXCommand *ic,
4940                                GCancellable *cancellable,
4941                                GError **error)
4942 {
4943         CamelIMAPXFolder *ifolder;
4944         CamelIMAPXSummary *isum;
4945         CamelIMAPXJob *job;
4946         CamelFolder *folder;
4947         RefreshInfoData *data;
4948         gint i;
4949         gboolean success = TRUE;
4950         CamelIMAPXSettings *settings;
4951         guint batch_count;
4952         gboolean mobile_mode;
4953
4954         job = camel_imapx_command_get_job (ic);
4955         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4956
4957         data = camel_imapx_job_get_data (job);
4958         g_return_val_if_fail (data != NULL, FALSE);
4959
4960         folder = camel_imapx_job_ref_folder (job);
4961         g_return_val_if_fail (folder != NULL, FALSE);
4962
4963         data->scan_changes = FALSE;
4964
4965         ifolder = CAMEL_IMAPX_FOLDER (folder);
4966         isum = CAMEL_IMAPX_SUMMARY (folder->summary);
4967
4968         settings = camel_imapx_server_ref_settings (is);
4969         batch_count = camel_imapx_settings_get_batch_fetch_count (settings);
4970         mobile_mode = camel_imapx_settings_get_mobile_mode (settings);
4971         g_object_unref (settings);
4972
4973         i = data->index;
4974
4975         //printf ("%s: Mobile mode: %d Fetch Count %d\n", camel_folder_get_display_name (folder), mobile_mode, batch_count);
4976         if (camel_imapx_command_set_error_if_failed (ic, error)) {
4977                 g_prefix_error (
4978                         error, "%s: ",
4979                         _("Error fetching message headers"));
4980                 success = FALSE;
4981                 goto exit;
4982         }
4983
4984         if (camel_folder_change_info_changed (data->changes)) {
4985                 imapx_update_store_summary (folder);
4986                 camel_folder_summary_save_to_db (folder->summary, NULL);
4987                 camel_folder_changed (folder, data->changes);
4988         }
4989
4990         camel_folder_change_info_clear (data->changes);
4991
4992         if (i < data->infos->len) {
4993                 gint total = camel_folder_summary_count (folder->summary);
4994                 gint fetch_limit = data->fetch_msg_limit;
4995
4996                 camel_imapx_command_unref (ic);
4997
4998                 ic = camel_imapx_command_new (
4999                         is, "FETCH", folder, "UID FETCH ");
5000                 ic->complete = imapx_command_step_fetch_done;
5001                 camel_imapx_command_set_job (ic, job);
5002                 ic->pri = job->pri - 1;
5003
5004                 //printf ("Total: %d: %d, %d, %d\n", total, fetch_limit, i, data->last_index);
5005                 data->last_index = i;
5006
5007                 /* If its mobile client and when total=0 (new account setup)
5008                  * fetch only one batch of mails, on futher attempts download
5009                  * all new mails as per the limit. */
5010                 //printf ("Total: %d: %d\n", total, fetch_limit);
5011                 for (; i < data->infos->len &&
5012                         (!mobile_mode || (total && i == 0) ||
5013                         ((fetch_limit != -1 && i < fetch_limit) ||
5014                         (fetch_limit == -1 && i < batch_count))); i++) {
5015
5016                         gint res;
5017                         struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
5018
5019                         if (!r->exists) {
5020                                 res = imapx_uidset_add (&data->uidset, ic, r->uid);
5021                                 if (res == 1) {
5022                                         camel_imapx_command_add (ic, " (RFC822.SIZE RFC822.HEADER)");
5023                                         data->index = i + 1;
5024
5025                                         g_object_unref (folder);
5026
5027                                         return imapx_command_queue (is, ic, cancellable, error);
5028                                 }
5029                         }
5030                 }
5031
5032                 //printf ("Existing : %d Gonna fetch in %s for %d/%d\n", total, camel_folder_get_full_name (folder), i, data->infos->len);
5033                 data->index = data->infos->len;
5034                 if (imapx_uidset_done (&data->uidset, ic)) {
5035                         camel_imapx_command_add (ic, " (RFC822.SIZE RFC822.HEADER)");
5036
5037                         g_object_unref (folder);
5038
5039                         return imapx_command_queue (is, ic, cancellable, error);
5040                 }
5041         }
5042
5043         if (camel_folder_summary_count (folder->summary)) {
5044                 gchar *uid = imapx_get_uid_from_index (
5045                         folder->summary,
5046                         camel_folder_summary_count (folder->summary) - 1);
5047                 guint64 uidl = strtoull (uid, NULL, 10);
5048                 g_free (uid);
5049
5050                 uidl++;
5051
5052                 if (uidl > ifolder->uidnext_on_server) {
5053                         c (
5054                                 is->tagprefix, "Updating uidnext_on_server for '%s' to %" G_GUINT64_FORMAT "\n",
5055                                 camel_folder_get_full_name (folder), uidl);
5056                         ifolder->uidnext_on_server = uidl;
5057                 }
5058         }
5059         isum->uidnext = ifolder->uidnext_on_server;
5060
5061 exit:
5062         refresh_info_data_infos_free (data);
5063
5064         g_object_unref (folder);
5065
5066         imapx_unregister_job (is, job);
5067         camel_imapx_command_unref (ic);
5068
5069         return success;
5070 }
5071
5072 static gint
5073 imapx_uid_cmp (gconstpointer ap,
5074                gconstpointer bp,
5075                gpointer data)
5076 {
5077         const gchar *a = ap, *b = bp;
5078         gchar *ae, *be;
5079         gulong av, bv;
5080
5081         av = strtoul (a, &ae, 10);
5082         bv = strtoul (b, &be, 10);
5083
5084         if (av < bv)
5085                 return -1;
5086         else if (av > bv)
5087                 return 1;
5088
5089         if (*ae == '-')
5090                 ae++;
5091         if (*be == '-')
5092                 be++;
5093
5094         return strcmp (ae, be);
5095 }
5096
5097 static gboolean
5098 imapx_job_scan_changes_done (CamelIMAPXServer *is,
5099                              CamelIMAPXCommand *ic,
5100                              GCancellable *cancellable,
5101                              GError **error)
5102 {
5103         CamelIMAPXJob *job;
5104         CamelIMAPXSettings *settings;
5105         CamelFolder *folder;
5106         RefreshInfoData *data;
5107         guint uidset_size;
5108         gboolean success = TRUE;
5109         gboolean mobile_mode;
5110
5111         job = camel_imapx_command_get_job (ic);
5112         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
5113
5114         data = camel_imapx_job_get_data (job);
5115         g_return_val_if_fail (data != NULL, FALSE);
5116
5117         folder = camel_imapx_job_ref_folder (job);
5118         g_return_val_if_fail (folder != NULL, FALSE);
5119
5120         data->scan_changes = FALSE;
5121
5122         settings = camel_imapx_server_ref_settings (is);
5123         uidset_size = camel_imapx_settings_get_batch_fetch_count (settings);
5124         mobile_mode = camel_imapx_settings_get_mobile_mode (settings);
5125         g_object_unref (settings);
5126
5127         if (camel_imapx_command_set_error_if_failed (ic, error)) {
5128                 g_prefix_error (
5129                         error, "%s: ",
5130                         _("Error retrieving message"));
5131                 success = FALSE;
5132
5133         } else {
5134                 GCompareDataFunc uid_cmp = imapx_uid_cmp;
5135                 CamelMessageInfo *s_minfo = NULL;
5136                 CamelIMAPXMessageInfo *info;
5137                 CamelFolderSummary *s = folder->summary;
5138                 CamelIMAPXFolder *ifolder;
5139                 GList *removed = NULL, *l;
5140                 gboolean fetch_new = FALSE;
5141                 gint i;
5142                 guint j = 0;
5143                 GPtrArray *uids;
5144
5145                 ifolder = CAMEL_IMAPX_FOLDER (folder);
5146
5147                 /* Actually we wanted to do this after the SELECT but before the
5148                  * FETCH command was issued. But this should suffice. */
5149                 ((CamelIMAPXSummary *) s)->uidnext = ifolder->uidnext_on_server;
5150                 ((CamelIMAPXSummary *) s)->modseq = ifolder->modseq_on_server;
5151
5152                 /* Here we do the typical sort/iterate/merge loop.
5153                  * If the server flags dont match what we had, we modify our
5154                  * flags to pick up what the server now has - but we merge
5155                  * not overwrite */
5156
5157                 /* FIXME: We also have to check the offline directory for
5158                  * anything missing in our summary, and also queue up jobs
5159                  * for all outstanding messages to be uploaded */
5160
5161                 /* obtain a copy to be thread safe */
5162                 uids = camel_folder_summary_get_array (s);
5163
5164                 qsort (data->infos->data, data->infos->len, sizeof (struct _refresh_info), imapx_refresh_info_cmp);
5165                 g_ptr_array_sort (uids, (GCompareFunc) imapx_uids_array_cmp);
5166
5167                 if (uids->len)
5168                         s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, 0));
5169
5170                 for (i = 0; i < data->infos->len; i++) {
5171                         struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
5172
5173                         while (s_minfo && uid_cmp (camel_message_info_uid (s_minfo), r->uid, s) < 0) {
5174                                 const gchar *uid = camel_message_info_uid (s_minfo);
5175
5176                                 camel_folder_change_info_remove_uid (data->changes, uid);
5177                                 removed = g_list_prepend (removed, (gpointer ) g_strdup (uid));
5178                                 camel_message_info_free (s_minfo);
5179                                 s_minfo = NULL;
5180
5181                                 j = imapx_index_next (uids, s, j);
5182                                 if (j < uids->len)
5183                                         s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
5184                         }
5185
5186                         info = NULL;
5187                         if (s_minfo && uid_cmp (s_minfo->uid, r->uid, s) == 0) {
5188                                 info = (CamelIMAPXMessageInfo *) s_minfo;
5189
5190                                 if (imapx_update_message_info_flags (
5191                                                 (CamelMessageInfo *) info,
5192                                                 r->server_flags,
5193                                                 r->server_user_flags,
5194                                                 is->permanentflags,
5195                                                 folder, FALSE))
5196                                         camel_folder_change_info_change_uid (
5197                                                 data->changes,
5198                                                 camel_message_info_uid (s_minfo));
5199                                 r->exists = TRUE;
5200                         } else
5201                                 fetch_new = TRUE;
5202
5203                         if (s_minfo) {
5204                                 camel_message_info_free (s_minfo);
5205                                 s_minfo = NULL;
5206                         }
5207
5208                         if (j >= uids->len)
5209                                 break;
5210
5211                         j = imapx_index_next (uids, s, j);
5212                         if (j < uids->len)
5213                                 s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
5214                 }
5215
5216                 if (s_minfo)
5217                         camel_message_info_free (s_minfo);
5218
5219                 while (j < uids->len) {
5220                         s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
5221
5222                         if (!s_minfo) {
5223                                 j++;
5224                                 continue;
5225                         }
5226
5227                         e (is->tagprefix, "Message %s vanished\n", s_minfo->uid);
5228                         removed = g_list_prepend (removed, (gpointer) g_strdup (s_minfo->uid));
5229                         camel_message_info_free (s_minfo);
5230                         j++;
5231                 }
5232
5233                 for (l = removed; l != NULL; l = g_list_next (l)) {
5234                         gchar *uid = (gchar *) l->data;
5235
5236                         camel_folder_change_info_remove_uid (data->changes, uid);
5237                 }
5238
5239                 if (removed != NULL) {
5240                         camel_folder_summary_remove_uids (s, removed);
5241                         camel_folder_summary_touch (s);
5242
5243                         g_list_free_full (removed, (GDestroyNotify) g_free);
5244                 }
5245
5246                 camel_folder_summary_save_to_db (s, NULL);
5247                 imapx_update_store_summary (folder);
5248
5249                 if (camel_folder_change_info_changed (data->changes))
5250                         camel_folder_changed (folder, data->changes);
5251                 camel_folder_change_info_clear (data->changes);
5252
5253                 camel_folder_summary_free_array (uids);
5254
5255                 /* If we have any new messages, download their headers, but only a few (100?) at a time */
5256                 if (fetch_new) {
5257                         job->pop_operation_msg = TRUE;
5258
5259                         camel_operation_push_message (
5260                                 cancellable,
5261                                 _("Fetching summary information for new messages in '%s'"),
5262                                 camel_folder_get_display_name (folder));
5263
5264                         imapx_uidset_init (&data->uidset, uidset_size, 0);
5265                         /* These are new messages which arrived since we last knew the unseen count;
5266                          * update it as they arrive. */
5267                         data->update_unseen = TRUE;
5268
5269                         g_object_unref (folder);
5270
5271                         return imapx_command_step_fetch_done (
5272                                 is, ic, cancellable, error);
5273                 }
5274         }
5275
5276         refresh_info_data_infos_free (data);
5277
5278         /* There's no sane way to get the server-side unseen count on the
5279          * select mailbox. So just work it out from the flags if its not in
5280          * mobile mode. In mobile mode we would have this filled up already
5281          * with a STATUS command.
5282          **/
5283         if (!mobile_mode)
5284                 ((CamelIMAPXFolder *) folder)->unread_on_server =
5285                         camel_folder_summary_get_unread_count (folder->summary);
5286
5287         g_object_unref (folder);
5288
5289         imapx_unregister_job (is, job);
5290         camel_imapx_command_unref (ic);
5291
5292         return success;
5293 }
5294
5295 static gboolean
5296 imapx_job_scan_changes_start (CamelIMAPXJob *job,
5297                               CamelIMAPXServer *is,
5298                               GCancellable *cancellable,
5299                               GError **error)
5300 {
5301         CamelFolder *folder;
5302         CamelIMAPXCommand *ic;
5303         RefreshInfoData *data;
5304         CamelIMAPXSettings *settings;
5305         gboolean mobile_mode;
5306         gchar *uid = NULL;
5307
5308         data = camel_imapx_job_get_data (job);
5309         g_return_val_if_fail (data != NULL, FALSE);
5310
5311         folder = camel_imapx_job_ref_folder (job);
5312         g_return_val_if_fail (folder != NULL, FALSE);
5313
5314         settings = camel_imapx_server_ref_settings (is);
5315         mobile_mode = camel_imapx_settings_get_mobile_mode (settings);
5316         g_object_unref (settings);
5317
5318         if (mobile_mode)
5319                 uid = imapx_get_uid_from_index (folder->summary, 0);
5320
5321         job->pop_operation_msg = TRUE;
5322
5323         camel_operation_push_message (
5324                 cancellable,
5325                 _("Scanning for changed messages in '%s'"),
5326                 camel_folder_get_display_name (folder));
5327
5328         e (
5329                 'E', "Scanning from %s in %s\n", uid ? uid : "start",
5330                 camel_folder_get_full_name (folder));
5331
5332         ic = camel_imapx_command_new (
5333                 is, "FETCH", folder,
5334                 "UID FETCH %s:* (UID FLAGS)", uid ? uid : "1");
5335         camel_imapx_command_set_job (ic, job);
5336         ic->complete = imapx_job_scan_changes_done;
5337
5338         data->scan_changes = TRUE;
5339         ic->pri = job->pri;
5340         refresh_info_data_infos_free (data);
5341         data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
5342
5343         g_free (uid);
5344
5345         g_object_unref (folder);
5346
5347         return imapx_command_queue (is, ic, cancellable, error);
5348 }
5349
5350 static gboolean
5351 imapx_command_fetch_new_messages_done (CamelIMAPXServer *is,
5352                                        CamelIMAPXCommand *ic,
5353                                        GCancellable *cancellable,
5354                                        GError **error)
5355 {
5356         CamelIMAPXJob *job;
5357         CamelIMAPXSummary *isum;
5358         CamelIMAPXFolder *ifolder;
5359         CamelFolder *folder;
5360         RefreshInfoData *data;
5361         gboolean success = TRUE;
5362
5363         job = camel_imapx_command_get_job (ic);
5364         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
5365
5366         data = camel_imapx_job_get_data (job);
5367         g_return_val_if_fail (data != NULL, FALSE);
5368
5369         folder = camel_imapx_job_ref_folder (job);
5370         g_return_val_if_fail (folder != NULL, FALSE);
5371
5372         ifolder = CAMEL_IMAPX_FOLDER (folder);
5373         isum = CAMEL_IMAPX_SUMMARY (folder->summary);
5374
5375         if (camel_imapx_command_set_error_if_failed (ic, error)) {
5376                 g_prefix_error (
5377                         error, "%s: ",
5378                         _("Error fetching new messages"));
5379                 success = FALSE;
5380                 goto exit;
5381         }
5382
5383         if (camel_folder_change_info_changed (data->changes)) {
5384                 camel_folder_summary_save_to_db (folder->summary, NULL);
5385                 imapx_update_store_summary (folder);
5386                 camel_folder_changed (folder, data->changes);
5387                 camel_folder_change_info_clear (data->changes);
5388         }
5389
5390         if (camel_folder_summary_count (folder->summary)) {
5391                 gchar *uid = imapx_get_uid_from_index (
5392                         folder->summary,
5393                         camel_folder_summary_count (folder->summary) - 1);
5394                 guint64 uidl = strtoull (uid, NULL, 10);
5395                 g_free (uid);
5396
5397                 uidl++;
5398
5399                 if (uidl > ifolder->uidnext_on_server) {
5400                         c (
5401                                 is->tagprefix, "Updating uidnext_on_server for '%s' to %" G_GUINT64_FORMAT "\n",
5402                                 camel_folder_get_full_name (folder), uidl);
5403                         ifolder->uidnext_on_server = uidl;
5404                 }
5405         }
5406
5407         isum->uidnext = ifolder->uidnext_on_server;
5408
5409 exit:
5410         g_object_unref (folder);
5411
5412         imapx_unregister_job (is, job);
5413         camel_imapx_command_unref (ic);
5414
5415         return success;
5416 }
5417
5418 static gboolean
5419 imapx_command_fetch_new_uids_done (CamelIMAPXServer *is,
5420                                    CamelIMAPXCommand *ic,
5421                                    GCancellable *cancellable,
5422                                    GError **error)
5423 {
5424         CamelIMAPXJob *job;
5425         RefreshInfoData *data;
5426
5427         job = camel_imapx_command_get_job (ic);
5428         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
5429
5430         data = camel_imapx_job_get_data (job);
5431         g_return_val_if_fail (data != NULL, FALSE);
5432
5433         data->scan_changes = FALSE;
5434
5435         qsort (
5436                 data->infos->data,
5437                 data->infos->len,
5438                 sizeof (struct _refresh_info),
5439                 imapx_refresh_info_cmp_descending);
5440
5441         return imapx_command_step_fetch_done (is, ic, cancellable, error);
5442 }
5443
5444 static gboolean
5445 imapx_job_fetch_new_messages_start (CamelIMAPXJob *job,
5446                                     CamelIMAPXServer *is,
5447                                     GCancellable *cancellable,
5448                                     GError **error)
5449 {
5450         CamelIMAPXCommand *ic;
5451         CamelFolder *folder;
5452         CamelIMAPXFolder *ifolder;
5453         CamelIMAPXSettings *settings;
5454         CamelSortType fetch_order;
5455         RefreshInfoData *data;
5456         guint32 total, diff;
5457         guint uidset_size;
5458         gchar *uid = NULL;
5459
5460         data = camel_imapx_job_get_data (job);
5461         g_return_val_if_fail (data != NULL, FALSE);
5462
5463         folder = camel_imapx_job_ref_folder (job);
5464         g_return_val_if_fail (folder != NULL, FALSE);
5465
5466         settings = camel_imapx_server_ref_settings (is);
5467         fetch_order = camel_imapx_settings_get_fetch_order (settings);
5468         uidset_size = camel_imapx_settings_get_batch_fetch_count (settings);
5469         g_object_unref (settings);
5470
5471         ifolder = CAMEL_IMAPX_FOLDER (folder);
5472
5473         total = camel_folder_summary_count (folder->summary);
5474         diff = ifolder->exists_on_server - total;
5475
5476         if (total > 0) {
5477                 guint64 uidl;
5478                 uid = imapx_get_uid_from_index (folder->summary, total - 1);
5479                 uidl = strtoull (uid, NULL, 10);
5480                 g_free (uid);
5481                 uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uidl + 1);
5482         } else
5483                 uid = g_strdup ("1");
5484
5485         job->pop_operation_msg = TRUE;
5486
5487         camel_operation_push_message (
5488                 cancellable,
5489                 _("Fetching summary information for new messages in '%s'"),
5490                 camel_folder_get_display_name (folder));
5491
5492         //printf ("Fetch order: %d/%d\n", fetch_order, CAMEL_SORT_DESCENDING);
5493         if (diff > uidset_size || fetch_order == CAMEL_SORT_DESCENDING) {
5494                 ic = camel_imapx_command_new (
5495                         is, "FETCH", folder,
5496                         "UID FETCH %s:* (UID FLAGS)", uid);
5497                 imapx_uidset_init (&data->uidset, uidset_size, 0);
5498                 refresh_info_data_infos_free (data);
5499                 data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
5500                 ic->pri = job->pri;
5501
5502                 data->scan_changes = TRUE;
5503
5504                 if (fetch_order == CAMEL_SORT_DESCENDING)
5505                         ic->complete = imapx_command_fetch_new_uids_done;
5506                 else
5507                         ic->complete = imapx_command_step_fetch_done;
5508         } else {
5509                 ic = camel_imapx_command_new (
5510                         is, "FETCH", folder,
5511                         "UID FETCH %s:* (RFC822.SIZE RFC822.HEADER FLAGS)", uid);
5512                 ic->pri = job->pri;
5513                 ic->complete = imapx_command_fetch_new_messages_done;
5514         }
5515
5516         g_free (uid);
5517
5518         camel_imapx_command_set_job (ic, job);
5519
5520         g_object_unref (folder);
5521
5522         return imapx_command_queue (is, ic, cancellable, error);
5523 }
5524
5525 static gboolean
5526 imapx_job_fetch_messages_start (CamelIMAPXJob *job,
5527                                 CamelIMAPXServer *is,
5528                                 GCancellable *cancellable,
5529                                 GError **error)
5530 {
5531         CamelIMAPXCommand *ic;
5532         CamelFolder *folder;
5533         guint32 total;
5534         gchar *start_uid = NULL, *end_uid = NULL;
5535         CamelFetchType ftype;
5536         gint fetch_limit;
5537         CamelSortType fetch_order;
5538         CamelIMAPXSettings *settings;
5539         guint uidset_size;
5540         RefreshInfoData *data;
5541
5542         data = camel_imapx_job_get_data (job);
5543         g_return_val_if_fail (data != NULL, FALSE);
5544
5545         folder = camel_imapx_job_ref_folder (job);
5546         g_return_val_if_fail (folder != NULL, FALSE);
5547
5548         settings = camel_imapx_server_ref_settings (is);
5549         fetch_order = camel_imapx_settings_get_fetch_order (settings);
5550         uidset_size = camel_imapx_settings_get_batch_fetch_count (settings);
5551         g_object_unref (settings);
5552
5553         total = camel_folder_summary_count (folder->summary);
5554
5555         ftype = data->fetch_type;
5556         fetch_limit = data->fetch_msg_limit;
5557
5558         if (ftype == CAMEL_FETCH_NEW_MESSAGES ||
5559                 (ftype ==  CAMEL_FETCH_OLD_MESSAGES && total <=0 )) {
5560
5561                 gchar *uid;
5562
5563                 if (total > 0) {
5564                         /* This means that we are fetching limited number of new mails */
5565                         uid = g_strdup_printf ("%d", total);
5566                 } else {
5567                         /* For empty accounts, we always fetch the specified number of new mails independent of
5568                          * being asked to fetch old or new.
5569                          */
5570                         uid = g_strdup ("1");
5571                 }
5572
5573                 if (ftype == CAMEL_FETCH_NEW_MESSAGES) {
5574                         gboolean success;
5575
5576                         /* We need to issue Status command to get the total unread count */
5577                         ic = camel_imapx_command_new (
5578                                 is, "STATUS", NULL,
5579                                 "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
5580                         camel_imapx_command_set_job (ic, job);
5581                         ic->pri = job->pri;
5582
5583                         success = imapx_command_run_sync (
5584                                 is, ic, cancellable, error);
5585
5586                         camel_imapx_command_unref (ic);
5587
5588                         if (!success) {
5589                                 g_prefix_error (
5590                                         error, "%s: ",
5591                                         _("Error while fetching messages"));
5592                                 g_object_unref (folder);
5593                                 return FALSE;
5594                         }
5595                 }
5596
5597                 camel_operation_push_message (
5598                         cancellable, dngettext (GETTEXT_PACKAGE,
5599                         "Fetching summary information for %d message in '%s'",
5600                         "Fetching summary information for %d messages in '%s'",
5601                         data->fetch_msg_limit),
5602                         data->fetch_msg_limit,
5603                         camel_folder_get_display_name (folder));
5604
5605                 /* New account and fetching old messages, we would return just the limited number of newest messages */
5606                 ic = camel_imapx_command_new (
5607                         is, "FETCH", folder,
5608                         "UID FETCH %s:* (UID FLAGS)", uid);
5609
5610                 imapx_uidset_init (&data->uidset, uidset_size, 0);
5611                 refresh_info_data_infos_free (data);
5612                 data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
5613                 ic->pri = job->pri;
5614
5615                 data->scan_changes = TRUE;
5616
5617                 if (fetch_order == CAMEL_SORT_DESCENDING)
5618                         ic->complete = imapx_command_fetch_new_uids_done;
5619                 else
5620                         ic->complete = imapx_command_step_fetch_done;
5621
5622                 g_free (uid);
5623
5624         } else if (ftype == CAMEL_FETCH_OLD_MESSAGES && total > 0) {
5625                 guint64 uidl;
5626                 start_uid = imapx_get_uid_from_index (folder->summary, 0);
5627                 uidl = strtoull (start_uid, NULL, 10);
5628                 end_uid = g_strdup_printf ("%" G_GINT64_MODIFIER "d", (((gint) uidl) - fetch_limit > 0) ? (uidl - fetch_limit) : 1);
5629
5630                 camel_operation_push_message (
5631                         cancellable, dngettext (GETTEXT_PACKAGE,
5632                         "Fetching summary information for %d message in '%s'",
5633                         "Fetching summary information for %d messages in '%s'",
5634                         data->fetch_msg_limit),
5635                         data->fetch_msg_limit,
5636                         camel_folder_get_display_name (folder));
5637
5638                 ic = camel_imapx_command_new (
5639                         is, "FETCH", folder,
5640                         "UID FETCH %s:%s (RFC822.SIZE RFC822.HEADER FLAGS)", start_uid, end_uid);
5641                 ic->pri = job->pri;
5642                 ic->complete = imapx_command_fetch_new_messages_done;
5643
5644                 g_free (start_uid);
5645                 g_free (end_uid);
5646
5647         } else {
5648                 g_error ("Shouldn't reach here. Incorrect fetch type");
5649         }
5650
5651         camel_imapx_command_set_job (ic, job);
5652
5653         g_object_unref (folder);
5654
5655         return imapx_command_queue (is, ic, cancellable, error);
5656 }
5657
5658 static gboolean
5659 imapx_job_refresh_info_start (CamelIMAPXJob *job,
5660                               CamelIMAPXServer *is,
5661                               GCancellable *cancellable,
5662                               GError **error)
5663 {
5664         CamelIMAPXFolder *ifolder;
5665         CamelIMAPXSettings *settings;
5666         CamelIMAPXSummary *isum;
5667         CamelFolder *folder;
5668         const gchar *full_name;
5669         gboolean need_rescan = FALSE;
5670         gboolean is_selected = FALSE;
5671         gboolean can_qresync = FALSE;
5672         gboolean mobile_mode;
5673         gboolean success;
5674         guint32 total;
5675
5676         folder = camel_imapx_job_ref_folder (job);
5677         g_return_val_if_fail (folder != NULL, FALSE);
5678
5679         settings = camel_imapx_server_ref_settings (is);
5680         mobile_mode = camel_imapx_settings_get_mobile_mode (settings);
5681         g_object_unref (settings);
5682
5683         ifolder = CAMEL_IMAPX_FOLDER (folder);
5684         isum = CAMEL_IMAPX_SUMMARY (folder->summary);
5685
5686         full_name = camel_folder_get_full_name (folder);
5687
5688         /* Sync changes first, else unread count will not
5689          * match. Need to think about better ways for this */
5690         success = imapx_server_sync_changes (
5691                 is, folder, job->type, job->pri, cancellable, error);
5692         if (!success)
5693                 goto done;
5694
5695 #if 0   /* There are issues with this still; continue with the buggy
5696          * behaviour where we issue STATUS on the current folder, for now. */
5697         if (is->select_folder == folder)
5698                 is_selected = TRUE;
5699 #endif
5700         total = camel_folder_summary_count (folder->summary);
5701
5702         if (ifolder->uidvalidity_on_server && isum->validity && isum->validity != ifolder->uidvalidity_on_server) {
5703                 invalidate_local_cache (ifolder, ifolder->uidvalidity_on_server);
5704                 need_rescan = TRUE;
5705         }
5706
5707         /* We don't have valid unread count or modseq for currently-selected server
5708          * (unless we want to re-SELECT it). We fake unread count when fetching
5709          * message flags, but don't depend on modseq for the selected folder */
5710         if (total != ifolder->exists_on_server ||
5711             isum->uidnext != ifolder->uidnext_on_server ||
5712             camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
5713             (!is_selected && isum->modseq != ifolder->modseq_on_server))
5714                 need_rescan = TRUE;
5715
5716         /* This is probably the first check of this folder after startup;
5717          * use STATUS to check whether the cached summary is valid, rather
5718          * than blindly updating. Only for servers which support CONDSTORE
5719          * though. */
5720         if ((isum->modseq && !ifolder->modseq_on_server))
5721                 need_rescan = FALSE;
5722
5723         /* If we don't think there's anything to do, poke it to check */
5724         if (!need_rescan) {
5725                 CamelIMAPXCommand *ic;
5726
5727                 #if 0  /* see comment for disabled bits above */
5728                 if (is_selected) {
5729                         /* We may not issue STATUS on the current folder. Use SELECT or NOOP instead. */
5730                         if (0 /* server needs SELECT not just NOOP */) {
5731                                 if (imapx_idle_supported (is) && imapx_in_idle (is))
5732                                         if (!imapx_stop_idle (is, error))
5733                                                 goto done;
5734                                 /* This doesn't work -- this is an immediate command, not queued */
5735                                 if (!imapx_select (
5736                                         is, folder, TRUE, cancellable, error))
5737                                         goto done;
5738                         } else {
5739                                 /* Or maybe just NOOP, unless we're in IDLE in which case do nothing */
5740                                 if (!imapx_idle_supported (is) || !imapx_in_idle (is)) {
5741                                         if (!camel_imapx_server_noop (is, folder, cancellable, error))
5742                                                 goto done;
5743                                 }
5744                         }
5745                 } else
5746                 #endif
5747                 {
5748                         if (is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_CONDSTORE) != 0)
5749                                 ic = camel_imapx_command_new (
5750                                         is, "STATUS", NULL,
5751                                         "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT HIGHESTMODSEQ)", folder);
5752                         else
5753                                 ic = camel_imapx_command_new (
5754                                         is, "STATUS", NULL,
5755                                         "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
5756
5757                         camel_imapx_command_set_job (ic, job);
5758                         ic->pri = job->pri;
5759
5760                         success = imapx_command_run_sync (
5761                                 is, ic, cancellable, error);
5762
5763                         camel_imapx_command_unref (ic);
5764
5765                         if (!success) {
5766                                 g_prefix_error (
5767                                         error, "%s: ",
5768                                         _("Error refreshing folder"));
5769                                 goto done;
5770                         }
5771                 }
5772
5773                 /* Recalulate need_rescan */
5774                 if (total != ifolder->exists_on_server ||
5775                     isum->uidnext != ifolder->uidnext_on_server ||
5776                     camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
5777                     (!is_selected && isum->modseq != ifolder->modseq_on_server))
5778                         need_rescan = TRUE;
5779
5780         } else if (mobile_mode) {
5781                 /* We need to issue Status command to get the total unread count */
5782                 CamelIMAPXCommand *ic;
5783
5784                 ic = camel_imapx_command_new (
5785                         is, "STATUS", NULL,
5786                         "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
5787                 camel_imapx_command_set_job (ic, job);
5788                 ic->pri = job->pri;
5789
5790                 success = imapx_command_run_sync (
5791                         is, ic, cancellable, error);
5792
5793                 camel_imapx_command_unref (ic);
5794
5795                 if (!success) {
5796                         g_prefix_error (
5797                                 error, "%s: ",
5798                                 _("Error refreshing folder"));
5799                         goto done;
5800                 }
5801         }
5802
5803         if (is->use_qresync && isum->modseq && ifolder->uidvalidity_on_server)
5804                 can_qresync = TRUE;
5805
5806         e (
5807                 is->tagprefix,
5808                 "folder %s is %sselected, "
5809                 "total %u / %u, unread %u / %u, modseq %"
5810                 G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT
5811                 ", uidnext %u / %u: will %srescan\n",
5812                 full_name,
5813                 is_selected ? "" : "not ",
5814                 total,
5815                 ifolder->exists_on_server,
5816                 camel_folder_summary_get_unread_count (folder->summary),
5817                 ifolder->unread_on_server,
5818                 (guint64) isum->modseq,
5819                 (guint64) ifolder->modseq_on_server,
5820                 isum->uidnext,
5821                 ifolder->uidnext_on_server,
5822                 need_rescan ? "" : "not ");
5823
5824         /* Fetch new messages first, so that they appear to the user ASAP */
5825         if (ifolder->exists_on_server > total ||
5826             ifolder->uidnext_on_server > isum->uidnext)
5827         {
5828                 if (!total)
5829                         need_rescan = FALSE;
5830
5831                 success = imapx_server_fetch_new_messages (
5832                         is, folder, FALSE, FALSE, cancellable, error);
5833                 if (!success)
5834                         goto done;
5835
5836                 /* If QRESYNC-capable we'll have got all flags changes in SELECT */
5837                 if (can_qresync)
5838                         goto qresync_done;
5839         }
5840
5841         if (!need_rescan)
5842                 goto done;
5843
5844         if (can_qresync) {
5845                 /* Actually we only want to select it; no need for the NOOP */
5846                 success = camel_imapx_server_noop (
5847                         is, folder, cancellable, error);
5848                 if (!success)
5849                         goto done;
5850         qresync_done:
5851                 isum->modseq = ifolder->modseq_on_server;
5852                 total = camel_folder_summary_count (folder->summary);
5853                 if (total != ifolder->exists_on_server ||
5854                     camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
5855                     (isum->modseq != ifolder->modseq_on_server)) {
5856                         c (
5857                                 is->tagprefix,
5858                                 "Eep, after QRESYNC we're out of sync. "
5859                                 "total %u / %u, unread %u / %u, modseq %"
5860                                 G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT "\n",
5861                                 total, ifolder->exists_on_server,
5862                                 camel_folder_summary_get_unread_count (folder->summary),
5863                                 ifolder->unread_on_server,
5864                                 isum->modseq,
5865                                 ifolder->modseq_on_server);
5866                 } else {
5867                         c (
5868                                 is->tagprefix,
5869                                 "OK, after QRESYNC we're still in sync. "
5870                                 "total %u / %u, unread %u / %u, modseq %"
5871                                 G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT "\n",
5872                                 total, ifolder->exists_on_server,
5873                                 camel_folder_summary_get_unread_count (folder->summary),
5874                                 ifolder->unread_on_server,
5875                                 isum->modseq,
5876                                 ifolder->modseq_on_server);
5877                         goto done;
5878                 }
5879         }
5880
5881         g_object_unref (folder);
5882
5883         return imapx_job_scan_changes_start (job, is, cancellable, error);
5884
5885 done:
5886         g_object_unref (folder);
5887
5888         imapx_unregister_job (is, job);
5889
5890         return success;
5891 }
5892
5893 static gboolean
5894 imapx_job_refresh_info_matches (CamelIMAPXJob *job,
5895                                 CamelFolder *folder,
5896                                 const gchar *uid)
5897 {
5898         return camel_imapx_job_has_folder (job, folder);
5899 }
5900
5901 /* ********************************************************************** */
5902
5903 static gboolean
5904 imapx_command_expunge_done (CamelIMAPXServer *is,
5905                             CamelIMAPXCommand *ic,
5906                             GCancellable *cancellable,
5907                             GError **error)
5908 {
5909         CamelIMAPXJob *job;
5910         CamelFolder *folder;
5911         gboolean success = TRUE;
5912
5913         job = camel_imapx_command_get_job (ic);
5914         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
5915
5916         folder = camel_imapx_job_ref_folder (job);
5917         g_return_val_if_fail (folder != NULL, FALSE);
5918
5919         if (camel_imapx_command_set_error_if_failed (ic, error)) {
5920                 g_prefix_error (
5921                         error, "%s: ",
5922                         _("Error expunging message"));
5923                 success = FALSE;
5924
5925         } else {
5926                 GPtrArray *uids;
5927                 CamelStore *parent_store;
5928                 const gchar *full_name;
5929
5930                 full_name = camel_folder_get_full_name (folder);
5931                 parent_store = camel_folder_get_parent_store (folder);
5932
5933                 camel_folder_summary_save_to_db (folder->summary, NULL);
5934                 uids = camel_db_get_folder_deleted_uids (parent_store->cdb_r, full_name, error);
5935
5936                 if (uids && uids->len)  {
5937                         CamelFolderChangeInfo *changes;
5938                         GList *removed = NULL;
5939                         gint i;
5940
5941                         changes = camel_folder_change_info_new ();
5942                         for (i = 0; i < uids->len; i++) {
5943                                 gchar *uid = uids->pdata[i];
5944                                 CamelMessageInfo *mi;
5945
5946                                 mi = camel_folder_summary_peek_loaded (folder->summary, uid);
5947                                 if (mi) {
5948                                         camel_folder_summary_remove (folder->summary, mi);
5949                                         camel_message_info_free (mi);
5950                                 } else {
5951                                         camel_folder_summary_remove_uid (folder->summary, uid);
5952                                 }
5953
5954                                 camel_folder_change_info_remove_uid (changes, uids->pdata[i]);
5955                                 removed = g_list_prepend (removed, (gpointer) uids->pdata[i]);
5956                         }
5957
5958                         camel_folder_summary_save_to_db (folder->summary, NULL);
5959                         camel_folder_changed (folder, changes);
5960                         camel_folder_change_info_free (changes);
5961
5962                         g_list_free (removed);
5963                         g_ptr_array_foreach (uids, (GFunc) camel_pstring_free, NULL);
5964                         g_ptr_array_free (uids, TRUE);
5965                 }
5966         }
5967
5968         g_object_unref (folder);
5969
5970         imapx_unregister_job (is, job);
5971         camel_imapx_command_unref (ic);
5972
5973         return success;
5974 }
5975
5976 static gboolean
5977 imapx_job_expunge_start (CamelIMAPXJob *job,
5978                          CamelIMAPXServer *is,
5979                          GCancellable *cancellable,
5980                          GError **error)
5981 {
5982         CamelIMAPXCommand *ic;
5983         CamelFolder *folder;
5984         gboolean success;
5985
5986         folder = camel_imapx_job_ref_folder (job);
5987         g_return_val_if_fail (folder != NULL, FALSE);
5988
5989         success = imapx_server_sync_changes (
5990                 is, folder, job->type, job->pri, cancellable, error);
5991
5992         if (success) {
5993                 /* TODO handle UIDPLUS capability */
5994                 ic = camel_imapx_command_new (
5995                         is, "EXPUNGE", folder, "EXPUNGE");
5996                 camel_imapx_command_set_job (ic, job);
5997                 ic->pri = job->pri;
5998                 ic->complete = imapx_command_expunge_done;
5999
6000                 success = imapx_command_queue (is, ic, cancellable, error);
6001         }
6002
6003         g_object_unref (folder);
6004
6005         return success;
6006 }
6007
6008 static gboolean
6009 imapx_job_expunge_matches (CamelIMAPXJob *job,
6010                            CamelFolder *folder,
6011                            const gchar *uid)
6012 {
6013         return camel_imapx_job_has_folder (job, folder);
6014 }
6015
6016 /* ********************************************************************** */
6017
6018 static gboolean
6019 imapx_command_list_done (CamelIMAPXServer *is,
6020                          CamelIMAPXCommand *ic,
6021                          GCancellable *cancellable,
6022                          GError **error)
6023 {
6024         CamelIMAPXJob *job;
6025         gboolean success = TRUE;
6026
6027         job = camel_imapx_command_get_job (ic);
6028         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6029
6030         if (camel_imapx_command_set_error_if_failed (ic, error)) {
6031                 g_prefix_error (
6032                         error, "%s: ",
6033                         _("Error fetching folders"));
6034                 success = FALSE;
6035         }
6036
6037         e (is->tagprefix, "==== list or lsub completed ==== \n");
6038         imapx_unregister_job (is, job);
6039         camel_imapx_command_unref (ic);
6040
6041         return success;
6042 }
6043
6044 static gboolean
6045 imapx_job_list_start (CamelIMAPXJob *job,
6046                       CamelIMAPXServer *is,
6047                       GCancellable *cancellable,
6048                       GError **error)
6049 {
6050         CamelIMAPXCommand *ic;
6051         ListData *data;
6052
6053         data = camel_imapx_job_get_data (job);
6054         g_return_val_if_fail (data != NULL, FALSE);
6055
6056         ic = camel_imapx_command_new (
6057                 is, "LIST", NULL,
6058                 "%s \"\" %s",
6059                 (data->flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) ?
6060                         "LSUB" : "LIST",
6061                 data->pattern);
6062         if (data->ext) {
6063                 /* Hm, we need a way to add atoms _without_ quoting or using literals */
6064                 camel_imapx_command_add (ic, " ");
6065                 camel_imapx_command_add (ic, data->ext);
6066         }
6067         ic->pri = job->pri;
6068         camel_imapx_command_set_job (ic, job);
6069         ic->complete = imapx_command_list_done;
6070
6071         return imapx_command_queue (is, ic, cancellable, error);
6072 }
6073
6074 static gboolean
6075 imapx_job_list_matches (CamelIMAPXJob *job,
6076                         CamelFolder *folder,
6077                         const gchar *uid)
6078 {
6079         return TRUE;  /* matches everything */
6080 }
6081
6082 /* ********************************************************************** */
6083
6084 static gchar *
6085 imapx_encode_folder_name (CamelIMAPXStore *istore,
6086                           const gchar *folder_name)
6087 {
6088         gchar *fname, *encoded;
6089
6090         fname = camel_imapx_store_summary_full_from_path (istore->summary, folder_name);
6091         if (fname) {
6092                 encoded = camel_utf8_utf7 (fname);
6093                 g_free (fname);
6094         } else
6095                 encoded = camel_utf8_utf7 (folder_name);
6096
6097         return encoded;
6098 }
6099
6100 static gboolean
6101 imapx_command_subscription_done (CamelIMAPXServer *is,
6102                                  CamelIMAPXCommand *ic,
6103                                  GCancellable *cancellable,
6104                                  GError **error)
6105 {
6106         CamelIMAPXJob *job;
6107         gboolean success = TRUE;
6108
6109         job = camel_imapx_command_get_job (ic);
6110         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6111
6112         if (camel_imapx_command_set_error_if_failed (ic, error)) {
6113                 g_prefix_error (
6114                         error, "%s: ",
6115                         _("Error subscribing to folder"));
6116                 success = FALSE;
6117         }
6118
6119         imapx_unregister_job (is, job);
6120         camel_imapx_command_unref (ic);
6121
6122         return success;
6123 }
6124
6125 static gboolean
6126 imapx_job_manage_subscription_start (CamelIMAPXJob *job,
6127                                      CamelIMAPXServer *is,
6128                                      GCancellable *cancellable,
6129                                      GError **error)
6130 {
6131         CamelIMAPXCommand *ic;
6132         CamelIMAPXStore *store;
6133         ManageSubscriptionsData *data;
6134         gchar *encoded_fname = NULL;
6135
6136         data = camel_imapx_job_get_data (job);
6137         g_return_val_if_fail (data != NULL, FALSE);
6138
6139         store = camel_imapx_server_ref_store (is);
6140
6141         encoded_fname = imapx_encode_folder_name (store, data->folder_name);
6142
6143         if (data->subscribe)
6144                 ic = camel_imapx_command_new (
6145                         is, "SUBSCRIBE", NULL,
6146                         "SUBSCRIBE %s", encoded_fname);
6147         else
6148                 ic = camel_imapx_command_new (
6149                         is, "UNSUBSCRIBE", NULL,
6150                         "UNSUBSCRIBE %s", encoded_fname);
6151
6152         ic->pri = job->pri;
6153         camel_imapx_command_set_job (ic, job);
6154         ic->complete = imapx_command_subscription_done;
6155
6156         g_free (encoded_fname);
6157
6158         g_object_unref (store);
6159
6160         return imapx_command_queue (is, ic, cancellable, error);
6161 }
6162
6163 /* ********************************************************************** */
6164
6165 static gboolean
6166 imapx_command_create_folder_done (CamelIMAPXServer *is,
6167                                   CamelIMAPXCommand *ic,
6168                                   GCancellable *cancellable,
6169                                   GError **error)
6170 {
6171         CamelIMAPXJob *job;
6172         gboolean success = TRUE;
6173
6174         job = camel_imapx_command_get_job (ic);
6175         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6176
6177         if (camel_imapx_command_set_error_if_failed (ic, error)) {
6178                 g_prefix_error (
6179                         error, "%s: ",
6180                         _("Error creating folder"));
6181                 success = FALSE;
6182         }
6183
6184         imapx_unregister_job (is, job);
6185         camel_imapx_command_unref (ic);
6186
6187         return success;
6188 }
6189
6190 static gboolean
6191 imapx_job_create_folder_start (CamelIMAPXJob *job,
6192                                CamelIMAPXServer *is,
6193                                GCancellable *cancellable,
6194                                GError **error)
6195 {
6196         CamelIMAPXCommand *ic;
6197         CreateFolderData *data;
6198         gchar *encoded_fname = NULL;
6199
6200         data = camel_imapx_job_get_data (job);
6201         g_return_val_if_fail (data != NULL, FALSE);
6202
6203         encoded_fname = camel_utf8_utf7 (data->folder_name);
6204
6205         ic = camel_imapx_command_new (
6206                 is, "CREATE", NULL,
6207                 "CREATE %s", encoded_fname);
6208         ic->pri = job->pri;
6209         camel_imapx_command_set_job (ic, job);
6210         ic->complete = imapx_command_create_folder_done;
6211
6212         g_free (encoded_fname);
6213
6214         return imapx_command_queue (is, ic, cancellable, error);
6215 }
6216
6217 /* ********************************************************************** */
6218
6219 static gboolean
6220 imapx_command_delete_folder_done (CamelIMAPXServer *is,
6221                                   CamelIMAPXCommand *ic,
6222                                   GCancellable *cancellable,
6223                                   GError **error)
6224 {
6225         CamelIMAPXJob *job;
6226         gboolean success = TRUE;
6227
6228         job = camel_imapx_command_get_job (ic);
6229         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6230
6231         if (camel_imapx_command_set_error_if_failed (ic, error)) {
6232                 g_prefix_error (
6233                         error, "%s: ",
6234                         _("Error deleting folder"));
6235                 success = FALSE;
6236         }
6237
6238         imapx_unregister_job (is, job);
6239         camel_imapx_command_unref (ic);
6240
6241         return success;
6242 }
6243
6244 static gboolean
6245 imapx_job_delete_folder_start (CamelIMAPXJob *job,
6246                                CamelIMAPXServer *is,
6247                                GCancellable *cancellable,
6248                                GError **error)
6249 {
6250         CamelIMAPXCommand *ic;
6251         CamelIMAPXStore *store;
6252         CamelFolder *folder;
6253         DeleteFolderData *data;
6254         gchar *encoded_fname = NULL;
6255         gboolean success = FALSE;
6256
6257         data = camel_imapx_job_get_data (job);
6258         g_return_val_if_fail (data != NULL, FALSE);
6259
6260         store = camel_imapx_server_ref_store (is);
6261
6262         encoded_fname = imapx_encode_folder_name (store, data->folder_name);
6263
6264         folder = camel_store_get_folder_sync (
6265                 CAMEL_STORE (store), "INBOX", 0, cancellable, error);
6266
6267         if (folder != NULL) {
6268                 camel_imapx_job_set_folder (job, folder);
6269
6270                 /* Make sure the to-be-deleted folder is not
6271                  * selected by selecting INBOX for this operation. */
6272                 ic = camel_imapx_command_new (
6273                         is, "DELETE", folder,
6274                         "DELETE %s", encoded_fname);
6275                 ic->pri = job->pri;
6276                 camel_imapx_command_set_job (ic, job);
6277                 ic->complete = imapx_command_delete_folder_done;
6278
6279                 success = imapx_command_queue (is, ic, cancellable, error);
6280
6281                 g_object_unref (folder);
6282         }
6283
6284         g_free (encoded_fname);
6285
6286         g_object_unref (store);
6287
6288         return success;
6289 }
6290
6291 /* ********************************************************************** */
6292
6293 static gboolean
6294 imapx_command_rename_folder_done (CamelIMAPXServer *is,
6295                                   CamelIMAPXCommand *ic,
6296                                   GCancellable *cancellable,
6297                                   GError **error)
6298 {
6299         CamelIMAPXJob *job;
6300         gboolean success = TRUE;
6301
6302         job = camel_imapx_command_get_job (ic);
6303         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6304
6305         if (camel_imapx_command_set_error_if_failed (ic, error)) {
6306                 g_prefix_error (
6307                         error, "%s: ",
6308                         _("Error renaming folder"));
6309                 success = FALSE;
6310         }
6311
6312         imapx_unregister_job (is, job);
6313         camel_imapx_command_unref (ic);
6314
6315         return success;
6316 }
6317
6318 static gboolean
6319 imapx_job_rename_folder_start (CamelIMAPXJob *job,
6320                                CamelIMAPXServer *is,
6321                                GCancellable *cancellable,
6322                                GError **error)
6323 {
6324         CamelIMAPXCommand *ic;
6325         CamelIMAPXStore *store;
6326         RenameFolderData *data;
6327         CamelFolder *folder;
6328         gchar *en_ofname = NULL;
6329         gchar *en_nfname = NULL;
6330         gboolean success = FALSE;
6331
6332         data = camel_imapx_job_get_data (job);
6333         g_return_val_if_fail (data != NULL, FALSE);
6334
6335         store = camel_imapx_server_ref_store (is);
6336
6337         en_ofname = imapx_encode_folder_name (store, data->old_folder_name);
6338         en_nfname = imapx_encode_folder_name (store, data->new_folder_name);
6339
6340         folder = camel_store_get_folder_sync (
6341                 CAMEL_STORE (store), "INBOX", 0, cancellable, error);
6342
6343         if (folder != NULL) {
6344                 camel_imapx_job_set_folder (job, folder);
6345
6346                 ic = camel_imapx_command_new (
6347                         is, "RENAME", folder,
6348                         "RENAME %s %s", en_ofname, en_nfname);
6349                 ic->pri = job->pri;
6350                 camel_imapx_command_set_job (ic, job);
6351                 ic->complete = imapx_command_rename_folder_done;
6352
6353                 success = imapx_command_queue (is, ic, cancellable, error);
6354
6355                 g_object_unref (folder);
6356         }
6357
6358         g_free (en_ofname);
6359         g_free (en_nfname);
6360
6361         g_object_unref (store);
6362
6363         return success;
6364 }
6365
6366 /* ********************************************************************** */
6367
6368 static gboolean
6369 imapx_command_update_quota_info_done (CamelIMAPXServer *is,
6370                                       CamelIMAPXCommand *ic,
6371                                       GCancellable *cancellable,
6372                                       GError **error)
6373 {
6374         CamelIMAPXJob *job;
6375         gboolean success = TRUE;
6376
6377         job = camel_imapx_command_get_job (ic);
6378         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6379
6380         if (camel_imapx_command_set_error_if_failed (ic, error)) {
6381                 g_prefix_error (
6382                         error, "%s: ",
6383                         _("Error retrieving quota information"));
6384                 success = FALSE;
6385         }
6386
6387         imapx_unregister_job (is, job);
6388         camel_imapx_command_unref (ic);
6389
6390         return success;
6391 }
6392
6393 static gboolean
6394 imapx_job_update_quota_info_start (CamelIMAPXJob *job,
6395                                    CamelIMAPXServer *is,
6396                                    GCancellable *cancellable,
6397                                    GError **error)
6398 {
6399         CamelIMAPXCommand *ic;
6400         CamelIMAPXStore *store;
6401         QuotaData *data;
6402         gchar *encoded_folder_name;
6403         gboolean success;
6404
6405         data = camel_imapx_job_get_data (job);
6406         g_return_val_if_fail (data != NULL, FALSE);
6407
6408         store = camel_imapx_server_ref_store (is);
6409
6410         encoded_folder_name =
6411                 imapx_encode_folder_name (store, data->folder_name);
6412
6413         ic = camel_imapx_command_new (
6414                 is, "GETQUOTAROOT", NULL,
6415                 "GETQUOTAROOT %s", encoded_folder_name);
6416         ic->pri = job->pri;
6417         camel_imapx_command_set_job (ic, job);
6418         ic->complete = imapx_command_update_quota_info_done;
6419
6420         success = imapx_command_queue (is, ic, cancellable, error);
6421
6422         g_free (encoded_folder_name);
6423
6424         g_object_unref (store);
6425
6426         return success;
6427 }
6428
6429 /* ********************************************************************** */
6430
6431 static gboolean
6432 imapx_command_uid_search_done (CamelIMAPXServer *is,
6433                                CamelIMAPXCommand *ic,
6434                                GCancellable *cancellable,
6435                                GError **error)
6436 {
6437         CamelIMAPXJob *job;
6438         SearchData *data;
6439         gboolean success = TRUE;
6440
6441         job = camel_imapx_command_get_job (ic);
6442         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6443
6444         data = camel_imapx_job_get_data (job);
6445         g_return_val_if_fail (data != NULL, FALSE);
6446
6447         if (camel_imapx_command_set_error_if_failed (ic, error)) {
6448                 g_prefix_error (error, "%s: ", _("Search failed"));
6449                 success = FALSE;
6450         }
6451
6452         /* Don't worry about the success state and presence of search
6453          * results not agreeing here.  camel_imapx_server_uid_search()
6454          * will disregard the search results if an error occurred. */
6455         g_mutex_lock (&is->priv->search_results_lock);
6456         data->results = is->priv->search_results;
6457         is->priv->search_results = NULL;
6458         g_mutex_unlock (&is->priv->search_results_lock);
6459
6460         imapx_unregister_job (is, job);
6461         camel_imapx_command_unref (ic);
6462
6463         return success;
6464 }
6465
6466 static gboolean
6467 imapx_job_uid_search_start (CamelIMAPXJob *job,
6468                             CamelIMAPXServer *is,
6469                             GCancellable *cancellable,
6470                             GError **error)
6471 {
6472         CamelFolder *folder;
6473         CamelIMAPXCommand *ic;
6474         SearchData *data;
6475
6476         data = camel_imapx_job_get_data (job);
6477         g_return_val_if_fail (data != NULL, FALSE);
6478
6479         folder = camel_imapx_job_ref_folder (job);
6480         g_return_val_if_fail (folder != NULL, FALSE);
6481
6482         ic = camel_imapx_command_new (
6483                 is, "UID SEARCH", folder,
6484                 "UID SEARCH %t", data->criteria);
6485         ic->pri = job->pri;
6486         camel_imapx_command_set_job (ic, job);
6487         ic->complete = imapx_command_uid_search_done;
6488
6489         g_object_unref (folder);
6490
6491         return imapx_command_queue (is, ic, cancellable, error);
6492 }
6493
6494 /* ********************************************************************** */
6495
6496 static gboolean
6497 imapx_command_noop_done (CamelIMAPXServer *is,
6498                          CamelIMAPXCommand *ic,
6499                          GCancellable *cancellable,
6500                          GError **error)
6501 {
6502         CamelIMAPXJob *job;
6503         gboolean success = TRUE;
6504
6505         job = camel_imapx_command_get_job (ic);
6506         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6507
6508         if (camel_imapx_command_set_error_if_failed (ic, error)) {
6509                 g_prefix_error (
6510                         error, "%s: ",
6511                         _("Error performing NOOP"));
6512                 success = FALSE;
6513         }
6514
6515         imapx_unregister_job (is, job);
6516         camel_imapx_command_unref (ic);
6517
6518         return success;
6519 }
6520
6521 static gboolean
6522 imapx_job_noop_start (CamelIMAPXJob *job,
6523                       CamelIMAPXServer *is,
6524                       GCancellable *cancellable,
6525                       GError **error)
6526 {
6527         CamelIMAPXCommand *ic;
6528         CamelFolder *folder;
6529
6530         folder = camel_imapx_job_ref_folder (job);
6531
6532         ic = camel_imapx_command_new (
6533                 is, "NOOP", folder, "NOOP");
6534
6535         camel_imapx_command_set_job (ic, job);
6536         ic->complete = imapx_command_noop_done;
6537         if (folder != NULL) {
6538                 ic->pri = IMAPX_PRIORITY_REFRESH_INFO;
6539                 g_object_unref (folder);
6540         } else {
6541                 ic->pri = IMAPX_PRIORITY_NOOP;
6542         }
6543
6544         return imapx_command_queue (is, ic, cancellable, error);
6545 }
6546
6547 /* ********************************************************************** */
6548
6549 /* FIXME: this is basically a copy of the same in camel-imapx-utils.c */
6550 static struct {
6551         const gchar *name;
6552         guint32 flag;
6553 } flags_table[] = {
6554         { "\\ANSWERED", CAMEL_MESSAGE_ANSWERED },
6555         { "\\DELETED", CAMEL_MESSAGE_DELETED },
6556         { "\\DRAFT", CAMEL_MESSAGE_DRAFT },
6557         { "\\FLAGGED", CAMEL_MESSAGE_FLAGGED },
6558         { "\\SEEN", CAMEL_MESSAGE_SEEN },
6559         { "\\RECENT", CAMEL_IMAPX_MESSAGE_RECENT },
6560         { "JUNK", CAMEL_MESSAGE_JUNK },
6561         { "NOTJUNK", CAMEL_MESSAGE_NOTJUNK }
6562 };
6563
6564 /*
6565  *  flags 00101000
6566  * sflags 01001000
6567  * ^      01100000
6568  * ~flags 11010111
6569  * &      01000000
6570  *
6571  * &flags 00100000
6572  */
6573
6574 static gboolean
6575 imapx_command_sync_changes_done (CamelIMAPXServer *is,
6576                                  CamelIMAPXCommand *ic,
6577                                  GCancellable *cancellable,
6578                                  GError **error)
6579 {
6580         CamelIMAPXJob *job;
6581         CamelFolder *folder;
6582         CamelStore *parent_store;
6583         SyncChangesData *data;
6584         const gchar *full_name;
6585         CamelIMAPXSettings *settings;
6586         gboolean mobile_mode;
6587         gboolean success = TRUE;
6588
6589         job = camel_imapx_command_get_job (ic);
6590         g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6591
6592         data = camel_imapx_job_get_data (job);
6593         g_return_val_if_fail (data != NULL, FALSE);
6594
6595         folder = camel_imapx_job_ref_folder (job);
6596         g_return_val_if_fail (folder != NULL, FALSE);
6597
6598         settings = camel_imapx_server_ref_settings (is);
6599         mobile_mode = camel_imapx_settings_get_mobile_mode (settings);
6600         g_object_unref (settings);
6601
6602         job->commands--;
6603
6604         full_name = camel_folder_get_full_name (folder);
6605         parent_store = camel_folder_get_parent_store (folder);
6606
6607         /* If this worked, we should really just update the changes that we
6608          * sucessfully stored, so we dont have to worry about sending them
6609          * again ...
6610          * But then we'd have to track which uid's we actually updated, so
6611          * its easier just to refresh all of the ones we got.
6612          *
6613          * Not that ... given all the asynchronicity going on, we're guaranteed
6614          * that what we just set is actually what is on the server now .. but
6615          * if it isn't, i guess we'll fix up next refresh */
6616
6617         if (camel_imapx_command_set_error_if_failed (ic, error)) {
6618                 g_prefix_error (
6619                         error, "%s: ",
6620                         _("Error syncing changes"));
6621                 success = FALSE;
6622
6623         /* lock cache ? */
6624         } else {
6625                 gint i;
6626
6627                 for (i = 0; i < data->changed_uids->len; i++) {
6628                         CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) camel_folder_summary_get (folder->summary,
6629                                         data->changed_uids->pdata[i]);
6630
6631                         if (!xinfo)
6632                                 continue;
6633
6634                         if (data->remove_deleted_flags)
6635                                 xinfo->info.flags &= ~CAMEL_MESSAGE_DELETED;
6636                         xinfo->server_flags = xinfo->info.flags & CAMEL_IMAPX_SERVER_FLAGS;
6637                         xinfo->info.flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
6638                         xinfo->info.dirty = TRUE;
6639                         camel_flag_list_copy (&xinfo->server_user_flags, &xinfo->info.user_flags);
6640
6641                         camel_folder_summary_touch (folder->summary);
6642                         camel_message_info_free (xinfo);
6643                 }
6644                 /* Apply the changes to server-side unread count; it won't tell
6645                  * us of these changes, of course. */
6646                 ((CamelIMAPXFolder *) folder)->unread_on_server += data->unread_change;
6647         }
6648
6649         if (job->commands == 0) {
6650                 if (folder->summary && (folder->summary->flags & CAMEL_FOLDER_SUMMARY_DIRTY) != 0) {
6651                         CamelStoreInfo *si;
6652
6653                         /* ... and store's summary when folder's summary is dirty */
6654                         si = camel_store_summary_path ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary, full_name);
6655                         if (si) {
6656                                 if (si->total != camel_folder_summary_get_saved_count (folder->summary) ||
6657                                     si->unread != camel_folder_summary_get_unread_count (folder->summary)) {
6658                                         si->total = camel_folder_summary_get_saved_count (folder->summary);
6659                                         /* Don't mess with server's unread
6660                                          * count in mobile mode, as what we
6661                                          * have downloaded is little. */
6662                                         if (!mobile_mode)
6663                                                 si->unread = camel_folder_summary_get_unread_count (folder->summary);
6664                                         camel_store_summary_touch ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
6665                                 }
6666
6667                                 camel_store_summary_info_free ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary, si);
6668                         }
6669                 }
6670
6671                 camel_folder_summary_save_to_db (folder->summary, error);
6672                 camel_store_summary_save ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
6673
6674                 imapx_unregister_job (is, job);
6675         }
6676
6677         g_object_unref (folder);
6678
6679         camel_imapx_command_unref (ic);
6680
6681         return success;
6682 }
6683
6684 static gboolean
6685 imapx_job_sync_changes_start (CamelIMAPXJob *job,
6686                               CamelIMAPXServer *is,
6687                               GCancellable *cancellable,
6688                               GError **error)
6689 {
6690         SyncChangesData *data;
6691         CamelFolder *folder;
6692         guint32 i, j;
6693         struct _uidset_state ss;
6694         GPtrArray *uids;
6695         gint on;
6696
6697         data = camel_imapx_job_get_data (job);
6698         g_return_val_if_fail (data != NULL, FALSE);
6699
6700         folder = camel_imapx_job_ref_folder (job);
6701         g_return_val_if_fail (folder != NULL, FALSE);
6702
6703         uids = data->changed_uids;
6704
6705         for (on = 0; on < 2; on++) {
6706                 guint32 orset = on ? data->on_set : data->off_set;
6707                 GArray *user_set = on ? data->on_user : data->off_user;
6708
6709                 for (j = 0; j < G_N_ELEMENTS (flags_table); j++) {
6710                         guint32 flag = flags_table[j].flag;
6711                         CamelIMAPXCommand *ic = NULL;
6712
6713                         if ((orset & flag) == 0)
6714                                 continue;
6715
6716                         c (is->tagprefix, "checking/storing %s flags '%s'\n", on?"on":"off", flags_table[j].name);
6717                         imapx_uidset_init (&ss, 0, 100);
6718                         for (i = 0; i < uids->len; i++) {
6719                                 CamelIMAPXMessageInfo *info;
6720                                 gboolean remove_deleted_flag;
6721                                 guint32 flags;
6722                                 guint32 sflags;
6723                                 gint send;
6724
6725                                 info = (CamelIMAPXMessageInfo *)
6726                                         camel_folder_summary_get (
6727                                                 folder->summary,
6728                                                 uids->pdata[i]);
6729
6730                                 if (info == NULL)
6731                                         continue;
6732
6733                                 flags = info->info.flags & CAMEL_IMAPX_SERVER_FLAGS;
6734                                 sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
6735                                 send = 0;
6736
6737                                 remove_deleted_flag =
6738                                         data->remove_deleted_flags &&
6739                                         (flags & CAMEL_MESSAGE_DELETED);
6740
6741                                 if (remove_deleted_flag) {
6742                                         /* Remove the DELETED flag so the
6743                                          * message appears normally in the
6744                                          * real Trash folder when copied. */
6745                                         flags &= ~CAMEL_MESSAGE_DELETED;
6746                                 }
6747
6748                                 if ( (on && (((flags ^ sflags) & flags) & flag))
6749                                      || (!on && (((flags ^ sflags) & ~flags) & flag))) {
6750                                         if (ic == NULL) {
6751                                                 ic = camel_imapx_command_new (
6752                                                         is, "STORE", folder,
6753                                                         "UID STORE ");
6754                                                 ic->complete = imapx_command_sync_changes_done;
6755                                                 camel_imapx_command_set_job (ic, job);
6756                                                 ic->pri = job->pri;
6757                                         }
6758                                         send = imapx_uidset_add (&ss, ic, camel_message_info_uid (info));
6759                                 }
6760                                 if (send == 1 || (i == uids->len - 1 && imapx_uidset_done (&ss, ic))) {
6761                                         job->commands++;
6762                                         camel_imapx_command_add (ic, " %tFLAGS.SILENT (%t)", on?"+":"-", flags_table[j].name);
6763                                         if (!imapx_command_queue (is, ic, cancellable, error)) {
6764                                                 camel_message_info_free (info);
6765                                                 goto exit;
6766                                         }
6767                                         ic = NULL;
6768                                 }
6769                                 if (flag == CAMEL_MESSAGE_SEEN) {
6770                                         /* Remember how the server's unread count will change if this
6771                                          * command succeeds */
6772                                         if (on)
6773                                                 data->unread_change--;
6774                                         else
6775                                                 data->unread_change++;
6776                                 }
6777                                 camel_message_info_free (info);
6778                         }
6779                 }
6780
6781                 if (user_set) {
6782                         CamelIMAPXCommand *ic = NULL;
6783
6784                         for (j = 0; j < user_set->len; j++) {
6785                                 struct _imapx_flag_change *c = &g_array_index (user_set, struct _imapx_flag_change, j);
6786
6787                                 imapx_uidset_init (&ss, 0, 100);
6788                                 for (i = 0; i < c->infos->len; i++) {
6789                                         CamelIMAPXMessageInfo *info = c->infos->pdata[i];
6790
6791                                         if (ic == NULL) {
6792                                                 ic = camel_imapx_command_new (
6793                                                         is, "STORE", folder,
6794                                                         "UID STORE ");
6795                                                 ic->complete = imapx_command_sync_changes_done;
6796                                                 camel_imapx_command_set_job (ic, job);
6797                                                 ic->pri = job->pri;
6798                                         }
6799
6800                                         if (imapx_uidset_add (&ss, ic, camel_message_info_uid (info)) == 1
6801                                             || (i == c->infos->len - 1 && imapx_uidset_done (&ss, ic))) {
6802                                                 job->commands++;
6803                                                 camel_imapx_command_add (ic, " %tFLAGS.SILENT (%t)", on?"+":"-", c->name);
6804                                                 if (!imapx_command_queue (is, ic, cancellable, error))
6805                                                         goto exit;
6806                                                 ic = NULL;
6807                                         }
6808                                 }
6809                         }
6810                 }
6811         }
6812
6813 exit:
6814         g_object_unref (folder);
6815
6816         /* Since this may start in another thread ... we need to
6817          * lock the commands count, ho hum */
6818
6819         if (job->commands == 0)
6820                 imapx_unregister_job (is, job);
6821
6822         return TRUE;
6823 }
6824
6825 static gboolean
6826 imapx_job_sync_changes_matches (CamelIMAPXJob *job,
6827                                 CamelFolder *folder,
6828                                 const gchar *uid)
6829 {
6830         return camel_imapx_job_has_folder (job, folder);
6831 }
6832
6833 /* we cancel all the commands and their jobs, so associated jobs will be notified */
6834 static void
6835 cancel_all_jobs (CamelIMAPXServer *is,
6836                  GError *error)
6837 {
6838         CamelIMAPXCommandQueue *queue;
6839         GList *head, *link;
6840
6841         /* Transfer all pending and active commands to a separate
6842          * command queue to complete them without holding QUEUE_LOCK. */
6843
6844         queue = camel_imapx_command_queue_new ();
6845
6846         QUEUE_LOCK (is);
6847
6848         camel_imapx_command_queue_transfer (is->queue, queue);
6849         camel_imapx_command_queue_transfer (is->active, queue);
6850
6851         QUEUE_UNLOCK (is);
6852
6853         head = camel_imapx_command_queue_peek_head_link (queue);
6854
6855         for (link = head; link != NULL; link = g_list_next (link)) {
6856                 CamelIMAPXCommand *ic = link->data;
6857                 CamelIMAPXJob *job;
6858
6859                 /* Sanity check the CamelIMAPXCommand before proceeding.
6860                  * XXX We are actually getting reports of crashes here...
6861                  *     not sure how this is happening but it's happening. */
6862                 if (ic == NULL)
6863                         continue;
6864
6865                 /* Similarly with the CamelIMAPXJob contained within. */
6866                 job = camel_imapx_command_get_job (ic);
6867                 if (!CAMEL_IS_IMAPX_JOB (job))
6868                         continue;
6869
6870                 camel_imapx_job_cancel (job);
6871
6872                 /* Send a NULL GError since we already cancelled
6873                  * the job and we're not interested in individual
6874                  * command errors. */
6875                 ic->complete (is, ic, camel_imapx_job_get_cancellable (job), NULL);
6876         }
6877
6878         camel_imapx_command_queue_free (queue);
6879 }
6880
6881 /* ********************************************************************** */
6882
6883 static void
6884 parse_contents (CamelIMAPXServer *is,
6885                 GCancellable *cancellable,
6886                 GError **error)
6887 {
6888         CamelIMAPXStream *stream;
6889
6890         stream = camel_imapx_server_ref_stream (is);
6891         g_return_if_fail (stream != NULL);
6892
6893         while (imapx_step (is, cancellable, error))
6894                 if (camel_imapx_stream_buffered (stream) == 0)
6895                         break;
6896
6897         g_object_unref (stream);
6898 }
6899
6900 /*
6901  * The main processing (reading) loop.
6902  *
6903  * Main area of locking required is command_queue
6904  * and command_start_next, the 'literal' command,
6905  * the jobs queue, the active queue, the queue
6906  * queue. */
6907 static gpointer
6908 imapx_parser_thread (gpointer d)
6909 {
6910         CamelIMAPXServer *is = d;
6911         CamelIMAPXStream *stream;
6912         GCancellable *cancellable;
6913         gboolean have_stream;
6914         GError *local_error = NULL;
6915
6916         QUEUE_LOCK (is);
6917         cancellable = camel_operation_new ();
6918         is->cancellable = g_object_ref (cancellable);
6919         QUEUE_UNLOCK (is);
6920
6921         stream = camel_imapx_server_ref_stream (is);
6922         if (stream != NULL) {
6923                 have_stream = TRUE;
6924                 g_object_unref (stream);
6925         } else {
6926                 have_stream = FALSE;
6927         }
6928
6929         /* FIXME This should really be a GMainLoop instead of a 'while' loop.
6930          *       Testing for a stream on each loop iteration is pretty hokey.
6931          *       Disconnecting the stream could just terminate the parser
6932          *       thread's main loop. */
6933         while (local_error == NULL && have_stream) {
6934                 g_cancellable_reset (cancellable);
6935
6936 #ifndef G_OS_WIN32
6937                 if (is->is_process_stream)      {
6938                         GPollFD fds[2] = { {0, 0, 0}, {0, 0, 0} };
6939                         CamelStream *source;
6940                         gint res;
6941
6942                         stream = camel_imapx_server_ref_stream (is);
6943                         source = camel_imapx_stream_ref_source (stream);
6944
6945                         fds[0].fd = CAMEL_STREAM_PROCESS (source)->sockfd;
6946                         fds[0].events = G_IO_IN;
6947                         fds[1].fd = g_cancellable_get_fd (cancellable);
6948                         fds[1].events = G_IO_IN;
6949                         res = g_poll (fds, 2, -1);
6950                         if (res == -1)
6951                                 g_usleep (1) /* ?? */ ;
6952                         else if (res == 0)
6953                                 /* timed out */;
6954                         else if (fds[0].revents & G_IO_IN)
6955                                 parse_contents (is, cancellable, &local_error);
6956                         g_cancellable_release_fd (cancellable);
6957
6958                         g_object_unref (source);
6959                         g_object_unref (stream);
6960                 } else
6961 #endif
6962                 {
6963                         parse_contents (is, cancellable, &local_error);
6964                 }
6965
6966                 if (is->parser_quit)
6967                         g_cancellable_cancel (cancellable);
6968                 else if (g_cancellable_is_cancelled (cancellable)) {
6969                         gint is_empty;
6970
6971                         QUEUE_LOCK (is);
6972                         is_empty = camel_imapx_command_queue_is_empty (is->active);
6973                         QUEUE_UNLOCK (is);
6974
6975                         if (is_empty || (imapx_idle_supported (is) && imapx_in_idle (is))) {
6976                                 g_cancellable_reset (cancellable);
6977                                 g_clear_error (&local_error);
6978                         } else {
6979                                 /* Cancelled error should be set. */
6980                                 g_warn_if_fail (local_error != NULL);
6981                         }
6982                 }
6983
6984                 /* Jump out of the loop if an error occurred. */
6985                 if (local_error != NULL)
6986                         break;
6987
6988                 stream = camel_imapx_server_ref_stream (is);
6989                 if (stream != NULL) {
6990                         have_stream = TRUE;
6991                         g_object_unref (stream);
6992                 } else {
6993                         have_stream = FALSE;
6994                 }
6995         }
6996
6997         QUEUE_LOCK (is);
6998         is->state = IMAPX_SHUTDOWN;
6999         QUEUE_UNLOCK (is);
7000
7001         cancel_all_jobs (is, local_error);
7002
7003         g_clear_error (&local_error);
7004
7005         QUEUE_LOCK (is);
7006         if (is->cancellable != NULL) {
7007                 g_object_unref (is->cancellable);
7008                 is->cancellable = NULL;
7009         }
7010         g_object_unref (cancellable);
7011         QUEUE_UNLOCK (is);
7012
7013         is->parser_quit = FALSE;
7014         g_signal_emit (is, signals[SHUTDOWN], 0);
7015
7016         return NULL;
7017 }
7018
7019 static gboolean
7020 join_helper (gpointer thread)
7021 {
7022         g_thread_join (thread);
7023         return FALSE;
7024 }
7025
7026 static void
7027 imapx_server_set_store (CamelIMAPXServer *server,
7028                         CamelIMAPXStore *store)
7029 {
7030         g_return_if_fail (CAMEL_IS_IMAPX_STORE (store));
7031
7032         g_weak_ref_set (&server->priv->store, store);
7033 }
7034
7035 static void
7036 imapx_server_set_property (GObject *object,
7037                            guint property_id,
7038                            const GValue *value,
7039                            GParamSpec *pspec)
7040 {
7041         switch (property_id) {
7042                 case PROP_STORE:
7043                         imapx_server_set_store (
7044                                 CAMEL_IMAPX_SERVER (object),
7045                                 g_value_get_object (value));
7046                         return;
7047         }
7048
7049         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
7050 }
7051
7052 static void
7053 imapx_server_get_property (GObject *object,
7054                            guint property_id,
7055                            GValue *value,
7056                            GParamSpec *pspec)
7057 {
7058         switch (property_id) {
7059                 case PROP_STREAM:
7060                         g_value_take_object (
7061                                 value,
7062                                 camel_imapx_server_ref_stream (
7063                                 CAMEL_IMAPX_SERVER (object)));
7064                         return;
7065
7066                 case PROP_STORE:
7067                         g_value_take_object (
7068                                 value,
7069                                 camel_imapx_server_ref_store (
7070                                 CAMEL_IMAPX_SERVER (object)));
7071                         return;
7072         }
7073
7074         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
7075 }
7076
7077 static void
7078 imapx_server_dispose (GObject *object)
7079 {
7080         CamelIMAPXServer *server = CAMEL_IMAPX_SERVER (object);
7081
7082         QUEUE_LOCK (server);
7083         server->state = IMAPX_SHUTDOWN;
7084
7085         server->parser_quit = TRUE;
7086
7087         if (server->cancellable != NULL) {
7088                 g_cancellable_cancel (server->cancellable);
7089                 g_object_unref (server->cancellable);
7090                 server->cancellable = NULL;
7091         }
7092         QUEUE_UNLOCK (server);
7093
7094         if (server->parser_thread) {
7095                 if (server->parser_thread == g_thread_self ())
7096                         /* Prioritize ahead of GTK+ redraws. */
7097                         g_idle_add_full (
7098                                 G_PRIORITY_HIGH_IDLE,
7099                                 &join_helper, server->parser_thread, NULL);
7100                 else
7101                         g_thread_join (server->parser_thread);
7102                 server->parser_thread = NULL;
7103         }
7104
7105         if (server->cinfo && imapx_idle_supported (server))
7106                 imapx_exit_idle (server);
7107
7108         imapx_disconnect (server);
7109
7110         g_weak_ref_set (&server->priv->store, NULL);
7111
7112         /* Chain up to parent's dispose() method. */
7113         G_OBJECT_CLASS (camel_imapx_server_parent_class)->dispose (object);
7114 }
7115
7116 static void
7117 imapx_server_finalize (GObject *object)
7118 {
7119         CamelIMAPXServer *is = CAMEL_IMAPX_SERVER (object);
7120
7121         g_mutex_clear (&is->priv->stream_lock);
7122
7123         camel_imapx_command_queue_free (is->queue);
7124         camel_imapx_command_queue_free (is->active);
7125         camel_imapx_command_queue_free (is->done);
7126
7127         is->queue = NULL;
7128         is->active = NULL;
7129         is->done = NULL;
7130
7131         g_rec_mutex_clear (&is->queue_lock);
7132         g_mutex_clear (&is->select_lock);
7133         g_mutex_clear (&is->fetch_mutex);
7134         g_cond_clear (&is->fetch_cond);
7135
7136         camel_folder_change_info_free (is->changes);
7137
7138         g_free (is->priv->context);
7139         g_hash_table_destroy (is->priv->untagged_handlers);
7140
7141         if (is->priv->search_results != NULL)
7142                 g_array_unref (is->priv->search_results);
7143         g_mutex_clear (&is->priv->search_results_lock);
7144
7145         /* Chain up to parent's finalize() method. */
7146         G_OBJECT_CLASS (camel_imapx_server_parent_class)->finalize (object);
7147 }
7148
7149 static void
7150 imapx_server_constructed (GObject *object)
7151 {
7152         CamelIMAPXServer *server;
7153         CamelIMAPXServerClass *class;
7154
7155         server = CAMEL_IMAPX_SERVER (object);
7156         class = CAMEL_IMAPX_SERVER_GET_CLASS (server);
7157
7158         server->tagprefix = class->tagprefix;
7159         class->tagprefix++;
7160         if (class->tagprefix > 'Z')
7161                 class->tagprefix = 'A';
7162 }
7163
7164 static void
7165 camel_imapx_server_class_init (CamelIMAPXServerClass *class)
7166 {
7167         GObjectClass *object_class;
7168
7169         g_type_class_add_private (class, sizeof (CamelIMAPXServerPrivate));
7170
7171         object_class = G_OBJECT_CLASS (class);
7172         object_class->set_property = imapx_server_set_property;
7173         object_class->get_property = imapx_server_get_property;
7174         object_class->finalize = imapx_server_finalize;
7175         object_class->dispose = imapx_server_dispose;
7176         object_class->constructed = imapx_server_constructed;
7177
7178         class->select_changed = NULL;
7179         class->shutdown = NULL;
7180
7181         g_object_class_install_property (
7182                 object_class,
7183                 PROP_STREAM,
7184                 g_param_spec_object (
7185                         "stream",
7186                         "Stream",
7187                         "IMAP network stream",
7188                         CAMEL_TYPE_IMAPX_STREAM,
7189                         G_PARAM_READABLE |
7190                         G_PARAM_STATIC_STRINGS));
7191
7192         g_object_class_install_property (
7193                 object_class,
7194                 PROP_STORE,
7195                 g_param_spec_object (
7196                         "store",
7197                         "Store",
7198                         "IMAPX store for this server",
7199                         CAMEL_TYPE_IMAPX_STORE,
7200                         G_PARAM_READWRITE |
7201                         G_PARAM_CONSTRUCT_ONLY |
7202                         G_PARAM_STATIC_STRINGS));
7203
7204         /**
7205          * CamelIMAPXServer::select_changed
7206          * @server: the #CamelIMAPXServer which emitted the signal
7207          **/
7208         signals[SELECT_CHANGED] = g_signal_new (
7209                 "select_changed",
7210                 G_OBJECT_CLASS_TYPE (class),
7211                 G_SIGNAL_RUN_FIRST,
7212                 G_STRUCT_OFFSET (CamelIMAPXServerClass, select_changed),
7213                 NULL, NULL,
7214                 g_cclosure_marshal_VOID__STRING,
7215                 G_TYPE_NONE, 1, G_TYPE_STRING);
7216
7217         /**
7218          * CamelIMAPXServer::shutdown
7219          * @server: the #CamelIMAPXServer which emitted the signal
7220          **/
7221         signals[SHUTDOWN] = g_signal_new (
7222                 "shutdown",
7223                 G_OBJECT_CLASS_TYPE (class),
7224                 G_SIGNAL_RUN_FIRST,
7225                 G_STRUCT_OFFSET (CamelIMAPXServerClass, shutdown),
7226                 NULL, NULL,
7227                 g_cclosure_marshal_VOID__VOID,
7228                 G_TYPE_NONE, 0);
7229
7230         class->tagprefix = 'A';
7231 }
7232
7233 static void
7234 camel_imapx_server_init (CamelIMAPXServer *is)
7235 {
7236         is->priv = CAMEL_IMAPX_SERVER_GET_PRIVATE (is);
7237
7238         is->priv->untagged_handlers = create_initial_untagged_handler_table ();
7239
7240         g_mutex_init (&is->priv->stream_lock);
7241         g_mutex_init (&is->priv->search_results_lock);
7242
7243         is->queue = camel_imapx_command_queue_new ();
7244         is->active = camel_imapx_command_queue_new ();
7245         is->done = camel_imapx_command_queue_new ();
7246
7247         g_queue_init (&is->jobs);
7248
7249         /* not used at the moment. Use it in future */
7250         is->job_timeout = 29 * 60 * 1000 * 1000;
7251
7252         g_rec_mutex_init (&is->queue_lock);
7253
7254         g_mutex_init (&is->select_lock);
7255
7256         is->state = IMAPX_DISCONNECTED;
7257
7258         is->expunged = NULL;
7259         is->changes = camel_folder_change_info_new ();
7260         is->parser_quit = FALSE;
7261
7262         g_mutex_init (&is->fetch_mutex);
7263         g_cond_init (&is->fetch_cond);
7264 }
7265
7266 CamelIMAPXServer *
7267 camel_imapx_server_new (CamelIMAPXStore *store)
7268 {
7269         g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (store), NULL);
7270
7271         return g_object_new (
7272                 CAMEL_TYPE_IMAPX_SERVER,
7273                 "store", store, NULL);
7274 }
7275
7276 CamelIMAPXStore *
7277 camel_imapx_server_ref_store (CamelIMAPXServer *server)
7278 {
7279         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), NULL);
7280
7281         return g_weak_ref_get (&server->priv->store);
7282 }
7283
7284 CamelIMAPXSettings *
7285 camel_imapx_server_ref_settings (CamelIMAPXServer *server)
7286 {
7287         CamelIMAPXStore *store;
7288         CamelSettings *settings;
7289
7290         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), NULL);
7291
7292         store = camel_imapx_server_ref_store (server);
7293         settings = camel_service_ref_settings (CAMEL_SERVICE (store));
7294         g_object_unref (store);
7295
7296         return CAMEL_IMAPX_SETTINGS (settings);
7297 }
7298
7299 CamelIMAPXStream *
7300 camel_imapx_server_ref_stream (CamelIMAPXServer *server)
7301 {
7302         CamelIMAPXStream *stream = NULL;
7303
7304         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), NULL);
7305
7306         g_mutex_lock (&server->priv->stream_lock);
7307
7308         if (server->priv->stream != NULL)
7309                 stream = g_object_ref (server->priv->stream);
7310
7311         g_mutex_unlock (&server->priv->stream_lock);
7312
7313         return stream;
7314 }
7315
7316 static gboolean
7317 imapx_disconnect (CamelIMAPXServer *is)
7318 {
7319         gboolean ret = TRUE;
7320
7321         g_mutex_lock (&is->priv->stream_lock);
7322
7323         if (is->priv->stream != NULL) {
7324                 CamelStream *stream = CAMEL_STREAM (is->priv->stream);
7325
7326                 if (camel_stream_close (stream, NULL, NULL) == -1)
7327                         ret = FALSE;
7328
7329                 g_object_unref (is->priv->stream);
7330                 is->priv->stream = NULL;
7331         }
7332
7333         g_mutex_unlock (&is->priv->stream_lock);
7334
7335         g_mutex_lock (&is->select_lock);
7336         g_weak_ref_set (&is->select_folder, NULL);
7337         g_weak_ref_set (&is->select_pending, NULL);
7338         g_mutex_unlock (&is->select_lock);
7339
7340         if (is->cinfo) {
7341                 imapx_free_capability (is->cinfo);
7342                 is->cinfo = NULL;
7343         }
7344
7345         is->state = IMAPX_DISCONNECTED;
7346
7347         g_object_notify (G_OBJECT (is), "stream");
7348
7349         return ret;
7350 }
7351
7352 /* Client commands */
7353 gboolean
7354 camel_imapx_server_connect (CamelIMAPXServer *is,
7355                             GCancellable *cancellable,
7356                             GError **error)
7357 {
7358         if (is->state == IMAPX_SHUTDOWN) {
7359                 g_set_error (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE, "Shutting down");
7360                 return FALSE;
7361         }
7362
7363         if (is->state >= IMAPX_INITIALISED)
7364                 return TRUE;
7365
7366         if (!imapx_reconnect (is, cancellable, error))
7367                 return FALSE;
7368
7369         is->parser_thread = g_thread_new (NULL, (GThreadFunc) imapx_parser_thread, is);
7370
7371         return TRUE;
7372 }
7373
7374 static CamelStream *
7375 imapx_server_get_message (CamelIMAPXServer *is,
7376                           CamelFolder *folder,
7377                           const gchar *uid,
7378                           gint pri,
7379                           GCancellable *cancellable,
7380                           GError **error)
7381 {
7382         CamelStream *stream = NULL;
7383         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
7384         CamelIMAPXJob *job;
7385         CamelMessageInfo *mi;
7386         GetMessageData *data;
7387         gboolean registered;
7388         gboolean success;
7389
7390         QUEUE_LOCK (is);
7391
7392         if ((job = imapx_is_job_in_queue (is, folder, IMAPX_JOB_GET_MESSAGE, uid))) {
7393                 if (pri > job->pri)
7394                         job->pri = pri;
7395
7396                 /* Wait for the job to finish. This would be so much nicer if
7397                  * we could just use the queue lock with a GCond, but instead
7398                  * we have to use a GMutex. I miss the kernel waitqueues. */
7399                 do {
7400                         gint this;
7401
7402                         g_mutex_lock (&is->fetch_mutex);
7403                         this = is->fetch_count;
7404
7405                         QUEUE_UNLOCK (is);
7406
7407                         while (is->fetch_count == this)
7408                                 g_cond_wait (&is->fetch_cond, &is->fetch_mutex);
7409
7410                         g_mutex_unlock (&is->fetch_mutex);
7411
7412                         QUEUE_LOCK (is);
7413
7414                 } while (imapx_is_job_in_queue (is, folder,
7415                                                 IMAPX_JOB_GET_MESSAGE, uid));
7416
7417                 QUEUE_UNLOCK (is);
7418
7419                 stream = camel_data_cache_get (
7420                         ifolder->cache, "cur", uid, error);
7421                 if (stream == NULL)
7422                         g_prefix_error (
7423                                 error, "Could not retrieve the message: ");
7424                 return stream;
7425         }
7426
7427         mi = camel_folder_summary_get (folder->summary, uid);
7428         if (!mi) {
7429                 g_set_error (
7430                         error, CAMEL_FOLDER_ERROR,
7431                         CAMEL_FOLDER_ERROR_INVALID_UID,
7432                         _("Cannot get message with message ID %s: %s"),
7433                         uid, _("No such message available."));
7434                 QUEUE_UNLOCK (is);
7435                 return NULL;
7436         }
7437
7438         data = g_slice_new0 (GetMessageData);
7439         data->uid = g_strdup (uid);
7440         data->stream = camel_data_cache_add (ifolder->cache, "tmp", uid, NULL);
7441         data->size = ((CamelMessageInfoBase *) mi)->size;
7442         if (data->size > MULTI_SIZE)
7443                 data->use_multi_fetch = TRUE;
7444
7445         job = camel_imapx_job_new (cancellable);
7446         job->pri = pri;
7447         job->type = IMAPX_JOB_GET_MESSAGE;
7448         job->start = imapx_job_get_message_start;
7449         job->matches = imapx_job_get_message_matches;
7450
7451         camel_imapx_job_set_folder (job, folder);
7452
7453         camel_imapx_job_set_data (
7454                 job, data, (GDestroyNotify) get_message_data_free);
7455
7456         camel_message_info_free (mi);
7457         registered = imapx_register_job (is, job, error);
7458
7459         QUEUE_UNLOCK (is);
7460
7461         success = registered && camel_imapx_job_run (job, is, error);
7462
7463         if (success)
7464                 stream = g_object_ref (data->stream);
7465
7466         camel_imapx_job_unref (job);
7467
7468         g_mutex_lock (&is->fetch_mutex);
7469         is->fetch_count++;
7470         g_cond_broadcast (&is->fetch_cond);
7471         g_mutex_unlock (&is->fetch_mutex);
7472
7473         return stream;
7474 }
7475
7476 CamelStream *
7477 camel_imapx_server_get_message (CamelIMAPXServer *is,
7478                                 CamelFolder *folder,
7479                                 const gchar *uid,
7480                                 GCancellable *cancellable,
7481                                 GError **error)
7482 {
7483         CamelStream *stream;
7484
7485         stream = imapx_server_get_message (
7486                 is, folder, uid,
7487                 IMAPX_PRIORITY_GET_MESSAGE,
7488                 cancellable, error);
7489
7490         return stream;
7491 }
7492
7493 gboolean
7494 camel_imapx_server_sync_message (CamelIMAPXServer *is,
7495                                  CamelFolder *folder,
7496                                  const gchar *uid,
7497                                  GCancellable *cancellable,
7498                                  GError **error)
7499 {
7500         gchar *cache_file = NULL;
7501         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
7502         CamelStream *stream;
7503         gboolean is_cached;
7504         struct stat st;
7505
7506         /* Check if the cache file already exists and is non-empty. */
7507         cache_file = camel_data_cache_get_filename (
7508                 ifolder->cache, "cur", uid);
7509         is_cached = (g_stat (cache_file, &st) == 0 && st.st_size > 0);
7510         g_free (cache_file);
7511
7512         if (is_cached)
7513                 return TRUE;
7514
7515         stream = imapx_server_get_message (
7516                 is, folder, uid,
7517                 IMAPX_PRIORITY_SYNC_MESSAGE,
7518                 cancellable, error);
7519
7520         if (stream == NULL)
7521                 return FALSE;
7522
7523         g_object_unref (stream);
7524
7525         return TRUE;
7526 }
7527
7528 gboolean
7529 camel_imapx_server_copy_message (CamelIMAPXServer *is,
7530                                  CamelFolder *source,
7531                                  CamelFolder *dest,
7532                                  GPtrArray *uids,
7533                                  gboolean delete_originals,
7534                                  GCancellable *cancellable,
7535                                  GError **error)
7536 {
7537         CamelIMAPXJob *job;
7538         CopyMessagesData *data;
7539         gint ii;
7540
7541         data = g_slice_new0 (CopyMessagesData);
7542         data->dest = g_object_ref (dest);
7543         data->uids = g_ptr_array_new ();
7544         data->delete_originals = delete_originals;
7545
7546         for (ii = 0; ii < uids->len; ii++)
7547                 g_ptr_array_add (data->uids, g_strdup (uids->pdata[ii]));
7548
7549         job = camel_imapx_job_new (cancellable);
7550         job->pri = IMAPX_PRIORITY_APPEND_MESSAGE;
7551         job->type = IMAPX_JOB_COPY_MESSAGE;
7552         job->start = imapx_job_copy_messages_start;
7553
7554         camel_imapx_job_set_folder (job, source);
7555
7556         camel_imapx_job_set_data (
7557                 job, data, (GDestroyNotify) copy_messages_data_free);
7558
7559         return imapx_submit_job (is, job, error);
7560 }
7561
7562 gboolean
7563 camel_imapx_server_append_message (CamelIMAPXServer *is,
7564                                    CamelFolder *folder,
7565                                    CamelMimeMessage *message,
7566                                    const CamelMessageInfo *mi,
7567                                    gchar **appended_uid,
7568                                    GCancellable *cancellable,
7569                                    GError **error)
7570 {
7571         gchar *uid = NULL, *path = NULL;
7572         CamelStream *stream, *filter;
7573         CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
7574         CamelMimeFilter *canon;
7575         CamelIMAPXJob *job;
7576         CamelMessageInfo *info;
7577         AppendMessageData *data;
7578         gint res;
7579         gboolean success;
7580
7581         /* Append just assumes we have no/a dodgy connection.  We dump
7582          * stuff into the 'new' directory, and let the summary know it's
7583          * there.  Then we fire off a no-reply job which will asynchronously
7584          * upload the message at some point in the future, and fix up the
7585          * summary to match */
7586
7587         /* chen cleanup this later */
7588         uid = imapx_get_temp_uid ();
7589         stream = camel_data_cache_add (ifolder->cache, "new", uid, error);
7590         if (stream == NULL) {
7591                 g_prefix_error (error, _("Cannot create spool file: "));
7592                 g_free (uid);
7593                 return FALSE;
7594         }
7595
7596         filter = camel_stream_filter_new (stream);
7597         g_object_unref (stream);
7598         canon = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_CRLF);
7599         camel_stream_filter_add ((CamelStreamFilter *) filter, canon);
7600         res = camel_data_wrapper_write_to_stream_sync (
7601                 (CamelDataWrapper *) message, filter, cancellable, error);
7602         g_object_unref (canon);
7603         g_object_unref (filter);
7604
7605         if (res == -1) {
7606                 g_prefix_error (error, _("Cannot create spool file: "));
7607                 camel_data_cache_remove (ifolder->cache, "new", uid, NULL);
7608                 g_free (uid);
7609                 return FALSE;
7610         }
7611
7612         path = camel_data_cache_get_filename (ifolder->cache, "new", uid);
7613         info = camel_folder_summary_info_new_from_message ((CamelFolderSummary *) folder->summary, message, NULL);
7614         info->uid = camel_pstring_strdup (uid);
7615         if (mi) {
7616                 CamelMessageInfoBase *base_info = (CamelMessageInfoBase *) info;
7617
7618                 base_info->flags = camel_message_info_flags (mi);
7619                 base_info->size = camel_message_info_size (mi);
7620
7621                 if ((is->permanentflags & CAMEL_MESSAGE_USER) != 0) {
7622                         const CamelFlag *flag;
7623                         const CamelTag *tag;
7624
7625                         flag = camel_message_info_user_flags (mi);
7626                         while (flag) {
7627                                 if (flag->name && *flag->name)
7628                                         camel_flag_set (&base_info->user_flags, flag->name, TRUE);
7629                                 flag = flag->next;
7630                         }
7631
7632                         tag = camel_message_info_user_tags (mi);
7633                         while (tag) {
7634                                 if (tag->name && *tag->name)
7635                                         camel_tag_set (&base_info->user_tags, tag->name, tag->value);
7636                                 tag = tag->next;
7637                         }
7638                 }
7639         }
7640
7641         g_free (uid);
7642
7643         /* So, we actually just want to let the server loop that
7644          * messages need appending, i think.  This is so the same
7645          * mechanism is used for normal uploading as well as
7646          * offline re-syncing when we go back online */
7647
7648         data = g_slice_new0 (AppendMessageData);
7649         data->info = info;  /* takes ownership */
7650         data->path = path;  /* takes ownership */
7651         data->appended_uid = NULL;
7652
7653         job = camel_imapx_job_new (cancellable);
7654         job->pri = IMAPX_PRIORITY_APPEND_MESSAGE;
7655         job->type = IMAPX_JOB_APPEND_MESSAGE;
7656         job->start = imapx_job_append_message_start;
7657         job->noreply = FALSE;
7658
7659         camel_imapx_job_set_folder (job, folder);
7660
7661         camel_imapx_job_set_data (
7662                 job, data, (GDestroyNotify) append_message_data_free);
7663
7664         success = imapx_submit_job (is, job, error);
7665
7666         if (appended_uid != NULL) {
7667                 *appended_uid = data->appended_uid;
7668                 data->appended_uid = NULL;
7669         }
7670
7671         camel_imapx_job_unref (job);
7672
7673         return success;
7674 }
7675
7676 gboolean
7677 camel_imapx_server_noop (CamelIMAPXServer *is,
7678                          CamelFolder *folder,
7679                          GCancellable *cancellable,
7680                          GError **error)
7681 {
7682         CamelIMAPXJob *job;
7683         gboolean success;
7684
7685         job = camel_imapx_job_new (cancellable);
7686         job->type = IMAPX_JOB_NOOP;
7687         job->start = imapx_job_noop_start;
7688         job->pri = IMAPX_PRIORITY_NOOP;
7689
7690         camel_imapx_job_set_folder (job, folder);
7691
7692         success = imapx_submit_job (is, job, error);
7693
7694         camel_imapx_job_unref (job);
7695
7696         return success;
7697 }
7698
7699 gboolean
7700 camel_imapx_server_refresh_info (CamelIMAPXServer *is,
7701                                  CamelFolder *folder,
7702                                  GCancellable *cancellable,
7703                                  GError **error)
7704 {
7705         CamelIMAPXJob *job;
7706         RefreshInfoData *data;
7707         gboolean registered = TRUE;
7708         const gchar *full_name;
7709         gboolean success = TRUE;
7710
7711         full_name = camel_folder_get_full_name (folder);
7712
7713         QUEUE_LOCK (is);
7714
7715         /* Both RefreshInfo and Fetch messages can't operate simultaneously */
7716         if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_REFRESH_INFO, NULL) ||
7717                 imapx_is_job_in_queue (is, folder, IMAPX_JOB_FETCH_MESSAGES, NULL)) {
7718                 QUEUE_UNLOCK (is);
7719                 return TRUE;
7720         }
7721
7722         data = g_slice_new0 (RefreshInfoData);
7723         data->changes = camel_folder_change_info_new ();
7724
7725         job = camel_imapx_job_new (cancellable);
7726         job->type = IMAPX_JOB_REFRESH_INFO;
7727         job->start = imapx_job_refresh_info_start;
7728         job->matches = imapx_job_refresh_info_matches;
7729         job->pri = IMAPX_PRIORITY_REFRESH_INFO;
7730
7731         camel_imapx_job_set_folder (job, folder);
7732
7733         if (g_ascii_strcasecmp (full_name, "INBOX") == 0)
7734                 job->pri += 10;
7735
7736         camel_imapx_job_set_data (
7737                 job, data, (GDestroyNotify) refresh_info_data_free);
7738
7739         registered = imapx_register_job (is, job, error);
7740
7741         QUEUE_UNLOCK (is);
7742
7743         success = registered && camel_imapx_job_run (job, is, error);
7744
7745         if (success && camel_folder_change_info_changed (data->changes))
7746                 camel_folder_changed (folder, data->changes);
7747
7748         camel_imapx_job_unref (job);
7749
7750         return success;
7751 }
7752
7753 static void
7754 imapx_sync_free_user (GArray *user_set)
7755 {
7756         gint i;
7757
7758         if (user_set == NULL)
7759                 return;
7760
7761         for (i = 0; i < user_set->len; i++) {
7762                 struct _imapx_flag_change *flag_change = &g_array_index (user_set, struct _imapx_flag_change, i);
7763                 GPtrArray *infos = flag_change->infos;
7764                 gint j;
7765
7766                 for (j = 0; j < infos->len; j++) {
7767                         CamelMessageInfo *info = g_ptr_array_index (infos, j);
7768                         camel_message_info_free (info);
7769                 }
7770
7771                 g_ptr_array_free (infos, TRUE);
7772                 g_free (flag_change->name);
7773         }
7774         g_array_free (user_set, TRUE);
7775 }
7776
7777 static gboolean
7778 imapx_server_sync_changes (CamelIMAPXServer *is,
7779                            CamelFolder *folder,
7780                            guint32 job_type,
7781                            gint pri,
7782                            GCancellable *cancellable,
7783                            GError **error)
7784 {
7785         guint i, on_orset, off_orset;
7786         GPtrArray *changed_uids;
7787         GArray *on_user = NULL, *off_user = NULL;
7788         CamelIMAPXMessageInfo *info;
7789         CamelIMAPXJob *job;
7790         CamelIMAPXSettings *settings;
7791         SyncChangesData *data;
7792         gboolean use_real_junk_path;
7793         gboolean use_real_trash_path;
7794         gboolean nothing_to_do;
7795         gboolean registered;
7796         gboolean success = TRUE;
7797
7798         /* We calculate two masks, a mask of all flags which have been
7799          * turned off and a mask of all flags which have been turned
7800          * on. If either of these aren't 0, then we have work to do,
7801          * and we fire off a job to do it.
7802          *
7803          * User flags are a bit more tricky, we rely on the user
7804          * flags being sorted, and then we create a bunch of lists;
7805          * one for each flag being turned off, including each
7806          * info being turned off, and one for each flag being turned on.
7807          */
7808         changed_uids = camel_folder_summary_get_changed (folder->summary);
7809
7810         if (changed_uids->len == 0) {
7811                 camel_folder_free_uids (folder, changed_uids);
7812                 return TRUE;
7813         }
7814
7815         settings = camel_imapx_server_ref_settings (is);
7816         use_real_junk_path =
7817                 camel_imapx_settings_get_use_real_junk_path (settings);
7818         use_real_trash_path =
7819                 camel_imapx_settings_get_use_real_trash_path (settings);
7820         g_object_unref (settings);
7821
7822         off_orset = on_orset = 0;
7823         for (i = 0; i < changed_uids->len; i++) {
7824                 guint32 flags, sflags;
7825                 CamelFlag *uflags, *suflags;
7826                 const gchar *uid;
7827                 gboolean move_to_real_junk;
7828                 gboolean move_to_real_trash;
7829                 guint j = 0;
7830
7831                 uid = g_ptr_array_index (changed_uids, i);
7832
7833                 info = (CamelIMAPXMessageInfo *)
7834                         camel_folder_summary_get (folder->summary, uid);
7835
7836                 if (info == NULL)
7837                         continue;
7838
7839                 if (!(info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) {
7840                         camel_message_info_free (info);
7841                         continue;
7842                 }
7843
7844                 flags = info->info.flags & CAMEL_IMAPX_SERVER_FLAGS;
7845                 sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
7846
7847                 move_to_real_junk =
7848                         use_real_junk_path &&
7849                         (flags & CAMEL_MESSAGE_JUNK);
7850
7851                 move_to_real_trash =
7852                         use_real_trash_path &&
7853                         (flags & CAMEL_MESSAGE_DELETED);
7854
7855                 if (move_to_real_junk)
7856                         camel_imapx_folder_add_move_to_real_junk (
7857                                 CAMEL_IMAPX_FOLDER (folder), uid);
7858
7859                 if (move_to_real_trash)
7860                         camel_imapx_folder_add_move_to_real_trash (
7861                                 CAMEL_IMAPX_FOLDER (folder), uid);
7862
7863                 if (flags != sflags) {
7864                         off_orset |= (flags ^ sflags) & ~flags;
7865                         on_orset |= (flags ^ sflags) & flags;
7866                 }
7867
7868                 uflags = info->info.user_flags;
7869                 suflags = info->server_user_flags;
7870                 while (uflags || suflags) {
7871                         gint res;
7872
7873                         if (uflags) {
7874                                 if (suflags)
7875                                         res = strcmp (uflags->name, suflags->name);
7876                                 else if (*uflags->name)
7877                                         res = -1;
7878                                 else {
7879                                         uflags = uflags->next;
7880                                         continue;
7881                                 }
7882                         } else {
7883                                 res = 1;
7884                         }
7885
7886                         if (res == 0) {
7887                                 uflags = uflags->next;
7888                                 suflags = suflags->next;
7889                         } else {
7890                                 GArray *user_set;
7891                                 CamelFlag *user_flag;
7892                                 struct _imapx_flag_change *change = NULL, add = { 0 };
7893
7894                                 if (res < 0) {
7895                                         if (on_user == NULL)
7896                                                 on_user = g_array_new (FALSE, FALSE, sizeof (struct _imapx_flag_change));
7897                                         user_set = on_user;
7898                                         user_flag = uflags;
7899                                         uflags = uflags->next;
7900                                 } else {
7901                                         if (off_user == NULL)
7902                                                 off_user = g_array_new (FALSE, FALSE, sizeof (struct _imapx_flag_change));
7903                                         user_set = off_user;
7904                                         user_flag = suflags;
7905                                         suflags = suflags->next;
7906                                 }
7907
7908                                 /* Could sort this and binary search */
7909                                 for (j = 0; j < user_set->len; j++) {
7910                                         change = &g_array_index (user_set, struct _imapx_flag_change, j);
7911                                         if (strcmp (change->name, user_flag->name) == 0)
7912                                                 goto found;
7913                                 }
7914                                 add.name = g_strdup (user_flag->name);
7915                                 add.infos = g_ptr_array_new ();
7916                                 g_array_append_val (user_set, add);
7917                                 change = &add;
7918                         found:
7919                                 camel_message_info_ref (info);
7920                                 g_ptr_array_add (change->infos, info);
7921                         }
7922                 }
7923                 camel_message_info_free (info);
7924         }
7925
7926         nothing_to_do =
7927                 (on_orset == 0) &&
7928                 (off_orset == 0) &&
7929                 (on_user == NULL) &&
7930                 (off_user == NULL);
7931
7932         if (nothing_to_do) {
7933                 imapx_sync_free_user (on_user);
7934                 imapx_sync_free_user (off_user);
7935                 camel_folder_free_uids (folder, changed_uids);
7936                 return TRUE;
7937         }
7938
7939         /* TODO above code should go into changes_start */
7940
7941         QUEUE_LOCK (is);
7942
7943         if ((job = imapx_is_job_in_queue (is, folder, IMAPX_JOB_SYNC_CHANGES, NULL))) {
7944                 if (pri > job->pri)
7945                         job->pri = pri;
7946
7947                 QUEUE_UNLOCK (is);
7948
7949                 imapx_sync_free_user (on_user);
7950                 imapx_sync_free_user (off_user);
7951                 camel_folder_free_uids (folder, changed_uids);
7952                 return TRUE;
7953         }
7954
7955         data = g_slice_new0 (SyncChangesData);
7956         data->folder = g_object_ref (folder);
7957         data->changed_uids = changed_uids;  /* takes ownership */
7958         data->on_set = on_orset;
7959         data->off_set = off_orset;
7960         data->on_user = on_user;  /* takes ownership */
7961         data->off_user = off_user;  /* takes ownership */
7962
7963         data->remove_deleted_flags =
7964                 use_real_trash_path &&
7965                 (job_type != IMAPX_JOB_EXPUNGE);
7966
7967         job = camel_imapx_job_new (cancellable);
7968         job->type = IMAPX_JOB_SYNC_CHANGES;
7969         job->start = imapx_job_sync_changes_start;
7970         job->matches = imapx_job_sync_changes_matches;
7971         job->pri = pri;
7972
7973         camel_imapx_job_set_folder (job, folder);
7974
7975         camel_imapx_job_set_data (
7976                 job, data, (GDestroyNotify) sync_changes_data_free);
7977
7978         registered = imapx_register_job (is, job, error);
7979
7980         QUEUE_UNLOCK (is);
7981
7982         success = registered && camel_imapx_job_run (job, is, error);
7983
7984         camel_imapx_job_unref (job);
7985
7986         return success;
7987 }
7988
7989 gboolean
7990 camel_imapx_server_sync_changes (CamelIMAPXServer *is,
7991                                  CamelFolder *folder,
7992                                  GCancellable *cancellable,
7993                                  GError **error)
7994 {
7995         return imapx_server_sync_changes (
7996                 is, folder,
7997                 IMAPX_JOB_SYNC_CHANGES,
7998                 IMAPX_PRIORITY_SYNC_CHANGES,
7999                 cancellable, error);
8000 }
8001
8002 /* expunge-uids? */
8003 gboolean
8004 camel_imapx_server_expunge (CamelIMAPXServer *is,
8005                             CamelFolder *folder,
8006                             GCancellable *cancellable,
8007                             GError **error)
8008 {
8009         CamelIMAPXJob *job;
8010         gboolean registered;
8011         gboolean success;
8012
8013         /* Do we really care to wait for this one to finish? */
8014         QUEUE_LOCK (is);
8015
8016         if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_EXPUNGE, NULL)) {
8017                 QUEUE_UNLOCK (is);
8018                 return TRUE;
8019         }
8020
8021         job = camel_imapx_job_new (cancellable);
8022         job->type = IMAPX_JOB_EXPUNGE;
8023         job->start = imapx_job_expunge_start;
8024         job->matches = imapx_job_expunge_matches;
8025         job->pri = IMAPX_PRIORITY_EXPUNGE;
8026
8027         camel_imapx_job_set_folder (job, folder);
8028
8029         registered = imapx_register_job (is, job, error);
8030
8031         QUEUE_UNLOCK (is);
8032
8033         success = registered && camel_imapx_job_run (job, is, error);
8034
8035         camel_imapx_job_unref (job);
8036
8037         return success;
8038 }
8039
8040 static guint
8041 imapx_name_hash (gconstpointer key)
8042 {
8043         if (g_ascii_strcasecmp (key, "INBOX") == 0)
8044                 return g_str_hash ("INBOX");
8045         else
8046                 return g_str_hash (key);
8047 }
8048
8049 static gint
8050 imapx_name_equal (gconstpointer a,
8051                   gconstpointer b)
8052 {
8053         gconstpointer aname = a, bname = b;
8054
8055         if (g_ascii_strcasecmp (a, "INBOX") == 0)
8056                 aname = "INBOX";
8057         if (g_ascii_strcasecmp (b, "INBOX") == 0)
8058                 bname = "INBOX";
8059         return g_str_equal (aname, bname);
8060 }
8061
8062 static void
8063 imapx_list_flatten (gpointer k,
8064                     gpointer v,
8065                     gpointer d)
8066 {
8067         GPtrArray *folders = d;
8068
8069         g_ptr_array_add (folders, v);
8070 }
8071
8072 static gint
8073 imapx_list_cmp (gconstpointer ap,
8074                 gconstpointer bp)
8075 {
8076         struct _list_info *a = ((struct _list_info **) ap)[0];
8077         struct _list_info *b = ((struct _list_info **) bp)[0];
8078
8079         return strcmp (a->name, b->name);
8080 }
8081
8082 GPtrArray *
8083 camel_imapx_server_list (CamelIMAPXServer *is,
8084                          const gchar *top,
8085                          guint32 flags,
8086                          const gchar *ext,
8087                          GCancellable *cancellable,
8088                          GError **error)
8089 {
8090         CamelIMAPXJob *job;
8091         GPtrArray *folders = NULL;
8092         ListData *data;
8093         gchar *encoded_name;
8094
8095         encoded_name = camel_utf8_utf7 (top);
8096
8097         data = g_slice_new0 (ListData);
8098         data->flags = flags;
8099         data->ext = g_strdup (ext);
8100         data->folders = g_hash_table_new (imapx_name_hash, imapx_name_equal);
8101
8102         if (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)
8103                 data->pattern = g_strdup_printf ("%s*", encoded_name);
8104         else
8105                 data->pattern = g_strdup (encoded_name);
8106
8107         job = camel_imapx_job_new (cancellable);
8108         job->type = IMAPX_JOB_LIST;
8109         job->start = imapx_job_list_start;
8110         job->matches = imapx_job_list_matches;
8111         job->pri = IMAPX_PRIORITY_LIST;
8112
8113         camel_imapx_job_set_data (
8114                 job, data, (GDestroyNotify) list_data_free);
8115
8116         /* sync operation which is triggered by user */
8117         if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST)
8118                 job->pri += 300;
8119
8120         if (imapx_submit_job (is, job, error)) {
8121                 folders = g_ptr_array_new ();
8122                 g_hash_table_foreach (data->folders, imapx_list_flatten, folders);
8123                 qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), imapx_list_cmp);
8124         }
8125
8126         g_free (encoded_name);
8127         camel_imapx_job_unref (job);
8128
8129         return folders;
8130 }
8131
8132 gboolean
8133 camel_imapx_server_manage_subscription (CamelIMAPXServer *is,
8134                                         const gchar *folder_name,
8135                                         gboolean subscribe,
8136                                         GCancellable *cancellable,
8137                                         GError **error)
8138 {
8139         CamelIMAPXJob *job;
8140         ManageSubscriptionsData *data;
8141         gboolean success;
8142
8143         data = g_slice_new0 (ManageSubscriptionsData);
8144         data->folder_name = g_strdup (folder_name);
8145         data->subscribe = subscribe;
8146
8147         job = camel_imapx_job_new (cancellable);
8148         job->type = IMAPX_JOB_MANAGE_SUBSCRIPTION;
8149         job->start = imapx_job_manage_subscription_start;
8150         job->pri = IMAPX_PRIORITY_MANAGE_SUBSCRIPTION;
8151
8152         camel_imapx_job_set_data (
8153                 job, data, (GDestroyNotify) manage_subscriptions_data_free);
8154
8155         success = imapx_submit_job (is, job, error);
8156
8157         camel_imapx_job_unref (job);
8158
8159         return success;
8160 }
8161
8162 gboolean
8163 camel_imapx_server_create_folder (CamelIMAPXServer *is,
8164                                   const gchar *folder_name,
8165                                   GCancellable *cancellable,
8166                                   GError **error)
8167 {
8168         CamelIMAPXJob *job;
8169         CreateFolderData *data;
8170         gboolean success;
8171
8172         data = g_slice_new0 (CreateFolderData);
8173         data->folder_name = g_strdup (folder_name);
8174
8175         job = camel_imapx_job_new (cancellable);
8176         job->type = IMAPX_JOB_CREATE_FOLDER;
8177         job->start = imapx_job_create_folder_start;
8178         job->pri = IMAPX_PRIORITY_CREATE_FOLDER;
8179
8180         camel_imapx_job_set_data (
8181                 job, data, (GDestroyNotify) create_folder_data_free);
8182
8183         success = imapx_submit_job (is, job, error);
8184
8185         camel_imapx_job_unref (job);
8186
8187         return success;
8188 }
8189
8190 gboolean
8191 camel_imapx_server_delete_folder (CamelIMAPXServer *is,
8192                                   const gchar *folder_name,
8193                                   GCancellable *cancellable,
8194                                   GError **error)
8195 {
8196         CamelIMAPXJob *job;
8197         DeleteFolderData *data;
8198         gboolean success;
8199
8200         data = g_slice_new0 (DeleteFolderData);
8201         data->folder_name = g_strdup (folder_name);
8202
8203         job = camel_imapx_job_new (cancellable);
8204         job->type = IMAPX_JOB_DELETE_FOLDER;
8205         job->start = imapx_job_delete_folder_start;
8206         job->pri = IMAPX_PRIORITY_DELETE_FOLDER;
8207
8208         camel_imapx_job_set_data (
8209                 job, data, (GDestroyNotify) delete_folder_data_free);
8210
8211         success = imapx_submit_job (is, job, error);
8212
8213         camel_imapx_job_unref (job);
8214
8215         return success;
8216 }
8217
8218 static gboolean
8219 imapx_job_fetch_messages_matches (CamelIMAPXJob *job,
8220                                   CamelFolder *folder,
8221                                   const gchar *uid)
8222 {
8223         return camel_imapx_job_has_folder (job, folder);
8224 }
8225
8226 gboolean
8227 camel_imapx_server_fetch_messages (CamelIMAPXServer *is,
8228                                    CamelFolder *folder,
8229                                    CamelFetchType type,
8230                                    gint limit,
8231                                    GCancellable *cancellable,
8232                                    GError **error)
8233 {
8234         CamelIMAPXJob *job;
8235         RefreshInfoData *data;
8236         gboolean registered = TRUE;
8237         const gchar *full_name;
8238         gboolean success = TRUE;
8239         guint64 firstuid, newfirstuid;
8240         gchar *uid;
8241         gint old_len;
8242
8243         old_len = camel_folder_summary_count (folder->summary);
8244         uid = imapx_get_uid_from_index (folder->summary, 0);
8245         firstuid = strtoull (uid, NULL, 10);
8246         g_free (uid);
8247
8248         QUEUE_LOCK (is);
8249
8250         /* Both RefreshInfo and Fetch messages can't operate simultaneously */
8251         if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_REFRESH_INFO, NULL) ||
8252                 imapx_is_job_in_queue (is, folder, IMAPX_JOB_FETCH_MESSAGES, NULL)) {
8253                 QUEUE_UNLOCK (is);
8254                 return TRUE;
8255         }
8256
8257         data = g_slice_new0 (RefreshInfoData);
8258         data->changes = camel_folder_change_info_new ();
8259         data->fetch_msg_limit = limit;
8260         data->fetch_type = type;
8261
8262         job = camel_imapx_job_new (cancellable);
8263         job->type = IMAPX_JOB_FETCH_MESSAGES;
8264         job->start = imapx_job_fetch_messages_start;
8265         job->matches = imapx_job_fetch_messages_matches;
8266         job->pri = IMAPX_PRIORITY_NEW_MESSAGES;
8267
8268         camel_imapx_job_set_folder (job, folder);
8269
8270         full_name = camel_folder_get_full_name (folder);
8271
8272         if (g_ascii_strcasecmp (full_name, "INBOX") == 0)
8273                 job->pri += 10;
8274
8275         camel_imapx_job_set_data (
8276                 job, data, (GDestroyNotify) refresh_info_data_free);
8277
8278         registered = imapx_register_job (is, job, error);
8279
8280         QUEUE_UNLOCK (is);
8281
8282         success = registered && camel_imapx_job_run (job, is, error);
8283
8284         if (success && camel_folder_change_info_changed (data->changes) && camel_folder_change_info_changed (data->changes))
8285                 camel_folder_changed (folder, data->changes);
8286
8287         uid = imapx_get_uid_from_index (folder->summary, 0);
8288         newfirstuid = strtoull (uid, NULL, 10);
8289         g_free (uid);
8290
8291         camel_imapx_job_unref (job);
8292
8293         if (type == CAMEL_FETCH_OLD_MESSAGES && firstuid == newfirstuid)
8294                 return FALSE; /* No more old messages */
8295         else if (type == CAMEL_FETCH_NEW_MESSAGES &&
8296                         old_len == camel_folder_summary_count (folder->summary))
8297                 return FALSE; /* No more new messages */
8298
8299         return TRUE;
8300 }
8301
8302 gboolean
8303 camel_imapx_server_rename_folder (CamelIMAPXServer *is,
8304                                   const gchar *old_name,
8305                                   const gchar *new_name,
8306                                   GCancellable *cancellable,
8307                                   GError **error)
8308 {
8309         CamelIMAPXJob *job;
8310         RenameFolderData *data;
8311         gboolean success;
8312
8313         data = g_slice_new0 (RenameFolderData);
8314         data->old_folder_name = g_strdup (old_name);
8315         data->new_folder_name = g_strdup (new_name);
8316
8317         job = camel_imapx_job_new (cancellable);
8318         job->type = IMAPX_JOB_RENAME_FOLDER;
8319         job->start = imapx_job_rename_folder_start;
8320         job->pri = IMAPX_PRIORITY_RENAME_FOLDER;
8321
8322         camel_imapx_job_set_data (
8323                 job, data, (GDestroyNotify) rename_folder_data_free);
8324
8325         success = imapx_submit_job (is, job, error);
8326
8327         camel_imapx_job_unref (job);
8328
8329         return success;
8330 }
8331
8332 gboolean
8333 camel_imapx_server_update_quota_info (CamelIMAPXServer *is,
8334                                       const gchar *folder_name,
8335                                       GCancellable *cancellable,
8336                                       GError **error)
8337 {
8338         CamelIMAPXJob *job;
8339         QuotaData *data;
8340         gboolean success;
8341
8342         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
8343         g_return_val_if_fail (folder_name != NULL, FALSE);
8344
8345         if (is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_QUOTA) == 0) {
8346                 g_set_error_literal (
8347                         error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
8348                         _("IMAP server does not support quotas"));
8349                 return FALSE;
8350         }
8351
8352         data = g_slice_new0 (QuotaData);
8353         data->folder_name = g_strdup (folder_name);
8354
8355         job = camel_imapx_job_new (cancellable);
8356         job->type = IMAPX_JOB_UPDATE_QUOTA_INFO;
8357         job->start = imapx_job_update_quota_info_start;
8358         job->pri = IMAPX_PRIORITY_UPDATE_QUOTA_INFO;
8359
8360         camel_imapx_job_set_data (
8361                 job, data, (GDestroyNotify) quota_data_free);
8362
8363         success = imapx_submit_job (is, job, error);
8364
8365         camel_imapx_job_unref (job);
8366
8367         return success;
8368 }
8369
8370 GPtrArray *
8371 camel_imapx_server_uid_search (CamelIMAPXServer *is,
8372                                CamelFolder *folder,
8373                                const gchar *criteria,
8374                                GCancellable *cancellable,
8375                                GError **error)
8376 {
8377         CamelIMAPXJob *job;
8378         SearchData *data;
8379         GPtrArray *results = NULL;
8380
8381         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
8382         g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
8383         g_return_val_if_fail (criteria != NULL, NULL);
8384
8385         data = g_slice_new0 (SearchData);
8386         data->criteria = g_strdup (criteria);
8387
8388         job = camel_imapx_job_new (cancellable);
8389         job->type = IMAPX_JOB_UID_SEARCH;
8390         job->start = imapx_job_uid_search_start;
8391         job->pri = IMAPX_PRIORITY_SEARCH;
8392
8393         camel_imapx_job_set_folder (job, folder);
8394
8395         camel_imapx_job_set_data (
8396                 job, data, (GDestroyNotify) search_data_free);
8397
8398         if (imapx_submit_job (is, job, error)) {
8399                 guint ii;
8400
8401                 /* Convert the numeric UIDs to strings. */
8402
8403                 g_return_val_if_fail (data->results != NULL, NULL);
8404
8405                 results = g_ptr_array_new_full (
8406                         data->results->len,
8407                         (GDestroyNotify) camel_pstring_free);
8408
8409                 for (ii = 0; ii < data->results->len; ii++) {
8410                         const gchar *pooled_uid;
8411                         guint64 numeric_uid;
8412                         gchar *alloced_uid;
8413
8414                         numeric_uid = g_array_index (
8415                                 data->results, guint64, ii);
8416                         alloced_uid = g_strdup_printf (
8417                                 "%" G_GUINT64_FORMAT, numeric_uid);
8418                         pooled_uid = camel_pstring_add (alloced_uid, TRUE);
8419                         g_ptr_array_add (results, (gpointer) pooled_uid);
8420                 }
8421         }
8422
8423         camel_imapx_job_unref (job);
8424
8425         return results;
8426 }
8427
8428 IMAPXJobQueueInfo *
8429 camel_imapx_server_get_job_queue_info (CamelIMAPXServer *is)
8430 {
8431         IMAPXJobQueueInfo *jinfo = g_new0 (IMAPXJobQueueInfo, 1);
8432         CamelFolder *select_folder;
8433         CamelIMAPXJob *job = NULL;
8434         GList *head, *link;
8435
8436         QUEUE_LOCK (is);
8437
8438         jinfo->queue_len = g_queue_get_length (&is->jobs);
8439         jinfo->folders = g_hash_table_new_full (
8440                 (GHashFunc) g_str_hash,
8441                 (GEqualFunc) g_str_equal,
8442                 (GDestroyNotify) g_free,
8443                 (GDestroyNotify) NULL);
8444
8445         head = g_queue_peek_head_link (&is->jobs);
8446
8447         for (link = head; link != NULL; link = g_list_next (link)) {
8448                 CamelFolder *folder;
8449
8450                 job = (CamelIMAPXJob *) link->data;
8451                 folder = camel_imapx_job_ref_folder (job);
8452
8453                 if (folder != NULL) {
8454                         gchar *folder_name;
8455
8456                         folder_name = camel_folder_dup_full_name (folder);
8457                         g_hash_table_add (jinfo->folders, folder_name);
8458
8459                         g_object_unref (folder);
8460                 }
8461         }
8462
8463         select_folder = g_weak_ref_get (&is->select_folder);
8464
8465         if (select_folder != NULL) {
8466                 gchar *folder_name;
8467
8468                 folder_name = camel_folder_dup_full_name (select_folder);
8469                 g_hash_table_add (jinfo->folders, folder_name);
8470
8471                 g_object_unref (select_folder);
8472         }
8473
8474         QUEUE_UNLOCK (is);
8475
8476         return jinfo;
8477 }
8478
8479 /**
8480  * camel_imapx_server_register_untagged_handler:
8481  * @is: a #CamelIMAPXServer instance
8482  * @untagged_response: a string representation of the IMAP
8483  *                     untagged response code. Must be
8484  *                     all-uppercase with underscores allowed
8485  *                     (see RFC 3501)
8486  * @desc: a #CamelIMAPXUntaggedRespHandlerDesc handler description
8487  *        structure. The descriptor structure is expected to
8488  *        remain stable over the lifetime of the #CamelIMAPXServer
8489  *        instance it was registered with. It is the responsibility
8490  *        of the caller to ensure this
8491  *
8492  * Register a new handler function for IMAP untagged responses.
8493  * Pass in a NULL descriptor to delete an existing handler (the
8494  * untagged response will remain known, but will no longer be acted
8495  * upon if the handler is deleted). The return value is intended
8496  * to be used in cases where e.g. an extension to existing handler
8497  * code is implemented with just some new code to be run before
8498  * or after the original handler code
8499  *
8500  * Returns: the #CamelIMAPXUntaggedRespHandlerDesc previously
8501  *          registered for this untagged response, if any,
8502  *          NULL otherwise.
8503  *
8504  * Since: 3.6
8505  */
8506 const CamelIMAPXUntaggedRespHandlerDesc *
8507 camel_imapx_server_register_untagged_handler (CamelIMAPXServer *is,
8508                                               const gchar *untagged_response,
8509                                               const CamelIMAPXUntaggedRespHandlerDesc *desc)
8510 {
8511         const CamelIMAPXUntaggedRespHandlerDesc *previous = NULL;
8512
8513         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
8514         g_return_val_if_fail (untagged_response != NULL, NULL);
8515         /* desc may be NULL */
8516
8517         previous = replace_untagged_descriptor (
8518                 is->priv->untagged_handlers,
8519                 untagged_response, desc);
8520
8521         return previous;
8522 }
8523
8524 gboolean
8525 camel_imapx_server_command_run (CamelIMAPXServer *is,
8526                                 CamelIMAPXCommand *ic,
8527                                 GCancellable *cancellable,
8528                                 GError **error)
8529 {
8530         gboolean ok = FALSE;
8531         CamelIMAPXJob *job = NULL;
8532         gboolean local_job = FALSE;
8533
8534         g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
8535         g_return_val_if_fail (CAMEL_IS_IMAPX_COMMAND (ic), FALSE);
8536         /* cancellable may be NULL */
8537         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
8538
8539         job = camel_imapx_command_get_job (ic);
8540         if (job == NULL) {
8541                 job = camel_imapx_job_new (cancellable);
8542                 camel_imapx_command_set_job (ic, job);
8543                 local_job = TRUE;
8544         }
8545
8546         ok = imapx_command_run_sync (is, ic, cancellable, error);
8547
8548         if (local_job)
8549                 camel_imapx_command_set_job (ic, NULL);
8550
8551         return ok;
8552 }