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 CAMEL_IMAPX_SERVER_GET_PRIVATE(obj) \
54 (G_TYPE_INSTANCE_GET_PRIVATE \
55 ((obj), CAMEL_TYPE_IMAPX_SERVER, CamelIMAPXServerPrivate))
57 #define c(...) camel_imapx_debug(command, __VA_ARGS__)
58 #define e(...) camel_imapx_debug(extra, __VA_ARGS__)
60 #define CIF(x) ((CamelIMAPXFolder *)x)
62 #define QUEUE_LOCK(x) (g_rec_mutex_lock(&(x)->queue_lock))
63 #define QUEUE_UNLOCK(x) (g_rec_mutex_unlock(&(x)->queue_lock))
65 #define IDLE_LOCK(x) (g_mutex_lock(&(x)->idle_lock))
66 #define IDLE_UNLOCK(x) (g_mutex_unlock(&(x)->idle_lock))
68 /* Try pipelining fetch requests, 'in bits' */
69 #define MULTI_SIZE (20480)
71 /* How many outstanding commands do we allow before we just queue them? */
72 #define MAX_COMMANDS (10)
74 #define MAX_COMMAND_LEN 1000
76 extern gint camel_application_is_exiting;
78 /* Job-specific structs */
79 typedef struct _GetMessageData GetMessageData;
80 typedef struct _RefreshInfoData RefreshInfoData;
81 typedef struct _SyncChangesData SyncChangesData;
82 typedef struct _AppendMessageData AppendMessageData;
83 typedef struct _CopyMessagesData CopyMessagesData;
84 typedef struct _ListData ListData;
85 typedef struct _ManageSubscriptionsData ManageSubscriptionsData;
86 typedef struct _RenameFolderData RenameFolderData;
87 typedef struct _CreateFolderData CreateFolderData;
88 typedef struct _DeleteFolderData DeleteFolderData;
89 typedef struct _QuotaData QuotaData;
90 typedef struct _SearchData SearchData;
92 struct _GetMessageData {
93 /* in: uid requested */
95 /* in/out: message content stream output */
97 /* working variables */
102 gboolean use_multi_fetch;
105 struct _RefreshInfoData {
106 /* array of refresh info's */
108 /* used for building uidset stuff */
111 gint fetch_msg_limit;
112 CamelFetchType fetch_type;
113 gboolean update_unseen;
114 gboolean scan_changes;
115 struct _uidset_state uidset;
116 /* changes during refresh */
117 CamelFolderChangeInfo *changes;
120 struct _SyncChangesData {
122 GPtrArray *changed_uids;
125 GArray *on_user; /* imapx_flag_change */
129 /* Remove recently set DELETED flags before synchronizing.
130 * This is only set when using a real Trash folder and NOT
131 * about to expunge the folder. */
132 gboolean remove_deleted_flags;
135 struct _AppendMessageData {
137 CamelMessageInfo *info;
141 struct _CopyMessagesData {
144 gboolean delete_originals;
147 struct _uidset_state uidset;
157 struct _ManageSubscriptionsData {
162 struct _RenameFolderData {
163 gchar *old_folder_name;
164 gchar *new_folder_name;
167 struct _CreateFolderData {
171 struct _DeleteFolderData {
184 /* untagged response handling */
186 /* May need to turn this into separate,
187 * subclassable GObject with proper getter/setter
188 * functions so derived implementations can
189 * supply their own context information.
190 * The context supplied here, however, should
191 * not be exposed outside CamelIMAPXServer.
192 * An instance is created in imapx_untagged()
193 * with a lifetime of one run of this function.
194 * In order to supply a derived context instance,
195 * we would need to register a derived _new()
196 * function for it which will be called inside
199 * TODO: rethink this construct.
201 typedef struct _CamelIMAPXServerUntaggedContext CamelIMAPXServerUntaggedContext;
203 struct _CamelIMAPXServerUntaggedContext {
204 CamelSortType fetch_order;
210 struct _status_info *sinfo;
213 /* internal untagged handler prototypes */
214 static gboolean imapx_untagged_bye (CamelIMAPXServer *is,
215 CamelIMAPXStream *stream,
216 GCancellable *cancellable,
218 static gboolean imapx_untagged_capability (CamelIMAPXServer *is,
219 CamelIMAPXStream *stream,
220 GCancellable *cancellable,
222 static gboolean imapx_untagged_exists (CamelIMAPXServer *is,
223 CamelIMAPXStream *stream,
224 GCancellable *cancellable,
226 static gboolean imapx_untagged_expunge (CamelIMAPXServer *is,
227 CamelIMAPXStream *stream,
228 GCancellable *cancellable,
230 static gboolean imapx_untagged_fetch (CamelIMAPXServer *is,
231 CamelIMAPXStream *stream,
232 GCancellable *cancellable,
234 static gboolean imapx_untagged_flags (CamelIMAPXServer *is,
235 CamelIMAPXStream *stream,
236 GCancellable *cancellable,
238 static gboolean imapx_untagged_list (CamelIMAPXServer *is,
239 CamelIMAPXStream *stream,
240 GCancellable *cancellable,
242 static gboolean imapx_untagged_lsub (CamelIMAPXServer *is,
243 CamelIMAPXStream *stream,
244 GCancellable *cancellable,
246 static gboolean imapx_untagged_namespace (CamelIMAPXServer *is,
247 CamelIMAPXStream *stream,
248 GCancellable *cancellable,
250 static gboolean imapx_untagged_ok_no_bad (CamelIMAPXServer *is,
251 CamelIMAPXStream *stream,
252 GCancellable *cancellable,
254 static gboolean imapx_untagged_preauth (CamelIMAPXServer *is,
255 CamelIMAPXStream *stream,
256 GCancellable *cancellable,
258 static gboolean imapx_untagged_quota (CamelIMAPXServer *is,
259 CamelIMAPXStream *stream,
260 GCancellable *cancellable,
262 static gboolean imapx_untagged_quotaroot (CamelIMAPXServer *is,
263 CamelIMAPXStream *stream,
264 GCancellable *cancellable,
266 static gboolean imapx_untagged_recent (CamelIMAPXServer *is,
267 CamelIMAPXStream *stream,
268 GCancellable *cancellable,
270 static gboolean imapx_untagged_search (CamelIMAPXServer *is,
271 CamelIMAPXStream *stream,
272 GCancellable *cancellable,
274 static gboolean imapx_untagged_status (CamelIMAPXServer *is,
275 CamelIMAPXStream *stream,
276 GCancellable *cancellable,
278 static gboolean imapx_untagged_vanished (CamelIMAPXServer *is,
279 CamelIMAPXStream *stream,
280 GCancellable *cancellable,
284 IMAPX_UNTAGGED_ID_BAD = 0,
285 IMAPX_UNTAGGED_ID_BYE,
286 IMAPX_UNTAGGED_ID_CAPABILITY,
287 IMAPX_UNTAGGED_ID_EXISTS,
288 IMAPX_UNTAGGED_ID_EXPUNGE,
289 IMAPX_UNTAGGED_ID_FETCH,
290 IMAPX_UNTAGGED_ID_FLAGS,
291 IMAPX_UNTAGGED_ID_LIST,
292 IMAPX_UNTAGGED_ID_LSUB,
293 IMAPX_UNTAGGED_ID_NAMESPACE,
294 IMAPX_UNTAGGED_ID_NO,
295 IMAPX_UNTAGGED_ID_OK,
296 IMAPX_UNTAGGED_ID_PREAUTH,
297 IMAPX_UNTAGGED_ID_QUOTA,
298 IMAPX_UNTAGGED_ID_QUOTAROOT,
299 IMAPX_UNTAGGED_ID_RECENT,
300 IMAPX_UNTAGGED_ID_SEARCH,
301 IMAPX_UNTAGGED_ID_STATUS,
302 IMAPX_UNTAGGED_ID_VANISHED,
303 IMAPX_UNTAGGED_LAST_ID
306 static const CamelIMAPXUntaggedRespHandlerDesc _untagged_descr[] = {
307 {CAMEL_IMAPX_UNTAGGED_BAD, imapx_untagged_ok_no_bad, NULL, FALSE},
308 {CAMEL_IMAPX_UNTAGGED_BYE, imapx_untagged_bye, NULL, FALSE},
309 {CAMEL_IMAPX_UNTAGGED_CAPABILITY, imapx_untagged_capability, NULL, FALSE},
310 {CAMEL_IMAPX_UNTAGGED_EXISTS, imapx_untagged_exists, NULL, TRUE},
311 {CAMEL_IMAPX_UNTAGGED_EXPUNGE, imapx_untagged_expunge, NULL, TRUE},
312 {CAMEL_IMAPX_UNTAGGED_FETCH, imapx_untagged_fetch, NULL, TRUE},
313 {CAMEL_IMAPX_UNTAGGED_FLAGS, imapx_untagged_flags, NULL, TRUE},
314 {CAMEL_IMAPX_UNTAGGED_LIST, imapx_untagged_list, NULL, TRUE},
315 {CAMEL_IMAPX_UNTAGGED_LSUB, imapx_untagged_lsub, CAMEL_IMAPX_UNTAGGED_LIST, TRUE /*overridden */ },
316 {CAMEL_IMAPX_UNTAGGED_NAMESPACE, imapx_untagged_namespace, NULL, FALSE},
317 {CAMEL_IMAPX_UNTAGGED_NO, imapx_untagged_ok_no_bad, NULL, FALSE},
318 {CAMEL_IMAPX_UNTAGGED_OK, imapx_untagged_ok_no_bad, NULL, FALSE},
319 {CAMEL_IMAPX_UNTAGGED_PREAUTH, imapx_untagged_preauth, CAMEL_IMAPX_UNTAGGED_OK, TRUE /*overridden */ },
320 {CAMEL_IMAPX_UNTAGGED_QUOTA, imapx_untagged_quota, NULL, FALSE},
321 {CAMEL_IMAPX_UNTAGGED_QUOTAROOT, imapx_untagged_quotaroot, NULL, FALSE},
322 {CAMEL_IMAPX_UNTAGGED_RECENT, imapx_untagged_recent, NULL, TRUE},
323 {CAMEL_IMAPX_UNTAGGED_SEARCH, imapx_untagged_search, NULL, FALSE},
324 {CAMEL_IMAPX_UNTAGGED_STATUS, imapx_untagged_status, NULL, TRUE},
325 {CAMEL_IMAPX_UNTAGGED_VANISHED, imapx_untagged_vanished, NULL, TRUE},
328 struct _CamelIMAPXServerPrivate {
331 CamelIMAPXServerUntaggedContext *context;
332 GHashTable *untagged_handlers;
334 CamelIMAPXStream *stream;
337 /* Untagged SEARCH data gets deposited here.
338 * The search command should claim the results
339 * when finished and reset the pointer to NULL. */
340 GArray *search_results;
341 GMutex search_results_lock;
356 static guint signals[LAST_SIGNAL];
358 static void imapx_uidset_init (struct _uidset_state *ss,
361 static gint imapx_uidset_done (struct _uidset_state *ss,
362 CamelIMAPXCommand *ic);
363 static gint imapx_uidset_add (struct _uidset_state *ss,
364 CamelIMAPXCommand *ic,
367 static gboolean imapx_command_idle_stop (CamelIMAPXServer *is,
368 CamelIMAPXStream *stream,
369 GCancellable *cancellable,
371 static gboolean imapx_continuation (CamelIMAPXServer *is,
372 CamelIMAPXStream *stream,
374 GCancellable *cancellable,
376 static gboolean imapx_disconnect (CamelIMAPXServer *is);
377 static gboolean imapx_is_command_queue_empty (CamelIMAPXServer *is);
378 static gint imapx_uid_cmp (gconstpointer ap,
382 /* states for the connection? */
392 struct _refresh_info {
395 guint32 server_flags;
396 CamelFlag *server_user_flags;
400 IMAPX_JOB_GET_MESSAGE = 1 << 0,
401 IMAPX_JOB_APPEND_MESSAGE = 1 << 1,
402 IMAPX_JOB_COPY_MESSAGE = 1 << 2,
403 IMAPX_JOB_FETCH_NEW_MESSAGES = 1 << 3,
404 IMAPX_JOB_REFRESH_INFO = 1 << 4,
405 IMAPX_JOB_SYNC_CHANGES = 1 << 5,
406 IMAPX_JOB_EXPUNGE = 1 << 6,
407 IMAPX_JOB_NOOP = 1 << 7,
408 IMAPX_JOB_IDLE = 1 << 8,
409 IMAPX_JOB_LIST = 1 << 9,
410 IMAPX_JOB_MANAGE_SUBSCRIPTION = 1 << 10,
411 IMAPX_JOB_CREATE_FOLDER = 1 << 11,
412 IMAPX_JOB_DELETE_FOLDER = 1 << 12,
413 IMAPX_JOB_RENAME_FOLDER = 1 << 13,
414 IMAPX_JOB_FETCH_MESSAGES = 1 << 14,
415 IMAPX_JOB_UPDATE_QUOTA_INFO = 1 << 15,
416 IMAPX_JOB_UID_SEARCH = 1 << 16
419 /* Operations on the store (folder_tree) will have highest priority as we know for sure they are sync
420 * and user triggered. */
422 IMAPX_PRIORITY_CREATE_FOLDER = 200,
423 IMAPX_PRIORITY_DELETE_FOLDER = 200,
424 IMAPX_PRIORITY_RENAME_FOLDER = 200,
425 IMAPX_PRIORITY_MANAGE_SUBSCRIPTION = 200,
426 IMAPX_PRIORITY_SYNC_CHANGES = 150,
427 IMAPX_PRIORITY_EXPUNGE = 150,
428 IMAPX_PRIORITY_SEARCH = 150,
429 IMAPX_PRIORITY_GET_MESSAGE = 100,
430 IMAPX_PRIORITY_REFRESH_INFO = 0,
431 IMAPX_PRIORITY_NOOP = 0,
432 IMAPX_PRIORITY_NEW_MESSAGES = 0,
433 IMAPX_PRIORITY_APPEND_MESSAGE = -60,
434 IMAPX_PRIIORITY_COPY_MESSAGE = -60,
435 IMAPX_PRIORITY_LIST = -80,
436 IMAPX_PRIORITY_IDLE = -100,
437 IMAPX_PRIORITY_SYNC_MESSAGE = -120,
438 IMAPX_PRIORITY_UPDATE_QUOTA_INFO = -80
441 struct _imapx_flag_change {
446 static CamelIMAPXJob *
447 imapx_match_active_job (CamelIMAPXServer *is,
450 static gboolean imapx_job_fetch_new_messages_start
452 CamelIMAPXServer *is,
453 GCancellable *cancellable,
455 static gint imapx_refresh_info_uid_cmp (gconstpointer ap,
458 static gint imapx_uids_array_cmp (gconstpointer ap,
460 static gboolean imapx_server_sync_changes (CamelIMAPXServer *is,
464 GCancellable *cancellable,
466 static void imapx_sync_free_user (GArray *user_set);
468 static gboolean imapx_command_copy_messages_step_start
469 (CamelIMAPXServer *is,
472 GCancellable *cancellable,
477 IMAPX_IDLE_PENDING, /* Queue is idle; waiting to send IDLE command
478 soon if nothing more interesting happens */
479 IMAPX_IDLE_ISSUED, /* Sent IDLE command; waiting for response */
480 IMAPX_IDLE_STARTED, /* IDLE continuation received; IDLE active */
481 IMAPX_IDLE_CANCEL, /* Cancelled from ISSUED state; need to send
482 DONE as soon as we receive continuation */
484 #define IMAPX_IDLE_DWELL_TIME 2 /* Number of seconds to remain in PENDING
485 state waiting for other commands to be
486 queued, before actually sending IDLE */
488 struct _CamelIMAPXIdle {
490 GThread *idle_thread;
492 GCond start_watch_cond;
493 GMutex start_watch_mutex;
494 gboolean start_watch_is_set;
497 enum _idle_state state;
502 IMAPX_IDLE_STOP_NOOP,
503 IMAPX_IDLE_STOP_SUCCESS,
504 IMAPX_IDLE_STOP_ERROR
505 } CamelIMAPXIdleStopResult;
507 static gboolean imapx_in_idle (CamelIMAPXServer *is);
508 static gboolean imapx_idle_supported (CamelIMAPXServer *is);
509 static void imapx_start_idle (CamelIMAPXServer *is);
510 static void imapx_exit_idle (CamelIMAPXServer *is);
511 static void imapx_init_idle (CamelIMAPXServer *is);
512 static CamelIMAPXIdleStopResult
513 imapx_stop_idle (CamelIMAPXServer *is,
514 CamelIMAPXStream *stream,
515 GCancellable *cancellable,
517 static gboolean camel_imapx_server_idle (CamelIMAPXServer *is,
519 GCancellable *cancellable,
525 USE_SSL_WHEN_POSSIBLE
528 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
529 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
531 static gboolean imapx_select (CamelIMAPXServer *is,
534 GCancellable *cancellable,
537 G_DEFINE_TYPE (CamelIMAPXServer, camel_imapx_server, CAMEL_TYPE_OBJECT)
539 static const CamelIMAPXUntaggedRespHandlerDesc *
540 replace_untagged_descriptor (GHashTable *untagged_handlers,
542 const CamelIMAPXUntaggedRespHandlerDesc *descr)
544 const CamelIMAPXUntaggedRespHandlerDesc *prev = NULL;
546 g_return_val_if_fail (untagged_handlers != NULL, NULL);
547 g_return_val_if_fail (key != NULL, NULL);
548 /* descr may be NULL (to delete a handler) */
550 prev = g_hash_table_lookup (untagged_handlers, key);
551 g_hash_table_replace (
559 add_initial_untagged_descriptor (GHashTable *untagged_handlers,
562 const CamelIMAPXUntaggedRespHandlerDesc *prev = NULL;
563 const CamelIMAPXUntaggedRespHandlerDesc *cur = NULL;
565 g_return_if_fail (untagged_handlers != NULL);
566 g_return_if_fail (untagged_id < IMAPX_UNTAGGED_LAST_ID);
568 cur = &(_untagged_descr[untagged_id]);
569 prev = replace_untagged_descriptor (
571 cur->untagged_response,
573 /* there must not be any previous handler here */
574 g_return_if_fail (prev == NULL);
578 create_initial_untagged_handler_table (void)
580 GHashTable *uh = g_hash_table_new_full (
587 /* CamelIMAPXServer predefined handlers*/
588 for (ii = 0; ii < IMAPX_UNTAGGED_LAST_ID; ii++)
589 add_initial_untagged_descriptor (uh, ii);
591 g_return_val_if_fail (g_hash_table_size (uh) == IMAPX_UNTAGGED_LAST_ID, NULL);
597 get_message_data_free (GetMessageData *data)
601 if (data->stream != NULL)
602 g_object_unref (data->stream);
604 g_slice_free (GetMessageData, data);
608 refresh_info_data_infos_free (RefreshInfoData *data)
612 if (!data || !data->infos)
615 for (ii = 0; ii < data->infos->len; ii++) {
616 struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, ii);
618 camel_flag_list_free (&r->server_user_flags);
622 g_array_free (data->infos, TRUE);
627 refresh_info_data_free (RefreshInfoData *data)
629 camel_folder_change_info_free (data->changes);
630 refresh_info_data_infos_free (data);
632 g_slice_free (RefreshInfoData, data);
636 sync_changes_data_free (SyncChangesData *data)
638 if (data->folder != NULL) {
639 camel_folder_free_uids (data->folder, data->changed_uids);
640 g_object_unref (data->folder);
643 imapx_sync_free_user (data->on_user);
644 imapx_sync_free_user (data->off_user);
646 g_slice_free (SyncChangesData, data);
650 append_message_data_free (AppendMessageData *data)
653 g_free (data->appended_uid);
655 camel_message_info_free (data->info);
657 g_slice_free (AppendMessageData, data);
661 copy_messages_data_free (CopyMessagesData *data)
663 if (data->dest != NULL)
664 g_object_unref (data->dest);
666 if (data->uids != NULL) {
667 g_ptr_array_foreach (data->uids, (GFunc) g_free, NULL);
668 g_ptr_array_free (data->uids, TRUE);
671 g_slice_free (CopyMessagesData, data);
675 list_data_free (ListData *data)
677 g_free (data->pattern);
680 g_hash_table_destroy (data->folders);
682 g_slice_free (ListData, data);
686 manage_subscriptions_data_free (ManageSubscriptionsData *data)
688 g_free (data->folder_name);
690 g_slice_free (ManageSubscriptionsData, data);
694 rename_folder_data_free (RenameFolderData *data)
696 g_free (data->old_folder_name);
697 g_free (data->new_folder_name);
699 g_slice_free (RenameFolderData, data);
703 create_folder_data_free (CreateFolderData *data)
705 g_free (data->folder_name);
707 g_slice_free (CreateFolderData, data);
711 delete_folder_data_free (DeleteFolderData *data)
713 g_free (data->folder_name);
715 g_slice_free (DeleteFolderData, data);
719 search_data_free (SearchData *data)
721 g_free (data->criteria);
723 if (data->results != NULL)
724 g_array_unref (data->results);
726 g_slice_free (SearchData, data);
730 quota_data_free (QuotaData *data)
732 g_free (data->folder_name);
734 g_slice_free (QuotaData, data);
738 this creates a uid (or sequence number) set directly into a command,
739 if total is set, then we break it up into total uids. (i.e. command time)
740 if limit is set, then we break it up into limit entries (i.e. command length)
743 imapx_uidset_init (struct _uidset_state *ss,
756 imapx_uidset_done (struct _uidset_state *ss,
757 CamelIMAPXCommand *ic)
761 if (ss->last != 0 && ss->last != ss->start) {
762 camel_imapx_command_add (ic, ":%d", ss->last);
776 imapx_uidset_add (struct _uidset_state *ss,
777 CamelIMAPXCommand *ic,
782 uidn = strtoul (uid, NULL, 10);
788 e (ic->is->tagprefix, "uidset add '%s'\n", uid);
791 e (ic->is->tagprefix, " start\n");
792 camel_imapx_command_add (ic, "%d", uidn);
796 if (ss->last != uidn - 1) {
797 if (ss->last == ss->start) {
798 e (ic->is->tagprefix, " ,next\n");
799 camel_imapx_command_add (ic, ",%d", uidn);
802 e (ic->is->tagprefix, " :range\n");
803 camel_imapx_command_add (ic, ":%d,%d", ss->last, uidn);
812 if ((ss->limit && ss->entries >= ss->limit)
813 || (ss->total && ss->uids >= ss->total)) {
814 e (ic->is->tagprefix, " done, %d entries, %d uids\n", ss->entries, ss->uids);
815 if (!imapx_uidset_done (ss, ic))
823 /* Must hold QUEUE_LOCK */
825 imapx_command_start (CamelIMAPXServer *is,
826 CamelIMAPXCommand *ic,
827 GCancellable *cancellable,
830 CamelIMAPXStream *stream = NULL;
831 CamelIMAPXCommandPart *cp;
832 gboolean cp_continuation;
833 gboolean cp_literal_plus;
835 gboolean success = FALSE;
839 camel_imapx_command_close (ic);
841 head = g_queue_peek_head_link (&ic->parts);
842 g_return_val_if_fail (head != NULL, FALSE);
843 cp = (CamelIMAPXCommandPart *) head->data;
844 ic->current_part = head;
846 cp_continuation = ((cp->type & CAMEL_IMAPX_COMMAND_CONTINUATION) != 0);
847 cp_literal_plus = ((cp->type & CAMEL_IMAPX_COMMAND_LITERAL_PLUS) != 0);
849 /* TODO: If we support literal+ we should be able to write the whole command out
850 * at this point .... >here< */
852 if (cp_continuation || cp_literal_plus)
855 camel_imapx_command_queue_push_tail (is->active, ic);
857 stream = camel_imapx_server_ref_stream (is);
859 if (stream == NULL) {
861 error, CAMEL_IMAPX_ERROR, 1,
862 "Cannot issue command, no stream available");
868 "Starting command (active=%d,%s) %c%05u %s\r\n",
869 camel_imapx_command_queue_get_length (is->active),
870 is->literal ? " literal" : "",
873 cp->data && g_str_has_prefix (cp->data, "LOGIN") ?
874 "LOGIN..." : cp->data);
876 string = g_strdup_printf (
877 "%c%05u %s\r\n", is->tagprefix, ic->tag, cp->data);
878 retval = camel_stream_write_string (
879 CAMEL_STREAM (stream), string, cancellable, error);
885 while (is->literal == ic && cp_literal_plus) {
886 /* Sent LITERAL+ continuation immediately */
887 if (!imapx_continuation (is, stream, TRUE, cancellable, error))
896 camel_imapx_command_queue_remove (is->active, ic);
898 /* HACK: Since we're failing, make sure the command has a status
899 * structure and the result code indicates failure, so the
900 * ic->complete() callback does not start a new command. */
901 if (ic->status == NULL)
902 ic->status = g_malloc0 (sizeof (struct _status_info));
903 if (ic->status->result == IMAPX_OK)
904 ic->status->result = IMAPX_UNKNOWN;
906 /* Send a NULL GError since we've already set a
907 * GError to get here, and we're not interested
908 * in individual command errors. */
909 if (ic != NULL && ic->complete != NULL)
910 ic->complete (is, ic, NULL, NULL);
914 g_object_unref (stream);
920 duplicate_fetch_or_refresh (CamelIMAPXServer *is,
921 CamelIMAPXCommand *ic)
925 job = camel_imapx_command_get_job (ic);
930 if (!(job->type & (IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES)))
933 if (imapx_match_active_job (is, IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO | IMAPX_JOB_FETCH_MESSAGES, NULL)) {
934 c (is->tagprefix, "Not yet sending duplicate fetch/refresh %s command\n", ic->name);
941 /* See if we can start another task yet.
943 * If we're waiting for a literal, we cannot proceed.
945 * If we're about to change the folder we're
946 * looking at from user-direction, we dont proceed.
948 * If we have a folder selected, first see if any
949 * jobs are waiting on it, but only if they are
950 * at least as high priority as anything we
953 * If we dont, select the first folder required,
954 * then queue all the outstanding jobs on it, that
955 * are at least as high priority as the first.
957 * must have QUEUE lock */
960 imapx_command_start_next (CamelIMAPXServer *is,
961 GCancellable *cancellable,
964 CamelIMAPXCommand *first_ic;
967 gboolean success = TRUE;
969 c (is->tagprefix, "** Starting next command\n");
971 c (is->tagprefix, "* no; waiting for literal '%s'\n", is->literal->name);
975 folder = g_weak_ref_get (&is->select_pending);
976 if (folder != NULL) {
977 GQueue start = G_QUEUE_INIT;
980 c (is->tagprefix, "-- Checking job queue for non-folder jobs\n");
982 head = camel_imapx_command_queue_peek_head_link (is->queue);
984 /* Tag which commands in the queue to start. */
985 for (link = head; link != NULL; link = g_list_next (link)) {
986 CamelIMAPXCommand *ic = link->data;
988 if (ic->pri < min_pri)
991 c (is->tagprefix, "-- %3d '%s'?\n", (gint) ic->pri, ic->name);
993 c (is->tagprefix, "--> starting '%s'\n", ic->name);
995 g_queue_push_tail (&start, link);
998 if (g_queue_get_length (&start) == MAX_COMMANDS)
1002 if (g_queue_is_empty (&start))
1003 c (is->tagprefix, "* no, waiting for pending select '%s'\n", camel_folder_get_full_name (folder));
1005 /* Start the tagged commands.
1007 * Each command must be removed from 'is->queue' before
1008 * starting it, so we temporarily reference the command
1009 * to avoid accidentally finalizing it. */
1010 while ((link = g_queue_pop_head (&start)) != NULL) {
1011 CamelIMAPXCommand *ic;
1013 ic = camel_imapx_command_ref (link->data);
1014 camel_imapx_command_queue_delete_link (is->queue, link);
1016 success = imapx_command_start (
1017 is, ic, cancellable, error);
1019 camel_imapx_command_unref (ic);
1022 g_queue_clear (&start);
1027 g_clear_object (&folder);
1032 if (imapx_idle_supported (is) && is->state == IMAPX_SELECTED) {
1033 gboolean empty = imapx_is_command_queue_empty (is);
1035 if (imapx_in_idle (is) && !camel_imapx_command_queue_is_empty (is->queue)) {
1036 CamelIMAPXIdleStopResult stop_result;
1037 CamelIMAPXStream *stream;
1039 stop_result = IMAPX_IDLE_STOP_NOOP;
1040 stream = camel_imapx_server_ref_stream (is);
1042 if (stream != NULL) {
1043 stop_result = imapx_stop_idle (
1044 is, stream, cancellable, error);
1045 g_object_unref (stream);
1048 switch (stop_result) {
1049 /* Proceed with the next queued command. */
1050 case IMAPX_IDLE_STOP_NOOP:
1053 case IMAPX_IDLE_STOP_SUCCESS:
1056 "waiting for idle to stop \n");
1057 /* if there are more pending commands,
1058 * then they should be processed too */
1061 case IMAPX_IDLE_STOP_ERROR:
1065 } else if (empty && !imapx_in_idle (is)) {
1066 imapx_start_idle (is);
1067 c (is->tagprefix, "starting idle \n");
1072 if (camel_imapx_command_queue_is_empty (is->queue)) {
1073 c (is->tagprefix, "* no, no jobs\n");
1077 /* See if any queued jobs on this select first */
1078 folder = g_weak_ref_get (&is->select_folder);
1079 if (folder != NULL) {
1080 GQueue start = G_QUEUE_INIT;
1082 gboolean commands_started = FALSE;
1085 is->tagprefix, "- we're selected on '%s', current jobs?\n",
1086 camel_folder_get_full_name (folder));
1088 head = camel_imapx_command_queue_peek_head_link (is->active);
1090 /* Find the highest priority in the active queue. */
1091 for (link = head; link != NULL; link = g_list_next (link)) {
1092 CamelIMAPXCommand *ic = link->data;
1094 min_pri = MAX (min_pri, ic->pri);
1095 c (is->tagprefix, "- %3d '%s'\n", (gint) ic->pri, ic->name);
1098 if (camel_imapx_command_queue_get_length (is->active) >= MAX_COMMANDS) {
1099 c (is->tagprefix, "** too many jobs busy, waiting for results for now\n");
1100 g_object_unref (folder);
1104 c (is->tagprefix, "-- Checking job queue\n");
1106 head = camel_imapx_command_queue_peek_head_link (is->queue);
1108 /* Tag which commands in the queue to start. */
1109 for (link = head; link != NULL; link = g_list_next (link)) {
1110 CamelIMAPXCommand *ic = link->data;
1112 if (is->literal != NULL)
1115 if (ic->pri < min_pri)
1118 c (is->tagprefix, "-- %3d '%s'?\n", (gint) ic->pri, ic->name);
1120 if (!ic->select || ((ic->select == folder) &&
1121 !duplicate_fetch_or_refresh (is, ic))) {
1122 c (is->tagprefix, "--> starting '%s'\n", ic->name);
1124 g_queue_push_tail (&start, link);
1126 /* This job isn't for the selected folder, but we don't want to
1127 * consider jobs with _lower_ priority than this, even if they
1128 * are for the selected folder. */
1132 if (g_queue_get_length (&start) == MAX_COMMANDS)
1136 g_clear_object (&folder);
1138 /* Start the tagged commands.
1140 * Each command must be removed from 'is->queue' before
1141 * starting it, so we temporarily reference the command
1142 * to avoid accidentally finalizing it. */
1143 while ((link = g_queue_pop_head (&start)) != NULL) {
1144 CamelIMAPXCommand *ic;
1147 ic = camel_imapx_command_ref (link->data);
1148 camel_imapx_command_queue_delete_link (is->queue, link);
1150 success = imapx_command_start (
1151 is, ic, cancellable, error);
1153 camel_imapx_command_unref (ic);
1156 g_queue_clear (&start);
1160 commands_started = TRUE;
1163 if (commands_started)
1167 /* This won't be NULL because we checked for an empty queue above. */
1168 first_ic = camel_imapx_command_queue_peek_head (is->queue);
1170 /* If we need to select a folder for the first command, do it now,
1171 * once it is complete it will re-call us if it succeeded. */
1172 if (first_ic->select) {
1174 is->tagprefix, "Selecting folder '%s' for command '%s'(%p)\n",
1175 camel_folder_get_full_name (first_ic->select),
1176 first_ic->name, first_ic);
1177 imapx_select (is, first_ic->select, FALSE, cancellable, error);
1179 GQueue start = G_QUEUE_INIT;
1182 min_pri = first_ic->pri;
1184 folder = g_weak_ref_get (&is->select_folder);
1186 head = camel_imapx_command_queue_peek_head_link (is->queue);
1188 /* Tag which commands in the queue to start. */
1189 for (link = head; link != NULL; link = g_list_next (link)) {
1190 CamelIMAPXCommand *ic = link->data;
1192 if (is->literal != NULL)
1195 if (ic->pri < min_pri)
1198 if (!ic->select || (ic->select == folder &&
1199 !duplicate_fetch_or_refresh (is, ic))) {
1200 c (is->tagprefix, "* queueing job %3d '%s'\n", (gint) ic->pri, ic->name);
1202 g_queue_push_tail (&start, link);
1205 if (g_queue_get_length (&start) == MAX_COMMANDS)
1209 g_clear_object (&folder);
1211 /* Start the tagged commands.
1213 * Each command must be removed from 'is->queue' before
1214 * starting it, so we temporarily reference the command
1215 * to avoid accidentally finalizing it. */
1216 while ((link = g_queue_pop_head (&start)) != NULL) {
1217 CamelIMAPXCommand *ic;
1220 ic = camel_imapx_command_ref (link->data);
1221 camel_imapx_command_queue_delete_link (is->queue, link);
1223 success = imapx_command_start (
1224 is, ic, cancellable, error);
1226 camel_imapx_command_unref (ic);
1229 g_queue_clear (&start);
1239 imapx_is_command_queue_empty (CamelIMAPXServer *is)
1241 if (!camel_imapx_command_queue_is_empty (is->queue))
1244 if (!camel_imapx_command_queue_is_empty (is->active))
1251 imapx_command_queue (CamelIMAPXServer *is,
1252 CamelIMAPXCommand *ic,
1253 GCancellable *cancellable,
1259 /* We enqueue in priority order, new messages have
1260 * higher priority than older messages with the same priority */
1262 job = camel_imapx_command_get_job (ic);
1263 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
1265 camel_imapx_command_close (ic);
1269 "enqueue job '%.*s'\n",
1270 ((CamelIMAPXCommandPart *) ic->parts.head->data)->data_size,
1271 ((CamelIMAPXCommandPart *) ic->parts.head->data)->data);
1275 if (is->state == IMAPX_SHUTDOWN) {
1276 c (is->tagprefix, "refuse to queue job on disconnected server\n");
1278 error, CAMEL_IMAPX_ERROR, 1,
1279 "%s", _("Server disconnected"));
1283 /* Send a NULL GError since we've already set the
1284 * GError, and we're not interested in individual
1285 * command errors at this point. */
1286 if (ic->complete != NULL)
1287 ic->complete (is, ic, NULL, NULL);
1292 camel_imapx_command_queue_insert_sorted (is->queue, ic);
1294 success = imapx_command_start_next (is, cancellable, error);
1301 /* Must have QUEUE lock */
1302 static CamelIMAPXCommand *
1303 imapx_find_command_tag (CamelIMAPXServer *is,
1306 CamelIMAPXCommand *ic = NULL;
1311 if (is->literal != NULL && is->literal->tag == tag) {
1316 head = camel_imapx_command_queue_peek_head_link (is->active);
1318 for (link = head; link != NULL; link = g_list_next (link)) {
1319 CamelIMAPXCommand *candidate = link->data;
1321 if (candidate->tag == tag) {
1333 /* Must not have QUEUE lock */
1334 static CamelIMAPXJob *
1335 imapx_match_active_job (CamelIMAPXServer *is,
1339 CamelIMAPXJob *match = NULL;
1344 head = camel_imapx_command_queue_peek_head_link (is->active);
1346 for (link = head; link != NULL; link = g_list_next (link)) {
1347 CamelIMAPXCommand *ic = link->data;
1348 CamelFolder *folder;
1350 gboolean job_matches;
1352 job = camel_imapx_command_get_job (ic);
1357 if (!(job->type & type))
1360 folder = g_weak_ref_get (&is->select_folder);
1361 job_matches = camel_imapx_job_matches (job, folder, uid);
1362 g_clear_object (&folder);
1375 static CamelIMAPXJob *
1376 imapx_is_job_in_queue (CamelIMAPXServer *is,
1377 CamelFolder *folder,
1382 CamelIMAPXJob *job = NULL;
1383 gboolean found = FALSE;
1387 head = g_queue_peek_head_link (&is->jobs);
1389 for (link = head; link != NULL; link = g_list_next (link)) {
1390 job = (CamelIMAPXJob *) link->data;
1392 if (!job || !(job->type & type))
1395 if (camel_imapx_job_matches (job, folder, uid)) {
1410 imapx_expunge_uid_from_summary (CamelIMAPXServer *is,
1412 gboolean unsolicited)
1414 CamelFolder *folder;
1415 CamelIMAPXFolder *ifolder;
1416 CamelMessageInfo *mi;
1418 folder = g_weak_ref_get (&is->select_folder);
1419 g_return_if_fail (folder != NULL);
1421 ifolder = CAMEL_IMAPX_FOLDER (folder);
1423 if (unsolicited && ifolder->exists_on_server)
1424 ifolder->exists_on_server--;
1426 if (is->changes == NULL)
1427 is->changes = camel_folder_change_info_new ();
1429 mi = camel_folder_summary_peek_loaded (folder->summary, uid);
1431 camel_folder_summary_remove (folder->summary, mi);
1432 camel_message_info_free (mi);
1434 camel_folder_summary_remove_uid (folder->summary, uid);
1437 is->expunged = g_list_prepend (is->expunged, uid);
1439 camel_folder_change_info_remove_uid (is->changes, uid);
1441 if (imapx_idle_supported (is) && imapx_in_idle (is)) {
1442 camel_folder_summary_save_to_db (folder->summary, NULL);
1443 imapx_update_store_summary (folder);
1444 camel_folder_changed (folder, is->changes);
1446 g_list_free_full (is->expunged, (GDestroyNotify) g_free);
1447 is->expunged = NULL;
1449 camel_folder_change_info_clear (is->changes);
1452 g_object_unref (folder);
1456 imapx_get_uid_from_index (CamelFolderSummary *summary,
1462 g_return_val_if_fail (summary != NULL, NULL);
1464 array = camel_folder_summary_get_array (summary);
1465 g_return_val_if_fail (array != NULL, NULL);
1467 if (id < array->len) {
1468 camel_folder_sort_uids (camel_folder_summary_get_folder (summary), array);
1469 uid = g_strdup (g_ptr_array_index (array, id));
1472 camel_folder_summary_free_array (array);
1478 invalidate_local_cache (CamelIMAPXFolder *ifolder,
1479 guint64 new_uidvalidity)
1481 CamelFolder *cfolder;
1482 CamelFolderChangeInfo *changes;
1486 g_return_if_fail (ifolder != NULL);
1488 cfolder = CAMEL_FOLDER (ifolder);
1489 g_return_if_fail (cfolder != NULL);
1491 changes = camel_folder_change_info_new ();
1493 uids = camel_folder_summary_get_array (cfolder->summary);
1494 for (ii = 0; uids && ii < uids->len; ii++) {
1495 const gchar *uid = uids->pdata[ii];
1498 camel_folder_change_info_change_uid (changes, uid);
1501 camel_folder_summary_free_array (uids);
1503 CAMEL_IMAPX_SUMMARY (cfolder->summary)->validity = new_uidvalidity;
1504 camel_folder_summary_touch (cfolder->summary);
1505 camel_folder_summary_save_to_db (cfolder->summary, NULL);
1507 camel_data_cache_clear (ifolder->cache, "cache");
1508 camel_data_cache_clear (ifolder->cache, "cur");
1510 camel_folder_changed (cfolder, changes);
1511 camel_folder_change_info_free (changes);
1514 /* untagged response handler functions */
1517 imapx_untagged_capability (CamelIMAPXServer *is,
1518 CamelIMAPXStream *stream,
1519 GCancellable *cancellable,
1522 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1523 /* cancellable may be NULL */
1524 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1527 imapx_free_capability (is->cinfo);
1528 is->cinfo = imapx_parse_capability (stream, cancellable, error);
1529 if (is->cinfo == NULL)
1531 c (is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
1536 imapx_untagged_expunge (CamelIMAPXServer *is,
1537 CamelIMAPXStream *stream,
1538 GCancellable *cancellable,
1541 CamelFolder *folder;
1542 CamelIMAPXJob *job = NULL;
1543 guint32 expunge = 0;
1545 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1546 /* cancellable may be NULL */
1547 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1549 expunge = is->priv->context->id;
1550 job = imapx_match_active_job (is, IMAPX_JOB_EXPUNGE, NULL);
1552 /* If there is a job running, let it handle the deletion */
1556 c (is->tagprefix, "expunged: %d\n", is->priv->context->id);
1558 folder = g_weak_ref_get (&is->select_folder);
1560 if (folder != NULL) {
1563 uid = imapx_get_uid_from_index (folder->summary, expunge - 1);
1566 imapx_expunge_uid_from_summary (is, uid, TRUE);
1568 g_object_unref (folder);
1575 imapx_untagged_vanished (CamelIMAPXServer *is,
1576 CamelIMAPXStream *stream,
1577 GCancellable *cancellable,
1580 CamelFolder *folder;
1581 GPtrArray *uids = NULL;
1582 GList *uid_list = NULL;
1583 gboolean unsolicited = TRUE;
1586 guchar *token = NULL;
1589 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1590 /* cancellable may be NULL */
1591 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1593 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, error);
1597 unsolicited = FALSE;
1598 while (tok != ')') {
1599 /* We expect this to be 'EARLIER' */
1600 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, error);
1605 camel_imapx_stream_ungettoken (stream, tok, token, len);
1607 uids = imapx_parse_uids (stream, cancellable, error);
1611 folder = g_weak_ref_get (&is->select_folder);
1612 g_return_val_if_fail (folder != NULL, FALSE);
1615 CamelIMAPXFolder *ifolder = CAMEL_IMAPX_FOLDER (folder);
1617 if (ifolder->exists_on_server < uids->len) {
1619 is->tagprefix, "Error: exists_on_folder %d is fewer than vanished %d\n",
1620 ifolder->exists_on_server, uids->len);
1621 ifolder->exists_on_server = 0;
1623 ifolder->exists_on_server -= uids->len;
1625 if (is->changes == NULL)
1626 is->changes = camel_folder_change_info_new ();
1628 for (i = 0; i < uids->len; i++) {
1629 gchar *uid = g_strdup_printf ("%u", GPOINTER_TO_UINT (g_ptr_array_index (uids, i)));
1631 c (is->tagprefix, "vanished: %s\n", uid);
1633 uid_list = g_list_prepend (uid_list, uid);
1634 camel_folder_change_info_remove_uid (is->changes, uid);
1636 uid_list = g_list_reverse (uid_list);
1637 camel_folder_summary_remove_uids (folder->summary, uid_list);
1638 is->expunged = g_list_concat (is->expunged, uid_list);
1639 g_ptr_array_free (uids, TRUE);
1641 g_object_unref (folder);
1647 imapx_untagged_namespace (CamelIMAPXServer *is,
1648 CamelIMAPXStream *stream,
1649 GCancellable *cancellable,
1652 CamelIMAPXNamespaceList *nsl = NULL;
1653 CamelIMAPXStoreNamespace *ns = NULL;
1654 CamelIMAPXStore *store;
1656 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1657 /* cancellable may be NULL */
1658 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1660 nsl = imapx_parse_namespace_list (stream, cancellable, error);
1664 store = camel_imapx_server_ref_store (is);
1666 if (store->summary->namespaces)
1667 camel_imapx_namespace_list_clear (store->summary->namespaces);
1668 store->summary->namespaces = nsl;
1669 camel_store_summary_touch (CAMEL_STORE_SUMMARY (store->summary));
1671 /* TODO Need to remove store->dir_sep to support multiple namespaces */
1674 store->dir_sep = ns->sep;
1675 if (!store->dir_sep)
1676 store->dir_sep = '/';
1679 g_object_unref (store);
1685 imapx_untagged_exists (CamelIMAPXServer *is,
1686 CamelIMAPXStream *stream,
1687 GCancellable *cancellable,
1690 CamelFolder *folder;
1691 gboolean success = TRUE;
1693 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1695 c (is->tagprefix, "exists: %d\n", is->priv->context->id);
1696 is->exists = is->priv->context->id;
1698 folder = g_weak_ref_get (&is->select_folder);
1700 if (folder != NULL) {
1701 CAMEL_IMAPX_FOLDER (folder)->exists_on_server =
1702 is->priv->context->id;
1704 if (imapx_idle_supported (is) && imapx_in_idle (is)) {
1707 count = camel_folder_summary_count (folder->summary);
1708 if (count < is->priv->context->id) {
1709 CamelIMAPXIdleStopResult stop_result;
1711 stop_result = imapx_stop_idle (
1712 is, stream, cancellable, error);
1713 success = (stop_result != IMAPX_IDLE_STOP_ERROR);
1717 g_object_unref (folder);
1724 imapx_untagged_flags (CamelIMAPXServer *is,
1725 CamelIMAPXStream *stream,
1726 GCancellable *cancellable,
1731 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1732 /* cancellable may be NULL */
1733 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1735 imapx_parse_flags (stream, &flags, NULL, cancellable, error);
1736 c (is->tagprefix, "flags: %08x\n", flags);
1742 imapx_untagged_fetch (CamelIMAPXServer *is,
1743 CamelIMAPXStream *stream,
1744 GCancellable *cancellable,
1747 struct _fetch_info *finfo;
1749 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
1750 /* cancellable may be NULL */
1751 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1753 finfo = imapx_parse_fetch (stream, cancellable, error);
1754 if (finfo == NULL) {
1755 imapx_free_fetch (finfo);
1759 if ((finfo->got & (FETCH_BODY | FETCH_UID)) == (FETCH_BODY | FETCH_UID)) {
1761 GetMessageData *data;
1763 job = imapx_match_active_job (
1764 is, IMAPX_JOB_GET_MESSAGE, finfo->uid);
1765 g_return_val_if_fail (job != NULL, FALSE);
1767 data = camel_imapx_job_get_data (job);
1768 g_return_val_if_fail (data != NULL, FALSE);
1770 /* This must've been a get-message request,
1771 * fill out the body stream, in the right spot. */
1774 if (data->use_multi_fetch) {
1775 data->body_offset = finfo->offset;
1777 G_SEEKABLE (data->stream),
1778 finfo->offset, G_SEEK_SET,
1782 data->body_len = camel_stream_write_to_stream (
1783 finfo->body, data->stream, cancellable, error);
1784 if (data->body_len == -1) {
1787 _("Error writing to cache stream"));
1793 if ((finfo->got & FETCH_FLAGS) && !(finfo->got & FETCH_HEADER)) {
1795 CamelFolder *select_folder;
1796 CamelFolder *select_pending;
1797 RefreshInfoData *data = NULL;
1799 job = imapx_match_active_job (
1800 is, IMAPX_JOB_FETCH_NEW_MESSAGES |
1801 IMAPX_JOB_REFRESH_INFO |
1802 IMAPX_JOB_FETCH_MESSAGES, NULL);
1805 data = camel_imapx_job_get_data (job);
1806 g_return_val_if_fail (data != NULL, FALSE);
1809 g_mutex_lock (&is->select_lock);
1810 select_folder = g_weak_ref_get (&is->select_folder);
1811 select_pending = g_weak_ref_get (&is->select_pending);
1812 g_mutex_unlock (&is->select_lock);
1814 /* This is either a refresh_info job, check to see if it is
1815 * and update if so, otherwise it must've been an unsolicited
1816 * response, so update the summary to match. */
1817 if (data && (finfo->got & FETCH_UID) && data->scan_changes) {
1818 struct _refresh_info r;
1822 r.server_flags = finfo->flags;
1823 r.server_user_flags = finfo->user_flags;
1824 finfo->user_flags = NULL;
1826 g_array_append_val (data->infos, r);
1828 } else if (select_folder != NULL) {
1829 CamelMessageInfo *mi = NULL;
1830 gboolean changed = FALSE;
1833 c (is->tagprefix, "flag changed: %d\n", is->priv->context->id);
1835 if (finfo->got & FETCH_UID) {
1839 uid = imapx_get_uid_from_index (
1840 select_folder->summary,
1841 is->priv->context->id - 1);
1845 mi = camel_folder_summary_get (
1846 select_folder->summary, uid);
1848 /* It's unsolicited _unless_ is->select_pending (i.e. during
1849 * a QRESYNC SELECT */
1850 changed = imapx_update_message_info_flags (
1855 (select_pending == NULL));
1857 /* This (UID + FLAGS for previously unknown message) might
1858 * happen during a SELECT (QRESYNC). We should use it. */
1859 c (is->tagprefix, "flags changed for unknown uid %s\n.", uid);
1861 finfo->user_flags = NULL;
1865 if (is->changes == NULL)
1866 is->changes = camel_folder_change_info_new ();
1868 camel_folder_change_info_change_uid (is->changes, uid);
1872 if (imapx_idle_supported (is) && changed && imapx_in_idle (is)) {
1873 camel_folder_summary_save_to_db (
1874 select_folder->summary, NULL);
1875 imapx_update_store_summary (select_folder);
1876 camel_folder_changed (
1877 select_folder, is->changes);
1878 camel_folder_change_info_clear (is->changes);
1882 camel_message_info_free (mi);
1885 g_clear_object (&select_folder);
1886 g_clear_object (&select_pending);
1889 if ((finfo->got & (FETCH_HEADER | FETCH_UID)) == (FETCH_HEADER | FETCH_UID)) {
1892 /* This must be a refresh info job as well, but it has
1893 * asked for new messages to be added to the index. */
1895 job = imapx_match_active_job (
1896 is, IMAPX_JOB_FETCH_NEW_MESSAGES |
1897 IMAPX_JOB_REFRESH_INFO |
1898 IMAPX_JOB_FETCH_MESSAGES, NULL);
1901 CamelFolder *folder;
1902 CamelMimeParser *mp;
1903 CamelMessageInfo *mi;
1905 folder = camel_imapx_job_ref_folder (job);
1906 g_return_val_if_fail (folder != NULL, FALSE);
1908 /* Do we want to save these headers for later too? Do we care? */
1910 mp = camel_mime_parser_new ();
1911 camel_mime_parser_init_with_stream (mp, finfo->header, NULL);
1912 mi = camel_folder_summary_info_new_from_parser (folder->summary, mp);
1913 g_object_unref (mp);
1916 guint32 server_flags;
1917 CamelFlag *server_user_flags;
1918 CamelMessageInfoBase *binfo;
1919 gboolean free_user_flags = FALSE;
1921 mi->uid = camel_pstring_strdup (finfo->uid);
1923 if (!(finfo->got & FETCH_FLAGS)) {
1924 RefreshInfoData *data;
1925 struct _refresh_info *r = NULL;
1927 gboolean found = FALSE;
1929 data = camel_imapx_job_get_data (job);
1930 g_return_val_if_fail (data != NULL, FALSE);
1932 min = data->last_index;
1933 max = data->index - 1;
1935 /* array is sorted, so use a binary search */
1939 mid = (min + max) / 2;
1940 r = &g_array_index (data->infos, struct _refresh_info, mid);
1941 cmp = imapx_refresh_info_uid_cmp (
1944 is->priv->context->fetch_order == CAMEL_SORT_ASCENDING);
1953 } while (!found && min <= max);
1956 g_assert_not_reached ();
1958 server_flags = r->server_flags;
1959 server_user_flags = r->server_user_flags;
1961 server_flags = finfo->flags;
1962 server_user_flags = finfo->user_flags;
1963 /* free user_flags ? */
1964 finfo->user_flags = NULL;
1965 free_user_flags = TRUE;
1968 /* If the message is a really new one -- equal or higher than what
1969 * we know as UIDNEXT for the folder, then it came in since we last
1970 * fetched UIDNEXT and UNREAD count. We'll update UIDNEXT in the
1971 * command completion, but update UNREAD count now according to the
1972 * message SEEN flag */
1973 if (!(server_flags & CAMEL_MESSAGE_SEEN)) {
1974 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
1975 guint64 uidl = strtoull (mi->uid, NULL, 10);
1977 if (uidl >= ifolder->uidnext_on_server) {
1978 c (is->tagprefix, "Updating unread count for new message %s\n", mi->uid);
1979 ((CamelIMAPXFolder *) folder)->unread_on_server++;
1981 c (is->tagprefix, "Not updating unread count for new message %s\n", mi->uid);
1985 binfo = (CamelMessageInfoBase *) mi;
1986 binfo->size = finfo->size;
1988 if (!camel_folder_summary_check_uid (folder->summary, mi->uid)) {
1989 RefreshInfoData *data;
1990 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
1993 data = camel_imapx_job_get_data (job);
1994 g_return_val_if_fail (data != NULL, FALSE);
1996 imapx_set_message_info_flags_for_new_message (mi, server_flags, server_user_flags, folder);
1997 camel_folder_summary_add (folder->summary, mi);
1998 camel_folder_change_info_add_uid (data->changes, mi->uid);
2000 if (!g_hash_table_lookup (ifolder->ignore_recent, mi->uid)) {
2001 camel_folder_change_info_recent_uid (data->changes, mi->uid);
2002 g_hash_table_remove (ifolder->ignore_recent, mi->uid);
2005 cnt = (camel_folder_summary_count (folder->summary) * 100 ) / ifolder->exists_on_server;
2006 camel_operation_progress (cancellable, cnt ? cnt : 1);
2008 camel_message_info_free (mi);
2011 if (free_user_flags && server_user_flags)
2012 camel_flag_list_free (&server_user_flags);
2016 g_object_unref (folder);
2020 imapx_free_fetch (finfo);
2026 imapx_untagged_lsub (CamelIMAPXServer *is,
2027 CamelIMAPXStream *stream,
2028 GCancellable *cancellable,
2031 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2032 /* cancellable may be NULL */
2033 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2035 is->priv->context->lsub = TRUE;
2041 imapx_untagged_list (CamelIMAPXServer *is,
2042 CamelIMAPXStream *stream,
2043 GCancellable *cancellable,
2046 struct _list_info *linfo = NULL;
2047 CamelIMAPXJob *job = NULL;
2048 ListData *data = NULL;
2050 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2051 /* cancellable may be NULL */
2052 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2054 linfo = imapx_parse_list (stream, cancellable, error);
2058 job = imapx_match_active_job (is, IMAPX_JOB_LIST, linfo->name);
2060 data = camel_imapx_job_get_data (job);
2061 g_return_val_if_fail (data != NULL, FALSE);
2063 // TODO: we want to make sure the names match?
2065 if (data->flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) {
2066 c (is->tagprefix, "lsub: '%s' (%c)\n", linfo->name, linfo->separator);
2068 c (is->tagprefix, "list: '%s' (%c)\n", linfo->name, linfo->separator);
2071 if (job && g_hash_table_lookup (data->folders, linfo->name) == NULL) {
2072 if (is->priv->context->lsub)
2073 linfo->flags |= CAMEL_FOLDER_SUBSCRIBED;
2074 g_hash_table_insert (data->folders, linfo->name, linfo);
2076 g_warning ("got list response but no current listing job happening?\n");
2077 imapx_free_list (linfo);
2084 imapx_untagged_quota (CamelIMAPXServer *is,
2085 CamelIMAPXStream *stream,
2086 GCancellable *cancellable,
2089 gchar *quota_root_name = NULL;
2090 CamelFolderQuotaInfo *quota_info = NULL;
2093 success = camel_imapx_parse_quota (
2094 stream, cancellable, "a_root_name, "a_info, error);
2097 g_return_val_if_fail (
2098 (success && (quota_root_name != NULL)) ||
2099 (!success && (quota_root_name == NULL)), FALSE);
2102 CamelIMAPXStore *store;
2104 store = camel_imapx_server_ref_store (is);
2105 camel_imapx_store_set_quota_info (
2106 store, quota_root_name, quota_info);
2107 g_object_unref (store);
2109 g_free (quota_root_name);
2110 camel_folder_quota_info_free (quota_info);
2117 imapx_untagged_quotaroot (CamelIMAPXServer *is,
2118 CamelIMAPXStream *stream,
2119 GCancellable *cancellable,
2122 CamelIMAPXStore *store;
2123 CamelIMAPXStoreNamespace *ns;
2124 CamelFolder *folder = NULL;
2125 gchar *mailbox_name = NULL;
2126 gchar **quota_root_names = NULL;
2128 GError *local_error = NULL;
2130 success = camel_imapx_parse_quotaroot (
2131 stream, cancellable, &mailbox_name, "a_root_names, error);
2134 g_return_val_if_fail (
2135 (success && (mailbox_name != NULL)) ||
2136 (!success && (mailbox_name == NULL)), FALSE);
2141 store = camel_imapx_server_ref_store (is);
2143 ns = camel_imapx_store_summary_namespace_find_full (
2144 store->summary, mailbox_name);
2148 folder_path = camel_imapx_store_summary_full_to_path (
2149 store->summary, mailbox_name, ns->sep);
2150 if (folder_path != NULL) {
2151 folder = camel_store_get_folder_sync (
2152 CAMEL_STORE (store), folder_path, 0,
2153 cancellable, &local_error);
2154 g_free (folder_path);
2158 if (folder != NULL) {
2159 camel_imapx_folder_set_quota_root_names (
2160 CAMEL_IMAPX_FOLDER (folder),
2161 (const gchar **) quota_root_names);
2162 g_object_unref (folder);
2165 if (local_error != NULL) {
2167 "%s: Failed to get folder '%s': %s",
2168 G_STRFUNC, mailbox_name, local_error->message);
2169 g_error_free (local_error);
2172 g_free (mailbox_name);
2173 g_strfreev (quota_root_names);
2179 imapx_untagged_recent (CamelIMAPXServer *is,
2180 CamelIMAPXStream *stream,
2181 GCancellable *cancellable,
2184 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2185 /* cancellable may be NULL */
2186 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2188 c (is->tagprefix, "recent: %d\n", is->priv->context->id);
2189 is->recent = is->priv->context->id;
2195 imapx_untagged_search (CamelIMAPXServer *is,
2196 CamelIMAPXStream *stream,
2197 GCancellable *cancellable,
2200 GArray *search_results;
2205 gboolean success = FALSE;
2206 GError *local_error = NULL;
2208 search_results = g_array_new (FALSE, FALSE, sizeof (guint64));
2211 /* Peek at the next token, and break
2212 * out of the loop if we get a newline. */
2213 tok = camel_imapx_stream_token (
2214 stream, &token, &len, cancellable, error);
2217 if (tok == IMAPX_TOK_ERROR || tok == IMAPX_TOK_PROTOCOL)
2219 camel_imapx_stream_ungettoken (stream, tok, token, len);
2221 /* XXX camel_imapx_stream_number() should return the
2222 * number as an out parameter, so we can more easily
2223 * distinguish between a real '0' and an error. */
2224 number = camel_imapx_stream_number (
2225 stream, cancellable, &local_error);
2226 if (local_error == NULL) {
2227 g_array_append_val (search_results, number);
2229 g_propagate_error (error, local_error);
2234 g_mutex_lock (&is->priv->search_results_lock);
2236 if (is->priv->search_results == NULL)
2237 is->priv->search_results = g_array_ref (search_results);
2239 g_warning ("%s: Conflicting search results", G_STRFUNC);
2241 g_mutex_unlock (&is->priv->search_results_lock);
2246 g_array_unref (search_results);
2252 imapx_untagged_status (CamelIMAPXServer *is,
2253 CamelIMAPXStream *stream,
2254 GCancellable *cancellable,
2257 CamelIMAPXStore *store;
2258 struct _state_info *sinfo = NULL;
2260 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2261 /* cancellable may be NULL */
2262 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2264 store = camel_imapx_server_ref_store (is);
2266 sinfo = imapx_parse_status_info (stream, cancellable, error);
2269 CamelIMAPXStoreSummary *s = store->summary;
2270 CamelIMAPXStoreNamespace *ns;
2271 CamelFolder *folder = NULL;
2273 ns = camel_imapx_store_summary_namespace_find_full (s, sinfo->name);
2277 path_name = camel_imapx_store_summary_full_to_path (s, sinfo->name, ns->sep);
2278 c (is->tagprefix, "Got folder path '%s' for full '%s'\n", path_name, sinfo->name);
2280 folder = camel_store_get_folder_sync (
2281 CAMEL_STORE (store),
2282 path_name, 0, cancellable, error);
2286 if (folder != NULL) {
2287 CamelIMAPXFolder *ifolder;
2289 ifolder = CAMEL_IMAPX_FOLDER (folder);
2290 ifolder->unread_on_server = sinfo->unseen;
2291 ifolder->exists_on_server = sinfo->messages;
2292 ifolder->modseq_on_server = sinfo->highestmodseq;
2293 ifolder->uidnext_on_server = sinfo->uidnext;
2294 ifolder->uidvalidity_on_server = sinfo->uidvalidity;
2295 if (sinfo->uidvalidity && sinfo->uidvalidity != ((CamelIMAPXSummary *) folder->summary)->validity)
2296 invalidate_local_cache (ifolder, sinfo->uidvalidity);
2298 c (is->tagprefix, "Received STATUS for unknown folder '%s'\n", sinfo->name);
2301 g_free (sinfo->name);
2305 g_object_unref (store);
2311 imapx_untagged_bye (CamelIMAPXServer *is,
2312 CamelIMAPXStream *stream,
2313 GCancellable *cancellable,
2316 guchar *token = NULL;
2318 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2319 /* cancellable may be NULL */
2320 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2322 if (camel_imapx_stream_text (stream, &token, cancellable, NULL)) {
2323 c (is->tagprefix, "BYE: %s\n", token);
2325 error, CAMEL_IMAPX_ERROR, 1,
2326 "IMAP server said BYE: %s", token);
2328 is->state = IMAPX_SHUTDOWN;
2334 imapx_untagged_preauth (CamelIMAPXServer *is,
2335 CamelIMAPXStream *stream,
2336 GCancellable *cancellable,
2339 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2340 /* cancellable may be NULL */
2341 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2343 c (is->tagprefix, "preauthenticated\n");
2344 if (is->state < IMAPX_AUTHENTICATED)
2345 is->state = IMAPX_AUTHENTICATED;
2351 imapx_untagged_ok_no_bad (CamelIMAPXServer *is,
2352 CamelIMAPXStream *stream,
2353 GCancellable *cancellable,
2356 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
2357 /* cancellable may be NULL */
2358 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
2360 /* TODO: validate which ones of these can happen as unsolicited responses */
2361 /* TODO: handle bye/preauth differently */
2362 camel_imapx_stream_ungettoken (
2364 is->priv->context->tok,
2365 is->priv->context->token,
2366 is->priv->context->len);
2367 is->priv->context->sinfo =
2368 imapx_parse_status (stream, cancellable, error);
2369 if (is->priv->context->sinfo == NULL)
2371 switch (is->priv->context->sinfo->condition) {
2373 c (is->tagprefix, "previously selected folder is now closed\n");
2375 CamelFolder *select_folder;
2376 CamelFolder *select_pending;
2378 g_mutex_lock (&is->select_lock);
2380 select_folder = g_weak_ref_get (&is->select_folder);
2381 select_pending = g_weak_ref_get (&is->select_pending);
2383 if (select_folder == NULL)
2388 g_clear_object (&select_folder);
2389 g_clear_object (&select_pending);
2391 g_mutex_unlock (&is->select_lock);
2394 case IMAPX_READ_WRITE:
2395 is->mode = IMAPX_MODE_READ | IMAPX_MODE_WRITE;
2396 c (is->tagprefix, "folder is read-write\n");
2398 case IMAPX_READ_ONLY:
2399 is->mode = IMAPX_MODE_READ;
2400 c (is->tagprefix, "folder is read-only\n");
2402 case IMAPX_UIDVALIDITY:
2403 is->uidvalidity = is->priv->context->sinfo->u.uidvalidity;
2406 is->unseen = is->priv->context->sinfo->u.unseen;
2408 case IMAPX_HIGHESTMODSEQ:
2409 is->highestmodseq = is->priv->context->sinfo->u.highestmodseq;
2411 case IMAPX_PERMANENTFLAGS:
2412 is->permanentflags = is->priv->context->sinfo->u.permanentflags;
2415 is->uidnext = is->priv->context->sinfo->u.uidnext;
2418 c (is->tagprefix, "ALERT!: %s\n", is->priv->context->sinfo->text);
2421 c (is->tagprefix, "PARSE: %s\n", is->priv->context->sinfo->text);
2423 case IMAPX_CAPABILITY:
2424 if (is->priv->context->sinfo->u.cinfo) {
2425 struct _capability_info *cinfo = is->cinfo;
2426 is->cinfo = is->priv->context->sinfo->u.cinfo;
2427 is->priv->context->sinfo->u.cinfo = NULL;
2429 imapx_free_capability (cinfo);
2430 c (is->tagprefix, "got capability flags %08x\n", is->cinfo ? is->cinfo->capa : 0xFFFFFFFF);
2436 imapx_free_status (is->priv->context->sinfo);
2441 /* handle any untagged responses */
2443 imapx_untagged (CamelIMAPXServer *is,
2444 CamelIMAPXStream *stream,
2445 GCancellable *cancellable,
2448 CamelIMAPXSettings *settings;
2449 CamelSortType fetch_order;
2450 guchar *p = NULL, c;
2451 const gchar *token = NULL;
2452 gboolean ok = FALSE;
2454 /* If is->priv->context is not NULL here, it basically means
2455 * that imapx_untagged() got called concurrently for the same
2456 * CamelIMAPXServer instance. Should this ever happen, then
2457 * we will need to protect this data structure with locks
2459 g_return_val_if_fail (is->priv->context == NULL, FALSE);
2460 is->priv->context = g_new0 (CamelIMAPXServerUntaggedContext, 1);
2462 settings = camel_imapx_server_ref_settings (is);
2463 fetch_order = camel_imapx_settings_get_fetch_order (settings);
2464 g_object_unref (settings);
2466 is->priv->context->lsub = FALSE;
2467 is->priv->context->fetch_order = fetch_order;
2469 e (is->tagprefix, "got untagged response\n");
2470 is->priv->context->id = 0;
2471 is->priv->context->tok = camel_imapx_stream_token (
2473 &(is->priv->context->token),
2474 &(is->priv->context->len),
2475 cancellable, error);
2476 if (is->priv->context->tok < 0)
2479 if (is->priv->context->tok == IMAPX_TOK_INT) {
2480 is->priv->context->id = strtoul (
2481 (gchar *) is->priv->context->token, NULL, 10);
2482 is->priv->context->tok = camel_imapx_stream_token (
2484 &(is->priv->context->token),
2485 &(is->priv->context->len),
2486 cancellable, error);
2487 if (is->priv->context->tok < 0)
2491 if (is->priv->context->tok == '\n') {
2493 error, CAMEL_IMAPX_ERROR, 1,
2494 "truncated server response");
2498 e (is->tagprefix, "Have token '%s' id %d\n", is->priv->context->token, is->priv->context->id);
2499 p = is->priv->context->token;
2501 *p++ = toupper((gchar) c);
2503 token = (const gchar *) is->priv->context->token; /* FIXME need 'guchar *token' here */
2504 while (token != NULL) {
2505 CamelIMAPXUntaggedRespHandlerDesc *desc = NULL;
2507 desc = g_hash_table_lookup (is->priv->untagged_handlers, token);
2509 /* unknown response, just ignore it */
2510 c (is->tagprefix, "unknown token: %s\n", is->priv->context->token);
2513 if (desc->handler == NULL) {
2514 /* no handler function, ignore token */
2515 c (is->tagprefix, "no handler for token: %s\n", is->priv->context->token);
2519 /* call the handler function */
2520 ok = desc->handler (is, stream, cancellable, error);
2524 /* is there another handler next-in-line? */
2525 token = desc->next_response;
2526 if (token != NULL) {
2527 /* TODO do we need to update 'priv->context->token'
2528 * to the value of 'token' here, before
2529 * calling the handler next-in-line for this
2530 * specific run of imapx_untagged()?
2531 * It has not been done in the original code
2532 * in the "fall through" situation in the
2533 * token switch statement, which is what
2534 * we're mimicking here
2539 if (!desc->skip_stream_when_done)
2543 ok = (camel_imapx_stream_skip (stream, cancellable, error) == 0);
2545 g_free (is->priv->context);
2546 is->priv->context = NULL;
2551 /* handle any continuation requests
2552 * either data continuations, or auth continuation */
2554 imapx_continuation (CamelIMAPXServer *is,
2555 CamelIMAPXStream *stream,
2557 GCancellable *cancellable,
2560 CamelIMAPXCommand *ic, *newliteral = NULL;
2561 CamelIMAPXCommandPart *cp;
2563 gssize n_bytes_written;
2564 gboolean success = TRUE;
2566 /* The 'literal' pointer is like a write-lock, nothing else
2567 * can write while we have it ... so we dont need any
2568 * ohter lock here. All other writes go through
2570 if (imapx_idle_supported (is) && imapx_in_idle (is)) {
2571 camel_imapx_stream_skip (stream, cancellable, error);
2573 c (is->tagprefix, "Got continuation response for IDLE \n");
2574 IDLE_LOCK (is->idle);
2575 /* We might have actually sent the DONE already! */
2576 if (is->idle->state == IMAPX_IDLE_ISSUED)
2577 is->idle->state = IMAPX_IDLE_STARTED;
2578 else if (is->idle->state == IMAPX_IDLE_CANCEL) {
2579 /* IDLE got cancelled after we sent the command, while
2580 * we were waiting for this continuation. Send DONE
2582 success = imapx_command_idle_stop (
2583 is, stream, cancellable, error);
2585 IDLE_UNLOCK (is->idle);
2588 is->idle->state = IMAPX_IDLE_OFF;
2591 is->tagprefix, "idle starts in wrong state %d\n",
2594 IDLE_UNLOCK (is->idle);
2598 success = imapx_command_start_next (is, cancellable, error);
2607 camel_imapx_stream_skip (stream, cancellable, error);
2608 c (is->tagprefix, "got continuation response with no outstanding continuation requests?\n");
2611 c (is->tagprefix, "got continuation response for data\n");
2613 c (is->tagprefix, "sending LITERAL+ continuation\n");
2616 link = ic->current_part;
2617 g_return_val_if_fail (link != NULL, FALSE);
2618 cp = (CamelIMAPXCommandPart *) link->data;
2620 switch (cp->type & CAMEL_IMAPX_COMMAND_MASK) {
2621 case CAMEL_IMAPX_COMMAND_DATAWRAPPER:
2622 c (is->tagprefix, "writing data wrapper to literal\n");
2623 n_bytes_written = camel_data_wrapper_write_to_stream_sync (
2624 CAMEL_DATA_WRAPPER (cp->ob),
2625 CAMEL_STREAM (stream),
2626 cancellable, error);
2627 if (n_bytes_written < 0)
2630 case CAMEL_IMAPX_COMMAND_STREAM:
2631 c (is->tagprefix, "writing stream to literal\n");
2632 n_bytes_written = camel_stream_write_to_stream (
2633 CAMEL_STREAM (cp->ob),
2634 CAMEL_STREAM (stream),
2635 cancellable, error);
2636 if (n_bytes_written < 0)
2639 case CAMEL_IMAPX_COMMAND_AUTH: {
2643 if (camel_imapx_stream_text (stream, &token, cancellable, error))
2646 resp = camel_sasl_challenge_base64_sync (
2647 (CamelSasl *) cp->ob, (const gchar *) token,
2648 cancellable, error);
2652 c (is->tagprefix, "got auth continuation, feeding token '%s' back to auth mech\n", resp);
2654 n_bytes_written = camel_stream_write (
2655 CAMEL_STREAM (stream),
2656 resp, strlen (resp),
2657 cancellable, error);
2660 if (n_bytes_written < 0)
2663 /* we want to keep getting called until we get a status reponse from the server
2664 * ignore what sasl tells us */
2666 /* We already ate the end of the input stream line */
2669 case CAMEL_IMAPX_COMMAND_FILE: {
2672 c (is->tagprefix, "writing file '%s' to literal\n", (gchar *) cp->ob);
2675 if (cp->ob && (file = camel_stream_fs_new_with_name (cp->ob, O_RDONLY, 0, NULL))) {
2676 n_bytes_written = camel_stream_write_to_stream (
2677 file, CAMEL_STREAM (stream),
2678 cancellable, error);
2679 g_object_unref (file);
2681 if (n_bytes_written < 0)
2683 } else if (cp->ob_size > 0) {
2684 // Server is expecting data ... ummm, send it zeros? abort?
2687 case CAMEL_IMAPX_COMMAND_STRING:
2688 n_bytes_written = camel_stream_write (
2689 CAMEL_STREAM (stream),
2690 cp->ob, cp->ob_size,
2691 cancellable, error);
2692 if (n_bytes_written < 0)
2696 /* should we just ignore? */
2699 error, CAMEL_IMAPX_ERROR, 1,
2700 "continuation response for non-continuation request");
2705 if (camel_imapx_stream_skip (stream, cancellable, error) == -1)
2710 link = g_list_next (link);
2712 ic->current_part = link;
2713 cp = (CamelIMAPXCommandPart *) link->data;
2715 c (is->tagprefix, "next part of command \"%c%05u: %s\"\n", is->tagprefix, ic->tag, cp->data);
2717 n_bytes_written = camel_stream_write_string (
2718 CAMEL_STREAM (stream), cp->data, cancellable, error);
2719 if (n_bytes_written < 0)
2722 if (cp->type & (CAMEL_IMAPX_COMMAND_CONTINUATION | CAMEL_IMAPX_COMMAND_LITERAL_PLUS)) {
2725 g_assert (g_list_next (link) == NULL);
2728 c (is->tagprefix, "%p: queueing continuation\n", ic);
2731 n_bytes_written = camel_stream_write_string (
2732 CAMEL_STREAM (stream), "\r\n", cancellable, error);
2733 if (n_bytes_written < 0)
2737 is->literal = newliteral;
2740 success = imapx_command_start_next (is, cancellable, error);
2746 /* handle a completion line */
2748 imapx_completion (CamelIMAPXServer *is,
2749 CamelIMAPXStream *stream,
2752 GCancellable *cancellable,
2755 CamelIMAPXCommand *ic;
2759 /* Given "A0001 ...", 'A' = tag prefix, '0001' = tag. */
2761 if (token[0] != is->tagprefix) {
2763 error, CAMEL_IMAPX_ERROR, 1,
2764 "Server sent unexpected response: %s", token);
2768 tag = strtoul ((gchar *) token + 1, NULL, 10);
2770 if ((ic = imapx_find_command_tag (is, tag)) == NULL) {
2772 error, CAMEL_IMAPX_ERROR, 1,
2773 "got response tag unexpectedly: %s", token);
2777 c (is->tagprefix, "Got completion response for command %05u '%s'\n", ic->tag, ic->name);
2779 if (camel_folder_change_info_changed (is->changes)) {
2780 CamelFolder *folder;
2782 folder = g_weak_ref_get (&is->select_folder);
2783 g_return_val_if_fail (folder != NULL, FALSE);
2785 camel_folder_summary_save_to_db (folder->summary, NULL);
2787 g_list_free_full (is->expunged, (GDestroyNotify) g_free);
2788 is->expunged = NULL;
2790 imapx_update_store_summary (folder);
2791 camel_folder_changed (folder, is->changes);
2792 camel_folder_change_info_clear (is->changes);
2794 g_object_unref (folder);
2799 camel_imapx_command_ref (ic);
2800 camel_imapx_command_queue_remove (is->active, ic);
2801 camel_imapx_command_queue_push_tail (is->done, ic);
2802 camel_imapx_command_unref (ic);
2804 if (is->literal == ic)
2807 if (g_list_next (ic->current_part) != NULL) {
2810 error, CAMEL_IMAPX_ERROR, 1,
2811 "command still has unsent parts? %s", ic->name);
2815 camel_imapx_command_queue_remove (is->done, ic);
2819 ic->status = imapx_parse_status (stream, cancellable, error);
2821 if (ic->status == NULL)
2824 if (ic->complete != NULL)
2825 if (!ic->complete (is, ic, cancellable, error))
2829 success = imapx_command_start_next (is, cancellable, error);
2836 imapx_step (CamelIMAPXServer *is,
2837 GCancellable *cancellable,
2840 CamelIMAPXStream *stream;
2844 gboolean success = FALSE;
2846 stream = camel_imapx_server_ref_stream (is);
2848 // poll ? wait for other stuff? loop?
2849 tok = camel_imapx_stream_token (
2850 stream, &token, &len, cancellable, error);
2853 case IMAPX_TOK_PROTOCOL:
2854 case IMAPX_TOK_ERROR:
2855 /* GError is already set. */
2858 success = imapx_untagged (
2859 is, stream, cancellable, error);
2861 case IMAPX_TOK_TOKEN:
2862 success = imapx_completion (
2863 is, stream, token, len, cancellable, error);
2866 success = imapx_continuation (
2867 is, stream, FALSE, cancellable, error);
2871 error, CAMEL_IMAPX_ERROR, 1,
2872 "unexpected server response:");
2876 g_object_unref (stream);
2881 /* Used to run 1 command synchronously,
2882 * use for capa, login, and namespaces only. */
2884 imapx_command_run (CamelIMAPXServer *is,
2885 CamelIMAPXCommand *ic,
2886 GCancellable *cancellable,
2888 /* throws IO,PARSE exception */
2890 gboolean success = TRUE;
2892 camel_imapx_command_close (ic);
2895 imapx_command_start (is, ic, cancellable, error);
2898 while (success && ic->status == NULL)
2899 success = imapx_step (is, cancellable, error);
2901 if (is->literal == ic)
2905 camel_imapx_command_queue_remove (is->active, ic);
2912 imapx_command_complete (CamelIMAPXServer *is,
2913 CamelIMAPXCommand *ic,
2914 GCancellable *cancellable,
2917 camel_imapx_command_done (ic);
2918 camel_imapx_command_unref (ic);
2924 imapx_command_cancelled (GCancellable *cancellable,
2925 CamelIMAPXCommand *ic)
2927 /* Unblock imapx_command_run_sync() immediately.
2929 * If camel_imapx_command_done() is called sometime later,
2930 * the GCond will broadcast but no one will be listening. */
2932 camel_imapx_command_done (ic);
2935 /* The caller should free the command as well */
2937 imapx_command_run_sync (CamelIMAPXServer *is,
2938 CamelIMAPXCommand *ic,
2939 GCancellable *cancellable,
2942 guint cancel_id = 0;
2945 /* FIXME The only caller of this function currently does not set
2946 * a "complete" callback function, so we can get away with
2947 * referencing the command here and dropping the reference
2948 * in imapx_command_complete(). The queueing/dequeueing
2949 * of these things is too complex for my little mind, so
2950 * we may have to revisit the reference counting if this
2951 * function gets another caller. */
2953 g_warn_if_fail (ic->complete == NULL);
2954 ic->complete = imapx_command_complete;
2956 if (G_IS_CANCELLABLE (cancellable))
2957 cancel_id = g_cancellable_connect (
2959 G_CALLBACK (imapx_command_cancelled),
2960 camel_imapx_command_ref (ic),
2961 (GDestroyNotify) camel_imapx_command_unref);
2963 /* Unref'ed in imapx_command_complete(). */
2964 camel_imapx_command_ref (ic);
2966 success = imapx_command_queue (is, ic, cancellable, error);
2969 camel_imapx_command_wait (ic);
2972 g_cancellable_disconnect (cancellable, cancel_id);
2974 if (camel_imapx_command_set_error_if_failed (ic, error))
2981 imapx_register_job (CamelIMAPXServer *is,
2985 if (is->state >= IMAPX_INITIALISED) {
2987 g_queue_push_head (&is->jobs, camel_imapx_job_ref (job));
2991 e (is->tagprefix, "NO connection yet, maybe user cancelled jobs earlier ?");
2993 error, CAMEL_SERVICE_ERROR,
2994 CAMEL_SERVICE_ERROR_NOT_CONNECTED,
2995 _("Not authenticated"));
3003 imapx_unregister_job (CamelIMAPXServer *is,
3007 camel_imapx_job_done (job);
3010 if (g_queue_remove (&is->jobs, job))
3011 camel_imapx_job_unref (job);
3016 imapx_submit_job (CamelIMAPXServer *is,
3020 if (!imapx_register_job (is, job, error))
3023 return camel_imapx_job_run (job, is, error);
3026 /* ********************************************************************** */
3029 /*TODO handle negative cases sanely */
3031 imapx_command_idle_stop (CamelIMAPXServer *is,
3032 CamelIMAPXStream *stream,
3033 GCancellable *cancellable,
3038 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
3039 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (stream), FALSE);
3041 success = (camel_stream_write_string (
3042 CAMEL_STREAM (stream),
3043 "DONE\r\n", cancellable, error) != -1);
3046 g_prefix_error (error, "Unable to issue DONE: ");
3047 c (is->tagprefix, "Failed to issue DONE to terminate IDLE\n");
3048 is->state = IMAPX_SHUTDOWN;
3049 is->parser_quit = TRUE;
3050 if (is->cancellable)
3051 g_cancellable_cancel (is->cancellable);
3058 imapx_command_idle_done (CamelIMAPXServer *is,
3059 CamelIMAPXCommand *ic,
3060 GCancellable *cancellable,
3063 CamelIMAPXIdle *idle = is->idle;
3065 gboolean success = TRUE;
3067 job = camel_imapx_command_get_job (ic);
3068 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
3070 if (camel_imapx_command_set_error_if_failed (ic, error)) {
3073 _("Error performing IDLE"));
3078 idle->state = IMAPX_IDLE_OFF;
3081 imapx_unregister_job (is, job);
3082 camel_imapx_command_unref (ic);
3088 imapx_job_idle_start (CamelIMAPXJob *job,
3089 CamelIMAPXServer *is,
3090 GCancellable *cancellable,
3093 CamelFolder *folder;
3094 CamelIMAPXCommand *ic;
3095 CamelIMAPXCommandPart *cp;
3096 gboolean success = TRUE;
3098 folder = camel_imapx_job_ref_folder (job);
3099 g_return_val_if_fail (folder != NULL, FALSE);
3101 ic = camel_imapx_command_new (
3102 is, "IDLE", folder, "IDLE");
3103 camel_imapx_command_set_job (ic, job);
3105 ic->complete = imapx_command_idle_done;
3107 camel_imapx_command_close (ic);
3108 cp = g_queue_peek_head (&ic->parts);
3109 cp->type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
3112 IDLE_LOCK (is->idle);
3113 /* Don't issue it if the idle was cancelled already */
3114 if (is->idle->state == IMAPX_IDLE_PENDING) {
3115 is->idle->state = IMAPX_IDLE_ISSUED;
3116 success = imapx_command_start (is, ic, cancellable, error);
3118 imapx_unregister_job (is, job);
3119 camel_imapx_command_unref (ic);
3121 IDLE_UNLOCK (is->idle);
3124 g_object_unref (folder);
3130 camel_imapx_server_idle (CamelIMAPXServer *is,
3131 CamelFolder *folder,
3132 GCancellable *cancellable,
3138 job = camel_imapx_job_new (cancellable);
3139 job->type = IMAPX_JOB_IDLE;
3140 job->start = imapx_job_idle_start;
3142 camel_imapx_job_set_folder (job, folder);
3144 success = imapx_submit_job (is, job, error);
3146 camel_imapx_job_unref (job);
3152 imapx_job_fetch_new_messages_matches (CamelIMAPXJob *job,
3153 CamelFolder *folder,
3156 return camel_imapx_job_has_folder (job, folder);
3160 imapx_server_fetch_new_messages (CamelIMAPXServer *is,
3161 CamelFolder *folder,
3163 gboolean update_unseen,
3164 GCancellable *cancellable,
3168 RefreshInfoData *data;
3171 data = g_slice_new0 (RefreshInfoData);
3172 data->changes = camel_folder_change_info_new ();
3173 data->update_unseen = update_unseen;
3174 data->fetch_msg_limit = -1;
3176 job = camel_imapx_job_new (cancellable);
3177 job->type = IMAPX_JOB_FETCH_NEW_MESSAGES;
3178 job->start = imapx_job_fetch_new_messages_start;
3179 job->matches = imapx_job_fetch_new_messages_matches;
3180 job->noreply = async;
3182 camel_imapx_job_set_folder (job, folder);
3184 camel_imapx_job_set_data (
3185 job, data, (GDestroyNotify) refresh_info_data_free);
3187 success = imapx_submit_job (is, job, error);
3189 camel_imapx_job_unref (job);
3195 imapx_idle_thread (gpointer data)
3197 CamelIMAPXServer *is = (CamelIMAPXServer *) data;
3198 GError *local_error = NULL;
3201 g_mutex_lock (&is->idle->start_watch_mutex);
3202 is->idle->start_watch_is_set = FALSE;
3203 g_mutex_unlock (&is->idle->start_watch_mutex);
3205 IDLE_LOCK (is->idle);
3208 CamelFolder *folder;
3209 gboolean new_messages_on_server;
3212 if (is->idle->state != IMAPX_IDLE_PENDING)
3215 if (is->idle->idle_exit)
3218 folder = g_weak_ref_get (&is->select_folder);
3222 dwelled = time (NULL) - is->idle->started;
3223 if (dwelled < IMAPX_IDLE_DWELL_TIME) {
3226 g_object_unref (folder);
3228 IDLE_UNLOCK (is->idle);
3229 seconds = IMAPX_IDLE_DWELL_TIME - dwelled;
3230 g_usleep (seconds * G_USEC_PER_SEC);
3231 IDLE_LOCK (is->idle);
3236 IDLE_UNLOCK (is->idle);
3238 camel_imapx_server_idle (
3239 is, folder, is->cancellable, &local_error);
3241 new_messages_on_server =
3242 CAMEL_IMAPX_FOLDER (folder)->exists_on_server >
3243 camel_folder_summary_count (folder->summary);
3245 if (local_error == NULL &&
3246 new_messages_on_server &&
3247 imapx_is_command_queue_empty (is)) {
3248 imapx_server_fetch_new_messages (
3249 is, folder, TRUE, TRUE,
3250 is->cancellable, &local_error);
3253 if (local_error != NULL) {
3254 e (is->tagprefix, "Caught exception in idle thread: %s \n", local_error->message);
3255 /* No way to asyncronously notify UI ? */
3256 g_clear_error (&local_error);
3259 g_object_unref (folder);
3261 IDLE_LOCK (is->idle);
3264 IDLE_UNLOCK (is->idle);
3266 g_mutex_lock (&is->idle->start_watch_mutex);
3267 while (!is->idle->start_watch_is_set)
3269 &is->idle->start_watch_cond,
3270 &is->idle->start_watch_mutex);
3271 g_mutex_unlock (&is->idle->start_watch_mutex);
3273 if (is->idle->idle_exit)
3277 g_clear_error (&local_error);
3278 is->idle->idle_thread = NULL;
3282 static CamelIMAPXIdleStopResult
3283 imapx_stop_idle (CamelIMAPXServer *is,
3284 CamelIMAPXStream *stream,
3285 GCancellable *cancellable,
3288 CamelIMAPXIdleStopResult result = IMAPX_IDLE_STOP_NOOP;
3294 IDLE_LOCK (is->idle);
3296 switch (is->idle->state) {
3297 case IMAPX_IDLE_ISSUED:
3298 is->idle->state = IMAPX_IDLE_CANCEL;
3301 case IMAPX_IDLE_CANCEL:
3302 result = IMAPX_IDLE_STOP_SUCCESS;
3305 case IMAPX_IDLE_STARTED:
3306 success = imapx_command_idle_stop (
3307 is, stream, cancellable, error);
3309 result = IMAPX_IDLE_STOP_SUCCESS;
3311 result = IMAPX_IDLE_STOP_ERROR;
3317 "Stopping idle after %ld seconds\n",
3318 (glong) (now - is->idle->started));
3321 case IMAPX_IDLE_PENDING:
3322 is->idle->state = IMAPX_IDLE_OFF;
3325 case IMAPX_IDLE_OFF:
3330 IDLE_UNLOCK (is->idle);
3336 imapx_init_idle (CamelIMAPXServer *is)
3338 is->idle = g_new0 (CamelIMAPXIdle, 1);
3339 g_mutex_init (&is->idle->idle_lock);
3341 g_cond_init (&is->idle->start_watch_cond);
3342 g_mutex_init (&is->idle->start_watch_mutex);
3346 imapx_exit_idle (CamelIMAPXServer *is)
3348 CamelIMAPXIdle *idle = is->idle;
3349 GThread *thread = NULL;
3356 if (idle->idle_thread) {
3357 idle->idle_exit = TRUE;
3359 g_mutex_lock (&idle->start_watch_mutex);
3360 idle->start_watch_is_set = TRUE;
3361 g_cond_broadcast (&idle->start_watch_cond);
3362 g_mutex_unlock (&idle->start_watch_mutex);
3364 thread = idle->idle_thread;
3365 idle->idle_thread = NULL;
3368 idle->idle_thread = NULL;
3372 g_thread_join (thread);
3374 g_mutex_clear (&idle->idle_lock);
3375 g_cond_clear (&idle->start_watch_cond);
3376 g_mutex_clear (&idle->start_watch_mutex);
3383 imapx_start_idle (CamelIMAPXServer *is)
3385 CamelIMAPXIdle *idle = is->idle;
3387 if (camel_application_is_exiting)
3392 g_assert (idle->state == IMAPX_IDLE_OFF);
3393 time (&idle->started);
3394 idle->state = IMAPX_IDLE_PENDING;
3396 if (!idle->idle_thread) {
3397 idle->start_watch_is_set = FALSE;
3399 idle->idle_thread = g_thread_new (
3400 NULL, (GThreadFunc) imapx_idle_thread, is);
3402 g_mutex_lock (&idle->start_watch_mutex);
3403 idle->start_watch_is_set = TRUE;
3404 g_cond_broadcast (&idle->start_watch_cond);
3405 g_mutex_unlock (&idle->start_watch_mutex);
3412 imapx_in_idle (CamelIMAPXServer *is)
3414 gboolean ret = FALSE;
3415 CamelIMAPXIdle *idle = is->idle;
3418 ret = (idle->state > IMAPX_IDLE_OFF);
3425 imapx_idle_supported (CamelIMAPXServer *is)
3427 return (is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_IDLE) != 0 && is->use_idle);
3431 /* ********************************************************************** */
3433 imapx_command_select_done (CamelIMAPXServer *is,
3434 CamelIMAPXCommand *ic,
3435 GCancellable *cancellable,
3438 const gchar *selected_folder = NULL;
3439 gboolean success = TRUE;
3440 GError *local_error = NULL;
3442 if (camel_imapx_command_set_error_if_failed (ic, &local_error)) {
3443 GQueue failed = G_QUEUE_INIT;
3444 GQueue trash = G_QUEUE_INIT;
3445 CamelFolder *folder;
3448 c (is->tagprefix, "Select failed\n");
3450 g_mutex_lock (&is->select_lock);
3451 folder = g_weak_ref_get (&is->select_pending);
3452 g_weak_ref_set (&is->select_folder, NULL);
3453 g_weak_ref_set (&is->select_pending, NULL);
3454 is->state = IMAPX_INITIALISED;
3455 g_mutex_unlock (&is->select_lock);
3459 if (folder != NULL) {
3460 GList *head = camel_imapx_command_queue_peek_head_link (is->queue);
3462 for (link = head; link != NULL; link = g_list_next (link)) {
3463 CamelIMAPXCommand *cw = link->data;
3465 if (cw->select && cw->select == folder) {
3467 is->tagprefix, "Cancelling command '%s'(%p) for folder '%s'\n",
3468 cw->name, cw, camel_folder_get_full_name (cw->select));
3469 g_queue_push_tail (&trash, link);
3473 g_object_unref (folder);
3476 while ((link = g_queue_pop_head (&trash)) != NULL) {
3477 CamelIMAPXCommand *cw = link->data;
3478 camel_imapx_command_queue_delete_link (is->queue, link);
3479 g_queue_push_tail (&failed, cw);
3484 while (!g_queue_is_empty (&failed)) {
3485 CamelIMAPXCommand *cw;
3488 cw = g_queue_pop_head (&failed);
3489 job = camel_imapx_command_get_job (cw);
3491 if (!CAMEL_IS_IMAPX_JOB (job)) {
3492 g_warn_if_reached ();
3496 camel_imapx_job_cancel (job);
3499 cw->status = imapx_copy_status (ic->status);
3501 cw->complete (is, cw, NULL, NULL);
3504 g_propagate_error (error, local_error);
3508 CamelFolder *folder;
3509 CamelIMAPXFolder *ifolder;
3511 c (is->tagprefix, "Select ok!\n");
3513 g_mutex_lock (&is->select_lock);
3514 folder = g_weak_ref_get (&is->select_pending);
3515 g_weak_ref_set (&is->select_folder, folder);
3516 g_weak_ref_set (&is->select_pending, NULL);
3517 is->state = IMAPX_SELECTED;
3518 g_mutex_unlock (&is->select_lock);
3520 /* We should have a strong reference
3521 * on the newly-selected CamelFolder. */
3522 g_return_val_if_fail (folder != NULL, FALSE);
3523 ifolder = CAMEL_IMAPX_FOLDER (folder);
3525 ifolder->exists_on_server = is->exists;
3526 ifolder->modseq_on_server = is->highestmodseq;
3527 if (ifolder->uidnext_on_server < is->uidnext) {
3528 /* We don't want to fetch new messages if the command we selected this
3529 * folder for is *already* fetching all messages (i.e. scan_changes).
3531 CamelIMAPXJob *job = imapx_is_job_in_queue (
3532 is, folder, IMAPX_JOB_REFRESH_INFO, NULL);
3534 RefreshInfoData *data = camel_imapx_job_get_data (job);
3536 if (data->scan_changes) {
3537 c (is->tagprefix, "Will not fetch_new_messages when already in scan_changes\n");
3541 imapx_server_fetch_new_messages (is, folder, TRUE, TRUE, NULL, NULL);
3542 /* We don't do this right now because we want the new messages to
3543 * update the unseen count. */
3544 //ifolder->uidnext_on_server = is->uidnext;
3548 ifolder->uidvalidity_on_server = is->uidvalidity;
3549 selected_folder = camel_folder_get_full_name (folder);
3551 if (is->uidvalidity && is->uidvalidity != ((CamelIMAPXSummary *) folder->summary)->validity)
3552 invalidate_local_cache (ifolder, is->uidvalidity);
3554 #if 0 /* see comment for disabled bits in imapx_job_refresh_info_start() */
3555 /* This should trigger a new messages scan */
3556 if (is->exists != folder->summary->root_view->total_count)
3558 "exists is %d our summary is %d and summary exists is %d\n", is->exists,
3559 folder->summary->root_view->total_count,
3560 ((CamelIMAPXSummary *) folder->summary)->exists);
3563 g_object_unref (folder);
3566 camel_imapx_command_unref (ic);
3568 g_signal_emit (is, signals[SELECT_CHANGED], 0, selected_folder);
3573 /* Should have a queue lock. TODO Change the way select is written */
3575 imapx_select (CamelIMAPXServer *is,
3576 CamelFolder *folder,
3578 GCancellable *cancellable,
3581 CamelIMAPXCommand *ic;
3582 CamelFolder *select_folder;
3583 CamelFolder *select_pending;
3584 gboolean nothing_to_do = FALSE;
3586 /* Select is complicated by the fact we may have commands
3587 * active on the server for a different selection.
3589 * So this waits for any commands to complete, selects the
3590 * new folder, and halts the queuing of any new commands.
3591 * It is assumed whomever called is us about to issue
3592 * a high-priority command anyway */
3594 /* TODO check locking here, pending_select will do
3595 * most of the work for normal commands, but not
3596 * for another select */
3598 g_mutex_lock (&is->select_lock);
3600 select_folder = g_weak_ref_get (&is->select_folder);
3601 select_pending = g_weak_ref_get (&is->select_pending);
3603 if (select_pending != NULL) {
3604 nothing_to_do = TRUE;
3605 } else if (select_folder == folder && !forced) {
3606 nothing_to_do = TRUE;
3607 } else if (!camel_imapx_command_queue_is_empty (is->active)) {
3608 nothing_to_do = TRUE;
3610 g_weak_ref_set (&is->select_pending, folder);
3612 if (select_folder != NULL) {
3613 g_weak_ref_set (&is->select_folder, NULL);
3615 /* If no folder was selected, we won't get a
3616 * [CLOSED] status so just point select_folder
3617 * at the newly-selected folder immediately. */
3618 g_weak_ref_set (&is->select_folder, folder);
3621 is->uidvalidity = 0;
3623 is->highestmodseq = 0;
3624 is->permanentflags = 0;
3630 /* Hrm, what about reconnecting? */
3631 is->state = IMAPX_INITIALISED;
3634 g_clear_object (&select_folder);
3635 g_clear_object (&select_pending);
3637 g_mutex_unlock (&is->select_lock);
3642 ic = camel_imapx_command_new (
3643 is, "SELECT", NULL, "SELECT %f", folder);
3645 if (is->use_qresync) {
3646 CamelIMAPXSummary *isum = (CamelIMAPXSummary *) folder->summary;
3647 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
3648 gint total = camel_folder_summary_count (folder->summary);
3649 gchar *firstuid, *lastuid;
3651 if (total && isum->modseq && ifolder->uidvalidity_on_server) {
3653 firstuid = imapx_get_uid_from_index (folder->summary, 0);
3654 lastuid = imapx_get_uid_from_index (folder->summary, total - 1);
3657 is->tagprefix, "SELECT QRESYNC %" G_GUINT64_FORMAT
3658 " %" G_GUINT64_FORMAT "\n",
3659 ifolder->uidvalidity_on_server, isum->modseq);
3661 camel_imapx_command_add (
3663 G_GUINT64_FORMAT " %"
3664 G_GUINT64_FORMAT " %s:%s",
3665 ifolder->uidvalidity_on_server,
3674 GString *seqs, *uids;
3676 seqs = g_string_new (" ");
3677 uids = g_string_new (")");
3679 /* Include some seq/uid pairs to avoid a huge VANISHED list
3680 * (see RFC5162 §3.1). Work backwards exponentially from the
3681 * end of the mailbox, starting with the message 9 from the
3682 * end, then 27 from the end, then 81 from the end... */
3691 if (i != 9) { /* If not the first time */
3692 g_string_prepend (seqs, ",");
3693 g_string_prepend (uids, ",");
3696 /* IMAP sequence numbers are one higher than the corresponding
3697 * indices in our folder summary -- they start from one, while
3698 * the summary starts from zero. */
3699 sprintf (buf, "%d", total - i + 1);
3700 g_string_prepend (seqs, buf);
3701 uid = imapx_get_uid_from_index (folder->summary, total - i);
3702 g_string_prepend (uids, uid);
3704 } while (i < total);
3706 g_string_prepend (seqs, " (");
3708 c (is->tagprefix, "adding QRESYNC seq/uidset %s%s\n", seqs->str, uids->str);
3709 camel_imapx_command_add (ic, seqs->str);
3710 camel_imapx_command_add (ic, uids->str);
3712 g_string_free (seqs, TRUE);
3713 g_string_free (uids, TRUE);
3716 camel_imapx_command_add (ic, "))");
3720 ic->complete = imapx_command_select_done;
3721 imapx_command_start (is, ic, cancellable, error);
3728 /* Using custom commands to connect to IMAP servers is not supported on Win32 */
3730 static CamelStream *
3731 connect_to_server_process (CamelIMAPXServer *is,
3735 CamelNetworkSettings *network_settings;
3736 CamelProvider *provider;
3737 CamelSettings *settings;
3738 CamelStream *cmd_stream;
3739 CamelStream *imapx_stream;
3740 CamelIMAPXStore *store;
3746 gchar *child_env[7];
3747 const gchar *password;
3752 memset (&url, 0, sizeof (CamelURL));
3754 store = camel_imapx_server_ref_store (is);
3756 password = camel_service_get_password (CAMEL_SERVICE (store));
3757 provider = camel_service_get_provider (CAMEL_SERVICE (store));
3758 settings = camel_service_ref_settings (CAMEL_SERVICE (store));
3760 network_settings = CAMEL_NETWORK_SETTINGS (settings);
3761 host = camel_network_settings_dup_host (network_settings);
3762 port = camel_network_settings_get_port (network_settings);
3763 user = camel_network_settings_dup_user (network_settings);
3765 /* Put full details in the environment, in case the connection
3766 * program needs them */
3767 camel_url_set_protocol (&url, provider->protocol);
3768 camel_url_set_host (&url, host);
3769 camel_url_set_port (&url, port);
3770 camel_url_set_user (&url, user);
3771 buf = camel_url_to_string (&url, 0);
3772 child_env[i++] = g_strdup_printf ("URL=%s", buf);
3775 child_env[i++] = g_strdup_printf ("URLHOST=%s", host);
3777 child_env[i++] = g_strdup_printf ("URLPORT=%u", port);
3779 child_env[i++] = g_strdup_printf ("URLUSER=%s", user);
3781 child_env[i++] = g_strdup_printf ("URLPASSWD=%s", password);
3782 child_env[i] = NULL;
3784 g_object_unref (settings);
3785 g_object_unref (store);
3787 /* Now do %h, %u, etc. substitution in cmd */
3788 buf = cmd_copy = g_strdup (cmd);
3790 full_cmd = g_strdup ("");
3798 pc = strchr (buf, '%');
3801 tmp = g_strdup_printf ("%s%s", full_cmd, buf);
3820 /* If there wasn't a valid %-code, with an actual
3821 * variable to insert, pretend we didn't see the % */
3822 pc = strchr (pc + 1, '%');
3825 tmp = g_strdup_printf ("%s%.*s%s", full_cmd, len, buf, var);
3836 cmd_stream = camel_stream_process_new ();
3838 ret = camel_stream_process_connect (
3839 CAMEL_STREAM_PROCESS (cmd_stream),
3840 full_cmd, (const gchar **) child_env, error);
3843 g_free (child_env[--i]);
3846 g_object_unref (cmd_stream);
3853 imapx_stream = camel_imapx_stream_new (cmd_stream);
3855 g_object_unref (cmd_stream);
3857 /* Server takes ownership of the IMAPX stream. */
3858 g_mutex_lock (&is->priv->stream_lock);
3859 g_warn_if_fail (is->priv->stream == NULL);
3860 is->priv->stream = CAMEL_IMAPX_STREAM (imapx_stream);
3861 is->is_process_stream = TRUE;
3862 g_mutex_unlock (&is->priv->stream_lock);
3864 g_object_notify (G_OBJECT (is), "stream");
3866 return imapx_stream;
3868 #endif /* G_OS_WIN32 */
3871 imapx_connect_to_server (CamelIMAPXServer *is,
3872 GCancellable *cancellable,
3875 CamelNetworkSettings *network_settings;
3876 CamelNetworkSecurityMethod method;
3877 CamelStream *tcp_stream = NULL;
3878 CamelStream *imapx_stream = NULL;
3879 CamelSockOptData sockopt;
3880 CamelIMAPXStore *store;
3881 CamelSettings *settings;
3885 CamelIMAPXCommand *ic;
3886 gboolean success = TRUE;
3888 GError *local_error = NULL;
3891 gboolean use_shell_command;
3892 gchar *shell_command = NULL;
3895 store = camel_imapx_server_ref_store (is);
3897 settings = camel_service_ref_settings (CAMEL_SERVICE (store));
3899 network_settings = CAMEL_NETWORK_SETTINGS (settings);
3900 host = camel_network_settings_dup_host (network_settings);
3901 method = camel_network_settings_get_security_method (network_settings);
3904 use_shell_command = camel_imapx_settings_get_use_shell_command (
3905 CAMEL_IMAPX_SETTINGS (settings));
3907 if (use_shell_command)
3908 shell_command = camel_imapx_settings_dup_shell_command (
3909 CAMEL_IMAPX_SETTINGS (settings));
3912 g_object_unref (settings);
3915 if (shell_command != NULL) {
3916 imapx_stream = connect_to_server_process (
3917 is, shell_command, &local_error);
3919 g_free (shell_command);
3921 if (imapx_stream != NULL)
3928 tcp_stream = camel_network_service_connect_sync (
3929 CAMEL_NETWORK_SERVICE (store), cancellable, error);
3931 if (tcp_stream == NULL) {
3937 * We send a lot of small requests which nagle slows down. */
3938 sockopt.option = CAMEL_SOCKOPT_NODELAY;
3939 sockopt.value.no_delay = TRUE;
3940 camel_tcp_stream_setsockopt (CAMEL_TCP_STREAM (tcp_stream), &sockopt);
3943 * Needed for some hosts/router configurations, we're idle a lot. */
3944 sockopt.option = CAMEL_SOCKOPT_KEEPALIVE;
3945 sockopt.value.keep_alive = TRUE;
3946 camel_tcp_stream_setsockopt (CAMEL_TCP_STREAM (tcp_stream), &sockopt);
3948 imapx_stream = camel_imapx_stream_new (tcp_stream);
3950 /* CamelIMAPXServer takes ownership of the IMAPX stream.
3951 * We need to set this right away for imapx_command_run()
3952 * to work, but we delay emitting a "notify" signal until
3953 * we're fully connected. */
3954 g_mutex_lock (&is->priv->stream_lock);
3955 g_warn_if_fail (is->priv->stream == NULL);
3956 is->priv->stream = CAMEL_IMAPX_STREAM (imapx_stream);
3957 g_mutex_unlock (&is->priv->stream_lock);
3959 g_object_unref (tcp_stream);
3962 CAMEL_IMAPX_STREAM (imapx_stream)->tagprefix = is->tagprefix;
3965 // poll ? wait for other stuff? loop?
3966 if (camel_application_is_exiting || is->parser_quit) {
3969 G_IO_ERROR_CANCELLED,
3970 "Connection to server cancelled\n");
3975 tok = camel_imapx_stream_token (
3976 CAMEL_IMAPX_STREAM (imapx_stream),
3977 &token, &len, cancellable, error);
3985 is, CAMEL_IMAPX_STREAM (imapx_stream),
3986 cancellable, error);
3989 camel_imapx_stream_ungettoken (
3990 CAMEL_IMAPX_STREAM (imapx_stream), tok, token, len);
3992 success = camel_imapx_stream_text (
3993 CAMEL_IMAPX_STREAM (imapx_stream),
3994 &token, cancellable, error);
3997 e (is->tagprefix, "Got unexpected line before greeting: '%s'\n", token);
4002 ic = camel_imapx_command_new (
4003 is, "CAPABILITY", NULL, "CAPABILITY");
4004 if (!imapx_command_run (is, ic, cancellable, error)) {
4005 camel_imapx_command_unref (ic);
4010 /* Server reported error. */
4011 if (ic->status->result != IMAPX_OK) {
4014 CAMEL_ERROR_GENERIC,
4015 "%s", ic->status->text);
4016 camel_imapx_command_unref (ic);
4021 camel_imapx_command_unref (ic);
4024 if (method == CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT) {
4026 if (is->cinfo && !(is->cinfo->capa & IMAPX_CAPABILITY_STARTTLS)) {
4028 &local_error, CAMEL_ERROR,
4029 CAMEL_ERROR_GENERIC,
4030 _("Failed to connect to IMAP server %s in secure mode: %s"),
4031 host, _("STARTTLS not supported"));
4035 ic = camel_imapx_command_new (
4036 is, "STARTTLS", NULL, "STARTTLS");
4037 if (!imapx_command_run (is, ic, cancellable, error)) {
4038 camel_imapx_command_unref (ic);
4043 /* Server reported error. */
4044 if (ic->status->result != IMAPX_OK) {
4047 CAMEL_ERROR_GENERIC,
4048 "%s", ic->status->text);
4049 camel_imapx_command_unref (ic);
4054 /* See if we got new capabilities in the STARTTLS response */
4055 imapx_free_capability (is->cinfo);
4057 if (ic->status->condition == IMAPX_CAPABILITY) {
4058 is->cinfo = ic->status->u.cinfo;
4059 ic->status->u.cinfo = NULL;
4060 c (is->tagprefix, "got capability flags %08x\n", is->cinfo ? is->cinfo->capa : 0xFFFFFFFF);
4063 camel_imapx_command_unref (ic);
4065 if (camel_tcp_stream_ssl_enable_ssl (
4066 CAMEL_TCP_STREAM_SSL (tcp_stream),
4067 cancellable, &local_error) == -1) {
4070 _("Failed to connect to IMAP server %s in secure mode: "),
4074 /* Get new capabilities if they weren't already given */
4076 ic = camel_imapx_command_new (
4077 is, "CAPABILITY", NULL, "CAPABILITY");
4078 if (!imapx_command_run (is, ic, cancellable, error)) {
4079 camel_imapx_command_unref (ic);
4084 camel_imapx_command_unref (ic);
4090 g_object_notify (G_OBJECT (is), "stream");
4092 g_mutex_lock (&is->priv->stream_lock);
4094 if (is->priv->stream != NULL) {
4095 g_object_unref (is->priv->stream);
4096 is->priv->stream = NULL;
4099 if (is->cinfo != NULL) {
4100 imapx_free_capability (is->cinfo);
4104 g_mutex_unlock (&is->priv->stream_lock);
4109 g_object_unref (store);
4114 CamelAuthenticationResult
4115 camel_imapx_server_authenticate (CamelIMAPXServer *is,
4116 const gchar *mechanism,
4117 GCancellable *cancellable,
4120 CamelNetworkSettings *network_settings;
4121 CamelIMAPXStore *store;
4122 CamelService *service;
4123 CamelSettings *settings;
4124 CamelAuthenticationResult result;
4125 CamelIMAPXCommand *ic;
4126 CamelSasl *sasl = NULL;
4130 g_return_val_if_fail (
4131 CAMEL_IS_IMAPX_SERVER (is),
4132 CAMEL_AUTHENTICATION_REJECTED);
4134 store = camel_imapx_server_ref_store (is);
4136 service = CAMEL_SERVICE (store);
4137 settings = camel_service_ref_settings (service);
4139 network_settings = CAMEL_NETWORK_SETTINGS (settings);
4140 host = camel_network_settings_dup_host (network_settings);
4141 user = camel_network_settings_dup_user (network_settings);
4143 g_object_unref (settings);
4145 if (mechanism != NULL) {
4146 if (is->cinfo && !g_hash_table_lookup (is->cinfo->auth_types, mechanism)) {
4148 error, CAMEL_SERVICE_ERROR,
4149 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
4150 _("IMAP server %s does not support %s "
4151 "authentication"), host, mechanism);
4152 result = CAMEL_AUTHENTICATION_ERROR;
4156 sasl = camel_sasl_new ("imap", mechanism, service);
4159 error, CAMEL_SERVICE_ERROR,
4160 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
4161 _("No support for %s authentication"),
4163 result = CAMEL_AUTHENTICATION_ERROR;
4169 ic = camel_imapx_command_new (
4170 is, "AUTHENTICATE", NULL, "AUTHENTICATE %A", sasl);
4172 const gchar *password;
4174 password = camel_service_get_password (service);
4177 g_set_error_literal (
4178 error, CAMEL_SERVICE_ERROR,
4179 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
4180 _("Cannot authenticate without a username"));
4181 result = CAMEL_AUTHENTICATION_ERROR;
4185 if (password == NULL) {
4186 g_set_error_literal (
4187 error, CAMEL_SERVICE_ERROR,
4188 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
4189 _("Authentication password not available"));
4190 result = CAMEL_AUTHENTICATION_ERROR;
4194 ic = camel_imapx_command_new (
4195 is, "LOGIN", NULL, "LOGIN %s %s", user, password);
4198 if (!imapx_command_run (is, ic, cancellable, error))
4199 result = CAMEL_AUTHENTICATION_ERROR;
4200 else if (ic->status->result == IMAPX_OK)
4201 result = CAMEL_AUTHENTICATION_ACCEPTED;
4203 result = CAMEL_AUTHENTICATION_REJECTED;
4205 /* Forget old capabilities after login. */
4206 if (result == CAMEL_AUTHENTICATION_ACCEPTED) {
4208 imapx_free_capability (is->cinfo);
4212 if (ic->status->condition == IMAPX_CAPABILITY) {
4213 is->cinfo = ic->status->u.cinfo;
4214 ic->status->u.cinfo = NULL;
4215 c (is->tagprefix, "got capability flags %08x\n", is->cinfo ? is->cinfo->capa : 0xFFFFFFFF);
4219 camel_imapx_command_unref (ic);
4222 g_object_unref (sasl);
4228 g_object_unref (store);
4234 imapx_reconnect (CamelIMAPXServer *is,
4235 GCancellable *cancellable,
4238 CamelIMAPXCommand *ic;
4239 CamelService *service;
4240 CamelSession *session;
4241 CamelIMAPXStore *store;
4242 CamelSettings *settings;
4245 gboolean use_qresync;
4246 gboolean success = FALSE;
4248 store = camel_imapx_server_ref_store (is);
4250 service = CAMEL_SERVICE (store);
4251 session = camel_service_ref_session (service);
4253 settings = camel_service_ref_settings (service);
4255 mechanism = camel_network_settings_dup_auth_mechanism (
4256 CAMEL_NETWORK_SETTINGS (settings));
4258 use_idle = camel_imapx_settings_get_use_idle (
4259 CAMEL_IMAPX_SETTINGS (settings));
4261 use_qresync = camel_imapx_settings_get_use_qresync (
4262 CAMEL_IMAPX_SETTINGS (settings));
4264 g_object_unref (settings);
4266 if (!imapx_connect_to_server (is, cancellable, error))
4269 if (is->state == IMAPX_AUTHENTICATED)
4272 if (!camel_session_authenticate_sync (
4273 session, service, mechanism, cancellable, error))
4276 /* After login we re-capa unless the server already told us */
4278 ic = camel_imapx_command_new (
4279 is, "CAPABILITY", NULL, "CAPABILITY");
4280 if (!imapx_command_run (is, ic, cancellable, error)) {
4281 camel_imapx_command_unref (ic);
4285 camel_imapx_command_unref (ic);
4288 is->state = IMAPX_AUTHENTICATED;
4291 is->use_idle = use_idle;
4293 if (imapx_idle_supported (is))
4294 imapx_init_idle (is);
4296 /* Fetch namespaces */
4297 if (is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_NAMESPACE) != 0) {
4298 ic = camel_imapx_command_new (
4299 is, "NAMESPACE", NULL, "NAMESPACE");
4300 if (!imapx_command_run (is, ic, cancellable, error)) {
4301 camel_imapx_command_unref (ic);
4305 camel_imapx_command_unref (ic);
4308 if (use_qresync && is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_QRESYNC) != 0) {
4309 ic = camel_imapx_command_new (
4310 is, "ENABLE", NULL, "ENABLE CONDSTORE QRESYNC");
4311 if (!imapx_command_run (is, ic, cancellable, error)) {
4312 camel_imapx_command_unref (ic);
4316 camel_imapx_command_unref (ic);
4318 is->use_qresync = TRUE;
4320 is->use_qresync = FALSE;
4322 if (store->summary->namespaces == NULL) {
4323 CamelIMAPXNamespaceList *nsl = NULL;
4324 CamelIMAPXStoreNamespace *ns = NULL;
4326 /* set a default namespace */
4327 nsl = g_malloc0 (sizeof (CamelIMAPXNamespaceList));
4328 ns = g_new0 (CamelIMAPXStoreNamespace, 1);
4330 ns->path = g_strdup ("");
4331 ns->full_name = g_strdup ("");
4335 store->summary->namespaces = nsl;
4336 /* FIXME needs to be identified from list response */
4337 store->dir_sep = ns->sep;
4340 is->state = IMAPX_INITIALISED;
4348 imapx_disconnect (is);
4351 imapx_free_capability (is->cinfo);
4358 g_object_unref (session);
4359 g_object_unref (store);
4364 /* ********************************************************************** */
4367 imapx_command_fetch_message_done (CamelIMAPXServer *is,
4368 CamelIMAPXCommand *ic,
4369 GCancellable *cancellable,
4373 CamelFolder *folder;
4374 GetMessageData *data;
4375 CamelIMAPXFolder *ifolder;
4376 gboolean success = TRUE;
4377 GError *local_error = NULL;
4379 job = camel_imapx_command_get_job (ic);
4380 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4382 data = camel_imapx_job_get_data (job);
4383 g_return_val_if_fail (data != NULL, FALSE);
4385 folder = camel_imapx_job_ref_folder (job);
4386 g_return_val_if_fail (folder != NULL, FALSE);
4388 /* We either have more to fetch (partial mode?), we are complete,
4389 * or we failed. Failure is handled in the fetch code, so
4390 * we just return the job, or keep it alive with more requests */
4394 if (camel_imapx_command_set_error_if_failed (ic, &local_error)) {
4396 &local_error, "%s: ",
4397 _("Error fetching message"));
4398 data->body_len = -1;
4400 } else if (data->use_multi_fetch) {
4401 gsize really_fetched = g_seekable_tell (G_SEEKABLE (data->stream));
4402 /* Don't automatically stop when we reach the reported message
4403 * size -- some crappy servers (like Microsoft Exchange) have
4404 * a tendency to lie about it. Keep going (one request at a
4405 * time) until the data actually stop coming. */
4406 if (data->fetch_offset < data->size ||
4407 data->fetch_offset == really_fetched) {
4408 CamelIMAPXCommand *new_ic;
4410 camel_operation_progress (
4412 (data->fetch_offset *100) / data->size);
4414 new_ic = camel_imapx_command_new (
4415 is, "FETCH", folder,
4416 "UID FETCH %t (BODY.PEEK[]",
4418 camel_imapx_command_add (new_ic, "<%u.%u>", data->fetch_offset, MULTI_SIZE);
4419 camel_imapx_command_add (new_ic, ")");
4420 new_ic->complete = imapx_command_fetch_message_done;
4421 camel_imapx_command_set_job (new_ic, job);
4422 new_ic->pri = job->pri - 1;
4423 data->fetch_offset += MULTI_SIZE;
4426 success = imapx_command_queue (
4427 is, new_ic, cancellable, error);
4433 /* If we have more messages to fetch, skip the rest. */
4434 if (job->commands > 0)
4437 /* No more messages to fetch, let's wrap things up. */
4439 ifolder = CAMEL_IMAPX_FOLDER (folder);
4441 /* return the exception from last command */
4442 if (local_error != NULL) {
4443 if (data->stream != NULL) {
4444 g_object_unref (data->stream);
4445 data->stream = NULL;
4448 g_propagate_error (error, local_error);
4452 } else if (data->stream != NULL) {
4454 (camel_stream_flush (
4455 data->stream, cancellable, error) == 0) &&
4456 (camel_stream_close (
4457 data->stream, cancellable, error) == 0);
4460 gchar *cur_filename;
4461 gchar *tmp_filename;
4464 cur_filename = camel_data_cache_get_filename (
4465 ifolder->cache, "cur", data->uid);
4467 tmp_filename = camel_data_cache_get_filename (
4468 ifolder->cache, "tmp", data->uid);
4470 dirname = g_path_get_dirname (cur_filename);
4471 g_mkdir_with_parents (dirname, 0700);
4474 if (g_rename (tmp_filename, cur_filename) != 0)
4476 error, G_FILE_ERROR,
4477 g_file_error_from_errno (errno),
4479 _("Failed to copy the tmp file"),
4480 g_strerror (errno));
4482 g_free (cur_filename);
4483 g_free (tmp_filename);
4485 /* Exchange the "tmp" stream for the "cur" stream. */
4486 g_object_unref (data->stream);
4487 data->stream = camel_data_cache_get (
4488 ifolder->cache, "cur", data->uid, error);
4489 success = (data->stream != NULL);
4493 _("Failed to close the tmp stream"));
4497 camel_data_cache_remove (ifolder->cache, "tmp", data->uid, NULL);
4498 imapx_unregister_job (is, job);
4501 g_object_unref (folder);
4503 camel_imapx_command_unref (ic);
4505 g_clear_error (&local_error);
4511 imapx_job_get_message_start (CamelIMAPXJob *job,
4512 CamelIMAPXServer *is,
4513 GCancellable *cancellable,
4516 CamelFolder *folder;
4517 CamelIMAPXCommand *ic;
4518 GetMessageData *data;
4520 gboolean success = TRUE;
4522 data = camel_imapx_job_get_data (job);
4523 g_return_val_if_fail (data != NULL, FALSE);
4525 folder = camel_imapx_job_ref_folder (job);
4526 g_return_val_if_fail (folder != NULL, FALSE);
4528 if (data->use_multi_fetch) {
4529 for (i = 0; i < 3 && data->fetch_offset < data->size; i++) {
4530 ic = camel_imapx_command_new (
4531 is, "FETCH", folder,
4532 "UID FETCH %t (BODY.PEEK[]",
4534 camel_imapx_command_add (ic, "<%u.%u>", data->fetch_offset, MULTI_SIZE);
4535 camel_imapx_command_add (ic, ")");
4536 ic->complete = imapx_command_fetch_message_done;
4537 camel_imapx_command_set_job (ic, job);
4539 data->fetch_offset += MULTI_SIZE;
4542 success = imapx_command_queue (
4543 is, ic, cancellable, error);
4548 ic = camel_imapx_command_new (
4549 is, "FETCH", folder,
4550 "UID FETCH %t (BODY.PEEK[])",
4552 ic->complete = imapx_command_fetch_message_done;
4553 camel_imapx_command_set_job (ic, job);
4557 success = imapx_command_queue (is, ic, cancellable, error);
4560 g_object_unref (folder);
4566 imapx_job_get_message_matches (CamelIMAPXJob *job,
4567 CamelFolder *folder,
4570 GetMessageData *data;
4572 data = camel_imapx_job_get_data (job);
4573 g_return_val_if_fail (data != NULL, FALSE);
4575 if (!camel_imapx_job_has_folder (job, folder))
4578 if (g_strcmp0 (uid, data->uid) != 0)
4584 /* ********************************************************************** */
4587 imapx_command_copy_messages_step_done (CamelIMAPXServer *is,
4588 CamelIMAPXCommand *ic,
4589 GCancellable *cancellable,
4593 CamelFolder *folder;
4594 CopyMessagesData *data;
4597 gboolean success = TRUE;
4599 job = camel_imapx_command_get_job (ic);
4600 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4602 data = camel_imapx_job_get_data (job);
4603 g_return_val_if_fail (data != NULL, FALSE);
4605 folder = camel_imapx_job_ref_folder (job);
4606 g_return_val_if_fail (folder != NULL, FALSE);
4611 if (camel_imapx_command_set_error_if_failed (ic, error)) {
4614 _("Error copying messages"));
4619 if (data->delete_originals) {
4622 for (j = data->last_index; j < i; j++)
4623 camel_folder_delete_message (folder, uids->pdata[j]);
4626 /* TODO Copy the summary and cached messages to the new folder.
4627 * We might need a sorted insert to avoid refreshing the dest
4629 if (ic->status && ic->status->condition == IMAPX_COPYUID) {
4632 for (i = 0; i < ic->status->u.copyuid.copied_uids->len; i++) {
4633 guint32 uid = GPOINTER_TO_UINT (g_ptr_array_index (ic->status->u.copyuid.copied_uids, i));
4634 gchar *str = g_strdup_printf ("%d",uid);
4635 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) data->dest;
4637 g_hash_table_insert (ifolder->ignore_recent, str, GINT_TO_POINTER (1));
4642 if (i < uids->len) {
4643 g_object_unref (folder);
4645 camel_imapx_command_unref (ic);
4647 return imapx_command_copy_messages_step_start (
4648 is, job, i, cancellable, error);
4652 g_object_unref (folder);
4654 imapx_unregister_job (is, job);
4655 camel_imapx_command_unref (ic);
4661 imapx_command_copy_messages_step_start (CamelIMAPXServer *is,
4664 GCancellable *cancellable,
4667 CamelFolder *folder;
4668 CamelIMAPXCommand *ic;
4669 CopyMessagesData *data;
4673 data = camel_imapx_job_get_data (job);
4674 g_return_val_if_fail (data != NULL, FALSE);
4676 folder = camel_imapx_job_ref_folder (job);
4677 g_return_val_if_fail (folder != NULL, FALSE);
4681 ic = camel_imapx_command_new (is, "COPY", folder, "UID COPY ");
4682 ic->complete = imapx_command_copy_messages_step_done;
4683 camel_imapx_command_set_job (ic, job);
4685 data->last_index = i;
4687 g_object_unref (folder);
4689 for (; i < uids->len; i++) {
4691 const gchar *uid = (gchar *) g_ptr_array_index (uids, i);
4693 res = imapx_uidset_add (&data->uidset, ic, uid);
4695 camel_imapx_command_add (ic, " %f", data->dest);
4696 data->index = i + 1;
4697 return imapx_command_queue (is, ic, cancellable, error);
4702 if (imapx_uidset_done (&data->uidset, ic)) {
4703 camel_imapx_command_add (ic, " %f", data->dest);
4704 return imapx_command_queue (is, ic, cancellable, error);
4711 imapx_job_copy_messages_start (CamelIMAPXJob *job,
4712 CamelIMAPXServer *is,
4713 GCancellable *cancellable,
4716 CamelFolder *folder;
4717 CopyMessagesData *data;
4720 data = camel_imapx_job_get_data (job);
4721 g_return_val_if_fail (data != NULL, FALSE);
4723 folder = camel_imapx_job_ref_folder (job);
4724 g_return_val_if_fail (folder != NULL, FALSE);
4726 success = imapx_server_sync_changes (
4727 is, folder, job->type, job->pri, cancellable, error);
4729 imapx_unregister_job (is, job);
4731 /* XXX Should we still do this even if a failure occurred? */
4732 g_ptr_array_sort (data->uids, (GCompareFunc) imapx_uids_array_cmp);
4733 imapx_uidset_init (&data->uidset, 0, MAX_COMMAND_LEN);
4735 g_object_unref (folder);
4737 return imapx_command_copy_messages_step_start (
4738 is, job, 0, cancellable, error);
4741 /* ********************************************************************** */
4744 imapx_command_append_message_done (CamelIMAPXServer *is,
4745 CamelIMAPXCommand *ic,
4746 GCancellable *cancellable,
4750 CamelIMAPXFolder *ifolder;
4751 CamelFolder *folder;
4752 CamelMessageInfo *mi;
4753 AppendMessageData *data;
4754 gchar *cur, *old_uid;
4755 gboolean success = TRUE;
4757 job = camel_imapx_command_get_job (ic);
4758 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4760 data = camel_imapx_job_get_data (job);
4761 g_return_val_if_fail (data != NULL, FALSE);
4763 folder = camel_imapx_job_ref_folder (job);
4764 g_return_val_if_fail (folder != NULL, FALSE);
4766 ifolder = CAMEL_IMAPX_FOLDER (folder);
4768 /* Append done. If we the server supports UIDPLUS we will get
4769 * an APPENDUID response with the new uid. This lets us move the
4770 * message we have directly to the cache and also create a correctly
4771 * numbered MessageInfo, without losing any information. Otherwise
4772 * we have to wait for the server to let us know it was appended. */
4774 mi = camel_message_info_clone (data->info);
4775 old_uid = g_strdup (data->info->uid);
4777 if (camel_imapx_command_set_error_if_failed (ic, error)) {
4780 _("Error appending message"));
4783 } else if (ic->status && ic->status->condition == IMAPX_APPENDUID) {
4784 c (is->tagprefix, "Got appenduid %d %d\n", (gint) ic->status->u.appenduid.uidvalidity, (gint) ic->status->u.appenduid.uid);
4785 if (ic->status->u.appenduid.uidvalidity == ifolder->uidvalidity_on_server) {
4786 CamelFolderChangeInfo *changes;
4788 data->appended_uid = g_strdup_printf ("%u", (guint) ic->status->u.appenduid.uid);
4789 mi->uid = camel_pstring_add (data->appended_uid, FALSE);
4791 cur = camel_data_cache_get_filename (ifolder->cache, "cur", mi->uid);
4792 g_rename (data->path, cur);
4794 /* should we update the message count ? */
4795 imapx_set_message_info_flags_for_new_message (
4797 ((CamelMessageInfoBase *) data->info)->flags,
4798 ((CamelMessageInfoBase *) data->info)->user_flags,
4800 camel_folder_summary_add (folder->summary, mi);
4801 changes = camel_folder_change_info_new ();
4802 camel_folder_change_info_add_uid (changes, mi->uid);
4803 camel_folder_changed (folder, changes);
4804 camel_folder_change_info_free (changes);
4808 g_message ("but uidvalidity changed \n");
4812 camel_data_cache_remove (ifolder->cache, "new", old_uid, NULL);
4815 g_object_unref (folder);
4817 imapx_unregister_job (is, job);
4818 camel_imapx_command_unref (ic);
4824 imapx_job_append_message_start (CamelIMAPXJob *job,
4825 CamelIMAPXServer *is,
4826 GCancellable *cancellable,
4829 CamelFolder *folder;
4830 CamelIMAPXCommand *ic;
4831 AppendMessageData *data;
4833 data = camel_imapx_job_get_data (job);
4834 g_return_val_if_fail (data != NULL, FALSE);
4836 folder = camel_imapx_job_ref_folder (job);
4837 g_return_val_if_fail (folder != NULL, FALSE);
4839 /* TODO: we could supply the original append date from the file timestamp */
4840 ic = camel_imapx_command_new (
4842 "APPEND %f %F %P", folder,
4843 ((CamelMessageInfoBase *) data->info)->flags,
4844 ((CamelMessageInfoBase *) data->info)->user_flags,
4846 ic->complete = imapx_command_append_message_done;
4847 camel_imapx_command_set_job (ic, job);
4851 g_object_unref (folder);
4853 return imapx_command_queue (is, ic, cancellable, error);
4856 /* ********************************************************************** */
4859 imapx_refresh_info_uid_cmp (gconstpointer ap,
4865 av = g_ascii_strtoull ((const gchar *) ap, NULL, 10);
4866 bv = g_ascii_strtoull ((const gchar *) bp, NULL, 10);
4869 return ascending ? -1 : 1;
4871 return ascending ? 1 : -1;
4877 imapx_uids_array_cmp (gconstpointer ap,
4880 const gchar **a = (const gchar **) ap;
4881 const gchar **b = (const gchar **) bp;
4883 return imapx_refresh_info_uid_cmp (*a, *b, TRUE);
4887 imapx_refresh_info_cmp (gconstpointer ap,
4890 const struct _refresh_info *a = ap;
4891 const struct _refresh_info *b = bp;
4893 return imapx_refresh_info_uid_cmp (a->uid, b->uid, TRUE);
4897 imapx_refresh_info_cmp_descending (gconstpointer ap,
4900 const struct _refresh_info *a = ap;
4901 const struct _refresh_info *b = bp;
4903 return imapx_refresh_info_uid_cmp (a->uid, b->uid, FALSE);
4907 /* skips over non-server uids (pending appends) */
4909 imapx_index_next (GPtrArray *uids,
4910 CamelFolderSummary *s,
4914 while (index < uids->len) {
4915 CamelMessageInfo *info;
4918 if (index >= uids->len)
4921 info = camel_folder_summary_get (s, g_ptr_array_index (uids, index));
4925 if (info && (strchr (camel_message_info_uid (info), '-') != NULL)) {
4926 camel_message_info_free (info);
4927 e ('?', "Ignoring offline uid '%s'\n", camel_message_info_uid (info));
4929 camel_message_info_free (info);
4938 imapx_command_step_fetch_done (CamelIMAPXServer *is,
4939 CamelIMAPXCommand *ic,
4940 GCancellable *cancellable,
4943 CamelIMAPXFolder *ifolder;
4944 CamelIMAPXSummary *isum;
4946 CamelFolder *folder;
4947 RefreshInfoData *data;
4949 gboolean success = TRUE;
4950 CamelIMAPXSettings *settings;
4952 gboolean mobile_mode;
4954 job = camel_imapx_command_get_job (ic);
4955 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
4957 data = camel_imapx_job_get_data (job);
4958 g_return_val_if_fail (data != NULL, FALSE);
4960 folder = camel_imapx_job_ref_folder (job);
4961 g_return_val_if_fail (folder != NULL, FALSE);
4963 data->scan_changes = FALSE;
4965 ifolder = CAMEL_IMAPX_FOLDER (folder);
4966 isum = CAMEL_IMAPX_SUMMARY (folder->summary);
4968 settings = camel_imapx_server_ref_settings (is);
4969 batch_count = camel_imapx_settings_get_batch_fetch_count (settings);
4970 mobile_mode = camel_imapx_settings_get_mobile_mode (settings);
4971 g_object_unref (settings);
4975 //printf ("%s: Mobile mode: %d Fetch Count %d\n", camel_folder_get_display_name (folder), mobile_mode, batch_count);
4976 if (camel_imapx_command_set_error_if_failed (ic, error)) {
4979 _("Error fetching message headers"));
4984 if (camel_folder_change_info_changed (data->changes)) {
4985 imapx_update_store_summary (folder);
4986 camel_folder_summary_save_to_db (folder->summary, NULL);
4987 camel_folder_changed (folder, data->changes);
4990 camel_folder_change_info_clear (data->changes);
4992 if (i < data->infos->len) {
4993 gint total = camel_folder_summary_count (folder->summary);
4994 gint fetch_limit = data->fetch_msg_limit;
4996 camel_imapx_command_unref (ic);
4998 ic = camel_imapx_command_new (
4999 is, "FETCH", folder, "UID FETCH ");
5000 ic->complete = imapx_command_step_fetch_done;
5001 camel_imapx_command_set_job (ic, job);
5002 ic->pri = job->pri - 1;
5004 //printf ("Total: %d: %d, %d, %d\n", total, fetch_limit, i, data->last_index);
5005 data->last_index = i;
5007 /* If its mobile client and when total=0 (new account setup)
5008 * fetch only one batch of mails, on futher attempts download
5009 * all new mails as per the limit. */
5010 //printf ("Total: %d: %d\n", total, fetch_limit);
5011 for (; i < data->infos->len &&
5012 (!mobile_mode || (total && i == 0) ||
5013 ((fetch_limit != -1 && i < fetch_limit) ||
5014 (fetch_limit == -1 && i < batch_count))); i++) {
5017 struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
5020 res = imapx_uidset_add (&data->uidset, ic, r->uid);
5022 camel_imapx_command_add (ic, " (RFC822.SIZE RFC822.HEADER)");
5023 data->index = i + 1;
5025 g_object_unref (folder);
5027 return imapx_command_queue (is, ic, cancellable, error);
5032 //printf ("Existing : %d Gonna fetch in %s for %d/%d\n", total, camel_folder_get_full_name (folder), i, data->infos->len);
5033 data->index = data->infos->len;
5034 if (imapx_uidset_done (&data->uidset, ic)) {
5035 camel_imapx_command_add (ic, " (RFC822.SIZE RFC822.HEADER)");
5037 g_object_unref (folder);
5039 return imapx_command_queue (is, ic, cancellable, error);
5043 if (camel_folder_summary_count (folder->summary)) {
5044 gchar *uid = imapx_get_uid_from_index (
5046 camel_folder_summary_count (folder->summary) - 1);
5047 guint64 uidl = strtoull (uid, NULL, 10);
5052 if (uidl > ifolder->uidnext_on_server) {
5054 is->tagprefix, "Updating uidnext_on_server for '%s' to %" G_GUINT64_FORMAT "\n",
5055 camel_folder_get_full_name (folder), uidl);
5056 ifolder->uidnext_on_server = uidl;
5059 isum->uidnext = ifolder->uidnext_on_server;
5062 refresh_info_data_infos_free (data);
5064 g_object_unref (folder);
5066 imapx_unregister_job (is, job);
5067 camel_imapx_command_unref (ic);
5073 imapx_uid_cmp (gconstpointer ap,
5077 const gchar *a = ap, *b = bp;
5081 av = strtoul (a, &ae, 10);
5082 bv = strtoul (b, &be, 10);
5094 return strcmp (ae, be);
5098 imapx_job_scan_changes_done (CamelIMAPXServer *is,
5099 CamelIMAPXCommand *ic,
5100 GCancellable *cancellable,
5104 CamelIMAPXSettings *settings;
5105 CamelFolder *folder;
5106 RefreshInfoData *data;
5108 gboolean success = TRUE;
5109 gboolean mobile_mode;
5111 job = camel_imapx_command_get_job (ic);
5112 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
5114 data = camel_imapx_job_get_data (job);
5115 g_return_val_if_fail (data != NULL, FALSE);
5117 folder = camel_imapx_job_ref_folder (job);
5118 g_return_val_if_fail (folder != NULL, FALSE);
5120 data->scan_changes = FALSE;
5122 settings = camel_imapx_server_ref_settings (is);
5123 uidset_size = camel_imapx_settings_get_batch_fetch_count (settings);
5124 mobile_mode = camel_imapx_settings_get_mobile_mode (settings);
5125 g_object_unref (settings);
5127 if (camel_imapx_command_set_error_if_failed (ic, error)) {
5130 _("Error retrieving message"));
5134 GCompareDataFunc uid_cmp = imapx_uid_cmp;
5135 CamelMessageInfo *s_minfo = NULL;
5136 CamelIMAPXMessageInfo *info;
5137 CamelFolderSummary *s = folder->summary;
5138 CamelIMAPXFolder *ifolder;
5139 GList *removed = NULL, *l;
5140 gboolean fetch_new = FALSE;
5145 ifolder = CAMEL_IMAPX_FOLDER (folder);
5147 /* Actually we wanted to do this after the SELECT but before the
5148 * FETCH command was issued. But this should suffice. */
5149 ((CamelIMAPXSummary *) s)->uidnext = ifolder->uidnext_on_server;
5150 ((CamelIMAPXSummary *) s)->modseq = ifolder->modseq_on_server;
5152 /* Here we do the typical sort/iterate/merge loop.
5153 * If the server flags dont match what we had, we modify our
5154 * flags to pick up what the server now has - but we merge
5157 /* FIXME: We also have to check the offline directory for
5158 * anything missing in our summary, and also queue up jobs
5159 * for all outstanding messages to be uploaded */
5161 /* obtain a copy to be thread safe */
5162 uids = camel_folder_summary_get_array (s);
5164 qsort (data->infos->data, data->infos->len, sizeof (struct _refresh_info), imapx_refresh_info_cmp);
5165 g_ptr_array_sort (uids, (GCompareFunc) imapx_uids_array_cmp);
5168 s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, 0));
5170 for (i = 0; i < data->infos->len; i++) {
5171 struct _refresh_info *r = &g_array_index (data->infos, struct _refresh_info, i);
5173 while (s_minfo && uid_cmp (camel_message_info_uid (s_minfo), r->uid, s) < 0) {
5174 const gchar *uid = camel_message_info_uid (s_minfo);
5176 camel_folder_change_info_remove_uid (data->changes, uid);
5177 removed = g_list_prepend (removed, (gpointer ) g_strdup (uid));
5178 camel_message_info_free (s_minfo);
5181 j = imapx_index_next (uids, s, j);
5183 s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
5187 if (s_minfo && uid_cmp (s_minfo->uid, r->uid, s) == 0) {
5188 info = (CamelIMAPXMessageInfo *) s_minfo;
5190 if (imapx_update_message_info_flags (
5191 (CamelMessageInfo *) info,
5193 r->server_user_flags,
5196 camel_folder_change_info_change_uid (
5198 camel_message_info_uid (s_minfo));
5204 camel_message_info_free (s_minfo);
5211 j = imapx_index_next (uids, s, j);
5213 s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
5217 camel_message_info_free (s_minfo);
5219 while (j < uids->len) {
5220 s_minfo = camel_folder_summary_get (s, g_ptr_array_index (uids, j));
5227 e (is->tagprefix, "Message %s vanished\n", s_minfo->uid);
5228 removed = g_list_prepend (removed, (gpointer) g_strdup (s_minfo->uid));
5229 camel_message_info_free (s_minfo);
5233 for (l = removed; l != NULL; l = g_list_next (l)) {
5234 gchar *uid = (gchar *) l->data;
5236 camel_folder_change_info_remove_uid (data->changes, uid);
5239 if (removed != NULL) {
5240 camel_folder_summary_remove_uids (s, removed);
5241 camel_folder_summary_touch (s);
5243 g_list_free_full (removed, (GDestroyNotify) g_free);
5246 camel_folder_summary_save_to_db (s, NULL);
5247 imapx_update_store_summary (folder);
5249 if (camel_folder_change_info_changed (data->changes))
5250 camel_folder_changed (folder, data->changes);
5251 camel_folder_change_info_clear (data->changes);
5253 camel_folder_summary_free_array (uids);
5255 /* If we have any new messages, download their headers, but only a few (100?) at a time */
5257 job->pop_operation_msg = TRUE;
5259 camel_operation_push_message (
5261 _("Fetching summary information for new messages in '%s'"),
5262 camel_folder_get_display_name (folder));
5264 imapx_uidset_init (&data->uidset, uidset_size, 0);
5265 /* These are new messages which arrived since we last knew the unseen count;
5266 * update it as they arrive. */
5267 data->update_unseen = TRUE;
5269 g_object_unref (folder);
5271 return imapx_command_step_fetch_done (
5272 is, ic, cancellable, error);
5276 refresh_info_data_infos_free (data);
5278 /* There's no sane way to get the server-side unseen count on the
5279 * select mailbox. So just work it out from the flags if its not in
5280 * mobile mode. In mobile mode we would have this filled up already
5281 * with a STATUS command.
5284 ((CamelIMAPXFolder *) folder)->unread_on_server =
5285 camel_folder_summary_get_unread_count (folder->summary);
5287 g_object_unref (folder);
5289 imapx_unregister_job (is, job);
5290 camel_imapx_command_unref (ic);
5296 imapx_job_scan_changes_start (CamelIMAPXJob *job,
5297 CamelIMAPXServer *is,
5298 GCancellable *cancellable,
5301 CamelFolder *folder;
5302 CamelIMAPXCommand *ic;
5303 RefreshInfoData *data;
5304 CamelIMAPXSettings *settings;
5305 gboolean mobile_mode;
5308 data = camel_imapx_job_get_data (job);
5309 g_return_val_if_fail (data != NULL, FALSE);
5311 folder = camel_imapx_job_ref_folder (job);
5312 g_return_val_if_fail (folder != NULL, FALSE);
5314 settings = camel_imapx_server_ref_settings (is);
5315 mobile_mode = camel_imapx_settings_get_mobile_mode (settings);
5316 g_object_unref (settings);
5319 uid = imapx_get_uid_from_index (folder->summary, 0);
5321 job->pop_operation_msg = TRUE;
5323 camel_operation_push_message (
5325 _("Scanning for changed messages in '%s'"),
5326 camel_folder_get_display_name (folder));
5329 'E', "Scanning from %s in %s\n", uid ? uid : "start",
5330 camel_folder_get_full_name (folder));
5332 ic = camel_imapx_command_new (
5333 is, "FETCH", folder,
5334 "UID FETCH %s:* (UID FLAGS)", uid ? uid : "1");
5335 camel_imapx_command_set_job (ic, job);
5336 ic->complete = imapx_job_scan_changes_done;
5338 data->scan_changes = TRUE;
5340 refresh_info_data_infos_free (data);
5341 data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
5345 g_object_unref (folder);
5347 return imapx_command_queue (is, ic, cancellable, error);
5351 imapx_command_fetch_new_messages_done (CamelIMAPXServer *is,
5352 CamelIMAPXCommand *ic,
5353 GCancellable *cancellable,
5357 CamelIMAPXSummary *isum;
5358 CamelIMAPXFolder *ifolder;
5359 CamelFolder *folder;
5360 RefreshInfoData *data;
5361 gboolean success = TRUE;
5363 job = camel_imapx_command_get_job (ic);
5364 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
5366 data = camel_imapx_job_get_data (job);
5367 g_return_val_if_fail (data != NULL, FALSE);
5369 folder = camel_imapx_job_ref_folder (job);
5370 g_return_val_if_fail (folder != NULL, FALSE);
5372 ifolder = CAMEL_IMAPX_FOLDER (folder);
5373 isum = CAMEL_IMAPX_SUMMARY (folder->summary);
5375 if (camel_imapx_command_set_error_if_failed (ic, error)) {
5378 _("Error fetching new messages"));
5383 if (camel_folder_change_info_changed (data->changes)) {
5384 camel_folder_summary_save_to_db (folder->summary, NULL);
5385 imapx_update_store_summary (folder);
5386 camel_folder_changed (folder, data->changes);
5387 camel_folder_change_info_clear (data->changes);
5390 if (camel_folder_summary_count (folder->summary)) {
5391 gchar *uid = imapx_get_uid_from_index (
5393 camel_folder_summary_count (folder->summary) - 1);
5394 guint64 uidl = strtoull (uid, NULL, 10);
5399 if (uidl > ifolder->uidnext_on_server) {
5401 is->tagprefix, "Updating uidnext_on_server for '%s' to %" G_GUINT64_FORMAT "\n",
5402 camel_folder_get_full_name (folder), uidl);
5403 ifolder->uidnext_on_server = uidl;
5407 isum->uidnext = ifolder->uidnext_on_server;
5410 g_object_unref (folder);
5412 imapx_unregister_job (is, job);
5413 camel_imapx_command_unref (ic);
5419 imapx_command_fetch_new_uids_done (CamelIMAPXServer *is,
5420 CamelIMAPXCommand *ic,
5421 GCancellable *cancellable,
5425 RefreshInfoData *data;
5427 job = camel_imapx_command_get_job (ic);
5428 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
5430 data = camel_imapx_job_get_data (job);
5431 g_return_val_if_fail (data != NULL, FALSE);
5433 data->scan_changes = FALSE;
5438 sizeof (struct _refresh_info),
5439 imapx_refresh_info_cmp_descending);
5441 return imapx_command_step_fetch_done (is, ic, cancellable, error);
5445 imapx_job_fetch_new_messages_start (CamelIMAPXJob *job,
5446 CamelIMAPXServer *is,
5447 GCancellable *cancellable,
5450 CamelIMAPXCommand *ic;
5451 CamelFolder *folder;
5452 CamelIMAPXFolder *ifolder;
5453 CamelIMAPXSettings *settings;
5454 CamelSortType fetch_order;
5455 RefreshInfoData *data;
5456 guint32 total, diff;
5460 data = camel_imapx_job_get_data (job);
5461 g_return_val_if_fail (data != NULL, FALSE);
5463 folder = camel_imapx_job_ref_folder (job);
5464 g_return_val_if_fail (folder != NULL, FALSE);
5466 settings = camel_imapx_server_ref_settings (is);
5467 fetch_order = camel_imapx_settings_get_fetch_order (settings);
5468 uidset_size = camel_imapx_settings_get_batch_fetch_count (settings);
5469 g_object_unref (settings);
5471 ifolder = CAMEL_IMAPX_FOLDER (folder);
5473 total = camel_folder_summary_count (folder->summary);
5474 diff = ifolder->exists_on_server - total;
5478 uid = imapx_get_uid_from_index (folder->summary, total - 1);
5479 uidl = strtoull (uid, NULL, 10);
5481 uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uidl + 1);
5483 uid = g_strdup ("1");
5485 job->pop_operation_msg = TRUE;
5487 camel_operation_push_message (
5489 _("Fetching summary information for new messages in '%s'"),
5490 camel_folder_get_display_name (folder));
5492 //printf ("Fetch order: %d/%d\n", fetch_order, CAMEL_SORT_DESCENDING);
5493 if (diff > uidset_size || fetch_order == CAMEL_SORT_DESCENDING) {
5494 ic = camel_imapx_command_new (
5495 is, "FETCH", folder,
5496 "UID FETCH %s:* (UID FLAGS)", uid);
5497 imapx_uidset_init (&data->uidset, uidset_size, 0);
5498 refresh_info_data_infos_free (data);
5499 data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
5502 data->scan_changes = TRUE;
5504 if (fetch_order == CAMEL_SORT_DESCENDING)
5505 ic->complete = imapx_command_fetch_new_uids_done;
5507 ic->complete = imapx_command_step_fetch_done;
5509 ic = camel_imapx_command_new (
5510 is, "FETCH", folder,
5511 "UID FETCH %s:* (RFC822.SIZE RFC822.HEADER FLAGS)", uid);
5513 ic->complete = imapx_command_fetch_new_messages_done;
5518 camel_imapx_command_set_job (ic, job);
5520 g_object_unref (folder);
5522 return imapx_command_queue (is, ic, cancellable, error);
5526 imapx_job_fetch_messages_start (CamelIMAPXJob *job,
5527 CamelIMAPXServer *is,
5528 GCancellable *cancellable,
5531 CamelIMAPXCommand *ic;
5532 CamelFolder *folder;
5534 gchar *start_uid = NULL, *end_uid = NULL;
5535 CamelFetchType ftype;
5537 CamelSortType fetch_order;
5538 CamelIMAPXSettings *settings;
5540 RefreshInfoData *data;
5542 data = camel_imapx_job_get_data (job);
5543 g_return_val_if_fail (data != NULL, FALSE);
5545 folder = camel_imapx_job_ref_folder (job);
5546 g_return_val_if_fail (folder != NULL, FALSE);
5548 settings = camel_imapx_server_ref_settings (is);
5549 fetch_order = camel_imapx_settings_get_fetch_order (settings);
5550 uidset_size = camel_imapx_settings_get_batch_fetch_count (settings);
5551 g_object_unref (settings);
5553 total = camel_folder_summary_count (folder->summary);
5555 ftype = data->fetch_type;
5556 fetch_limit = data->fetch_msg_limit;
5558 if (ftype == CAMEL_FETCH_NEW_MESSAGES ||
5559 (ftype == CAMEL_FETCH_OLD_MESSAGES && total <=0 )) {
5564 /* This means that we are fetching limited number of new mails */
5565 uid = g_strdup_printf ("%d", total);
5567 /* For empty accounts, we always fetch the specified number of new mails independent of
5568 * being asked to fetch old or new.
5570 uid = g_strdup ("1");
5573 if (ftype == CAMEL_FETCH_NEW_MESSAGES) {
5576 /* We need to issue Status command to get the total unread count */
5577 ic = camel_imapx_command_new (
5579 "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
5580 camel_imapx_command_set_job (ic, job);
5583 success = imapx_command_run_sync (
5584 is, ic, cancellable, error);
5586 camel_imapx_command_unref (ic);
5591 _("Error while fetching messages"));
5592 g_object_unref (folder);
5597 camel_operation_push_message (
5598 cancellable, dngettext (GETTEXT_PACKAGE,
5599 "Fetching summary information for %d message in '%s'",
5600 "Fetching summary information for %d messages in '%s'",
5601 data->fetch_msg_limit),
5602 data->fetch_msg_limit,
5603 camel_folder_get_display_name (folder));
5605 /* New account and fetching old messages, we would return just the limited number of newest messages */
5606 ic = camel_imapx_command_new (
5607 is, "FETCH", folder,
5608 "UID FETCH %s:* (UID FLAGS)", uid);
5610 imapx_uidset_init (&data->uidset, uidset_size, 0);
5611 refresh_info_data_infos_free (data);
5612 data->infos = g_array_new (0, 0, sizeof (struct _refresh_info));
5615 data->scan_changes = TRUE;
5617 if (fetch_order == CAMEL_SORT_DESCENDING)
5618 ic->complete = imapx_command_fetch_new_uids_done;
5620 ic->complete = imapx_command_step_fetch_done;
5624 } else if (ftype == CAMEL_FETCH_OLD_MESSAGES && total > 0) {
5626 start_uid = imapx_get_uid_from_index (folder->summary, 0);
5627 uidl = strtoull (start_uid, NULL, 10);
5628 end_uid = g_strdup_printf ("%" G_GINT64_MODIFIER "d", (((gint) uidl) - fetch_limit > 0) ? (uidl - fetch_limit) : 1);
5630 camel_operation_push_message (
5631 cancellable, dngettext (GETTEXT_PACKAGE,
5632 "Fetching summary information for %d message in '%s'",
5633 "Fetching summary information for %d messages in '%s'",
5634 data->fetch_msg_limit),
5635 data->fetch_msg_limit,
5636 camel_folder_get_display_name (folder));
5638 ic = camel_imapx_command_new (
5639 is, "FETCH", folder,
5640 "UID FETCH %s:%s (RFC822.SIZE RFC822.HEADER FLAGS)", start_uid, end_uid);
5642 ic->complete = imapx_command_fetch_new_messages_done;
5648 g_error ("Shouldn't reach here. Incorrect fetch type");
5651 camel_imapx_command_set_job (ic, job);
5653 g_object_unref (folder);
5655 return imapx_command_queue (is, ic, cancellable, error);
5659 imapx_job_refresh_info_start (CamelIMAPXJob *job,
5660 CamelIMAPXServer *is,
5661 GCancellable *cancellable,
5664 CamelIMAPXFolder *ifolder;
5665 CamelIMAPXSettings *settings;
5666 CamelIMAPXSummary *isum;
5667 CamelFolder *folder;
5668 const gchar *full_name;
5669 gboolean need_rescan = FALSE;
5670 gboolean is_selected = FALSE;
5671 gboolean can_qresync = FALSE;
5672 gboolean mobile_mode;
5676 folder = camel_imapx_job_ref_folder (job);
5677 g_return_val_if_fail (folder != NULL, FALSE);
5679 settings = camel_imapx_server_ref_settings (is);
5680 mobile_mode = camel_imapx_settings_get_mobile_mode (settings);
5681 g_object_unref (settings);
5683 ifolder = CAMEL_IMAPX_FOLDER (folder);
5684 isum = CAMEL_IMAPX_SUMMARY (folder->summary);
5686 full_name = camel_folder_get_full_name (folder);
5688 /* Sync changes first, else unread count will not
5689 * match. Need to think about better ways for this */
5690 success = imapx_server_sync_changes (
5691 is, folder, job->type, job->pri, cancellable, error);
5695 #if 0 /* There are issues with this still; continue with the buggy
5696 * behaviour where we issue STATUS on the current folder, for now. */
5697 if (is->select_folder == folder)
5700 total = camel_folder_summary_count (folder->summary);
5702 if (ifolder->uidvalidity_on_server && isum->validity && isum->validity != ifolder->uidvalidity_on_server) {
5703 invalidate_local_cache (ifolder, ifolder->uidvalidity_on_server);
5707 /* We don't have valid unread count or modseq for currently-selected server
5708 * (unless we want to re-SELECT it). We fake unread count when fetching
5709 * message flags, but don't depend on modseq for the selected folder */
5710 if (total != ifolder->exists_on_server ||
5711 isum->uidnext != ifolder->uidnext_on_server ||
5712 camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
5713 (!is_selected && isum->modseq != ifolder->modseq_on_server))
5716 /* This is probably the first check of this folder after startup;
5717 * use STATUS to check whether the cached summary is valid, rather
5718 * than blindly updating. Only for servers which support CONDSTORE
5720 if ((isum->modseq && !ifolder->modseq_on_server))
5721 need_rescan = FALSE;
5723 /* If we don't think there's anything to do, poke it to check */
5725 CamelIMAPXCommand *ic;
5727 #if 0 /* see comment for disabled bits above */
5729 /* We may not issue STATUS on the current folder. Use SELECT or NOOP instead. */
5730 if (0 /* server needs SELECT not just NOOP */) {
5731 if (imapx_idle_supported (is) && imapx_in_idle (is))
5732 if (!imapx_stop_idle (is, error))
5734 /* This doesn't work -- this is an immediate command, not queued */
5736 is, folder, TRUE, cancellable, error))
5739 /* Or maybe just NOOP, unless we're in IDLE in which case do nothing */
5740 if (!imapx_idle_supported (is) || !imapx_in_idle (is)) {
5741 if (!camel_imapx_server_noop (is, folder, cancellable, error))
5748 if (is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_CONDSTORE) != 0)
5749 ic = camel_imapx_command_new (
5751 "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT HIGHESTMODSEQ)", folder);
5753 ic = camel_imapx_command_new (
5755 "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
5757 camel_imapx_command_set_job (ic, job);
5760 success = imapx_command_run_sync (
5761 is, ic, cancellable, error);
5763 camel_imapx_command_unref (ic);
5768 _("Error refreshing folder"));
5773 /* Recalulate need_rescan */
5774 if (total != ifolder->exists_on_server ||
5775 isum->uidnext != ifolder->uidnext_on_server ||
5776 camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
5777 (!is_selected && isum->modseq != ifolder->modseq_on_server))
5780 } else if (mobile_mode) {
5781 /* We need to issue Status command to get the total unread count */
5782 CamelIMAPXCommand *ic;
5784 ic = camel_imapx_command_new (
5786 "STATUS %f (MESSAGES UNSEEN UIDVALIDITY UIDNEXT)", folder);
5787 camel_imapx_command_set_job (ic, job);
5790 success = imapx_command_run_sync (
5791 is, ic, cancellable, error);
5793 camel_imapx_command_unref (ic);
5798 _("Error refreshing folder"));
5803 if (is->use_qresync && isum->modseq && ifolder->uidvalidity_on_server)
5808 "folder %s is %sselected, "
5809 "total %u / %u, unread %u / %u, modseq %"
5810 G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT
5811 ", uidnext %u / %u: will %srescan\n",
5813 is_selected ? "" : "not ",
5815 ifolder->exists_on_server,
5816 camel_folder_summary_get_unread_count (folder->summary),
5817 ifolder->unread_on_server,
5818 (guint64) isum->modseq,
5819 (guint64) ifolder->modseq_on_server,
5821 ifolder->uidnext_on_server,
5822 need_rescan ? "" : "not ");
5824 /* Fetch new messages first, so that they appear to the user ASAP */
5825 if (ifolder->exists_on_server > total ||
5826 ifolder->uidnext_on_server > isum->uidnext)
5829 need_rescan = FALSE;
5831 success = imapx_server_fetch_new_messages (
5832 is, folder, FALSE, FALSE, cancellable, error);
5836 /* If QRESYNC-capable we'll have got all flags changes in SELECT */
5845 /* Actually we only want to select it; no need for the NOOP */
5846 success = camel_imapx_server_noop (
5847 is, folder, cancellable, error);
5851 isum->modseq = ifolder->modseq_on_server;
5852 total = camel_folder_summary_count (folder->summary);
5853 if (total != ifolder->exists_on_server ||
5854 camel_folder_summary_get_unread_count (folder->summary) != ifolder->unread_on_server ||
5855 (isum->modseq != ifolder->modseq_on_server)) {
5858 "Eep, after QRESYNC we're out of sync. "
5859 "total %u / %u, unread %u / %u, modseq %"
5860 G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT "\n",
5861 total, ifolder->exists_on_server,
5862 camel_folder_summary_get_unread_count (folder->summary),
5863 ifolder->unread_on_server,
5865 ifolder->modseq_on_server);
5869 "OK, after QRESYNC we're still in sync. "
5870 "total %u / %u, unread %u / %u, modseq %"
5871 G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT "\n",
5872 total, ifolder->exists_on_server,
5873 camel_folder_summary_get_unread_count (folder->summary),
5874 ifolder->unread_on_server,
5876 ifolder->modseq_on_server);
5881 g_object_unref (folder);
5883 return imapx_job_scan_changes_start (job, is, cancellable, error);
5886 g_object_unref (folder);
5888 imapx_unregister_job (is, job);
5894 imapx_job_refresh_info_matches (CamelIMAPXJob *job,
5895 CamelFolder *folder,
5898 return camel_imapx_job_has_folder (job, folder);
5901 /* ********************************************************************** */
5904 imapx_command_expunge_done (CamelIMAPXServer *is,
5905 CamelIMAPXCommand *ic,
5906 GCancellable *cancellable,
5910 CamelFolder *folder;
5911 gboolean success = TRUE;
5913 job = camel_imapx_command_get_job (ic);
5914 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
5916 folder = camel_imapx_job_ref_folder (job);
5917 g_return_val_if_fail (folder != NULL, FALSE);
5919 if (camel_imapx_command_set_error_if_failed (ic, error)) {
5922 _("Error expunging message"));
5927 CamelStore *parent_store;
5928 const gchar *full_name;
5930 full_name = camel_folder_get_full_name (folder);
5931 parent_store = camel_folder_get_parent_store (folder);
5933 camel_folder_summary_save_to_db (folder->summary, NULL);
5934 uids = camel_db_get_folder_deleted_uids (parent_store->cdb_r, full_name, error);
5936 if (uids && uids->len) {
5937 CamelFolderChangeInfo *changes;
5938 GList *removed = NULL;
5941 changes = camel_folder_change_info_new ();
5942 for (i = 0; i < uids->len; i++) {
5943 gchar *uid = uids->pdata[i];
5944 CamelMessageInfo *mi;
5946 mi = camel_folder_summary_peek_loaded (folder->summary, uid);
5948 camel_folder_summary_remove (folder->summary, mi);
5949 camel_message_info_free (mi);
5951 camel_folder_summary_remove_uid (folder->summary, uid);
5954 camel_folder_change_info_remove_uid (changes, uids->pdata[i]);
5955 removed = g_list_prepend (removed, (gpointer) uids->pdata[i]);
5958 camel_folder_summary_save_to_db (folder->summary, NULL);
5959 camel_folder_changed (folder, changes);
5960 camel_folder_change_info_free (changes);
5962 g_list_free (removed);
5963 g_ptr_array_foreach (uids, (GFunc) camel_pstring_free, NULL);
5964 g_ptr_array_free (uids, TRUE);
5968 g_object_unref (folder);
5970 imapx_unregister_job (is, job);
5971 camel_imapx_command_unref (ic);
5977 imapx_job_expunge_start (CamelIMAPXJob *job,
5978 CamelIMAPXServer *is,
5979 GCancellable *cancellable,
5982 CamelIMAPXCommand *ic;
5983 CamelFolder *folder;
5986 folder = camel_imapx_job_ref_folder (job);
5987 g_return_val_if_fail (folder != NULL, FALSE);
5989 success = imapx_server_sync_changes (
5990 is, folder, job->type, job->pri, cancellable, error);
5993 /* TODO handle UIDPLUS capability */
5994 ic = camel_imapx_command_new (
5995 is, "EXPUNGE", folder, "EXPUNGE");
5996 camel_imapx_command_set_job (ic, job);
5998 ic->complete = imapx_command_expunge_done;
6000 success = imapx_command_queue (is, ic, cancellable, error);
6003 g_object_unref (folder);
6009 imapx_job_expunge_matches (CamelIMAPXJob *job,
6010 CamelFolder *folder,
6013 return camel_imapx_job_has_folder (job, folder);
6016 /* ********************************************************************** */
6019 imapx_command_list_done (CamelIMAPXServer *is,
6020 CamelIMAPXCommand *ic,
6021 GCancellable *cancellable,
6025 gboolean success = TRUE;
6027 job = camel_imapx_command_get_job (ic);
6028 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6030 if (camel_imapx_command_set_error_if_failed (ic, error)) {
6033 _("Error fetching folders"));
6037 e (is->tagprefix, "==== list or lsub completed ==== \n");
6038 imapx_unregister_job (is, job);
6039 camel_imapx_command_unref (ic);
6045 imapx_job_list_start (CamelIMAPXJob *job,
6046 CamelIMAPXServer *is,
6047 GCancellable *cancellable,
6050 CamelIMAPXCommand *ic;
6053 data = camel_imapx_job_get_data (job);
6054 g_return_val_if_fail (data != NULL, FALSE);
6056 ic = camel_imapx_command_new (
6059 (data->flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) ?
6063 /* Hm, we need a way to add atoms _without_ quoting or using literals */
6064 camel_imapx_command_add (ic, " ");
6065 camel_imapx_command_add (ic, data->ext);
6068 camel_imapx_command_set_job (ic, job);
6069 ic->complete = imapx_command_list_done;
6071 return imapx_command_queue (is, ic, cancellable, error);
6075 imapx_job_list_matches (CamelIMAPXJob *job,
6076 CamelFolder *folder,
6079 return TRUE; /* matches everything */
6082 /* ********************************************************************** */
6085 imapx_encode_folder_name (CamelIMAPXStore *istore,
6086 const gchar *folder_name)
6088 gchar *fname, *encoded;
6090 fname = camel_imapx_store_summary_full_from_path (istore->summary, folder_name);
6092 encoded = camel_utf8_utf7 (fname);
6095 encoded = camel_utf8_utf7 (folder_name);
6101 imapx_command_subscription_done (CamelIMAPXServer *is,
6102 CamelIMAPXCommand *ic,
6103 GCancellable *cancellable,
6107 gboolean success = TRUE;
6109 job = camel_imapx_command_get_job (ic);
6110 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6112 if (camel_imapx_command_set_error_if_failed (ic, error)) {
6115 _("Error subscribing to folder"));
6119 imapx_unregister_job (is, job);
6120 camel_imapx_command_unref (ic);
6126 imapx_job_manage_subscription_start (CamelIMAPXJob *job,
6127 CamelIMAPXServer *is,
6128 GCancellable *cancellable,
6131 CamelIMAPXCommand *ic;
6132 CamelIMAPXStore *store;
6133 ManageSubscriptionsData *data;
6134 gchar *encoded_fname = NULL;
6136 data = camel_imapx_job_get_data (job);
6137 g_return_val_if_fail (data != NULL, FALSE);
6139 store = camel_imapx_server_ref_store (is);
6141 encoded_fname = imapx_encode_folder_name (store, data->folder_name);
6143 if (data->subscribe)
6144 ic = camel_imapx_command_new (
6145 is, "SUBSCRIBE", NULL,
6146 "SUBSCRIBE %s", encoded_fname);
6148 ic = camel_imapx_command_new (
6149 is, "UNSUBSCRIBE", NULL,
6150 "UNSUBSCRIBE %s", encoded_fname);
6153 camel_imapx_command_set_job (ic, job);
6154 ic->complete = imapx_command_subscription_done;
6156 g_free (encoded_fname);
6158 g_object_unref (store);
6160 return imapx_command_queue (is, ic, cancellable, error);
6163 /* ********************************************************************** */
6166 imapx_command_create_folder_done (CamelIMAPXServer *is,
6167 CamelIMAPXCommand *ic,
6168 GCancellable *cancellable,
6172 gboolean success = TRUE;
6174 job = camel_imapx_command_get_job (ic);
6175 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6177 if (camel_imapx_command_set_error_if_failed (ic, error)) {
6180 _("Error creating folder"));
6184 imapx_unregister_job (is, job);
6185 camel_imapx_command_unref (ic);
6191 imapx_job_create_folder_start (CamelIMAPXJob *job,
6192 CamelIMAPXServer *is,
6193 GCancellable *cancellable,
6196 CamelIMAPXCommand *ic;
6197 CreateFolderData *data;
6198 gchar *encoded_fname = NULL;
6200 data = camel_imapx_job_get_data (job);
6201 g_return_val_if_fail (data != NULL, FALSE);
6203 encoded_fname = camel_utf8_utf7 (data->folder_name);
6205 ic = camel_imapx_command_new (
6207 "CREATE %s", encoded_fname);
6209 camel_imapx_command_set_job (ic, job);
6210 ic->complete = imapx_command_create_folder_done;
6212 g_free (encoded_fname);
6214 return imapx_command_queue (is, ic, cancellable, error);
6217 /* ********************************************************************** */
6220 imapx_command_delete_folder_done (CamelIMAPXServer *is,
6221 CamelIMAPXCommand *ic,
6222 GCancellable *cancellable,
6226 gboolean success = TRUE;
6228 job = camel_imapx_command_get_job (ic);
6229 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6231 if (camel_imapx_command_set_error_if_failed (ic, error)) {
6234 _("Error deleting folder"));
6238 imapx_unregister_job (is, job);
6239 camel_imapx_command_unref (ic);
6245 imapx_job_delete_folder_start (CamelIMAPXJob *job,
6246 CamelIMAPXServer *is,
6247 GCancellable *cancellable,
6250 CamelIMAPXCommand *ic;
6251 CamelIMAPXStore *store;
6252 CamelFolder *folder;
6253 DeleteFolderData *data;
6254 gchar *encoded_fname = NULL;
6255 gboolean success = FALSE;
6257 data = camel_imapx_job_get_data (job);
6258 g_return_val_if_fail (data != NULL, FALSE);
6260 store = camel_imapx_server_ref_store (is);
6262 encoded_fname = imapx_encode_folder_name (store, data->folder_name);
6264 folder = camel_store_get_folder_sync (
6265 CAMEL_STORE (store), "INBOX", 0, cancellable, error);
6267 if (folder != NULL) {
6268 camel_imapx_job_set_folder (job, folder);
6270 /* Make sure the to-be-deleted folder is not
6271 * selected by selecting INBOX for this operation. */
6272 ic = camel_imapx_command_new (
6273 is, "DELETE", folder,
6274 "DELETE %s", encoded_fname);
6276 camel_imapx_command_set_job (ic, job);
6277 ic->complete = imapx_command_delete_folder_done;
6279 success = imapx_command_queue (is, ic, cancellable, error);
6281 g_object_unref (folder);
6284 g_free (encoded_fname);
6286 g_object_unref (store);
6291 /* ********************************************************************** */
6294 imapx_command_rename_folder_done (CamelIMAPXServer *is,
6295 CamelIMAPXCommand *ic,
6296 GCancellable *cancellable,
6300 gboolean success = TRUE;
6302 job = camel_imapx_command_get_job (ic);
6303 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6305 if (camel_imapx_command_set_error_if_failed (ic, error)) {
6308 _("Error renaming folder"));
6312 imapx_unregister_job (is, job);
6313 camel_imapx_command_unref (ic);
6319 imapx_job_rename_folder_start (CamelIMAPXJob *job,
6320 CamelIMAPXServer *is,
6321 GCancellable *cancellable,
6324 CamelIMAPXCommand *ic;
6325 CamelIMAPXStore *store;
6326 RenameFolderData *data;
6327 CamelFolder *folder;
6328 gchar *en_ofname = NULL;
6329 gchar *en_nfname = NULL;
6330 gboolean success = FALSE;
6332 data = camel_imapx_job_get_data (job);
6333 g_return_val_if_fail (data != NULL, FALSE);
6335 store = camel_imapx_server_ref_store (is);
6337 en_ofname = imapx_encode_folder_name (store, data->old_folder_name);
6338 en_nfname = imapx_encode_folder_name (store, data->new_folder_name);
6340 folder = camel_store_get_folder_sync (
6341 CAMEL_STORE (store), "INBOX", 0, cancellable, error);
6343 if (folder != NULL) {
6344 camel_imapx_job_set_folder (job, folder);
6346 ic = camel_imapx_command_new (
6347 is, "RENAME", folder,
6348 "RENAME %s %s", en_ofname, en_nfname);
6350 camel_imapx_command_set_job (ic, job);
6351 ic->complete = imapx_command_rename_folder_done;
6353 success = imapx_command_queue (is, ic, cancellable, error);
6355 g_object_unref (folder);
6361 g_object_unref (store);
6366 /* ********************************************************************** */
6369 imapx_command_update_quota_info_done (CamelIMAPXServer *is,
6370 CamelIMAPXCommand *ic,
6371 GCancellable *cancellable,
6375 gboolean success = TRUE;
6377 job = camel_imapx_command_get_job (ic);
6378 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6380 if (camel_imapx_command_set_error_if_failed (ic, error)) {
6383 _("Error retrieving quota information"));
6387 imapx_unregister_job (is, job);
6388 camel_imapx_command_unref (ic);
6394 imapx_job_update_quota_info_start (CamelIMAPXJob *job,
6395 CamelIMAPXServer *is,
6396 GCancellable *cancellable,
6399 CamelIMAPXCommand *ic;
6400 CamelIMAPXStore *store;
6402 gchar *encoded_folder_name;
6405 data = camel_imapx_job_get_data (job);
6406 g_return_val_if_fail (data != NULL, FALSE);
6408 store = camel_imapx_server_ref_store (is);
6410 encoded_folder_name =
6411 imapx_encode_folder_name (store, data->folder_name);
6413 ic = camel_imapx_command_new (
6414 is, "GETQUOTAROOT", NULL,
6415 "GETQUOTAROOT %s", encoded_folder_name);
6417 camel_imapx_command_set_job (ic, job);
6418 ic->complete = imapx_command_update_quota_info_done;
6420 success = imapx_command_queue (is, ic, cancellable, error);
6422 g_free (encoded_folder_name);
6424 g_object_unref (store);
6429 /* ********************************************************************** */
6432 imapx_command_uid_search_done (CamelIMAPXServer *is,
6433 CamelIMAPXCommand *ic,
6434 GCancellable *cancellable,
6439 gboolean success = TRUE;
6441 job = camel_imapx_command_get_job (ic);
6442 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6444 data = camel_imapx_job_get_data (job);
6445 g_return_val_if_fail (data != NULL, FALSE);
6447 if (camel_imapx_command_set_error_if_failed (ic, error)) {
6448 g_prefix_error (error, "%s: ", _("Search failed"));
6452 /* Don't worry about the success state and presence of search
6453 * results not agreeing here. camel_imapx_server_uid_search()
6454 * will disregard the search results if an error occurred. */
6455 g_mutex_lock (&is->priv->search_results_lock);
6456 data->results = is->priv->search_results;
6457 is->priv->search_results = NULL;
6458 g_mutex_unlock (&is->priv->search_results_lock);
6460 imapx_unregister_job (is, job);
6461 camel_imapx_command_unref (ic);
6467 imapx_job_uid_search_start (CamelIMAPXJob *job,
6468 CamelIMAPXServer *is,
6469 GCancellable *cancellable,
6472 CamelFolder *folder;
6473 CamelIMAPXCommand *ic;
6476 data = camel_imapx_job_get_data (job);
6477 g_return_val_if_fail (data != NULL, FALSE);
6479 folder = camel_imapx_job_ref_folder (job);
6480 g_return_val_if_fail (folder != NULL, FALSE);
6482 ic = camel_imapx_command_new (
6483 is, "UID SEARCH", folder,
6484 "UID SEARCH %t", data->criteria);
6486 camel_imapx_command_set_job (ic, job);
6487 ic->complete = imapx_command_uid_search_done;
6489 g_object_unref (folder);
6491 return imapx_command_queue (is, ic, cancellable, error);
6494 /* ********************************************************************** */
6497 imapx_command_noop_done (CamelIMAPXServer *is,
6498 CamelIMAPXCommand *ic,
6499 GCancellable *cancellable,
6503 gboolean success = TRUE;
6505 job = camel_imapx_command_get_job (ic);
6506 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6508 if (camel_imapx_command_set_error_if_failed (ic, error)) {
6511 _("Error performing NOOP"));
6515 imapx_unregister_job (is, job);
6516 camel_imapx_command_unref (ic);
6522 imapx_job_noop_start (CamelIMAPXJob *job,
6523 CamelIMAPXServer *is,
6524 GCancellable *cancellable,
6527 CamelIMAPXCommand *ic;
6528 CamelFolder *folder;
6530 folder = camel_imapx_job_ref_folder (job);
6532 ic = camel_imapx_command_new (
6533 is, "NOOP", folder, "NOOP");
6535 camel_imapx_command_set_job (ic, job);
6536 ic->complete = imapx_command_noop_done;
6537 if (folder != NULL) {
6538 ic->pri = IMAPX_PRIORITY_REFRESH_INFO;
6539 g_object_unref (folder);
6541 ic->pri = IMAPX_PRIORITY_NOOP;
6544 return imapx_command_queue (is, ic, cancellable, error);
6547 /* ********************************************************************** */
6549 /* FIXME: this is basically a copy of the same in camel-imapx-utils.c */
6554 { "\\ANSWERED", CAMEL_MESSAGE_ANSWERED },
6555 { "\\DELETED", CAMEL_MESSAGE_DELETED },
6556 { "\\DRAFT", CAMEL_MESSAGE_DRAFT },
6557 { "\\FLAGGED", CAMEL_MESSAGE_FLAGGED },
6558 { "\\SEEN", CAMEL_MESSAGE_SEEN },
6559 { "\\RECENT", CAMEL_IMAPX_MESSAGE_RECENT },
6560 { "JUNK", CAMEL_MESSAGE_JUNK },
6561 { "NOTJUNK", CAMEL_MESSAGE_NOTJUNK }
6575 imapx_command_sync_changes_done (CamelIMAPXServer *is,
6576 CamelIMAPXCommand *ic,
6577 GCancellable *cancellable,
6581 CamelFolder *folder;
6582 CamelStore *parent_store;
6583 SyncChangesData *data;
6584 const gchar *full_name;
6585 CamelIMAPXSettings *settings;
6586 gboolean mobile_mode;
6587 gboolean success = TRUE;
6589 job = camel_imapx_command_get_job (ic);
6590 g_return_val_if_fail (CAMEL_IS_IMAPX_JOB (job), FALSE);
6592 data = camel_imapx_job_get_data (job);
6593 g_return_val_if_fail (data != NULL, FALSE);
6595 folder = camel_imapx_job_ref_folder (job);
6596 g_return_val_if_fail (folder != NULL, FALSE);
6598 settings = camel_imapx_server_ref_settings (is);
6599 mobile_mode = camel_imapx_settings_get_mobile_mode (settings);
6600 g_object_unref (settings);
6604 full_name = camel_folder_get_full_name (folder);
6605 parent_store = camel_folder_get_parent_store (folder);
6607 /* If this worked, we should really just update the changes that we
6608 * sucessfully stored, so we dont have to worry about sending them
6610 * But then we'd have to track which uid's we actually updated, so
6611 * its easier just to refresh all of the ones we got.
6613 * Not that ... given all the asynchronicity going on, we're guaranteed
6614 * that what we just set is actually what is on the server now .. but
6615 * if it isn't, i guess we'll fix up next refresh */
6617 if (camel_imapx_command_set_error_if_failed (ic, error)) {
6620 _("Error syncing changes"));
6627 for (i = 0; i < data->changed_uids->len; i++) {
6628 CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) camel_folder_summary_get (folder->summary,
6629 data->changed_uids->pdata[i]);
6634 if (data->remove_deleted_flags)
6635 xinfo->info.flags &= ~CAMEL_MESSAGE_DELETED;
6636 xinfo->server_flags = xinfo->info.flags & CAMEL_IMAPX_SERVER_FLAGS;
6637 xinfo->info.flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
6638 xinfo->info.dirty = TRUE;
6639 camel_flag_list_copy (&xinfo->server_user_flags, &xinfo->info.user_flags);
6641 camel_folder_summary_touch (folder->summary);
6642 camel_message_info_free (xinfo);
6644 /* Apply the changes to server-side unread count; it won't tell
6645 * us of these changes, of course. */
6646 ((CamelIMAPXFolder *) folder)->unread_on_server += data->unread_change;
6649 if (job->commands == 0) {
6650 if (folder->summary && (folder->summary->flags & CAMEL_FOLDER_SUMMARY_DIRTY) != 0) {
6653 /* ... and store's summary when folder's summary is dirty */
6654 si = camel_store_summary_path ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary, full_name);
6656 if (si->total != camel_folder_summary_get_saved_count (folder->summary) ||
6657 si->unread != camel_folder_summary_get_unread_count (folder->summary)) {
6658 si->total = camel_folder_summary_get_saved_count (folder->summary);
6659 /* Don't mess with server's unread
6660 * count in mobile mode, as what we
6661 * have downloaded is little. */
6663 si->unread = camel_folder_summary_get_unread_count (folder->summary);
6664 camel_store_summary_touch ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
6667 camel_store_summary_info_free ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary, si);
6671 camel_folder_summary_save_to_db (folder->summary, error);
6672 camel_store_summary_save ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
6674 imapx_unregister_job (is, job);
6677 g_object_unref (folder);
6679 camel_imapx_command_unref (ic);
6685 imapx_job_sync_changes_start (CamelIMAPXJob *job,
6686 CamelIMAPXServer *is,
6687 GCancellable *cancellable,
6690 SyncChangesData *data;
6691 CamelFolder *folder;
6693 struct _uidset_state ss;
6697 data = camel_imapx_job_get_data (job);
6698 g_return_val_if_fail (data != NULL, FALSE);
6700 folder = camel_imapx_job_ref_folder (job);
6701 g_return_val_if_fail (folder != NULL, FALSE);
6703 uids = data->changed_uids;
6705 for (on = 0; on < 2; on++) {
6706 guint32 orset = on ? data->on_set : data->off_set;
6707 GArray *user_set = on ? data->on_user : data->off_user;
6709 for (j = 0; j < G_N_ELEMENTS (flags_table); j++) {
6710 guint32 flag = flags_table[j].flag;
6711 CamelIMAPXCommand *ic = NULL;
6713 if ((orset & flag) == 0)
6716 c (is->tagprefix, "checking/storing %s flags '%s'\n", on?"on":"off", flags_table[j].name);
6717 imapx_uidset_init (&ss, 0, 100);
6718 for (i = 0; i < uids->len; i++) {
6719 CamelIMAPXMessageInfo *info;
6720 gboolean remove_deleted_flag;
6725 info = (CamelIMAPXMessageInfo *)
6726 camel_folder_summary_get (
6733 flags = info->info.flags & CAMEL_IMAPX_SERVER_FLAGS;
6734 sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
6737 remove_deleted_flag =
6738 data->remove_deleted_flags &&
6739 (flags & CAMEL_MESSAGE_DELETED);
6741 if (remove_deleted_flag) {
6742 /* Remove the DELETED flag so the
6743 * message appears normally in the
6744 * real Trash folder when copied. */
6745 flags &= ~CAMEL_MESSAGE_DELETED;
6748 if ( (on && (((flags ^ sflags) & flags) & flag))
6749 || (!on && (((flags ^ sflags) & ~flags) & flag))) {
6751 ic = camel_imapx_command_new (
6752 is, "STORE", folder,
6754 ic->complete = imapx_command_sync_changes_done;
6755 camel_imapx_command_set_job (ic, job);
6758 send = imapx_uidset_add (&ss, ic, camel_message_info_uid (info));
6760 if (send == 1 || (i == uids->len - 1 && imapx_uidset_done (&ss, ic))) {
6762 camel_imapx_command_add (ic, " %tFLAGS.SILENT (%t)", on?"+":"-", flags_table[j].name);
6763 if (!imapx_command_queue (is, ic, cancellable, error)) {
6764 camel_message_info_free (info);
6769 if (flag == CAMEL_MESSAGE_SEEN) {
6770 /* Remember how the server's unread count will change if this
6771 * command succeeds */
6773 data->unread_change--;
6775 data->unread_change++;
6777 camel_message_info_free (info);
6782 CamelIMAPXCommand *ic = NULL;
6784 for (j = 0; j < user_set->len; j++) {
6785 struct _imapx_flag_change *c = &g_array_index (user_set, struct _imapx_flag_change, j);
6787 imapx_uidset_init (&ss, 0, 100);
6788 for (i = 0; i < c->infos->len; i++) {
6789 CamelIMAPXMessageInfo *info = c->infos->pdata[i];
6792 ic = camel_imapx_command_new (
6793 is, "STORE", folder,
6795 ic->complete = imapx_command_sync_changes_done;
6796 camel_imapx_command_set_job (ic, job);
6800 if (imapx_uidset_add (&ss, ic, camel_message_info_uid (info)) == 1
6801 || (i == c->infos->len - 1 && imapx_uidset_done (&ss, ic))) {
6803 camel_imapx_command_add (ic, " %tFLAGS.SILENT (%t)", on?"+":"-", c->name);
6804 if (!imapx_command_queue (is, ic, cancellable, error))
6814 g_object_unref (folder);
6816 /* Since this may start in another thread ... we need to
6817 * lock the commands count, ho hum */
6819 if (job->commands == 0)
6820 imapx_unregister_job (is, job);
6826 imapx_job_sync_changes_matches (CamelIMAPXJob *job,
6827 CamelFolder *folder,
6830 return camel_imapx_job_has_folder (job, folder);
6833 /* we cancel all the commands and their jobs, so associated jobs will be notified */
6835 cancel_all_jobs (CamelIMAPXServer *is,
6838 CamelIMAPXCommandQueue *queue;
6841 /* Transfer all pending and active commands to a separate
6842 * command queue to complete them without holding QUEUE_LOCK. */
6844 queue = camel_imapx_command_queue_new ();
6848 camel_imapx_command_queue_transfer (is->queue, queue);
6849 camel_imapx_command_queue_transfer (is->active, queue);
6853 head = camel_imapx_command_queue_peek_head_link (queue);
6855 for (link = head; link != NULL; link = g_list_next (link)) {
6856 CamelIMAPXCommand *ic = link->data;
6859 /* Sanity check the CamelIMAPXCommand before proceeding.
6860 * XXX We are actually getting reports of crashes here...
6861 * not sure how this is happening but it's happening. */
6865 /* Similarly with the CamelIMAPXJob contained within. */
6866 job = camel_imapx_command_get_job (ic);
6867 if (!CAMEL_IS_IMAPX_JOB (job))
6870 camel_imapx_job_cancel (job);
6872 /* Send a NULL GError since we already cancelled
6873 * the job and we're not interested in individual
6874 * command errors. */
6875 ic->complete (is, ic, camel_imapx_job_get_cancellable (job), NULL);
6878 camel_imapx_command_queue_free (queue);
6881 /* ********************************************************************** */
6884 parse_contents (CamelIMAPXServer *is,
6885 GCancellable *cancellable,
6888 CamelIMAPXStream *stream;
6890 stream = camel_imapx_server_ref_stream (is);
6891 g_return_if_fail (stream != NULL);
6893 while (imapx_step (is, cancellable, error))
6894 if (camel_imapx_stream_buffered (stream) == 0)
6897 g_object_unref (stream);
6901 * The main processing (reading) loop.
6903 * Main area of locking required is command_queue
6904 * and command_start_next, the 'literal' command,
6905 * the jobs queue, the active queue, the queue
6908 imapx_parser_thread (gpointer d)
6910 CamelIMAPXServer *is = d;
6911 CamelIMAPXStream *stream;
6912 GCancellable *cancellable;
6913 gboolean have_stream;
6914 GError *local_error = NULL;
6917 cancellable = camel_operation_new ();
6918 is->cancellable = g_object_ref (cancellable);
6921 stream = camel_imapx_server_ref_stream (is);
6922 if (stream != NULL) {
6924 g_object_unref (stream);
6926 have_stream = FALSE;
6929 /* FIXME This should really be a GMainLoop instead of a 'while' loop.
6930 * Testing for a stream on each loop iteration is pretty hokey.
6931 * Disconnecting the stream could just terminate the parser
6932 * thread's main loop. */
6933 while (local_error == NULL && have_stream) {
6934 g_cancellable_reset (cancellable);
6937 if (is->is_process_stream) {
6938 GPollFD fds[2] = { {0, 0, 0}, {0, 0, 0} };
6939 CamelStream *source;
6942 stream = camel_imapx_server_ref_stream (is);
6943 source = camel_imapx_stream_ref_source (stream);
6945 fds[0].fd = CAMEL_STREAM_PROCESS (source)->sockfd;
6946 fds[0].events = G_IO_IN;
6947 fds[1].fd = g_cancellable_get_fd (cancellable);
6948 fds[1].events = G_IO_IN;
6949 res = g_poll (fds, 2, -1);
6951 g_usleep (1) /* ?? */ ;
6954 else if (fds[0].revents & G_IO_IN)
6955 parse_contents (is, cancellable, &local_error);
6956 g_cancellable_release_fd (cancellable);
6958 g_object_unref (source);
6959 g_object_unref (stream);
6963 parse_contents (is, cancellable, &local_error);
6966 if (is->parser_quit)
6967 g_cancellable_cancel (cancellable);
6968 else if (g_cancellable_is_cancelled (cancellable)) {
6972 is_empty = camel_imapx_command_queue_is_empty (is->active);
6975 if (is_empty || (imapx_idle_supported (is) && imapx_in_idle (is))) {
6976 g_cancellable_reset (cancellable);
6977 g_clear_error (&local_error);
6979 /* Cancelled error should be set. */
6980 g_warn_if_fail (local_error != NULL);
6984 /* Jump out of the loop if an error occurred. */
6985 if (local_error != NULL)
6988 stream = camel_imapx_server_ref_stream (is);
6989 if (stream != NULL) {
6991 g_object_unref (stream);
6993 have_stream = FALSE;
6998 is->state = IMAPX_SHUTDOWN;
7001 cancel_all_jobs (is, local_error);
7003 g_clear_error (&local_error);
7006 if (is->cancellable != NULL) {
7007 g_object_unref (is->cancellable);
7008 is->cancellable = NULL;
7010 g_object_unref (cancellable);
7013 is->parser_quit = FALSE;
7014 g_signal_emit (is, signals[SHUTDOWN], 0);
7020 join_helper (gpointer thread)
7022 g_thread_join (thread);
7027 imapx_server_set_store (CamelIMAPXServer *server,
7028 CamelIMAPXStore *store)
7030 g_return_if_fail (CAMEL_IS_IMAPX_STORE (store));
7032 g_weak_ref_set (&server->priv->store, store);
7036 imapx_server_set_property (GObject *object,
7038 const GValue *value,
7041 switch (property_id) {
7043 imapx_server_set_store (
7044 CAMEL_IMAPX_SERVER (object),
7045 g_value_get_object (value));
7049 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
7053 imapx_server_get_property (GObject *object,
7058 switch (property_id) {
7060 g_value_take_object (
7062 camel_imapx_server_ref_stream (
7063 CAMEL_IMAPX_SERVER (object)));
7067 g_value_take_object (
7069 camel_imapx_server_ref_store (
7070 CAMEL_IMAPX_SERVER (object)));
7074 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
7078 imapx_server_dispose (GObject *object)
7080 CamelIMAPXServer *server = CAMEL_IMAPX_SERVER (object);
7082 QUEUE_LOCK (server);
7083 server->state = IMAPX_SHUTDOWN;
7085 server->parser_quit = TRUE;
7087 if (server->cancellable != NULL) {
7088 g_cancellable_cancel (server->cancellable);
7089 g_object_unref (server->cancellable);
7090 server->cancellable = NULL;
7092 QUEUE_UNLOCK (server);
7094 if (server->parser_thread) {
7095 if (server->parser_thread == g_thread_self ())
7096 /* Prioritize ahead of GTK+ redraws. */
7098 G_PRIORITY_HIGH_IDLE,
7099 &join_helper, server->parser_thread, NULL);
7101 g_thread_join (server->parser_thread);
7102 server->parser_thread = NULL;
7105 if (server->cinfo && imapx_idle_supported (server))
7106 imapx_exit_idle (server);
7108 imapx_disconnect (server);
7110 g_weak_ref_set (&server->priv->store, NULL);
7112 /* Chain up to parent's dispose() method. */
7113 G_OBJECT_CLASS (camel_imapx_server_parent_class)->dispose (object);
7117 imapx_server_finalize (GObject *object)
7119 CamelIMAPXServer *is = CAMEL_IMAPX_SERVER (object);
7121 g_mutex_clear (&is->priv->stream_lock);
7123 camel_imapx_command_queue_free (is->queue);
7124 camel_imapx_command_queue_free (is->active);
7125 camel_imapx_command_queue_free (is->done);
7131 g_rec_mutex_clear (&is->queue_lock);
7132 g_mutex_clear (&is->select_lock);
7133 g_mutex_clear (&is->fetch_mutex);
7134 g_cond_clear (&is->fetch_cond);
7136 camel_folder_change_info_free (is->changes);
7138 g_free (is->priv->context);
7139 g_hash_table_destroy (is->priv->untagged_handlers);
7141 if (is->priv->search_results != NULL)
7142 g_array_unref (is->priv->search_results);
7143 g_mutex_clear (&is->priv->search_results_lock);
7145 /* Chain up to parent's finalize() method. */
7146 G_OBJECT_CLASS (camel_imapx_server_parent_class)->finalize (object);
7150 imapx_server_constructed (GObject *object)
7152 CamelIMAPXServer *server;
7153 CamelIMAPXServerClass *class;
7155 server = CAMEL_IMAPX_SERVER (object);
7156 class = CAMEL_IMAPX_SERVER_GET_CLASS (server);
7158 server->tagprefix = class->tagprefix;
7160 if (class->tagprefix > 'Z')
7161 class->tagprefix = 'A';
7165 camel_imapx_server_class_init (CamelIMAPXServerClass *class)
7167 GObjectClass *object_class;
7169 g_type_class_add_private (class, sizeof (CamelIMAPXServerPrivate));
7171 object_class = G_OBJECT_CLASS (class);
7172 object_class->set_property = imapx_server_set_property;
7173 object_class->get_property = imapx_server_get_property;
7174 object_class->finalize = imapx_server_finalize;
7175 object_class->dispose = imapx_server_dispose;
7176 object_class->constructed = imapx_server_constructed;
7178 class->select_changed = NULL;
7179 class->shutdown = NULL;
7181 g_object_class_install_property (
7184 g_param_spec_object (
7187 "IMAP network stream",
7188 CAMEL_TYPE_IMAPX_STREAM,
7190 G_PARAM_STATIC_STRINGS));
7192 g_object_class_install_property (
7195 g_param_spec_object (
7198 "IMAPX store for this server",
7199 CAMEL_TYPE_IMAPX_STORE,
7201 G_PARAM_CONSTRUCT_ONLY |
7202 G_PARAM_STATIC_STRINGS));
7205 * CamelIMAPXServer::select_changed
7206 * @server: the #CamelIMAPXServer which emitted the signal
7208 signals[SELECT_CHANGED] = g_signal_new (
7210 G_OBJECT_CLASS_TYPE (class),
7212 G_STRUCT_OFFSET (CamelIMAPXServerClass, select_changed),
7214 g_cclosure_marshal_VOID__STRING,
7215 G_TYPE_NONE, 1, G_TYPE_STRING);
7218 * CamelIMAPXServer::shutdown
7219 * @server: the #CamelIMAPXServer which emitted the signal
7221 signals[SHUTDOWN] = g_signal_new (
7223 G_OBJECT_CLASS_TYPE (class),
7225 G_STRUCT_OFFSET (CamelIMAPXServerClass, shutdown),
7227 g_cclosure_marshal_VOID__VOID,
7230 class->tagprefix = 'A';
7234 camel_imapx_server_init (CamelIMAPXServer *is)
7236 is->priv = CAMEL_IMAPX_SERVER_GET_PRIVATE (is);
7238 is->priv->untagged_handlers = create_initial_untagged_handler_table ();
7240 g_mutex_init (&is->priv->stream_lock);
7241 g_mutex_init (&is->priv->search_results_lock);
7243 is->queue = camel_imapx_command_queue_new ();
7244 is->active = camel_imapx_command_queue_new ();
7245 is->done = camel_imapx_command_queue_new ();
7247 g_queue_init (&is->jobs);
7249 /* not used at the moment. Use it in future */
7250 is->job_timeout = 29 * 60 * 1000 * 1000;
7252 g_rec_mutex_init (&is->queue_lock);
7254 g_mutex_init (&is->select_lock);
7256 is->state = IMAPX_DISCONNECTED;
7258 is->expunged = NULL;
7259 is->changes = camel_folder_change_info_new ();
7260 is->parser_quit = FALSE;
7262 g_mutex_init (&is->fetch_mutex);
7263 g_cond_init (&is->fetch_cond);
7267 camel_imapx_server_new (CamelIMAPXStore *store)
7269 g_return_val_if_fail (CAMEL_IS_IMAPX_STORE (store), NULL);
7271 return g_object_new (
7272 CAMEL_TYPE_IMAPX_SERVER,
7273 "store", store, NULL);
7277 camel_imapx_server_ref_store (CamelIMAPXServer *server)
7279 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), NULL);
7281 return g_weak_ref_get (&server->priv->store);
7284 CamelIMAPXSettings *
7285 camel_imapx_server_ref_settings (CamelIMAPXServer *server)
7287 CamelIMAPXStore *store;
7288 CamelSettings *settings;
7290 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), NULL);
7292 store = camel_imapx_server_ref_store (server);
7293 settings = camel_service_ref_settings (CAMEL_SERVICE (store));
7294 g_object_unref (store);
7296 return CAMEL_IMAPX_SETTINGS (settings);
7300 camel_imapx_server_ref_stream (CamelIMAPXServer *server)
7302 CamelIMAPXStream *stream = NULL;
7304 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (server), NULL);
7306 g_mutex_lock (&server->priv->stream_lock);
7308 if (server->priv->stream != NULL)
7309 stream = g_object_ref (server->priv->stream);
7311 g_mutex_unlock (&server->priv->stream_lock);
7317 imapx_disconnect (CamelIMAPXServer *is)
7319 gboolean ret = TRUE;
7321 g_mutex_lock (&is->priv->stream_lock);
7323 if (is->priv->stream != NULL) {
7324 CamelStream *stream = CAMEL_STREAM (is->priv->stream);
7326 if (camel_stream_close (stream, NULL, NULL) == -1)
7329 g_object_unref (is->priv->stream);
7330 is->priv->stream = NULL;
7333 g_mutex_unlock (&is->priv->stream_lock);
7335 g_mutex_lock (&is->select_lock);
7336 g_weak_ref_set (&is->select_folder, NULL);
7337 g_weak_ref_set (&is->select_pending, NULL);
7338 g_mutex_unlock (&is->select_lock);
7341 imapx_free_capability (is->cinfo);
7345 is->state = IMAPX_DISCONNECTED;
7347 g_object_notify (G_OBJECT (is), "stream");
7352 /* Client commands */
7354 camel_imapx_server_connect (CamelIMAPXServer *is,
7355 GCancellable *cancellable,
7358 if (is->state == IMAPX_SHUTDOWN) {
7359 g_set_error (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE, "Shutting down");
7363 if (is->state >= IMAPX_INITIALISED)
7366 if (!imapx_reconnect (is, cancellable, error))
7369 is->parser_thread = g_thread_new (NULL, (GThreadFunc) imapx_parser_thread, is);
7374 static CamelStream *
7375 imapx_server_get_message (CamelIMAPXServer *is,
7376 CamelFolder *folder,
7379 GCancellable *cancellable,
7382 CamelStream *stream = NULL;
7383 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
7385 CamelMessageInfo *mi;
7386 GetMessageData *data;
7387 gboolean registered;
7392 if ((job = imapx_is_job_in_queue (is, folder, IMAPX_JOB_GET_MESSAGE, uid))) {
7396 /* Wait for the job to finish. This would be so much nicer if
7397 * we could just use the queue lock with a GCond, but instead
7398 * we have to use a GMutex. I miss the kernel waitqueues. */
7402 g_mutex_lock (&is->fetch_mutex);
7403 this = is->fetch_count;
7407 while (is->fetch_count == this)
7408 g_cond_wait (&is->fetch_cond, &is->fetch_mutex);
7410 g_mutex_unlock (&is->fetch_mutex);
7414 } while (imapx_is_job_in_queue (is, folder,
7415 IMAPX_JOB_GET_MESSAGE, uid));
7419 stream = camel_data_cache_get (
7420 ifolder->cache, "cur", uid, error);
7423 error, "Could not retrieve the message: ");
7427 mi = camel_folder_summary_get (folder->summary, uid);
7430 error, CAMEL_FOLDER_ERROR,
7431 CAMEL_FOLDER_ERROR_INVALID_UID,
7432 _("Cannot get message with message ID %s: %s"),
7433 uid, _("No such message available."));
7438 data = g_slice_new0 (GetMessageData);
7439 data->uid = g_strdup (uid);
7440 data->stream = camel_data_cache_add (ifolder->cache, "tmp", uid, NULL);
7441 data->size = ((CamelMessageInfoBase *) mi)->size;
7442 if (data->size > MULTI_SIZE)
7443 data->use_multi_fetch = TRUE;
7445 job = camel_imapx_job_new (cancellable);
7447 job->type = IMAPX_JOB_GET_MESSAGE;
7448 job->start = imapx_job_get_message_start;
7449 job->matches = imapx_job_get_message_matches;
7451 camel_imapx_job_set_folder (job, folder);
7453 camel_imapx_job_set_data (
7454 job, data, (GDestroyNotify) get_message_data_free);
7456 camel_message_info_free (mi);
7457 registered = imapx_register_job (is, job, error);
7461 success = registered && camel_imapx_job_run (job, is, error);
7464 stream = g_object_ref (data->stream);
7466 camel_imapx_job_unref (job);
7468 g_mutex_lock (&is->fetch_mutex);
7470 g_cond_broadcast (&is->fetch_cond);
7471 g_mutex_unlock (&is->fetch_mutex);
7477 camel_imapx_server_get_message (CamelIMAPXServer *is,
7478 CamelFolder *folder,
7480 GCancellable *cancellable,
7483 CamelStream *stream;
7485 stream = imapx_server_get_message (
7487 IMAPX_PRIORITY_GET_MESSAGE,
7488 cancellable, error);
7494 camel_imapx_server_sync_message (CamelIMAPXServer *is,
7495 CamelFolder *folder,
7497 GCancellable *cancellable,
7500 gchar *cache_file = NULL;
7501 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
7502 CamelStream *stream;
7506 /* Check if the cache file already exists and is non-empty. */
7507 cache_file = camel_data_cache_get_filename (
7508 ifolder->cache, "cur", uid);
7509 is_cached = (g_stat (cache_file, &st) == 0 && st.st_size > 0);
7510 g_free (cache_file);
7515 stream = imapx_server_get_message (
7517 IMAPX_PRIORITY_SYNC_MESSAGE,
7518 cancellable, error);
7523 g_object_unref (stream);
7529 camel_imapx_server_copy_message (CamelIMAPXServer *is,
7530 CamelFolder *source,
7533 gboolean delete_originals,
7534 GCancellable *cancellable,
7538 CopyMessagesData *data;
7541 data = g_slice_new0 (CopyMessagesData);
7542 data->dest = g_object_ref (dest);
7543 data->uids = g_ptr_array_new ();
7544 data->delete_originals = delete_originals;
7546 for (ii = 0; ii < uids->len; ii++)
7547 g_ptr_array_add (data->uids, g_strdup (uids->pdata[ii]));
7549 job = camel_imapx_job_new (cancellable);
7550 job->pri = IMAPX_PRIORITY_APPEND_MESSAGE;
7551 job->type = IMAPX_JOB_COPY_MESSAGE;
7552 job->start = imapx_job_copy_messages_start;
7554 camel_imapx_job_set_folder (job, source);
7556 camel_imapx_job_set_data (
7557 job, data, (GDestroyNotify) copy_messages_data_free);
7559 return imapx_submit_job (is, job, error);
7563 camel_imapx_server_append_message (CamelIMAPXServer *is,
7564 CamelFolder *folder,
7565 CamelMimeMessage *message,
7566 const CamelMessageInfo *mi,
7567 gchar **appended_uid,
7568 GCancellable *cancellable,
7571 gchar *uid = NULL, *path = NULL;
7572 CamelStream *stream, *filter;
7573 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
7574 CamelMimeFilter *canon;
7576 CamelMessageInfo *info;
7577 AppendMessageData *data;
7581 /* Append just assumes we have no/a dodgy connection. We dump
7582 * stuff into the 'new' directory, and let the summary know it's
7583 * there. Then we fire off a no-reply job which will asynchronously
7584 * upload the message at some point in the future, and fix up the
7585 * summary to match */
7587 /* chen cleanup this later */
7588 uid = imapx_get_temp_uid ();
7589 stream = camel_data_cache_add (ifolder->cache, "new", uid, error);
7590 if (stream == NULL) {
7591 g_prefix_error (error, _("Cannot create spool file: "));
7596 filter = camel_stream_filter_new (stream);
7597 g_object_unref (stream);
7598 canon = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_CRLF);
7599 camel_stream_filter_add ((CamelStreamFilter *) filter, canon);
7600 res = camel_data_wrapper_write_to_stream_sync (
7601 (CamelDataWrapper *) message, filter, cancellable, error);
7602 g_object_unref (canon);
7603 g_object_unref (filter);
7606 g_prefix_error (error, _("Cannot create spool file: "));
7607 camel_data_cache_remove (ifolder->cache, "new", uid, NULL);
7612 path = camel_data_cache_get_filename (ifolder->cache, "new", uid);
7613 info = camel_folder_summary_info_new_from_message ((CamelFolderSummary *) folder->summary, message, NULL);
7614 info->uid = camel_pstring_strdup (uid);
7616 CamelMessageInfoBase *base_info = (CamelMessageInfoBase *) info;
7618 base_info->flags = camel_message_info_flags (mi);
7619 base_info->size = camel_message_info_size (mi);
7621 if ((is->permanentflags & CAMEL_MESSAGE_USER) != 0) {
7622 const CamelFlag *flag;
7623 const CamelTag *tag;
7625 flag = camel_message_info_user_flags (mi);
7627 if (flag->name && *flag->name)
7628 camel_flag_set (&base_info->user_flags, flag->name, TRUE);
7632 tag = camel_message_info_user_tags (mi);
7634 if (tag->name && *tag->name)
7635 camel_tag_set (&base_info->user_tags, tag->name, tag->value);
7643 /* So, we actually just want to let the server loop that
7644 * messages need appending, i think. This is so the same
7645 * mechanism is used for normal uploading as well as
7646 * offline re-syncing when we go back online */
7648 data = g_slice_new0 (AppendMessageData);
7649 data->info = info; /* takes ownership */
7650 data->path = path; /* takes ownership */
7651 data->appended_uid = NULL;
7653 job = camel_imapx_job_new (cancellable);
7654 job->pri = IMAPX_PRIORITY_APPEND_MESSAGE;
7655 job->type = IMAPX_JOB_APPEND_MESSAGE;
7656 job->start = imapx_job_append_message_start;
7657 job->noreply = FALSE;
7659 camel_imapx_job_set_folder (job, folder);
7661 camel_imapx_job_set_data (
7662 job, data, (GDestroyNotify) append_message_data_free);
7664 success = imapx_submit_job (is, job, error);
7666 if (appended_uid != NULL) {
7667 *appended_uid = data->appended_uid;
7668 data->appended_uid = NULL;
7671 camel_imapx_job_unref (job);
7677 camel_imapx_server_noop (CamelIMAPXServer *is,
7678 CamelFolder *folder,
7679 GCancellable *cancellable,
7685 job = camel_imapx_job_new (cancellable);
7686 job->type = IMAPX_JOB_NOOP;
7687 job->start = imapx_job_noop_start;
7688 job->pri = IMAPX_PRIORITY_NOOP;
7690 camel_imapx_job_set_folder (job, folder);
7692 success = imapx_submit_job (is, job, error);
7694 camel_imapx_job_unref (job);
7700 camel_imapx_server_refresh_info (CamelIMAPXServer *is,
7701 CamelFolder *folder,
7702 GCancellable *cancellable,
7706 RefreshInfoData *data;
7707 gboolean registered = TRUE;
7708 const gchar *full_name;
7709 gboolean success = TRUE;
7711 full_name = camel_folder_get_full_name (folder);
7715 /* Both RefreshInfo and Fetch messages can't operate simultaneously */
7716 if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_REFRESH_INFO, NULL) ||
7717 imapx_is_job_in_queue (is, folder, IMAPX_JOB_FETCH_MESSAGES, NULL)) {
7722 data = g_slice_new0 (RefreshInfoData);
7723 data->changes = camel_folder_change_info_new ();
7725 job = camel_imapx_job_new (cancellable);
7726 job->type = IMAPX_JOB_REFRESH_INFO;
7727 job->start = imapx_job_refresh_info_start;
7728 job->matches = imapx_job_refresh_info_matches;
7729 job->pri = IMAPX_PRIORITY_REFRESH_INFO;
7731 camel_imapx_job_set_folder (job, folder);
7733 if (g_ascii_strcasecmp (full_name, "INBOX") == 0)
7736 camel_imapx_job_set_data (
7737 job, data, (GDestroyNotify) refresh_info_data_free);
7739 registered = imapx_register_job (is, job, error);
7743 success = registered && camel_imapx_job_run (job, is, error);
7745 if (success && camel_folder_change_info_changed (data->changes))
7746 camel_folder_changed (folder, data->changes);
7748 camel_imapx_job_unref (job);
7754 imapx_sync_free_user (GArray *user_set)
7758 if (user_set == NULL)
7761 for (i = 0; i < user_set->len; i++) {
7762 struct _imapx_flag_change *flag_change = &g_array_index (user_set, struct _imapx_flag_change, i);
7763 GPtrArray *infos = flag_change->infos;
7766 for (j = 0; j < infos->len; j++) {
7767 CamelMessageInfo *info = g_ptr_array_index (infos, j);
7768 camel_message_info_free (info);
7771 g_ptr_array_free (infos, TRUE);
7772 g_free (flag_change->name);
7774 g_array_free (user_set, TRUE);
7778 imapx_server_sync_changes (CamelIMAPXServer *is,
7779 CamelFolder *folder,
7782 GCancellable *cancellable,
7785 guint i, on_orset, off_orset;
7786 GPtrArray *changed_uids;
7787 GArray *on_user = NULL, *off_user = NULL;
7788 CamelIMAPXMessageInfo *info;
7790 CamelIMAPXSettings *settings;
7791 SyncChangesData *data;
7792 gboolean use_real_junk_path;
7793 gboolean use_real_trash_path;
7794 gboolean nothing_to_do;
7795 gboolean registered;
7796 gboolean success = TRUE;
7798 /* We calculate two masks, a mask of all flags which have been
7799 * turned off and a mask of all flags which have been turned
7800 * on. If either of these aren't 0, then we have work to do,
7801 * and we fire off a job to do it.
7803 * User flags are a bit more tricky, we rely on the user
7804 * flags being sorted, and then we create a bunch of lists;
7805 * one for each flag being turned off, including each
7806 * info being turned off, and one for each flag being turned on.
7808 changed_uids = camel_folder_summary_get_changed (folder->summary);
7810 if (changed_uids->len == 0) {
7811 camel_folder_free_uids (folder, changed_uids);
7815 settings = camel_imapx_server_ref_settings (is);
7816 use_real_junk_path =
7817 camel_imapx_settings_get_use_real_junk_path (settings);
7818 use_real_trash_path =
7819 camel_imapx_settings_get_use_real_trash_path (settings);
7820 g_object_unref (settings);
7822 off_orset = on_orset = 0;
7823 for (i = 0; i < changed_uids->len; i++) {
7824 guint32 flags, sflags;
7825 CamelFlag *uflags, *suflags;
7827 gboolean move_to_real_junk;
7828 gboolean move_to_real_trash;
7831 uid = g_ptr_array_index (changed_uids, i);
7833 info = (CamelIMAPXMessageInfo *)
7834 camel_folder_summary_get (folder->summary, uid);
7839 if (!(info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) {
7840 camel_message_info_free (info);
7844 flags = info->info.flags & CAMEL_IMAPX_SERVER_FLAGS;
7845 sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
7848 use_real_junk_path &&
7849 (flags & CAMEL_MESSAGE_JUNK);
7851 move_to_real_trash =
7852 use_real_trash_path &&
7853 (flags & CAMEL_MESSAGE_DELETED);
7855 if (move_to_real_junk)
7856 camel_imapx_folder_add_move_to_real_junk (
7857 CAMEL_IMAPX_FOLDER (folder), uid);
7859 if (move_to_real_trash)
7860 camel_imapx_folder_add_move_to_real_trash (
7861 CAMEL_IMAPX_FOLDER (folder), uid);
7863 if (flags != sflags) {
7864 off_orset |= (flags ^ sflags) & ~flags;
7865 on_orset |= (flags ^ sflags) & flags;
7868 uflags = info->info.user_flags;
7869 suflags = info->server_user_flags;
7870 while (uflags || suflags) {
7875 res = strcmp (uflags->name, suflags->name);
7876 else if (*uflags->name)
7879 uflags = uflags->next;
7887 uflags = uflags->next;
7888 suflags = suflags->next;
7891 CamelFlag *user_flag;
7892 struct _imapx_flag_change *change = NULL, add = { 0 };
7895 if (on_user == NULL)
7896 on_user = g_array_new (FALSE, FALSE, sizeof (struct _imapx_flag_change));
7899 uflags = uflags->next;
7901 if (off_user == NULL)
7902 off_user = g_array_new (FALSE, FALSE, sizeof (struct _imapx_flag_change));
7903 user_set = off_user;
7904 user_flag = suflags;
7905 suflags = suflags->next;
7908 /* Could sort this and binary search */
7909 for (j = 0; j < user_set->len; j++) {
7910 change = &g_array_index (user_set, struct _imapx_flag_change, j);
7911 if (strcmp (change->name, user_flag->name) == 0)
7914 add.name = g_strdup (user_flag->name);
7915 add.infos = g_ptr_array_new ();
7916 g_array_append_val (user_set, add);
7919 camel_message_info_ref (info);
7920 g_ptr_array_add (change->infos, info);
7923 camel_message_info_free (info);
7929 (on_user == NULL) &&
7932 if (nothing_to_do) {
7933 imapx_sync_free_user (on_user);
7934 imapx_sync_free_user (off_user);
7935 camel_folder_free_uids (folder, changed_uids);
7939 /* TODO above code should go into changes_start */
7943 if ((job = imapx_is_job_in_queue (is, folder, IMAPX_JOB_SYNC_CHANGES, NULL))) {
7949 imapx_sync_free_user (on_user);
7950 imapx_sync_free_user (off_user);
7951 camel_folder_free_uids (folder, changed_uids);
7955 data = g_slice_new0 (SyncChangesData);
7956 data->folder = g_object_ref (folder);
7957 data->changed_uids = changed_uids; /* takes ownership */
7958 data->on_set = on_orset;
7959 data->off_set = off_orset;
7960 data->on_user = on_user; /* takes ownership */
7961 data->off_user = off_user; /* takes ownership */
7963 data->remove_deleted_flags =
7964 use_real_trash_path &&
7965 (job_type != IMAPX_JOB_EXPUNGE);
7967 job = camel_imapx_job_new (cancellable);
7968 job->type = IMAPX_JOB_SYNC_CHANGES;
7969 job->start = imapx_job_sync_changes_start;
7970 job->matches = imapx_job_sync_changes_matches;
7973 camel_imapx_job_set_folder (job, folder);
7975 camel_imapx_job_set_data (
7976 job, data, (GDestroyNotify) sync_changes_data_free);
7978 registered = imapx_register_job (is, job, error);
7982 success = registered && camel_imapx_job_run (job, is, error);
7984 camel_imapx_job_unref (job);
7990 camel_imapx_server_sync_changes (CamelIMAPXServer *is,
7991 CamelFolder *folder,
7992 GCancellable *cancellable,
7995 return imapx_server_sync_changes (
7997 IMAPX_JOB_SYNC_CHANGES,
7998 IMAPX_PRIORITY_SYNC_CHANGES,
7999 cancellable, error);
8004 camel_imapx_server_expunge (CamelIMAPXServer *is,
8005 CamelFolder *folder,
8006 GCancellable *cancellable,
8010 gboolean registered;
8013 /* Do we really care to wait for this one to finish? */
8016 if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_EXPUNGE, NULL)) {
8021 job = camel_imapx_job_new (cancellable);
8022 job->type = IMAPX_JOB_EXPUNGE;
8023 job->start = imapx_job_expunge_start;
8024 job->matches = imapx_job_expunge_matches;
8025 job->pri = IMAPX_PRIORITY_EXPUNGE;
8027 camel_imapx_job_set_folder (job, folder);
8029 registered = imapx_register_job (is, job, error);
8033 success = registered && camel_imapx_job_run (job, is, error);
8035 camel_imapx_job_unref (job);
8041 imapx_name_hash (gconstpointer key)
8043 if (g_ascii_strcasecmp (key, "INBOX") == 0)
8044 return g_str_hash ("INBOX");
8046 return g_str_hash (key);
8050 imapx_name_equal (gconstpointer a,
8053 gconstpointer aname = a, bname = b;
8055 if (g_ascii_strcasecmp (a, "INBOX") == 0)
8057 if (g_ascii_strcasecmp (b, "INBOX") == 0)
8059 return g_str_equal (aname, bname);
8063 imapx_list_flatten (gpointer k,
8067 GPtrArray *folders = d;
8069 g_ptr_array_add (folders, v);
8073 imapx_list_cmp (gconstpointer ap,
8076 struct _list_info *a = ((struct _list_info **) ap)[0];
8077 struct _list_info *b = ((struct _list_info **) bp)[0];
8079 return strcmp (a->name, b->name);
8083 camel_imapx_server_list (CamelIMAPXServer *is,
8087 GCancellable *cancellable,
8091 GPtrArray *folders = NULL;
8093 gchar *encoded_name;
8095 encoded_name = camel_utf8_utf7 (top);
8097 data = g_slice_new0 (ListData);
8098 data->flags = flags;
8099 data->ext = g_strdup (ext);
8100 data->folders = g_hash_table_new (imapx_name_hash, imapx_name_equal);
8102 if (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)
8103 data->pattern = g_strdup_printf ("%s*", encoded_name);
8105 data->pattern = g_strdup (encoded_name);
8107 job = camel_imapx_job_new (cancellable);
8108 job->type = IMAPX_JOB_LIST;
8109 job->start = imapx_job_list_start;
8110 job->matches = imapx_job_list_matches;
8111 job->pri = IMAPX_PRIORITY_LIST;
8113 camel_imapx_job_set_data (
8114 job, data, (GDestroyNotify) list_data_free);
8116 /* sync operation which is triggered by user */
8117 if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST)
8120 if (imapx_submit_job (is, job, error)) {
8121 folders = g_ptr_array_new ();
8122 g_hash_table_foreach (data->folders, imapx_list_flatten, folders);
8123 qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), imapx_list_cmp);
8126 g_free (encoded_name);
8127 camel_imapx_job_unref (job);
8133 camel_imapx_server_manage_subscription (CamelIMAPXServer *is,
8134 const gchar *folder_name,
8136 GCancellable *cancellable,
8140 ManageSubscriptionsData *data;
8143 data = g_slice_new0 (ManageSubscriptionsData);
8144 data->folder_name = g_strdup (folder_name);
8145 data->subscribe = subscribe;
8147 job = camel_imapx_job_new (cancellable);
8148 job->type = IMAPX_JOB_MANAGE_SUBSCRIPTION;
8149 job->start = imapx_job_manage_subscription_start;
8150 job->pri = IMAPX_PRIORITY_MANAGE_SUBSCRIPTION;
8152 camel_imapx_job_set_data (
8153 job, data, (GDestroyNotify) manage_subscriptions_data_free);
8155 success = imapx_submit_job (is, job, error);
8157 camel_imapx_job_unref (job);
8163 camel_imapx_server_create_folder (CamelIMAPXServer *is,
8164 const gchar *folder_name,
8165 GCancellable *cancellable,
8169 CreateFolderData *data;
8172 data = g_slice_new0 (CreateFolderData);
8173 data->folder_name = g_strdup (folder_name);
8175 job = camel_imapx_job_new (cancellable);
8176 job->type = IMAPX_JOB_CREATE_FOLDER;
8177 job->start = imapx_job_create_folder_start;
8178 job->pri = IMAPX_PRIORITY_CREATE_FOLDER;
8180 camel_imapx_job_set_data (
8181 job, data, (GDestroyNotify) create_folder_data_free);
8183 success = imapx_submit_job (is, job, error);
8185 camel_imapx_job_unref (job);
8191 camel_imapx_server_delete_folder (CamelIMAPXServer *is,
8192 const gchar *folder_name,
8193 GCancellable *cancellable,
8197 DeleteFolderData *data;
8200 data = g_slice_new0 (DeleteFolderData);
8201 data->folder_name = g_strdup (folder_name);
8203 job = camel_imapx_job_new (cancellable);
8204 job->type = IMAPX_JOB_DELETE_FOLDER;
8205 job->start = imapx_job_delete_folder_start;
8206 job->pri = IMAPX_PRIORITY_DELETE_FOLDER;
8208 camel_imapx_job_set_data (
8209 job, data, (GDestroyNotify) delete_folder_data_free);
8211 success = imapx_submit_job (is, job, error);
8213 camel_imapx_job_unref (job);
8219 imapx_job_fetch_messages_matches (CamelIMAPXJob *job,
8220 CamelFolder *folder,
8223 return camel_imapx_job_has_folder (job, folder);
8227 camel_imapx_server_fetch_messages (CamelIMAPXServer *is,
8228 CamelFolder *folder,
8229 CamelFetchType type,
8231 GCancellable *cancellable,
8235 RefreshInfoData *data;
8236 gboolean registered = TRUE;
8237 const gchar *full_name;
8238 gboolean success = TRUE;
8239 guint64 firstuid, newfirstuid;
8243 old_len = camel_folder_summary_count (folder->summary);
8244 uid = imapx_get_uid_from_index (folder->summary, 0);
8245 firstuid = strtoull (uid, NULL, 10);
8250 /* Both RefreshInfo and Fetch messages can't operate simultaneously */
8251 if (imapx_is_job_in_queue (is, folder, IMAPX_JOB_REFRESH_INFO, NULL) ||
8252 imapx_is_job_in_queue (is, folder, IMAPX_JOB_FETCH_MESSAGES, NULL)) {
8257 data = g_slice_new0 (RefreshInfoData);
8258 data->changes = camel_folder_change_info_new ();
8259 data->fetch_msg_limit = limit;
8260 data->fetch_type = type;
8262 job = camel_imapx_job_new (cancellable);
8263 job->type = IMAPX_JOB_FETCH_MESSAGES;
8264 job->start = imapx_job_fetch_messages_start;
8265 job->matches = imapx_job_fetch_messages_matches;
8266 job->pri = IMAPX_PRIORITY_NEW_MESSAGES;
8268 camel_imapx_job_set_folder (job, folder);
8270 full_name = camel_folder_get_full_name (folder);
8272 if (g_ascii_strcasecmp (full_name, "INBOX") == 0)
8275 camel_imapx_job_set_data (
8276 job, data, (GDestroyNotify) refresh_info_data_free);
8278 registered = imapx_register_job (is, job, error);
8282 success = registered && camel_imapx_job_run (job, is, error);
8284 if (success && camel_folder_change_info_changed (data->changes) && camel_folder_change_info_changed (data->changes))
8285 camel_folder_changed (folder, data->changes);
8287 uid = imapx_get_uid_from_index (folder->summary, 0);
8288 newfirstuid = strtoull (uid, NULL, 10);
8291 camel_imapx_job_unref (job);
8293 if (type == CAMEL_FETCH_OLD_MESSAGES && firstuid == newfirstuid)
8294 return FALSE; /* No more old messages */
8295 else if (type == CAMEL_FETCH_NEW_MESSAGES &&
8296 old_len == camel_folder_summary_count (folder->summary))
8297 return FALSE; /* No more new messages */
8303 camel_imapx_server_rename_folder (CamelIMAPXServer *is,
8304 const gchar *old_name,
8305 const gchar *new_name,
8306 GCancellable *cancellable,
8310 RenameFolderData *data;
8313 data = g_slice_new0 (RenameFolderData);
8314 data->old_folder_name = g_strdup (old_name);
8315 data->new_folder_name = g_strdup (new_name);
8317 job = camel_imapx_job_new (cancellable);
8318 job->type = IMAPX_JOB_RENAME_FOLDER;
8319 job->start = imapx_job_rename_folder_start;
8320 job->pri = IMAPX_PRIORITY_RENAME_FOLDER;
8322 camel_imapx_job_set_data (
8323 job, data, (GDestroyNotify) rename_folder_data_free);
8325 success = imapx_submit_job (is, job, error);
8327 camel_imapx_job_unref (job);
8333 camel_imapx_server_update_quota_info (CamelIMAPXServer *is,
8334 const gchar *folder_name,
8335 GCancellable *cancellable,
8342 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
8343 g_return_val_if_fail (folder_name != NULL, FALSE);
8345 if (is->cinfo && (is->cinfo->capa & IMAPX_CAPABILITY_QUOTA) == 0) {
8346 g_set_error_literal (
8347 error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
8348 _("IMAP server does not support quotas"));
8352 data = g_slice_new0 (QuotaData);
8353 data->folder_name = g_strdup (folder_name);
8355 job = camel_imapx_job_new (cancellable);
8356 job->type = IMAPX_JOB_UPDATE_QUOTA_INFO;
8357 job->start = imapx_job_update_quota_info_start;
8358 job->pri = IMAPX_PRIORITY_UPDATE_QUOTA_INFO;
8360 camel_imapx_job_set_data (
8361 job, data, (GDestroyNotify) quota_data_free);
8363 success = imapx_submit_job (is, job, error);
8365 camel_imapx_job_unref (job);
8371 camel_imapx_server_uid_search (CamelIMAPXServer *is,
8372 CamelFolder *folder,
8373 const gchar *criteria,
8374 GCancellable *cancellable,
8379 GPtrArray *results = NULL;
8381 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
8382 g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
8383 g_return_val_if_fail (criteria != NULL, NULL);
8385 data = g_slice_new0 (SearchData);
8386 data->criteria = g_strdup (criteria);
8388 job = camel_imapx_job_new (cancellable);
8389 job->type = IMAPX_JOB_UID_SEARCH;
8390 job->start = imapx_job_uid_search_start;
8391 job->pri = IMAPX_PRIORITY_SEARCH;
8393 camel_imapx_job_set_folder (job, folder);
8395 camel_imapx_job_set_data (
8396 job, data, (GDestroyNotify) search_data_free);
8398 if (imapx_submit_job (is, job, error)) {
8401 /* Convert the numeric UIDs to strings. */
8403 g_return_val_if_fail (data->results != NULL, NULL);
8405 results = g_ptr_array_new_full (
8407 (GDestroyNotify) camel_pstring_free);
8409 for (ii = 0; ii < data->results->len; ii++) {
8410 const gchar *pooled_uid;
8411 guint64 numeric_uid;
8414 numeric_uid = g_array_index (
8415 data->results, guint64, ii);
8416 alloced_uid = g_strdup_printf (
8417 "%" G_GUINT64_FORMAT, numeric_uid);
8418 pooled_uid = camel_pstring_add (alloced_uid, TRUE);
8419 g_ptr_array_add (results, (gpointer) pooled_uid);
8423 camel_imapx_job_unref (job);
8429 camel_imapx_server_get_job_queue_info (CamelIMAPXServer *is)
8431 IMAPXJobQueueInfo *jinfo = g_new0 (IMAPXJobQueueInfo, 1);
8432 CamelFolder *select_folder;
8433 CamelIMAPXJob *job = NULL;
8438 jinfo->queue_len = g_queue_get_length (&is->jobs);
8439 jinfo->folders = g_hash_table_new_full (
8440 (GHashFunc) g_str_hash,
8441 (GEqualFunc) g_str_equal,
8442 (GDestroyNotify) g_free,
8443 (GDestroyNotify) NULL);
8445 head = g_queue_peek_head_link (&is->jobs);
8447 for (link = head; link != NULL; link = g_list_next (link)) {
8448 CamelFolder *folder;
8450 job = (CamelIMAPXJob *) link->data;
8451 folder = camel_imapx_job_ref_folder (job);
8453 if (folder != NULL) {
8456 folder_name = camel_folder_dup_full_name (folder);
8457 g_hash_table_add (jinfo->folders, folder_name);
8459 g_object_unref (folder);
8463 select_folder = g_weak_ref_get (&is->select_folder);
8465 if (select_folder != NULL) {
8468 folder_name = camel_folder_dup_full_name (select_folder);
8469 g_hash_table_add (jinfo->folders, folder_name);
8471 g_object_unref (select_folder);
8480 * camel_imapx_server_register_untagged_handler:
8481 * @is: a #CamelIMAPXServer instance
8482 * @untagged_response: a string representation of the IMAP
8483 * untagged response code. Must be
8484 * all-uppercase with underscores allowed
8486 * @desc: a #CamelIMAPXUntaggedRespHandlerDesc handler description
8487 * structure. The descriptor structure is expected to
8488 * remain stable over the lifetime of the #CamelIMAPXServer
8489 * instance it was registered with. It is the responsibility
8490 * of the caller to ensure this
8492 * Register a new handler function for IMAP untagged responses.
8493 * Pass in a NULL descriptor to delete an existing handler (the
8494 * untagged response will remain known, but will no longer be acted
8495 * upon if the handler is deleted). The return value is intended
8496 * to be used in cases where e.g. an extension to existing handler
8497 * code is implemented with just some new code to be run before
8498 * or after the original handler code
8500 * Returns: the #CamelIMAPXUntaggedRespHandlerDesc previously
8501 * registered for this untagged response, if any,
8506 const CamelIMAPXUntaggedRespHandlerDesc *
8507 camel_imapx_server_register_untagged_handler (CamelIMAPXServer *is,
8508 const gchar *untagged_response,
8509 const CamelIMAPXUntaggedRespHandlerDesc *desc)
8511 const CamelIMAPXUntaggedRespHandlerDesc *previous = NULL;
8513 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), NULL);
8514 g_return_val_if_fail (untagged_response != NULL, NULL);
8515 /* desc may be NULL */
8517 previous = replace_untagged_descriptor (
8518 is->priv->untagged_handlers,
8519 untagged_response, desc);
8525 camel_imapx_server_command_run (CamelIMAPXServer *is,
8526 CamelIMAPXCommand *ic,
8527 GCancellable *cancellable,
8530 gboolean ok = FALSE;
8531 CamelIMAPXJob *job = NULL;
8532 gboolean local_job = FALSE;
8534 g_return_val_if_fail (CAMEL_IS_IMAPX_SERVER (is), FALSE);
8535 g_return_val_if_fail (CAMEL_IS_IMAPX_COMMAND (ic), FALSE);
8536 /* cancellable may be NULL */
8537 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
8539 job = camel_imapx_command_get_job (ic);
8541 job = camel_imapx_job_new (cancellable);
8542 camel_imapx_command_set_job (ic, job);
8546 ok = imapx_command_run_sync (is, ic, cancellable, error);
8549 camel_imapx_command_set_job (ic, NULL);