1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-folder.c: class for an imap folder */
6 * Dan Winship <danw@ximian.com>
7 * Jeffrey Stedfast <fejj@ximian.com>
9 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of version 2 of the GNU Lesser General Public
13 * License as published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
35 #include <sys/types.h>
37 #include <glib/gi18n-lib.h>
39 #include <camel/camel.h>
41 #include "camel-imap-command.h"
42 #include "camel-imap-folder.h"
43 #include "camel-imap-journal.h"
44 #include "camel-imap-message-cache.h"
45 #include "camel-imap-private.h"
46 #include "camel-imap-search.h"
47 #include "camel-imap-settings.h"
48 #include "camel-imap-store.h"
49 #include "camel-imap-store-summary.h"
50 #include "camel-imap-summary.h"
51 #include "camel-imap-utils.h"
52 #include "camel-imap-wrapper.h"
56 /* set to -1 for infinite size (suggested max command-line length is
57 * 1000 octets (see rfc2683), so we should keep the uid-set length to
58 * something under that so that our command-lines don't exceed 1000
60 #define UID_SET_LIMIT (768)
62 #define CAMEL_IMAP_FOLDER_GET_PRIVATE(obj) \
63 (G_TYPE_INSTANCE_GET_PRIVATE \
64 ((obj), CAMEL_TYPE_IMAP_FOLDER, CamelImapFolderPrivate))
66 /* The custom property ID is a CamelArg artifact.
67 * It still identifies the property in state files. */
70 PROP_CHECK_FOLDER = 0x2500,
74 extern gint camel_application_is_exiting;
76 static gboolean imap_rescan (CamelFolder *folder, gint exists, GCancellable *cancellable, GError **error);
77 static gboolean imap_refresh_info_sync (CamelFolder *folder, GCancellable *cancellable, GError **error);
78 static gboolean imap_sync_offline (CamelFolder *folder, GError **error);
79 static gboolean imap_synchronize_sync (CamelFolder *folder, gboolean expunge, GCancellable *cancellable, GError **error);
80 static gboolean imap_expunge_uids_online (CamelFolder *folder, GPtrArray *uids, GCancellable *cancellable, GError **error);
81 static gboolean imap_expunge_uids_offline (CamelFolder *folder, GPtrArray *uids, GCancellable *cancellable, GError **error);
82 static gboolean imap_expunge_sync (CamelFolder *folder, GCancellable *cancellable, GError **error);
83 /*static void imap_cache_message (CamelDiscoFolder *disco_folder, const gchar *uid, GError **error);*/
84 static void imap_rename (CamelFolder *folder, const gchar *new);
85 static GPtrArray * imap_get_uncached_uids (CamelFolder *folder, GPtrArray * uids, GError **error);
86 static gchar * imap_get_filename (CamelFolder *folder, const gchar *uid, GError **error);
88 /* message manipulation */
89 static CamelMimeMessage *imap_get_message_sync (CamelFolder *folder, const gchar *uid, GCancellable *cancellable,
91 static gboolean imap_synchronize_message_sync (CamelFolder *folder, const gchar *uid, GCancellable *cancellable,
93 static gboolean imap_append_online (CamelFolder *folder, CamelMimeMessage *message,
94 CamelMessageInfo *info, gchar **appended_uid,
95 GCancellable *cancellable,
97 static gboolean imap_append_offline (CamelFolder *folder, CamelMimeMessage *message,
98 CamelMessageInfo *info, gchar **appended_uid,
101 static gboolean imap_transfer_online (CamelFolder *source,
104 gboolean delete_originals,
105 GPtrArray **transferred_uids,
106 GCancellable *cancellable,
108 static gboolean imap_transfer_offline (CamelFolder *source,
111 gboolean delete_originals,
112 GPtrArray **transferred_uids,
113 GCancellable *cancellable,
117 static GPtrArray *imap_search_by_expression (CamelFolder *folder, const gchar *expression, GCancellable *cancellable, GError **error);
118 static guint32 imap_count_by_expression (CamelFolder *folder, const gchar *expression, GCancellable *cancellable, GError **error);
119 static GPtrArray *imap_search_by_uids (CamelFolder *folder, const gchar *expression, GPtrArray *uids, GCancellable *cancellable, GError **error);
120 static void imap_search_free (CamelFolder *folder, GPtrArray *uids);
122 static void imap_thaw (CamelFolder *folder);
123 static CamelFolderQuotaInfo *
124 imap_get_quota_info_sync (CamelFolder *folder,
125 GCancellable *cancellable,
128 static GData *parse_fetch_response (CamelImapFolder *imap_folder, gchar *msg_att);
130 /* internal helpers */
131 static CamelImapMessageInfo * imap_folder_summary_uid_or_error (
132 CamelFolderSummary *summary,
136 static gboolean imap_transfer_messages (CamelFolder *source,
139 gboolean delete_originals,
140 GPtrArray **transferred_uids,
141 gboolean can_call_sync,
142 GCancellable *cancellable,
146 imap_folder_get_apply_filters (CamelImapFolder *folder)
148 g_return_val_if_fail (folder != NULL, FALSE);
149 g_return_val_if_fail (CAMEL_IS_IMAP_FOLDER (folder), FALSE);
151 return folder->priv->apply_filters;
155 imap_folder_set_apply_filters (CamelImapFolder *folder,
156 gboolean apply_filters)
158 g_return_if_fail (folder != NULL);
159 g_return_if_fail (CAMEL_IS_IMAP_FOLDER (folder));
161 if ((folder->priv->apply_filters ? 1 : 0) == (apply_filters ? 1 : 0))
164 folder->priv->apply_filters = apply_filters;
166 g_object_notify (G_OBJECT (folder), "apply-filters");
170 /* The strtok() in Microsoft's C library is MT-safe (but still uses
171 * only one buffer pointer per thread, but for the use of strtok_r()
172 * here that's enough).
174 #define strtok_r(s,sep,lasts) (*(lasts)=strtok((s),(sep)))
177 G_DEFINE_TYPE (CamelImapFolder, camel_imap_folder, CAMEL_TYPE_OFFLINE_FOLDER)
180 imap_folder_set_property (GObject *object,
185 switch (property_id) {
186 case PROP_CHECK_FOLDER:
187 camel_imap_folder_set_check_folder (
188 CAMEL_IMAP_FOLDER (object),
189 g_value_get_boolean (value));
192 case PROP_APPLY_FILTERS:
193 imap_folder_set_apply_filters (
194 CAMEL_IMAP_FOLDER (object),
195 g_value_get_boolean (value));
199 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
203 imap_folder_get_property (GObject *object,
208 switch (property_id) {
209 case PROP_CHECK_FOLDER:
210 g_value_set_boolean (
211 value, camel_imap_folder_get_check_folder (
212 CAMEL_IMAP_FOLDER (object)));
215 case PROP_APPLY_FILTERS:
216 g_value_set_boolean (
217 value, imap_folder_get_apply_filters (
218 CAMEL_IMAP_FOLDER (object)));
222 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
226 imap_folder_dispose (GObject *object)
228 CamelImapFolder *imap_folder;
229 CamelStore *parent_store;
231 imap_folder = CAMEL_IMAP_FOLDER (object);
233 parent_store = camel_folder_get_parent_store (CAMEL_FOLDER (imap_folder));
235 camel_store_summary_disconnect_folder_summary (
236 (CamelStoreSummary *) ((CamelImapStore *) parent_store)->summary,
237 CAMEL_FOLDER (imap_folder)->summary);
240 if (imap_folder->search != NULL) {
241 g_object_unref (imap_folder->search);
242 imap_folder->search = NULL;
245 if (imap_folder->cache != NULL) {
246 g_object_unref (imap_folder->cache);
247 imap_folder->cache = NULL;
250 if (imap_folder->priv->ignore_recent != NULL) {
251 g_hash_table_unref (imap_folder->priv->ignore_recent);
252 imap_folder->priv->ignore_recent = NULL;
255 if (imap_folder->journal != NULL) {
256 camel_offline_journal_write (imap_folder->journal, NULL);
257 g_object_unref (imap_folder->journal);
258 imap_folder->journal = NULL;
261 /* Chain up to parent's dispose() method. */
262 G_OBJECT_CLASS (camel_imap_folder_parent_class)->dispose (object);
266 imap_folder_finalize (GObject *object)
268 CamelImapFolder *imap_folder;
270 imap_folder = CAMEL_IMAP_FOLDER (object);
272 g_static_mutex_free (&imap_folder->priv->search_lock);
273 g_static_rec_mutex_free (&imap_folder->priv->cache_lock);
275 /* Chain up to parent's finalize() method. */
276 G_OBJECT_CLASS (camel_imap_folder_parent_class)->finalize (object);
280 imap_folder_constructed (GObject *object)
282 CamelNetworkSettings *network_settings;
283 CamelSettings *settings;
284 CamelService *service;
286 CamelStore *parent_store;
287 const gchar *full_name;
292 folder = CAMEL_FOLDER (object);
293 full_name = camel_folder_get_full_name (folder);
294 parent_store = camel_folder_get_parent_store (folder);
296 service = CAMEL_SERVICE (parent_store);
297 settings = camel_service_get_settings (service);
299 network_settings = CAMEL_NETWORK_SETTINGS (settings);
300 host = camel_network_settings_get_host (network_settings);
301 user = camel_network_settings_get_user (network_settings);
303 description = g_strdup_printf (
304 "%s@%s:%s", user, host, full_name);
305 camel_folder_set_description (folder, description);
306 g_free (description);
310 camel_imap_folder_class_init (CamelImapFolderClass *class)
312 GObjectClass *object_class;
313 CamelFolderClass *folder_class;
315 g_type_class_add_private (class, sizeof (CamelImapFolderPrivate));
317 object_class = G_OBJECT_CLASS (class);
318 object_class->set_property = imap_folder_set_property;
319 object_class->get_property = imap_folder_get_property;
320 object_class->dispose = imap_folder_dispose;
321 object_class->finalize = imap_folder_finalize;
322 object_class->constructed = imap_folder_constructed;
324 folder_class = CAMEL_FOLDER_CLASS (class);
325 folder_class->rename = imap_rename;
326 folder_class->search_by_expression = imap_search_by_expression;
327 folder_class->count_by_expression = imap_count_by_expression;
328 folder_class->search_by_uids = imap_search_by_uids;
329 folder_class->search_free = imap_search_free;
330 folder_class->thaw = imap_thaw;
331 folder_class->get_uncached_uids = imap_get_uncached_uids;
332 folder_class->get_filename = imap_get_filename;
333 folder_class->append_message_sync = imap_append_online;
334 folder_class->expunge_sync = imap_expunge_sync;
335 folder_class->get_message_sync = imap_get_message_sync;
336 folder_class->get_quota_info_sync = imap_get_quota_info_sync;
337 folder_class->refresh_info_sync = imap_refresh_info_sync;
338 folder_class->synchronize_sync = imap_synchronize_sync;
339 folder_class->synchronize_message_sync = imap_synchronize_message_sync;
340 folder_class->transfer_messages_to_sync = imap_transfer_online;
342 g_object_class_install_property (
345 g_param_spec_boolean (
348 _("Always check for _new mail in this folder"),
351 CAMEL_PARAM_PERSISTENT));
353 g_object_class_install_property (
356 g_param_spec_boolean (
359 _("Apply message _filters to this folder"),
362 CAMEL_PARAM_PERSISTENT));
366 camel_imap_folder_init (CamelImapFolder *imap_folder)
368 CamelFolder *folder = CAMEL_FOLDER (imap_folder);
370 imap_folder->priv = CAMEL_IMAP_FOLDER_GET_PRIVATE (imap_folder);
372 folder->permanent_flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_DELETED |
373 CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN;
375 folder->folder_flags |= (CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY |
376 CAMEL_FOLDER_HAS_SEARCH_CAPABILITY);
378 g_static_mutex_init (&imap_folder->priv->search_lock);
379 g_static_rec_mutex_init (&imap_folder->priv->cache_lock);
380 imap_folder->priv->ignore_recent = NULL;
382 imap_folder->journal = NULL;
383 imap_folder->need_rescan = TRUE;
387 replay_offline_journal (CamelImapStore *imap_store,
388 CamelImapFolder *imap_folder,
389 GCancellable *cancellable,
392 CamelIMAPJournal *imap_journal;
394 g_return_if_fail (imap_store != NULL);
395 g_return_if_fail (imap_folder != NULL);
396 g_return_if_fail (imap_folder->journal != NULL);
398 imap_journal = CAMEL_IMAP_JOURNAL (imap_folder->journal);
399 g_return_if_fail (imap_journal != NULL);
401 /* do not replay when still in offline */
402 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (imap_store)) || !camel_imap_store_connected (imap_store, error))
405 /* Check if the replay is already in progress as imap_sync would be called while expunge resync */
406 if (!imap_journal->rp_in_progress) {
407 imap_journal->rp_in_progress++;
409 camel_offline_journal_replay (
410 imap_folder->journal, cancellable, error);
411 camel_imap_journal_close_folders (imap_journal);
412 camel_offline_journal_write (imap_folder->journal, error);
414 imap_journal->rp_in_progress--;
415 g_return_if_fail (imap_journal->rp_in_progress >= 0);
420 camel_imap_folder_new (CamelStore *parent,
421 const gchar *folder_name,
422 const gchar *folder_dir,
426 CamelImapFolder *imap_folder;
427 const gchar *short_name;
428 gchar *state_file, *path;
429 CamelService *service;
430 CamelSettings *settings;
432 gboolean filter_inbox;
433 gboolean filter_junk;
434 gboolean filter_junk_inbox;
436 if (g_mkdir_with_parents (folder_dir, S_IRWXU) != 0) {
439 g_io_error_from_errno (errno),
440 _("Could not create directory %s: %s"),
441 folder_dir, g_strerror (errno));
445 short_name = strrchr (folder_name, '/');
449 short_name = folder_name;
450 folder = g_object_new (
451 CAMEL_TYPE_IMAP_FOLDER,
452 "full-name", folder_name,
453 "display-name", short_name,
454 "parent-store", parent, NULL);
456 folder->summary = camel_imap_summary_new (folder);
457 if (!folder->summary) {
458 g_object_unref (folder);
460 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
461 _("Could not load summary for %s"), folder_name);
465 imap_folder = CAMEL_IMAP_FOLDER (folder);
466 path = g_build_filename (folder_dir, "journal", NULL);
467 imap_folder->journal = camel_imap_journal_new (imap_folder, path);
470 /* set/load persistent state */
471 state_file = g_build_filename (folder_dir, "cmeta", NULL);
472 camel_object_set_state_filename (CAMEL_OBJECT (folder), state_file);
474 camel_object_state_read (CAMEL_OBJECT (folder));
476 imap_folder->cache = camel_imap_message_cache_new (folder_dir, folder->summary, error);
477 if (!imap_folder->cache) {
478 g_object_unref (folder);
482 service = CAMEL_SERVICE (parent);
483 settings = camel_service_get_settings (service);
487 "filter-all", &filter_all,
488 "filter-inbox", &filter_inbox,
489 "filter-junk", &filter_junk,
490 "filter-junk-inbox", &filter_junk_inbox,
493 if (g_ascii_strcasecmp (folder_name, "INBOX") == 0) {
494 if (filter_inbox || filter_all)
495 folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
497 folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
498 if (filter_junk_inbox)
499 folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
501 gboolean folder_is_trash, folder_is_junk;
505 junk_path = camel_imap_settings_dup_real_junk_path (
506 CAMEL_IMAP_SETTINGS (settings));
508 /* So we can safely compare strings. */
509 if (junk_path == NULL)
510 junk_path = g_strdup ("");
512 trash_path = camel_imap_settings_dup_real_trash_path (
513 CAMEL_IMAP_SETTINGS (settings));
515 /* So we can safely compare strings. */
516 if (trash_path == NULL)
517 trash_path = g_strdup ("");
519 if (filter_junk && !filter_junk_inbox)
520 folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
523 (parent->flags & CAMEL_STORE_VTRASH) == 0 &&
524 g_ascii_strcasecmp (trash_path, folder_name) == 0;
527 folder->folder_flags |= CAMEL_FOLDER_IS_TRASH;
530 (parent->flags & CAMEL_STORE_VJUNK) == 0 &&
531 g_ascii_strcasecmp (junk_path, folder_name) == 0;
534 folder->folder_flags |= CAMEL_FOLDER_IS_JUNK;
536 if (filter_all || imap_folder_get_apply_filters (imap_folder))
537 folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
543 imap_folder->search = camel_imap_search_new (folder_dir);
545 camel_store_summary_connect_folder_summary (
546 (CamelStoreSummary *) ((CamelImapStore *) parent)->summary,
547 folder_name, folder->summary);
553 camel_imap_folder_get_check_folder (CamelImapFolder *imap_folder)
555 g_return_val_if_fail (CAMEL_IS_IMAP_FOLDER (imap_folder), FALSE);
557 return imap_folder->priv->check_folder;
561 camel_imap_folder_set_check_folder (CamelImapFolder *imap_folder,
562 gboolean check_folder)
565 CamelStore *parent_store;
566 const gchar *full_name;
568 g_return_if_fail (CAMEL_IS_IMAP_FOLDER (imap_folder));
570 imap_folder->priv->check_folder = check_folder;
572 folder = CAMEL_FOLDER (imap_folder);
573 full_name = camel_folder_get_full_name (folder);
574 parent_store = camel_folder_get_parent_store (folder);
576 /* Update the summary so the value is restored
577 * correctly the next time the folder is loaded. */
578 if (CAMEL_IS_IMAP_STORE (parent_store)) {
579 CamelImapStore *imap_store;
580 CamelStoreSummary *summary;
583 imap_store = CAMEL_IMAP_STORE (parent_store);
584 summary = CAMEL_STORE_SUMMARY (imap_store->summary);
586 si = camel_store_summary_path (summary, full_name);
588 guint32 old_flags = si->flags;
590 si->flags &= ~CAMEL_STORE_INFO_FOLDER_CHECK_FOR_NEW;
591 si->flags |= check_folder ? CAMEL_STORE_INFO_FOLDER_CHECK_FOR_NEW : 0;
593 if (si->flags != old_flags) {
594 camel_store_summary_touch (summary);
595 camel_store_summary_save (summary);
598 camel_store_summary_info_free (summary, si);
602 g_object_notify (G_OBJECT (imap_folder), "check-folder");
605 /* Called with the store's connect_lock locked */
607 camel_imap_folder_selected (CamelFolder *folder,
608 CamelImapResponse *response,
609 GCancellable *cancellable,
612 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
613 CamelImapSummary *imap_summary = CAMEL_IMAP_SUMMARY (folder->summary);
614 gulong exists = 0, validity = 0, val, uid;
615 CamelMessageFlags perm_flags = 0;
620 count = camel_folder_summary_count (folder->summary);
622 for (i = 0; i < response->untagged->len; i++) {
623 resp = (gchar *) response->untagged->pdata[i] + 2;
625 if (!g_ascii_strncasecmp (resp, "FLAGS ", 6) && !perm_flags) {
627 imap_parse_flag_list (&resp, &folder->permanent_flags, NULL);
628 } else if (!g_ascii_strncasecmp (resp, "OK [PERMANENTFLAGS ", 19)) {
631 /* workaround for broken IMAP servers that send
632 * "* OK [PERMANENTFLAGS ()] Permanent flags"
633 * even tho they do allow storing flags. */
634 imap_parse_flag_list (&resp, &perm_flags, NULL);
636 folder->permanent_flags = perm_flags;
637 } else if (!g_ascii_strncasecmp (resp, "OK [UIDVALIDITY ", 16)) {
638 validity = strtoul (resp + 16, NULL, 10);
639 } else if (isdigit ((guchar) * resp)) {
640 gulong num = strtoul (resp, &resp, 10);
642 if (!g_ascii_strncasecmp (resp, " EXISTS", 7)) {
644 /* Remove from the response so nothing
645 * else tries to interpret it.
647 g_free (response->untagged->pdata[i]);
648 g_ptr_array_remove_index (response->untagged, i--);
653 if (camel_strstrcase (response->status, "OK [READ-ONLY]"))
654 imap_folder->read_only = TRUE;
656 if (!imap_summary->validity)
657 imap_summary->validity = validity;
658 else if (validity != imap_summary->validity) {
659 imap_summary->validity = validity;
660 camel_folder_summary_clear (folder->summary, NULL);
661 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
662 camel_imap_message_cache_clear (imap_folder->cache);
663 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
664 imap_folder->need_rescan = FALSE;
665 return camel_imap_folder_changed (
666 folder, exists, NULL, cancellable, error);
669 /* If we've lost messages, we have to rescan everything */
671 imap_folder->need_rescan = TRUE;
672 else if (count != 0 && !imap_folder->need_rescan) {
673 CamelStore *parent_store;
674 CamelImapStore *store;
675 GPtrArray *known_uids;
676 const gchar *old_uid;
678 parent_store = camel_folder_get_parent_store (folder);
679 store = CAMEL_IMAP_STORE (parent_store);
681 /* Similarly, if the UID of the highest message we
682 * know about has changed, then that indicates that
683 * messages have been both added and removed, so we
684 * have to rescan to find the removed ones. (We pass
685 * NULL for the folder since we know that this folder
686 * is selected, and we don't want camel_imap_command
687 * to worry about it.)
689 response = camel_imap_command (store, NULL, cancellable, error, "FETCH %d UID", count);
693 for (i = 0; i < response->untagged->len; i++) {
694 resp = response->untagged->pdata[i];
695 val = strtoul (resp + 2, &resp, 10);
698 if (!g_ascii_strcasecmp (resp, " EXISTS")) {
703 if (uid != 0 || val != count || g_ascii_strncasecmp (resp, " FETCH (", 8) != 0)
706 fetch_data = parse_fetch_response (imap_folder, resp + 7);
707 uid = strtoul (g_datalist_get_data (&fetch_data, "UID"), NULL, 10);
708 g_datalist_clear (&fetch_data);
710 camel_imap_response_free_without_processing (store, response);
712 known_uids = camel_folder_summary_get_array (folder->summary);
713 camel_folder_sort_uids (folder, known_uids);
715 if (known_uids && count - 1 >= 0 && count - 1 < known_uids->len)
716 old_uid = g_ptr_array_index (known_uids, count - 1);
718 val = strtoul (old_uid, NULL, 10);
719 if (uid == 0 || uid != val)
720 imap_folder->need_rescan = TRUE;
722 camel_folder_summary_free_array (known_uids);
725 /* Now rescan if we need to */
726 if (imap_folder->need_rescan)
727 return imap_rescan (folder, exists, cancellable, error);
729 /* If we don't need to rescan completely, but new messages
730 * have been added, find out about them.
733 camel_imap_folder_changed (
734 folder, exists, NULL, cancellable, error);
736 /* And we're done. */
742 imap_get_filename (CamelFolder *folder,
746 CamelImapFolder *imap_folder = (CamelImapFolder *) folder;
748 return camel_imap_message_cache_get_filename (imap_folder->cache, uid, "", error);
752 imap_rename (CamelFolder *folder,
755 CamelService *service;
756 CamelStore *parent_store;
757 CamelImapFolder *imap_folder = (CamelImapFolder *) folder;
758 const gchar *user_cache_dir;
759 gchar *folder_dir, *state_file;
762 parent_store = camel_folder_get_parent_store (folder);
764 service = CAMEL_SERVICE (parent_store);
765 user_cache_dir = camel_service_get_user_cache_dir (service);
767 folders = g_build_filename (user_cache_dir, "folders", NULL);
768 folder_dir = imap_path_to_physical (folders, new);
771 CAMEL_IMAP_FOLDER_REC_LOCK (folder, cache_lock);
772 camel_imap_message_cache_set_path (imap_folder->cache, folder_dir);
773 CAMEL_IMAP_FOLDER_REC_UNLOCK (folder, cache_lock);
775 state_file = g_build_filename (folder_dir, "cmeta", NULL);
776 camel_object_set_state_filename (CAMEL_OBJECT (folder), state_file);
781 camel_store_summary_disconnect_folder_summary (
782 (CamelStoreSummary *) ((CamelImapStore *) parent_store)->summary,
785 CAMEL_FOLDER_CLASS (camel_imap_folder_parent_class)->rename (folder, new);
787 camel_store_summary_connect_folder_summary (
788 (CamelStoreSummary *) ((CamelImapStore *) parent_store)->summary,
789 camel_folder_get_full_name (folder), folder->summary);
792 /* called with connect_lock locked */
794 get_folder_status (CamelFolder *folder,
797 GCancellable *cancellable,
800 CamelStore *parent_store;
801 CamelImapStore *imap_store;
802 CamelImapResponse *response;
803 const gchar *full_name;
804 gboolean res = FALSE;
806 g_return_val_if_fail (folder != NULL, FALSE);
808 full_name = camel_folder_get_full_name (folder);
809 parent_store = camel_folder_get_parent_store (folder);
811 imap_store = CAMEL_IMAP_STORE (parent_store);
813 response = camel_imap_command (imap_store, folder, cancellable, error, "STATUS %F (MESSAGES UNSEEN)", full_name);
818 for (i = 0; i < response->untagged->len; i++) {
819 const gchar *resp = response->untagged->pdata[i];
821 if (resp && g_str_has_prefix (resp, "* STATUS ")) {
822 const gchar *p = NULL;
830 if (p && *(resp - 1) == ')') {
831 const gchar *msgs = NULL, *unseen = NULL;
835 while (p && (!msgs || !unseen)) {
836 const gchar **dest = NULL;
838 if (g_str_has_prefix (p, "MESSAGES "))
840 else if (g_str_has_prefix (p, "UNSEEN "))
844 *dest = imap_next_word (p);
849 p = imap_next_word (*dest);
851 p = imap_next_word (p);
853 p = imap_next_word (p);
857 if (msgs && unseen) {
861 *total = strtoul (msgs, NULL, 10);
864 *unread = strtoul (unseen, NULL, 10);
869 camel_imap_response_free (imap_store, response);
876 imap_refresh_info_sync (CamelFolder *folder,
877 GCancellable *cancellable,
880 CamelStore *parent_store;
881 CamelImapStore *imap_store;
882 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
883 CamelImapResponse *response;
885 const gchar *full_name;
886 gint check_rescan = -1;
887 GError *local_error = NULL;
889 parent_store = camel_folder_get_parent_store (folder);
890 imap_store = CAMEL_IMAP_STORE (parent_store);
892 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (imap_store)))
895 if (camel_folder_is_frozen (folder)) {
896 imap_folder->need_refresh = TRUE;
900 /* If the folder isn't selected, select it (which will force
901 * a rescan if one is needed).
902 * Also, if this is the INBOX, some servers (cryus) wont tell
903 * us with a NOOP of new messages, so force a reselect which
905 camel_service_lock (CAMEL_SERVICE (imap_store), CAMEL_SERVICE_REC_CONNECT_LOCK);
907 if (camel_application_is_exiting || !camel_imap_store_connected (imap_store, &local_error))
910 /* try to store local changes first, as the summary contains new local messages */
911 replay_offline_journal (
912 imap_store, imap_folder, cancellable, &local_error);
914 full_name = camel_folder_get_full_name (folder);
916 if (imap_store->current_folder != folder
917 || g_ascii_strcasecmp (full_name, "INBOX") == 0) {
918 response = camel_imap_command (imap_store, folder, cancellable, &local_error, NULL);
920 camel_imap_folder_selected (
922 cancellable, &local_error);
923 camel_imap_response_free (imap_store, response);
925 } else if (imap_folder->need_rescan) {
926 /* Otherwise, if we need a rescan, do it, and if not, just do
927 * a NOOP to give the server a chance to tell us about new
931 folder, camel_folder_summary_count (
932 folder->summary), cancellable, &local_error);
936 /* on some servers need to CHECKpoint INBOX to recieve new messages?? */
937 /* rfc2060 suggests this, but havent seen a server that requires it */
938 if (g_ascii_strcasecmp (full_name, "INBOX") == 0) {
939 response = camel_imap_command (imap_store, folder, &local_error, "CHECK");
940 camel_imap_response_free (imap_store, response);
943 response = camel_imap_command (imap_store, folder, cancellable, &local_error, "NOOP");
944 camel_imap_response_free (imap_store, response);
947 si = camel_store_summary_path ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary, full_name);
949 guint32 unread, total;
951 total = camel_folder_summary_count (folder->summary);
952 unread = camel_folder_summary_get_unread_count (folder->summary);
954 if (si->total != total
955 || si->unread != unread) {
958 camel_store_summary_touch ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary);
961 camel_store_summary_info_free ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary, si);
964 if (check_rescan && !camel_application_is_exiting && local_error == NULL) {
965 if (check_rescan == -1) {
966 guint32 total, unread = 0, server_total = 0, server_unread = 0;
970 /* Check whether there are changes in total/unread messages in the folders
971 * and if so, then rescan whole summary */
972 if (get_folder_status (folder, &server_total, &server_unread, cancellable, &local_error)) {
974 total = camel_folder_summary_count (folder->summary);
975 unread = camel_folder_summary_get_unread_count (folder->summary);
977 if (total != server_total || unread != server_unread)
984 folder, camel_folder_summary_count (
985 folder->summary), cancellable, &local_error);
988 camel_service_unlock (CAMEL_SERVICE (imap_store), CAMEL_SERVICE_REC_CONNECT_LOCK);
990 camel_folder_summary_save_to_db (folder->summary, NULL);
991 camel_store_summary_save ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary);
993 if (local_error != NULL) {
994 g_propagate_error (error, local_error);
1002 fillup_custom_flags (CamelMessageInfo *mi,
1003 gchar *custom_flags)
1008 array_str = g_strsplit (custom_flags, " ", -1);
1010 while (array_str[index] != NULL) {
1011 camel_flag_set (&((CamelMessageInfoBase *) mi)->user_flags, array_str[index], TRUE);
1015 g_strfreev (array_str);
1018 /* This will merge custom flags with those in message info. Returns whether was some change. */
1020 merge_custom_flags (CamelMessageInfo *mi,
1021 const gchar *custom_flags)
1027 const CamelFlag *flag;
1028 gboolean changed = FALSE;
1030 g_return_val_if_fail (mi != NULL, FALSE);
1036 server = g_hash_table_new (g_str_hash, g_str_equal);
1038 cflags = g_strsplit (custom_flags, " ", -1);
1039 for (i = 0; cflags[i]; i++) {
1040 gchar *name = cflags[i];
1042 if (name && *name) {
1043 g_hash_table_insert (server, name, name);
1044 list = g_list_prepend (list, name);
1048 for (flag = camel_message_info_user_flags (mi); flag; flag = flag->next) {
1049 gchar *name = (gchar *) flag->name;
1052 list = g_list_prepend (list, name);
1055 list = g_list_sort (list, (GCompareFunc) strcmp);
1056 for (p = list; p; p = p->next) {
1057 if (p->next && strcmp (p->data, p->next->data) == 0) {
1058 /* This flag is there twice, which means it was on the server and
1059 * in our local summary too; thus skip these two elements. */
1062 /* If this value came from the server, then add it to our local summary,
1063 * otherwise it was in local summary, but isn't on the server, thus remove it. */
1067 camel_folder_summary_touch (mi->summary);
1068 camel_flag_set (&((CamelMessageInfoBase *) mi)->user_flags, p->data, g_hash_table_lookup (server, p->data) != NULL);
1069 ((CamelMessageInfoBase *) mi)->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
1074 g_hash_table_destroy (server);
1075 g_strfreev (cflags);
1080 /* Called with the store's connect_lock locked */
1082 imap_rescan (CamelFolder *folder,
1084 GCancellable *cancellable,
1087 CamelStore *parent_store;
1088 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
1089 CamelImapStore *store;
1093 gchar *custom_flags;
1096 CamelImapResponseType type;
1097 gint i, j, seq, summary_got, del = 0;
1099 CamelMessageInfo *info;
1100 CamelImapMessageInfo *iinfo;
1103 CamelFolderChangeInfo *changes = NULL;
1105 GPtrArray *known_uids;
1107 parent_store = camel_folder_get_parent_store (folder);
1108 store = CAMEL_IMAP_STORE (parent_store);
1110 if (camel_application_is_exiting)
1113 imap_folder->need_rescan = FALSE;
1115 known_uids = camel_folder_summary_get_array (folder->summary);
1116 summary_len = known_uids ? known_uids->len : 0;
1117 if (summary_len == 0) {
1118 camel_folder_summary_free_array (known_uids);
1120 return camel_imap_folder_changed (
1121 folder, exists, NULL, cancellable, error);
1125 /* Check UIDs and flags of all messages we already know of. */
1126 camel_operation_push_message (
1127 cancellable, _("Scanning for changed messages in %s"),
1128 camel_folder_get_display_name (folder));
1130 camel_folder_sort_uids (folder, known_uids);
1132 uid = g_ptr_array_index (known_uids, summary_len - 1);
1134 camel_operation_pop_message (cancellable);
1135 camel_folder_summary_free_array (known_uids);
1139 ok = camel_imap_command_start (
1140 store, folder, cancellable, error,
1141 "UID FETCH 1:%s (FLAGS)", uid);
1143 camel_operation_pop_message (cancellable);
1144 camel_folder_summary_free_array (known_uids);
1149 new = g_malloc0 (summary_len * sizeof (*new));
1151 while ((type = camel_imap_command_response (store, folder, &resp, cancellable, error)) == CAMEL_IMAP_RESPONSE_UNTAGGED && !camel_application_is_exiting) {
1156 data = parse_fetch_response (imap_folder, resp);
1162 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
1163 uid = g_datalist_get_data (&data, "UID");
1164 flags = GPOINTER_TO_UINT (g_datalist_get_data (&data, "FLAGS"));
1166 if (!uid || !seq || seq > summary_len || seq < 0) {
1167 g_datalist_clear (&data);
1171 camel_operation_progress (
1172 cancellable, ++summary_got * 100 / summary_len);
1174 new[seq - 1].uid = g_strdup (uid);
1175 new[seq - 1].flags = flags;
1176 new[seq - 1].custom_flags = g_strdup (g_datalist_get_data (&data, "CUSTOM.FLAGS"));
1177 g_datalist_clear (&data);
1180 if (summary_got == 0 && summary_len == 0) {
1181 camel_operation_pop_message (cancellable);
1182 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
1186 camel_folder_summary_free_array (known_uids);
1190 camel_operation_pop_message (cancellable);
1192 if (type == CAMEL_IMAP_RESPONSE_ERROR || camel_application_is_exiting) {
1193 for (i = 0; i < summary_len && new[i].uid; i++) {
1194 g_free (new[i].uid);
1195 g_free (new[i].custom_flags);
1200 if (type != CAMEL_IMAP_RESPONSE_ERROR && type != CAMEL_IMAP_RESPONSE_TAGGED)
1201 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
1203 camel_folder_summary_free_array (known_uids);
1207 /* Free the final tagged response */
1210 /* If we find a UID in the summary that doesn't correspond to
1211 * the UID in the folder, then either: (a) it's a real UID,
1212 * but the message was deleted on the server, or (b) it's a
1213 * fake UID, and needs to be removed from the summary in order
1214 * to sync up with the server. So either way, we remove it
1217 removed = g_array_new (FALSE, FALSE, sizeof (gint));
1219 camel_folder_summary_prepare_fetch_all (folder->summary, NULL);
1221 for (i = 0, j = 0; i < summary_len && new[j].uid; i++) {
1222 gboolean changed = FALSE;
1224 uid = g_ptr_array_index (known_uids, i);
1228 info = camel_folder_summary_get (folder->summary, uid);
1230 if (g_getenv("CRASH_IMAP")) { /* Debug logs to tackle on hard to get imap crasher */
1231 printf ("CRASH: %s: %s",
1232 camel_folder_get_full_name (folder), uid);
1238 iinfo = (CamelImapMessageInfo *) info;
1240 if (strcmp (uid, new[j].uid) != 0) {
1243 g_array_append_val (removed, seq);
1244 camel_message_info_free (info);
1248 /* Update summary flags */
1250 if (new[j].flags != iinfo->server_flags) {
1251 guint32 server_set, server_cleared;
1253 server_set = new[j].flags & ~iinfo->server_flags;
1254 server_cleared = iinfo->server_flags & ~new[j].flags;
1256 camel_message_info_set_flags ((CamelMessageInfo *) iinfo, server_set | server_cleared, (iinfo->info.flags | server_set) & ~server_cleared);
1257 iinfo->server_flags = new[j].flags;
1258 /* unset folder_flagged, because these are flags received froma server */
1259 iinfo->info.flags = iinfo->info.flags & (~CAMEL_MESSAGE_FOLDER_FLAGGED);
1260 iinfo->info.dirty = TRUE;
1262 camel_folder_summary_touch (info->summary);
1266 /* Do not merge custom flags when server doesn't support it.
1267 * Because server always reports NULL, which means none, which
1268 * will remove user's flags from local machine, which is bad.
1270 if ((folder->permanent_flags & CAMEL_MESSAGE_USER) != 0 &&
1271 (iinfo->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED) == 0 &&
1272 merge_custom_flags (info, new[j].custom_flags))
1276 if (changes == NULL)
1277 changes = camel_folder_change_info_new ();
1278 camel_folder_change_info_change_uid (changes, new[j].uid);
1281 camel_message_info_free (info);
1282 g_free (new[j].uid);
1283 g_free (new[j].custom_flags);
1288 camel_folder_changed (folder, changes);
1289 camel_folder_change_info_free (changes);
1295 /* FIXME: Srini: I don't think this will be called any longer. */
1296 /* Free remaining memory. */
1297 while (i < summary_len && new[i].uid) {
1298 g_free (new[i].uid);
1299 g_free (new[i].custom_flags);
1305 /* Remove any leftover cached summary messages. (Yes, we
1306 * repeatedly add the same number to the removed array.
1307 * See RFC2060 7.4.1)
1310 for (i = seq; i <= summary_len; i++) {
1314 g_array_append_val (removed, j);
1317 /* And finally update the summary. */
1318 success = camel_imap_folder_changed (
1319 folder, exists, removed, cancellable, error);
1320 g_array_free (removed, TRUE);
1322 camel_folder_summary_free_array (known_uids);
1326 static const gchar *
1327 get_message_uid (CamelFolder *folder,
1328 CamelImapMessageInfo *info)
1332 g_return_val_if_fail (folder != NULL, NULL);
1333 g_return_val_if_fail (info != NULL, NULL);
1335 uid = camel_message_info_uid (info);
1336 g_return_val_if_fail (uid != NULL, NULL);
1338 if (!isdigit ((guchar) * uid)) {
1339 uid = camel_imap_journal_uidmap_lookup ((CamelIMAPJournal *) CAMEL_IMAP_FOLDER (folder)->journal, uid);
1340 g_return_val_if_fail (uid != NULL, NULL);
1346 /* the max number of chars that an unsigned 32-bit gint can be is 10 chars plus 1 for a possible : */
1347 #define UID_SET_FULL(setlen, maxlen) (maxlen > 0 ? setlen + 11 >= maxlen : FALSE)
1349 /* Find all messages in @folder with flags matching @flags and @mask.
1350 * If no messages match, returns %NULL. Otherwise, returns an array of
1351 * CamelMessageInfo and sets *@set to a message set corresponding the
1352 * UIDs of the matched messages (up to @UID_SET_LIMIT bytes). The
1353 * caller must free the infos, the array, and the set string.
1356 get_matching (CamelFolder *folder,
1359 CamelMessageInfo *master_info,
1362 GPtrArray *deleted_uids,
1363 GPtrArray *junked_uids)
1366 CamelImapMessageInfo *info;
1367 gint i, max, range, last_range_uid;
1369 GList *list1 = NULL;
1373 /* use the local rinfo in the close_range, because we want to keep our info untouched */
1374 #define close_range() \
1375 if (range != -1) { \
1376 if (range != i - 1) { \
1377 CamelImapMessageInfo *rinfo = matches->pdata[matches->len - 1]; \
1379 g_string_append_printf (gset, ":%s", get_message_uid (folder, rinfo)); \
1382 last_range_uid = -1; \
1385 matches = g_ptr_array_new ();
1386 gset = g_string_new ("");
1389 last_range_uid = -1;
1390 for (i = 0; i < max && !UID_SET_FULL (gset->len, UID_SET_LIMIT); i++) {
1392 uid = summary->pdata[i];
1395 info = (CamelImapMessageInfo *) camel_folder_summary_get (folder->summary, uid);
1402 /* if the resulting flag list is empty, then "concat" other message
1403 * only when server_flags are same, because there will be a flag removal
1404 * command for this type of situation */
1405 if ((info->info.flags & mask) != flags || (flags == 0 && info->server_flags != ((CamelImapMessageInfo *) master_info)->server_flags)) {
1406 camel_message_info_free ((CamelMessageInfo *) info);
1411 uid_num = atoi (uid);
1413 /* we got only changes, thus the uid's can be mixed up, not the consecutive list,
1414 * thus close range if we are not in it */
1415 if (last_range_uid != -1 && uid_num != last_range_uid + 1) {
1419 /* only check user flags when we see other message than our 'master' */
1420 if (strcmp (master_info->uid, ((CamelMessageInfo *) info)->uid)) {
1421 const CamelFlag *flag;
1422 GList *list2 = NULL, *l1, *l2;
1423 gint count2 = 0, cmp = 0;
1426 for (flag = camel_message_info_user_flags (master_info); flag; flag = flag->next) {
1429 list1 = g_list_prepend (list1, (gchar *) flag->name);
1433 list1 = g_list_sort (list1, (GCompareFunc) strcmp);
1436 for (flag = camel_message_info_user_flags (info); flag; flag = flag->next) {
1439 list2 = g_list_prepend (list2, (gchar *) flag->name);
1443 if (count1 != count2) {
1444 g_list_free (list2);
1449 list2 = g_list_sort (list2, (GCompareFunc) strcmp);
1450 for (l1 = list1, l2 = list2; l1 && l2 && !cmp; l1 = l1->next, l2 = l2->next) {
1451 cmp = strcmp (l1->data, l2->data);
1455 g_list_free (list2);
1461 if (deleted_uids && (info->info.flags & (CAMEL_MESSAGE_DELETED | CAMEL_IMAP_MESSAGE_MOVED)) == CAMEL_MESSAGE_DELETED) {
1462 g_ptr_array_add (deleted_uids, (gpointer) camel_pstring_strdup (camel_message_info_uid (info)));
1463 info->info.flags &= ~CAMEL_MESSAGE_DELETED;
1464 } else if (junked_uids && (info->info.flags & CAMEL_MESSAGE_JUNK) != 0) {
1465 g_ptr_array_add (junked_uids, (gpointer) camel_pstring_strdup (camel_message_info_uid (info)));
1468 g_ptr_array_add (matches, info);
1469 /* Remove the uid from the list, to optimize*/
1470 camel_pstring_free (summary->pdata[i]);
1471 summary->pdata[i] = NULL;
1474 last_range_uid = uid_num;
1479 last_range_uid = uid_num;
1481 g_string_append_c (gset, ',');
1482 g_string_append_printf (gset, "%s", get_message_uid (folder, info));
1485 if (range != -1 && range != max - 1) {
1486 info = matches->pdata[matches->len - 1];
1487 g_string_append_printf (gset, ":%s", get_message_uid (folder, info));
1491 g_list_free (list1);
1495 g_string_free (gset, FALSE);
1499 g_string_free (gset, TRUE);
1500 g_ptr_array_free (matches, TRUE);
1508 imap_sync_offline (CamelFolder *folder,
1511 CamelStore *parent_store;
1513 parent_store = camel_folder_get_parent_store (folder);
1515 if (folder->summary && (folder->summary->flags & CAMEL_SUMMARY_DIRTY) != 0) {
1517 const gchar *full_name;
1519 /* ... and store's summary when folder's summary is dirty */
1520 full_name = camel_folder_get_full_name (folder);
1521 si = camel_store_summary_path ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary, full_name);
1523 if (si->total != camel_folder_summary_get_saved_count (folder->summary) || si->unread != camel_folder_summary_get_unread_count (folder->summary)) {
1524 si->total = camel_folder_summary_get_saved_count (folder->summary);
1525 si->unread = camel_folder_summary_get_unread_count (folder->summary);
1526 camel_store_summary_touch ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary);
1529 camel_store_summary_info_free ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary, si);
1533 camel_folder_summary_save_to_db (folder->summary, NULL);
1534 camel_store_summary_save ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary);
1540 host_ends_with (const gchar *host,
1543 gint host_len, ends_len;
1545 g_return_val_if_fail (host != NULL, FALSE);
1546 g_return_val_if_fail (ends != NULL, FALSE);
1548 host_len = strlen (host);
1549 ends_len = strlen (ends);
1551 return ends_len <= host_len && g_ascii_strcasecmp (host + host_len - ends_len, ends) == 0;
1555 is_google_account (CamelStore *store)
1557 CamelNetworkSettings *network_settings;
1558 CamelSettings *settings;
1559 CamelService *service;
1563 g_return_val_if_fail (store != NULL, FALSE);
1564 g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
1566 service = CAMEL_SERVICE (store);
1567 settings = camel_service_get_settings (service);
1569 network_settings = CAMEL_NETWORK_SETTINGS (settings);
1570 host = camel_network_settings_dup_host (network_settings);
1574 host_ends_with (host, "gmail.com") ||
1575 host_ends_with (host, "googlemail.com"));
1583 move_messages (CamelFolder *src_folder,
1585 CamelFolder *des_folder,
1586 GCancellable *cancellable,
1589 g_return_if_fail (src_folder != NULL);
1591 /* it's OK to have these NULL */
1592 if (!uids || uids->len == 0 || des_folder == NULL)
1595 /* moving to the same folder means expunge only */
1596 if (src_folder != des_folder) {
1597 /* do 'copy' to not be bothered with CAMEL_MESSAGE_DELETED again */
1598 if (!imap_transfer_messages (
1599 src_folder, uids, des_folder, FALSE,
1600 NULL, FALSE, cancellable, error))
1604 camel_imap_expunge_uids_only (src_folder, uids, cancellable, error);
1608 imap_synchronize_sync (CamelFolder *folder,
1610 GCancellable *cancellable,
1613 CamelService *service;
1614 CamelSettings *settings;
1615 CamelStore *parent_store;
1616 CamelImapStore *store;
1617 CamelImapMessageInfo *info;
1618 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
1619 gboolean success, is_gmail;
1620 CamelFolder *real_junk = NULL;
1621 CamelFolder *real_trash = NULL;
1623 GError *local_error = NULL;
1625 GPtrArray *matches, *summary, *deleted_uids = NULL, *junked_uids = NULL;
1626 gchar *set, *flaglist, *uid;
1629 parent_store = camel_folder_get_parent_store (folder);
1630 store = CAMEL_IMAP_STORE (parent_store);
1631 is_gmail = is_google_account (parent_store);
1633 service = CAMEL_SERVICE (parent_store);
1634 settings = camel_service_get_settings (service);
1636 if (folder->permanent_flags == 0 || !camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store))) {
1638 if (!imap_expunge_sync (folder, cancellable, error))
1641 return imap_sync_offline (folder, error);
1644 camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
1646 /* write local changes first */
1647 replay_offline_journal (store, imap_folder, cancellable, NULL);
1649 /* Find a message with changed flags, find all of the other
1650 * messages like it, sync them as a group, mark them as
1651 * updated, and continue.
1653 summary = camel_folder_summary_get_changed (folder->summary); /* These should be in memory anyways */
1654 camel_folder_sort_uids (folder, summary);
1657 /* deleted_uids is NULL when not using real trash */
1658 folder_path = camel_imap_settings_dup_real_trash_path (
1659 CAMEL_IMAP_SETTINGS (settings));
1660 if (folder_path != NULL) {
1661 if ((folder->folder_flags & CAMEL_FOLDER_IS_TRASH) != 0) {
1662 /* syncing the trash, expunge deleted when found any */
1663 real_trash = g_object_ref (folder);
1665 real_trash = camel_store_get_trash_folder_sync (
1666 parent_store, cancellable, NULL);
1668 if (folder_path == NULL && real_trash) {
1669 /* failed to open real trash */
1670 g_object_unref (real_trash);
1675 g_free (folder_path);
1678 deleted_uids = g_ptr_array_new ();
1680 /* junked_uids is NULL when not using real junk */
1681 folder_path = camel_imap_settings_dup_real_junk_path (
1682 CAMEL_IMAP_SETTINGS (settings));
1683 if (folder_path != NULL) {
1684 if ((folder->folder_flags & CAMEL_FOLDER_IS_JUNK) != 0) {
1685 /* syncing the junk, but cannot move
1686 * messages to itself, thus do nothing */
1689 real_junk = camel_store_get_junk_folder_sync (
1690 parent_store, cancellable, NULL);
1692 if (folder_path == NULL && real_junk) {
1693 /* failed to open real junk */
1694 g_object_unref (real_junk);
1699 g_free (folder_path);
1702 junked_uids = g_ptr_array_new ();
1704 for (i = 0; i < max; i++) {
1705 gboolean unset = FALSE;
1706 CamelImapResponse *response = NULL;
1708 uid = summary->pdata[i];
1710 if (!uid) /* Possibly it was sync by matching flags, which we NULLify */
1713 if (!(info = (CamelImapMessageInfo *) camel_folder_summary_get (folder->summary, uid))) {
1717 if (!(info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) {
1718 camel_message_info_free ((CamelMessageInfo *) info);
1722 /* Note: get_matching() uses UID_SET_LIMIT to limit
1723 * the size of the uid-set string. We don't have to
1724 * loop here to flush all the matching uids because
1725 * they will be scooped up later by our parent loop (I
1726 * think?). -- Jeff */
1727 matches = get_matching (folder, info->info.flags & (folder->permanent_flags | CAMEL_MESSAGE_FOLDER_FLAGGED),
1728 folder->permanent_flags | CAMEL_MESSAGE_FOLDER_FLAGGED, (CamelMessageInfo *) info, &set, summary,
1729 deleted_uids, junked_uids);
1730 if (matches == NULL) {
1731 camel_message_info_free (info);
1735 /* Make sure we're connected before issuing commands */
1736 if (!camel_imap_store_connected (store, NULL)) {
1738 camel_message_info_free (info);
1742 if (deleted_uids && !is_gmail && (info->info.flags & CAMEL_MESSAGE_DELETED) != 0) {
1743 /* there is a real trash, do not set it on the server */
1744 info->info.flags &= ~CAMEL_MESSAGE_DELETED;
1747 flaglist = imap_create_flag_list (info->info.flags & folder->permanent_flags, (CamelMessageInfo *) info, folder->permanent_flags);
1749 if (strcmp (flaglist, "()") == 0) {
1750 /* Note: Cyrus is broken and will not accept an
1751 * empty-set of flags so... if this is true then we
1752 * set and unset \Seen flag. It's necessary because
1753 * we do not know the previously set user flags. */
1757 /* unset all known server flags, because there left none in the actual flags */
1758 flaglist = imap_create_flag_list (info->server_flags & folder->permanent_flags, (CamelMessageInfo *) info, folder->permanent_flags);
1760 if (strcmp (flaglist, "()") == 0) {
1761 /* this should not happen, really */
1763 flaglist = strdup ("(\\Seen)");
1765 response = camel_imap_command (store, folder, cancellable, &local_error,
1766 "UID STORE %s +FLAGS.SILENT %s",
1769 camel_imap_response_free (store, response);
1775 /* We don't use the info any more */
1776 camel_message_info_free (info);
1778 /* Note: to 'unset' flags, use -FLAGS.SILENT (<flag list>) */
1779 if (local_error == NULL) {
1780 response = camel_imap_command (store, folder, cancellable, &local_error,
1781 "UID STORE %s %sFLAGS.SILENT %s",
1782 set, unset ? "-" : "", flaglist);
1789 camel_imap_response_free (store, response);
1791 if (local_error == NULL) {
1792 for (j = 0; j < matches->len; j++) {
1793 info = matches->pdata[j];
1794 if (deleted_uids && !is_gmail) {
1795 /* there is a real trash, do not keep this set */
1796 info->info.flags &= ~CAMEL_MESSAGE_DELETED;
1799 info->info.flags &= ~(CAMEL_MESSAGE_FOLDER_FLAGGED | CAMEL_IMAP_MESSAGE_MOVED);
1800 ((CamelImapMessageInfo *) info)->server_flags = info->info.flags & CAMEL_IMAP_SERVER_FLAGS;
1801 info->info.dirty = TRUE; /* Sync it back to the DB */
1802 if (((CamelMessageInfo *) info)->summary)
1803 camel_folder_summary_touch (((CamelMessageInfo *) info)->summary);
1805 camel_folder_summary_touch (folder->summary);
1808 for (j = 0; j < matches->len; j++) {
1809 info = matches->pdata[j];
1810 camel_message_info_free (&info->info);
1812 g_ptr_array_free (matches, TRUE);
1814 /* We unlock here so that other threads can have a chance to grab the connect_lock */
1815 camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
1817 /* check for an exception */
1818 if (local_error != NULL) {
1819 g_propagate_error (error, local_error);
1821 g_ptr_array_foreach (deleted_uids, (GFunc) camel_pstring_free, NULL);
1822 g_ptr_array_free (deleted_uids, TRUE);
1825 g_ptr_array_foreach (junked_uids, (GFunc) camel_pstring_free, NULL);
1826 g_ptr_array_free (junked_uids, TRUE);
1829 g_object_unref (real_trash);
1831 g_object_unref (real_junk);
1835 /* Re-lock the connect_lock */
1836 camel_service_lock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
1839 if (local_error == NULL)
1841 folder, deleted_uids, real_trash,
1842 cancellable, &local_error);
1843 if (local_error == NULL)
1845 folder, junked_uids, real_junk,
1846 cancellable, &local_error);
1849 g_ptr_array_foreach (deleted_uids, (GFunc) camel_pstring_free, NULL);
1850 g_ptr_array_free (deleted_uids, TRUE);
1853 g_ptr_array_foreach (junked_uids, (GFunc) camel_pstring_free, NULL);
1854 g_ptr_array_free (junked_uids, TRUE);
1857 g_object_unref (real_trash);
1859 g_object_unref (real_junk);
1861 if (expunge && local_error == NULL)
1862 imap_expunge_sync (folder, cancellable, &local_error);
1864 if (local_error != NULL)
1865 g_propagate_error (error, local_error);
1867 g_ptr_array_foreach (summary, (GFunc) camel_pstring_free, NULL);
1868 g_ptr_array_free (summary, TRUE);
1870 /* Save the summary */
1871 success = imap_sync_offline (folder, error);
1873 camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
1879 uid_compar (gconstpointer va,
1882 const gchar **sa = (const gchar **) va, **sb = (const gchar **) vb;
1885 a = strtoul (*sa, NULL, 10);
1886 b = strtoul (*sb, NULL, 10);
1896 imap_expunge_uids_offline (CamelFolder *folder,
1898 GCancellable *cancellable,
1901 CamelFolderChangeInfo *changes;
1902 CamelStore *parent_store;
1904 const gchar *full_name;
1907 full_name = camel_folder_get_full_name (folder);
1908 parent_store = camel_folder_get_parent_store (folder);
1910 qsort (uids->pdata, uids->len, sizeof (gpointer), uid_compar);
1912 changes = camel_folder_change_info_new ();
1914 for (i = 0; i < uids->len; i++) {
1915 camel_folder_summary_remove_uid (folder->summary, uids->pdata[i]);
1916 camel_folder_change_info_remove_uid (changes, uids->pdata[i]);
1917 list = g_list_prepend (list, (gpointer) uids->pdata[i]);
1918 /* We intentionally don't remove it from the cache because
1919 * the cached data may be useful in replaying a COPY later.
1923 camel_db_delete_uids (parent_store->cdb_w, full_name, list, NULL);
1925 camel_folder_summary_save_to_db (folder->summary, NULL);
1927 camel_imap_journal_log (CAMEL_IMAP_FOLDER (folder)->journal,
1928 CAMEL_IMAP_JOURNAL_ENTRY_EXPUNGE, uids);
1930 camel_folder_changed (folder, changes);
1931 camel_folder_change_info_free (changes);
1937 imap_expunge_uids_online (CamelFolder *folder,
1939 GCancellable *cancellable,
1942 CamelImapStore *store;
1943 CamelImapResponse *response;
1946 gboolean full_expunge;
1947 CamelFolderChangeInfo *changes;
1948 CamelStore *parent_store;
1949 const gchar *full_name;
1953 full_name = camel_folder_get_full_name (folder);
1954 parent_store = camel_folder_get_parent_store (folder);
1956 store = CAMEL_IMAP_STORE (parent_store);
1957 full_expunge = (store->capabilities & IMAP_CAPABILITY_UIDPLUS) == 0;
1959 camel_service_lock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
1961 if ((store->capabilities & IMAP_CAPABILITY_UIDPLUS) == 0) {
1962 if (!CAMEL_FOLDER_GET_CLASS (folder)->synchronize_sync (
1963 folder, 0, cancellable, error)) {
1964 camel_service_unlock (
1965 CAMEL_SERVICE (store),
1966 CAMEL_SERVICE_REC_CONNECT_LOCK);
1971 qsort (uids->pdata, uids->len, sizeof (gpointer), uid_compar);
1973 while (uid < uids->len) {
1974 set = imap_uid_array_to_set (folder->summary, uids, uid, UID_SET_LIMIT, &uid);
1975 response = camel_imap_command (store, folder, cancellable, error,
1976 "UID STORE %s +FLAGS.SILENT (\\Deleted)",
1979 camel_imap_response_free (store, response);
1981 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
1986 if (!full_expunge) {
1987 GError *local_error = NULL;
1989 response = camel_imap_command (
1990 store, folder, cancellable, &local_error,
1991 "UID EXPUNGE %s", set);
1993 if (local_error != NULL) {
1994 g_clear_error (&local_error);
1996 /* UID EXPUNGE failed, something is broken on the server probably,
1997 * thus fall back to the full expunge. It's not so good, especially
1998 * when resyncing, it will remove already marked messages on the
1999 * server too. I guess that's fine anyway, isn't it?
2000 * For failed command see Gnome's bug #536486 */
2001 full_expunge = TRUE;
2006 response = camel_imap_command (store, folder, cancellable, NULL, "EXPUNGE");
2009 camel_imap_response_free (store, response);
2014 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2016 changes = camel_folder_change_info_new ();
2017 for (i = 0; i < uids->len; i++) {
2018 camel_folder_summary_remove_uid (folder->summary, uids->pdata[i]);
2019 camel_folder_change_info_remove_uid (changes, uids->pdata[i]);
2020 list = g_list_prepend (list, (gpointer) uids->pdata[i]);
2021 /* We intentionally don't remove it from the cache because
2022 * the cached data may be useful in replaying a COPY later.
2026 camel_db_delete_uids (parent_store->cdb_w, full_name, list, NULL);
2028 camel_folder_summary_save_to_db (folder->summary, NULL);
2029 camel_folder_changed (folder, changes);
2030 camel_folder_change_info_free (changes);
2036 imap_expunge_sync (CamelFolder *folder,
2037 GCancellable *cancellable,
2040 CamelStore *parent_store;
2041 GPtrArray *uids = NULL;
2042 const gchar *full_name;
2043 gboolean success, real_trash = FALSE;
2045 full_name = camel_folder_get_full_name (folder);
2046 parent_store = camel_folder_get_parent_store (folder);
2048 camel_folder_summary_save_to_db (folder->summary, NULL);
2050 if ((parent_store->flags & CAMEL_STORE_VTRASH) == 0) {
2052 GError *local_error = NULL;
2054 trash = camel_store_get_trash_folder_sync (
2055 parent_store, cancellable, &local_error);
2057 if (local_error == NULL && trash && (folder == trash || g_ascii_strcasecmp (full_name, camel_folder_get_full_name (trash)) == 0)) {
2058 /* it's a real trash folder, thus get all mails from there */
2060 uids = camel_folder_summary_get_array (folder->summary);
2063 if (local_error != NULL)
2064 g_clear_error (&local_error);
2068 uids = camel_db_get_folder_deleted_uids (parent_store->cdb_r, full_name, NULL);
2073 if (camel_offline_store_get_online (CAMEL_OFFLINE_STORE (parent_store)))
2074 success = imap_expunge_uids_online (
2075 folder, uids, cancellable, error);
2077 success = imap_expunge_uids_offline (
2078 folder, uids, cancellable, error);
2081 camel_folder_summary_free_array (uids);
2083 g_ptr_array_foreach (uids, (GFunc) camel_pstring_free, NULL);
2084 g_ptr_array_free (uids, TRUE);
2091 camel_imap_expunge_uids_resyncing (CamelFolder *folder,
2093 GCancellable *cancellable,
2096 CamelStore *parent_store;
2097 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
2098 CamelImapStore *store;
2099 GPtrArray *keep_uids, *mark_uids;
2100 CamelImapResponse *response;
2103 parent_store = camel_folder_get_parent_store (folder);
2104 store = CAMEL_IMAP_STORE (parent_store);
2106 if (imap_folder->read_only)
2109 if (store->capabilities & IMAP_CAPABILITY_UIDPLUS)
2110 return imap_expunge_uids_online (
2111 folder, uids, cancellable, error);
2113 /* If we don't have UID EXPUNGE we need to avoid expunging any
2114 * of the wrong messages. So we search for deleted messages,
2115 * and any that aren't in our to-expunge list get temporarily
2116 * marked un-deleted.
2119 camel_service_lock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2121 if (!CAMEL_FOLDER_GET_CLASS (folder)->synchronize_sync (
2122 folder, 0, cancellable, error)) {
2123 camel_service_unlock (
2124 CAMEL_SERVICE (store),
2125 CAMEL_SERVICE_REC_CONNECT_LOCK);
2129 response = camel_imap_command (store, folder, cancellable, error, "UID SEARCH DELETED");
2131 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2134 result = camel_imap_response_extract (store, response, "SEARCH", error);
2136 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2140 if (result[8] == ' ') {
2141 gchar *uid, *lasts = NULL;
2145 keep_uids = g_ptr_array_new ();
2146 mark_uids = g_ptr_array_new ();
2148 /* Parse SEARCH response */
2149 for (uid = strtok_r (result + 9, " ", &lasts); uid; uid = strtok_r (NULL, " ", &lasts))
2150 g_ptr_array_add (keep_uids, uid);
2151 qsort (keep_uids->pdata, keep_uids->len,
2152 sizeof (gpointer), uid_compar);
2154 /* Fill in "mark_uids", empty out "keep_uids" as needed */
2155 for (ei = ki = 0; ei < uids->len; ei++) {
2156 euid = strtoul (uids->pdata[ei], NULL, 10);
2158 for (kuid = 0; ki < keep_uids->len; ki++) {
2159 kuid = strtoul (keep_uids->pdata[ki], NULL, 10);
2166 g_ptr_array_remove_index (keep_uids, ki);
2168 g_ptr_array_add (mark_uids, uids->pdata[ei]);
2171 /* Empty SEARCH result, meaning nothing is marked deleted
2179 /* Unmark messages to be kept */
2185 while (uid < keep_uids->len) {
2186 uidset = imap_uid_array_to_set (folder->summary, keep_uids, uid, UID_SET_LIMIT, &uid);
2188 response = camel_imap_command (store, folder, cancellable, error,
2189 "UID STORE %s -FLAGS.SILENT (\\Deleted)",
2195 g_ptr_array_free (keep_uids, TRUE);
2196 g_ptr_array_free (mark_uids, TRUE);
2197 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2200 camel_imap_response_free (store, response);
2204 /* Mark any messages that still need to be marked */
2209 while (uid < mark_uids->len) {
2210 uidset = imap_uid_array_to_set (folder->summary, mark_uids, uid, UID_SET_LIMIT, &uid);
2212 response = camel_imap_command (store, folder, cancellable, error,
2213 "UID STORE %s +FLAGS.SILENT (\\Deleted)",
2219 g_ptr_array_free (keep_uids, TRUE);
2220 g_ptr_array_free (mark_uids, TRUE);
2221 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2224 camel_imap_response_free (store, response);
2227 if (mark_uids != uids)
2228 g_ptr_array_free (mark_uids, TRUE);
2231 /* Do the actual expunging */
2232 response = camel_imap_command (store, folder, cancellable, NULL, "EXPUNGE");
2234 camel_imap_response_free (store, response);
2236 /* And fix the remaining messages if we mangled them */
2241 while (uid < keep_uids->len) {
2242 uidset = imap_uid_array_to_set (folder->summary, keep_uids, uid, UID_SET_LIMIT, &uid);
2244 response = camel_imap_command (store, folder, cancellable, NULL,
2245 "UID STORE %s +FLAGS.SILENT (\\Deleted)",
2250 camel_imap_response_free (store, response);
2253 g_ptr_array_free (keep_uids, TRUE);
2256 /* now we can free this, now that we're done with keep_uids */
2259 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2265 camel_imap_expunge_uids_only (CamelFolder *folder,
2267 GCancellable *cancellable,
2270 CamelStore *parent_store;
2272 g_return_val_if_fail (folder != NULL, FALSE);
2274 parent_store = camel_folder_get_parent_store (folder);
2275 g_return_val_if_fail (parent_store != NULL, FALSE);
2277 g_return_val_if_fail (uids != NULL, FALSE);
2279 if (camel_offline_store_get_online (CAMEL_OFFLINE_STORE (parent_store)))
2280 return camel_imap_expunge_uids_resyncing (
2281 folder, uids, cancellable, error);
2283 return imap_expunge_uids_offline (
2284 folder, uids, cancellable, error);
2292 static gint counter = 0;
2293 G_LOCK_DEFINE_STATIC (lock);
2296 res = g_strdup_printf ("tempuid-%lx-%d",
2297 (gulong) time (NULL),
2305 imap_append_offline (CamelFolder *folder,
2306 CamelMimeMessage *message,
2307 CamelMessageInfo *info,
2308 gchar **appended_uid,
2311 CamelImapMessageCache *cache = CAMEL_IMAP_FOLDER (folder)->cache;
2312 CamelFolderChangeInfo *changes;
2315 uid = get_temp_uid ();
2317 camel_imap_summary_add_offline (
2318 folder->summary, uid, message, info);
2319 CAMEL_IMAP_FOLDER_REC_LOCK (folder, cache_lock);
2320 camel_imap_message_cache_insert_wrapper (
2321 cache, uid, "", CAMEL_DATA_WRAPPER (message));
2322 CAMEL_IMAP_FOLDER_REC_UNLOCK (folder, cache_lock);
2324 changes = camel_folder_change_info_new ();
2325 camel_folder_change_info_add_uid (changes, uid);
2326 camel_folder_changed (folder, changes);
2327 camel_folder_change_info_free (changes);
2329 camel_imap_journal_log (CAMEL_IMAP_FOLDER (folder)->journal,
2330 CAMEL_IMAP_JOURNAL_ENTRY_APPEND, uid);
2332 *appended_uid = uid;
2340 imap_folder_add_ignore_recent (CamelImapFolder *imap_folder,
2343 g_return_if_fail (imap_folder != NULL);
2344 g_return_if_fail (uid != NULL);
2346 if (!imap_folder->priv->ignore_recent)
2347 imap_folder->priv->ignore_recent = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
2349 g_hash_table_insert (imap_folder->priv->ignore_recent, g_strdup (uid), GINT_TO_POINTER (1));
2353 imap_folder_uid_in_ignore_recent (CamelImapFolder *imap_folder,
2356 g_return_val_if_fail (imap_folder != NULL, FALSE);
2357 g_return_val_if_fail (uid != NULL, FALSE);
2359 return imap_folder->priv->ignore_recent && g_hash_table_lookup (imap_folder->priv->ignore_recent, uid);
2362 static CamelImapResponse *
2363 do_append (CamelFolder *folder,
2364 CamelMimeMessage *message,
2365 CamelMessageInfo *info,
2367 GCancellable *cancellable,
2370 CamelStore *parent_store;
2371 CamelImapStore *store;
2372 CamelImapResponse *response, *response2;
2373 CamelStream *memstream;
2374 CamelMimeFilter *crlf_filter;
2375 CamelStream *streamfilter;
2377 const gchar *full_name;
2378 gchar *flagstr, *end;
2380 GError *local_error = NULL;
2382 parent_store = camel_folder_get_parent_store (folder);
2383 store = CAMEL_IMAP_STORE (parent_store);
2385 /* encode any 8bit parts so we avoid sending embedded nul-chars and such */
2386 camel_mime_message_encode_8bit_parts (message);
2388 /* FIXME: We could avoid this if we knew how big the message was. */
2389 memstream = camel_stream_mem_new ();
2390 ba = g_byte_array_new ();
2391 camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (memstream), ba);
2393 streamfilter = camel_stream_filter_new (memstream);
2394 crlf_filter = camel_mime_filter_crlf_new (
2395 CAMEL_MIME_FILTER_CRLF_ENCODE,
2396 CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
2397 camel_stream_filter_add (
2398 CAMEL_STREAM_FILTER (streamfilter), crlf_filter);
2399 camel_data_wrapper_write_to_stream_sync (
2400 CAMEL_DATA_WRAPPER (message), streamfilter, cancellable, NULL);
2401 g_object_unref (streamfilter);
2402 g_object_unref (crlf_filter);
2403 g_object_unref (memstream);
2405 /* Some servers don't let us append with (CamelMessageInfo *)custom flags. If the command fails for
2406 * whatever reason, assume this is the case and save the state and try again */
2409 flags = camel_message_info_flags (info);
2412 flags &= folder->permanent_flags;
2414 flagstr = imap_create_flag_list (flags, (CamelMessageInfo *) info, folder->permanent_flags);
2418 full_name = camel_folder_get_full_name (folder);
2419 response = camel_imap_command (
2420 store, NULL, cancellable, &local_error, "APPEND %F%s%s {%d}",
2421 full_name, flagstr ? " " : "",
2422 flagstr ? flagstr : "", ba->len);
2426 if (g_error_matches (local_error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_INVALID) && !store->nocustomappend) {
2427 g_clear_error (&local_error);
2428 store->nocustomappend = 1;
2431 g_propagate_error (error, local_error);
2432 g_byte_array_free (ba, TRUE);
2436 if (*response->status != '+') {
2437 camel_imap_response_free (store, response);
2438 g_byte_array_free (ba, TRUE);
2442 /* send the rest of our data - the mime message */
2443 response2 = camel_imap_command_continuation (store, folder, (const gchar *) ba->data, ba->len, cancellable, error);
2444 g_byte_array_free (ba, TRUE);
2446 /* free it only after message is sent. This may cause more FETCHes. */
2447 camel_imap_response_free (store, response);
2451 if ((store->capabilities & IMAP_CAPABILITY_UIDPLUS) != 0 ||
2452 is_google_account (parent_store)) {
2453 *uid = camel_strstrcase (response2->status, "[APPENDUID ");
2455 *uid = strchr (*uid + 11, ' ');
2457 *uid = g_strndup (*uid + 1, strcspn (*uid + 1, "]"));
2458 /* Make sure it's a number */
2459 if (strtoul (*uid, &end, 10) == 0 || *end) {
2468 imap_folder_add_ignore_recent (CAMEL_IMAP_FOLDER (folder), *uid);
2474 imap_append_online (CamelFolder *folder,
2475 CamelMimeMessage *message,
2476 CamelMessageInfo *info,
2477 gchar **appended_uid,
2478 GCancellable *cancellable,
2481 CamelStore *parent_store;
2482 CamelImapStore *store;
2483 CamelImapResponse *response;
2484 gboolean success = TRUE;
2488 parent_store = camel_folder_get_parent_store (folder);
2489 store = CAMEL_IMAP_STORE (parent_store);
2491 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store))) {
2492 return imap_append_offline (
2493 folder, message, info, appended_uid, error);
2496 count = camel_folder_summary_count (folder->summary);
2497 response = do_append (folder, message, info, &uid, cancellable, error);
2502 /* Cache first, since freeing response may trigger a
2503 * summary update that will want this information.
2505 CAMEL_IMAP_FOLDER_REC_LOCK (folder, cache_lock);
2506 camel_imap_message_cache_insert_wrapper (
2507 CAMEL_IMAP_FOLDER (folder)->cache, uid,
2508 "", CAMEL_DATA_WRAPPER (message));
2509 CAMEL_IMAP_FOLDER_REC_UNLOCK (folder, cache_lock);
2511 *appended_uid = uid;
2514 } else if (appended_uid)
2515 *appended_uid = NULL;
2517 camel_imap_response_free (store, response);
2519 /* Make sure a "folder_changed" is emitted. */
2520 camel_service_lock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2521 if (store->current_folder != folder ||
2522 camel_folder_summary_count (folder->summary) == count)
2523 success = imap_refresh_info_sync (folder, cancellable, error);
2524 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2530 camel_imap_append_resyncing (CamelFolder *folder,
2531 CamelMimeMessage *message,
2532 CamelMessageInfo *info,
2533 gchar **appended_uid,
2534 GCancellable *cancellable,
2537 CamelStore *parent_store;
2538 CamelImapStore *store;
2539 CamelImapResponse *response;
2542 parent_store = camel_folder_get_parent_store (folder);
2543 store = CAMEL_IMAP_STORE (parent_store);
2545 response = do_append (folder, message, info, &uid, cancellable, error);
2550 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
2551 const gchar *olduid = camel_message_info_uid (info);
2553 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
2554 camel_imap_message_cache_copy (imap_folder->cache, olduid,
2555 imap_folder->cache, uid);
2556 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
2559 *appended_uid = uid;
2562 } else if (appended_uid)
2563 *appended_uid = NULL;
2565 camel_imap_response_free (store, response);
2571 imap_transfer_offline (CamelFolder *source,
2574 gboolean delete_originals,
2575 GPtrArray **transferred_uids,
2576 GCancellable *cancellable,
2579 CamelStore *parent_store;
2580 CamelImapStore *store;
2581 CamelImapMessageCache *sc = CAMEL_IMAP_FOLDER (source)->cache;
2582 CamelImapMessageCache *dc = CAMEL_IMAP_FOLDER (dest)->cache;
2583 CamelFolderChangeInfo *changes;
2584 CamelMimeMessage *message;
2585 CamelMessageInfo *mi;
2586 gchar *uid, *destuid;
2588 GError *local_error = NULL;
2590 parent_store = camel_folder_get_parent_store (source);
2591 store = CAMEL_IMAP_STORE (parent_store);
2593 /* We grab the store's command lock first, and then grab the
2594 * source and destination cache_locks. This way we can't
2595 * deadlock in the case where we're simultaneously also trying
2596 * to copy messages in the other direction from another thread.
2598 camel_service_lock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2599 CAMEL_IMAP_FOLDER_REC_LOCK (source, cache_lock);
2600 CAMEL_IMAP_FOLDER_REC_LOCK (dest, cache_lock);
2601 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2603 if (transferred_uids) {
2604 *transferred_uids = g_ptr_array_new ();
2605 g_ptr_array_set_size (*transferred_uids, uids->len);
2608 changes = camel_folder_change_info_new ();
2610 for (i = 0; i < uids->len && local_error == NULL; i++) {
2611 uid = uids->pdata[i];
2613 destuid = get_temp_uid ();
2615 mi = camel_folder_summary_get (source->summary, uid);
2616 g_return_val_if_fail (mi != NULL, FALSE);
2618 message = camel_folder_get_message_sync (
2619 source, uid, cancellable, &local_error);
2622 camel_imap_summary_add_offline (
2623 dest->summary, destuid, message, mi);
2624 g_object_unref (message);
2626 camel_imap_summary_add_offline_uncached (
2627 dest->summary, destuid, mi);
2629 camel_imap_message_cache_copy (sc, uid, dc, destuid);
2630 camel_message_info_free (mi);
2632 camel_folder_change_info_add_uid (changes, destuid);
2633 if (transferred_uids)
2634 (*transferred_uids)->pdata[i] = destuid;
2638 if (delete_originals && local_error == NULL)
2639 camel_folder_delete_message (source, uid);
2642 CAMEL_IMAP_FOLDER_REC_UNLOCK (dest, cache_lock);
2643 CAMEL_IMAP_FOLDER_REC_UNLOCK (source, cache_lock);
2645 camel_folder_changed (dest, changes);
2646 camel_folder_change_info_free (changes);
2648 camel_imap_journal_log (
2649 CAMEL_IMAP_FOLDER (source)->journal,
2650 CAMEL_IMAP_JOURNAL_ENTRY_TRANSFER,
2651 dest, uids, delete_originals, NULL);
2653 if (local_error != NULL) {
2654 g_propagate_error (error, local_error);
2662 handle_copyuid (CamelImapResponse *response,
2663 CamelFolder *source,
2664 CamelFolder *destination)
2666 CamelImapMessageCache *scache = CAMEL_IMAP_FOLDER (source)->cache;
2667 CamelImapMessageCache *dcache = CAMEL_IMAP_FOLDER (destination)->cache;
2668 gchar *validity, *srcset, *destset;
2669 GPtrArray *src, *dest;
2672 validity = camel_strstrcase (response->status, "[COPYUID ");
2676 if (strtoul (validity, NULL, 10) !=
2677 CAMEL_IMAP_SUMMARY (destination->summary)->validity)
2680 srcset = strchr (validity, ' ');
2683 destset = strchr (srcset, ' ');
2687 src = imap_uid_set_to_array (source->summary, srcset);
2688 dest = imap_uid_set_to_array (destination->summary, destset);
2690 if (src && dest && src->len == dest->len) {
2691 /* We don't have to worry about deadlocking on the
2692 * cache locks here, because we've got the store's
2693 * command lock too, so no one else could be here.
2695 CAMEL_IMAP_FOLDER_REC_LOCK (source, cache_lock);
2696 CAMEL_IMAP_FOLDER_REC_LOCK (destination, cache_lock);
2697 for (i = 0; i < src->len; i++) {
2698 camel_imap_message_cache_copy (scache, src->pdata[i],
2699 dcache, dest->pdata[i]);
2701 imap_folder_add_ignore_recent (CAMEL_IMAP_FOLDER (destination), dest->pdata[i]);
2703 CAMEL_IMAP_FOLDER_REC_UNLOCK (source, cache_lock);
2704 CAMEL_IMAP_FOLDER_REC_UNLOCK (destination, cache_lock);
2706 imap_uid_array_free (src);
2707 imap_uid_array_free (dest);
2712 imap_uid_array_free (src);
2714 imap_uid_array_free (dest);
2716 g_warning ("Bad COPYUID response from server");
2720 handle_copyuid_copy_user_tags (CamelImapResponse *response,
2721 CamelFolder *source,
2722 CamelFolder *destination,
2723 GCancellable *cancellable)
2725 CamelStore *parent_store;
2726 gchar *validity, *srcset, *destset;
2727 GPtrArray *src, *dest;
2730 validity = camel_strstrcase (response->status, "[COPYUID ");
2734 if (strtoul (validity, NULL, 10) !=
2735 CAMEL_IMAP_SUMMARY (destination->summary)->validity)
2738 srcset = strchr (validity, ' ');
2741 destset = strchr (srcset, ' ');
2745 /* first do NOOP on the destination folder, so server has enough time to propagate our copy command there */
2746 parent_store = camel_folder_get_parent_store (destination);
2747 camel_imap_response_free (
2748 CAMEL_IMAP_STORE (parent_store), camel_imap_command (
2749 CAMEL_IMAP_STORE (parent_store), destination, cancellable, NULL, "NOOP"));
2751 /* refresh folder's summary first, we copied messages there on the server,
2752 * but do not know about it in a local summary */
2753 if (!imap_refresh_info_sync (destination, cancellable, NULL))
2756 src = imap_uid_set_to_array (source->summary, srcset);
2757 dest = imap_uid_set_to_array (destination->summary, destset);
2759 if (src && dest && src->len == dest->len) {
2760 /* We don't have to worry about deadlocking on the
2761 * cache locks here, because we've got the store's
2762 * command lock too, so no one else could be here.
2764 CAMEL_IMAP_FOLDER_REC_LOCK (source, cache_lock);
2765 CAMEL_IMAP_FOLDER_REC_LOCK (destination, cache_lock);
2766 for (i = 0; i < src->len; i++) {
2767 CamelMessageInfo *mi = camel_folder_get_message_info (source, src->pdata[i]);
2770 const CamelTag *tag = camel_message_info_user_tags (mi);
2773 camel_folder_set_message_user_tag (destination, dest->pdata[i], tag->name, tag->value);
2777 camel_folder_free_message_info (source, mi);
2780 CAMEL_IMAP_FOLDER_REC_UNLOCK (source, cache_lock);
2781 CAMEL_IMAP_FOLDER_REC_UNLOCK (destination, cache_lock);
2783 imap_uid_array_free (src);
2784 imap_uid_array_free (dest);
2789 imap_uid_array_free (src);
2791 imap_uid_array_free (dest);
2793 g_warning ("Bad COPYUID response from server");
2796 /* returns whether any of messages from uidset has set any user tag or not */
2798 any_has_user_tag (CamelFolder *source,
2803 g_return_val_if_fail (source != NULL && uidset != NULL, FALSE);
2805 src = imap_uid_set_to_array (source->summary, uidset);
2807 gboolean have = FALSE;
2810 CAMEL_IMAP_FOLDER_REC_LOCK (source, cache_lock);
2811 for (i = 0; i < src->len && !have; i++) {
2812 CamelMessageInfo *mi = camel_folder_get_message_info (source, src->pdata[i]);
2815 have = camel_message_info_user_tags (mi) != NULL;
2817 camel_folder_free_message_info (source, mi);
2820 CAMEL_IMAP_FOLDER_REC_UNLOCK (source, cache_lock);
2822 imap_uid_array_free (src);
2831 do_copy (CamelFolder *source,
2833 CamelFolder *destination,
2834 gint delete_originals,
2835 GCancellable *cancellable,
2838 CamelService *service;
2839 CamelSettings *settings;
2840 CamelStore *parent_store;
2841 CamelImapStore *store;
2842 CamelImapResponse *response;
2843 const gchar *full_name;
2846 gint uid = 0, last = 0, i;
2847 GError *local_error = NULL;
2848 gboolean mark_moved;
2849 gboolean success = TRUE;
2851 parent_store = camel_folder_get_parent_store (source);
2852 store = CAMEL_IMAP_STORE (parent_store);
2854 service = CAMEL_SERVICE (parent_store);
2855 settings = camel_service_get_settings (service);
2857 trash_path = camel_imap_settings_dup_real_trash_path (
2858 CAMEL_IMAP_SETTINGS (settings));
2860 mark_moved = is_google_account (parent_store) && trash_path != NULL;
2862 full_name = camel_folder_get_full_name (destination);
2864 while (uid < uids->len && local_error == NULL) {
2865 uidset = imap_uid_array_to_set (source->summary, uids, uid, UID_SET_LIMIT, &uid);
2867 /* use XGWMOVE only when none of the moving messages has set any user tag */
2868 if ((store->capabilities & IMAP_CAPABILITY_XGWMOVE) != 0 && delete_originals && !any_has_user_tag (source, uidset)) {
2869 camel_service_lock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2870 response = camel_imap_command (
2871 store, source, cancellable, &local_error,
2872 "UID XGWMOVE %s %F", uidset, full_name);
2873 /* returns only 'A00012 OK UID XGWMOVE completed' '* 2 XGWMOVE' so nothing useful */
2874 camel_imap_response_free (store, response);
2875 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2877 camel_service_lock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2878 response = camel_imap_command (
2879 store, source, cancellable, &local_error,
2880 "UID COPY %s %F", uidset, full_name);
2881 if (response && (store->capabilities & IMAP_CAPABILITY_UIDPLUS))
2882 handle_copyuid (response, source, destination);
2884 handle_copyuid_copy_user_tags (
2885 response, source, destination,
2887 camel_imap_response_free (store, response);
2888 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
2891 if (local_error == NULL && delete_originals && (mark_moved || !trash_path)) {
2892 for (i = last; i < uid; i++) {
2893 camel_folder_delete_message (
2894 source, uids->pdata[i]);
2896 CamelMessageInfoBase *info = (CamelMessageInfoBase *) camel_folder_summary_get (source->summary, uids->pdata[i]);
2899 info->flags |= CAMEL_IMAP_MESSAGE_MOVED;
2907 if (local_error != NULL) {
2908 g_propagate_error (error, local_error);
2911 /* There is a real trash folder set, which is not on a google account
2912 * and copied messages should be deleted, thus do not move them into
2913 * a trash folder, but just expunge them, because the copy part of
2914 * the operation was successful. */
2915 } else if (trash_path && !mark_moved && delete_originals)
2916 camel_imap_expunge_uids_only (source, uids, cancellable, NULL);
2918 g_free (trash_path);
2924 imap_transfer_messages (CamelFolder *source,
2927 gboolean delete_originals,
2928 GPtrArray **transferred_uids,
2929 gboolean can_call_sync,
2930 GCancellable *cancellable,
2933 CamelStore *parent_store;
2934 CamelImapStore *store;
2935 gboolean success = TRUE;
2938 parent_store = camel_folder_get_parent_store (source);
2939 store = CAMEL_IMAP_STORE (parent_store);
2941 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store)))
2942 return imap_transfer_offline (
2943 source, uids, dest, delete_originals,
2944 transferred_uids, cancellable, error);
2946 /* Sync message flags if needed. */
2947 if (can_call_sync && !imap_synchronize_sync (
2948 source, FALSE, cancellable, error))
2951 count = camel_folder_summary_count (dest->summary);
2953 qsort (uids->pdata, uids->len, sizeof (gpointer), uid_compar);
2955 /* Now copy the messages */
2956 if (!do_copy (source, uids, dest, delete_originals, cancellable, error))
2959 /* Make the destination notice its new messages */
2960 if (store->current_folder != dest ||
2961 camel_folder_summary_count (dest->summary) == count)
2962 success = imap_refresh_info_sync (dest, cancellable, error);
2965 if (transferred_uids)
2966 *transferred_uids = NULL;
2972 imap_transfer_online (CamelFolder *source,
2975 gboolean delete_originals,
2976 GPtrArray **transferred_uids,
2977 GCancellable *cancellable,
2980 return imap_transfer_messages (
2981 source, uids, dest, delete_originals,
2982 transferred_uids, TRUE, cancellable, error);
2986 camel_imap_transfer_resyncing (CamelFolder *source,
2989 gboolean delete_originals,
2990 GPtrArray **transferred_uids,
2991 GCancellable *cancellable,
2994 GPtrArray *realuids;
2997 CamelMimeMessage *message;
2998 CamelMessageInfo *info;
2999 GError *local_error = NULL;
3001 qsort (uids->pdata, uids->len, sizeof (gpointer), uid_compar);
3003 /*This is trickier than append_resyncing, because some of
3004 * the messages we are copying may have been copied or
3005 * appended into @source while we were offline, in which case
3006 * if we don't have UIDPLUS, we won't know their real UIDs,
3007 * so we'll have to append them rather than copying. */
3009 realuids = g_ptr_array_new ();
3012 while (i < uids->len && local_error == NULL) {
3013 /* Skip past real UIDs */
3014 for (first = i; i < uids->len; i++) {
3015 uid = uids->pdata[i];
3017 if (!isdigit ((guchar) * uid)) {
3018 uid = camel_imap_journal_uidmap_lookup ((CamelIMAPJournal *) CAMEL_IMAP_FOLDER (source)->journal, uid);
3022 g_ptr_array_add (realuids, (gchar *) uid);
3025 /* If we saw any real UIDs, do a COPY */
3028 source, realuids, dest, delete_originals,
3029 cancellable, &local_error);
3030 g_ptr_array_set_size (realuids, 0);
3031 if (i == uids->len || local_error != NULL)
3035 /* Deal with fake UIDs */
3036 while (i < uids->len &&
3037 !isdigit (*(guchar *)(uids->pdata[i])) &&
3038 local_error == NULL) {
3039 uid = uids->pdata[i];
3040 message = camel_folder_get_message_sync (
3041 source, uid, cancellable, NULL);
3043 /* Message must have been expunged */
3047 info = camel_folder_get_message_info (source, uid);
3048 g_return_val_if_fail (info != NULL, FALSE);
3050 imap_append_online (
3051 dest, message, info,
3052 NULL, cancellable, &local_error);
3053 camel_folder_free_message_info (source, info);
3054 g_object_unref (message);
3055 if (delete_originals && local_error == NULL)
3056 camel_folder_delete_message (source, uid);
3061 g_ptr_array_free (realuids, FALSE);
3064 if (transferred_uids)
3065 *transferred_uids = NULL;
3067 if (local_error != NULL) {
3068 g_propagate_error (error, local_error);
3076 imap_search_by_expression (CamelFolder *folder,
3077 const gchar *expression,
3078 GCancellable *cancellable,
3081 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3084 /* we could get around this by creating a new search object each time,
3085 * but i doubt its worth it since any long operation would lock the
3086 * command channel too */
3087 CAMEL_IMAP_FOLDER_LOCK (folder, search_lock);
3089 camel_folder_search_set_folder (imap_folder->search, folder);
3090 matches = camel_folder_search_search (imap_folder->search, expression, NULL, cancellable, error);
3092 CAMEL_IMAP_FOLDER_UNLOCK (folder, search_lock);
3098 imap_count_by_expression (CamelFolder *folder,
3099 const gchar *expression,
3100 GCancellable *cancellable,
3103 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3106 /* we could get around this by creating a new search object each time,
3107 * but i doubt its worth it since any long operation would lock the
3108 * command channel too */
3109 CAMEL_IMAP_FOLDER_LOCK (folder, search_lock);
3111 camel_folder_search_set_folder (imap_folder->search, folder);
3112 matches = camel_folder_search_count (imap_folder->search, expression, cancellable, error);
3114 CAMEL_IMAP_FOLDER_UNLOCK (folder, search_lock);
3120 imap_search_by_uids (CamelFolder *folder,
3121 const gchar *expression,
3123 GCancellable *cancellable,
3126 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3130 return g_ptr_array_new ();
3132 CAMEL_IMAP_FOLDER_LOCK (folder, search_lock);
3134 camel_folder_search_set_folder (imap_folder->search, folder);
3135 matches = camel_folder_search_search (imap_folder->search, expression, uids, cancellable, error);
3137 CAMEL_IMAP_FOLDER_UNLOCK (folder, search_lock);
3143 imap_search_free (CamelFolder *folder,
3146 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3148 g_return_if_fail (imap_folder->search);
3150 CAMEL_IMAP_FOLDER_LOCK (folder, search_lock);
3152 camel_folder_search_free_result (imap_folder->search, uids);
3154 CAMEL_IMAP_FOLDER_UNLOCK (folder, search_lock);
3157 static CamelMimeMessage *get_message (CamelImapFolder *imap_folder,
3159 CamelMessageContentInfo *ci,
3160 GCancellable *cancellable,
3163 struct _part_spec_stack {
3164 struct _part_spec_stack *parent;
3169 part_spec_push (struct _part_spec_stack **stack,
3172 struct _part_spec_stack *node;
3174 node = g_new (struct _part_spec_stack, 1);
3175 node->parent = *stack;
3182 part_spec_pop (struct _part_spec_stack **stack)
3184 struct _part_spec_stack *node;
3187 g_return_val_if_fail (*stack != NULL, 0);
3190 *stack = node->parent;
3199 content_info_get_part_spec (CamelMessageContentInfo *ci)
3201 struct _part_spec_stack *stack = NULL;
3202 CamelMessageContentInfo *node;
3203 gchar *part_spec, *buf;
3208 while (node->parent) {
3209 CamelMessageContentInfo *child;
3211 /* FIXME: is this only supposed to apply if 'node' is a multipart? */
3212 if (node->parent->parent &&
3213 camel_content_type_is (node->parent->type, "message", "*") &&
3214 !camel_content_type_is (node->parent->parent->type, "message", "*")) {
3215 node = node->parent;
3219 child = node->parent->childs;
3220 for (part = 1; child; part++) {
3224 child = child->next;
3227 part_spec_push (&stack, part);
3230 while ((part = part / 10))
3233 node = node->parent;
3236 buf = part_spec = g_malloc (len);
3237 part_spec[0] = '\0';
3240 part = part_spec_pop (&stack);
3241 buf += sprintf (buf, "%d%s", part, stack ? "." : "");
3247 /* Fetch the contents of the MIME part indicated by @ci, which is part
3248 * of message @uid in @folder.
3250 static CamelDataWrapper *
3251 get_content (CamelImapFolder *imap_folder,
3253 CamelMimePart *part,
3254 CamelMessageContentInfo *ci,
3256 GCancellable *cancellable,
3259 CamelDataWrapper *content = NULL;
3260 CamelStream *stream;
3263 part_spec = content_info_get_part_spec (ci);
3265 d(printf("get content '%s' '%s' (frommsg = %d)\n", part_spec, camel_content_type_format(ci->type), frommsg));
3267 /* There are three cases: multipart/signed, multipart, message/rfc822, and "other" */
3268 if (camel_content_type_is (ci->type, "multipart", "signed")) {
3269 CamelMultipartSigned *body_mp;
3273 /* Note: because we get the content parts uninterpreted anyway, we could potentially
3274 * just use the normalmultipart code, except that multipart/signed wont let you yet! */
3276 body_mp = camel_multipart_signed_new ();
3277 /* need to set this so it grabs the boundary and other info about the signed type */
3278 /* we assume that part->content_type is more accurate/full than ci->type */
3279 camel_data_wrapper_set_mime_type_field (CAMEL_DATA_WRAPPER (body_mp), CAMEL_DATA_WRAPPER (part)->mime_type);
3281 spec = g_alloca (strlen (part_spec) + 6);
3283 sprintf(spec, part_spec[0] ? "%s.TEXT" : "TEXT", part_spec);
3285 strcpy (spec, part_spec);
3288 stream = camel_imap_folder_fetch_data (imap_folder, uid, spec, FALSE, cancellable, error);
3290 success = camel_data_wrapper_construct_from_stream_sync (
3291 CAMEL_DATA_WRAPPER (body_mp), stream, cancellable, error);
3292 g_object_unref (stream);
3294 g_object_unref ( body_mp);
3299 return (CamelDataWrapper *) body_mp;
3300 } else if (camel_content_type_is (ci->type, "multipart", "*")) {
3301 CamelMultipart *body_mp;
3303 gint speclen, num, isdigest;
3305 if (camel_content_type_is (ci->type, "multipart", "encrypted"))
3306 body_mp = (CamelMultipart *) camel_multipart_encrypted_new ();
3308 body_mp = camel_multipart_new ();
3310 /* need to set this so it grabs the boundary and other info about the multipart */
3311 /* we assume that part->content_type is more accurate/full than ci->type */
3312 camel_data_wrapper_set_mime_type_field (CAMEL_DATA_WRAPPER (body_mp), CAMEL_DATA_WRAPPER (part)->mime_type);
3313 isdigest = camel_content_type_is(((CamelDataWrapper *)part)->mime_type, "multipart", "digest");
3315 speclen = strlen (part_spec);
3316 child_spec = g_malloc (speclen + 17); /* dot + 10 + dot + MIME + nul */
3317 memcpy (child_spec, part_spec, speclen);
3319 child_spec[speclen++] = '.';
3325 sprintf (child_spec + speclen, "%d.MIME", num++);
3326 stream = camel_imap_folder_fetch_data (imap_folder, uid, child_spec, FALSE, cancellable, error);
3330 part = camel_mime_part_new ();
3331 success = camel_data_wrapper_construct_from_stream_sync (
3332 CAMEL_DATA_WRAPPER (part), stream, cancellable, error);
3333 g_object_unref (stream);
3335 g_object_unref (part);
3336 g_object_unref (body_mp);
3337 g_free (child_spec);
3341 content = get_content (imap_folder, uid, part, ci, FALSE, cancellable, error);
3344 if (!stream || !content) {
3345 g_object_unref (body_mp);
3346 g_free (child_spec);
3350 if (camel_debug("imap:folder")) {
3351 gchar *ct = camel_content_type_format (camel_mime_part_get_content_type ((CamelMimePart *) part));
3352 gchar *ct2 = camel_content_type_format (ci->type);
3354 printf("Setting part content type to '%s' contentinfo type is '%s'\n", ct, ct2);
3359 /* if we had no content-type header on a multipart/digest sub-part, then we need to
3360 * treat it as message/rfc822 instead */
3361 if (isdigest && camel_medium_get_header((CamelMedium *)part, "content-type") == NULL) {
3362 CamelContentType *ct = camel_content_type_new("message", "rfc822");
3364 camel_data_wrapper_set_mime_type_field (content, ct);
3365 camel_content_type_unref (ct);
3367 camel_data_wrapper_set_mime_type_field (content, camel_mime_part_get_content_type (part));
3370 camel_medium_set_content (CAMEL_MEDIUM (part), content);
3371 g_object_unref (content);
3373 camel_multipart_add_part (body_mp, part);
3374 g_object_unref (part);
3379 g_free (child_spec);
3381 return (CamelDataWrapper *) body_mp;
3382 } else if (camel_content_type_is (ci->type, "message", "rfc822")) {
3383 content = (CamelDataWrapper *) get_message (imap_folder, uid, ci->childs, cancellable, error);
3387 CamelTransferEncoding enc;
3390 /* NB: we need this differently to multipart/signed case above on purpose */
3391 spec = g_alloca (strlen (part_spec) + 6);
3393 sprintf(spec, part_spec[0] ? "%s.1" : "1", part_spec);
3395 strcpy(spec, part_spec[0]?part_spec:"1");
3397 enc = ci->encoding ? camel_transfer_encoding_from_string (ci->encoding) : CAMEL_TRANSFER_ENCODING_DEFAULT;
3398 content = camel_imap_wrapper_new (imap_folder, ci->type, enc, uid, spec, part);
3404 static CamelMimeMessage *
3405 get_message (CamelImapFolder *imap_folder,
3407 CamelMessageContentInfo *ci,
3408 GCancellable *cancellable,
3411 CamelFolder *folder;
3412 CamelStore *parent_store;
3413 CamelImapStore *store;
3414 CamelDataWrapper *content;
3415 CamelMimeMessage *msg;
3416 CamelStream *stream;
3417 gchar *section_text, *part_spec;
3420 folder = CAMEL_FOLDER (imap_folder);
3421 parent_store = camel_folder_get_parent_store (folder);
3422 store = CAMEL_IMAP_STORE (parent_store);
3424 part_spec = content_info_get_part_spec (ci);
3425 d(printf("get message '%s'\n", part_spec));
3426 section_text = g_strdup_printf ("%s%s%s", part_spec, *part_spec ? "." : "",
3427 store->server_level >= IMAP_LEVEL_IMAP4REV1 ? "HEADER" : "0");
3429 stream = camel_imap_folder_fetch_data (imap_folder, uid, section_text, FALSE, cancellable, error);
3430 g_free (section_text);
3435 msg = camel_mime_message_new ();
3436 success = camel_data_wrapper_construct_from_stream_sync (
3437 CAMEL_DATA_WRAPPER (msg), stream, cancellable, error);
3438 g_object_unref (stream);
3440 g_object_unref (msg);
3444 content = get_content (imap_folder, uid, CAMEL_MIME_PART (msg), ci, TRUE, cancellable, error);
3446 g_object_unref (msg);
3450 if (camel_debug("imap:folder")) {
3451 gchar *ct = camel_content_type_format (camel_mime_part_get_content_type ((CamelMimePart *) msg));
3452 gchar *ct2 = camel_content_type_format (ci->type);
3454 printf("Setting message content type to '%s' contentinfo type is '%s'\n", ct, ct2);
3459 camel_data_wrapper_set_mime_type_field (content, camel_mime_part_get_content_type ((CamelMimePart *) msg));
3460 camel_medium_set_content (CAMEL_MEDIUM (msg), content);
3461 g_object_unref (content);
3466 #define IMAP_SMALL_BODY_SIZE 5120
3468 static CamelMimeMessage *
3469 get_message_simple (CamelImapFolder *imap_folder,
3471 CamelStream *stream,
3472 GCancellable *cancellable,
3475 CamelMimeMessage *msg;
3479 stream = camel_imap_folder_fetch_data (imap_folder, uid, "",
3480 FALSE, cancellable, error);
3485 msg = camel_mime_message_new ();
3486 success = camel_data_wrapper_construct_from_stream_sync (
3487 CAMEL_DATA_WRAPPER (msg), stream, cancellable, error);
3488 g_object_unref (stream);
3490 g_prefix_error (error, _("Unable to retrieve message: "));
3491 g_object_unref (msg);
3499 content_info_incomplete (CamelMessageContentInfo *ci)
3504 if (camel_content_type_is (ci->type, "multipart", "*")
3505 || camel_content_type_is (ci->type, "message", "rfc822")) {
3508 for (ci = ci->childs; ci; ci = ci->next)
3509 if (content_info_incomplete (ci))
3516 static CamelImapMessageInfo *
3517 imap_folder_summary_uid_or_error (CamelFolderSummary *summary,
3521 CamelImapMessageInfo *mi;
3522 mi = (CamelImapMessageInfo *) camel_folder_summary_get (summary, uid);
3525 error, CAMEL_FOLDER_ERROR,
3526 CAMEL_FOLDER_ERROR_INVALID_UID,
3527 _("Cannot get message with message ID %s: %s"),
3528 uid, _("No such message available."));
3533 static CamelMimeMessage *
3534 imap_get_message_sync (CamelFolder *folder,
3536 GCancellable *cancellable,
3539 CamelStore *parent_store;
3540 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3541 CamelImapStore *store;
3542 CamelImapMessageInfo *mi;
3543 CamelMimeMessage *msg = NULL;
3544 CamelStream *stream = NULL;
3546 GError *local_error = NULL;
3548 parent_store = camel_folder_get_parent_store (folder);
3549 store = CAMEL_IMAP_STORE (parent_store);
3551 mi = imap_folder_summary_uid_or_error (folder->summary, uid, error);
3555 /* If its cached in full, just get it as is, this is only a shortcut,
3556 * since we get stuff from the cache anyway. It affects a busted
3557 * connection though. */
3558 stream = camel_imap_folder_fetch_data (imap_folder, uid, "", TRUE, cancellable, NULL);
3559 if (stream != NULL) {
3560 msg = get_message_simple (imap_folder, uid, stream, cancellable, NULL);
3565 /* All this mess is so we silently retry a fetch if we fail with
3566 * service_unavailable, without an (equivalent) mess of gotos */
3570 g_clear_error (&local_error);
3572 /* If the message is small or only 1 part, or server doesn't do 4v1 (properly) fetch it in one piece. */
3573 if (store->server_level < IMAP_LEVEL_IMAP4REV1
3574 || store->braindamaged
3575 || mi->info.size < IMAP_SMALL_BODY_SIZE
3576 || (!content_info_incomplete (mi->info.content) && !mi->info.content->childs)) {
3577 CamelMessageInfoBase *info = (CamelMessageInfoBase *) camel_folder_summary_get (folder->summary, uid);
3578 msg = get_message_simple (imap_folder, uid, NULL, cancellable, &local_error);
3579 if (info && !info->preview && msg && camel_folder_summary_get_need_preview (folder->summary)) {
3580 if (camel_mime_message_build_preview ((CamelMimePart *) msg, (CamelMessageInfo *) info) && info->preview)
3581 camel_folder_summary_add_preview (folder->summary, (CamelMessageInfo *) info);
3584 camel_message_info_free (info);
3586 if (content_info_incomplete (mi->info.content)) {
3587 /* For larger messages, fetch the structure and build a message
3588 * with offline parts. (We check mi->content->type rather than
3589 * mi->content because camel_folder_summary_info_new always creates
3590 * an empty content struct.)
3592 CamelImapResponse *response;
3593 GData *fetch_data = NULL;
3594 gchar *body, *found_uid;
3597 camel_service_lock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
3598 if (!camel_imap_store_connected (store, NULL)) {
3599 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
3601 error, CAMEL_SERVICE_ERROR,
3602 CAMEL_SERVICE_ERROR_UNAVAILABLE,
3603 _("This message is not currently available"));
3607 response = camel_imap_command (store, folder, cancellable, &local_error, "UID FETCH %s BODY", uid);
3610 for (i = 0, body = NULL; i < response->untagged->len; i++) {
3611 fetch_data = parse_fetch_response (imap_folder, response->untagged->pdata[i]);
3613 found_uid = g_datalist_get_data (&fetch_data, "UID");
3614 body = g_datalist_get_data (&fetch_data, "BODY");
3615 if (found_uid && body && !strcmp (found_uid, uid))
3617 g_datalist_clear (&fetch_data);
3624 /* NB: small race here, setting the info.content */
3625 imap_parse_body ((const gchar **) &body, folder, mi->info.content);
3626 mi->info.dirty = TRUE;
3627 camel_folder_summary_touch (folder->summary);
3631 g_datalist_clear (&fetch_data);
3633 camel_imap_response_free (store, response);
3635 g_clear_error (&local_error);
3637 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
3640 if (camel_debug_start("imap:folder")) {
3641 printf("Folder get message '%s' folder info ->\n", uid);
3642 camel_message_info_dump ((CamelMessageInfo *) mi);
3646 /* FETCH returned OK, but we didn't parse a BODY
3647 * response. Courier will return invalid BODY
3648 * responses for invalidly MIMEd messages, so
3649 * fall back to fetching the entire thing and
3650 * let the mailer's "bad MIME" code handle it.
3652 if (content_info_incomplete (mi->info.content))
3653 msg = get_message_simple (imap_folder, uid, NULL, cancellable, &local_error);
3655 msg = get_message (imap_folder, uid, mi->info.content, cancellable, &local_error);
3656 if (msg && camel_folder_summary_get_need_preview (folder->summary)) {
3657 CamelMessageInfoBase *info = (CamelMessageInfoBase *) camel_folder_summary_get (folder->summary, uid);
3658 if (info && !info->preview) {
3659 if (camel_mime_message_build_preview ((CamelMimePart *) msg, (CamelMessageInfo *) info) && info->preview)
3660 camel_folder_summary_add_preview (folder->summary, (CamelMessageInfo *) info);
3662 camel_message_info_free (info);
3666 } while (msg == NULL
3668 && g_error_matches (local_error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE));
3672 gboolean has_attachment;
3674 if (!mi->info.mlist || !*mi->info.mlist) {
3675 /* update mailing list information, if necessary */
3676 gchar *mlist = camel_header_raw_check_mailing_list (&(CAMEL_MIME_PART (msg)->headers));
3680 camel_pstring_free (mi->info.mlist);
3681 mi->info.mlist = camel_pstring_add (mlist, TRUE);
3682 mi->info.dirty = TRUE;
3684 if (mi->info.summary)
3685 camel_folder_summary_touch (mi->info.summary);
3689 has_attachment = camel_mime_message_has_attachment (msg);
3690 if (((camel_message_info_flags ((CamelMessageInfo *) mi) & CAMEL_MESSAGE_ATTACHMENTS) && !has_attachment) ||
3691 ((camel_message_info_flags ((CamelMessageInfo *) mi) & CAMEL_MESSAGE_ATTACHMENTS) == 0 && has_attachment)) {
3692 camel_message_info_set_flags ((CamelMessageInfo *) mi, CAMEL_MESSAGE_ATTACHMENTS, has_attachment ? CAMEL_MESSAGE_ATTACHMENTS : 0);
3696 if (local_error != NULL)
3697 g_propagate_error (error, local_error);
3700 camel_message_info_free (&mi->info);
3706 * imap_synchronize_message_sync
3708 * Ensure that a message is cached locally, but don't retrieve the content if
3709 * it is already local.
3712 imap_synchronize_message_sync (CamelFolder *folder,
3714 GCancellable *cancellable,
3717 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3718 CamelImapMessageInfo *mi;
3719 CamelMimeMessage *msg = NULL;
3720 CamelStream *stream = NULL;
3721 gboolean success = FALSE;
3723 mi = imap_folder_summary_uid_or_error (folder->summary, uid, error);
3725 /* No such UID - is this duplicate work? The sync process selects
3726 * UIDs to start with.
3729 camel_message_info_free (&mi->info);
3731 /* If we can get a stream, assume its fully cached. This may be false
3732 * if partial streams are saved elsewhere in the code - but that seems
3733 * best solved by knowning more about whether a given message is fully
3734 * available locally or not,
3736 /* If its cached in full, just get it as is, this is only a shortcut,
3737 * since we get stuff from the cache anyway. It affects a busted connection though. */
3738 if ((stream = camel_imap_folder_fetch_data(imap_folder, uid, "", TRUE, cancellable, NULL))) {
3739 g_object_unref (stream);
3742 msg = imap_get_message_sync (folder, uid, cancellable, error);
3744 g_object_unref (msg);
3751 /* We pretend that a FLAGS or RFC822.SIZE response is always exactly
3752 * 20 bytes long, and a BODY[HEADERS] response is always 2000 bytes
3753 * long. Since we know how many of each kind of response we're
3754 * expecting, we can find the total (pretend) amount of server traffic
3755 * to expect and then count off the responses as we read them to update
3758 #define IMAP_PRETEND_SIZEOF_FLAGS 20
3759 #define IMAP_PRETEND_SIZEOF_SIZE 20
3760 #define IMAP_PRETEND_SIZEOF_HEADERS 2000
3762 static const gchar *tm_months[] = {
3763 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
3764 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
3768 decode_time (const guchar **in,
3773 register const guchar *inptr;
3774 gint *val, colons = 0;
3776 *hour = *min = *sec = 0;
3779 for (inptr = *in; *inptr && !isspace ((gint) *inptr); inptr++) {
3780 if (*inptr == ':') {
3792 } else if (!isdigit ((gint) *inptr))
3795 *val = (*val * 10) + (*inptr - '0');
3804 decode_internaldate (const guchar *in)
3806 const guchar *inptr = in;
3807 gint hour, min, sec, n;
3812 memset ((gpointer) &tm, 0, sizeof (struct tm));
3814 tm.tm_mday = strtoul ((gchar *) inptr, (gchar **) &buf, 10);
3815 if (buf == inptr || *buf != '-')
3819 if (inptr[3] != '-')
3822 for (n = 0; n < 12; n++) {
3823 if (!g_ascii_strncasecmp ((gchar *) inptr, tm_months[n], 3))
3834 n = strtoul ((gchar *) inptr, (gchar **) &buf, 10);
3835 if (buf == inptr || *buf != ' ')
3838 tm.tm_year = n - 1900;
3841 if (!decode_time (&inptr, &hour, &min, &sec))
3848 n = strtol ((gchar *) inptr, NULL, 10);
3850 date = camel_mktime_utc (&tm);
3852 /* date is now GMT of the time we want, but not offset by the timezone ... */
3854 /* this should convert the time to the GMT equiv time */
3855 date -= ((n / 100) * 60 * 60) + (n % 100) * 60;
3861 add_message_from_data (CamelFolder *folder,
3862 GPtrArray *messages,
3865 GCancellable *cancellable)
3867 CamelMimeMessage *msg;
3868 CamelStream *stream;
3869 CamelImapMessageInfo *mi;
3871 const gchar *bodystructure;
3874 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
3877 stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
3881 if (seq - first >= messages->len)
3882 g_ptr_array_set_size (messages, seq - first + 1);
3884 msg = camel_mime_message_new ();
3885 if (!camel_data_wrapper_construct_from_stream_sync (
3886 CAMEL_DATA_WRAPPER (msg), stream, cancellable, NULL)) {
3887 g_object_unref (msg);
3891 bodystructure = g_datalist_get_data (&data, "BODY");
3893 mi = (CamelImapMessageInfo *)
3894 camel_folder_summary_info_new_from_message (
3895 folder->summary, msg, bodystructure);
3896 g_object_unref (msg);
3898 if ((idate = g_datalist_get_data (&data, "INTERNALDATE")))
3899 mi->info.date_received = decode_internaldate ((const guchar *) idate);
3901 if (mi->info.date_received == -1)
3902 mi->info.date_received = mi->info.date_sent;
3904 messages->pdata[seq - first] = mi;
3909 CamelMessageInfoBase *mi;
3913 construct_junk_headers (gchar *header,
3915 struct _junk_data *jdata)
3917 gchar *bs, *es, *flag = NULL;
3918 gchar *bdata = g_datalist_get_data (&(jdata->data), "BODY_PART_DATA");
3919 struct _camel_header_param *node;
3921 /* FIXME: This can be written in a much clever way.
3922 * We can create HEADERS file or carry all headers till filtering so
3923 * that header based filtering can be much faster. But all that later. */
3924 bs = camel_strstrcase (bdata ? bdata:"", header);
3926 bs += strlen (header);
3927 bs = strchr (bs, ':');
3932 es = strchr (bs, '\n');
3934 flag = g_strndup (bs, es - bs);
3942 node = g_new (struct _camel_header_param, 1);
3943 node->name = g_strdup (header);
3945 node->next = jdata->mi->headers;
3946 jdata->mi->headers = node;
3950 #define CAMEL_MESSAGE_INFO_HEADERS "DATE FROM TO CC SUBJECT REFERENCES IN-REPLY-TO MESSAGE-ID MIME-VERSION CONTENT-TYPE CONTENT-CLASS X-CALENDAR-ATTACHMENT "
3952 /* FIXME: this needs to be kept in sync with camel-mime-utils.c's list
3953 * of mailing-list headers and so might be best if this were
3954 * auto-generated? */
3955 #define MAILING_LIST_HEADERS "X-MAILING-LIST X-LOOP LIST-ID LIST-POST MAILING-LIST ORIGINATOR X-LIST SENDER RETURN-PATH X-BEENTHERE "
3958 imap_update_summary (CamelFolder *folder,
3960 CamelFolderChangeInfo *changes,
3961 GCancellable *cancellable,
3964 CamelStore *parent_store;
3965 CamelService *service;
3966 CamelSettings *settings;
3967 CamelImapStore *store;
3968 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3969 GPtrArray *fetch_data = NULL, *messages = NULL, *needheaders;
3970 CamelFetchHeadersType fetch_headers;
3971 gchar **extra_headers;
3972 guint32 flags, uidval;
3973 gint i, seq, first, size, got;
3974 CamelImapResponseType type;
3975 GString *header_spec = NULL;
3976 CamelImapMessageInfo *mi;
3977 CamelStream *stream;
3978 gchar *uid, *resp, *tempuid;
3982 parent_store = camel_folder_get_parent_store (folder);
3983 store = CAMEL_IMAP_STORE (parent_store);
3984 service = CAMEL_SERVICE (parent_store);
3985 settings = camel_service_get_settings (service);
3987 fetch_headers = camel_imap_settings_get_fetch_headers (
3988 CAMEL_IMAP_SETTINGS (settings));
3990 extra_headers = camel_imap_settings_dup_fetch_headers_extra (
3991 CAMEL_IMAP_SETTINGS (settings));
3993 if (store->server_level >= IMAP_LEVEL_IMAP4REV1) {
3994 if (fetch_headers == CAMEL_FETCH_HEADERS_ALL)
3995 header_spec = g_string_new ("HEADER");
3998 header_spec = g_string_new ("HEADER.FIELDS (");
3999 g_string_append (header_spec, CAMEL_MESSAGE_INFO_HEADERS);
4000 if (fetch_headers == CAMEL_FETCH_HEADERS_BASIC_AND_MAILING_LIST)
4001 g_string_append (header_spec, MAILING_LIST_HEADERS);
4002 if (extra_headers != NULL) {
4005 length = g_strv_length ((gchar **) extra_headers);
4006 for (ii = 0; ii < length; ii++) {
4007 g_string_append (header_spec, extra_headers[ii]);
4008 if (ii + 1 < length)
4009 g_string_append_c (header_spec, ' ');
4013 temp = g_string_free (header_spec, FALSE);
4014 temp = g_strstrip (temp);
4015 header_spec = g_string_new (temp);
4017 g_string_append (header_spec, ")");
4020 header_spec = g_string_new ("0");
4022 g_strfreev (extra_headers);
4024 d(printf("Header is : %s", header_spec->str));
4026 /* Figure out if any of the new messages are already cached (which
4027 * may be the case if we're re-syncing after disconnected operation).
4028 * If so, get their UIDs, FLAGS, and SIZEs. If not, get all that
4029 * and ask for the headers too at the same time.
4031 seq = camel_folder_summary_count (folder->summary);
4034 GPtrArray *known_uids;
4036 known_uids = camel_folder_summary_get_array (folder->summary);
4038 camel_folder_sort_uids (folder, known_uids);
4040 tempuid = g_ptr_array_index (known_uids, seq - 1);
4042 uidval = strtoul (tempuid, NULL, 10);
4046 camel_folder_summary_free_array (known_uids);
4053 if (!camel_imap_command_start (store, folder, cancellable, error,
4054 "UID FETCH %d:* (FLAGS RFC822.SIZE INTERNALDATE BODYSTRUCTURE BODY.PEEK[%s])",
4055 uidval + 1, header_spec->str)) {
4056 g_string_free (header_spec, TRUE);
4060 camel_operation_push_message (
4062 _("Fetching summary information for new messages in %s"),
4063 camel_folder_get_display_name (folder));
4065 /* Parse the responses. We can't add a message to the summary
4066 * until we've gotten its headers, and there's no guarantee
4067 * the server will send the responses in a useful order...
4069 fetch_data = g_ptr_array_new ();
4070 messages = g_ptr_array_new ();
4072 while ((type = camel_imap_command_response (store, folder, &resp, cancellable, error)) ==
4073 CAMEL_IMAP_RESPONSE_UNTAGGED && !camel_application_is_exiting) {
4074 data = parse_fetch_response (imap_folder, resp);
4080 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
4082 g_datalist_clear (&data);
4086 if (g_datalist_get_data (&data, "FLAGS"))
4087 got += IMAP_PRETEND_SIZEOF_FLAGS;
4088 if (g_datalist_get_data (&data, "RFC822.SIZE"))
4089 got += IMAP_PRETEND_SIZEOF_SIZE;
4090 stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
4092 got += IMAP_PRETEND_SIZEOF_HEADERS;
4094 /* Use the stream now so we don't tie up many
4095 * many fds if we're fetching many many messages.
4097 add_message_from_data (
4098 folder, messages, first, data, cancellable);
4099 g_datalist_set_data (&data, "BODY_PART_STREAM", NULL);
4102 camel_operation_progress (cancellable, k * 100 / ct);
4104 g_ptr_array_add (fetch_data, data);
4107 camel_operation_pop_message (cancellable);
4109 if (type == CAMEL_IMAP_RESPONSE_ERROR || camel_application_is_exiting) {
4110 if (type != CAMEL_IMAP_RESPONSE_ERROR && type != CAMEL_IMAP_RESPONSE_TAGGED)
4111 camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
4113 g_string_free (header_spec, TRUE);
4117 /* Free the final tagged response */
4120 /* Figure out which headers we still need to fetch. */
4121 needheaders = g_ptr_array_new ();
4123 for (i = 0; i < fetch_data->len; i++) {
4124 data = fetch_data->pdata[i];
4125 if (g_datalist_get_data (&data, "BODY_PART_LEN"))
4128 uid = g_datalist_get_data (&data, "UID");
4130 g_ptr_array_add (needheaders, uid);
4131 size += IMAP_PRETEND_SIZEOF_HEADERS;
4135 /* And fetch them */
4136 if (needheaders->len) {
4140 qsort (needheaders->pdata, needheaders->len,
4141 sizeof (gpointer), uid_compar);
4143 camel_operation_push_message (
4145 _("Fetching summary information for new messages in %s"),
4146 camel_folder_get_display_name (folder));
4148 while (uid < needheaders->len && !camel_application_is_exiting) {
4149 uidset = imap_uid_array_to_set (folder->summary, needheaders, uid, UID_SET_LIMIT, &uid);
4150 if (!camel_imap_command_start (store, folder, cancellable, error,
4151 "UID FETCH %s BODYSTRUCTURE BODY.PEEK[%s]",
4152 uidset, header_spec->str)) {
4153 g_ptr_array_free (needheaders, TRUE);
4154 camel_operation_pop_message (cancellable);
4156 g_string_free (header_spec, TRUE);
4161 while ((type = camel_imap_command_response (store, folder, &resp, cancellable, error))
4162 == CAMEL_IMAP_RESPONSE_UNTAGGED && !camel_application_is_exiting) {
4163 data = parse_fetch_response (imap_folder, resp);
4168 stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
4170 add_message_from_data (
4171 folder, messages, first,
4173 got += IMAP_PRETEND_SIZEOF_HEADERS;
4174 camel_operation_progress (
4175 cancellable, got * 100 / size);
4177 g_datalist_clear (&data);
4180 if (type == CAMEL_IMAP_RESPONSE_ERROR || camel_application_is_exiting) {
4181 g_ptr_array_free (needheaders, TRUE);
4182 g_string_free (header_spec, TRUE);
4183 camel_operation_pop_message (cancellable);
4185 if (type != CAMEL_IMAP_RESPONSE_ERROR && type != CAMEL_IMAP_RESPONSE_TAGGED)
4186 camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
4191 camel_operation_pop_message (cancellable);
4194 g_ptr_array_free (needheaders, TRUE);
4195 g_string_free (header_spec, TRUE);
4197 /* Now finish up summary entries (fix UIDs, set flags and size) */
4198 for (i = 0; i < fetch_data->len; i++) {
4199 struct _junk_data jdata;
4200 data = fetch_data->pdata[i];
4202 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
4203 if (seq >= first + messages->len) {
4204 g_datalist_clear (&data);
4208 mi = messages->pdata[seq - first];
4210 CamelMessageInfo *pmi = NULL;
4213 /* This is a kludge around a bug in Exchange
4214 * 5.5 that sometimes claims multiple messages
4215 * have the same UID. See bug #17694 for
4216 * details. The "solution" is to create a fake
4217 * message-info with the same details as the
4218 * previously valid message. Yes, the user
4219 * will have a clone in his/her message-list,
4220 * but at least we don't crash.
4223 /* find the previous valid message info */
4224 for (j = seq - first - 1; j >= 0; j--) {
4225 pmi = messages->pdata[j];
4234 mi = (CamelImapMessageInfo *) camel_message_info_clone (pmi);
4237 uid = g_datalist_get_data (&data, "UID");
4239 mi->info.uid = camel_pstring_strdup (uid);
4240 flags = GPOINTER_TO_INT (g_datalist_get_data (&data, "FLAGS"));
4242 gchar *custom_flags = NULL;
4244 ((CamelImapMessageInfo *) mi)->server_flags = flags;
4245 /* "or" them in with the existing flags that may
4246 * have been set by summary_info_new_from_message.
4248 mi->info.flags |= flags;
4250 custom_flags = g_datalist_get_data (&data, "CUSTOM.FLAGS");
4252 fillup_custom_flags ((CamelMessageInfo *) mi, custom_flags);
4254 size = GPOINTER_TO_INT (g_datalist_get_data (&data, "RFC822.SIZE"));
4256 mi->info.size = size;
4258 /* Just do this to build the junk required headers to be built*/
4260 jdata.mi = (CamelMessageInfoBase *) mi;
4261 g_hash_table_foreach ((GHashTable *) camel_session_get_junk_headers (camel_service_get_session (service)), (GHFunc) construct_junk_headers, &jdata);
4262 g_datalist_clear (&data);
4264 g_ptr_array_free (fetch_data, TRUE);
4266 if (camel_application_is_exiting) {
4267 /* it will hopefully update summary next time */
4272 /* And add the entries to the summary, etc. */
4273 for (i = 0; i < messages->len; i++) {
4274 mi = messages->pdata[i];
4276 g_warning ("No information for message %d", i + first);
4278 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
4279 _("Incomplete server response: "
4280 "no information provided for message %d"),
4284 uid = (gchar *) camel_message_info_uid (mi);
4286 g_warning("Server provided no uid: message %d", i + first);
4288 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
4289 _("Incomplete server response: "
4290 "no UID provided for message %d"),
4295 /* FIXME: If it enters if (info) it will always match the exception. So stupid */
4296 /* FIXME[disk-summary] Use a db query to see if the DB exists */
4297 /* info = (CamelImapMessageInfo *)camel_folder_summary_get (folder->summary, uid); */
4299 /* for (seq = 0; seq < camel_folder_summary_count (folder->summary); seq++) { */
4300 /* if (folder->summary->messages->pdata[seq] == info) */
4304 ((CamelMessageInfoBase *) mi)->dirty = TRUE;
4305 if (((CamelMessageInfoBase *) mi)->summary)
4306 camel_folder_summary_touch (((CamelMessageInfoBase *) mi)->summary);
4307 camel_folder_summary_add (folder->summary, (CamelMessageInfo *) mi);
4308 camel_folder_change_info_add_uid (changes, camel_message_info_uid (mi));
4310 /* Report all new messages as recent, even without that flag, thus new
4311 * messages will be filtered even after saw by other software earlier.
4312 * Only skip those which we added ourself, like after drag&drop to this folder. */
4313 if (!imap_folder_uid_in_ignore_recent (imap_folder, camel_message_info_uid (mi))
4314 && ((mi->info.flags & CAMEL_IMAP_MESSAGE_RECENT) != 0 || getenv ("FILTER_RECENT") == NULL))
4315 camel_folder_change_info_recent_uid (changes, camel_message_info_uid (mi));
4319 g_ptr_array_free (messages, TRUE);
4321 if (imap_folder->priv->ignore_recent) {
4322 g_hash_table_unref (imap_folder->priv->ignore_recent);
4323 imap_folder->priv->ignore_recent = NULL;
4330 for (i = 0; i < fetch_data->len; i++) {
4331 data = fetch_data->pdata[i];
4332 g_datalist_clear (&data);
4334 g_ptr_array_free (fetch_data, TRUE);
4337 for (i = 0; i < messages->len; i++) {
4338 if (messages->pdata[i])
4339 camel_message_info_free (messages->pdata[i]);
4341 g_ptr_array_free (messages, TRUE);
4344 if (imap_folder->priv->ignore_recent) {
4345 g_hash_table_unref (imap_folder->priv->ignore_recent);
4346 imap_folder->priv->ignore_recent = NULL;
4352 /* Called with the store's connect_lock locked */
4354 camel_imap_folder_changed (CamelFolder *folder,
4357 GCancellable *cancellable,
4360 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
4361 CamelFolderChangeInfo *changes;
4363 gboolean success = TRUE;
4365 changes = camel_folder_change_info_new ();
4367 CamelStore *parent_store;
4369 GList *deleted = NULL;
4370 const gchar *full_name;
4372 GPtrArray *known_uids;
4374 known_uids = camel_folder_summary_get_array (folder->summary);
4375 camel_folder_sort_uids (folder, known_uids);
4376 for (i = 0; i < expunged->len; i++) {
4377 id = g_array_index (expunged, int, i);
4378 uid = id - 1 + i >= 0 && id - 1 + i < known_uids->len ? g_ptr_array_index (known_uids, id - 1 + i) : NULL;
4380 /* FIXME: danw: does this mean that the summary is corrupt? */
4381 /* I guess a message that we never retrieved got expunged? */
4385 deleted = g_list_prepend (deleted, (gpointer) uid);
4386 camel_folder_change_info_remove_uid (changes, uid);
4387 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
4388 camel_imap_message_cache_remove (imap_folder->cache, uid);
4389 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
4390 camel_folder_summary_remove_uid (folder->summary, uid);
4393 /* Delete all in one transaction */
4394 full_name = camel_folder_get_full_name (folder);
4395 parent_store = camel_folder_get_parent_store (folder);
4396 camel_db_delete_uids (parent_store->cdb_w, full_name, deleted, NULL);
4397 g_list_free (deleted);
4399 camel_folder_summary_free_array (known_uids);
4402 len = camel_folder_summary_count (folder->summary);
4403 if (exists > len && !camel_application_is_exiting)
4404 success = imap_update_summary (
4405 folder, exists, changes, cancellable, error);
4407 camel_folder_summary_save_to_db (folder->summary, NULL);
4408 if (camel_folder_change_info_changed (changes))
4409 camel_folder_changed (folder, changes);
4411 camel_folder_change_info_free (changes);
4417 imap_thaw (CamelFolder *folder)
4419 CamelImapFolder *imap_folder;
4421 CAMEL_FOLDER_CLASS (camel_imap_folder_parent_class)->thaw (folder);
4422 if (camel_folder_is_frozen (folder))
4425 /* FIXME imap_refresh_info_sync() may block, but camel_folder_thaw()
4426 * is not supposed to block. Potential hang here. */
4427 imap_folder = CAMEL_IMAP_FOLDER (folder);
4428 if (imap_folder->need_refresh) {
4429 imap_folder->need_refresh = FALSE;
4430 imap_refresh_info_sync (folder, NULL, NULL);
4435 camel_imap_folder_fetch_data (CamelImapFolder *imap_folder,
4437 const gchar *section_text,
4438 gboolean cache_only,
4439 GCancellable *cancellable,
4442 CamelFolder *folder = CAMEL_FOLDER (imap_folder);
4443 CamelStore *parent_store;
4444 CamelImapStore *store;
4445 CamelImapResponse *response;
4446 CamelStream *stream;
4451 parent_store = camel_folder_get_parent_store (folder);
4452 store = CAMEL_IMAP_STORE (parent_store);
4454 /* EXPUNGE responses have to modify the cache, which means
4455 * they have to grab the cache_lock while holding the
4458 * Because getting the service lock may cause MUCH unecessary
4459 * delay when we already have the data locally, we do the
4460 * locking separately. This could cause a race
4461 * getting the same data from the cache, but that is only
4462 * an inefficiency, and bad luck.
4464 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
4465 stream = camel_imap_message_cache_get (imap_folder->cache, uid, section_text, NULL);
4466 if (!stream && (!strcmp (section_text, "HEADER") || !strcmp (section_text, "0"))) {
4467 stream = camel_imap_message_cache_get (imap_folder->cache, uid, "", NULL);
4469 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
4471 if (stream || cache_only)
4474 camel_service_lock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
4475 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
4477 if (!camel_imap_store_connected (store, NULL)) {
4480 CAMEL_SERVICE_ERROR_UNAVAILABLE,
4481 _("This message is not currently available"));
4482 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
4483 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
4487 if (store->server_level < IMAP_LEVEL_IMAP4REV1 && !*section_text) {
4488 response = camel_imap_command (
4489 store, folder, cancellable, error,
4490 "UID FETCH %s RFC822.PEEK", uid);
4492 response = camel_imap_command (
4493 store, folder, cancellable, error,
4494 "UID FETCH %s BODY.PEEK[%s]", uid,
4499 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
4500 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
4504 for (i = 0; i < response->untagged->len; i++) {
4505 fetch_data = parse_fetch_response (imap_folder, response->untagged->pdata[i]);
4506 found_uid = g_datalist_get_data (&fetch_data, "UID");
4507 stream = g_datalist_get_data (&fetch_data, "BODY_PART_STREAM");
4508 if (found_uid && stream && !strcmp (uid, found_uid))
4511 g_datalist_clear (&fetch_data);
4514 camel_imap_response_free (store, response);
4515 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
4516 camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
4520 CAMEL_SERVICE_ERROR_UNAVAILABLE,
4521 _("Could not find message body in FETCH response."));
4523 g_object_ref (stream);
4524 g_datalist_clear (&fetch_data);
4531 parse_fetch_response (CamelImapFolder *imap_folder,
4535 gchar *start, *part_spec = NULL, *body = NULL, *uid = NULL, *idate = NULL;
4536 gboolean cache_header = TRUE, header = FALSE;
4539 if (*response != '(') {
4542 if (*response != '*' || *(response + 1) != ' ')
4544 seq = strtoul (response + 2, &response, 10);
4547 if (g_ascii_strncasecmp (response, " FETCH (", 8) != 0)
4551 g_datalist_set_data (&data, "SEQUENCE", GINT_TO_POINTER (seq));
4555 /* Skip the initial '(' or the ' ' between elements */
4558 if (!g_ascii_strncasecmp (response, "FLAGS ", 6)) {
4559 CamelMessageFlags flags;
4560 gchar *custom_flags = NULL;
4564 if (imap_parse_flag_list (&response, &flags, &custom_flags)) {
4565 g_datalist_set_data (&data, "FLAGS", GUINT_TO_POINTER (flags));
4568 g_datalist_set_data_full (&data, "CUSTOM.FLAGS", custom_flags, g_free);
4570 } else if (!g_ascii_strncasecmp (response, "RFC822.SIZE ", 12)) {
4574 size = strtoul (response, &response, 10);
4575 g_datalist_set_data (&data, "RFC822.SIZE", GUINT_TO_POINTER (size));
4576 } else if (!g_ascii_strncasecmp (response, "BODY[", 5) ||
4577 !g_ascii_strncasecmp (response, "RFC822 ", 7)) {
4580 if (*response == 'B') {
4583 /* HEADER], HEADER.FIELDS (...)], or 0] */
4584 if (!g_ascii_strncasecmp (response, "HEADER", 6)) {
4586 if (!g_ascii_strncasecmp (response + 6, ".FIELDS", 7))
4587 cache_header = FALSE;
4588 } else if (!g_ascii_strncasecmp (response, "0]", 2))
4591 p = strchr (response, ']');
4592 if (!p || *(p + 1) != ' ')
4596 part_spec = g_strndup (response, p - response);
4598 part_spec = g_strdup ("HEADER.FIELDS");
4602 part_spec = g_strdup ("");
4605 if (!g_ascii_strncasecmp (response, "HEADER", 6))
4609 body = imap_parse_nstring ((const gchar **) &response, &body_len);
4616 body = g_strdup ("");
4617 g_datalist_set_data_full (&data, "BODY_PART_SPEC", part_spec, g_free);
4618 g_datalist_set_data_full (&data, "BODY_PART_DATA", body, g_free);
4619 g_datalist_set_data (&data, "BODY_PART_LEN", GINT_TO_POINTER (body_len));
4620 } else if (!g_ascii_strncasecmp (response, "BODY ", 5) ||
4621 !g_ascii_strncasecmp (response, "BODYSTRUCTURE ", 14)) {
4622 response = strchr (response, ' ') + 1;
4624 imap_skip_list ((const gchar **) &response);
4625 if (response && (response != start)) {
4626 /* To handle IMAP Server brokenness, Returning empty body, etc. See #355640 */
4627 g_datalist_set_data_full (&data, "BODY", g_strndup (start, response - start), g_free);
4629 } else if (!g_ascii_strncasecmp (response, "UID ", 4)) {
4632 len = strcspn (response + 4, " )");
4633 uid = g_strndup (response + 4, len);
4634 g_datalist_set_data_full (&data, "UID", uid, g_free);
4635 response += 4 + len;
4636 } else if (!g_ascii_strncasecmp (response, "INTERNALDATE ", 13)) {
4640 if (*response == '"') {
4642 len = strcspn (response, "\"");
4643 idate = g_strndup (response, len);
4644 g_datalist_set_data_full (&data, "INTERNALDATE", idate, g_free);
4645 response += len + 1;
4648 g_warning ("Unexpected FETCH response from server: (%s", response);
4651 } while (response && *response != ')');
4653 if (!response || *response != ')') {
4654 g_datalist_clear (&data);
4659 CamelStream *stream;
4661 if (header && !cache_header) {
4662 stream = camel_stream_mem_new_with_buffer (body, body_len);
4664 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
4665 stream = camel_imap_message_cache_insert (imap_folder->cache,
4667 body, body_len, NULL, NULL);
4668 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
4670 stream = camel_stream_mem_new_with_buffer (body, body_len);
4674 g_datalist_set_data_full (&data, "BODY_PART_STREAM", stream,
4675 (GDestroyNotify) g_object_unref);
4681 /* it uses connect_lock, thus be sure it doesn't run in main thread */
4682 static CamelFolderQuotaInfo *
4683 imap_get_quota_info_sync (CamelFolder *folder,
4684 GCancellable *cancellable,
4687 CamelStore *parent_store;
4688 CamelImapStore *imap_store;
4689 CamelImapResponse *response;
4690 CamelFolderQuotaInfo *res = NULL, *last = NULL;
4692 parent_store = camel_folder_get_parent_store (folder);
4693 imap_store = CAMEL_IMAP_STORE (parent_store);
4695 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (imap_store)))
4698 camel_service_lock (CAMEL_SERVICE (imap_store), CAMEL_SERVICE_REC_CONNECT_LOCK);
4700 if (!camel_imap_store_connected (imap_store, NULL))
4703 if (imap_store->capabilities & IMAP_CAPABILITY_QUOTA) {
4704 const gchar *full_name = camel_folder_get_full_name (folder);
4705 CamelImapStoreNamespace *ns = camel_imap_store_summary_namespace_find_full (imap_store->summary, full_name);
4706 gchar *folder_name = camel_imap_store_summary_path_to_full (imap_store->summary, full_name, ns ? ns->sep : '/');
4708 response = camel_imap_command (imap_store, NULL, cancellable, error, "GETQUOTAROOT \"%s\"", folder_name);
4713 for (i = 0; i < response->untagged->len; i++) {
4714 const gchar *resp = response->untagged->pdata[i];
4716 if (resp && g_str_has_prefix (resp, "* QUOTA ")) {
4717 gboolean skipped = TRUE;
4722 astr = imap_parse_astring (&resp, &sz);
4725 while (resp && *resp && *resp != '(')
4728 if (resp && *resp == '(') {
4730 const gchar *used = NULL, *total = NULL;
4733 name = imap_parse_astring (&resp, &sz);
4736 used = imap_next_word (resp);
4738 total = imap_next_word (used);
4740 while (resp && *resp && *resp != ')')
4743 if (resp && *resp == ')' && used && total) {
4746 u = strtoull (used, NULL, 10);
4747 t = strtoull (total, NULL, 10);
4750 CamelFolderQuotaInfo *info = camel_folder_quota_info_new (name, u, t);
4766 g_debug ("Unexpected quota response '%s'; skipping it...", (const gchar *)response->untagged->pdata[i]);
4769 camel_imap_response_free (imap_store, response);
4772 g_free (folder_name);
4775 camel_service_unlock (CAMEL_SERVICE (imap_store), CAMEL_SERVICE_REC_CONNECT_LOCK);
4780 * Scan for messages that are local and return the rest.
4783 imap_get_uncached_uids (CamelFolder *folder,
4788 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
4790 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
4792 result = camel_imap_message_cache_filter_cached (
4793 imap_folder->cache, uids, error);
4795 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);