1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
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.
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.
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.
26 #include <glib/gstdio.h>
27 #include <glib/gi18n-lib.h>
29 // fixme, use own type funcs
37 #include "camel-imapx-server.h"
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"
53 #define c(...) camel_imapx_debug(command, __VA_ARGS__)
54 #define e(...) camel_imapx_debug(extra, __VA_ARGS__)
56 #define CIF(x) ((CamelIMAPXFolder *)x)
58 #define QUEUE_LOCK(x) (g_static_rec_mutex_lock(&(x)->queue_lock))
59 #define QUEUE_UNLOCK(x) (g_static_rec_mutex_unlock(&(x)->queue_lock))
61 #define IDLE_LOCK(x) (g_mutex_lock((x)->idle_lock))
62 #define IDLE_UNLOCK(x) (g_mutex_unlock((x)->idle_lock))
64 /* Try pipelining fetch requests, 'in bits' */
65 #define MULTI_SIZE (20480)
67 /* How many outstanding commands do we allow before we just queue them? */
68 #define MAX_COMMANDS (10)
70 #define MAX_COMMAND_LEN 1000
72 extern gint camel_application_is_exiting;
74 /* Job-specific structs */
75 typedef struct _GetMessageData GetMessageData;
76 typedef struct _RefreshInfoData RefreshInfoData;
77 typedef struct _SyncChangesData SyncChangesData;
78 typedef struct _AppendMessageData AppendMessageData;
79 typedef struct _CopyMessagesData CopyMessagesData;
80 typedef struct _ListData ListData;
81 typedef struct _ManageSubscriptionsData ManageSubscriptionsData;
82 typedef struct _RenameFolderData RenameFolderData;
83 typedef struct _CreateFolderData CreateFolderData;
84 typedef struct _DeleteFolderData DeleteFolderData;
86 struct _GetMessageData {
87 /* in: uid requested */
89 /* in/out: message content stream output */
91 /* working variables */
96 gboolean use_multi_fetch;
99 struct _RefreshInfoData {
100 /* array of refresh info's */
102 /* used for building uidset stuff */
105 gint fetch_msg_limit;
106 CamelFetchType fetch_type;
107 gboolean update_unseen;
108 struct _uidset_state uidset;
109 /* changes during refresh */
110 CamelFolderChangeInfo *changes;
113 struct _SyncChangesData {
115 GPtrArray *changed_uids;
118 GArray *on_user; /* imapx_flag_change */
123 struct _AppendMessageData {
125 CamelMessageInfo *info;
129 struct _CopyMessagesData {
132 gboolean delete_originals;
135 struct _uidset_state uidset;
145 struct _ManageSubscriptionsData {
150 struct _RenameFolderData {
151 gchar *old_folder_name;
152 gchar *new_folder_name;
155 struct _CreateFolderData {
159 struct _DeleteFolderData {
169 static guint signals[LAST_SIGNAL];
171 void imapx_uidset_init (struct _uidset_state *ss, gint total, gint limit);
172 gint imapx_uidset_done (struct _uidset_state *ss, struct _CamelIMAPXCommand *ic);
173 gint imapx_uidset_add (struct _uidset_state *ss, struct _CamelIMAPXCommand *ic, const gchar *uid);
174 static gboolean imapx_command_idle_stop (CamelIMAPXServer *is, GError **error);
175 static gboolean imapx_continuation (CamelIMAPXServer *is, gboolean litplus, GCancellable *cancellable, GError **error);
176 static gboolean imapx_disconnect (CamelIMAPXServer *is);
177 static gint imapx_uid_cmp (gconstpointer ap, gconstpointer bp, gpointer data);
179 static gboolean imapx_is_command_queue_empty (CamelIMAPXServer *is);
181 /* states for the connection? */
191 struct _refresh_info {
194 guint32 server_flags;
195 CamelFlag *server_user_flags;
199 IMAPX_JOB_GET_MESSAGE = 1 << 0,
200 IMAPX_JOB_APPEND_MESSAGE = 1 << 1,
201 IMAPX_JOB_COPY_MESSAGE = 1 << 2,
202 IMAPX_JOB_FETCH_NEW_MESSAGES = 1 << 3,
203 IMAPX_JOB_REFRESH_INFO = 1 << 4,
204 IMAPX_JOB_SYNC_CHANGES = 1 << 5,
205 IMAPX_JOB_EXPUNGE = 1 << 6,
206 IMAPX_JOB_NOOP = 1 << 7,
207 IMAPX_JOB_IDLE = 1 << 8,
208 IMAPX_JOB_LIST = 1 << 9,
209 IMAPX_JOB_MANAGE_SUBSCRIPTION = 1 << 10,
210 IMAPX_JOB_CREATE_FOLDER = 1 << 11,
211 IMAPX_JOB_DELETE_FOLDER = 1 << 12,
212 IMAPX_JOB_RENAME_FOLDER = 1 << 13,
213 IMAPX_JOB_FETCH_MESSAGES = 1 << 14,
216 /* Operations on the store (folder_tree) will have highest priority as we know for sure they are sync
217 * and user triggered. */
219 IMAPX_PRIORITY_CREATE_FOLDER = 200,
220 IMAPX_PRIORITY_DELETE_FOLDER = 200,
221 IMAPX_PRIORITY_RENAME_FOLDER = 200,
222 IMAPX_PRIORITY_MANAGE_SUBSCRIPTION = 200,
223 IMAPX_PRIORITY_SYNC_CHANGES = 150,
224 IMAPX_PRIORITY_EXPUNGE = 150,
225 IMAPX_PRIORITY_GET_MESSAGE = 100,
226 IMAPX_PRIORITY_REFRESH_INFO = 0,
227 IMAPX_PRIORITY_NOOP = 0,
228 IMAPX_PRIORITY_NEW_MESSAGES = 0,
229 IMAPX_PRIORITY_APPEND_MESSAGE = -60,
230 IMAPX_PRIIORITY_COPY_MESSAGE = -60,
231 IMAPX_PRIORITY_LIST = -80,
232 IMAPX_PRIORITY_IDLE = -100,
233 IMAPX_PRIORITY_SYNC_MESSAGE = -120
236 struct _imapx_flag_change {
241 static CamelIMAPXJob *imapx_match_active_job (CamelIMAPXServer *is, guint32 type, const gchar *uid);
242 static void imapx_job_fetch_new_messages_start (CamelIMAPXJob *job, CamelIMAPXServer *is);
243 static gint imapx_refresh_info_uid_cmp (gconstpointer ap, gconstpointer bp, gboolean ascending);
244 static gint imapx_uids_array_cmp (gconstpointer ap, gconstpointer bp);
245 static gboolean imapx_server_sync_changes (CamelIMAPXServer *is, CamelFolder *folder, gint pri, GCancellable *cancellable, GError **error);
246 static void imapx_sync_free_user (GArray *user_set);
248 static void imapx_command_copy_messages_step_start
249 (CamelIMAPXServer *is,
255 IMAPX_IDLE_PENDING, /* Queue is idle; waiting to send IDLE command
256 soon if nothing more interesting happens */
257 IMAPX_IDLE_ISSUED, /* Sent IDLE command; waiting for response */
258 IMAPX_IDLE_STARTED, /* IDLE continuation received; IDLE active */
259 IMAPX_IDLE_CANCEL, /* Cancelled from ISSUED state; need to send
260 DONE as soon as we receive continuation */
262 #define IMAPX_IDLE_DWELL_TIME 2 /* Number of seconds to remain in PENDING
263 state waiting for other commands to be
264 queued, before actually sending IDLE */
266 struct _CamelIMAPXIdle {
268 GThread *idle_thread;
270 GCond *start_watch_cond;
271 GMutex *start_watch_mutex;
272 gboolean start_watch_is_set;
275 enum _idle_state state;
279 static gboolean imapx_in_idle (CamelIMAPXServer *is);
280 static gboolean imapx_idle_supported (CamelIMAPXServer *is);
281 static void imapx_start_idle (CamelIMAPXServer *is);
282 static void imapx_exit_idle (CamelIMAPXServer *is);
283 static void imapx_init_idle (CamelIMAPXServer *is);
284 static gboolean imapx_stop_idle (CamelIMAPXServer *is, GError **error);
285 static gboolean camel_imapx_server_idle (CamelIMAPXServer *is, CamelFolder *folder, GCancellable *cancellable, GError **error);
290 USE_SSL_WHEN_POSSIBLE
293 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
294 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
296 static gboolean imapx_select (CamelIMAPXServer *is, CamelFolder *folder, gboolean force, GCancellable *cancellable, GError **error);
298 G_DEFINE_TYPE (CamelIMAPXServer, camel_imapx_server, CAMEL_TYPE_OBJECT)
301 get_message_data_free (GetMessageData *data)
305 if (data->stream != NULL)
306 g_object_unref (data->stream);
308 g_slice_free (GetMessageData, data);
312 refresh_info_data_free (RefreshInfoData *data)
314 camel_folder_change_info_free (data->changes);
316 g_slice_free (RefreshInfoData, data);
320 sync_changes_data_free (SyncChangesData *data)
322 if (data->folder != NULL) {
323 camel_folder_free_uids (data->folder, data->changed_uids);
324 g_object_unref (data->folder);
327 imapx_sync_free_user (data->on_user);
328 imapx_sync_free_user (data->off_user);
330 g_slice_free (SyncChangesData, data);
334 append_message_data_free (AppendMessageData *data)
337 g_free (data->appended_uid);
339 camel_message_info_free (data->info);
341 g_slice_free (AppendMessageData, data);
345 copy_messages_data_free (CopyMessagesData *data)
347 if (data->dest != NULL)
348 g_object_unref (data->dest);
350 if (data->uids != NULL) {
351 g_ptr_array_foreach (data->uids, (GFunc) g_free, NULL);
352 g_ptr_array_free (data->uids, TRUE);
355 g_slice_free (CopyMessagesData, data);
359 list_data_free (ListData *data)
361 g_free (data->pattern);
364 g_hash_table_destroy (data->folders);
366 g_slice_free (ListData, data);
370 manage_subscriptions_data_free (ManageSubscriptionsData *data)
372 g_free (data->folder_name);
374 g_slice_free (ManageSubscriptionsData, data);
378 rename_folder_data_free (RenameFolderData *data)
380 g_free (data->old_folder_name);
381 g_free (data->new_folder_name);
383 g_slice_free (RenameFolderData, data);
387 create_folder_data_free (CreateFolderData *data)
389 g_free (data->folder_name);
391 g_slice_free (CreateFolderData, data);
395 delete_folder_data_free (DeleteFolderData *data)
397 g_free (data->folder_name);
399 g_slice_free (DeleteFolderData, data);
403 this creates a uid (or sequence number) set directly into a command,
404 if total is set, then we break it up into total uids. (i.e. command time)
405 if limit is set, then we break it up into limit entries (i.e. command length)
408 imapx_uidset_init (struct _uidset_state *ss,
421 imapx_uidset_done (struct _uidset_state *ss,
422 CamelIMAPXCommand *ic)
426 if (ss->last != 0 && ss->last != ss->start) {
427 camel_imapx_command_add (ic, ":%d", ss->last);
441 imapx_uidset_add (struct _uidset_state *ss,
442 CamelIMAPXCommand *ic,
447 uidn = strtoul (uid, NULL, 10);
453 e(ic->is->tagprefix, "uidset add '%s'\n", uid);
456 e(ic->is->tagprefix, " start\n");
457 camel_imapx_command_add (ic, "%d", uidn);
461 if (ss->last != uidn - 1) {
462 if (ss->last == ss->start) {
463 e(ic->is->tagprefix, " ,next\n");
464 camel_imapx_command_add (ic, ",%d", uidn);
467 e(ic->is->tagprefix, " :range\n");
468 camel_imapx_command_add (ic, ":%d,%d", ss->last, uidn);
477 if ((ss->limit && ss->entries >= ss->limit)
478 || (ss->total && ss->uids >= ss->total)) {
479 e(ic->is->tagprefix, " done, %d entries, %d uids\n", ss->entries, ss->uids);
480 if (!imapx_uidset_done (ss, ic))
488 /* Must hold QUEUE_LOCK */
490 imapx_command_start (CamelIMAPXServer *is,
491 CamelIMAPXCommand *ic,
492 GCancellable *cancellable,
495 CamelIMAPXCommandPart *cp;
496 gboolean cp_continuation;
497 gboolean cp_literal_plus;
501 camel_imapx_command_close (ic);
503 head = g_queue_peek_head_link (&ic->parts);
504 g_return_val_if_fail (head != NULL, FALSE);
505 cp = (CamelIMAPXCommandPart *) head->data;
506 ic->current_part = head;
508 cp_continuation = ((cp->type & CAMEL_IMAPX_COMMAND_CONTINUATION) != 0);
509 cp_literal_plus = ((cp->type & CAMEL_IMAPX_COMMAND_LITERAL_PLUS) != 0);
511 /* TODO: If we support literal+ we should be able to write the whole command out
512 * at this point .... >here< */
514 if (cp_continuation || cp_literal_plus)
517 camel_imapx_command_queue_push_tail (is->active, ic);
519 g_static_rec_mutex_lock (&is->ostream_lock);
521 c(is->tagprefix, "Starting command (active=%d,%s) %c%05u %s\r\n", camel_imapx_command_queue_get_length (is->active), is->literal?" literal":"", is->tagprefix, ic->tag, cp->data && g_str_has_prefix (cp->data, "LOGIN") ? "LOGIN..." : cp->data);
522 if (is->stream != NULL) {
525 string = g_strdup_printf ("%c%05u %s\r\n", is->tagprefix, ic->tag, cp->data);
526 retval = camel_stream_write_string ((CamelStream *) is->stream, string, cancellable, NULL);
532 error, CAMEL_IMAPX_ERROR, 1,
533 "Failed to issue the command");
536 while (is->literal == ic && cp_literal_plus) {
537 /* Sent LITERAL+ continuation immediately */
538 if (!imapx_continuation (is, TRUE, cancellable, error))
542 g_static_rec_mutex_unlock (&is->ostream_lock);
547 g_static_rec_mutex_unlock (&is->ostream_lock);
549 camel_imapx_command_queue_remove (is->active, ic);
551 /* HACK: Since we're failing, make sure the command has a status
552 * structure and the result code indicates failure, so the
553 * ic->complete() callback does not start a new command. */
554 if (ic->status == NULL)
555 ic->status = g_malloc0 (sizeof (struct _status_info));
556 if (ic->status->result == IMAPX_OK)
557 ic->status->result = IMAPX_UNKNOWN;
559 /* Send a NULL GError since we've already set a
560 * GError to get here, and we're not interested
561 * in individual command errors. */
562 if (ic != NULL && ic->complete != NULL)
563 ic->complete (is, ic, NULL);
569 duplicate_fetch_or_refresh (CamelIMAPXServer *is,
570 CamelIMAPXCommand *ic)
574 job = camel_imapx_command_get_job (ic);
579 if (!(job->type & (IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES)))
582 if (imapx_match_active_job (is, IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES, NULL)) {
583 c(is->tagprefix, "Not yet sending duplicate fetch/refresh %s command\n", ic->name);
590 /* See if we can start another task yet.
592 * If we're waiting for a literal, we cannot proceed.
594 * If we're about to change the folder we're
595 * looking at from user-direction, we dont proceed.
597 * If we have a folder selected, first see if any
598 * jobs are waiting on it, but only if they are
599 * at least as high priority as anything we
602 * If we dont, select the first folder required,
603 * then queue all the outstanding jobs on it, that
604 * are at least as high priority as the first.
606 * must have QUEUE lock */
609 imapx_command_start_next (CamelIMAPXServer *is,
610 GCancellable *cancellable,
613 CamelIMAPXCommand *first_ic;
616 c(is->tagprefix, "** Starting next command\n");
618 c(is->tagprefix, "* no; waiting for literal '%s'\n", is->literal->name);
622 if (is->select_pending) {
623 GQueue start = G_QUEUE_INIT;
626 c(is->tagprefix, "-- Checking job queue for non-folder jobs\n");
628 head = camel_imapx_command_queue_peek_head_link (is->queue);
630 /* Tag which commands in the queue to start. */
631 for (link = head; link != NULL; link = g_list_next (link)) {
632 CamelIMAPXCommand *ic = link->data;
634 if (ic->pri < min_pri)
637 c(is->tagprefix, "-- %3d '%s'?\n", (gint)ic->pri, ic->name);
639 c(is->tagprefix, "--> starting '%s'\n", ic->name);
641 g_queue_push_tail (&start, link);
644 if (g_queue_get_length (&start) == MAX_COMMANDS)
648 if (g_queue_is_empty (&start))
649 c(is->tagprefix, "* no, waiting for pending select '%s'\n", camel_folder_get_full_name (is->select_pending));
651 /* Start the tagged commands.
653 * Each command must be removed from 'is->queue' before
654 * starting it, so we temporarily reference the command
655 * to avoid accidentally finalizing it. */
656 while ((link = g_queue_pop_head (&start)) != NULL) {
657 CamelIMAPXCommand *ic;
658 ic = camel_imapx_command_ref (link->data);
659 camel_imapx_command_queue_delete_link (is->queue, link);
660 imapx_command_start (is, ic, cancellable, error);
661 camel_imapx_command_unref (ic);
667 if (imapx_idle_supported (is) && is->state == IMAPX_SELECTED) {
668 gboolean empty = imapx_is_command_queue_empty (is);
670 if (imapx_in_idle (is) && !camel_imapx_command_queue_is_empty (is->queue)) {
671 /* if imapx_stop_idle() returns FALSE, it was only
672 * pending and we can go ahead and send a new command
673 * immediately. If it returns TRUE, either it sent the
674 * DONE to exit IDLE mode, or there was an error.
675 * Either way, we do nothing more right now. */
676 if (imapx_stop_idle (is, error)) {
677 c(is->tagprefix, "waiting for idle to stop \n");
680 } else if (empty && !imapx_in_idle (is)) {
681 imapx_start_idle (is);
682 c(is->tagprefix, "starting idle \n");
687 if (camel_imapx_command_queue_is_empty (is->queue)) {
688 c(is->tagprefix, "* no, no jobs\n");
692 /* See if any queued jobs on this select first */
693 if (is->select_folder) {
694 GQueue start = G_QUEUE_INIT;
696 gboolean commands_started = FALSE;
698 c(is->tagprefix, "- we're selected on '%s', current jobs?\n",
699 camel_folder_get_full_name (is->select_folder));
701 head = camel_imapx_command_queue_peek_head_link (is->active);
703 /* Find the highest priority in the active queue. */
704 for (link = head; link != NULL; link = g_list_next (link)) {
705 CamelIMAPXCommand *ic = link->data;
707 min_pri = MAX (min_pri, ic->pri);
708 c(is->tagprefix, "- %3d '%s'\n", (gint)ic->pri, ic->name);
711 if (camel_imapx_command_queue_get_length (is->active) >= MAX_COMMANDS) {
712 c(is->tagprefix, "** too many jobs busy, waiting for results for now\n");
716 c(is->tagprefix, "-- Checking job queue\n");
718 head = camel_imapx_command_queue_peek_head_link (is->queue);
720 /* Tag which commands in the queue to start. */
721 for (link = head; link != NULL; link = g_list_next (link)) {
722 CamelIMAPXCommand *ic = link->data;
724 if (is->literal != NULL)
727 if (ic->pri < min_pri)
730 c(is->tagprefix, "-- %3d '%s'?\n", (gint)ic->pri, ic->name);
731 if (!ic->select || ((ic->select == is->select_folder) &&
732 !duplicate_fetch_or_refresh (is, ic))) {
733 c(is->tagprefix, "--> starting '%s'\n", ic->name);
735 g_queue_push_tail (&start, link);
737 /* This job isn't for the selected folder, but we don't want to
738 * consider jobs with _lower_ priority than this, even if they
739 * are for the selected folder. */
743 if (g_queue_get_length (&start) == MAX_COMMANDS)
747 /* Start the tagged commands.
749 * Each command must be removed from 'is->queue' before
750 * starting it, so we temporarily reference the command
751 * to avoid accidentally finalizing it. */
752 while ((link = g_queue_pop_head (&start)) != NULL) {
753 CamelIMAPXCommand *ic;
754 ic = camel_imapx_command_ref (link->data);
755 camel_imapx_command_queue_delete_link (is->queue, link);
756 imapx_command_start (is, ic, cancellable, error);
757 camel_imapx_command_unref (ic);
758 commands_started = TRUE;
761 if (commands_started)
765 /* This won't be NULL because we checked for an empty queue above. */
766 first_ic = camel_imapx_command_queue_peek_head (is->queue);
768 /* If we need to select a folder for the first command, do it now,
769 * once it is complete it will re-call us if it succeeded. */
770 if (first_ic->select) {
771 c(is->tagprefix, "Selecting folder '%s' for command '%s'(%p)\n",
772 camel_folder_get_full_name (first_ic->select),
773 first_ic->name, first_ic);
774 imapx_select (is, first_ic->select, FALSE, cancellable, error);
776 GQueue start = G_QUEUE_INIT;
779 min_pri = first_ic->pri;
781 head = camel_imapx_command_queue_peek_head_link (is->queue);
783 /* Tag which commands in the queue to start. */
784 for (link = head; link != NULL; link = g_list_next (link)) {
785 CamelIMAPXCommand *ic = link->data;
787 if (is->literal != NULL)
790 if (ic->pri < min_pri)
793 if (!ic->select || (ic->select == is->select_folder &&
794 !duplicate_fetch_or_refresh (is, ic))) {
795 c(is->tagprefix, "* queueing job %3d '%s'\n", (gint)ic->pri, ic->name);
797 g_queue_push_tail (&start, link);
800 if (g_queue_get_length (&start) == MAX_COMMANDS)
804 /* Start the tagged commands.
806 * Each command must be removed from 'is->queue' before
807 * starting it, so we temporarily reference the command
808 * to avoid accidentally finalizing it. */
809 while ((link = g_queue_pop_head (&start)) != NULL) {
810 CamelIMAPXCommand *ic;
811 ic = camel_imapx_command_ref (link->data);
812 camel_imapx_command_queue_delete_link (is->queue, link);
813 imapx_command_start (is, ic, cancellable, error);
814 camel_imapx_command_unref (ic);
820 imapx_is_command_queue_empty (CamelIMAPXServer *is)
822 if (!camel_imapx_command_queue_is_empty (is->queue))
825 if (!camel_imapx_command_queue_is_empty (is->active))
832 imapx_command_queue (CamelIMAPXServer *is,
833 CamelIMAPXCommand *ic)
837 /* We enqueue in priority order, new messages have
838 * higher priority than older messages with the same priority */
840 job = camel_imapx_command_get_job (ic);
841 g_return_if_fail (CAMEL_IS_IMAPX_JOB (job));
843 camel_imapx_command_close (ic);
845 c(is->tagprefix, "enqueue job '%.*s'\n", ((CamelIMAPXCommandPart *)ic->parts.head->data)->data_size, ((CamelIMAPXCommandPart *)ic->parts.head->data)->data);
849 if (is->state == IMAPX_SHUTDOWN) {
850 c(is->tagprefix, "refuse to queue job on disconnected server\n");
851 if (job->error == NULL)
853 &job->error, CAMEL_IMAPX_ERROR, 1,
854 "%s", _("Server disconnected"));
857 /* Send a NULL GError since we've already set
858 * the job's GError, and we're not interested
859 * in individual command errors. */
860 if (ic->complete != NULL)
861 ic->complete (is, ic, NULL);
865 camel_imapx_command_queue_insert_sorted (is->queue, ic);
867 imapx_command_start_next (is, job->cancellable, NULL);
874 /* Must have QUEUE lock */
875 static CamelIMAPXCommand *
876 imapx_find_command_tag (CamelIMAPXServer *is,
879 CamelIMAPXCommand *ic = NULL;
884 if (is->literal != NULL && is->literal->tag == tag) {
889 head = camel_imapx_command_queue_peek_head_link (is->active);
891 for (link = head; link != NULL; link = g_list_next (link)) {
892 CamelIMAPXCommand *candidate = link->data;
894 if (candidate->tag == tag) {
906 /* Must not have QUEUE lock */
907 static CamelIMAPXJob *
908 imapx_match_active_job (CamelIMAPXServer *is,
912 CamelIMAPXJob *match = NULL;
917 head = camel_imapx_command_queue_peek_head_link (is->active);
919 for (link = head; link != NULL; link = g_list_next (link)) {
920 CamelIMAPXCommand *ic = link->data;
923 job = camel_imapx_command_get_job (ic);
928 if (!(job->type & type))
931 if (camel_imapx_job_matches (job, is->select_folder, uid)) {
942 static CamelIMAPXJob *
943 imapx_is_job_in_queue (CamelIMAPXServer *is,
949 CamelIMAPXJob *job = NULL;
950 gboolean found = FALSE;
954 head = g_queue_peek_head_link (&is->jobs);
956 for (link = head; link != NULL; link = g_list_next (link)) {
957 job = (CamelIMAPXJob *) link->data;
959 if (!job || !(job->type & type))
962 if (camel_imapx_job_matches (job, folder, uid)) {
977 imapx_expunge_uid_from_summary (CamelIMAPXServer *is,
979 gboolean unsolicited)
981 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) is->select_folder;
983 if (unsolicited && ifolder->exists_on_server)
984 ifolder->exists_on_server--;
986 if (is->changes == NULL)
987 is->changes = camel_folder_change_info_new ();
989 camel_folder_summary_remove_uid (is->select_folder->summary, uid);
990 is->expunged = g_list_prepend (is->expunged, uid);
992 camel_folder_change_info_remove_uid (is->changes, uid);
994 if (imapx_idle_supported (is) && imapx_in_idle (is)) {
995 camel_folder_summary_save_to_db (is->select_folder->summary, NULL);
996 imapx_update_store_summary (is->select_folder);
997 camel_folder_changed (is->select_folder, is->changes);
999 g_list_free_full (is->expunged, (GDestroyNotify) g_free);
1000 is->expunged = NULL;
1002 camel_folder_change_info_clear (is->changes);
1007 imapx_get_uid_from_index (CamelFolderSummary *summary,
1013 g_return_val_if_fail (summary != NULL, NULL);
1015 array = camel_folder_summary_get_array (summary);
1016 g_return_val_if_fail (array != NULL, NULL);
1018 if (id < array->len) {
1019 camel_folder_sort_uids (camel_folder_summary_get_folder (summary), array);
1020 uid = g_strdup (g_ptr_array_index (array, id));
1023 camel_folder_summary_free_array (array);
1029 invalidate_local_cache (CamelIMAPXFolder *ifolder,
1030 guint64 new_uidvalidity)
1032 CamelFolder *cfolder;
1033 CamelFolderChangeInfo *changes;
1037 g_return_if_fail (ifolder != NULL);
1039 cfolder = CAMEL_FOLDER (ifolder);
1040 g_return_if_fail (cfolder != NULL);
1042 changes = camel_folder_change_info_new ();
1044 uids = camel_folder_summary_get_array (cfolder->summary);
1045 for (ii = 0; uids && ii < uids->len; ii++) {
1046 const gchar *uid = uids->pdata[ii];
1049 camel_folder_change_info_change_uid (changes, uid);
1052 camel_folder_summary_free_array (uids);
1054 CAMEL_IMAPX_SUMMARY (cfolder->summary)->validity = new_uidvalidity;
1055 camel_folder_summary_touch (cfolder->summary);
1056 camel_folder_summary_save_to_db (cfolder->summary, NULL);
1058 camel_data_cache_clear (ifolder->cache, "cache");
1059 camel_data_cache_clear (ifolder->cache, "cur");
1061 camel_folder_changed (cfolder, changes);
1062 camel_folder_change_info_free (changes);
1065 /* handle any untagged responses */
1067 imapx_untagged (CamelIMAPXServer *is,
1068 GCancellable *cancellable,
1071 CamelService *service;
1072 CamelSettings *settings;
1073 CamelSortType fetch_order;
1075 guchar *token, *p, c;
1077 gboolean lsub = FALSE;
1078 struct _status_info *sinfo;
1080 service = CAMEL_SERVICE (is->store);
1081 settings = camel_service_get_settings (service);
1083 fetch_order = camel_imapx_settings_get_fetch_order (
1084 CAMEL_IMAPX_SETTINGS (settings));
1086 e(is->tagprefix, "got untagged response\n");
1088 tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
1092 if (tok == IMAPX_TOK_INT) {
1093 id = strtoul ((gchar *) token, NULL, 10);
1094 tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
1101 error, CAMEL_IMAPX_ERROR, 1,
1102 "truncated server response");
1106 e(is->tagprefix, "Have token '%s' id %d\n", token, id);
1109 *p++ = toupper((gchar) c);
1111 switch (imapx_tokenise ((const gchar *) token, len)) {
1112 case IMAPX_CAPABILITY:
1114 imapx_free_capability (is->cinfo);
1115 is->cinfo = imapx_parse_capability (is->stream, cancellable, error);
1116 if (is->cinfo == NULL)
1118 c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
1120 case IMAPX_EXPUNGE: {
1121 guint32 expunge = id;
1122 CamelIMAPXJob *job = imapx_match_active_job (is, IMAPX_JOB_EXPUNGE, NULL);
1124 /* If there is a job running, let it handle the deletion */
1128 c(is->tagprefix, "expunged: %d\n", id);
1129 if (is->select_folder) {
1132 uid = imapx_get_uid_from_index (is->select_folder->summary, expunge - 1);
1136 imapx_expunge_uid_from_summary (is, uid, TRUE);
1141 case IMAPX_VANISHED: {
1143 gboolean unsolicited = TRUE;
1149 tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
1153 unsolicited = FALSE;
1154 while (tok != ')') {
1155 /* We expect this to be 'EARLIER' */
1156 tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
1161 camel_imapx_stream_ungettoken (is->stream, tok, token, len);
1163 uids = imapx_parse_uids (is->stream, cancellable, error);
1166 for (i = 0; i < uids->len; i++) {
1167 gchar *uid = g_strdup_printf("%u", GPOINTER_TO_UINT(g_ptr_array_index (uids, i)));
1168 c(is->tagprefix, "vanished: %s\n", uid);
1169 imapx_expunge_uid_from_summary (is, uid, unsolicited);
1171 g_ptr_array_free (uids, FALSE);
1174 case IMAPX_NAMESPACE: {
1175 CamelIMAPXNamespaceList *nsl = NULL;
1177 nsl = imapx_parse_namespace_list (is->stream, cancellable, error);
1179 CamelIMAPXStore *imapx_store = (CamelIMAPXStore *) is->store;
1180 CamelIMAPXStoreNamespace *ns;
1182 imapx_store->summary->namespaces = nsl;
1183 camel_store_summary_touch ((CamelStoreSummary *) imapx_store->summary);
1185 /* TODO Need to remove imapx_store->dir_sep to support multiple namespaces */
1188 imapx_store->dir_sep = ns->sep;
1194 c(is->tagprefix, "exists: %d\n", id);
1197 if (is->select_folder)
1198 ((CamelIMAPXFolder *) is->select_folder)->exists_on_server = id;
1200 if (imapx_idle_supported (is) && imapx_in_idle (is)) {
1201 if (camel_folder_summary_count (is->select_folder->summary) < id)
1202 imapx_stop_idle (is, error);
1209 imapx_parse_flags (is->stream, &flags, NULL, cancellable, error);
1211 c(is->tagprefix, "flags: %08x\n", flags);
1215 struct _fetch_info *finfo;
1217 finfo = imapx_parse_fetch (is->stream, cancellable, error);
1218 if (finfo == NULL) {
1219 imapx_free_fetch (finfo);
1223 if ((finfo->got & (FETCH_BODY | FETCH_UID)) == (FETCH_BODY | FETCH_UID)) {
1224 CamelIMAPXJob *job = imapx_match_active_job (is, IMAPX_JOB_GET_MESSAGE, finfo->uid);
1225 GetMessageData *data;
1227 data = camel_imapx_job_get_data (job);
1228 g_return_val_if_fail (data != NULL, FALSE);
1230 /* This must've been a get-message request, fill out the body stream,
1231 * in the right spot */
1233 if (job && job->error == NULL) {
1234 if (data->use_multi_fetch) {
1235 data->body_offset = finfo->offset;
1236 g_seekable_seek (G_SEEKABLE (data->stream), finfo->offset, G_SEEK_SET, NULL, NULL);
1239 data->body_len = camel_stream_write_to_stream (finfo->body, data->stream, job->cancellable, &job->error);
1240 if (data->body_len == -1)
1243 _("Error writing to cache stream: "));
1247 if ((finfo->got & FETCH_FLAGS) && !(finfo->got & FETCH_HEADER)) {
1248 CamelIMAPXJob *job = imapx_match_active_job (is, IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES, NULL);
1249 /* This is either a refresh_info job, check to see if it is and update
1250 * if so, otherwise it must've been an unsolicited response, so update
1251 * the summary to match */
1253 if (job && (finfo->got & FETCH_UID)) {
1254 RefreshInfoData *data;
1255 struct _refresh_info r;
1257 data = camel_imapx_job_get_data (job);
1258 g_return_val_if_fail (data != NULL, FALSE);
1262 r.server_flags = finfo->flags;
1263 r.server_user_flags = finfo->user_flags;
1264 finfo->user_flags = NULL;
1266 g_array_append_val (data->infos, r);
1267 } else if (is->select_folder) {
1268 CamelFolder *folder;
1269 CamelMessageInfo *mi = NULL;
1270 gboolean changed = FALSE;
1273 g_object_ref (is->select_folder);
1274 folder = is->select_folder;
1276 c(is->tagprefix, "flag changed: %d\n", id);
1278 if (finfo->got & FETCH_UID) {
1282 uid = imapx_get_uid_from_index (folder->summary, id - 1);
1286 mi = camel_folder_summary_get (folder->summary, uid);
1288 /* It's unsolicited _unless_ is->select_pending (i.e. during
1289 * a QRESYNC SELECT */
1290 changed = imapx_update_message_info_flags (mi, finfo->flags, finfo->user_flags, is->permanentflags, folder, !is->select_pending);
1292 /* This (UID + FLAGS for previously unknown message) might
1293 * happen during a SELECT (QRESYNC). We should use it. */
1294 c(is->tagprefix, "flags changed for unknown uid %s\n.", uid);
1296 finfo->user_flags = NULL;
1300 if (is->changes == NULL)
1301 is->changes = camel_folder_change_info_new ();
1303 camel_folder_change_info_change_uid (is->changes, uid);
1307 if (imapx_idle_supported (is) && changed && imapx_in_idle (is)) {
1308 camel_folder_summary_save_to_db (is->select_folder->summary, NULL);
1309 imapx_update_store_summary (is->select_folder);
1310 camel_folder_changed (is->select_folder, is->changes);
1311 camel_folder_change_info_clear (is->changes);
1315 camel_message_info_free (mi);
1316 g_object_unref (folder);
1320 if ((finfo->got & (FETCH_HEADER | FETCH_UID)) == (FETCH_HEADER | FETCH_UID)) {
1321 CamelIMAPXJob *job = imapx_match_active_job (is, IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES, NULL);
1323 /* This must be a refresh info job as well, but it has asked for
1324 * new messages to be added to the index */
1327 CamelMimeParser *mp;
1328 CamelMessageInfo *mi;
1330 /* Do we want to save these headers for later too? Do we care? */
1332 mp = camel_mime_parser_new ();
1333 camel_mime_parser_init_with_stream (mp, finfo->header, NULL);
1334 mi = camel_folder_summary_info_new_from_parser (job->folder->summary, mp);
1335 g_object_unref (mp);
1338 guint32 server_flags;
1339 CamelFlag *server_user_flags;
1340 CamelMessageInfoBase *binfo;
1341 gboolean free_user_flags = FALSE;
1343 mi->uid = camel_pstring_strdup (finfo->uid);
1345 if (!(finfo->got & FETCH_FLAGS)) {
1346 RefreshInfoData *data;
1347 struct _refresh_info *r = NULL;
1349 gboolean found = FALSE;
1351 data = camel_imapx_job_get_data (job);
1352 g_return_val_if_fail (data != NULL, FALSE);
1354 min = data->last_index;
1357 /* array is sorted, so use a binary search */
1361 mid = (min + max) / 2;
1362 r = &g_array_index (data->infos, struct _refresh_info, mid);
1363 cmp = imapx_refresh_info_uid_cmp (finfo->uid, r->uid, fetch_order == CAMEL_SORT_ASCENDING);
1372 } while (!found && min <= max);
1375 g_assert_not_reached ();
1377 server_flags = r->server_flags;
1378 server_user_flags = r->server_user_flags;
1380 server_flags = finfo->flags;
1381 server_user_flags = finfo->user_flags;
1382 /* free user_flags ? */
1383 finfo->user_flags = NULL;
1384 free_user_flags = TRUE;
1387 /* If the message is a really new one -- equal or higher than what
1388 * we know as UIDNEXT for the folder, then it came in since we last
1389 * fetched UIDNEXT and UNREAD count. We'll update UIDNEXT in the
1390 * command completion, but update UNREAD count now according to the
1391 * message SEEN flag */
1392 if (!(server_flags & CAMEL_MESSAGE_SEEN)) {
1393 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
1394 guint64 uidl = strtoull (mi->uid, NULL, 10);
1396 if (uidl >= ifolder->uidnext_on_server) {
1397 c(is->tagprefix, "Updating unread count for new message %s\n", mi->uid);
1398 ((CamelIMAPXFolder *) job->folder)->unread_on_server++;
1400 c(is->tagprefix, "Not updating unread count for new message %s\n", mi->uid);
1404 binfo = (CamelMessageInfoBase *) mi;
1405 binfo->size = finfo->size;
1407 if (!camel_folder_summary_check_uid (job->folder->summary, mi->uid)) {
1408 RefreshInfoData *data;
1409 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
1412 data = camel_imapx_job_get_data (job);
1413 g_return_val_if_fail (data != NULL, FALSE);
1415 imapx_set_message_info_flags_for_new_message (mi, server_flags, server_user_flags, job->folder);
1416 camel_folder_summary_add (job->folder->summary, mi);
1417 camel_folder_change_info_add_uid (data->changes, mi->uid);
1419 if (!g_hash_table_lookup (ifolder->ignore_recent, mi->uid)) {
1420 camel_folder_change_info_recent_uid (data->changes, mi->uid);
1421 g_hash_table_remove (ifolder->ignore_recent, mi->uid);
1424 cnt = (camel_folder_summary_count (job->folder->summary) * 100 ) / ifolder->exists_on_server;
1425 camel_operation_progress (job->cancellable, cnt ? cnt : 1);
1428 if (free_user_flags && server_user_flags)
1429 camel_flag_list_free (&server_user_flags);
1435 imapx_free_fetch (finfo);
1441 struct _list_info *linfo = imapx_parse_list (is->stream, cancellable, error);
1448 job = imapx_match_active_job (is, IMAPX_JOB_LIST, linfo->name);
1450 data = camel_imapx_job_get_data (job);
1451 g_return_val_if_fail (data != NULL, FALSE);
1453 // TODO: we want to make sure the names match?
1455 if (data->flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) {
1456 c(is->tagprefix, "lsub: '%s' (%c)\n", linfo->name, linfo->separator);
1459 c(is->tagprefix, "list: '%s' (%c)\n", linfo->name, linfo->separator);
1462 if (job && g_hash_table_lookup (data->folders, linfo->name) == NULL) {
1464 linfo->flags |= CAMEL_FOLDER_SUBSCRIBED;
1465 g_hash_table_insert (data->folders, linfo->name, linfo);
1467 g_warning("got list response but no current listing job happening?\n");
1468 imapx_free_list (linfo);
1473 c(is->tagprefix, "recent: %d\n", id);
1476 case IMAPX_STATUS: {
1477 struct _state_info *sinfo = imapx_parse_status_info (is->stream, cancellable, error);
1479 CamelIMAPXStoreSummary *s = ((CamelIMAPXStore *) is->store)->summary;
1480 CamelIMAPXStoreNamespace *ns;
1481 CamelIMAPXFolder *ifolder = NULL;;
1483 ns = camel_imapx_store_summary_namespace_find_full (s, sinfo->name);
1487 path_name = camel_imapx_store_summary_full_to_path (s, sinfo->name, ns->sep);
1488 c(is->tagprefix, "Got folder path '%s' for full '%s'\n", path_name, sinfo->name);
1490 ifolder = (gpointer) camel_store_get_folder_sync (is->store, path_name, 0, cancellable, error);
1495 CamelFolder *cfolder = CAMEL_FOLDER (ifolder);
1497 ifolder->unread_on_server = sinfo->unseen;
1498 ifolder->exists_on_server = sinfo->messages;
1499 ifolder->modseq_on_server = sinfo->highestmodseq;
1500 ifolder->uidnext_on_server = sinfo->uidnext;
1501 ifolder->uidvalidity_on_server = sinfo->uidvalidity;
1502 if (sinfo->uidvalidity && sinfo->uidvalidity != ((CamelIMAPXSummary *) cfolder->summary)->validity)
1503 invalidate_local_cache (ifolder, sinfo->uidvalidity);
1505 c(is->tagprefix, "Received STATUS for unknown folder '%s'\n", sinfo->name);
1508 g_free (sinfo->name);
1516 if (camel_imapx_stream_text (is->stream, &token, cancellable, NULL)) {
1517 c(is->tagprefix, "BYE: %s\n", token);
1519 error, CAMEL_IMAPX_ERROR, 1,
1520 "IMAP server said BYE: %s", token);
1522 is->state = IMAPX_SHUTDOWN;
1526 c(is->tagprefix, "preauthenticated\n");
1527 if (is->state < IMAPX_AUTHENTICATED)
1528 is->state = IMAPX_AUTHENTICATED;
1529 /* fall through... */
1530 case IMAPX_OK: case IMAPX_NO: case IMAPX_BAD:
1531 /* TODO: validate which ones of these can happen as unsolicited responses */
1532 /* TODO: handle bye/preauth differently */
1533 camel_imapx_stream_ungettoken (is->stream, tok, token, len);
1534 sinfo = imapx_parse_status (is->stream, cancellable, error);
1537 switch (sinfo->condition) {
1539 c(is->tagprefix, "previously selected folder is now closed\n");
1540 if (is->select_pending && !is->select_folder) {
1541 is->select_folder = is->select_pending;
1544 case IMAPX_READ_WRITE:
1545 is->mode = IMAPX_MODE_READ | IMAPX_MODE_WRITE;
1546 c(is->tagprefix, "folder is read-write\n");
1548 case IMAPX_READ_ONLY:
1549 is->mode = IMAPX_MODE_READ;
1550 c(is->tagprefix, "folder is read-only\n");
1552 case IMAPX_UIDVALIDITY:
1553 is->uidvalidity = sinfo->u.uidvalidity;
1556 is->unseen = sinfo->u.unseen;
1558 case IMAPX_HIGHESTMODSEQ:
1559 is->highestmodseq = sinfo->u.highestmodseq;
1561 case IMAPX_PERMANENTFLAGS:
1562 is->permanentflags = sinfo->u.permanentflags;
1565 is->uidnext = sinfo->u.uidnext;
1568 c(is->tagprefix, "ALERT!: %s\n", sinfo->text);
1571 c(is->tagprefix, "PARSE: %s\n", sinfo->text);
1573 case IMAPX_CAPABILITY:
1574 if (sinfo->u.cinfo) {
1575 struct _capability_info *cinfo = is->cinfo;
1576 is->cinfo = sinfo->u.cinfo;
1577 sinfo->u.cinfo = NULL;
1579 imapx_free_capability (cinfo);
1580 c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
1586 imapx_free_status (sinfo);
1589 /* unknown response, just ignore it */
1590 c(is->tagprefix, "unknown token: %s\n", token);
1593 return (camel_imapx_stream_skip (is->stream, cancellable, error) == 0);
1596 /* handle any continuation requests
1597 * either data continuations, or auth continuation */
1599 imapx_continuation (CamelIMAPXServer *is,
1601 GCancellable *cancellable,
1604 CamelIMAPXCommand *ic, *newliteral = NULL;
1605 CamelIMAPXCommandPart *cp;
1608 /* The 'literal' pointer is like a write-lock, nothing else
1609 * can write while we have it ... so we dont need any
1610 * ohter lock here. All other writes go through
1612 if (imapx_idle_supported (is) && imapx_in_idle (is)) {
1613 camel_imapx_stream_skip (is->stream, cancellable, error);
1615 c(is->tagprefix, "Got continuation response for IDLE \n");
1616 IDLE_LOCK (is->idle);
1617 /* We might have actually sent the DONE already! */
1618 if (is->idle->state == IMAPX_IDLE_ISSUED)
1619 is->idle->state = IMAPX_IDLE_STARTED;
1620 else if (is->idle->state == IMAPX_IDLE_CANCEL) {
1621 /* IDLE got cancelled after we sent the command, while
1622 * we were waiting for this continuation. Send DONE
1624 if (!imapx_command_idle_stop (is, error)) {
1625 IDLE_UNLOCK (is->idle);
1628 is->idle->state = IMAPX_IDLE_OFF;
1630 c(is->tagprefix, "idle starts in wrong state %d\n",
1633 IDLE_UNLOCK (is->idle);
1637 imapx_command_start_next (is, cancellable, error);
1646 camel_imapx_stream_skip (is->stream, cancellable, error);
1647 c(is->tagprefix, "got continuation response with no outstanding continuation requests?\n");
1650 c(is->tagprefix, "got continuation response for data\n");
1652 c(is->tagprefix, "sending LITERAL+ continuation\n");
1655 link = ic->current_part;
1656 g_return_val_if_fail (link != NULL, FALSE);
1657 cp = (CamelIMAPXCommandPart *) link->data;
1659 switch (cp->type & CAMEL_IMAPX_COMMAND_MASK) {
1660 case CAMEL_IMAPX_COMMAND_DATAWRAPPER:
1661 c(is->tagprefix, "writing data wrapper to literal\n");
1662 camel_data_wrapper_write_to_stream_sync ((CamelDataWrapper *) cp->ob, (CamelStream *) is->stream, cancellable, NULL);
1664 case CAMEL_IMAPX_COMMAND_STREAM:
1665 c(is->tagprefix, "writing stream to literal\n");
1666 camel_stream_write_to_stream ((CamelStream *) cp->ob, (CamelStream *) is->stream, cancellable, NULL);
1668 case CAMEL_IMAPX_COMMAND_AUTH: {
1672 if (camel_imapx_stream_text (is->stream, &token, cancellable, error))
1675 resp = camel_sasl_challenge_base64_sync (
1676 (CamelSasl *) cp->ob, (const gchar *) token,
1677 cancellable, error);
1681 c(is->tagprefix, "got auth continuation, feeding token '%s' back to auth mech\n", resp);
1683 camel_stream_write ((CamelStream *) is->stream, resp, strlen (resp), cancellable, NULL);
1685 /* we want to keep getting called until we get a status reponse from the server
1686 * ignore what sasl tells us */
1688 /* We already ate the end of the input stream line */
1691 case CAMEL_IMAPX_COMMAND_FILE: {
1694 c(is->tagprefix, "writing file '%s' to literal\n", (gchar *)cp->ob);
1697 if (cp->ob && (file = camel_stream_fs_new_with_name (cp->ob, O_RDONLY, 0, NULL))) {
1698 camel_stream_write_to_stream (file, (CamelStream *) is->stream, cancellable, NULL);
1699 g_object_unref (file);
1700 } else if (cp->ob_size > 0) {
1701 // Server is expecting data ... ummm, send it zeros? abort?
1704 case CAMEL_IMAPX_COMMAND_STRING:
1705 camel_stream_write ((CamelStream *) is->stream, cp->ob, cp->ob_size, cancellable, NULL);
1708 /* should we just ignore? */
1711 error, CAMEL_IMAPX_ERROR, 1,
1712 "continuation response for non-continuation request");
1717 camel_imapx_stream_skip (is->stream, cancellable, error);
1720 link = g_list_next (link);
1722 ic->current_part = link;
1723 cp = (CamelIMAPXCommandPart *) link->data;
1725 c(is->tagprefix, "next part of command \"%c%05u: %s\"\n", is->tagprefix, ic->tag, cp->data);
1726 camel_stream_write_string ((CamelStream *) is->stream, cp->data, cancellable, NULL);
1727 camel_stream_write_string ((CamelStream *) is->stream, "\r\n", cancellable, NULL);
1728 if (cp->type & (CAMEL_IMAPX_COMMAND_CONTINUATION | CAMEL_IMAPX_COMMAND_LITERAL_PLUS)) {
1731 g_assert (g_list_next (link) == NULL);
1734 c(is->tagprefix, "%p: queueing continuation\n", ic);
1735 camel_stream_write_string((CamelStream *)is->stream, "\r\n", cancellable, NULL);
1739 is->literal = newliteral;
1742 imapx_command_start_next (is, cancellable, error);
1748 /* handle a completion line */
1750 imapx_completion (CamelIMAPXServer *is,
1753 GCancellable *cancellable,
1756 CamelIMAPXCommand *ic;
1759 /* Given "A0001 ...", 'A' = tag prefix, '0001' = tag. */
1761 if (token[0] != is->tagprefix) {
1763 error, CAMEL_IMAPX_ERROR, 1,
1764 "Server sent unexpected response: %s", token);
1768 tag = strtoul ((gchar *) token + 1, NULL, 10);
1770 if ((ic = imapx_find_command_tag (is, tag)) == NULL) {
1772 error, CAMEL_IMAPX_ERROR, 1,
1773 "got response tag unexpectedly: %s", token);
1777 c(is->tagprefix, "Got completion response for command %05u '%s'\n", ic->tag, ic->name);
1779 if (camel_folder_change_info_changed (is->changes)) {
1780 camel_folder_summary_save_to_db (is->select_folder->summary, NULL);
1782 g_list_free_full (is->expunged, (GDestroyNotify) g_free);
1783 is->expunged = NULL;
1785 imapx_update_store_summary (is->select_folder);
1786 camel_folder_changed (is->select_folder, is->changes);
1787 camel_folder_change_info_clear (is->changes);
1792 camel_imapx_command_ref (ic);
1793 camel_imapx_command_queue_remove (is->active, ic);
1794 camel_imapx_command_queue_push_tail (is->done, ic);
1795 camel_imapx_command_unref (ic);
1797 if (is->literal == ic)
1800 if (g_list_next (ic->current_part) != NULL) {
1803 error, CAMEL_IMAPX_ERROR, 1,
1804 "command still has unsent parts? %s", ic->name);
1808 camel_imapx_command_queue_remove (is->done, ic);
1812 ic->status = imapx_parse_status (is->stream, cancellable, error);
1814 if (ic->status == NULL)
1817 if (ic->complete != NULL)
1818 if (!ic->complete (is, ic, error))
1822 imapx_command_start_next (is, cancellable, error);
1829 imapx_step (CamelIMAPXServer *is,
1830 GCancellable *cancellable,
1837 // poll ? wait for other stuff? loop?
1838 tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
1843 return imapx_untagged (is, cancellable, error);
1844 else if (tok == IMAPX_TOK_TOKEN)
1845 return imapx_completion (is, token, len, cancellable, error);
1846 else if (tok == '+')
1847 return imapx_continuation (is, FALSE, cancellable, error);
1850 error, CAMEL_IMAPX_ERROR, 1,
1851 "unexpected server response:");
1856 /* Used to run 1 command synchronously,
1857 * use for capa, login, and namespaces only. */
1859 imapx_command_run (CamelIMAPXServer *is,
1860 CamelIMAPXCommand *ic,
1861 GCancellable *cancellable,
1863 /* throws IO,PARSE exception */
1865 gboolean success = TRUE;
1867 camel_imapx_command_close (ic);
1870 imapx_command_start (is, ic, cancellable, error);
1873 while (success && ic->status == NULL)
1874 success = imapx_step (is, cancellable, error);
1876 if (is->literal == ic)
1880 camel_imapx_command_queue_remove (is->active, ic);
1887 imapx_command_complete (CamelIMAPXServer *is,
1888 CamelIMAPXCommand *ic,
1891 camel_imapx_command_done (ic);
1892 camel_imapx_command_unref (ic);
1898 imapx_command_cancelled (GCancellable *cancellable,
1899 CamelIMAPXCommand *ic)
1901 /* Unblock imapx_command_run_sync() immediately.
1903 * If camel_imapx_command_done() is called sometime later,
1904 * the GCond will broadcast but no one will be listening. */
1906 camel_imapx_command_done (ic);
1909 /* The caller should free the command as well */
1911 imapx_command_run_sync (CamelIMAPXServer *is,
1912 CamelIMAPXCommand *ic,
1913 GCancellable *cancellable,
1916 guint cancel_id = 0;
1918 /* FIXME The only caller of this function currently does not set
1919 * a "complete" callback function, so we can get away with
1920 * referencing the command here and dropping the reference
1921 * in imapx_command_complete(). The queueing/dequeueing
1922 * of these things is too complex for my little mind, so
1923 * we may have to revisit the reference counting if this
1924 * function gets another caller. */
1926 g_warn_if_fail (ic->complete == NULL);
1927 ic->complete = imapx_command_complete;
1929 if (G_IS_CANCELLABLE (cancellable))
1930 cancel_id = g_cancellable_connect (
1932 G_CALLBACK (imapx_command_cancelled),
1933 camel_imapx_command_ref (ic),
1934 (GDestroyNotify) camel_imapx_command_unref);
1936 /* Unref'ed in imapx_command_complete(). */
1937 camel_imapx_command_ref (ic);
1939 imapx_command_queue (is, ic);
1941 camel_imapx_command_wait (ic);
1944 g_cancellable_disconnect (cancellable, cancel_id);
1946 if (g_cancellable_set_error_if_cancelled (cancellable, error))
1949 if (camel_imapx_command_set_error_if_failed (ic, error))
1956 imapx_register_job (CamelIMAPXServer *is,
1960 if (is->state >= IMAPX_INITIALISED) {
1962 g_queue_push_head (&is->jobs, camel_imapx_job_ref (job));
1966 e (is->tagprefix, "NO connection yet, maybe user cancelled jobs earlier ?");
1968 error, CAMEL_SERVICE_ERROR,
1969 CAMEL_SERVICE_ERROR_NOT_CONNECTED,
1970 _("Not authenticated"));
1978 imapx_unregister_job (CamelIMAPXServer *is,
1982 camel_imapx_job_done (job);
1985 if (g_queue_remove (&is->jobs, job))
1986 camel_imapx_job_unref (job);
1991 imapx_submit_job (CamelIMAPXServer *is,
1995 if (!imapx_register_job (is, job, error))
1998 return camel_imapx_job_run (job, is, error);
2001 /* ********************************************************************** */
2004 /*TODO handle negative cases sanely */
2006 imapx_command_idle_stop (CamelIMAPXServer *is,
2009 if (!is->stream || camel_stream_write_string ((CamelStream *)is->stream, "DONE\r\n", NULL, NULL) == -1) {
2011 error, CAMEL_IMAPX_ERROR, 1,
2012 "Unable to issue DONE");
2013 c(is->tagprefix, "Failed to issue DONE to terminate IDLE\n");
2014 is->state = IMAPX_SHUTDOWN;
2015 is->parser_quit = TRUE;
2016 if (is->cancellable)
2017 g_cancellable_cancel (is->cancellable);
2025 imapx_command_idle_done (CamelIMAPXServer *is,
2026 CamelIMAPXCommand *ic,
2029 CamelIMAPXIdle *idle = is->idle;
2031 gboolean success = TRUE;
2033 job = camel_imapx_command_get_job (ic);
2034 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
2036 if (camel_imapx_command_set_error_if_failed (ic, error)) {
2039 _("Error performing IDLE"));
2044 idle->state = IMAPX_IDLE_OFF;
2047 imapx_unregister_job (is, job);
2048 camel_imapx_command_unref (ic);
2054 imapx_job_idle_start (CamelIMAPXJob *job,
2055 CamelIMAPXServer *is)
2057 CamelIMAPXCommand *ic;
2058 CamelIMAPXCommandPart *cp;
2060 ic = camel_imapx_command_new (
2061 is, "IDLE", job->folder, "IDLE");
2062 camel_imapx_command_set_job (ic, job);
2064 ic->complete = imapx_command_idle_done;
2066 camel_imapx_command_close (ic);
2067 cp = g_queue_peek_head (&ic->parts);
2068 cp->type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
2071 IDLE_LOCK (is->idle);
2072 /* Don't issue it if the idle was cancelled already */
2073 if (is->idle->state == IMAPX_IDLE_PENDING) {
2074 is->idle->state = IMAPX_IDLE_ISSUED;
2075 imapx_command_start (is, ic, job->cancellable, &job->error);
2077 imapx_unregister_job (is, job);
2078 camel_imapx_command_unref (ic);
2080 IDLE_UNLOCK (is->idle);
2085 camel_imapx_server_idle (CamelIMAPXServer *is,
2086 CamelFolder *folder,
2087 GCancellable *cancellable,
2093 job = camel_imapx_job_new (cancellable);
2094 job->type = IMAPX_JOB_IDLE;
2095 job->start = imapx_job_idle_start;
2096 job->folder = folder;
2098 success = imapx_submit_job (is, job, error);
2100 camel_imapx_job_unref (job);
2106 imapx_job_fetch_new_messages_matches (CamelIMAPXJob *job,
2107 CamelFolder *folder,
2110 return (folder == job->folder);
2114 imapx_server_fetch_new_messages (CamelIMAPXServer *is,
2115 CamelFolder *folder,
2117 gboolean update_unseen,
2118 GCancellable *cancellable,
2122 RefreshInfoData *data;
2125 data = g_slice_new0 (RefreshInfoData);
2126 data->changes = camel_folder_change_info_new ();
2127 data->update_unseen = update_unseen;
2128 data->fetch_msg_limit = -1;
2130 job = camel_imapx_job_new (cancellable);
2131 job->type = IMAPX_JOB_FETCH_NEW_MESSAGES;
2132 job->start = imapx_job_fetch_new_messages_start;
2133 job->matches = imapx_job_fetch_new_messages_matches;
2134 job->folder = folder;
2135 job->noreply = async;
2137 camel_imapx_job_set_data (
2138 job, data, (GDestroyNotify) refresh_info_data_free);
2140 success = imapx_submit_job (is, job, error);
2142 camel_imapx_job_unref (job);
2148 imapx_idle_thread (gpointer data)
2150 CamelIMAPXServer *is = (CamelIMAPXServer *) data;
2151 GError *local_error = NULL;
2154 CamelIMAPXFolder *ifolder;
2156 g_mutex_lock (is->idle->start_watch_mutex);
2157 is->idle->start_watch_is_set = FALSE;
2158 g_mutex_unlock (is->idle->start_watch_mutex);
2160 IDLE_LOCK (is->idle);
2161 while ((ifolder = (CamelIMAPXFolder *) is->select_folder) &&
2162 is->idle->state == IMAPX_IDLE_PENDING &&
2163 !is->idle->idle_exit) {
2164 time_t dwelled = time (NULL) - is->idle->started;
2166 if (dwelled < IMAPX_IDLE_DWELL_TIME) {
2167 IDLE_UNLOCK (is->idle);
2168 g_usleep ((IMAPX_IDLE_DWELL_TIME - dwelled) * G_USEC_PER_SEC);
2169 IDLE_LOCK (is->idle);
2172 IDLE_UNLOCK (is->idle);
2174 camel_imapx_server_idle (is, (gpointer) ifolder, is->cancellable, &local_error);
2176 if (local_error == NULL && ifolder->exists_on_server >
2177 camel_folder_summary_count (((CamelFolder *) ifolder)->summary) && imapx_is_command_queue_empty (is))
2178 imapx_server_fetch_new_messages (is, is->select_folder, TRUE, TRUE, is->cancellable, &local_error);
2180 if (local_error != NULL) {
2181 e (is->tagprefix, "Caught exception in idle thread: %s \n", local_error->message);
2182 /* No way to asyncronously notify UI ? */
2183 g_clear_error (&local_error);
2185 IDLE_LOCK (is->idle);
2187 IDLE_UNLOCK (is->idle);
2189 g_mutex_lock (is->idle->start_watch_mutex);
2190 while (!is->idle->start_watch_is_set)
2192 is->idle->start_watch_cond,
2193 is->idle->start_watch_mutex);
2194 g_mutex_unlock (is->idle->start_watch_mutex);
2196 if (is->idle->idle_exit)
2200 g_clear_error (&local_error);
2201 is->idle->idle_thread = NULL;
2206 imapx_stop_idle (CamelIMAPXServer *is,
2209 CamelIMAPXIdle *idle = is->idle;
2210 gint stopped = FALSE;
2216 switch (idle->state) {
2217 case IMAPX_IDLE_ISSUED:
2218 idle->state = IMAPX_IDLE_CANCEL;
2219 case IMAPX_IDLE_CANCEL:
2223 case IMAPX_IDLE_STARTED:
2224 /* We set 'stopped' even if sending DONE fails, to ensure that
2225 * our caller doesn't try to submit its own command. */
2227 if (!imapx_command_idle_stop (is, error))
2230 idle->state = IMAPX_IDLE_OFF;
2231 c(is->tagprefix, "Stopping idle after %ld seconds\n",
2232 (long)(now - idle->started));
2233 case IMAPX_IDLE_PENDING:
2234 idle->state = IMAPX_IDLE_OFF;
2235 case IMAPX_IDLE_OFF:
2244 imapx_init_idle (CamelIMAPXServer *is)
2246 is->idle = g_new0 (CamelIMAPXIdle, 1);
2247 is->idle->idle_lock = g_mutex_new ();
2251 imapx_exit_idle (CamelIMAPXServer *is)
2253 CamelIMAPXIdle *idle = is->idle;
2254 GThread *thread = NULL;
2261 if (idle->idle_thread) {
2262 idle->idle_exit = TRUE;
2264 g_mutex_lock (idle->start_watch_mutex);
2265 idle->start_watch_is_set = TRUE;
2266 g_cond_broadcast (idle->start_watch_cond);
2267 g_mutex_unlock (idle->start_watch_mutex);
2269 thread = idle->idle_thread;
2270 idle->idle_thread = 0;
2273 idle->idle_thread = NULL;
2277 g_thread_join (thread);
2279 g_mutex_free (idle->idle_lock);
2281 if (idle->start_watch_cond != NULL)
2282 g_cond_free (idle->start_watch_cond);
2284 if (idle->start_watch_mutex != NULL)
2285 g_mutex_free (idle->start_watch_mutex);
2292 imapx_start_idle (CamelIMAPXServer *is)
2294 CamelIMAPXIdle *idle = is->idle;
2296 if (camel_application_is_exiting)
2301 g_assert (idle->state == IMAPX_IDLE_OFF);
2302 time (&idle->started);
2303 idle->state = IMAPX_IDLE_PENDING;
2305 if (!idle->idle_thread) {
2306 idle->start_watch_cond = g_cond_new ();
2307 idle->start_watch_mutex = g_mutex_new ();
2308 idle->start_watch_is_set = FALSE;
2310 idle->idle_thread = g_thread_create (
2311 (GThreadFunc) imapx_idle_thread, is, TRUE, NULL);
2313 g_mutex_lock (idle->start_watch_mutex);
2314 idle->start_watch_is_set = TRUE;
2315 g_cond_broadcast (idle->start_watch_cond);
2316 g_mutex_unlock (idle->start_watch_mutex);
2323 imapx_in_idle (CamelIMAPXServer *is)
2325 gboolean ret = FALSE;
2326 CamelIMAPXIdle *idle = is->idle;
2329 ret = (idle->state > IMAPX_IDLE_OFF);
2336 imapx_idle_supported (CamelIMAPXServer *is)
2338 return (is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_IDLE) != 0 && is->use_idle);
2342 /* ********************************************************************** */
2344 imapx_command_select_done (CamelIMAPXServer *is,
2345 CamelIMAPXCommand *ic,
2348 const gchar *selected_folder = NULL;
2349 gboolean success = TRUE;
2350 GError *local_error = NULL;
2352 if (camel_imapx_command_set_error_if_failed (ic, &local_error)) {
2353 GQueue failed = G_QUEUE_INIT;
2354 GQueue trash = G_QUEUE_INIT;
2357 c(is->tagprefix, "Select failed\n");
2361 if (is->select_pending) {
2362 GList *head = camel_imapx_command_queue_peek_head_link (is->queue);
2364 for (link = head; link != NULL; link = g_list_next (link)) {
2365 CamelIMAPXCommand *cw = link->data;
2367 if (cw->select && cw->select == is->select_pending) {
2368 c(is->tagprefix, "Cancelling command '%s'(%p) for folder '%s'\n",
2369 cw->name, cw, camel_folder_get_full_name (cw->select));
2370 g_queue_push_tail (&trash, link);
2375 while ((link = g_queue_pop_head (&trash)) != NULL) {
2376 CamelIMAPXCommand *cw = link->data;
2377 camel_imapx_command_queue_delete_link (is->queue, link);
2378 g_queue_push_tail (&failed, cw);
2383 while (!g_queue_is_empty (&failed)) {
2384 CamelIMAPXCommand *cw;
2387 cw = g_queue_pop_head (&failed);
2388 job = camel_imapx_command_get_job (cw);
2390 if (!CAMEL_IS_IMAPX_JOB (job)) {
2391 g_warn_if_reached ();
2396 cw->status = imapx_copy_status (ic->status);
2397 if (job->error == NULL) {
2398 if (ic->status == NULL)
2399 /* FIXME: why is ic->status == NULL here? It shouldn't happen. */
2400 g_debug ("imapx_command_select_done: ic->status is NULL.");
2403 CAMEL_IMAPX_ERROR, 1,
2404 "SELECT %s failed: %s",
2405 camel_folder_get_full_name (cw->select),
2406 ic->status && ic->status->text? ic->status->text:"<unknown reason>");
2408 cw->complete (is, cw, NULL);
2411 if (is->select_pending)
2412 g_object_unref (is->select_pending);
2414 /* A [CLOSED] status may have caused us to assume that it had happened */
2415 if (is->select_folder)
2416 is->select_folder = NULL;
2418 is->state = IMAPX_INITIALISED;
2420 g_propagate_error (error, local_error);
2424 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) is->select_pending;
2425 CamelFolder *cfolder = is->select_pending;
2427 c(is->tagprefix, "Select ok!\n");
2429 if (!is->select_folder) {
2430 /* This could have been done earlier by a [CLOSED] status */
2431 is->select_folder = is->select_pending;
2433 is->state = IMAPX_SELECTED;
2434 ifolder->exists_on_server = is->exists;
2435 ifolder->modseq_on_server = is->highestmodseq;
2436 if (ifolder->uidnext_on_server < is->uidnext) {
2437 imapx_server_fetch_new_messages (is, is->select_pending, TRUE, TRUE, NULL, NULL);
2438 /* We don't do this right now because we want the new messages to
2439 * update the unseen count. */
2440 //ifolder->uidnext_on_server = is->uidnext;
2442 ifolder->uidvalidity_on_server = is->uidvalidity;
2443 selected_folder = camel_folder_get_full_name (is->select_folder);
2445 if (is->uidvalidity && is->uidvalidity != ((CamelIMAPXSummary *) cfolder->summary)->validity)
2446 invalidate_local_cache (ifolder, is->uidvalidity);
2449 /* This should trigger a new messages scan */
2450 if (is->exists != is->select_folder->summary->root_view->total_count)
2451 g_warning("exists is %d our summary is %d and summary exists is %d\n", is->exists,
2452 is->select_folder->summary->root_view->total_count,
2453 ((CamelIMAPXSummary *) is->select_folder->summary)->exists);
2457 is->select_pending = NULL;
2458 camel_imapx_command_unref (ic);
2460 g_signal_emit (is, signals[SELECT_CHANGED], 0, selected_folder);
2465 /* Should have a queue lock. TODO Change the way select is written */
2467 imapx_select (CamelIMAPXServer *is,
2468 CamelFolder *folder,
2470 GCancellable *cancellable,
2473 CamelIMAPXCommand *ic;
2475 /* Select is complicated by the fact we may have commands
2476 * active on the server for a different selection.
2478 * So this waits for any commands to complete, selects the
2479 * new folder, and halts the queuing of any new commands.
2480 * It is assumed whomever called is us about to issue
2481 * a high-priority command anyway */
2483 /* TODO check locking here, pending_select will do
2484 * most of the work for normal commands, but not
2485 * for another select */
2487 if (is->select_pending)
2490 if (is->select_folder == folder && !forced)
2493 if (!camel_imapx_command_queue_is_empty (is->active))
2496 is->select_pending = folder;
2497 g_object_ref (folder);
2498 if (is->select_folder) {
2499 g_object_unref (is->select_folder);
2500 is->select_folder = NULL;
2502 /* If no folder was selected, we won't get a [CLOSED] status
2503 * so just point select_folder at the new folder immediately */
2504 is->select_folder = is->select_pending;
2507 is->uidvalidity = 0;
2509 is->highestmodseq = 0;
2510 is->permanentflags = 0;
2516 /* Hrm, what about reconnecting? */
2517 is->state = IMAPX_INITIALISED;
2519 ic = camel_imapx_command_new (
2520 is, "SELECT", NULL, "SELECT %f", folder);
2522 if (is->use_qresync) {
2523 CamelIMAPXSummary *isum = (CamelIMAPXSummary *) folder->summary;
2524 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
2525 gint total = camel_folder_summary_count (folder->summary);
2526 gchar *firstuid, *lastuid;
2528 if (total && isum->modseq && ifolder->uidvalidity_on_server) {
2530 firstuid = imapx_get_uid_from_index (folder->summary, 0);
2531 lastuid = imapx_get_uid_from_index (folder->summary, total - 1);
2533 c(is->tagprefix, "SELECT QRESYNC %" G_GUINT64_FORMAT
2534 " %" G_GUINT64_FORMAT "\n",
2535 ifolder->uidvalidity_on_server, isum->modseq);
2537 camel_imapx_command_add (
2539 G_GUINT64_FORMAT " %"
2540 G_GUINT64_FORMAT " %s:%s",
2541 ifolder->uidvalidity_on_server,
2550 GString *seqs, *uids;
2552 seqs = g_string_new(" ");
2553 uids = g_string_new(")");
2555 /* Include some seq/uid pairs to avoid a huge VANISHED list
2556 * (see RFC5162 §3.1). Work backwards exponentially from the
2557 * end of the mailbox, starting with the message 9 from the
2558 * end, then 27 from the end, then 81 from the end... */
2567 if (i != 9) { /* If not the first time */
2568 g_string_prepend(seqs, ",");
2569 g_string_prepend(uids, ",");
2572 /* IMAP sequence numbers are one higher than the corresponding
2573 * indices in our folder summary -- they start from one, while
2574 * the summary starts from zero. */
2575 sprintf(buf, "%d", total - i + 1);
2576 g_string_prepend (seqs, buf);
2577 uid = imapx_get_uid_from_index (folder->summary, total - i);
2578 g_string_prepend (uids, uid);
2580 } while (i < total);
2582 g_string_prepend(seqs, " (");
2584 c(is->tagprefix, "adding QRESYNC seq/uidset %s%s\n", seqs->str, uids->str);
2585 camel_imapx_command_add (ic, seqs->str);
2586 camel_imapx_command_add (ic, uids->str);
2588 g_string_free (seqs, TRUE);
2589 g_string_free (uids, TRUE);
2592 camel_imapx_command_add (ic, "))");
2596 ic->complete = imapx_command_select_done;
2597 imapx_command_start (is, ic, cancellable, error);
2604 /* Using custom commands to connect to IMAP servers is not supported on Win32 */
2607 connect_to_server_process (CamelIMAPXServer *is,
2611 CamelNetworkSettings *network_settings;
2612 CamelProvider *provider;
2613 CamelSettings *settings;
2614 CamelStream *cmd_stream;
2615 CamelService *service;
2621 gchar *child_env[7];
2622 const gchar *password;
2627 memset (&url, 0, sizeof (CamelURL));
2629 service = CAMEL_SERVICE (is->store);
2630 password = camel_service_get_password (service);
2631 provider = camel_service_get_provider (service);
2632 settings = camel_service_get_settings (service);
2634 network_settings = CAMEL_NETWORK_SETTINGS (settings);
2635 host = camel_network_settings_dup_host (network_settings);
2636 port = camel_network_settings_get_port (network_settings);
2637 user = camel_network_settings_dup_user (network_settings);
2639 /* Put full details in the environment, in case the connection
2640 * program needs them */
2641 camel_url_set_protocol (&url, provider->protocol);
2642 camel_url_set_host (&url, host);
2643 camel_url_set_port (&url, port);
2644 camel_url_set_user (&url, user);
2645 buf = camel_url_to_string (&url, 0);
2646 child_env[i++] = g_strdup_printf("URL=%s", buf);
2649 child_env[i++] = g_strdup_printf("URLHOST=%s", host);
2651 child_env[i++] = g_strdup_printf("URLPORT=%u", port);
2653 child_env[i++] = g_strdup_printf("URLUSER=%s", user);
2655 child_env[i++] = g_strdup_printf("URLPASSWD=%s", password);
2656 child_env[i] = NULL;
2658 /* Now do %h, %u, etc. substitution in cmd */
2659 buf = cmd_copy = g_strdup (cmd);
2661 full_cmd = g_strdup("");
2669 pc = strchr (buf, '%');
2672 tmp = g_strdup_printf("%s%s", full_cmd, buf);
2691 /* If there wasn't a valid %-code, with an actual
2692 * variable to insert, pretend we didn't see the % */
2693 pc = strchr (pc + 1, '%');
2696 tmp = g_strdup_printf("%s%.*s%s", full_cmd, len, buf, var);
2707 cmd_stream = camel_stream_process_new ();
2709 ret = camel_stream_process_connect (
2710 CAMEL_STREAM_PROCESS (cmd_stream),
2711 full_cmd, (const gchar **) child_env, error);
2714 g_free (child_env[--i]);
2717 g_object_unref (cmd_stream);
2724 is->stream = (CamelIMAPXStream *) camel_imapx_stream_new (cmd_stream);
2725 g_object_unref (cmd_stream);
2726 is->is_process_stream = TRUE;
2730 #endif /* G_OS_WIN32 */
2733 imapx_connect_to_server (CamelIMAPXServer *is,
2734 GCancellable *cancellable,
2737 CamelNetworkSettings *network_settings;
2738 CamelNetworkSecurityMethod method;
2739 CamelStream * tcp_stream = NULL;
2740 CamelSockOptData sockopt;
2741 CamelSettings *settings;
2742 CamelService *service;
2746 CamelIMAPXCommand *ic;
2747 gboolean success = TRUE;
2749 GError *local_error = NULL;
2752 gboolean use_shell_command;
2753 gchar *shell_command = NULL;
2756 service = CAMEL_SERVICE (is->store);
2757 settings = camel_service_get_settings (service);
2759 network_settings = CAMEL_NETWORK_SETTINGS (settings);
2760 host = camel_network_settings_dup_host (network_settings);
2761 method = camel_network_settings_get_security_method (network_settings);
2764 use_shell_command = camel_imapx_settings_get_use_shell_command (
2765 CAMEL_IMAPX_SETTINGS (settings));
2767 if (use_shell_command)
2768 shell_command = camel_imapx_settings_dup_shell_command (
2769 CAMEL_IMAPX_SETTINGS (settings));
2771 if (shell_command != NULL) {
2774 success = connect_to_server_process (
2775 is, shell_command, &local_error);
2777 g_free (shell_command);
2786 tcp_stream = camel_network_service_connect_sync (
2787 CAMEL_NETWORK_SERVICE (is->store), cancellable, error);
2789 if (tcp_stream == NULL) {
2794 is->stream = (CamelIMAPXStream *) camel_imapx_stream_new (tcp_stream);
2795 g_object_unref (tcp_stream);
2797 /* Disable Nagle - we send a lot of small requests which nagle slows down */
2798 sockopt.option = CAMEL_SOCKOPT_NODELAY;
2799 sockopt.value.no_delay = TRUE;
2800 camel_tcp_stream_setsockopt ((CamelTcpStream *) tcp_stream, &sockopt);
2802 /* Set keepalive - needed for some hosts/router configurations, we're idle a lot */
2803 sockopt.option = CAMEL_SOCKOPT_KEEPALIVE;
2804 sockopt.value.keep_alive = TRUE;
2805 camel_tcp_stream_setsockopt ((CamelTcpStream *) tcp_stream, &sockopt);
2808 is->stream->tagprefix = is->tagprefix;
2810 // poll ? wait for other stuff? loop?
2811 if (camel_application_is_exiting || is->parser_quit) {
2814 G_IO_ERROR_CANCELLED,
2815 "Connection to server cancelled\n");
2820 tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
2827 imapx_untagged (is, cancellable, error);
2830 camel_imapx_stream_ungettoken (is->stream, tok, token, len);
2831 if (camel_imapx_stream_text (is->stream, &token, cancellable, error)) {
2835 e(is->tagprefix, "Got unexpected line before greeting: '%s'\n", token);
2840 ic = camel_imapx_command_new (
2841 is, "CAPABILITY", NULL, "CAPABILITY");
2842 if (!imapx_command_run (is, ic, cancellable, error)) {
2843 camel_imapx_command_unref (ic);
2848 /* Server reported error. */
2849 if (ic->status->result != IMAPX_OK) {
2852 CAMEL_ERROR_GENERIC,
2853 "%s", ic->status->text);
2854 camel_imapx_command_unref (ic);
2859 camel_imapx_command_unref (ic);
2862 if (method == CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT) {
2864 if (!(is->cinfo->capa & IMAPX_CAPABILITY_STARTTLS)) {
2866 &local_error, CAMEL_ERROR,
2867 CAMEL_ERROR_GENERIC,
2868 _("Failed to connect to IMAP server %s in secure mode: %s"),
2869 host, _("STARTTLS not supported"));
2873 ic = camel_imapx_command_new (
2874 is, "STARTTLS", NULL, "STARTTLS");
2875 if (!imapx_command_run (is, ic, cancellable, error)) {
2876 camel_imapx_command_unref (ic);
2881 /* Server reported error. */
2882 if (ic->status->result != IMAPX_OK) {
2885 CAMEL_ERROR_GENERIC,
2886 "%s", ic->status->text);
2887 camel_imapx_command_unref (ic);
2892 /* See if we got new capabilities in the STARTTLS response */
2893 imapx_free_capability (is->cinfo);
2895 if (ic->status->condition == IMAPX_CAPABILITY) {
2896 is->cinfo = ic->status->u.cinfo;
2897 ic->status->u.cinfo = NULL;
2898 c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
2901 camel_imapx_command_unref (ic);
2903 if (camel_tcp_stream_ssl_enable_ssl (
2904 CAMEL_TCP_STREAM_SSL (tcp_stream),
2905 cancellable, &local_error) == -1) {
2908 _("Failed to connect to IMAP server %s in secure mode: "),
2912 /* Get new capabilities if they weren't already given */
2914 ic = camel_imapx_command_new (
2915 is, "CAPABILITY", NULL, "CAPABILITY");
2916 if (!imapx_command_run (is, ic, cancellable, error)) {
2917 camel_imapx_command_unref (ic);
2922 camel_imapx_command_unref (ic);
2928 if (is->stream != NULL) {
2929 g_object_unref (is->stream);
2933 if (is->cinfo != NULL) {
2934 imapx_free_capability (is->cinfo);
2944 CamelAuthenticationResult
2945 camel_imapx_server_authenticate (CamelIMAPXServer *is,
2946 const gchar *mechanism,
2947 GCancellable *cancellable,
2950 CamelNetworkSettings *network_settings;
2951 CamelSettings *settings;
2952 CamelAuthenticationResult result;
2953 CamelIMAPXCommand *ic;
2954 CamelService *service;
2955 CamelSasl *sasl = NULL;
2959 g_return_val_if_fail (
2960 CAMEL_IS_IMAPX_SERVER (is),
2961 CAMEL_AUTHENTICATION_REJECTED);
2963 service = CAMEL_SERVICE (is->store);
2964 settings = camel_service_get_settings (service);
2966 network_settings = CAMEL_NETWORK_SETTINGS (settings);
2967 host = camel_network_settings_dup_host (network_settings);
2968 user = camel_network_settings_dup_user (network_settings);
2970 if (mechanism != NULL) {
2971 if (!g_hash_table_lookup (is->cinfo->auth_types, mechanism)) {
2973 error, CAMEL_SERVICE_ERROR,
2974 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
2975 _("IMAP server %s does not support %s "
2976 "authentication"), host, mechanism);
2977 result = CAMEL_AUTHENTICATION_ERROR;
2981 sasl = camel_sasl_new ("imap", mechanism, service);
2984 error, CAMEL_SERVICE_ERROR,
2985 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
2986 _("No support for %s authentication"),
2988 result = CAMEL_AUTHENTICATION_ERROR;
2994 ic = camel_imapx_command_new (
2995 is, "AUTHENTICATE", NULL, "AUTHENTICATE %A", sasl);
2997 const gchar *password;
2999 password = camel_service_get_password (service);
3002 g_set_error_literal (
3003 error, CAMEL_SERVICE_ERROR,
3004 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
3005 _("Cannot authenticate without a username"));
3006 result = CAMEL_AUTHENTICATION_ERROR;
3010 if (password == NULL) {
3011 g_set_error_literal (
3012 error, CAMEL_SERVICE_ERROR,
3013 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
3014 _("Authentication password not available"));
3015 result = CAMEL_AUTHENTICATION_ERROR;
3019 ic = camel_imapx_command_new (
3020 is, "LOGIN", NULL, "LOGIN %s %s", user, password);
3023 if (!imapx_command_run (is, ic, cancellable, error))
3024 result = CAMEL_AUTHENTICATION_ERROR;
3025 else if (ic->status->result == IMAPX_OK)
3026 result = CAMEL_AUTHENTICATION_ACCEPTED;
3028 result = CAMEL_AUTHENTICATION_REJECTED;
3030 /* Forget old capabilities after login. */
3031 if (result == CAMEL_AUTHENTICATION_ACCEPTED) {
3033 imapx_free_capability (is->cinfo);
3037 if (ic->status->condition == IMAPX_CAPABILITY) {
3038 is->cinfo = ic->status->u.cinfo;
3039 ic->status->u.cinfo = NULL;
3040 c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
3044 camel_imapx_command_unref (ic);
3047 g_object_unref (sasl);
3057 imapx_reconnect (CamelIMAPXServer *is,
3058 GCancellable *cancellable,
3061 CamelIMAPXCommand *ic;
3062 CamelService *service;
3063 CamelSession *session;
3064 CamelSettings *settings;
3067 gboolean use_qresync;
3069 service = CAMEL_SERVICE (is->store);
3070 session = camel_service_get_session (service);
3071 settings = camel_service_get_settings (service);
3073 mechanism = camel_network_settings_dup_auth_mechanism (
3074 CAMEL_NETWORK_SETTINGS (settings));
3076 use_idle = camel_imapx_settings_get_use_idle (
3077 CAMEL_IMAPX_SETTINGS (settings));
3079 use_qresync = camel_imapx_settings_get_use_qresync (
3080 CAMEL_IMAPX_SETTINGS (settings));
3082 if (!imapx_connect_to_server (is, cancellable, error))
3085 if (is->state == IMAPX_AUTHENTICATED)
3088 if (!camel_session_authenticate_sync (
3089 session, service, mechanism, cancellable, error))
3092 /* After login we re-capa unless the server already told us */
3094 ic = camel_imapx_command_new (
3095 is, "CAPABILITY", NULL, "CAPABILITY");
3096 if (!imapx_command_run (is, ic, cancellable, error)) {
3097 camel_imapx_command_unref (ic);
3101 camel_imapx_command_unref (ic);
3104 is->state = IMAPX_AUTHENTICATED;
3107 is->use_idle = use_idle;
3109 if (imapx_idle_supported (is))
3110 imapx_init_idle (is);
3112 /* Fetch namespaces */
3113 if (is->cinfo->capa & IMAPX_CAPABILITY_NAMESPACE) {
3114 ic = camel_imapx_command_new (
3115 is, "NAMESPACE", NULL, "NAMESPACE");
3116 if (!imapx_command_run (is, ic, cancellable, error)) {
3117 camel_imapx_command_unref (ic);
3121 camel_imapx_command_unref (ic);
3124 if (use_qresync && is->cinfo->capa & IMAPX_CAPABILITY_QRESYNC) {
3125 ic = camel_imapx_command_new (
3126 is, "ENABLE", NULL, "ENABLE CONDSTORE QRESYNC");
3127 if (!imapx_command_run (is, ic, cancellable, error)) {
3128 camel_imapx_command_unref (ic);
3132 camel_imapx_command_unref (ic);
3134 is->use_qresync = TRUE;
3136 is->use_qresync = FALSE;
3138 if (((CamelIMAPXStore *) is->store)->summary->namespaces == NULL) {
3139 CamelIMAPXNamespaceList *nsl = NULL;
3140 CamelIMAPXStoreNamespace *ns = NULL;
3141 CamelIMAPXStore *imapx_store = (CamelIMAPXStore *) is->store;
3143 /* set a default namespace */
3144 nsl = g_malloc0 (sizeof (CamelIMAPXNamespaceList));
3145 ns = g_new0 (CamelIMAPXStoreNamespace, 1);
3147 ns->path = g_strdup ("");
3148 ns->full_name = g_strdup ("");
3151 imapx_store->summary->namespaces = nsl;
3152 /* FIXME needs to be identified from list response */
3153 imapx_store->dir_sep = ns->sep;
3156 is->state = IMAPX_INITIALISED;
3164 imapx_disconnect (is);
3167 imapx_free_capability (is->cinfo);
3176 /* ********************************************************************** */
3179 imapx_command_fetch_message_done (CamelIMAPXServer *is,
3180 CamelIMAPXCommand *ic,
3184 GetMessageData *data;
3185 gboolean success = TRUE;
3186 GError *local_error = NULL;
3188 job = camel_imapx_command_get_job (ic);
3189 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3191 data = camel_imapx_job_get_data (job);
3192 g_return_val_if_fail (data != NULL, FALSE);
3194 /* We either have more to fetch (partial mode?), we are complete,
3195 * or we failed. Failure is handled in the fetch code, so
3196 * we just return the job, or keep it alive with more requests */
3200 if (camel_imapx_command_set_error_if_failed (ic, &local_error)) {
3202 &local_error, "%s: ",
3203 _("Error fetching message"));
3204 data->body_len = -1;
3206 } else if (data->use_multi_fetch) {
3207 gsize really_fetched = g_seekable_tell (G_SEEKABLE (data->stream));
3208 /* Don't automatically stop when we reach the reported message
3209 * size -- some crappy servers (like Microsoft Exchange) have
3210 * a tendency to lie about it. Keep going (one request at a
3211 * time) until the data actually stop coming. */
3212 if (data->fetch_offset < data->size ||
3213 data->fetch_offset == really_fetched) {
3214 CamelIMAPXCommand *new_ic;
3216 camel_operation_progress (
3218 (data->fetch_offset *100) / data->size);
3220 new_ic = camel_imapx_command_new (
3221 is, "FETCH", job->folder,
3222 "UID FETCH %t (BODY.PEEK[]",
3224 camel_imapx_command_add (new_ic, "<%u.%u>", data->fetch_offset, MULTI_SIZE);
3225 camel_imapx_command_add (new_ic, ")");
3226 new_ic->complete = imapx_command_fetch_message_done;
3227 camel_imapx_command_set_job (new_ic, job);
3228 new_ic->pri = job->pri - 1;
3229 data->fetch_offset += MULTI_SIZE;
3231 imapx_command_queue (is, new_ic);
3233 camel_imapx_command_unref (ic);
3239 if (job->commands == 0) {
3240 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
3241 CamelStream *stream = data->stream;
3243 /* return the exception from last command */
3244 if (local_error != NULL) {
3246 g_object_unref (stream);
3247 data->stream = NULL;
3249 g_propagate_error (error, local_error);
3254 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
3257 gchar *tmp = camel_data_cache_get_filename (ifolder->cache, "tmp", data->uid, NULL);
3259 if (camel_stream_flush (stream, job->cancellable, &job->error) == 0 && camel_stream_close (stream, job->cancellable, &job->error) == 0) {
3260 gchar *cache_file = camel_data_cache_get_filename (ifolder->cache, "cur", data->uid, NULL);
3261 gchar *temp = g_strrstr (cache_file, "/"), *dir;
3263 dir = g_strndup (cache_file, temp - cache_file);
3264 g_mkdir_with_parents (dir, 0700);
3267 if (g_rename (tmp, cache_file) != 0)
3269 &job->error, CAMEL_IMAPX_ERROR, 1,
3270 "failed to copy the tmp file");
3271 g_free (cache_file);
3275 _("Closing tmp stream failed: "));
3278 g_object_unref (data->stream);
3279 data->stream = camel_data_cache_get (ifolder->cache, "cur", data->uid, NULL);
3283 camel_data_cache_remove (ifolder->cache, "tmp", data->uid, NULL);
3284 imapx_unregister_job (is, job);
3287 camel_imapx_command_unref (ic);
3289 g_clear_error (&local_error);
3295 imapx_job_get_message_start (CamelIMAPXJob *job,
3296 CamelIMAPXServer *is)
3298 CamelIMAPXCommand *ic;
3299 GetMessageData *data;
3302 data = camel_imapx_job_get_data (job);
3303 g_return_if_fail (data != NULL);
3305 if (data->use_multi_fetch) {
3306 for (i = 0; i < 3 && data->fetch_offset < data->size; i++) {
3307 ic = camel_imapx_command_new (
3308 is, "FETCH", job->folder,
3309 "UID FETCH %t (BODY.PEEK[]",
3311 camel_imapx_command_add (ic, "<%u.%u>", data->fetch_offset, MULTI_SIZE);
3312 camel_imapx_command_add (ic, ")");
3313 ic->complete = imapx_command_fetch_message_done;
3314 camel_imapx_command_set_job (ic, job);
3316 data->fetch_offset += MULTI_SIZE;
3318 imapx_command_queue (is, ic);
3321 ic = camel_imapx_command_new (
3322 is, "FETCH", job->folder,
3323 "UID FETCH %t (BODY.PEEK[])",
3325 ic->complete = imapx_command_fetch_message_done;
3326 camel_imapx_command_set_job (ic, job);
3329 imapx_command_queue (is, ic);
3334 imapx_job_get_message_matches (CamelIMAPXJob *job,
3335 CamelFolder *folder,
3338 GetMessageData *data;
3340 data = camel_imapx_job_get_data (job);
3341 g_return_val_if_fail (data != NULL, FALSE);
3343 if (folder != job->folder)
3346 if (g_strcmp0 (uid, data->uid) != 0)
3352 /* ********************************************************************** */
3355 imapx_command_copy_messages_step_done (CamelIMAPXServer *is,
3356 CamelIMAPXCommand *ic,
3360 CopyMessagesData *data;
3363 gboolean success = TRUE;
3365 job = camel_imapx_command_get_job (ic);
3366 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3368 data = camel_imapx_job_get_data (job);
3369 g_return_val_if_fail (data != NULL, FALSE);
3374 if (camel_imapx_command_set_error_if_failed (ic, error)) {
3376 &job->error, "%s: ",
3377 _("Error copying messages"));
3382 if (data->delete_originals) {
3385 for (j = data->last_index; j < i; j++)
3386 camel_folder_delete_message (job->folder, uids->pdata[j]);
3389 /* TODO copy the summary and cached messages to the new folder. We might need a sorted insert to avoid refreshing the dest folder */
3390 if (ic->status->condition == IMAPX_COPYUID) {
3393 for (i = 0; i < ic->status->u.copyuid.copied_uids->len; i++) {
3394 guint32 uid = GPOINTER_TO_UINT (g_ptr_array_index (ic->status->u.copyuid.copied_uids, i));
3395 gchar *str = g_strdup_printf ("%d",uid);
3396 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) data->dest;
3398 g_hash_table_insert (ifolder->ignore_recent, str, GINT_TO_POINTER (1));
3403 if (i < uids->len) {
3404 camel_imapx_command_unref (ic);
3405 imapx_command_copy_messages_step_start (is, job, i);
3410 g_object_unref (job->folder);
3412 imapx_unregister_job (is, job);
3413 camel_imapx_command_unref (ic);
3419 imapx_command_copy_messages_step_start (CamelIMAPXServer *is,
3423 CamelIMAPXCommand *ic;
3424 CopyMessagesData *data;
3428 data = camel_imapx_job_get_data (job);
3429 g_return_if_fail (data != NULL);
3433 ic = camel_imapx_command_new (
3434 is, "COPY", job->folder, "UID COPY ");
3435 ic->complete = imapx_command_copy_messages_step_done;
3436 camel_imapx_command_set_job (ic, job);
3438 data->last_index = i;
3440 for (; i < uids->len; i++) {
3442 const gchar *uid = (gchar *) g_ptr_array_index (uids, i);
3444 res = imapx_uidset_add (&data->uidset, ic, uid);
3446 camel_imapx_command_add (ic, " %f", data->dest);
3448 imapx_command_queue (is, ic);
3454 if (imapx_uidset_done (&data->uidset, ic)) {
3455 camel_imapx_command_add (ic, " %f", data->dest);
3456 imapx_command_queue (is, ic);
3462 imapx_job_copy_messages_start (CamelIMAPXJob *job,
3463 CamelIMAPXServer *is)
3465 CopyMessagesData *data;
3467 data = camel_imapx_job_get_data (job);
3468 g_return_if_fail (data != NULL);
3470 if (!imapx_server_sync_changes (
3471 is, job->folder, job->pri, job->cancellable, &job->error))
3472 imapx_unregister_job (is, job);
3474 g_ptr_array_sort (data->uids, (GCompareFunc) imapx_uids_array_cmp);
3475 imapx_uidset_init (&data->uidset, 0, MAX_COMMAND_LEN);
3476 imapx_command_copy_messages_step_start (is, job, 0);
3479 /* ********************************************************************** */
3482 imapx_command_append_message_done (CamelIMAPXServer *is,
3483 CamelIMAPXCommand *ic,
3487 CamelIMAPXFolder *ifolder;
3488 CamelMessageInfo *mi;
3489 AppendMessageData *data;
3490 gchar *cur, *old_uid;
3491 gboolean success = TRUE;
3493 job = camel_imapx_command_get_job (ic);
3494 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3496 data = camel_imapx_job_get_data (job);
3497 g_return_val_if_fail (data != NULL, FALSE);
3499 ifolder = (CamelIMAPXFolder *) job->folder;
3501 /* Append done. If we the server supports UIDPLUS we will get an APPENDUID response
3502 * with the new uid. This lets us move the message we have directly to the cache
3503 * and also create a correctly numbered MessageInfo, without losing any information.
3504 * Otherwise we have to wait for the server to less us know it was appended. */
3506 mi = camel_message_info_clone (data->info);
3507 old_uid = g_strdup (data->info->uid);
3509 if (camel_imapx_command_set_error_if_failed (ic, error)) {
3512 _("Error appending message"));
3515 } else if (ic->status->condition == IMAPX_APPENDUID) {
3516 c(is->tagprefix, "Got appenduid %d %d\n", (gint)ic->status->u.appenduid.uidvalidity, (gint)ic->status->u.appenduid.uid);
3517 if (ic->status->u.appenduid.uidvalidity == ifolder->uidvalidity_on_server) {
3518 CamelFolderChangeInfo *changes;
3520 data->appended_uid = g_strdup_printf ("%u", (guint) ic->status->u.appenduid.uid);
3521 mi->uid = camel_pstring_add (data->appended_uid, FALSE);
3523 cur = camel_data_cache_get_filename (ifolder->cache, "cur", mi->uid, NULL);
3524 g_rename (data->path, cur);
3526 /* should we update the message count ? */
3527 imapx_set_message_info_flags_for_new_message (mi,
3528 ((CamelMessageInfoBase *) data->info)->flags,
3529 ((CamelMessageInfoBase *) data->info)->user_flags,
3531 camel_folder_summary_add (job->folder->summary, mi);
3532 changes = camel_folder_change_info_new ();
3533 camel_folder_change_info_add_uid (changes, mi->uid);
3534 camel_folder_changed (job->folder, changes);
3535 camel_folder_change_info_free (changes);
3539 g_message ("but uidvalidity changed \n");
3543 camel_data_cache_remove (ifolder->cache, "new", old_uid, NULL);
3545 g_object_unref (job->folder);
3547 imapx_unregister_job (is, job);
3548 camel_imapx_command_unref (ic);
3554 imapx_job_append_message_start (CamelIMAPXJob *job,
3555 CamelIMAPXServer *is)
3557 CamelIMAPXCommand *ic;
3558 AppendMessageData *data;
3560 data = camel_imapx_job_get_data (job);
3561 g_return_if_fail (data != NULL);
3563 /* TODO: we could supply the original append date from the file timestamp */
3564 ic = camel_imapx_command_new (
3566 "APPEND %f %F %P", job->folder,
3567 ((CamelMessageInfoBase *) data->info)->flags,
3568 ((CamelMessageInfoBase *) data->info)->user_flags,
3570 ic->complete = imapx_command_append_message_done;
3571 camel_imapx_command_set_job (ic, job);
3574 imapx_command_queue (is, ic);
3577 /* ********************************************************************** */
3580 imapx_refresh_info_uid_cmp (gconstpointer ap,
3586 av = g_ascii_strtoull ((const gchar *) ap, NULL, 10);
3587 bv = g_ascii_strtoull ((const gchar *) bp, NULL, 10);
3590 return ascending ? -1 : 1;
3592 return ascending ? 1 : -1;
3598 imapx_uids_array_cmp (gconstpointer ap,
3601 const gchar **a = (const gchar **) ap;
3602 const gchar **b = (const gchar **) bp;
3604 return imapx_refresh_info_uid_cmp (*a, *b, TRUE);
3608 imapx_refresh_info_cmp (gconstpointer ap,
3611 const struct _refresh_info *a = ap;
3612 const struct _refresh_info *b = bp;
3614 return imapx_refresh_info_uid_cmp (a->uid, b->uid, TRUE);
3618 imapx_refresh_info_cmp_descending (gconstpointer ap,
3621 const struct _refresh_info *a = ap;
3622 const struct _refresh_info *b = bp;
3624 return imapx_refresh_info_uid_cmp (a->uid, b->uid, FALSE);
3628 /* skips over non-server uids (pending appends) */
3630 imapx_index_next (GPtrArray *uids,
3631 CamelFolderSummary *s,
3635 while (index < uids->len) {
3636 CamelMessageInfo *info;
3639 if (index >= uids->len)
3642 info = camel_folder_summary_get (s, g_ptr_array_index (uids, index));
3646 if (info && (strchr (camel_message_info_uid (info), '-') != NULL)) {
3647 camel_message_info_free (info);
3648 e('?', "Ignoring offline uid '%s'\n", camel_message_info_uid(info));
3650 camel_message_info_free (info);
3659 imapx_command_step_fetch_done (CamelIMAPXServer *is,
3660 CamelIMAPXCommand *ic,
3663 CamelIMAPXFolder *ifolder;
3664 CamelIMAPXSummary *isum;
3666 RefreshInfoData *data;
3668 gboolean success = TRUE;
3669 CamelSettings *settings;
3670 CamelService *service;
3672 gboolean mobile_mode;
3674 job = camel_imapx_command_get_job (ic);
3675 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3677 data = camel_imapx_job_get_data (job);
3678 g_return_val_if_fail (data != NULL, FALSE);
3680 ifolder = (CamelIMAPXFolder *) job->folder;
3681 isum = (CamelIMAPXSummary *) job->folder->summary;
3683 service = CAMEL_SERVICE (is->store);
3684 settings = camel_service_get_settings (service);
3686 batch_count = camel_imapx_settings_get_batch_fetch_count (
3687 CAMEL_IMAPX_SETTINGS (settings));
3688 mobile_mode = camel_imapx_settings_get_mobile_mode (
3689 CAMEL_IMAPX_SETTINGS (settings));
3693 //printf("%s: Mobile mode: %d Fetch Count %d\n", camel_folder_get_display_name (job->folder), mobile_mode, batch_count);
3694 if (camel_imapx_command_set_error_if_failed (ic, error)) {
3697 _("Error fetching message headers"));
3702 if (camel_folder_change_info_changed (data->changes)) {
3703 imapx_update_store_summary (job->folder);
3704 camel_folder_summary_save_to_db (job->folder->summary, NULL);
3705 camel_folder_changed (job->folder, data->changes);
3708 camel_folder_change_info_clear (data->changes);
3710 if (i < data->infos->len) {
3711 gint total = camel_folder_summary_count (job->folder->summary);
3712 gint fetch_limit = data->fetch_msg_limit;
3714 camel_imapx_command_unref (ic);
3716 ic = camel_imapx_command_new (
3717 is, "FETCH", job->folder, "UID FETCH ");
3718 ic->complete = imapx_command_step_fetch_done;
3719 camel_imapx_command_set_job (ic, job);
3720 ic->pri = job->pri - 1;
3722 //printf("Total: %d: %d, %d, %d\n", total, fetch_limit, i, data->last_index);
3723 data->last_index = i;
3725 /* If its mobile client and when total=0 (new account setup) fetch only one batch of mails,
3726 * on futher attempts download all new mails as per the limit. */
3727 //printf("Total: %d: %d\n", total, fetch_limit);
3728 for (; i < data->infos->len && (!mobile_mode || (total && i == 0) || ((fetch_limit != -1 && i < fetch_limit) || (fetch_limit == -1 && i < batch_count))); i++) {
3731 struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
3734 res = imapx_uidset_add (&data->uidset, ic, r->uid);
3736 camel_imapx_command_add (ic, " (RFC822.SIZE RFC822.HEADER)");
3738 imapx_command_queue (is, ic);
3744 //printf("Existing : %d Gonna fetch in %s for %d/%d\n", total, camel_folder_get_full_name(job->folder), i, data->infos->len);
3745 data->index = data->infos->len;
3746 if (imapx_uidset_done (&data->uidset, ic)) {
3747 camel_imapx_command_add (ic, " (RFC822.SIZE RFC822.HEADER)");
3749 imapx_command_queue (is, ic);
3754 if (camel_folder_summary_count (job->folder->summary)) {
3755 gchar *uid = imapx_get_uid_from_index (job->folder->summary,
3756 camel_folder_summary_count (job->folder->summary) - 1);
3757 guint64 uidl = strtoull (uid, NULL, 10);
3762 if (uidl > ifolder->uidnext_on_server) {
3763 c(is->tagprefix, "Updating uidnext_on_server for '%s' to %" G_GUINT64_FORMAT "\n",
3764 camel_folder_get_full_name (job->folder), uidl);
3765 ifolder->uidnext_on_server = uidl;
3768 isum->uidnext = ifolder->uidnext_on_server;
3771 for (i = 0; i < data->infos->len; i++) {
3772 struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
3774 camel_flag_list_free (&r->server_user_flags);
3777 g_array_free (data->infos, TRUE);
3779 imapx_unregister_job (is, job);
3780 camel_imapx_command_unref (ic);
3786 imapx_uid_cmp (gconstpointer ap,
3790 const gchar *a = ap, *b = bp;
3794 av = strtoul (a, &ae, 10);
3795 bv = strtoul (b, &be, 10);
3807 return strcmp (ae, be);
3811 imapx_job_scan_changes_done (CamelIMAPXServer *is,
3812 CamelIMAPXCommand *ic,
3816 CamelService *service;
3817 CamelSettings *settings;
3818 RefreshInfoData *data;
3821 gboolean success = TRUE;
3822 gboolean mobile_mode;
3824 job = camel_imapx_command_get_job (ic);
3825 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3827 data = camel_imapx_job_get_data (job);
3828 g_return_val_if_fail (data != NULL, FALSE);
3830 service = CAMEL_SERVICE (is->store);
3831 settings = camel_service_get_settings (service);
3833 uidset_size = camel_imapx_settings_get_batch_fetch_count (
3834 CAMEL_IMAPX_SETTINGS (settings));
3835 mobile_mode = camel_imapx_settings_get_mobile_mode (
3836 CAMEL_IMAPX_SETTINGS (settings));
3838 if (camel_imapx_command_set_error_if_failed (ic, error)) {
3841 _("Error retrieving message"));
3845 GCompareDataFunc uid_cmp = imapx_uid_cmp;
3846 CamelMessageInfo *s_minfo = NULL;
3847 CamelIMAPXMessageInfo *info;
3848 CamelFolderSummary *s = job->folder->summary;
3849 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
3850 GList *removed = NULL, *l;
3851 gboolean fetch_new = FALSE;
3856 /* Actually we wanted to do this after the SELECT but before the
3857 * FETCH command was issued. But this should suffice. */
3858 ((CamelIMAPXSummary *) s)->uidnext = ifolder->uidnext_on_server;
3859 ((CamelIMAPXSummary *) s)->modseq = ifolder->modseq_on_server;
3861 /* Here we do the typical sort/iterate/merge loop.
3862 * If the server flags dont match what we had, we modify our
3863 * flags to pick up what the server now has - but we merge
3866 /* FIXME: We also have to check the offline directory for
3867 * anything missing in our summary, and also queue up jobs
3868 * for all outstanding messages to be uploaded */
3870 /* obtain a copy to be thread safe */
3871 uids = camel_folder_summary_get_array (s);
3873 qsort (data->infos->data, data->infos->len, sizeof (struct _refresh_info), imapx_refresh_info_cmp);
3874 g_ptr_array_sort (uids, (GCompareFunc) imapx_uids_array_cmp);
3877 s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, 0));
3879 for (i = 0; i < data->infos->len; i++) {
3880 struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
3882 while (s_minfo && uid_cmp (camel_message_info_uid (s_minfo), r->uid, s) < 0) {
3883 const gchar *uid = camel_message_info_uid (s_minfo);
3885 camel_folder_change_info_remove_uid (data->changes, uid);
3886 removed = g_list_prepend (removed, (gpointer ) g_strdup (uid));
3887 camel_message_info_free (s_minfo);
3890 j = imapx_index_next (uids, s, j);
3892 s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
3896 if (s_minfo && uid_cmp (s_minfo->uid, r->uid, s) == 0) {
3897 info = (CamelIMAPXMessageInfo *) s_minfo;
3899 if (imapx_update_message_info_flags ((CamelMessageInfo *) info, r->server_flags, r->server_user_flags, is->permanentflags, job->folder, FALSE))
3900 camel_folder_change_info_change_uid (data->changes, camel_message_info_uid (s_minfo));
3906 camel_message_info_free (s_minfo);
3913 j = imapx_index_next (uids, s, j);
3915 s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
3919 camel_message_info_free (s_minfo);
3921 while (j < uids->len) {
3922 s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
3929 e(is->tagprefix, "Message %s vanished\n", s_minfo->uid);
3930 removed = g_list_prepend (removed, (gpointer) g_strdup (s_minfo->uid));
3931 camel_message_info_free (s_minfo);
3935 for (l = removed; l != NULL; l = g_list_next (l)) {
3936 gchar *uid = (gchar *) l->data;
3938 camel_folder_change_info_remove_uid (data->changes, uid);
3939 camel_folder_summary_remove_uid (s, uid);
3942 if (removed != NULL) {
3943 camel_folder_summary_touch (s);
3945 g_list_free_full (removed, (GDestroyNotify) g_free);
3948 camel_folder_summary_save_to_db (s, NULL);
3949 imapx_update_store_summary (job->folder);
3951 if (camel_folder_change_info_changed (data->changes))
3952 camel_folder_changed (job->folder, data->changes);
3953 camel_folder_change_info_clear (data->changes);
3955 camel_folder_summary_free_array (uids);
3957 /* If we have any new messages, download their headers, but only a few (100?) at a time */
3959 job->pop_operation_msg = TRUE;
3961 camel_operation_push_message (
3963 _("Fetching summary information for new messages in %s"),
3964 camel_folder_get_display_name (job->folder));
3966 imapx_uidset_init (&data->uidset, uidset_size, 0);
3967 /* These are new messages which arrived since we last knew the unseen count;
3968 * update it as they arrive. */
3969 data->update_unseen = TRUE;
3970 return imapx_command_step_fetch_done (is, ic, error);
3974 for (i = 0; i < data->infos->len; i++) {
3975 struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
3977 camel_flag_list_free (&r->server_user_flags);
3981 /* There's no sane way to get the server-side unseen count on the
3982 * select mailbox. So just work it out from the flags if its not in
3983 * mobile mode. In mobile mode we would have this filled up already
3984 * with a STATUS command.
3987 ((CamelIMAPXFolder *) job->folder)->unread_on_server = camel_folder_summary_get_unread_count (job->folder->summary);
3989 g_array_free (data->infos, TRUE);
3990 imapx_unregister_job (is, job);
3991 camel_imapx_command_unref (ic);
3997 imapx_job_scan_changes_start (CamelIMAPXJob *job,
3998 CamelIMAPXServer *is)
4000 CamelIMAPXCommand *ic;
4001 RefreshInfoData *data;
4002 gchar *uid = imapx_get_uid_from_index (job->folder->summary, 0);
4004 data = camel_imapx_job_get_data (job);
4005 g_return_if_fail (data != NULL);
4007 job->pop_operation_msg = TRUE;
4009 camel_operation_push_message (
4011 _("Scanning for changed messages in %s"),
4012 camel_folder_get_display_name (job->folder));
4014 e('E', "Scanning from %s in %s\n", uid, camel_folder_get_full_name (job->folder));
4015 ic = camel_imapx_command_new (
4016 is, "FETCH", job->folder,
4017 "UID FETCH %s:* (UID FLAGS)", uid ? uid : "1");
4018 camel_imapx_command_set_job (ic, job);
4019 ic->complete = imapx_job_scan_changes_done;
4021 data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
4022 imapx_command_queue (is, ic);
4027 imapx_command_fetch_new_messages_done (CamelIMAPXServer *is,
4028 CamelIMAPXCommand *ic,
4032 CamelIMAPXSummary *isum;
4033 CamelIMAPXFolder *ifolder;
4034 RefreshInfoData *data;
4035 gboolean success = TRUE;
4037 job = camel_imapx_command_get_job (ic);
4038 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4040 data = camel_imapx_job_get_data (job);
4041 g_return_val_if_fail (data != NULL, FALSE);
4043 ifolder = (CamelIMAPXFolder *) job->folder;
4044 isum = (CamelIMAPXSummary *) job->folder->summary;
4046 if (camel_imapx_command_set_error_if_failed (ic, error)) {
4049 _("Error fetching new messages"));
4054 if (camel_folder_change_info_changed (data->changes)) {
4055 camel_folder_summary_save_to_db (job->folder->summary, NULL);
4056 imapx_update_store_summary (job->folder);
4057 camel_folder_changed (job->folder, data->changes);
4058 camel_folder_change_info_clear (data->changes);
4061 if (camel_folder_summary_count (job->folder->summary)) {
4062 gchar *uid = imapx_get_uid_from_index (job->folder->summary,
4063 camel_folder_summary_count (job->folder->summary) - 1);
4064 guint64 uidl = strtoull (uid, NULL, 10);
4069 if (uidl > ifolder->uidnext_on_server) {
4070 c(is->tagprefix, "Updating uidnext_on_server for '%s' to %" G_GUINT64_FORMAT "\n",
4071 camel_folder_get_full_name (job->folder), uidl);
4072 ifolder->uidnext_on_server = uidl;
4076 isum->uidnext = ifolder->uidnext_on_server;
4079 imapx_unregister_job (is, job);
4080 camel_imapx_command_unref (ic);
4086 imapx_command_fetch_new_uids_done (CamelIMAPXServer *is,
4087 CamelIMAPXCommand *ic,
4091 RefreshInfoData *data;
4093 job = camel_imapx_command_get_job (ic);
4094 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4096 data = camel_imapx_job_get_data (job);
4097 g_return_val_if_fail (data != NULL, FALSE);
4102 sizeof (struct _refresh_info),
4103 imapx_refresh_info_cmp_descending);
4105 return imapx_command_step_fetch_done (is, ic, error);
4109 imapx_job_fetch_new_messages_start (CamelIMAPXJob *job,
4110 CamelIMAPXServer *is)
4112 CamelIMAPXCommand *ic;
4113 CamelFolder *folder = job->folder;
4114 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
4115 CamelService *service;
4116 CamelSettings *settings;
4117 CamelSortType fetch_order;
4118 RefreshInfoData *data;
4119 guint32 total, diff;
4123 data = camel_imapx_job_get_data (job);
4124 g_return_if_fail (data != NULL);
4126 service = CAMEL_SERVICE (is->store);
4127 settings = camel_service_get_settings (service);
4129 fetch_order = camel_imapx_settings_get_fetch_order (
4130 CAMEL_IMAPX_SETTINGS (settings));
4132 uidset_size = camel_imapx_settings_get_batch_fetch_count (
4133 CAMEL_IMAPX_SETTINGS (settings));
4135 total = camel_folder_summary_count (folder->summary);
4136 diff = ifolder->exists_on_server - total;
4140 uid = imapx_get_uid_from_index (folder->summary, total - 1);
4141 uidl = strtoull (uid, NULL, 10);
4143 uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uidl+1);
4145 uid = g_strdup ("1");
4147 job->pop_operation_msg = TRUE;
4149 camel_operation_push_message (
4151 _("Fetching summary information for new messages in %s"),
4152 camel_folder_get_display_name (folder));
4154 //printf("Fetch order: %d/%d\n", fetch_order, CAMEL_SORT_DESCENDING);
4155 if (diff > uidset_size || fetch_order == CAMEL_SORT_DESCENDING) {
4156 ic = camel_imapx_command_new (
4157 is, "FETCH", job->folder,
4158 "UID FETCH %s:* (UID FLAGS)", uid);
4159 imapx_uidset_init (&data->uidset, uidset_size, 0);
4160 data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
4163 if (fetch_order == CAMEL_SORT_DESCENDING)
4164 ic->complete = imapx_command_fetch_new_uids_done;
4166 ic->complete = imapx_command_step_fetch_done;
4168 ic = camel_imapx_command_new (
4169 is, "FETCH", job->folder,
4170 "UID FETCH %s:* (RFC822.SIZE RFC822.HEADER FLAGS)", uid);
4172 ic->complete = imapx_command_fetch_new_messages_done;
4176 camel_imapx_command_set_job (ic, job);
4177 imapx_command_queue (is, ic);
4181 imapx_job_fetch_messages_start (CamelIMAPXJob *job,
4182 CamelIMAPXServer *is)
4184 CamelIMAPXCommand *ic;
4185 CamelFolder *folder = job->folder;
4187 gchar *start_uid = NULL, *end_uid = NULL;
4188 CamelFetchType ftype;
4190 CamelSortType fetch_order;
4191 CamelService *service;
4192 CamelSettings *settings;
4194 RefreshInfoData *data;
4196 data = camel_imapx_job_get_data (job);
4197 g_return_if_fail (data != NULL);
4199 service = CAMEL_SERVICE (is->store);
4200 settings = camel_service_get_settings (service);
4202 fetch_order = camel_imapx_settings_get_fetch_order (
4203 CAMEL_IMAPX_SETTINGS (settings));
4205 total = camel_folder_summary_count (folder->summary);
4207 ftype = data->fetch_type;
4208 fetch_limit = data->fetch_msg_limit;
4210 uidset_size = camel_imapx_settings_get_batch_fetch_count (
4211 CAMEL_IMAPX_SETTINGS (settings));
4213 if (ftype == CAMEL_FETCH_NEW_MESSAGES ||
4214 (ftype == CAMEL_FETCH_OLD_MESSAGES && total <=0 )) {
4219 /* This means that we are fetching limited number of new mails */
4220 uid = g_strdup_printf("%d", total);
4222 /* For empty accounts, we always fetch the specified number of new mails independent of
4223 * being asked to fetch old or new.
4225 uid = g_strdup ("1");
4228 if (ftype == CAMEL_FETCH_NEW_MESSAGES) {
4229 /* We need to issue Status command to get the total unread count */
4230 ic = camel_imapx_command_new (
4232 "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
4233 camel_imapx_command_set_job (ic, job);
4236 imapx_command_run_sync (is, ic, job->cancellable, &job->error);
4238 job = camel_imapx_command_get_job (ic);
4239 g_return_if_fail (CAMEL_IS_IMAPX_JOB (job));
4241 if (job->error != NULL || camel_imapx_command_set_error_if_failed (ic, &job->error)) {
4243 &job->error, "%s: ",
4244 _("Error while fetching messages"));
4247 camel_imapx_command_unref (ic);
4250 camel_operation_push_message (
4252 ngettext ("Fetching summary information for %d message in %s",
4253 "Fetching summary information for %d messages in %s", data->fetch_msg_limit),
4254 data->fetch_msg_limit, camel_folder_get_full_name (folder));
4255 /* New account and fetching old messages, we would return just the limited number of newest messages */
4256 ic = camel_imapx_command_new (
4257 is, "FETCH", job->folder,
4258 "UID FETCH %s:* (UID FLAGS)", uid);
4260 imapx_uidset_init (&data->uidset, uidset_size, 0);
4261 data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
4264 if (fetch_order == CAMEL_SORT_DESCENDING)
4265 ic->complete = imapx_command_fetch_new_uids_done;
4267 ic->complete = imapx_command_step_fetch_done;
4271 camel_imapx_command_set_job (ic, job);
4272 imapx_command_queue (is, ic);
4274 } else if (ftype == CAMEL_FETCH_OLD_MESSAGES && total > 0) {
4276 start_uid = imapx_get_uid_from_index (folder->summary, 0);
4277 uidl = strtoull (start_uid, NULL, 10);
4278 end_uid = g_strdup_printf ("%" G_GINT64_MODIFIER "d", (((int)uidl)-fetch_limit > 0) ? (uidl-fetch_limit) : 1);
4280 camel_operation_push_message (
4282 ngettext ("Fetching summary information for %d message in %s",
4283 "Fetching summary information for %d messages in %s", data->fetch_msg_limit),
4284 data->fetch_msg_limit, camel_folder_get_full_name (folder));
4286 ic = camel_imapx_command_new (is, "FETCH", job->folder,
4287 "UID FETCH %s:%s (RFC822.SIZE RFC822.HEADER FLAGS)", start_uid, end_uid);
4289 ic->complete = imapx_command_fetch_new_messages_done;
4294 camel_imapx_command_set_job (ic, job);
4295 imapx_command_queue (is, ic);
4298 g_error ("Shouldn't reach here. Incorrect fetch type");
4303 imapx_job_refresh_info_start (CamelIMAPXJob *job,
4304 CamelIMAPXServer *is)
4307 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
4308 CamelIMAPXSummary *isum = (CamelIMAPXSummary *) job->folder->summary;
4309 CamelFolder *folder = job->folder;
4310 const gchar *full_name;
4311 gboolean need_rescan = FALSE;
4312 gboolean is_selected = FALSE;
4313 gboolean can_qresync = FALSE;
4314 CamelService *service;
4315 CamelSettings *settings;
4316 gboolean mobile_mode;
4318 service = CAMEL_SERVICE (is->store);
4319 settings = camel_service_get_settings (service);
4320 mobile_mode = camel_imapx_settings_get_mobile_mode (
4321 CAMEL_IMAPX_SETTINGS (settings));
4323 full_name = camel_folder_get_full_name (folder);
4325 /* Sync changes first, else unread count will not
4326 * match. Need to think about better ways for this */
4327 if (!imapx_server_sync_changes (
4328 is, folder, job->pri,
4329 job->cancellable, &job->error))
4332 #if 0 /* There are issues with this still; continue with the buggy behaviour
4333 where we issue STATUS on the current folder, for now...*/
4334 if (is->select_folder == folder)
4337 total = camel_folder_summary_count (folder->summary);
4339 if (ifolder->uidvalidity_on_server && isum->validity && isum->validity != ifolder->uidvalidity_on_server) {
4340 invalidate_local_cache (ifolder, ifolder->uidvalidity_on_server);
4344 /* We don't have valid unread count or modseq for currently-selected server
4345 * (unless we want to re-SELECT it). We fake unread count when fetching
4346 * message flags, but don't depend on modseq for the selected folder */
4347 if (total != ifolder->exists_on_server ||
4348 isum->uidnext != ifolder->uidnext_on_server ||
4349 camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
4350 (!is_selected && isum->modseq != ifolder->modseq_on_server))
4353 /* This is probably the first check of this folder after startup;
4354 * use STATUS to check whether the cached summary is valid, rather
4355 * than blindly updating. Only for servers which support CONDSTORE
4357 if ((isum->modseq && !ifolder->modseq_on_server))
4358 need_rescan = FALSE;
4360 /* If we don't think there's anything to do, poke it to check */
4362 CamelIMAPXCommand *ic;
4366 /* We may not issue STATUS on the current folder. Use SELECT or NOOP instead. */
4367 if (0 /* server needs SELECT not just NOOP */) {
4368 if (imapx_idle_supported (is) && imapx_in_idle (is))
4369 if (!imapx_stop_idle (is, &job->error))
4371 /* This doesn't work -- this is an immediate command, not queued */
4374 job->cancellable, &job->error))
4377 /* Or maybe just NOOP, unless we're in IDLE in which case do nothing */
4378 if (!imapx_idle_supported (is) || !imapx_in_idle (is)) {
4379 if (!camel_imapx_server_noop (is, folder, job->cancellable, &job->error))
4386 if (is->cinfo->capa & IMAPX_CAPABILITY_CONDSTORE)
4387 ic = camel_imapx_command_new (
4389 "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT HIGHESTMODSEQ)", folder);
4391 ic = camel_imapx_command_new (
4393 "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
4395 camel_imapx_command_set_job (ic, job);
4398 imapx_command_run_sync (
4399 is, ic, job->cancellable, &job->error);
4401 job = camel_imapx_command_get_job (ic);
4402 g_return_if_fail (CAMEL_IS_IMAPX_JOB (job));
4404 if (job->error != NULL || camel_imapx_command_set_error_if_failed (ic, &job->error)) {
4406 &job->error, "%s: ",
4407 _("Error refreshing folder"));
4410 if (job->error != NULL) {
4411 camel_imapx_command_unref (ic);
4415 camel_imapx_command_unref (ic);
4418 /* Recalulate need_rescan */
4419 if (total != ifolder->exists_on_server ||
4420 isum->uidnext != ifolder->uidnext_on_server ||
4421 camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
4422 (!is_selected && isum->modseq != ifolder->modseq_on_server))
4425 } else if (mobile_mode) {
4426 /* We need to issue Status command to get the total unread count */
4427 CamelIMAPXCommand *ic;
4429 ic = camel_imapx_command_new (
4431 "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
4432 camel_imapx_command_set_job (ic, job);
4435 imapx_command_run_sync (is, ic, job->cancellable, &job->error);
4437 job = camel_imapx_command_get_job (ic);
4438 g_return_if_fail (CAMEL_IS_IMAPX_JOB (job));
4440 if (job->error != NULL || camel_imapx_command_set_error_if_failed (ic, &job->error)) {
4442 &job->error, "%s: ",
4443 _("Error refreshing folder"));
4446 if (job->error != NULL) {
4447 camel_imapx_command_unref (ic);
4450 camel_imapx_command_unref (ic);
4453 if (is->use_qresync && isum->modseq && ifolder->uidvalidity_on_server)
4456 e(is->tagprefix, "folder %s is %sselected, total %u / %u, unread %u / %u, modseq %" G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT ", uidnext %u / %u: will %srescan\n",
4457 full_name, is_selected?"": "not ", total, ifolder->exists_on_server,
4458 camel_folder_summary_get_unread_count (folder->summary), ifolder->unread_on_server,
4459 (guint64) isum->modseq, (guint64) ifolder->modseq_on_server,
4460 isum->uidnext, ifolder->uidnext_on_server,
4461 need_rescan?"":"not ");
4463 /* Fetch new messages first, so that they appear to the user ASAP */
4464 if (ifolder->exists_on_server > total ||
4465 ifolder->uidnext_on_server > isum->uidnext)
4468 need_rescan = FALSE;
4470 if (!imapx_server_fetch_new_messages (
4471 is, folder, FALSE, FALSE,
4472 job->cancellable, &job->error))
4475 /* If QRESYNC-capable we'll have got all flags changes in SELECT */
4484 /* Actually we only want to select it; no need for the NOOP */
4485 camel_imapx_server_noop (is, folder, job->cancellable, &job->error);
4487 isum->modseq = ifolder->modseq_on_server;
4488 total = camel_folder_summary_count (job->folder->summary);
4489 if (total != ifolder->exists_on_server ||
4490 camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
4491 (isum->modseq != ifolder->modseq_on_server)) {
4492 c(is->tagprefix, "Eep, after QRESYNC we're out of sync. total %u / %u, unread %u / %u, modseq %" G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT "\n",
4493 total, ifolder->exists_on_server,
4494 camel_folder_summary_get_unread_count (folder->summary), ifolder->unread_on_server,
4495 isum->modseq, ifolder->modseq_on_server);
4497 c(is->tagprefix, "OK, after QRESYNC we're still in sync. total %u / %u, unread %u / %u, modseq %" G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT "\n",
4498 total, ifolder->exists_on_server,
4499 camel_folder_summary_get_unread_count (folder->summary), ifolder->unread_on_server,
4500 isum->modseq, ifolder->modseq_on_server);
4505 imapx_job_scan_changes_start (job, is);
4509 imapx_unregister_job (is, job);
4513 imapx_job_refresh_info_matches (CamelIMAPXJob *job,
4514 CamelFolder *folder,
4517 return (folder == job->folder);
4520 /* ********************************************************************** */
4523 imapx_command_expunge_done (CamelIMAPXServer *is,
4524 CamelIMAPXCommand *ic,
4528 gboolean success = TRUE;
4530 job = camel_imapx_command_get_job (ic);
4531 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4533 if (camel_imapx_command_set_error_if_failed (ic, error)) {
4536 _("Error expunging message"));
4541 CamelFolder *folder = job->folder;
4542 CamelStore *parent_store;
4543 const gchar *full_name;
4545 full_name = camel_folder_get_full_name (folder);
4546 parent_store = camel_folder_get_parent_store (folder);
4548 camel_folder_summary_save_to_db (folder->summary, NULL);
4549 uids = camel_db_get_folder_deleted_uids (parent_store->cdb_r, full_name, &job->error);
4551 if (uids && uids->len) {
4552 CamelFolderChangeInfo *changes;
4553 GList *removed = NULL;
4556 changes = camel_folder_change_info_new ();
4557 for (i = 0; i < uids->len; i++) {
4558 gchar *uid = uids->pdata[i];
4560 camel_folder_summary_remove_uid (folder->summary, uid);
4561 camel_folder_change_info_remove_uid (changes, uids->pdata[i]);
4562 removed = g_list_prepend (removed, (gpointer) uids->pdata[i]);
4565 camel_folder_summary_save_to_db (folder->summary, NULL);
4566 camel_folder_changed (folder, changes);
4567 camel_folder_change_info_free (changes);
4569 g_list_free (removed);
4570 g_ptr_array_foreach (uids, (GFunc) camel_pstring_free, NULL);
4571 g_ptr_array_free (uids, TRUE);
4575 imapx_unregister_job (is, job);
4576 camel_imapx_command_unref (ic);
4582 imapx_job_expunge_start (CamelIMAPXJob *job,
4583 CamelIMAPXServer *is)
4585 CamelIMAPXCommand *ic;
4587 imapx_server_sync_changes (
4588 is, job->folder, job->pri,
4589 job->cancellable, &job->error);
4591 /* TODO handle UIDPLUS capability */
4592 ic = camel_imapx_command_new (
4593 is, "EXPUNGE", job->folder, "EXPUNGE");
4594 camel_imapx_command_set_job (ic, job);
4596 ic->complete = imapx_command_expunge_done;
4598 imapx_command_queue (is, ic);
4602 imapx_job_expunge_matches (CamelIMAPXJob *job,
4603 CamelFolder *folder,
4606 return (folder == job->folder);
4609 /* ********************************************************************** */
4612 imapx_command_list_done (CamelIMAPXServer *is,
4613 CamelIMAPXCommand *ic,
4617 gboolean success = TRUE;
4619 job = camel_imapx_command_get_job (ic);
4620 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4622 if (camel_imapx_command_set_error_if_failed (ic, error)) {
4625 _("Error fetching folders"));
4629 e (is->tagprefix, "==== list or lsub completed ==== \n");
4630 imapx_unregister_job (is, job);
4631 camel_imapx_command_unref (ic);
4637 imapx_job_list_start (CamelIMAPXJob *job,
4638 CamelIMAPXServer *is)
4640 CamelIMAPXCommand *ic;
4643 data = camel_imapx_job_get_data (job);
4644 g_return_if_fail (data != NULL);
4646 ic = camel_imapx_command_new (
4649 (data->flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) ?
4653 /* Hm, we need a way to add atoms _without_ quoting or using literals */
4654 camel_imapx_command_add (ic, " ");
4655 camel_imapx_command_add (ic, data->ext);
4658 camel_imapx_command_set_job (ic, job);
4659 ic->complete = imapx_command_list_done;
4661 imapx_command_queue (is, ic);
4665 imapx_job_list_matches (CamelIMAPXJob *job,
4666 CamelFolder *folder,
4669 return TRUE; /* matches everything */
4672 /* ********************************************************************** */
4675 imapx_encode_folder_name (CamelIMAPXStore *istore,
4676 const gchar *folder_name)
4678 gchar *fname, *encoded;
4680 fname = camel_imapx_store_summary_full_from_path (istore->summary, folder_name);
4682 encoded = camel_utf8_utf7 (fname);
4685 encoded = camel_utf8_utf7 (folder_name);
4691 imapx_command_subscription_done (CamelIMAPXServer *is,
4692 CamelIMAPXCommand *ic,
4696 gboolean success = TRUE;
4698 job = camel_imapx_command_get_job (ic);
4699 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4701 if (camel_imapx_command_set_error_if_failed (ic, error)) {
4704 _("Error subscribing to folder"));
4708 imapx_unregister_job (is, job);
4709 camel_imapx_command_unref (ic);
4715 imapx_job_manage_subscription_start (CamelIMAPXJob *job,
4716 CamelIMAPXServer *is)
4718 CamelIMAPXCommand *ic;
4719 ManageSubscriptionsData *data;
4720 gchar *encoded_fname = NULL;
4722 data = camel_imapx_job_get_data (job);
4723 g_return_if_fail (data != NULL);
4725 encoded_fname = imapx_encode_folder_name (
4726 (CamelIMAPXStore *) is->store,
4728 if (data->subscribe)
4729 ic = camel_imapx_command_new (
4730 is, "SUBSCRIBE", NULL,
4731 "SUBSCRIBE %s", encoded_fname);
4733 ic = camel_imapx_command_new (
4734 is, "UNSUBSCRIBE", NULL,
4735 "UNSUBSCRIBE %s", encoded_fname);
4738 camel_imapx_command_set_job (ic, job);
4739 ic->complete = imapx_command_subscription_done;
4740 imapx_command_queue (is, ic);
4742 g_free (encoded_fname);
4745 /* ********************************************************************** */
4748 imapx_command_create_folder_done (CamelIMAPXServer *is,
4749 CamelIMAPXCommand *ic,
4753 gboolean success = TRUE;
4755 job = camel_imapx_command_get_job (ic);
4756 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4758 if (camel_imapx_command_set_error_if_failed (ic, error)) {
4761 _("Error creating folder"));
4765 imapx_unregister_job (is, job);
4766 camel_imapx_command_unref (ic);
4772 imapx_job_create_folder_start (CamelIMAPXJob *job,
4773 CamelIMAPXServer *is)
4775 CamelIMAPXCommand *ic;
4776 CreateFolderData *data;
4777 gchar *encoded_fname = NULL;
4779 data = camel_imapx_job_get_data (job);
4780 g_return_if_fail (data != NULL);
4782 encoded_fname = camel_utf8_utf7 (data->folder_name);
4783 ic = camel_imapx_command_new (
4785 "CREATE %s", encoded_fname);
4787 camel_imapx_command_set_job (ic, job);
4788 ic->complete = imapx_command_create_folder_done;
4789 imapx_command_queue (is, ic);
4791 g_free (encoded_fname);
4794 /* ********************************************************************** */
4797 imapx_command_delete_folder_done (CamelIMAPXServer *is,
4798 CamelIMAPXCommand *ic,
4804 job = camel_imapx_command_get_job (ic);
4805 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4807 if (camel_imapx_command_set_error_if_failed (ic, error)) {
4810 _("Error deleting folder"));
4814 imapx_unregister_job (is, job);
4815 camel_imapx_command_unref (ic);
4821 imapx_job_delete_folder_start (CamelIMAPXJob *job,
4822 CamelIMAPXServer *is)
4824 CamelIMAPXCommand *ic;
4825 DeleteFolderData *data;
4826 gchar *encoded_fname = NULL;
4828 data = camel_imapx_job_get_data (job);
4829 g_return_if_fail (data != NULL);
4831 encoded_fname = imapx_encode_folder_name ((CamelIMAPXStore *) is->store, data->folder_name);
4833 job->folder = camel_store_get_folder_sync (
4834 is->store, "INBOX", 0, job->cancellable, &job->error);
4836 /* make sure to-be-deleted folder is not selected by selecting INBOX for this operation */
4837 ic = camel_imapx_command_new (
4838 is, "DELETE", job->folder,
4839 "DELETE %s", encoded_fname);
4841 camel_imapx_command_set_job (ic, job);
4842 ic->complete = imapx_command_delete_folder_done;
4843 imapx_command_queue (is, ic);
4845 g_free (encoded_fname);
4848 /* ********************************************************************** */
4851 imapx_command_rename_folder_done (CamelIMAPXServer *is,
4852 CamelIMAPXCommand *ic,
4856 gboolean success = TRUE;
4858 job = camel_imapx_command_get_job (ic);
4859 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4861 if (camel_imapx_command_set_error_if_failed (ic, error)) {
4864 _("Error renaming folder"));
4868 imapx_unregister_job (is, job);
4869 camel_imapx_command_unref (ic);
4875 imapx_job_rename_folder_start (CamelIMAPXJob *job,
4876 CamelIMAPXServer *is)
4878 CamelIMAPXCommand *ic;
4879 RenameFolderData *data;
4880 gchar *en_ofname = NULL, *en_nfname = NULL;
4882 data = camel_imapx_job_get_data (job);
4883 g_return_if_fail (data != NULL);
4885 job->folder = camel_store_get_folder_sync (
4886 is->store, "INBOX", 0, job->cancellable, &job->error);
4888 en_ofname = imapx_encode_folder_name ((CamelIMAPXStore *) is->store, data->old_folder_name);
4889 en_nfname = imapx_encode_folder_name ((CamelIMAPXStore *) is->store, data->new_folder_name);
4891 ic = camel_imapx_command_new (
4892 is, "RENAME", job->folder,
4893 "RENAME %s %s", en_ofname, en_nfname);
4895 camel_imapx_command_set_job (ic, job);
4896 ic->complete = imapx_command_rename_folder_done;
4897 imapx_command_queue (is, ic);
4903 /* ********************************************************************** */
4906 imapx_command_noop_done (CamelIMAPXServer *is,
4907 CamelIMAPXCommand *ic,
4911 gboolean success = TRUE;
4913 job = camel_imapx_command_get_job (ic);
4914 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4916 if (camel_imapx_command_set_error_if_failed (ic, error)) {
4919 _("Error performing NOOP"));
4923 imapx_unregister_job (is, job);
4924 camel_imapx_command_unref (ic);
4930 imapx_job_noop_start (CamelIMAPXJob *job,
4931 CamelIMAPXServer *is)
4933 CamelIMAPXCommand *ic;
4935 ic = camel_imapx_command_new (
4936 is, "NOOP", job->folder, "NOOP");
4938 camel_imapx_command_set_job (ic, job);
4939 ic->complete = imapx_command_noop_done;
4941 ic->pri = IMAPX_PRIORITY_REFRESH_INFO;
4943 ic->pri = IMAPX_PRIORITY_NOOP;
4944 imapx_command_queue (is, ic);
4947 /* ********************************************************************** */
4949 /* FIXME: this is basically a copy of the same in camel-imapx-utils.c */
4954 { "\\ANSWERED", CAMEL_MESSAGE_ANSWERED },
4955 { "\\DELETED", CAMEL_MESSAGE_DELETED },
4956 { "\\DRAFT", CAMEL_MESSAGE_DRAFT },
4957 { "\\FLAGGED", CAMEL_MESSAGE_FLAGGED },
4958 { "\\SEEN", CAMEL_MESSAGE_SEEN },
4959 { "\\RECENT", CAMEL_IMAPX_MESSAGE_RECENT },
4960 { "JUNK", CAMEL_MESSAGE_JUNK },
4961 { "NOTJUNK", CAMEL_MESSAGE_NOTJUNK }
4975 imapx_command_sync_changes_done (CamelIMAPXServer *is,
4976 CamelIMAPXCommand *ic,
4980 CamelStore *parent_store;
4981 SyncChangesData *data;
4982 const gchar *full_name;
4983 CamelService *service;
4984 CamelSettings *settings;
4985 gboolean mobile_mode;
4986 gboolean success = TRUE;
4988 job = camel_imapx_command_get_job (ic);
4989 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4991 data = camel_imapx_job_get_data (job);
4992 g_return_val_if_fail (data != NULL, FALSE);
4994 service = CAMEL_SERVICE (is->store);
4995 settings = camel_service_get_settings (service);
4996 mobile_mode = camel_imapx_settings_get_mobile_mode (
4997 CAMEL_IMAPX_SETTINGS (settings));
5001 full_name = camel_folder_get_full_name (job->folder);
5002 parent_store = camel_folder_get_parent_store (job->folder);
5004 /* If this worked, we should really just update the changes that we
5005 * sucessfully stored, so we dont have to worry about sending them
5007 * But then we'd have to track which uid's we actually updated, so
5008 * its easier just to refresh all of the ones we got.
5010 * Not that ... given all the asynchronicity going on, we're guaranteed
5011 * that what we just set is actually what is on the server now .. but
5012 * if it isn't, i guess we'll fix up next refresh */
5014 if (camel_imapx_command_set_error_if_failed (ic, error)) {
5017 _("Error syncing changes"));
5024 for (i = 0; i < data->changed_uids->len; i++) {
5025 CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) camel_folder_summary_get (job->folder->summary,
5026 data->changed_uids->pdata[i]);
5031 xinfo->server_flags = ((CamelMessageInfoBase *) xinfo)->flags & CAMEL_IMAPX_SERVER_FLAGS;
5032 xinfo->info.flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
5033 xinfo->info.dirty = TRUE;
5034 camel_flag_list_copy (&xinfo->server_user_flags, &xinfo->info.user_flags);
5036 camel_folder_summary_touch (job->folder->summary);
5037 camel_message_info_free (xinfo);
5039 /* Apply the changes to server-side unread count; it won't tell
5040 * us of these changes, of course. */
5041 ((CamelIMAPXFolder *) job->folder)->unread_on_server += data->unread_change;
5044 if (job->commands == 0) {
5045 if (job->folder->summary && (job->folder->summary->flags & CAMEL_SUMMARY_DIRTY) != 0) {
5048 /* ... and store's summary when folder's summary is dirty */
5049 si = camel_store_summary_path ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary, full_name);
5051 if (si->total != camel_folder_summary_get_saved_count (job->folder->summary) ||
5052 si->unread != camel_folder_summary_get_unread_count (job->folder->summary)) {
5053 si->total = camel_folder_summary_get_saved_count (job->folder->summary);
5054 if (!mobile_mode) /* Don't mess with server's unread count in mobile mode, as what we have downloaded is little */
5055 si->unread = camel_folder_summary_get_unread_count (job->folder->summary);
5056 camel_store_summary_touch ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
5059 camel_store_summary_info_free ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary, si);
5063 camel_folder_summary_save_to_db (job->folder->summary, &job->error);
5064 camel_store_summary_save ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
5066 imapx_unregister_job (is, job);
5069 camel_imapx_command_unref (ic);
5075 imapx_job_sync_changes_start (CamelIMAPXJob *job,
5076 CamelIMAPXServer *is)
5078 SyncChangesData *data;
5080 struct _uidset_state ss;
5084 data = camel_imapx_job_get_data (job);
5085 g_return_if_fail (data != NULL);
5087 uids = data->changed_uids;
5089 for (on = 0; on < 2; on++) {
5090 guint32 orset = on ? data->on_set : data->off_set;
5091 GArray *user_set = on ? data->on_user : data->off_user;
5093 for (j = 0; j < G_N_ELEMENTS (flags_table); j++) {
5094 guint32 flag = flags_table[j].flag;
5095 CamelIMAPXCommand *ic = NULL;
5097 if ((orset & flag) == 0)
5100 c(is->tagprefix, "checking/storing %s flags '%s'\n", on?"on":"off", flags_table[j].name);
5101 imapx_uidset_init (&ss, 0, 100);
5102 for (i = 0; i < uids->len; i++) {
5103 CamelIMAPXMessageInfo *info = (CamelIMAPXMessageInfo *) camel_folder_summary_get
5104 (job->folder->summary, uids->pdata[i]);
5112 flags = ((CamelMessageInfoBase *) info)->flags & CAMEL_IMAPX_SERVER_FLAGS;
5113 sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
5116 if ( (on && (((flags ^ sflags) & flags) & flag))
5117 || (!on && (((flags ^ sflags) & ~flags) & flag))) {
5119 ic = camel_imapx_command_new (
5120 is, "STORE", job->folder,
5122 ic->complete = imapx_command_sync_changes_done;
5123 camel_imapx_command_set_job (ic, job);
5126 send = imapx_uidset_add (&ss, ic, camel_message_info_uid (info));
5128 if (send == 1 || (i == uids->len - 1 && imapx_uidset_done (&ss, ic))) {
5130 camel_imapx_command_add (ic, " %tFLAGS.SILENT (%t)", on?"+":"-", flags_table[j].name);
5131 imapx_command_queue (is, ic);
5134 if (flag == CAMEL_MESSAGE_SEEN) {
5135 /* Remember how the server's unread count will change if this
5136 * command succeeds */
5138 data->unread_change--;
5140 data->unread_change++;
5142 camel_message_info_free (info);
5147 CamelIMAPXCommand *ic = NULL;
5149 for (j = 0; j < user_set->len; j++) {
5150 struct _imapx_flag_change *c = &g_array_index (user_set, struct _imapx_flag_change, j);
5152 imapx_uidset_init (&ss, 0, 100);
5153 for (i = 0; i < c->infos->len; i++) {
5154 CamelIMAPXMessageInfo *info = c->infos->pdata[i];
5157 ic = camel_imapx_command_new (
5158 is, "STORE", job->folder,
5160 ic->complete = imapx_command_sync_changes_done;
5161 camel_imapx_command_set_job (ic, job);
5165 if (imapx_uidset_add (&ss, ic, camel_message_info_uid (info)) == 1
5166 || (i == c->infos->len - 1 && imapx_uidset_done (&ss, ic))) {
5168 camel_imapx_command_add (ic, " %tFLAGS.SILENT (%t)", on?"+":"-", c->name);
5169 imapx_command_queue (is, ic);
5177 /* Since this may start in another thread ... we need to
5178 * lock the commands count, ho hum */
5180 if (job->commands == 0) {
5181 imapx_unregister_job (is, job);
5186 imapx_job_sync_changes_matches (CamelIMAPXJob *job,
5187 CamelFolder *folder,
5190 return (folder == job->folder);
5193 /* we cancel all the commands and their jobs, so associated jobs will be notified */
5195 cancel_all_jobs (CamelIMAPXServer *is,
5198 CamelIMAPXCommandQueue *queue;
5201 /* Transfer all pending and active commands to a separate
5202 * command queue to complete them without holding QUEUE_LOCK. */
5204 queue = camel_imapx_command_queue_new ();
5208 camel_imapx_command_queue_transfer (is->queue, queue);
5209 camel_imapx_command_queue_transfer (is->active, queue);
5213 head = camel_imapx_command_queue_peek_head_link (queue);
5215 for (link = head; link != NULL; link = g_list_next (link)) {
5216 CamelIMAPXCommand *ic = link->data;
5219 /* Sanity check the CamelIMAPXCommand before proceeding.
5220 * XXX We are actually getting reports of crashes here...
5221 * not sure how this is happening but it's happening. */
5225 /* Similarly with the CamelIMAPXJob contained within. */
5226 job = camel_imapx_command_get_job (ic);
5227 if (!CAMEL_IS_IMAPX_JOB (job))
5230 if (job->error == NULL)
5231 job->error = g_error_copy (error);
5233 /* Send a NULL GError since we've already set
5234 * the job's GError, and we're not interested
5235 * in individual command errors. */
5236 ic->complete (is, ic, NULL);
5239 camel_imapx_command_queue_free (queue);
5242 /* ********************************************************************** */
5245 parse_contents (CamelIMAPXServer *is,
5246 GCancellable *cancellable,
5249 while (imapx_step (is, cancellable, error))
5250 if (camel_imapx_stream_buffered (is->stream) == 0)
5255 * The main processing (reading) loop.
5257 * Main area of locking required is command_queue
5258 * and command_start_next, the 'literal' command,
5259 * the jobs queue, the active queue, the queue
5262 imapx_parser_thread (gpointer d)
5264 CamelIMAPXServer *is = d;
5265 GCancellable *cancellable;
5266 GError *local_error = NULL;
5269 cancellable = camel_operation_new ();
5270 is->cancellable = g_object_ref (cancellable);
5273 while (local_error == NULL && is->stream) {
5274 g_cancellable_reset (cancellable);
5277 if (is->is_process_stream) {
5278 GPollFD fds[2] = { {0, 0, 0}, {0, 0, 0} };
5281 fds[0].fd = ((CamelStreamProcess *) is->stream->source)->sockfd;
5282 fds[0].events = G_IO_IN;
5283 fds[1].fd = g_cancellable_get_fd (cancellable);
5284 fds[1].events = G_IO_IN;
5285 res = g_poll (fds, 2, -1);
5287 g_usleep (1) /* ?? */ ;
5290 else if (fds[0].revents & G_IO_IN)
5291 parse_contents (is, cancellable, &local_error);
5292 g_cancellable_release_fd (cancellable);
5296 parse_contents (is, cancellable, &local_error);
5299 if (is->parser_quit)
5300 g_cancellable_cancel (cancellable);
5301 else if (g_cancellable_is_cancelled (cancellable)) {
5305 is_empty = camel_imapx_command_queue_is_empty (is->active);
5308 if (is_empty || (imapx_idle_supported (is) && imapx_in_idle (is))) {
5309 g_cancellable_reset (cancellable);
5310 g_clear_error (&local_error);
5312 /* Cancelled error should be set. */
5313 g_warn_if_fail (local_error != NULL);
5317 /* Jump out of the loop if an error occurred. */
5318 if (local_error != NULL)
5323 is->state = IMAPX_SHUTDOWN;
5326 cancel_all_jobs (is, local_error);
5328 g_clear_error (&local_error);
5331 if (is->cancellable != NULL) {
5332 g_object_unref (is->cancellable);
5333 is->cancellable = NULL;
5335 g_object_unref (cancellable);
5338 is->parser_quit = FALSE;
5339 if (is->state != IMAPX_SHUTDOWN)
5340 g_signal_emit (is, signals[SHUTDOWN], 0);
5346 join_helper (gpointer thread)
5348 g_thread_join (thread);
5353 imapx_server_dispose (GObject *object)
5355 CamelIMAPXServer *server = CAMEL_IMAPX_SERVER (object);
5357 QUEUE_LOCK (server);
5358 server->state = IMAPX_SHUTDOWN;
5360 server->parser_quit = TRUE;
5362 if (server->cancellable != NULL) {
5363 g_cancellable_cancel (server->cancellable);
5364 g_object_unref (server->cancellable);
5365 server->cancellable = NULL;
5367 QUEUE_UNLOCK (server);
5369 if (server->parser_thread) {
5370 if (server->parser_thread == g_thread_self ())
5371 g_idle_add (&join_helper, server->parser_thread);
5373 g_thread_join (server->parser_thread);
5374 server->parser_thread = NULL;
5377 if (server->cinfo && imapx_idle_supported (server))
5378 imapx_exit_idle (server);
5380 imapx_disconnect (server);
5382 if (server->session != NULL) {
5383 g_object_unref (server->session);
5384 server->session = NULL;
5387 /* Chain up to parent's dispose() method. */
5388 G_OBJECT_CLASS (camel_imapx_server_parent_class)->dispose (object);
5392 imapx_server_finalize (GObject *object)
5394 CamelIMAPXServer *is = CAMEL_IMAPX_SERVER (object);
5396 g_static_rec_mutex_free (&is->queue_lock);
5397 g_static_rec_mutex_free (&is->ostream_lock);
5398 g_mutex_free (is->fetch_mutex);
5399 g_cond_free (is->fetch_cond);
5401 camel_folder_change_info_free (is->changes);
5403 /* Chain up to parent's finalize() method. */
5404 G_OBJECT_CLASS (camel_imapx_server_parent_class)->finalize (object);
5408 imapx_server_constructed (GObject *object)
5410 CamelIMAPXServer *server;
5411 CamelIMAPXServerClass *class;
5413 server = CAMEL_IMAPX_SERVER (object);
5414 class = CAMEL_IMAPX_SERVER_GET_CLASS (server);
5416 server->tagprefix = class->tagprefix;
5418 if (class->tagprefix > 'Z')
5419 class->tagprefix = 'A';
5423 camel_imapx_server_class_init (CamelIMAPXServerClass *class)
5425 GObjectClass *object_class;
5427 object_class = G_OBJECT_CLASS (class);
5428 object_class->finalize = imapx_server_finalize;
5429 object_class->constructed = imapx_server_constructed;
5430 object_class->dispose = imapx_server_dispose;
5432 class->select_changed = NULL;
5433 class->shutdown = NULL;
5436 * CamelIMAPXServer::select_changed
5437 * @server: the #CamelIMAPXServer which emitted the signal
5439 signals[SELECT_CHANGED] = g_signal_new (
5441 G_OBJECT_CLASS_TYPE (class),
5443 G_STRUCT_OFFSET (CamelIMAPXServerClass, select_changed),
5445 g_cclosure_marshal_VOID__STRING,
5446 G_TYPE_NONE, 1, G_TYPE_STRING);
5449 * CamelIMAPXServer::shutdown
5450 * @server: the #CamelIMAPXServer which emitted the signal
5452 signals[SHUTDOWN] = g_signal_new (
5454 G_OBJECT_CLASS_TYPE (class),
5456 G_STRUCT_OFFSET (CamelIMAPXServerClass, shutdown),
5458 g_cclosure_marshal_VOID__VOID,
5461 class->tagprefix = 'A';
5465 camel_imapx_server_init (CamelIMAPXServer *is)
5467 is->queue = camel_imapx_command_queue_new ();
5468 is->active = camel_imapx_command_queue_new ();
5469 is->done = camel_imapx_command_queue_new ();
5471 g_queue_init (&is->jobs);
5473 /* not used at the moment. Use it in future */
5474 is->job_timeout = 29 * 60 * 1000 * 1000;
5476 g_static_rec_mutex_init (&is->queue_lock);
5477 g_static_rec_mutex_init (&is->ostream_lock);
5479 is->state = IMAPX_DISCONNECTED;
5481 is->expunged = NULL;
5482 is->changes = camel_folder_change_info_new ();
5483 is->parser_quit = FALSE;
5485 is->fetch_mutex = g_mutex_new ();
5486 is->fetch_cond = g_cond_new ();
5490 camel_imapx_server_new (CamelStore *store)
5492 CamelService *service;
5493 CamelSession *session;
5494 CamelIMAPXServer *is;
5496 service = CAMEL_SERVICE (store);
5497 session = camel_service_get_session (service);
5499 is = g_object_new (CAMEL_TYPE_IMAPX_SERVER, NULL);
5500 is->session = g_object_ref (session);
5507 imapx_disconnect (CamelIMAPXServer *is)
5509 gboolean ret = TRUE;
5511 g_static_rec_mutex_lock (&is->ostream_lock);
5514 if (camel_stream_close (is->stream->source, NULL, NULL) == -1)
5517 g_object_unref (is->stream);
5521 /* TODO need a select lock */
5522 if (is->select_folder) {
5523 g_object_unref (is->select_folder);
5524 is->select_folder = NULL;
5527 if (is->select_pending) {
5528 g_object_unref (is->select_pending);
5529 is->select_pending = NULL;
5533 imapx_free_capability (is->cinfo);
5537 is->state = IMAPX_DISCONNECTED;
5539 g_static_rec_mutex_unlock (&is->ostream_lock);
5544 /* Client commands */
5546 camel_imapx_server_connect (CamelIMAPXServer *is,
5547 GCancellable *cancellable,
5552 if (is->state == IMAPX_SHUTDOWN) {
5553 g_set_error (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE, "Shutting down");
5557 if (is->state >= IMAPX_INITIALISED)
5560 g_static_rec_mutex_lock (&is->ostream_lock);
5561 success = imapx_reconnect (is, cancellable, error);
5562 g_static_rec_mutex_unlock (&is->ostream_lock);
5567 is->parser_thread = g_thread_create ((GThreadFunc) imapx_parser_thread, is, TRUE, NULL);
5571 static CamelStream *
5572 imapx_server_get_message (CamelIMAPXServer *is,
5573 CamelFolder *folder,
5576 GCancellable *cancellable,
5579 CamelStream *stream = NULL;
5580 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
5582 CamelMessageInfo *mi;
5583 GetMessageData *data;
5584 gboolean registered;
5589 if ((job = imapx_is_job_in_queue (is, folder, IMAPX_JOB_GET_MESSAGE, uid))) {
5593 /* Wait for the job to finish. This would be so much nicer if
5594 * we could just use the queue lock with a GCond, but instead
5595 * we have to use a GMutex. I miss the kernel waitqueues. */
5599 g_mutex_lock (is->fetch_mutex);
5600 this = is->fetch_count;
5604 while (is->fetch_count == this)
5605 g_cond_wait (is->fetch_cond, is->fetch_mutex);
5607 g_mutex_unlock (is->fetch_mutex);
5611 } while (imapx_is_job_in_queue (is, folder,
5612 IMAPX_JOB_GET_MESSAGE, uid));
5616 stream = camel_data_cache_get (
5617 ifolder->cache, "cur", uid, error);
5620 error, "Could not retrieve the message: ");
5624 mi = camel_folder_summary_get (folder->summary, uid);
5627 error, CAMEL_FOLDER_ERROR,
5628 CAMEL_FOLDER_ERROR_INVALID_UID,
5629 _("Cannot get message with message ID %s: %s"),
5630 uid, _("No such message available."));
5635 data = g_slice_new0 (GetMessageData);
5636 data->uid = g_strdup (uid);
5637 data->stream = camel_data_cache_add (ifolder->cache, "tmp", uid, NULL);
5638 data->size = ((CamelMessageInfoBase *) mi)->size;
5639 if (data->size > MULTI_SIZE)
5640 data->use_multi_fetch = TRUE;
5642 job = camel_imapx_job_new (cancellable);
5644 job->type = IMAPX_JOB_GET_MESSAGE;
5645 job->start = imapx_job_get_message_start;
5646 job->matches = imapx_job_get_message_matches;
5647 job->folder = folder;
5649 camel_imapx_job_set_data (
5650 job, data, (GDestroyNotify) get_message_data_free);
5652 camel_message_info_free (mi);
5653 registered = imapx_register_job (is, job, error);
5657 success = registered && camel_imapx_job_run (job, is, error);
5660 stream = g_object_ref (data->stream);
5662 camel_imapx_job_unref (job);
5664 g_mutex_lock (is->fetch_mutex);
5666 g_cond_broadcast (is->fetch_cond);
5667 g_mutex_unlock (is->fetch_mutex);
5673 camel_imapx_server_get_message (CamelIMAPXServer *is,
5674 CamelFolder *folder,
5676 GCancellable *cancellable,
5679 CamelStream *stream;
5681 stream = imapx_server_get_message (
5683 IMAPX_PRIORITY_GET_MESSAGE,
5684 cancellable, error);
5690 camel_imapx_server_sync_message (CamelIMAPXServer *is,
5691 CamelFolder *folder,
5693 GCancellable *cancellable,
5696 gchar *cache_file = NULL;
5697 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
5698 CamelStream *stream;
5702 /* Check if the cache file already exists and is non-empty. */
5703 cache_file = camel_data_cache_get_filename (
5704 ifolder->cache, "cur", uid, NULL);
5705 is_cached = (g_stat (cache_file, &st) == 0 && st.st_size > 0);
5706 g_free (cache_file);
5711 stream = imapx_server_get_message (
5713 IMAPX_PRIORITY_SYNC_MESSAGE,
5714 cancellable, error);
5719 g_object_unref (stream);
5725 camel_imapx_server_copy_message (CamelIMAPXServer *is,
5726 CamelFolder *source,
5729 gboolean delete_originals,
5730 GCancellable *cancellable,
5734 CopyMessagesData *data;
5737 data = g_slice_new0 (CopyMessagesData);
5738 data->dest = g_object_ref (dest);
5739 data->uids = g_ptr_array_new ();
5740 data->delete_originals = delete_originals;
5742 for (ii = 0; ii < uids->len; ii++)
5743 g_ptr_array_add (data->uids, g_strdup (uids->pdata[ii]));
5745 job = camel_imapx_job_new (cancellable);
5746 job->pri = IMAPX_PRIORITY_APPEND_MESSAGE;
5747 job->type = IMAPX_JOB_COPY_MESSAGE;
5748 job->start = imapx_job_copy_messages_start;
5749 job->folder = g_object_ref (source);
5751 camel_imapx_job_set_data (
5752 job, data, (GDestroyNotify) copy_messages_data_free);
5754 return imapx_submit_job (is, job, error);
5758 camel_imapx_server_append_message (CamelIMAPXServer *is,
5759 CamelFolder *folder,
5760 CamelMimeMessage *message,
5761 const CamelMessageInfo *mi,
5762 gchar **appended_uid,
5763 GCancellable *cancellable,
5766 gchar *uid = NULL, *path = NULL;
5767 CamelStream *stream, *filter;
5768 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
5769 CamelMimeFilter *canon;
5771 CamelMessageInfo *info;
5772 AppendMessageData *data;
5776 /* Append just assumes we have no/a dodgy connection. We dump stuff into the 'new'
5777 * directory, and let the summary know it's there. Then we fire off a no-reply
5778 * job which will asynchronously upload the message at some point in the future,
5779 * and fix up the summary to match */
5781 /* chen cleanup this later */
5782 uid = imapx_get_temp_uid ();
5783 stream = camel_data_cache_add (ifolder->cache, "new", uid, error);
5784 if (stream == NULL) {
5785 g_prefix_error (error, _("Cannot create spool file: "));
5790 filter = camel_stream_filter_new (stream);
5791 g_object_unref (stream);
5792 canon = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_CRLF);
5793 camel_stream_filter_add ((CamelStreamFilter *) filter, canon);
5794 res = camel_data_wrapper_write_to_stream_sync (
5795 (CamelDataWrapper *) message, filter, cancellable, error);
5796 g_object_unref (canon);
5797 g_object_unref (filter);
5800 g_prefix_error (error, _("Cannot create spool file: "));
5801 camel_data_cache_remove (ifolder->cache, "new", uid, NULL);
5806 path = camel_data_cache_get_filename (ifolder->cache, "new", uid, NULL);
5807 info = camel_folder_summary_info_new_from_message ((CamelFolderSummary *) folder->summary, message, NULL);
5808 info->uid = camel_pstring_strdup (uid);
5810 ((CamelMessageInfoBase *) info)->flags = ((CamelMessageInfoBase *) mi)->flags;
5813 /* So, we actually just want to let the server loop that
5814 * messages need appending, i think. This is so the same
5815 * mechanism is used for normal uploading as well as
5816 * offline re-syncing when we go back online */
5818 data = g_slice_new0 (AppendMessageData);
5819 data->info = info; /* takes ownership */
5820 data->path = path; /* takes ownership */
5821 data->appended_uid = NULL;
5823 job = camel_imapx_job_new (cancellable);
5824 job->pri = IMAPX_PRIORITY_APPEND_MESSAGE;
5825 job->type = IMAPX_JOB_APPEND_MESSAGE;
5826 job->start = imapx_job_append_message_start;
5827 job->folder = g_object_ref (folder);
5828 job->noreply = FALSE;
5830 camel_imapx_job_set_data (
5831 job, data, (GDestroyNotify) append_message_data_free);
5833 success = imapx_submit_job (is, job, error);
5836 *appended_uid = data->appended_uid;
5837 data->appended_uid = NULL;
5840 camel_imapx_job_unref (job);
5846 camel_imapx_server_noop (CamelIMAPXServer *is,
5847 CamelFolder *folder,
5848 GCancellable *cancellable,
5854 job = camel_imapx_job_new (cancellable);
5855 job->type = IMAPX_JOB_NOOP;
5856 job->start = imapx_job_noop_start;
5857 job->folder = folder;
5858 job->pri = IMAPX_PRIORITY_NOOP;
5860 success = imapx_submit_job (is, job, error);
5862 camel_imapx_job_unref (job);
5868 camel_imapx_server_refresh_info (CamelIMAPXServer *is,
5869 CamelFolder *folder,
5870 GCancellable *cancellable,
5874 RefreshInfoData *data;
5875 gboolean registered = TRUE;
5876 const gchar *full_name;
5877 gboolean success = TRUE;
5879 full_name = camel_folder_get_full_name (folder);
5883 /* Both RefreshInfo and Fetch messages can't operate simultaneously */
5884 if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_REFRESH_INFO, NULL) ||
5885 imapx_is_job_in_queue (is, folder, IMAPX_JOB_FETCH_MESSAGES, NULL)) {
5890 data = g_slice_new0 (RefreshInfoData);
5891 data->changes = camel_folder_change_info_new ();
5893 job = camel_imapx_job_new (cancellable);
5894 job->type = IMAPX_JOB_REFRESH_INFO;
5895 job->start = imapx_job_refresh_info_start;
5896 job->matches = imapx_job_refresh_info_matches;
5897 job->folder = folder;
5898 job->pri = IMAPX_PRIORITY_REFRESH_INFO;
5900 if (g_ascii_strcasecmp(full_name, "INBOX") == 0)
5903 camel_imapx_job_set_data (
5904 job, data, (GDestroyNotify) refresh_info_data_free);
5906 registered = imapx_register_job (is, job, error);
5910 success = registered && camel_imapx_job_run (job, is, error);
5912 if (success && camel_folder_change_info_changed (data->changes))
5913 camel_folder_changed (folder, data->changes);
5915 camel_imapx_job_unref (job);
5921 imapx_sync_free_user (GArray *user_set)
5925 if (user_set == NULL)
5928 for (i = 0; i < user_set->len; i++) {
5929 struct _imapx_flag_change *flag_change = &g_array_index (user_set, struct _imapx_flag_change, i);
5930 GPtrArray *infos = flag_change->infos;
5933 for (j = 0; j < infos->len; j++) {
5934 CamelMessageInfo *info = g_ptr_array_index (infos, j);
5935 camel_message_info_free (info);
5938 g_ptr_array_free (infos, TRUE);
5939 g_free (flag_change->name);
5941 g_array_free (user_set, TRUE);
5945 imapx_server_sync_changes (CamelIMAPXServer *is,
5946 CamelFolder *folder,
5948 GCancellable *cancellable,
5951 guint i, on_orset, off_orset;
5953 GArray *on_user = NULL, *off_user = NULL;
5954 CamelIMAPXMessageInfo *info;
5956 SyncChangesData *data;
5957 gboolean registered;
5958 gboolean success = TRUE;
5960 /* We calculate two masks, a mask of all flags which have been
5961 * turned off and a mask of all flags which have been turned
5962 * on. If either of these aren't 0, then we have work to do,
5963 * and we fire off a job to do it.
5965 * User flags are a bit more tricky, we rely on the user
5966 * flags being sorted, and then we create a bunch of lists;
5967 * one for each flag being turned off, including each
5968 * info being turned off, and one for each flag being turned on.
5970 uids = camel_folder_summary_get_changed (folder->summary);
5972 if (uids->len == 0) {
5973 camel_folder_free_uids (folder, uids);
5977 off_orset = on_orset = 0;
5978 for (i = 0; i < uids->len; i++) {
5979 guint32 flags, sflags;
5980 CamelFlag *uflags, *suflags;
5983 info = (CamelIMAPXMessageInfo *) camel_folder_summary_get (folder->summary, uids->pdata[i]);
5988 if (!(info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) {
5989 camel_message_info_free (info);
5993 flags = ((CamelMessageInfoBase *) info)->flags & CAMEL_IMAPX_SERVER_FLAGS;
5994 sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
5995 if (flags != sflags) {
5996 off_orset |= ( flags ^ sflags ) & ~flags;
5997 on_orset |= (flags ^ sflags) & flags;
6000 uflags = ((CamelMessageInfoBase *) info)->user_flags;
6001 suflags = info->server_user_flags;
6002 while (uflags || suflags) {
6007 res = strcmp (uflags->name, suflags->name);
6008 else if (*uflags->name)
6011 uflags = uflags->next;
6019 uflags = uflags->next;
6020 suflags = suflags->next;
6023 CamelFlag *user_flag;
6024 struct _imapx_flag_change *change = NULL, add = { 0 };
6027 if (on_user == NULL)
6028 on_user = g_array_new (FALSE, FALSE, sizeof (struct _imapx_flag_change));
6031 uflags = uflags->next;
6033 if (off_user == NULL)
6034 off_user = g_array_new (FALSE, FALSE, sizeof (struct _imapx_flag_change));
6035 user_set = off_user;
6036 user_flag = suflags;
6037 suflags = suflags->next;
6040 /* Could sort this and binary search */
6041 for (j = 0; j < user_set->len; j++) {
6042 change = &g_array_index (user_set, struct _imapx_flag_change, j);
6043 if (strcmp (change->name, user_flag->name) == 0)
6046 add.name = g_strdup (user_flag->name);
6047 add.infos = g_ptr_array_new ();
6048 g_array_append_val (user_set, add);
6051 camel_message_info_ref (info);
6052 g_ptr_array_add (change->infos, info);
6055 camel_message_info_free (info);
6058 if ((on_orset | off_orset) == 0 && on_user == NULL && off_user == NULL) {
6059 imapx_sync_free_user (on_user);
6060 imapx_sync_free_user (off_user);
6061 camel_folder_free_uids (folder, uids);
6066 /* TODO above code should go into changes_start */
6070 if ((job = imapx_is_job_in_queue (is, folder, IMAPX_JOB_SYNC_CHANGES, NULL))) {
6076 imapx_sync_free_user (on_user);
6077 imapx_sync_free_user (off_user);
6078 camel_folder_free_uids (folder, uids);
6083 data = g_slice_new0 (SyncChangesData);
6084 data->folder = g_object_ref (folder);
6085 data->changed_uids = uids; /* takes ownership */
6086 data->on_set = on_orset;
6087 data->off_set = off_orset;
6088 data->on_user = on_user; /* takes ownership */
6089 data->off_user = off_user; /* takes ownership */
6091 job = camel_imapx_job_new (cancellable);
6092 job->type = IMAPX_JOB_SYNC_CHANGES;
6093 job->start = imapx_job_sync_changes_start;
6094 job->matches = imapx_job_sync_changes_matches;
6096 job->folder = folder;
6098 camel_imapx_job_set_data (
6099 job, data, (GDestroyNotify) sync_changes_data_free);
6101 registered = imapx_register_job (is, job, error);
6105 success = registered && camel_imapx_job_run (job, is, error);
6107 camel_imapx_job_unref (job);
6113 camel_imapx_server_sync_changes (CamelIMAPXServer *is,
6114 CamelFolder *folder,
6115 GCancellable *cancellable,
6118 return imapx_server_sync_changes (
6119 is, folder, IMAPX_PRIORITY_SYNC_CHANGES,
6120 cancellable, error);
6125 camel_imapx_server_expunge (CamelIMAPXServer *is,
6126 CamelFolder *folder,
6127 GCancellable *cancellable,
6131 gboolean registered;
6134 /* Do we really care to wait for this one to finish? */
6137 if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_EXPUNGE, NULL)) {
6142 job = camel_imapx_job_new (cancellable);
6143 job->type = IMAPX_JOB_EXPUNGE;
6144 job->start = imapx_job_expunge_start;
6145 job->matches = imapx_job_expunge_matches;
6146 job->pri = IMAPX_PRIORITY_EXPUNGE;
6147 job->folder = folder;
6149 registered = imapx_register_job (is, job, error);
6153 success = registered && camel_imapx_job_run (job, is, error);
6155 camel_imapx_job_unref (job);
6161 imapx_name_hash (gconstpointer key)
6163 if (g_ascii_strcasecmp(key, "INBOX") == 0)
6164 return g_str_hash("INBOX");
6166 return g_str_hash (key);
6170 imapx_name_equal (gconstpointer a,
6173 gconstpointer aname = a, bname = b;
6175 if (g_ascii_strcasecmp(a, "INBOX") == 0)
6177 if (g_ascii_strcasecmp(b, "INBOX") == 0)
6179 return g_str_equal (aname, bname);
6183 imapx_list_flatten (gpointer k,
6187 GPtrArray *folders = d;
6189 g_ptr_array_add (folders, v);
6193 imapx_list_cmp (gconstpointer ap,
6196 struct _list_info *a = ((struct _list_info **) ap)[0];
6197 struct _list_info *b = ((struct _list_info **) bp)[0];
6199 return strcmp (a->name, b->name);
6203 camel_imapx_server_list (CamelIMAPXServer *is,
6207 GCancellable *cancellable,
6211 GPtrArray *folders = NULL;
6213 gchar *encoded_name;
6215 encoded_name = camel_utf8_utf7 (top);
6217 data = g_slice_new0 (ListData);
6218 data->flags = flags;
6219 data->ext = g_strdup (ext);
6220 data->folders = g_hash_table_new (imapx_name_hash, imapx_name_equal);
6222 if (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)
6223 data->pattern = g_strdup_printf ("%s*", encoded_name);
6225 data->pattern = g_strdup (encoded_name);
6227 job = camel_imapx_job_new (cancellable);
6228 job->type = IMAPX_JOB_LIST;
6229 job->start = imapx_job_list_start;
6230 job->matches = imapx_job_list_matches;
6231 job->pri = IMAPX_PRIORITY_LIST;
6233 camel_imapx_job_set_data (
6234 job, data, (GDestroyNotify) list_data_free);
6236 /* sync operation which is triggered by user */
6237 if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST)
6240 if (imapx_submit_job (is, job, error)) {
6241 folders = g_ptr_array_new ();
6242 g_hash_table_foreach (data->folders, imapx_list_flatten, folders);
6243 qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), imapx_list_cmp);
6246 g_free (encoded_name);
6247 camel_imapx_job_unref (job);
6253 camel_imapx_server_manage_subscription (CamelIMAPXServer *is,
6254 const gchar *folder_name,
6256 GCancellable *cancellable,
6260 ManageSubscriptionsData *data;
6263 data = g_slice_new0 (ManageSubscriptionsData);
6264 data->folder_name = g_strdup (folder_name);
6265 data->subscribe = subscribe;
6267 job = camel_imapx_job_new (cancellable);
6268 job->type = IMAPX_JOB_MANAGE_SUBSCRIPTION;
6269 job->start = imapx_job_manage_subscription_start;
6270 job->pri = IMAPX_PRIORITY_MANAGE_SUBSCRIPTION;
6272 camel_imapx_job_set_data (
6273 job, data, (GDestroyNotify) manage_subscriptions_data_free);
6275 success = imapx_submit_job (is, job, error);
6277 camel_imapx_job_unref (job);
6283 camel_imapx_server_create_folder (CamelIMAPXServer *is,
6284 const gchar *folder_name,
6285 GCancellable *cancellable,
6289 CreateFolderData *data;
6292 data = g_slice_new0 (CreateFolderData);
6293 data->folder_name = g_strdup (folder_name);
6295 job = camel_imapx_job_new (cancellable);
6296 job->type = IMAPX_JOB_CREATE_FOLDER;
6297 job->start = imapx_job_create_folder_start;
6298 job->pri = IMAPX_PRIORITY_CREATE_FOLDER;
6300 camel_imapx_job_set_data (
6301 job, data, (GDestroyNotify) create_folder_data_free);
6303 success = imapx_submit_job (is, job, error);
6305 camel_imapx_job_unref (job);
6311 camel_imapx_server_delete_folder (CamelIMAPXServer *is,
6312 const gchar *folder_name,
6313 GCancellable *cancellable,
6317 DeleteFolderData *data;
6320 data = g_slice_new0 (DeleteFolderData);
6321 data->folder_name = g_strdup (folder_name);
6323 job = camel_imapx_job_new (cancellable);
6324 job->type = IMAPX_JOB_DELETE_FOLDER;
6325 job->start = imapx_job_delete_folder_start;
6326 job->pri = IMAPX_PRIORITY_DELETE_FOLDER;
6328 camel_imapx_job_set_data (
6329 job, data, (GDestroyNotify) delete_folder_data_free);
6331 success = imapx_submit_job (is, job, error);
6333 camel_imapx_job_unref (job);
6339 imapx_job_fetch_messages_matches (CamelIMAPXJob *job,
6340 CamelFolder *folder,
6343 return (folder == job->folder);
6347 camel_imapx_server_fetch_messages (CamelIMAPXServer *is,
6348 CamelFolder *folder,
6349 CamelFetchType type,
6351 GCancellable *cancellable,
6355 RefreshInfoData *data;
6356 gboolean registered = TRUE;
6357 const gchar *full_name;
6358 gboolean success = TRUE;
6359 guint64 firstuid, newfirstuid;
6363 old_len = camel_folder_summary_count (folder->summary);
6364 uid = imapx_get_uid_from_index (folder->summary, 0);
6365 firstuid = strtoull (uid, NULL, 10);
6370 /* Both RefreshInfo and Fetch messages can't operate simultaneously */
6371 if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_REFRESH_INFO, NULL) ||
6372 imapx_is_job_in_queue (is, folder, IMAPX_JOB_FETCH_MESSAGES, NULL)) {
6377 data = g_slice_new0 (RefreshInfoData);
6378 data->changes = camel_folder_change_info_new ();
6379 data->fetch_msg_limit = limit;
6380 data->fetch_type = type;
6382 job = camel_imapx_job_new (cancellable);
6383 job->type = IMAPX_JOB_FETCH_MESSAGES;
6384 job->start = imapx_job_fetch_messages_start;
6385 job->matches = imapx_job_fetch_messages_matches;
6386 job->folder = folder;
6387 job->pri = IMAPX_PRIORITY_NEW_MESSAGES;
6389 full_name = camel_folder_get_full_name (folder);
6391 if (g_ascii_strcasecmp(full_name, "INBOX") == 0)
6394 camel_imapx_job_set_data (
6395 job, data, (GDestroyNotify) refresh_info_data_free);
6397 registered = imapx_register_job (is, job, error);
6401 success = registered && camel_imapx_job_run (job, is, error);
6403 if (success && camel_folder_change_info_changed (data->changes) && camel_folder_change_info_changed (data->changes))
6404 camel_folder_changed (folder, data->changes);
6406 uid = imapx_get_uid_from_index (folder->summary, 0);
6407 newfirstuid = strtoull (uid, NULL, 10);
6410 camel_imapx_job_unref (job);
6412 if (type == CAMEL_FETCH_OLD_MESSAGES && firstuid == newfirstuid)
6413 return FALSE; /* No more old messages */
6414 else if (type == CAMEL_FETCH_NEW_MESSAGES &&
6415 old_len == camel_folder_summary_count (folder->summary))
6416 return FALSE; /* No more new messages */
6422 camel_imapx_server_rename_folder (CamelIMAPXServer *is,
6423 const gchar *old_name,
6424 const gchar *new_name,
6425 GCancellable *cancellable,
6429 RenameFolderData *data;
6432 data = g_slice_new0 (RenameFolderData);
6433 data->old_folder_name = g_strdup (old_name);
6434 data->new_folder_name = g_strdup (new_name);
6436 job = camel_imapx_job_new (cancellable);
6437 job->type = IMAPX_JOB_RENAME_FOLDER;
6438 job->start = imapx_job_rename_folder_start;
6439 job->pri = IMAPX_PRIORITY_RENAME_FOLDER;
6441 camel_imapx_job_set_data (
6442 job, data, (GDestroyNotify) rename_folder_data_free);
6444 success = imapx_submit_job (is, job, error);
6446 camel_imapx_job_unref (job);
6452 camel_imapx_server_get_job_queue_info (CamelIMAPXServer *is)
6454 IMAPXJobQueueInfo *jinfo = g_new0 (IMAPXJobQueueInfo, 1);
6455 CamelIMAPXJob *job = NULL;
6460 jinfo->queue_len = g_queue_get_length (&is->jobs);
6461 jinfo->folders = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
6463 head = g_queue_peek_head_link (&is->jobs);
6465 for (link = head; link != NULL; link = g_list_next (link)) {
6466 job = (CamelIMAPXJob *) link->data;
6469 const gchar *full_name = camel_folder_get_full_name (job->folder);
6470 g_hash_table_insert (jinfo->folders, g_strdup (full_name), GINT_TO_POINTER (1));
6474 if (is->select_folder)
6475 g_hash_table_insert (jinfo->folders, g_strdup (camel_folder_get_full_name (is->select_folder)), GINT_TO_POINTER (1));