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);
298 settings = camel_service_ref_settings (service);
300 network_settings = CAMEL_NETWORK_SETTINGS (settings);
301 host = camel_network_settings_dup_host (network_settings);
302 user = camel_network_settings_dup_user (network_settings);
304 g_object_unref (settings);
306 description = g_strdup_printf (
307 "%s@%s:%s", user, host, full_name);
308 camel_folder_set_description (folder, description);
309 g_free (description);
316 camel_imap_folder_class_init (CamelImapFolderClass *class)
318 GObjectClass *object_class;
319 CamelFolderClass *folder_class;
321 g_type_class_add_private (class, sizeof (CamelImapFolderPrivate));
323 object_class = G_OBJECT_CLASS (class);
324 object_class->set_property = imap_folder_set_property;
325 object_class->get_property = imap_folder_get_property;
326 object_class->dispose = imap_folder_dispose;
327 object_class->finalize = imap_folder_finalize;
328 object_class->constructed = imap_folder_constructed;
330 folder_class = CAMEL_FOLDER_CLASS (class);
331 folder_class->rename = imap_rename;
332 folder_class->search_by_expression = imap_search_by_expression;
333 folder_class->count_by_expression = imap_count_by_expression;
334 folder_class->search_by_uids = imap_search_by_uids;
335 folder_class->search_free = imap_search_free;
336 folder_class->thaw = imap_thaw;
337 folder_class->get_uncached_uids = imap_get_uncached_uids;
338 folder_class->get_filename = imap_get_filename;
339 folder_class->append_message_sync = imap_append_online;
340 folder_class->expunge_sync = imap_expunge_sync;
341 folder_class->get_message_sync = imap_get_message_sync;
342 folder_class->get_quota_info_sync = imap_get_quota_info_sync;
343 folder_class->refresh_info_sync = imap_refresh_info_sync;
344 folder_class->synchronize_sync = imap_synchronize_sync;
345 folder_class->synchronize_message_sync = imap_synchronize_message_sync;
346 folder_class->transfer_messages_to_sync = imap_transfer_online;
348 g_object_class_install_property (
351 g_param_spec_boolean (
354 _("Always check for _new mail in this folder"),
357 CAMEL_PARAM_PERSISTENT));
359 g_object_class_install_property (
362 g_param_spec_boolean (
365 _("Apply message _filters to this folder"),
368 CAMEL_PARAM_PERSISTENT));
372 camel_imap_folder_init (CamelImapFolder *imap_folder)
374 CamelFolder *folder = CAMEL_FOLDER (imap_folder);
376 imap_folder->priv = CAMEL_IMAP_FOLDER_GET_PRIVATE (imap_folder);
378 folder->permanent_flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_DELETED |
379 CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN;
381 folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY;
383 g_static_mutex_init (&imap_folder->priv->search_lock);
384 g_static_rec_mutex_init (&imap_folder->priv->cache_lock);
385 imap_folder->priv->ignore_recent = NULL;
387 imap_folder->journal = NULL;
388 imap_folder->need_rescan = TRUE;
392 replay_offline_journal (CamelImapStore *imap_store,
393 CamelImapFolder *imap_folder,
394 GCancellable *cancellable,
397 CamelIMAPJournal *imap_journal;
399 g_return_if_fail (imap_store != NULL);
400 g_return_if_fail (imap_folder != NULL);
401 g_return_if_fail (imap_folder->journal != NULL);
403 imap_journal = CAMEL_IMAP_JOURNAL (imap_folder->journal);
404 g_return_if_fail (imap_journal != NULL);
406 /* do not replay when still in offline */
407 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (imap_store)) || !camel_imap_store_connected (imap_store, error))
410 /* Check if the replay is already in progress as imap_sync would be called while expunge resync */
411 if (!imap_journal->rp_in_progress) {
412 imap_journal->rp_in_progress++;
414 camel_offline_journal_replay (
415 imap_folder->journal, cancellable, error);
416 camel_imap_journal_close_folders (imap_journal);
417 camel_offline_journal_write (imap_folder->journal, error);
419 imap_journal->rp_in_progress--;
420 g_return_if_fail (imap_journal->rp_in_progress >= 0);
425 camel_imap_folder_new (CamelStore *parent,
426 const gchar *folder_name,
427 const gchar *folder_dir,
431 CamelImapFolder *imap_folder;
432 const gchar *short_name;
433 gchar *state_file, *path;
434 CamelService *service;
435 CamelSettings *settings;
437 gboolean filter_inbox;
438 gboolean filter_junk;
439 gboolean filter_junk_inbox;
441 if (g_mkdir_with_parents (folder_dir, S_IRWXU) != 0) {
444 g_io_error_from_errno (errno),
445 _("Could not create directory %s: %s"),
446 folder_dir, g_strerror (errno));
450 short_name = strrchr (folder_name, '/');
454 short_name = folder_name;
455 folder = g_object_new (
456 CAMEL_TYPE_IMAP_FOLDER,
457 "full-name", folder_name,
458 "display-name", short_name,
459 "parent-store", parent, NULL);
461 folder->summary = camel_imap_summary_new (folder);
462 if (!folder->summary) {
463 g_object_unref (folder);
465 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
466 _("Could not load summary for %s"), folder_name);
470 imap_folder = CAMEL_IMAP_FOLDER (folder);
471 path = g_build_filename (folder_dir, "journal", NULL);
472 imap_folder->journal = camel_imap_journal_new (imap_folder, path);
475 /* set/load persistent state */
476 state_file = g_build_filename (folder_dir, "cmeta", NULL);
477 camel_object_set_state_filename (CAMEL_OBJECT (folder), state_file);
479 camel_object_state_read (CAMEL_OBJECT (folder));
481 imap_folder->cache = camel_imap_message_cache_new (folder_dir, folder->summary, error);
482 if (!imap_folder->cache) {
483 g_object_unref (folder);
487 service = CAMEL_SERVICE (parent);
488 settings = camel_service_ref_settings (service);
492 "filter-all", &filter_all,
493 "filter-inbox", &filter_inbox,
494 "filter-junk", &filter_junk,
495 "filter-junk-inbox", &filter_junk_inbox,
498 if (g_ascii_strcasecmp (folder_name, "INBOX") == 0) {
499 if (filter_inbox || filter_all)
500 folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
502 folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
503 if (filter_junk_inbox)
504 folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
506 gboolean folder_is_trash, folder_is_junk;
510 junk_path = camel_imap_settings_dup_real_junk_path (
511 CAMEL_IMAP_SETTINGS (settings));
513 /* So we can safely compare strings. */
514 if (junk_path == NULL)
515 junk_path = g_strdup ("");
517 trash_path = camel_imap_settings_dup_real_trash_path (
518 CAMEL_IMAP_SETTINGS (settings));
520 /* So we can safely compare strings. */
521 if (trash_path == NULL)
522 trash_path = g_strdup ("");
524 if (filter_junk && !filter_junk_inbox)
525 folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
528 (parent->flags & CAMEL_STORE_VTRASH) == 0 &&
529 g_ascii_strcasecmp (trash_path, folder_name) == 0;
532 folder->folder_flags |= CAMEL_FOLDER_IS_TRASH;
535 (parent->flags & CAMEL_STORE_VJUNK) == 0 &&
536 g_ascii_strcasecmp (junk_path, folder_name) == 0;
539 folder->folder_flags |= CAMEL_FOLDER_IS_JUNK;
541 if (filter_all || imap_folder_get_apply_filters (imap_folder))
542 folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
548 g_object_unref (settings);
550 imap_folder->search = camel_imap_search_new (folder_dir);
552 camel_store_summary_connect_folder_summary (
553 (CamelStoreSummary *) ((CamelImapStore *) parent)->summary,
554 folder_name, folder->summary);
560 camel_imap_folder_get_check_folder (CamelImapFolder *imap_folder)
562 g_return_val_if_fail (CAMEL_IS_IMAP_FOLDER (imap_folder), FALSE);
564 return imap_folder->priv->check_folder;
568 camel_imap_folder_set_check_folder (CamelImapFolder *imap_folder,
569 gboolean check_folder)
572 CamelStore *parent_store;
573 const gchar *full_name;
575 g_return_if_fail (CAMEL_IS_IMAP_FOLDER (imap_folder));
577 if ((imap_folder->priv->check_folder ? 1 : 0) == (check_folder ? 1 : 0))
580 imap_folder->priv->check_folder = check_folder;
582 folder = CAMEL_FOLDER (imap_folder);
583 full_name = camel_folder_get_full_name (folder);
584 parent_store = camel_folder_get_parent_store (folder);
586 /* Update the summary so the value is restored
587 * correctly the next time the folder is loaded. */
588 if (CAMEL_IS_IMAP_STORE (parent_store)) {
589 CamelImapStore *imap_store;
590 CamelStoreSummary *summary;
593 imap_store = CAMEL_IMAP_STORE (parent_store);
594 summary = CAMEL_STORE_SUMMARY (imap_store->summary);
596 si = camel_store_summary_path (summary, full_name);
598 guint32 old_flags = si->flags;
600 si->flags &= ~CAMEL_STORE_INFO_FOLDER_CHECK_FOR_NEW;
601 si->flags |= check_folder ? CAMEL_STORE_INFO_FOLDER_CHECK_FOR_NEW : 0;
603 if (si->flags != old_flags) {
604 camel_store_summary_touch (summary);
605 camel_store_summary_save (summary);
608 camel_store_summary_info_free (summary, si);
612 g_object_notify (G_OBJECT (imap_folder), "check-folder");
615 /* Called with the store's connect_lock locked */
617 camel_imap_folder_selected (CamelFolder *folder,
618 CamelImapResponse *response,
619 GCancellable *cancellable,
622 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
623 CamelImapSummary *imap_summary = CAMEL_IMAP_SUMMARY (folder->summary);
624 gulong exists = 0, validity = 0, val, uid;
625 CamelMessageFlags perm_flags = 0;
630 count = camel_folder_summary_count (folder->summary);
632 for (i = 0; i < response->untagged->len; i++) {
633 resp = (gchar *) response->untagged->pdata[i] + 2;
635 if (!g_ascii_strncasecmp (resp, "FLAGS ", 6) && !perm_flags) {
637 imap_parse_flag_list (&resp, &folder->permanent_flags, NULL);
638 } else if (!g_ascii_strncasecmp (resp, "OK [PERMANENTFLAGS ", 19)) {
641 /* workaround for broken IMAP servers that send
642 * "* OK [PERMANENTFLAGS ()] Permanent flags"
643 * even tho they do allow storing flags. */
644 imap_parse_flag_list (&resp, &perm_flags, NULL);
646 folder->permanent_flags = perm_flags;
647 } else if (!g_ascii_strncasecmp (resp, "OK [UIDVALIDITY ", 16)) {
648 validity = strtoul (resp + 16, NULL, 10);
649 } else if (isdigit ((guchar) * resp)) {
650 gulong num = strtoul (resp, &resp, 10);
652 if (!g_ascii_strncasecmp (resp, " EXISTS", 7)) {
654 /* Remove from the response so nothing
655 * else tries to interpret it.
657 g_free (response->untagged->pdata[i]);
658 g_ptr_array_remove_index (response->untagged, i--);
663 if (camel_strstrcase (response->status, "OK [READ-ONLY]"))
664 imap_folder->read_only = TRUE;
666 if (!imap_summary->validity)
667 imap_summary->validity = validity;
668 else if (validity != imap_summary->validity) {
669 imap_summary->validity = validity;
670 camel_folder_summary_clear (folder->summary, NULL);
671 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
672 camel_imap_message_cache_clear (imap_folder->cache);
673 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
674 imap_folder->need_rescan = FALSE;
675 return camel_imap_folder_changed (
676 folder, exists, NULL, cancellable, error);
679 /* If we've lost messages, we have to rescan everything */
681 imap_folder->need_rescan = TRUE;
682 else if (count != 0 && !imap_folder->need_rescan) {
683 CamelStore *parent_store;
684 CamelImapStore *store;
685 GPtrArray *known_uids;
686 const gchar *old_uid;
688 parent_store = camel_folder_get_parent_store (folder);
689 store = CAMEL_IMAP_STORE (parent_store);
691 /* Similarly, if the UID of the highest message we
692 * know about has changed, then that indicates that
693 * messages have been both added and removed, so we
694 * have to rescan to find the removed ones. (We pass
695 * NULL for the folder since we know that this folder
696 * is selected, and we don't want camel_imap_command
697 * to worry about it.)
699 response = camel_imap_command (store, NULL, cancellable, error, "FETCH %d UID", count);
703 for (i = 0; i < response->untagged->len; i++) {
704 resp = response->untagged->pdata[i];
705 val = strtoul (resp + 2, &resp, 10);
708 if (!g_ascii_strcasecmp (resp, " EXISTS")) {
713 if (uid != 0 || val != count || g_ascii_strncasecmp (resp, " FETCH (", 8) != 0)
716 fetch_data = parse_fetch_response (imap_folder, resp + 7);
717 uid = strtoul (g_datalist_get_data (&fetch_data, "UID"), NULL, 10);
718 g_datalist_clear (&fetch_data);
720 camel_imap_response_free_without_processing (store, response);
722 known_uids = camel_folder_summary_get_array (folder->summary);
723 camel_folder_sort_uids (folder, known_uids);
725 if (known_uids && count - 1 >= 0 && count - 1 < known_uids->len)
726 old_uid = g_ptr_array_index (known_uids, count - 1);
728 val = strtoul (old_uid, NULL, 10);
729 if (uid == 0 || uid != val)
730 imap_folder->need_rescan = TRUE;
732 camel_folder_summary_free_array (known_uids);
735 /* Now rescan if we need to */
736 if (imap_folder->need_rescan)
737 return imap_rescan (folder, exists, cancellable, error);
739 /* If we don't need to rescan completely, but new messages
740 * have been added, find out about them.
743 camel_imap_folder_changed (
744 folder, exists, NULL, cancellable, error);
746 /* And we're done. */
752 imap_get_filename (CamelFolder *folder,
756 CamelImapFolder *imap_folder = (CamelImapFolder *) folder;
758 return camel_imap_message_cache_get_filename (imap_folder->cache, uid, "", error);
762 imap_rename (CamelFolder *folder,
765 CamelService *service;
766 CamelStore *parent_store;
767 CamelImapFolder *imap_folder = (CamelImapFolder *) folder;
768 const gchar *user_cache_dir;
769 gchar *folder_dir, *state_file;
772 parent_store = camel_folder_get_parent_store (folder);
774 service = CAMEL_SERVICE (parent_store);
775 user_cache_dir = camel_service_get_user_cache_dir (service);
777 folders = g_build_filename (user_cache_dir, "folders", NULL);
778 folder_dir = imap_path_to_physical (folders, new);
781 CAMEL_IMAP_FOLDER_REC_LOCK (folder, cache_lock);
782 camel_imap_message_cache_set_path (imap_folder->cache, folder_dir);
783 CAMEL_IMAP_FOLDER_REC_UNLOCK (folder, cache_lock);
785 state_file = g_build_filename (folder_dir, "cmeta", NULL);
786 camel_object_set_state_filename (CAMEL_OBJECT (folder), state_file);
791 camel_store_summary_disconnect_folder_summary (
792 (CamelStoreSummary *) ((CamelImapStore *) parent_store)->summary,
795 CAMEL_FOLDER_CLASS (camel_imap_folder_parent_class)->rename (folder, new);
797 camel_store_summary_connect_folder_summary (
798 (CamelStoreSummary *) ((CamelImapStore *) parent_store)->summary,
799 camel_folder_get_full_name (folder), folder->summary);
802 /* called with connect_lock locked */
804 get_folder_status (CamelFolder *folder,
807 GCancellable *cancellable,
810 CamelStore *parent_store;
811 CamelImapStore *imap_store;
812 CamelImapResponse *response;
813 const gchar *full_name;
814 gboolean res = FALSE;
816 g_return_val_if_fail (folder != NULL, FALSE);
818 full_name = camel_folder_get_full_name (folder);
819 parent_store = camel_folder_get_parent_store (folder);
821 imap_store = CAMEL_IMAP_STORE (parent_store);
823 response = camel_imap_command (imap_store, folder, cancellable, error, "STATUS %F (MESSAGES UNSEEN)", full_name);
828 for (i = 0; i < response->untagged->len; i++) {
829 const gchar *resp = response->untagged->pdata[i];
831 if (resp && g_str_has_prefix (resp, "* STATUS ")) {
832 const gchar *p = NULL;
840 if (p && *(resp - 1) == ')') {
841 const gchar *msgs = NULL, *unseen = NULL;
845 while (p && (!msgs || !unseen)) {
846 const gchar **dest = NULL;
848 if (g_str_has_prefix (p, "MESSAGES "))
850 else if (g_str_has_prefix (p, "UNSEEN "))
854 *dest = imap_next_word (p);
859 p = imap_next_word (*dest);
861 p = imap_next_word (p);
863 p = imap_next_word (p);
867 if (msgs && unseen) {
871 *total = strtoul (msgs, NULL, 10);
874 *unread = strtoul (unseen, NULL, 10);
879 camel_imap_response_free (imap_store, response);
886 imap_refresh_info_sync (CamelFolder *folder,
887 GCancellable *cancellable,
890 CamelStore *parent_store;
891 CamelImapStore *imap_store;
892 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
893 CamelImapResponse *response;
895 const gchar *full_name;
896 gint check_rescan = -1;
897 GError *local_error = NULL;
899 parent_store = camel_folder_get_parent_store (folder);
900 imap_store = CAMEL_IMAP_STORE (parent_store);
902 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (imap_store)))
905 if (camel_folder_is_frozen (folder)) {
906 imap_folder->need_refresh = TRUE;
910 /* If the folder isn't selected, select it (which will force
911 * a rescan if one is needed).
912 * Also, if this is the INBOX, some servers (cryus) wont tell
913 * us with a NOOP of new messages, so force a reselect which
916 if (camel_application_is_exiting || !camel_imap_store_connected (imap_store, &local_error))
919 /* try to store local changes first, as the summary contains new local messages */
920 replay_offline_journal (
921 imap_store, imap_folder, cancellable, &local_error);
923 full_name = camel_folder_get_full_name (folder);
925 if (imap_store->current_folder != folder
926 || g_ascii_strcasecmp (full_name, "INBOX") == 0) {
927 response = camel_imap_command (imap_store, folder, cancellable, &local_error, NULL);
929 camel_imap_folder_selected (
931 cancellable, &local_error);
932 camel_imap_response_free (imap_store, response);
934 } else if (imap_folder->need_rescan) {
935 /* Otherwise, if we need a rescan, do it, and if not, just do
936 * a NOOP to give the server a chance to tell us about new
940 folder, camel_folder_summary_count (
941 folder->summary), cancellable, &local_error);
945 /* on some servers need to CHECKpoint INBOX to recieve new messages?? */
946 /* rfc2060 suggests this, but havent seen a server that requires it */
947 if (g_ascii_strcasecmp (full_name, "INBOX") == 0) {
948 response = camel_imap_command (imap_store, folder, &local_error, "CHECK");
949 camel_imap_response_free (imap_store, response);
952 response = camel_imap_command (imap_store, folder, cancellable, &local_error, "NOOP");
953 camel_imap_response_free (imap_store, response);
956 si = camel_store_summary_path ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary, full_name);
958 guint32 unread, total;
960 total = camel_folder_summary_count (folder->summary);
961 unread = camel_folder_summary_get_unread_count (folder->summary);
963 if (si->total != total
964 || si->unread != unread) {
967 camel_store_summary_touch ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary);
970 camel_store_summary_info_free ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary, si);
973 if (check_rescan && !camel_application_is_exiting && local_error == NULL) {
974 if (check_rescan == -1) {
975 guint32 total, unread = 0, server_total = 0, server_unread = 0;
979 /* Check whether there are changes in total/unread messages in the folders
980 * and if so, then rescan whole summary */
981 if (get_folder_status (folder, &server_total, &server_unread, cancellable, &local_error)) {
983 total = camel_folder_summary_count (folder->summary);
984 unread = camel_folder_summary_get_unread_count (folder->summary);
986 if (total != server_total || unread != server_unread)
993 folder, camel_folder_summary_count (
994 folder->summary), cancellable, &local_error);
998 camel_folder_summary_save_to_db (folder->summary, NULL);
999 camel_store_summary_save ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary);
1001 if (local_error != NULL) {
1002 g_propagate_error (error, local_error);
1010 fillup_custom_flags (CamelMessageInfo *mi,
1011 gchar *custom_flags)
1016 array_str = g_strsplit (custom_flags, " ", -1);
1018 while (array_str[index] != NULL) {
1019 camel_flag_set (&((CamelMessageInfoBase *) mi)->user_flags, array_str[index], TRUE);
1023 g_strfreev (array_str);
1026 /* This will merge custom flags with those in message info. Returns whether was some change. */
1028 merge_custom_flags (CamelMessageInfo *mi,
1029 const gchar *custom_flags)
1035 const CamelFlag *flag;
1036 gboolean changed = FALSE;
1038 g_return_val_if_fail (mi != NULL, FALSE);
1044 server = g_hash_table_new (g_str_hash, g_str_equal);
1046 cflags = g_strsplit (custom_flags, " ", -1);
1047 for (i = 0; cflags[i]; i++) {
1048 gchar *name = cflags[i];
1050 if (name && *name) {
1051 g_hash_table_insert (server, name, name);
1052 list = g_list_prepend (list, name);
1056 for (flag = camel_message_info_user_flags (mi); flag; flag = flag->next) {
1057 gchar *name = (gchar *) flag->name;
1060 list = g_list_prepend (list, name);
1063 list = g_list_sort (list, (GCompareFunc) strcmp);
1064 for (p = list; p; p = p->next) {
1065 if (p->next && strcmp (p->data, p->next->data) == 0) {
1066 /* This flag is there twice, which means it was on the server and
1067 * in our local summary too; thus skip these two elements. */
1070 /* If this value came from the server, then add it to our local summary,
1071 * otherwise it was in local summary, but isn't on the server, thus remove it. */
1075 camel_folder_summary_touch (mi->summary);
1076 camel_flag_set (&((CamelMessageInfoBase *) mi)->user_flags, p->data, g_hash_table_lookup (server, p->data) != NULL);
1077 ((CamelMessageInfoBase *) mi)->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
1082 g_hash_table_destroy (server);
1083 g_strfreev (cflags);
1088 /* Called with the store's connect_lock locked */
1090 imap_rescan (CamelFolder *folder,
1092 GCancellable *cancellable,
1095 CamelStore *parent_store;
1096 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
1097 CamelImapStore *store;
1101 gchar *custom_flags;
1104 CamelImapResponseType type;
1105 gint i, j, seq, summary_got, del = 0;
1107 CamelMessageInfo *info;
1108 CamelImapMessageInfo *iinfo;
1111 CamelFolderChangeInfo *changes = NULL;
1113 GPtrArray *known_uids;
1115 parent_store = camel_folder_get_parent_store (folder);
1116 store = CAMEL_IMAP_STORE (parent_store);
1118 if (camel_application_is_exiting)
1121 imap_folder->need_rescan = FALSE;
1123 known_uids = camel_folder_summary_get_array (folder->summary);
1124 summary_len = known_uids ? known_uids->len : 0;
1125 if (summary_len == 0) {
1126 camel_folder_summary_free_array (known_uids);
1128 return camel_imap_folder_changed (
1129 folder, exists, NULL, cancellable, error);
1133 /* Check UIDs and flags of all messages we already know of. */
1134 camel_operation_push_message (
1135 cancellable, _("Scanning for changed messages in %s"),
1136 camel_folder_get_display_name (folder));
1138 camel_folder_sort_uids (folder, known_uids);
1140 uid = g_ptr_array_index (known_uids, summary_len - 1);
1142 camel_operation_pop_message (cancellable);
1143 camel_folder_summary_free_array (known_uids);
1147 if (!camel_imap_store_connected (CAMEL_IMAP_STORE (parent_store), error)) {
1148 camel_operation_pop_message (cancellable);
1149 camel_folder_summary_free_array (known_uids);
1153 ok = camel_imap_command_start (
1154 store, folder, cancellable, error,
1155 "UID FETCH 1:%s (FLAGS)", uid);
1157 camel_operation_pop_message (cancellable);
1158 camel_folder_summary_free_array (known_uids);
1163 new = g_malloc0 (summary_len * sizeof (*new));
1165 while ((type = camel_imap_command_response (store, folder, &resp, cancellable, error)) == CAMEL_IMAP_RESPONSE_UNTAGGED && !camel_application_is_exiting) {
1170 data = parse_fetch_response (imap_folder, resp);
1176 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
1177 uid = g_datalist_get_data (&data, "UID");
1178 flags = GPOINTER_TO_UINT (g_datalist_get_data (&data, "FLAGS"));
1180 if (!uid || !seq || seq > summary_len || seq < 0) {
1181 g_datalist_clear (&data);
1185 camel_operation_progress (
1186 cancellable, ++summary_got * 100 / summary_len);
1188 new[seq - 1].uid = g_strdup (uid);
1189 new[seq - 1].flags = flags;
1190 new[seq - 1].custom_flags = g_strdup (g_datalist_get_data (&data, "CUSTOM.FLAGS"));
1191 g_datalist_clear (&data);
1194 if (summary_got == 0 && summary_len == 0) {
1195 camel_operation_pop_message (cancellable);
1199 camel_folder_summary_free_array (known_uids);
1203 camel_operation_pop_message (cancellable);
1205 if (type == CAMEL_IMAP_RESPONSE_ERROR || camel_application_is_exiting) {
1206 for (i = 0; i < summary_len && new[i].uid; i++) {
1207 g_free (new[i].uid);
1208 g_free (new[i].custom_flags);
1213 camel_folder_summary_free_array (known_uids);
1217 /* Free the final tagged response */
1220 /* If we find a UID in the summary that doesn't correspond to
1221 * the UID in the folder, then either: (a) it's a real UID,
1222 * but the message was deleted on the server, or (b) it's a
1223 * fake UID, and needs to be removed from the summary in order
1224 * to sync up with the server. So either way, we remove it
1227 removed = g_array_new (FALSE, FALSE, sizeof (gint));
1229 camel_folder_summary_prepare_fetch_all (folder->summary, NULL);
1231 for (i = 0, j = 0; i < summary_len && new[j].uid; i++) {
1232 gboolean changed = FALSE;
1234 uid = g_ptr_array_index (known_uids, i);
1238 info = camel_folder_summary_get (folder->summary, uid);
1240 if (g_getenv("CRASH_IMAP")) { /* Debug logs to tackle on hard to get imap crasher */
1241 printf ("CRASH: %s: %s",
1242 camel_folder_get_full_name (folder), uid);
1248 iinfo = (CamelImapMessageInfo *) info;
1250 if (strcmp (uid, new[j].uid) != 0) {
1253 g_array_append_val (removed, seq);
1254 camel_message_info_free (info);
1258 /* Update summary flags */
1260 if (new[j].flags != iinfo->server_flags) {
1261 guint32 server_set, server_cleared;
1263 server_set = new[j].flags & ~iinfo->server_flags;
1264 server_cleared = iinfo->server_flags & ~new[j].flags;
1266 camel_message_info_set_flags ((CamelMessageInfo *) iinfo, server_set | server_cleared, (iinfo->info.flags | server_set) & ~server_cleared);
1267 iinfo->server_flags = new[j].flags;
1268 /* unset folder_flagged, because these are flags received froma server */
1269 iinfo->info.flags = iinfo->info.flags & (~CAMEL_MESSAGE_FOLDER_FLAGGED);
1270 iinfo->info.dirty = TRUE;
1272 camel_folder_summary_touch (info->summary);
1276 /* Do not merge custom flags when server doesn't support it.
1277 * Because server always reports NULL, which means none, which
1278 * will remove user's flags from local machine, which is bad.
1280 if ((folder->permanent_flags & CAMEL_MESSAGE_USER) != 0 &&
1281 (iinfo->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED) == 0 &&
1282 merge_custom_flags (info, new[j].custom_flags))
1286 if (changes == NULL)
1287 changes = camel_folder_change_info_new ();
1288 camel_folder_change_info_change_uid (changes, new[j].uid);
1291 camel_message_info_free (info);
1292 g_free (new[j].uid);
1293 g_free (new[j].custom_flags);
1298 camel_folder_changed (folder, changes);
1299 camel_folder_change_info_free (changes);
1305 /* FIXME: Srini: I don't think this will be called any longer. */
1306 /* Free remaining memory. */
1307 while (i < summary_len && new[i].uid) {
1308 g_free (new[i].uid);
1309 g_free (new[i].custom_flags);
1315 /* Remove any leftover cached summary messages. (Yes, we
1316 * repeatedly add the same number to the removed array.
1317 * See RFC2060 7.4.1)
1320 for (i = seq; i <= summary_len; i++) {
1324 g_array_append_val (removed, j);
1327 /* And finally update the summary. */
1328 success = camel_imap_folder_changed (
1329 folder, exists, removed, cancellable, error);
1330 g_array_free (removed, TRUE);
1332 camel_folder_summary_free_array (known_uids);
1336 static const gchar *
1337 get_message_uid (CamelFolder *folder,
1338 CamelImapMessageInfo *info)
1342 g_return_val_if_fail (folder != NULL, NULL);
1343 g_return_val_if_fail (info != NULL, NULL);
1345 uid = camel_message_info_uid (info);
1346 g_return_val_if_fail (uid != NULL, NULL);
1348 if (!isdigit ((guchar) * uid)) {
1349 uid = camel_imap_journal_uidmap_lookup ((CamelIMAPJournal *) CAMEL_IMAP_FOLDER (folder)->journal, uid);
1350 g_return_val_if_fail (uid != NULL, NULL);
1356 /* the max number of chars that an unsigned 32-bit gint can be is 10 chars plus 1 for a possible : */
1357 #define UID_SET_FULL(setlen, maxlen) (maxlen > 0 ? setlen + 11 >= maxlen : FALSE)
1359 /* Find all messages in @folder with flags matching @flags and @mask.
1360 * If no messages match, returns %NULL. Otherwise, returns an array of
1361 * CamelMessageInfo and sets *@set to a message set corresponding the
1362 * UIDs of the matched messages (up to @UID_SET_LIMIT bytes). The
1363 * caller must free the infos, the array, and the set string.
1366 get_matching (CamelFolder *folder,
1369 CamelMessageInfo *master_info,
1372 GPtrArray *deleted_uids,
1373 GPtrArray *junked_uids)
1376 CamelImapMessageInfo *info;
1377 gint i, max, range, last_range_uid;
1379 GList *list1 = NULL;
1383 /* use the local rinfo in the close_range, because we want to keep our info untouched */
1384 #define close_range() \
1385 if (range != -1) { \
1386 if (range != i - 1) { \
1387 CamelImapMessageInfo *rinfo = matches->pdata[matches->len - 1]; \
1389 g_string_append_printf (gset, ":%s", get_message_uid (folder, rinfo)); \
1392 last_range_uid = -1; \
1395 matches = g_ptr_array_new ();
1396 gset = g_string_new ("");
1399 last_range_uid = -1;
1400 for (i = 0; i < max && !UID_SET_FULL (gset->len, UID_SET_LIMIT); i++) {
1402 uid = summary->pdata[i];
1405 info = (CamelImapMessageInfo *) camel_folder_summary_get (folder->summary, uid);
1412 /* if the resulting flag list is empty, then "concat" other message
1413 * only when server_flags are same, because there will be a flag removal
1414 * command for this type of situation */
1415 if ((info->info.flags & mask) != flags || (flags == 0 && info->server_flags != ((CamelImapMessageInfo *) master_info)->server_flags)) {
1416 camel_message_info_free ((CamelMessageInfo *) info);
1421 uid_num = atoi (uid);
1423 /* we got only changes, thus the uid's can be mixed up, not the consecutive list,
1424 * thus close range if we are not in it */
1425 if (last_range_uid != -1 && uid_num != last_range_uid + 1) {
1429 /* only check user flags when we see other message than our 'master' */
1430 if (strcmp (master_info->uid, ((CamelMessageInfo *) info)->uid)) {
1431 const CamelFlag *flag;
1432 GList *list2 = NULL, *l1, *l2;
1433 gint count2 = 0, cmp = 0;
1436 for (flag = camel_message_info_user_flags (master_info); flag; flag = flag->next) {
1439 list1 = g_list_prepend (list1, (gchar *) flag->name);
1443 list1 = g_list_sort (list1, (GCompareFunc) strcmp);
1446 for (flag = camel_message_info_user_flags (info); flag; flag = flag->next) {
1449 list2 = g_list_prepend (list2, (gchar *) flag->name);
1453 if (count1 != count2) {
1454 g_list_free (list2);
1455 camel_message_info_free ((CamelMessageInfo *) info);
1460 list2 = g_list_sort (list2, (GCompareFunc) strcmp);
1461 for (l1 = list1, l2 = list2; l1 && l2 && !cmp; l1 = l1->next, l2 = l2->next) {
1462 cmp = strcmp (l1->data, l2->data);
1466 g_list_free (list2);
1467 camel_message_info_free ((CamelMessageInfo *) info);
1473 if (deleted_uids && (info->info.flags & (CAMEL_MESSAGE_DELETED | CAMEL_IMAP_MESSAGE_MOVED)) == CAMEL_MESSAGE_DELETED) {
1474 g_ptr_array_add (deleted_uids, (gpointer) camel_pstring_strdup (camel_message_info_uid (info)));
1475 info->info.flags &= ~CAMEL_MESSAGE_DELETED;
1476 } else if (junked_uids && (info->info.flags & CAMEL_MESSAGE_JUNK) != 0) {
1477 g_ptr_array_add (junked_uids, (gpointer) camel_pstring_strdup (camel_message_info_uid (info)));
1480 g_ptr_array_add (matches, info);
1481 /* Remove the uid from the list, to optimize*/
1482 camel_pstring_free (summary->pdata[i]);
1483 summary->pdata[i] = NULL;
1486 last_range_uid = uid_num;
1491 last_range_uid = uid_num;
1493 g_string_append_c (gset, ',');
1494 g_string_append_printf (gset, "%s", get_message_uid (folder, info));
1497 if (range != -1 && range != max - 1) {
1498 info = matches->pdata[matches->len - 1];
1499 g_string_append_printf (gset, ":%s", get_message_uid (folder, info));
1503 g_list_free (list1);
1507 g_string_free (gset, FALSE);
1511 g_string_free (gset, TRUE);
1512 g_ptr_array_free (matches, TRUE);
1520 imap_sync_offline (CamelFolder *folder,
1523 CamelStore *parent_store;
1525 parent_store = camel_folder_get_parent_store (folder);
1527 if (folder->summary && (folder->summary->flags & CAMEL_FOLDER_SUMMARY_DIRTY) != 0) {
1529 const gchar *full_name;
1531 /* ... and store's summary when folder's summary is dirty */
1532 full_name = camel_folder_get_full_name (folder);
1533 si = camel_store_summary_path ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary, full_name);
1535 if (si->total != camel_folder_summary_get_saved_count (folder->summary) || si->unread != camel_folder_summary_get_unread_count (folder->summary)) {
1536 si->total = camel_folder_summary_get_saved_count (folder->summary);
1537 si->unread = camel_folder_summary_get_unread_count (folder->summary);
1538 camel_store_summary_touch ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary);
1541 camel_store_summary_info_free ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary, si);
1545 camel_folder_summary_save_to_db (folder->summary, NULL);
1546 camel_store_summary_save ((CamelStoreSummary *)((CamelImapStore *) parent_store)->summary);
1552 host_ends_with (const gchar *host,
1555 gint host_len, ends_len;
1557 g_return_val_if_fail (host != NULL, FALSE);
1558 g_return_val_if_fail (ends != NULL, FALSE);
1560 host_len = strlen (host);
1561 ends_len = strlen (ends);
1563 return ends_len <= host_len && g_ascii_strcasecmp (host + host_len - ends_len, ends) == 0;
1567 is_google_account (CamelStore *store)
1569 CamelNetworkSettings *network_settings;
1570 CamelSettings *settings;
1571 CamelService *service;
1575 g_return_val_if_fail (store != NULL, FALSE);
1576 g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
1578 service = CAMEL_SERVICE (store);
1580 settings = camel_service_ref_settings (service);
1582 network_settings = CAMEL_NETWORK_SETTINGS (settings);
1583 host = camel_network_settings_dup_host (network_settings);
1585 g_object_unref (settings);
1589 host_ends_with (host, "gmail.com") ||
1590 host_ends_with (host, "googlemail.com"));
1598 move_messages (CamelFolder *src_folder,
1600 CamelFolder *des_folder,
1601 GCancellable *cancellable,
1604 g_return_if_fail (src_folder != NULL);
1606 /* it's OK to have these NULL */
1607 if (!uids || uids->len == 0 || des_folder == NULL)
1610 /* moving to the same folder means expunge only */
1611 if (src_folder != des_folder) {
1612 /* do 'copy' to not be bothered with CAMEL_MESSAGE_DELETED again */
1613 if (!imap_transfer_messages (
1614 src_folder, uids, des_folder, FALSE,
1615 NULL, FALSE, cancellable, error))
1619 camel_imap_expunge_uids_only (src_folder, uids, cancellable, error);
1623 imap_synchronize_sync (CamelFolder *folder,
1625 GCancellable *cancellable,
1628 CamelService *service;
1629 CamelSettings *settings;
1630 CamelStore *parent_store;
1631 CamelImapStore *store;
1632 CamelImapMessageInfo *info;
1633 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
1635 CamelFolder *real_junk = NULL;
1636 CamelFolder *real_trash = NULL;
1638 GError *local_error = NULL;
1640 GPtrArray *matches, *summary, *deleted_uids = NULL, *junked_uids = NULL;
1641 gchar *set, *flaglist, *uid;
1644 parent_store = camel_folder_get_parent_store (folder);
1645 store = CAMEL_IMAP_STORE (parent_store);
1646 is_gmail = is_google_account (parent_store);
1648 service = CAMEL_SERVICE (parent_store);
1650 if (folder->permanent_flags == 0 || !camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store))) {
1652 if (!imap_expunge_sync (folder, cancellable, error))
1655 return imap_sync_offline (folder, error);
1658 /* write local changes first */
1659 replay_offline_journal (store, imap_folder, cancellable, NULL);
1661 /* Find a message with changed flags, find all of the other
1662 * messages like it, sync them as a group, mark them as
1663 * updated, and continue.
1665 summary = camel_folder_summary_get_changed (folder->summary); /* These should be in memory anyways */
1666 camel_folder_sort_uids (folder, summary);
1669 settings = camel_service_ref_settings (service);
1671 /* deleted_uids is NULL when not using real trash */
1672 folder_path = camel_imap_settings_dup_real_trash_path (
1673 CAMEL_IMAP_SETTINGS (settings));
1674 if (folder_path != NULL && *folder_path) {
1675 if ((folder->folder_flags & CAMEL_FOLDER_IS_TRASH) != 0) {
1676 /* syncing the trash, expunge deleted when found any */
1677 real_trash = g_object_ref (folder);
1679 real_trash = camel_store_get_trash_folder_sync (
1680 parent_store, cancellable, NULL);
1683 g_free (folder_path);
1685 /* junked_uids is NULL when not using real junk */
1686 folder_path = camel_imap_settings_dup_real_junk_path (
1687 CAMEL_IMAP_SETTINGS (settings));
1688 if (folder_path != NULL && *folder_path) {
1689 if ((folder->folder_flags & CAMEL_FOLDER_IS_JUNK) != 0) {
1690 /* syncing the junk, but cannot move
1691 * messages to itself, thus do nothing */
1694 real_junk = camel_store_get_junk_folder_sync (
1695 parent_store, cancellable, NULL);
1698 g_free (folder_path);
1700 g_object_unref (settings);
1703 deleted_uids = g_ptr_array_new ();
1706 junked_uids = g_ptr_array_new ();
1708 for (i = 0; i < max; i++) {
1709 gboolean unset = FALSE;
1710 CamelImapResponse *response = NULL;
1712 uid = summary->pdata[i];
1714 if (!uid) /* Possibly it was sync by matching flags, which we NULLify */
1717 if (!(info = (CamelImapMessageInfo *) camel_folder_summary_get (folder->summary, uid))) {
1721 if (!(info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) {
1722 camel_message_info_free ((CamelMessageInfo *) info);
1726 /* Note: get_matching() uses UID_SET_LIMIT to limit
1727 * the size of the uid-set string. We don't have to
1728 * loop here to flush all the matching uids because
1729 * they will be scooped up later by our parent loop (I
1730 * think?). -- Jeff */
1731 matches = get_matching (folder, info->info.flags & (folder->permanent_flags | CAMEL_MESSAGE_FOLDER_FLAGGED),
1732 folder->permanent_flags | CAMEL_MESSAGE_FOLDER_FLAGGED, (CamelMessageInfo *) info, &set, summary,
1733 deleted_uids, junked_uids);
1734 if (matches == NULL) {
1735 camel_message_info_free (info);
1739 /* Make sure we're connected before issuing commands */
1740 if (!camel_imap_store_connected (store, NULL)) {
1742 camel_message_info_free (info);
1743 g_ptr_array_foreach (matches, (GFunc) camel_message_info_free, NULL);
1744 g_ptr_array_free (matches, TRUE);
1748 if (deleted_uids && !is_gmail && (info->info.flags & CAMEL_MESSAGE_DELETED) != 0) {
1749 /* there is a real trash, do not set it on the server */
1750 info->info.flags &= ~CAMEL_MESSAGE_DELETED;
1753 flaglist = imap_create_flag_list (info->info.flags & folder->permanent_flags, (CamelMessageInfo *) info, folder->permanent_flags);
1755 if (strcmp (flaglist, "()") == 0) {
1756 /* Note: Cyrus is broken and will not accept an
1757 * empty-set of flags so... if this is true then we
1758 * set and unset \Seen flag. It's necessary because
1759 * we do not know the previously set user flags. */
1763 /* unset all known server flags, because there left none in the actual flags */
1764 flaglist = imap_create_flag_list (info->server_flags & folder->permanent_flags, (CamelMessageInfo *) info, folder->permanent_flags);
1766 if (strcmp (flaglist, "()") == 0) {
1767 /* this should not happen, really */
1769 flaglist = strdup ("(\\Seen)");
1771 response = camel_imap_command (store, folder, cancellable, &local_error,
1772 "UID STORE %s +FLAGS.SILENT %s",
1775 camel_imap_response_free (store, response);
1781 /* We don't use the info any more */
1782 camel_message_info_free (info);
1784 /* Note: to 'unset' flags, use -FLAGS.SILENT (<flag list>) */
1785 if (local_error == NULL) {
1786 response = camel_imap_command (store, folder, cancellable, &local_error,
1787 "UID STORE %s %sFLAGS.SILENT %s",
1788 set, unset ? "-" : "", flaglist);
1795 camel_imap_response_free (store, response);
1797 if (local_error == NULL) {
1798 for (j = 0; j < matches->len; j++) {
1799 info = matches->pdata[j];
1800 if (deleted_uids && !is_gmail) {
1801 /* there is a real trash, do not keep this set */
1802 info->info.flags &= ~CAMEL_MESSAGE_DELETED;
1805 info->info.flags &= ~(CAMEL_MESSAGE_FOLDER_FLAGGED | CAMEL_IMAP_MESSAGE_MOVED);
1806 ((CamelImapMessageInfo *) info)->server_flags = info->info.flags & CAMEL_IMAP_SERVER_FLAGS;
1807 info->info.dirty = TRUE; /* Sync it back to the DB */
1808 if (((CamelMessageInfo *) info)->summary)
1809 camel_folder_summary_touch (((CamelMessageInfo *) info)->summary);
1811 camel_folder_summary_touch (folder->summary);
1814 g_ptr_array_foreach (matches, (GFunc) camel_message_info_free, NULL);
1815 g_ptr_array_free (matches, TRUE);
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);
1833 g_ptr_array_foreach (summary, (GFunc) camel_pstring_free, NULL);
1834 g_ptr_array_free (summary, TRUE);
1840 if (local_error == NULL)
1842 folder, deleted_uids, real_trash,
1843 cancellable, &local_error);
1844 if (local_error == NULL)
1846 folder, junked_uids, real_junk,
1847 cancellable, &local_error);
1850 g_ptr_array_foreach (deleted_uids, (GFunc) camel_pstring_free, NULL);
1851 g_ptr_array_free (deleted_uids, TRUE);
1854 g_ptr_array_foreach (junked_uids, (GFunc) camel_pstring_free, NULL);
1855 g_ptr_array_free (junked_uids, TRUE);
1858 g_object_unref (real_trash);
1860 g_object_unref (real_junk);
1862 if (expunge && local_error == NULL)
1863 imap_expunge_sync (folder, cancellable, &local_error);
1865 if (local_error != NULL)
1866 g_propagate_error (error, local_error);
1868 g_ptr_array_foreach (summary, (GFunc) camel_pstring_free, NULL);
1869 g_ptr_array_free (summary, TRUE);
1871 /* Save the summary */
1872 return imap_sync_offline (folder, error);
1876 uid_compar (gconstpointer va,
1879 const gchar **sa = (const gchar **) va, **sb = (const gchar **) vb;
1882 a = strtoul (*sa, NULL, 10);
1883 b = strtoul (*sb, NULL, 10);
1893 imap_expunge_uids_offline (CamelFolder *folder,
1895 GCancellable *cancellable,
1898 CamelFolderChangeInfo *changes;
1899 CamelStore *parent_store;
1901 const gchar *full_name;
1904 full_name = camel_folder_get_full_name (folder);
1905 parent_store = camel_folder_get_parent_store (folder);
1907 qsort (uids->pdata, uids->len, sizeof (gpointer), uid_compar);
1909 changes = camel_folder_change_info_new ();
1911 for (i = 0; i < uids->len; i++) {
1912 CamelMessageInfo *mi = camel_folder_summary_peek_loaded (folder->summary, uids->pdata[i]);
1915 camel_folder_summary_remove (folder->summary, mi);
1916 camel_message_info_free (mi);
1918 camel_folder_summary_remove_uid (folder->summary, uids->pdata[i]);
1921 camel_folder_change_info_remove_uid (changes, uids->pdata[i]);
1922 list = g_list_prepend (list, (gpointer) uids->pdata[i]);
1923 /* We intentionally don't remove it from the cache because
1924 * the cached data may be useful in replaying a COPY later.
1928 camel_db_delete_uids (parent_store->cdb_w, full_name, list, NULL);
1930 camel_folder_summary_save_to_db (folder->summary, NULL);
1932 camel_imap_journal_log (CAMEL_IMAP_FOLDER (folder)->journal,
1933 CAMEL_IMAP_JOURNAL_ENTRY_EXPUNGE, uids);
1935 camel_folder_changed (folder, changes);
1936 camel_folder_change_info_free (changes);
1942 imap_expunge_uids_online (CamelFolder *folder,
1944 GCancellable *cancellable,
1947 CamelImapStore *store;
1948 CamelImapResponse *response;
1951 gboolean full_expunge;
1952 CamelFolderChangeInfo *changes;
1953 CamelStore *parent_store;
1954 const gchar *full_name;
1958 full_name = camel_folder_get_full_name (folder);
1959 parent_store = camel_folder_get_parent_store (folder);
1961 store = CAMEL_IMAP_STORE (parent_store);
1962 full_expunge = (store->capabilities & IMAP_CAPABILITY_UIDPLUS) == 0;
1964 if (!camel_imap_store_connected (store, error))
1967 if ((store->capabilities & IMAP_CAPABILITY_UIDPLUS) == 0) {
1968 if (!CAMEL_FOLDER_GET_CLASS (folder)->synchronize_sync (
1969 folder, 0, cancellable, error)) {
1974 qsort (uids->pdata, uids->len, sizeof (gpointer), uid_compar);
1976 while (uid < uids->len) {
1977 set = imap_uid_array_to_set (folder->summary, uids, uid, UID_SET_LIMIT, &uid);
1978 response = camel_imap_command (store, folder, cancellable, error,
1979 "UID STORE %s +FLAGS.SILENT (\\Deleted)",
1982 camel_imap_response_free (store, response);
1988 if (!full_expunge) {
1989 GError *local_error = NULL;
1991 response = camel_imap_command (
1992 store, folder, cancellable, &local_error,
1993 "UID EXPUNGE %s", set);
1995 if (local_error != NULL) {
1996 g_clear_error (&local_error);
1998 /* UID EXPUNGE failed, something is broken on the server probably,
1999 * thus fall back to the full expunge. It's not so good, especially
2000 * when resyncing, it will remove already marked messages on the
2001 * server too. I guess that's fine anyway, isn't it?
2002 * For failed command see Gnome's bug #536486 */
2003 full_expunge = TRUE;
2008 response = camel_imap_command (store, folder, cancellable, NULL, "EXPUNGE");
2011 camel_imap_response_free (store, response);
2016 changes = camel_folder_change_info_new ();
2017 for (i = 0; i < uids->len; i++) {
2018 CamelMessageInfo *mi = camel_folder_summary_peek_loaded (folder->summary, uids->pdata[i]);
2021 camel_folder_summary_remove (folder->summary, mi);
2022 camel_message_info_free (mi);
2024 camel_folder_summary_remove_uid (folder->summary, uids->pdata[i]);
2027 camel_folder_change_info_remove_uid (changes, uids->pdata[i]);
2028 list = g_list_prepend (list, (gpointer) uids->pdata[i]);
2029 /* We intentionally don't remove it from the cache because
2030 * the cached data may be useful in replaying a COPY later.
2034 camel_db_delete_uids (parent_store->cdb_w, full_name, list, NULL);
2036 camel_folder_summary_save_to_db (folder->summary, NULL);
2037 camel_folder_changed (folder, changes);
2038 camel_folder_change_info_free (changes);
2044 imap_expunge_sync (CamelFolder *folder,
2045 GCancellable *cancellable,
2048 CamelStore *parent_store;
2049 GPtrArray *uids = NULL;
2050 const gchar *full_name;
2051 gboolean success, real_trash = FALSE;
2053 full_name = camel_folder_get_full_name (folder);
2054 parent_store = camel_folder_get_parent_store (folder);
2056 camel_folder_summary_save_to_db (folder->summary, NULL);
2058 if ((parent_store->flags & CAMEL_STORE_VTRASH) == 0) {
2060 GError *local_error = NULL;
2062 trash = camel_store_get_trash_folder_sync (
2063 parent_store, cancellable, &local_error);
2065 if (local_error == NULL && trash && (folder == trash || g_ascii_strcasecmp (full_name, camel_folder_get_full_name (trash)) == 0)) {
2066 /* it's a real trash folder, thus get all mails from there */
2068 uids = camel_folder_summary_get_array (folder->summary);
2071 if (local_error != NULL)
2072 g_clear_error (&local_error);
2076 uids = camel_db_get_folder_deleted_uids (parent_store->cdb_r, full_name, NULL);
2081 if (camel_offline_store_get_online (CAMEL_OFFLINE_STORE (parent_store)))
2082 success = imap_expunge_uids_online (
2083 folder, uids, cancellable, error);
2085 success = imap_expunge_uids_offline (
2086 folder, uids, cancellable, error);
2089 camel_folder_summary_free_array (uids);
2091 g_ptr_array_foreach (uids, (GFunc) camel_pstring_free, NULL);
2092 g_ptr_array_free (uids, TRUE);
2099 camel_imap_expunge_uids_resyncing (CamelFolder *folder,
2101 GCancellable *cancellable,
2104 CamelStore *parent_store;
2105 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
2106 CamelImapStore *store;
2107 GPtrArray *keep_uids, *mark_uids;
2108 CamelImapResponse *response;
2111 parent_store = camel_folder_get_parent_store (folder);
2112 store = CAMEL_IMAP_STORE (parent_store);
2114 if (imap_folder->read_only)
2117 if (!camel_imap_store_connected (store, error))
2120 if (store->capabilities & IMAP_CAPABILITY_UIDPLUS)
2121 return imap_expunge_uids_online (
2122 folder, uids, cancellable, error);
2124 /* If we don't have UID EXPUNGE we need to avoid expunging any
2125 * of the wrong messages. So we search for deleted messages,
2126 * and any that aren't in our to-expunge list get temporarily
2127 * marked un-deleted.
2130 if (!CAMEL_FOLDER_GET_CLASS (folder)->synchronize_sync (
2131 folder, 0, cancellable, error))
2134 response = camel_imap_command (store, folder, cancellable, error, "UID SEARCH DELETED");
2138 result = camel_imap_response_extract (store, response, "SEARCH", error);
2142 if (result[8] == ' ') {
2143 gchar *uid, *lasts = NULL;
2147 keep_uids = g_ptr_array_new ();
2148 mark_uids = g_ptr_array_new ();
2150 /* Parse SEARCH response */
2151 for (uid = strtok_r (result + 9, " ", &lasts); uid; uid = strtok_r (NULL, " ", &lasts))
2152 g_ptr_array_add (keep_uids, uid);
2153 qsort (keep_uids->pdata, keep_uids->len,
2154 sizeof (gpointer), uid_compar);
2156 /* Fill in "mark_uids", empty out "keep_uids" as needed */
2157 for (ei = ki = 0; ei < uids->len; ei++) {
2158 euid = strtoul (uids->pdata[ei], NULL, 10);
2160 for (kuid = 0; ki < keep_uids->len; ki++) {
2161 kuid = strtoul (keep_uids->pdata[ki], NULL, 10);
2168 g_ptr_array_remove_index (keep_uids, ki);
2170 g_ptr_array_add (mark_uids, uids->pdata[ei]);
2173 /* Empty SEARCH result, meaning nothing is marked deleted
2181 /* Unmark messages to be kept */
2187 while (uid < keep_uids->len) {
2188 uidset = imap_uid_array_to_set (folder->summary, keep_uids, uid, UID_SET_LIMIT, &uid);
2190 response = camel_imap_command (store, folder, cancellable, error,
2191 "UID STORE %s -FLAGS.SILENT (\\Deleted)",
2197 g_ptr_array_free (keep_uids, TRUE);
2198 g_ptr_array_free (mark_uids, TRUE);
2201 camel_imap_response_free (store, response);
2205 /* Mark any messages that still need to be marked */
2210 while (uid < mark_uids->len) {
2211 uidset = imap_uid_array_to_set (folder->summary, mark_uids, uid, UID_SET_LIMIT, &uid);
2213 response = camel_imap_command (store, folder, cancellable, error,
2214 "UID STORE %s +FLAGS.SILENT (\\Deleted)",
2220 g_ptr_array_free (keep_uids, TRUE);
2221 g_ptr_array_free (mark_uids, TRUE);
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 */
2263 camel_imap_expunge_uids_only (CamelFolder *folder,
2265 GCancellable *cancellable,
2268 CamelStore *parent_store;
2270 g_return_val_if_fail (folder != NULL, FALSE);
2272 parent_store = camel_folder_get_parent_store (folder);
2273 g_return_val_if_fail (parent_store != NULL, FALSE);
2275 g_return_val_if_fail (uids != NULL, FALSE);
2277 if (camel_offline_store_get_online (CAMEL_OFFLINE_STORE (parent_store)))
2278 return camel_imap_expunge_uids_resyncing (
2279 folder, uids, cancellable, error);
2281 return imap_expunge_uids_offline (
2282 folder, uids, cancellable, error);
2290 static gint counter = 0;
2291 G_LOCK_DEFINE_STATIC (lock);
2294 res = g_strdup_printf ("tempuid-%lx-%d",
2295 (gulong) time (NULL),
2303 imap_append_offline (CamelFolder *folder,
2304 CamelMimeMessage *message,
2305 CamelMessageInfo *info,
2306 gchar **appended_uid,
2309 CamelImapMessageCache *cache = CAMEL_IMAP_FOLDER (folder)->cache;
2310 CamelFolderChangeInfo *changes;
2313 uid = get_temp_uid ();
2315 camel_imap_summary_add_offline (
2316 folder->summary, uid, message, info);
2317 CAMEL_IMAP_FOLDER_REC_LOCK (folder, cache_lock);
2318 camel_imap_message_cache_insert_wrapper (
2319 cache, uid, "", CAMEL_DATA_WRAPPER (message));
2320 CAMEL_IMAP_FOLDER_REC_UNLOCK (folder, cache_lock);
2322 changes = camel_folder_change_info_new ();
2323 camel_folder_change_info_add_uid (changes, uid);
2324 camel_folder_changed (folder, changes);
2325 camel_folder_change_info_free (changes);
2327 camel_imap_journal_log (CAMEL_IMAP_FOLDER (folder)->journal,
2328 CAMEL_IMAP_JOURNAL_ENTRY_APPEND, uid);
2330 *appended_uid = uid;
2338 imap_folder_add_ignore_recent (CamelImapFolder *imap_folder,
2341 g_return_if_fail (imap_folder != NULL);
2342 g_return_if_fail (uid != NULL);
2344 if (!imap_folder->priv->ignore_recent)
2345 imap_folder->priv->ignore_recent = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
2347 g_hash_table_insert (imap_folder->priv->ignore_recent, g_strdup (uid), GINT_TO_POINTER (1));
2351 imap_folder_uid_in_ignore_recent (CamelImapFolder *imap_folder,
2354 g_return_val_if_fail (imap_folder != NULL, FALSE);
2355 g_return_val_if_fail (uid != NULL, FALSE);
2357 return imap_folder->priv->ignore_recent && g_hash_table_lookup (imap_folder->priv->ignore_recent, uid);
2360 static CamelImapResponse *
2361 do_append (CamelFolder *folder,
2362 CamelMimeMessage *message,
2363 CamelMessageInfo *info,
2365 GCancellable *cancellable,
2368 CamelStore *parent_store;
2369 CamelImapStore *store;
2370 CamelImapResponse *response, *response2;
2371 CamelStream *memstream;
2372 CamelMimeFilter *crlf_filter;
2373 CamelStream *streamfilter;
2375 const gchar *full_name;
2376 gchar *flagstr, *end;
2378 GError *local_error = NULL;
2380 parent_store = camel_folder_get_parent_store (folder);
2381 store = CAMEL_IMAP_STORE (parent_store);
2383 /* encode any 8bit parts so we avoid sending embedded nul-chars and such */
2384 camel_mime_message_encode_8bit_parts (message);
2386 /* FIXME: We could avoid this if we knew how big the message was. */
2387 memstream = camel_stream_mem_new ();
2388 ba = g_byte_array_new ();
2389 camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (memstream), ba);
2391 streamfilter = camel_stream_filter_new (memstream);
2392 crlf_filter = camel_mime_filter_crlf_new (
2393 CAMEL_MIME_FILTER_CRLF_ENCODE,
2394 CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
2395 camel_stream_filter_add (
2396 CAMEL_STREAM_FILTER (streamfilter), crlf_filter);
2397 camel_data_wrapper_write_to_stream_sync (
2398 CAMEL_DATA_WRAPPER (message), streamfilter, cancellable, NULL);
2399 g_object_unref (streamfilter);
2400 g_object_unref (crlf_filter);
2401 g_object_unref (memstream);
2403 /* Some servers don't let us append with (CamelMessageInfo *)custom flags. If the command fails for
2404 * whatever reason, assume this is the case and save the state and try again */
2407 flags = camel_message_info_flags (info);
2410 flags &= folder->permanent_flags;
2412 flagstr = imap_create_flag_list (flags, (CamelMessageInfo *) info, folder->permanent_flags);
2416 full_name = camel_folder_get_full_name (folder);
2417 response = camel_imap_command (
2418 store, NULL, cancellable, &local_error, "APPEND %F%s%s {%d}",
2419 full_name, flagstr ? " " : "",
2420 flagstr ? flagstr : "", ba->len);
2424 if (g_error_matches (local_error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_INVALID) && !store->nocustomappend) {
2425 g_clear_error (&local_error);
2426 store->nocustomappend = 1;
2429 g_propagate_error (error, local_error);
2430 g_byte_array_free (ba, TRUE);
2434 if (*response->status != '+') {
2435 camel_imap_response_free (store, response);
2436 g_byte_array_free (ba, TRUE);
2440 /* send the rest of our data - the mime message */
2441 response2 = camel_imap_command_continuation (store, folder, (const gchar *) ba->data, ba->len, cancellable, error);
2442 g_byte_array_free (ba, TRUE);
2444 /* free it only after message is sent. This may cause more FETCHes. */
2445 camel_imap_response_free (store, response);
2449 if ((store->capabilities & IMAP_CAPABILITY_UIDPLUS) != 0 ||
2450 is_google_account (parent_store)) {
2451 *uid = camel_strstrcase (response2->status, "[APPENDUID ");
2453 *uid = strchr (*uid + 11, ' ');
2455 *uid = g_strndup (*uid + 1, strcspn (*uid + 1, "]"));
2456 /* Make sure it's a number */
2457 if (strtoul (*uid, &end, 10) == 0 || *end) {
2466 imap_folder_add_ignore_recent (CAMEL_IMAP_FOLDER (folder), *uid);
2472 imap_append_online (CamelFolder *folder,
2473 CamelMimeMessage *message,
2474 CamelMessageInfo *info,
2475 gchar **appended_uid,
2476 GCancellable *cancellable,
2479 CamelStore *parent_store;
2480 CamelImapStore *store;
2481 CamelImapResponse *response;
2482 gboolean success = TRUE;
2486 parent_store = camel_folder_get_parent_store (folder);
2487 store = CAMEL_IMAP_STORE (parent_store);
2489 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store))) {
2490 return imap_append_offline (
2491 folder, message, info, appended_uid, error);
2494 if (!camel_imap_store_connected (store, error))
2497 count = camel_folder_summary_count (folder->summary);
2498 response = do_append (folder, message, info, &uid, cancellable, error);
2503 /* Cache first, since freeing response may trigger a
2504 * summary update that will want this information.
2506 CAMEL_IMAP_FOLDER_REC_LOCK (folder, cache_lock);
2507 camel_imap_message_cache_insert_wrapper (
2508 CAMEL_IMAP_FOLDER (folder)->cache, uid,
2509 "", CAMEL_DATA_WRAPPER (message));
2510 CAMEL_IMAP_FOLDER_REC_UNLOCK (folder, cache_lock);
2512 *appended_uid = uid;
2515 } else if (appended_uid)
2516 *appended_uid = NULL;
2518 camel_imap_response_free (store, response);
2520 /* Make sure a "folder_changed" is emitted. */
2521 if (store->current_folder != folder ||
2522 camel_folder_summary_count (folder->summary) == count)
2523 success = imap_refresh_info_sync (folder, cancellable, error);
2529 camel_imap_append_resyncing (CamelFolder *folder,
2530 CamelMimeMessage *message,
2531 CamelMessageInfo *info,
2532 gchar **appended_uid,
2533 GCancellable *cancellable,
2536 CamelStore *parent_store;
2537 CamelImapStore *store;
2538 CamelImapResponse *response;
2541 parent_store = camel_folder_get_parent_store (folder);
2542 store = CAMEL_IMAP_STORE (parent_store);
2544 response = do_append (folder, message, info, &uid, cancellable, error);
2549 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
2550 const gchar *olduid = camel_message_info_uid (info);
2552 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
2553 camel_imap_message_cache_copy (imap_folder->cache, olduid,
2554 imap_folder->cache, uid);
2555 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
2558 *appended_uid = uid;
2561 } else if (appended_uid)
2562 *appended_uid = NULL;
2564 camel_imap_response_free (store, response);
2570 imap_transfer_offline (CamelFolder *source,
2573 gboolean delete_originals,
2574 GPtrArray **transferred_uids,
2575 GCancellable *cancellable,
2578 CamelImapMessageCache *sc = CAMEL_IMAP_FOLDER (source)->cache;
2579 CamelImapMessageCache *dc = CAMEL_IMAP_FOLDER (dest)->cache;
2580 CamelFolderChangeInfo *changes;
2581 CamelMimeMessage *message;
2582 CamelMessageInfo *mi;
2583 gchar *uid, *destuid;
2585 GError *local_error = NULL;
2587 /* We grab the store's command lock first, and then grab the
2588 * source and destination cache_locks. This way we can't
2589 * deadlock in the case where we're simultaneously also trying
2590 * to copy messages in the other direction from another thread.
2592 CAMEL_IMAP_FOLDER_REC_LOCK (source, cache_lock);
2593 CAMEL_IMAP_FOLDER_REC_LOCK (dest, cache_lock);
2595 if (transferred_uids) {
2596 *transferred_uids = g_ptr_array_new ();
2597 g_ptr_array_set_size (*transferred_uids, uids->len);
2600 changes = camel_folder_change_info_new ();
2602 for (i = 0; i < uids->len && local_error == NULL; i++) {
2603 uid = uids->pdata[i];
2605 destuid = get_temp_uid ();
2607 mi = camel_folder_summary_get (source->summary, uid);
2608 g_return_val_if_fail (mi != NULL, FALSE);
2610 message = camel_folder_get_message_sync (
2611 source, uid, cancellable, &local_error);
2614 camel_imap_summary_add_offline (
2615 dest->summary, destuid, message, mi);
2616 g_object_unref (message);
2618 camel_imap_summary_add_offline_uncached (
2619 dest->summary, destuid, mi);
2621 camel_imap_message_cache_copy (sc, uid, dc, destuid);
2622 camel_message_info_free (mi);
2624 camel_folder_change_info_add_uid (changes, destuid);
2625 if (transferred_uids)
2626 (*transferred_uids)->pdata[i] = destuid;
2630 if (delete_originals && local_error == NULL)
2631 camel_folder_delete_message (source, uid);
2634 CAMEL_IMAP_FOLDER_REC_UNLOCK (dest, cache_lock);
2635 CAMEL_IMAP_FOLDER_REC_UNLOCK (source, cache_lock);
2637 camel_folder_changed (dest, changes);
2638 camel_folder_change_info_free (changes);
2640 camel_imap_journal_log (
2641 CAMEL_IMAP_FOLDER (source)->journal,
2642 CAMEL_IMAP_JOURNAL_ENTRY_TRANSFER,
2643 dest, uids, delete_originals, NULL);
2645 if (local_error != NULL) {
2646 g_propagate_error (error, local_error);
2653 /* Call with lock held on destination folder cache */
2655 handle_copyuid (CamelImapResponse *response,
2656 CamelFolder *source,
2657 CamelFolder *destination)
2659 CamelImapMessageCache *scache = CAMEL_IMAP_FOLDER (source)->cache;
2660 CamelImapMessageCache *dcache = CAMEL_IMAP_FOLDER (destination)->cache;
2661 gchar *validity, *srcset, *destset;
2662 GPtrArray *src, *dest;
2665 validity = camel_strstrcase (response->status, "[COPYUID ");
2669 if (strtoul (validity, NULL, 10) !=
2670 CAMEL_IMAP_SUMMARY (destination->summary)->validity)
2673 srcset = strchr (validity, ' ');
2676 destset = strchr (srcset, ' ');
2680 src = imap_uid_set_to_array (source->summary, srcset);
2681 dest = imap_uid_set_to_array (destination->summary, destset);
2683 if (src && dest && src->len == dest->len) {
2684 /* We don't have to worry about deadlocking on the
2685 * cache locks here, because we've got the store's
2686 * command lock too, so no one else could be here.
2688 CAMEL_IMAP_FOLDER_REC_LOCK (source, cache_lock);
2689 for (i = 0; i < src->len; i++) {
2690 camel_imap_message_cache_copy (scache, src->pdata[i],
2691 dcache, dest->pdata[i]);
2693 imap_folder_add_ignore_recent (CAMEL_IMAP_FOLDER (destination), dest->pdata[i]);
2695 CAMEL_IMAP_FOLDER_REC_UNLOCK (source, cache_lock);
2697 imap_uid_array_free (src);
2698 imap_uid_array_free (dest);
2703 imap_uid_array_free (src);
2705 imap_uid_array_free (dest);
2707 g_warning ("Bad COPYUID response from server");
2710 /* Call with lock held on destination folder cache */
2712 handle_copyuid_copy_user_tags (CamelImapResponse *response,
2713 CamelFolder *source,
2714 CamelFolder *destination,
2715 GCancellable *cancellable)
2717 CamelStore *parent_store;
2718 gchar *validity, *srcset, *destset;
2719 GPtrArray *src, *dest;
2722 validity = camel_strstrcase (response->status, "[COPYUID ");
2726 if (strtoul (validity, NULL, 10) !=
2727 CAMEL_IMAP_SUMMARY (destination->summary)->validity)
2730 srcset = strchr (validity, ' ');
2733 destset = strchr (srcset, ' ');
2737 /* first do NOOP on the destination folder, so server has enough time to propagate our copy command there */
2738 parent_store = camel_folder_get_parent_store (destination);
2739 camel_imap_response_free (
2740 CAMEL_IMAP_STORE (parent_store), camel_imap_command (
2741 CAMEL_IMAP_STORE (parent_store), destination, cancellable, NULL, "NOOP"));
2743 /* refresh folder's summary first, we copied messages there on the server,
2744 * but do not know about it in a local summary */
2745 if (!imap_refresh_info_sync (destination, cancellable, NULL))
2748 src = imap_uid_set_to_array (source->summary, srcset);
2749 dest = imap_uid_set_to_array (destination->summary, destset);
2751 if (src && dest && src->len == dest->len) {
2752 CAMEL_IMAP_FOLDER_REC_LOCK (source, cache_lock);
2753 for (i = 0; i < src->len; i++) {
2754 CamelMessageInfo *mi = camel_folder_get_message_info (source, src->pdata[i]);
2757 const CamelTag *tag = camel_message_info_user_tags (mi);
2760 camel_folder_set_message_user_tag (destination, dest->pdata[i], tag->name, tag->value);
2764 camel_folder_free_message_info (source, mi);
2767 CAMEL_IMAP_FOLDER_REC_UNLOCK (source, cache_lock);
2769 imap_uid_array_free (src);
2770 imap_uid_array_free (dest);
2775 imap_uid_array_free (src);
2777 imap_uid_array_free (dest);
2779 g_warning ("Bad COPYUID response from server");
2782 /* returns whether any of messages from uidset has set any user tag or not */
2784 any_has_user_tag (CamelFolder *source,
2789 g_return_val_if_fail (source != NULL && uidset != NULL, FALSE);
2791 src = imap_uid_set_to_array (source->summary, uidset);
2793 gboolean have = FALSE;
2796 CAMEL_IMAP_FOLDER_REC_LOCK (source, cache_lock);
2797 for (i = 0; i < src->len && !have; i++) {
2798 CamelMessageInfo *mi = camel_folder_get_message_info (source, src->pdata[i]);
2801 have = camel_message_info_user_tags (mi) != NULL;
2803 camel_folder_free_message_info (source, mi);
2806 CAMEL_IMAP_FOLDER_REC_UNLOCK (source, cache_lock);
2808 imap_uid_array_free (src);
2817 do_copy (CamelFolder *source,
2819 CamelFolder *destination,
2820 gint delete_originals,
2821 GCancellable *cancellable,
2824 CamelService *service;
2825 CamelSettings *settings;
2826 CamelStore *parent_store;
2827 CamelImapStore *store;
2828 CamelImapResponse *response;
2829 const gchar *full_name;
2832 gint uid = 0, last = 0, i;
2833 GError *local_error = NULL;
2834 gboolean mark_moved;
2835 gboolean success = TRUE;
2837 parent_store = camel_folder_get_parent_store (source);
2838 store = CAMEL_IMAP_STORE (parent_store);
2840 if (!camel_imap_store_connected (store, error))
2843 service = CAMEL_SERVICE (parent_store);
2845 settings = camel_service_ref_settings (service);
2847 trash_path = camel_imap_settings_dup_real_trash_path (
2848 CAMEL_IMAP_SETTINGS (settings));
2850 g_object_unref (settings);
2852 mark_moved = is_google_account (parent_store) && trash_path != NULL;
2854 full_name = camel_folder_get_full_name (destination);
2856 while (uid < uids->len && local_error == NULL) {
2857 uidset = imap_uid_array_to_set (source->summary, uids, uid, UID_SET_LIMIT, &uid);
2859 /* use XGWMOVE only when none of the moving messages has set any user tag */
2860 if ((store->capabilities & IMAP_CAPABILITY_XGWMOVE) != 0 && delete_originals && !any_has_user_tag (source, uidset)) {
2861 response = camel_imap_command (
2862 store, source, cancellable, &local_error,
2863 "UID XGWMOVE %s %F", uidset, full_name);
2864 /* returns only 'A00012 OK UID XGWMOVE completed' '* 2 XGWMOVE' so nothing useful */
2865 camel_imap_response_free (store, response);
2867 CAMEL_IMAP_FOLDER_REC_LOCK (destination, cache_lock);
2868 response = camel_imap_command (
2869 store, source, cancellable, &local_error,
2870 "UID COPY %s %F", uidset, full_name);
2871 if (response && (store->capabilities & IMAP_CAPABILITY_UIDPLUS))
2872 handle_copyuid (response, source, destination);
2874 handle_copyuid_copy_user_tags (
2875 response, source, destination,
2877 camel_imap_response_free (store, response);
2878 CAMEL_IMAP_FOLDER_REC_UNLOCK (destination, cache_lock);
2881 if (local_error == NULL && delete_originals && (mark_moved || !trash_path)) {
2882 for (i = last; i < uid; i++) {
2883 camel_folder_delete_message (
2884 source, uids->pdata[i]);
2886 CamelMessageInfoBase *info = (CamelMessageInfoBase *) camel_folder_summary_get (source->summary, uids->pdata[i]);
2889 info->flags |= CAMEL_IMAP_MESSAGE_MOVED;
2897 if (local_error != NULL) {
2898 g_propagate_error (error, local_error);
2901 /* There is a real trash folder set, which is not on a google account
2902 * and copied messages should be deleted, thus do not move them into
2903 * a trash folder, but just expunge them, because the copy part of
2904 * the operation was successful. */
2905 } else if (trash_path && !mark_moved && delete_originals)
2906 camel_imap_expunge_uids_only (source, uids, cancellable, NULL);
2908 g_free (trash_path);
2914 imap_transfer_messages (CamelFolder *source,
2917 gboolean delete_originals,
2918 GPtrArray **transferred_uids,
2919 gboolean can_call_sync,
2920 GCancellable *cancellable,
2923 CamelStore *parent_store;
2924 CamelImapStore *store;
2925 gboolean success = TRUE;
2928 parent_store = camel_folder_get_parent_store (source);
2929 store = CAMEL_IMAP_STORE (parent_store);
2931 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store)))
2932 return imap_transfer_offline (
2933 source, uids, dest, delete_originals,
2934 transferred_uids, cancellable, error);
2936 /* Sync message flags if needed. */
2937 if (can_call_sync && !imap_synchronize_sync (
2938 source, FALSE, cancellable, error))
2941 count = camel_folder_summary_count (dest->summary);
2943 qsort (uids->pdata, uids->len, sizeof (gpointer), uid_compar);
2945 /* Now copy the messages */
2946 if (!do_copy (source, uids, dest, delete_originals, cancellable, error))
2949 /* Make the destination notice its new messages */
2950 if (store->current_folder != dest ||
2951 camel_folder_summary_count (dest->summary) == count)
2952 success = imap_refresh_info_sync (dest, cancellable, error);
2955 if (transferred_uids)
2956 *transferred_uids = NULL;
2962 imap_transfer_online (CamelFolder *source,
2965 gboolean delete_originals,
2966 GPtrArray **transferred_uids,
2967 GCancellable *cancellable,
2970 return imap_transfer_messages (
2971 source, uids, dest, delete_originals,
2972 transferred_uids, TRUE, cancellable, error);
2976 camel_imap_transfer_resyncing (CamelFolder *source,
2979 gboolean delete_originals,
2980 GPtrArray **transferred_uids,
2981 GCancellable *cancellable,
2984 GPtrArray *realuids;
2987 CamelMimeMessage *message;
2988 CamelMessageInfo *info;
2989 GError *local_error = NULL;
2991 qsort (uids->pdata, uids->len, sizeof (gpointer), uid_compar);
2993 /*This is trickier than append_resyncing, because some of
2994 * the messages we are copying may have been copied or
2995 * appended into @source while we were offline, in which case
2996 * if we don't have UIDPLUS, we won't know their real UIDs,
2997 * so we'll have to append them rather than copying. */
2999 realuids = g_ptr_array_new ();
3002 while (i < uids->len && local_error == NULL) {
3003 /* Skip past real UIDs */
3004 for (first = i; i < uids->len; i++) {
3005 uid = uids->pdata[i];
3007 if (!isdigit ((guchar) * uid)) {
3008 uid = camel_imap_journal_uidmap_lookup ((CamelIMAPJournal *) CAMEL_IMAP_FOLDER (source)->journal, uid);
3012 g_ptr_array_add (realuids, (gchar *) uid);
3015 /* If we saw any real UIDs, do a COPY */
3018 source, realuids, dest, delete_originals,
3019 cancellable, &local_error);
3020 g_ptr_array_set_size (realuids, 0);
3021 if (i == uids->len || local_error != NULL)
3025 /* Deal with fake UIDs */
3026 while (i < uids->len &&
3027 !isdigit (*(guchar *)(uids->pdata[i])) &&
3028 local_error == NULL) {
3029 uid = uids->pdata[i];
3030 message = camel_folder_get_message_sync (
3031 source, uid, cancellable, NULL);
3033 /* Message must have been expunged */
3037 info = camel_folder_get_message_info (source, uid);
3038 g_return_val_if_fail (info != NULL, FALSE);
3040 imap_append_online (
3041 dest, message, info,
3042 NULL, cancellable, &local_error);
3043 camel_folder_free_message_info (source, info);
3044 g_object_unref (message);
3045 if (delete_originals && local_error == NULL)
3046 camel_folder_delete_message (source, uid);
3051 g_ptr_array_free (realuids, FALSE);
3054 if (transferred_uids)
3055 *transferred_uids = NULL;
3057 if (local_error != NULL) {
3058 g_propagate_error (error, local_error);
3066 imap_search_by_expression (CamelFolder *folder,
3067 const gchar *expression,
3068 GCancellable *cancellable,
3071 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3074 /* we could get around this by creating a new search object each time,
3075 * but i doubt its worth it since any long operation would lock the
3076 * command channel too */
3077 CAMEL_IMAP_FOLDER_LOCK (folder, search_lock);
3079 camel_folder_search_set_folder (imap_folder->search, folder);
3080 matches = camel_folder_search_search (imap_folder->search, expression, NULL, cancellable, error);
3082 CAMEL_IMAP_FOLDER_UNLOCK (folder, search_lock);
3088 imap_count_by_expression (CamelFolder *folder,
3089 const gchar *expression,
3090 GCancellable *cancellable,
3093 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3096 /* we could get around this by creating a new search object each time,
3097 * but i doubt its worth it since any long operation would lock the
3098 * command channel too */
3099 CAMEL_IMAP_FOLDER_LOCK (folder, search_lock);
3101 camel_folder_search_set_folder (imap_folder->search, folder);
3102 matches = camel_folder_search_count (imap_folder->search, expression, cancellable, error);
3104 CAMEL_IMAP_FOLDER_UNLOCK (folder, search_lock);
3110 imap_search_by_uids (CamelFolder *folder,
3111 const gchar *expression,
3113 GCancellable *cancellable,
3116 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3120 return g_ptr_array_new ();
3122 CAMEL_IMAP_FOLDER_LOCK (folder, search_lock);
3124 camel_folder_search_set_folder (imap_folder->search, folder);
3125 matches = camel_folder_search_search (imap_folder->search, expression, uids, cancellable, error);
3127 CAMEL_IMAP_FOLDER_UNLOCK (folder, search_lock);
3133 imap_search_free (CamelFolder *folder,
3136 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3138 g_return_if_fail (imap_folder->search);
3140 CAMEL_IMAP_FOLDER_LOCK (folder, search_lock);
3142 camel_folder_search_free_result (imap_folder->search, uids);
3144 CAMEL_IMAP_FOLDER_UNLOCK (folder, search_lock);
3147 static CamelMimeMessage *get_message (CamelImapFolder *imap_folder,
3149 CamelMessageContentInfo *ci,
3150 GCancellable *cancellable,
3153 struct _part_spec_stack {
3154 struct _part_spec_stack *parent;
3159 part_spec_push (struct _part_spec_stack **stack,
3162 struct _part_spec_stack *node;
3164 node = g_new (struct _part_spec_stack, 1);
3165 node->parent = *stack;
3172 part_spec_pop (struct _part_spec_stack **stack)
3174 struct _part_spec_stack *node;
3177 g_return_val_if_fail (*stack != NULL, 0);
3180 *stack = node->parent;
3189 content_info_get_part_spec (CamelMessageContentInfo *ci)
3191 struct _part_spec_stack *stack = NULL;
3192 CamelMessageContentInfo *node;
3193 gchar *part_spec, *buf;
3198 while (node->parent) {
3199 CamelMessageContentInfo *child;
3201 /* FIXME: is this only supposed to apply if 'node' is a multipart? */
3202 if (node->parent->parent &&
3203 camel_content_type_is (node->parent->type, "message", "*") &&
3204 !camel_content_type_is (node->parent->parent->type, "message", "*")) {
3205 node = node->parent;
3209 child = node->parent->childs;
3210 for (part = 1; child; part++) {
3214 child = child->next;
3217 part_spec_push (&stack, part);
3220 while ((part = part / 10))
3223 node = node->parent;
3226 buf = part_spec = g_malloc (len);
3227 part_spec[0] = '\0';
3230 part = part_spec_pop (&stack);
3231 buf += sprintf (buf, "%d%s", part, stack ? "." : "");
3237 /* Fetch the contents of the MIME part indicated by @ci, which is part
3238 * of message @uid in @folder.
3240 static CamelDataWrapper *
3241 get_content (CamelImapFolder *imap_folder,
3243 CamelMimePart *part,
3244 CamelMessageContentInfo *ci,
3246 GCancellable *cancellable,
3249 CamelDataWrapper *content = NULL;
3250 CamelStream *stream;
3253 part_spec = content_info_get_part_spec (ci);
3255 d(printf("get content '%s' '%s' (frommsg = %d)\n", part_spec, camel_content_type_format(ci->type), frommsg));
3257 /* There are three cases: multipart/signed, multipart, message/rfc822, and "other" */
3258 if (camel_content_type_is (ci->type, "multipart", "signed")) {
3259 CamelMultipartSigned *body_mp;
3263 /* Note: because we get the content parts uninterpreted anyway, we could potentially
3264 * just use the normalmultipart code, except that multipart/signed wont let you yet! */
3266 body_mp = camel_multipart_signed_new ();
3267 /* need to set this so it grabs the boundary and other info about the signed type */
3268 /* we assume that part->content_type is more accurate/full than ci->type */
3269 camel_data_wrapper_set_mime_type_field (CAMEL_DATA_WRAPPER (body_mp), CAMEL_DATA_WRAPPER (part)->mime_type);
3271 spec = g_alloca (strlen (part_spec) + 6);
3273 sprintf(spec, part_spec[0] ? "%s.TEXT" : "TEXT", part_spec);
3275 strcpy (spec, part_spec);
3278 stream = camel_imap_folder_fetch_data (imap_folder, uid, spec, FALSE, cancellable, error);
3280 success = camel_data_wrapper_construct_from_stream_sync (
3281 CAMEL_DATA_WRAPPER (body_mp), stream, cancellable, error);
3282 g_object_unref (stream);
3284 g_object_unref ( body_mp);
3289 return (CamelDataWrapper *) body_mp;
3290 } else if (camel_content_type_is (ci->type, "multipart", "*")) {
3291 CamelMultipart *body_mp;
3293 gint speclen, num, isdigest;
3295 if (camel_content_type_is (ci->type, "multipart", "encrypted"))
3296 body_mp = (CamelMultipart *) camel_multipart_encrypted_new ();
3298 body_mp = camel_multipart_new ();
3300 /* need to set this so it grabs the boundary and other info about the multipart */
3301 /* we assume that part->content_type is more accurate/full than ci->type */
3302 camel_data_wrapper_set_mime_type_field (CAMEL_DATA_WRAPPER (body_mp), CAMEL_DATA_WRAPPER (part)->mime_type);
3303 isdigest = camel_content_type_is(((CamelDataWrapper *)part)->mime_type, "multipart", "digest");
3305 speclen = strlen (part_spec);
3306 child_spec = g_malloc (speclen + 17); /* dot + 10 + dot + MIME + nul */
3307 memcpy (child_spec, part_spec, speclen);
3309 child_spec[speclen++] = '.';
3315 sprintf (child_spec + speclen, "%d.MIME", num++);
3316 stream = camel_imap_folder_fetch_data (imap_folder, uid, child_spec, FALSE, cancellable, error);
3320 part = camel_mime_part_new ();
3321 success = camel_data_wrapper_construct_from_stream_sync (
3322 CAMEL_DATA_WRAPPER (part), stream, cancellable, error);
3323 g_object_unref (stream);
3325 g_object_unref (part);
3326 g_object_unref (body_mp);
3327 g_free (child_spec);
3331 content = get_content (imap_folder, uid, part, ci, FALSE, cancellable, error);
3334 if (!stream || !content) {
3335 g_object_unref (body_mp);
3336 g_free (child_spec);
3340 if (camel_debug("imap:folder")) {
3341 gchar *ct = camel_content_type_format (camel_mime_part_get_content_type ((CamelMimePart *) part));
3342 gchar *ct2 = camel_content_type_format (ci->type);
3344 printf("Setting part content type to '%s' contentinfo type is '%s'\n", ct, ct2);
3349 /* if we had no content-type header on a multipart/digest sub-part, then we need to
3350 * treat it as message/rfc822 instead */
3351 if (isdigest && camel_medium_get_header((CamelMedium *)part, "content-type") == NULL) {
3352 CamelContentType *ct = camel_content_type_new("message", "rfc822");
3354 camel_data_wrapper_set_mime_type_field (content, ct);
3355 camel_content_type_unref (ct);
3357 camel_data_wrapper_set_mime_type_field (content, camel_mime_part_get_content_type (part));
3360 camel_medium_set_content (CAMEL_MEDIUM (part), content);
3361 g_object_unref (content);
3363 camel_multipart_add_part (body_mp, part);
3364 g_object_unref (part);
3369 g_free (child_spec);
3371 return (CamelDataWrapper *) body_mp;
3372 } else if (camel_content_type_is (ci->type, "message", "rfc822")) {
3373 content = (CamelDataWrapper *) get_message (imap_folder, uid, ci->childs, cancellable, error);
3377 CamelTransferEncoding enc;
3380 /* NB: we need this differently to multipart/signed case above on purpose */
3381 spec = g_alloca (strlen (part_spec) + 6);
3383 sprintf(spec, part_spec[0] ? "%s.1" : "1", part_spec);
3385 strcpy(spec, part_spec[0]?part_spec:"1");
3387 enc = ci->encoding ? camel_transfer_encoding_from_string (ci->encoding) : CAMEL_TRANSFER_ENCODING_DEFAULT;
3388 content = camel_imap_wrapper_new (imap_folder, ci->type, enc, uid, spec, part);
3394 static CamelMimeMessage *
3395 get_message (CamelImapFolder *imap_folder,
3397 CamelMessageContentInfo *ci,
3398 GCancellable *cancellable,
3401 CamelFolder *folder;
3402 CamelStore *parent_store;
3403 CamelImapStore *store;
3404 CamelDataWrapper *content;
3405 CamelMimeMessage *msg;
3406 CamelStream *stream;
3407 gchar *section_text, *part_spec;
3410 folder = CAMEL_FOLDER (imap_folder);
3411 parent_store = camel_folder_get_parent_store (folder);
3412 store = CAMEL_IMAP_STORE (parent_store);
3414 part_spec = content_info_get_part_spec (ci);
3415 d(printf("get message '%s'\n", part_spec));
3416 section_text = g_strdup_printf ("%s%s%s", part_spec, *part_spec ? "." : "",
3417 store->server_level >= IMAP_LEVEL_IMAP4REV1 ? "HEADER" : "0");
3419 stream = camel_imap_folder_fetch_data (imap_folder, uid, section_text, FALSE, cancellable, error);
3420 g_free (section_text);
3425 msg = camel_mime_message_new ();
3426 success = camel_data_wrapper_construct_from_stream_sync (
3427 CAMEL_DATA_WRAPPER (msg), stream, cancellable, error);
3428 g_object_unref (stream);
3430 g_object_unref (msg);
3434 content = get_content (imap_folder, uid, CAMEL_MIME_PART (msg), ci, TRUE, cancellable, error);
3436 g_object_unref (msg);
3440 if (camel_debug("imap:folder")) {
3441 gchar *ct = camel_content_type_format (camel_mime_part_get_content_type ((CamelMimePart *) msg));
3442 gchar *ct2 = camel_content_type_format (ci->type);
3444 printf("Setting message content type to '%s' contentinfo type is '%s'\n", ct, ct2);
3449 camel_data_wrapper_set_mime_type_field (content, camel_mime_part_get_content_type ((CamelMimePart *) msg));
3450 camel_medium_set_content (CAMEL_MEDIUM (msg), content);
3451 g_object_unref (content);
3456 #define IMAP_SMALL_BODY_SIZE 5120
3458 static CamelMimeMessage *
3459 get_message_simple (CamelImapFolder *imap_folder,
3461 CamelStream *stream,
3462 GCancellable *cancellable,
3465 CamelMimeMessage *msg;
3469 stream = camel_imap_folder_fetch_data (imap_folder, uid, "",
3470 FALSE, cancellable, error);
3475 msg = camel_mime_message_new ();
3476 success = camel_data_wrapper_construct_from_stream_sync (
3477 CAMEL_DATA_WRAPPER (msg), stream, cancellable, error);
3478 g_object_unref (stream);
3480 g_prefix_error (error, _("Unable to retrieve message: "));
3481 g_object_unref (msg);
3489 content_info_incomplete (CamelMessageContentInfo *ci)
3494 if (camel_content_type_is (ci->type, "multipart", "*")
3495 || camel_content_type_is (ci->type, "message", "rfc822")) {
3498 for (ci = ci->childs; ci; ci = ci->next)
3499 if (content_info_incomplete (ci))
3506 static CamelImapMessageInfo *
3507 imap_folder_summary_uid_or_error (CamelFolderSummary *summary,
3511 CamelImapMessageInfo *mi;
3512 mi = (CamelImapMessageInfo *) camel_folder_summary_get (summary, uid);
3515 error, CAMEL_FOLDER_ERROR,
3516 CAMEL_FOLDER_ERROR_INVALID_UID,
3517 _("Cannot get message with message ID %s: %s"),
3518 uid, _("No such message available."));
3523 static CamelMimeMessage *
3524 imap_get_message_sync (CamelFolder *folder,
3526 GCancellable *cancellable,
3529 CamelStore *parent_store;
3530 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3531 CamelImapStore *store;
3532 CamelImapMessageInfo *mi;
3533 CamelMimeMessage *msg = NULL;
3534 CamelStream *stream = NULL;
3536 GError *local_error = NULL;
3538 parent_store = camel_folder_get_parent_store (folder);
3539 store = CAMEL_IMAP_STORE (parent_store);
3541 if (!camel_imap_store_connected (store, error))
3544 mi = imap_folder_summary_uid_or_error (folder->summary, uid, error);
3548 /* If its cached in full, just get it as is, this is only a shortcut,
3549 * since we get stuff from the cache anyway. It affects a busted
3550 * connection though. */
3551 stream = camel_imap_folder_fetch_data (imap_folder, uid, "", TRUE, cancellable, NULL);
3552 if (stream != NULL) {
3553 msg = get_message_simple (imap_folder, uid, stream, cancellable, NULL);
3558 /* All this mess is so we silently retry a fetch if we fail with
3559 * service_unavailable, without an (equivalent) mess of gotos */
3563 g_clear_error (&local_error);
3565 /* If the message is small or only 1 part, or server doesn't do 4v1 (properly) fetch it in one piece. */
3566 if (store->server_level < IMAP_LEVEL_IMAP4REV1
3567 || store->braindamaged
3568 || mi->info.size < IMAP_SMALL_BODY_SIZE
3569 || (!content_info_incomplete (mi->info.content) && !mi->info.content->childs)) {
3570 CamelMessageInfoBase *info = (CamelMessageInfoBase *) camel_folder_summary_get (folder->summary, uid);
3571 msg = get_message_simple (imap_folder, uid, NULL, cancellable, &local_error);
3572 if (info && !info->preview && msg && camel_folder_summary_get_need_preview (folder->summary)) {
3573 if (camel_mime_message_build_preview ((CamelMimePart *) msg, (CamelMessageInfo *) info) && info->preview)
3574 camel_folder_summary_add_preview (folder->summary, (CamelMessageInfo *) info);
3577 camel_message_info_free (info);
3579 if (content_info_incomplete (mi->info.content)) {
3580 /* For larger messages, fetch the structure and build a message
3581 * with offline parts. (We check mi->content->type rather than
3582 * mi->content because camel_folder_summary_info_new always creates
3583 * an empty content struct.)
3585 CamelImapResponse *response;
3586 GData *fetch_data = NULL;
3587 gchar *body, *found_uid;
3590 if (!camel_imap_store_connected (store, NULL)) {
3592 error, CAMEL_SERVICE_ERROR,
3593 CAMEL_SERVICE_ERROR_UNAVAILABLE,
3594 _("This message is not currently available"));
3598 response = camel_imap_command (store, folder, cancellable, &local_error, "UID FETCH %s BODY", uid);
3601 for (i = 0, body = NULL; i < response->untagged->len; i++) {
3602 fetch_data = parse_fetch_response (imap_folder, response->untagged->pdata[i]);
3604 found_uid = g_datalist_get_data (&fetch_data, "UID");
3605 body = g_datalist_get_data (&fetch_data, "BODY");
3606 if (found_uid && body && !strcmp (found_uid, uid))
3608 g_datalist_clear (&fetch_data);
3615 /* NB: small race here, setting the info.content */
3616 imap_parse_body ((const gchar **) &body, folder, mi->info.content);
3617 mi->info.dirty = TRUE;
3618 camel_folder_summary_touch (folder->summary);
3622 g_datalist_clear (&fetch_data);
3624 camel_imap_response_free (store, response);
3626 g_clear_error (&local_error);
3630 if (camel_debug_start("imap:folder")) {
3631 printf("Folder get message '%s' folder info ->\n", uid);
3632 camel_message_info_dump ((CamelMessageInfo *) mi);
3636 /* FETCH returned OK, but we didn't parse a BODY
3637 * response. Courier will return invalid BODY
3638 * responses for invalidly MIMEd messages, so
3639 * fall back to fetching the entire thing and
3640 * let the mailer's "bad MIME" code handle it.
3642 if (content_info_incomplete (mi->info.content))
3643 msg = get_message_simple (imap_folder, uid, NULL, cancellable, &local_error);
3645 msg = get_message (imap_folder, uid, mi->info.content, cancellable, &local_error);
3646 if (msg && camel_folder_summary_get_need_preview (folder->summary)) {
3647 CamelMessageInfoBase *info = (CamelMessageInfoBase *) camel_folder_summary_get (folder->summary, uid);
3648 if (info && !info->preview) {
3649 if (camel_mime_message_build_preview ((CamelMimePart *) msg, (CamelMessageInfo *) info) && info->preview)
3650 camel_folder_summary_add_preview (folder->summary, (CamelMessageInfo *) info);
3652 camel_message_info_free (info);
3656 } while (msg == NULL
3658 && g_error_matches (local_error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE));
3662 gboolean has_attachment;
3664 if (!mi->info.mlist || !*mi->info.mlist) {
3665 /* update mailing list information, if necessary */
3666 gchar *mlist = camel_header_raw_check_mailing_list (&(CAMEL_MIME_PART (msg)->headers));
3670 camel_pstring_free (mi->info.mlist);
3671 mi->info.mlist = camel_pstring_add (mlist, TRUE);
3672 mi->info.dirty = TRUE;
3674 if (mi->info.summary)
3675 camel_folder_summary_touch (mi->info.summary);
3679 has_attachment = camel_mime_message_has_attachment (msg);
3680 if (((camel_message_info_flags ((CamelMessageInfo *) mi) & CAMEL_MESSAGE_ATTACHMENTS) && !has_attachment) ||
3681 ((camel_message_info_flags ((CamelMessageInfo *) mi) & CAMEL_MESSAGE_ATTACHMENTS) == 0 && has_attachment)) {
3682 camel_message_info_set_flags ((CamelMessageInfo *) mi, CAMEL_MESSAGE_ATTACHMENTS, has_attachment ? CAMEL_MESSAGE_ATTACHMENTS : 0);
3686 if (local_error != NULL)
3687 g_propagate_error (error, local_error);
3690 camel_message_info_free (&mi->info);
3696 * imap_synchronize_message_sync
3698 * Ensure that a message is cached locally, but don't retrieve the content if
3699 * it is already local.
3702 imap_synchronize_message_sync (CamelFolder *folder,
3704 GCancellable *cancellable,
3707 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3708 CamelImapMessageInfo *mi;
3709 CamelMimeMessage *msg = NULL;
3710 CamelStream *stream = NULL;
3711 gboolean success = FALSE;
3713 mi = imap_folder_summary_uid_or_error (folder->summary, uid, error);
3715 /* No such UID - is this duplicate work? The sync process selects
3716 * UIDs to start with.
3719 camel_message_info_free (&mi->info);
3721 /* If we can get a stream, assume its fully cached. This may be false
3722 * if partial streams are saved elsewhere in the code - but that seems
3723 * best solved by knowning more about whether a given message is fully
3724 * available locally or not,
3726 /* If its cached in full, just get it as is, this is only a shortcut,
3727 * since we get stuff from the cache anyway. It affects a busted connection though. */
3728 if ((stream = camel_imap_folder_fetch_data(imap_folder, uid, "", TRUE, cancellable, NULL))) {
3729 g_object_unref (stream);
3732 msg = imap_get_message_sync (folder, uid, cancellable, error);
3734 g_object_unref (msg);
3741 /* We pretend that a FLAGS or RFC822.SIZE response is always exactly
3742 * 20 bytes long, and a BODY[HEADERS] response is always 2000 bytes
3743 * long. Since we know how many of each kind of response we're
3744 * expecting, we can find the total (pretend) amount of server traffic
3745 * to expect and then count off the responses as we read them to update
3748 #define IMAP_PRETEND_SIZEOF_FLAGS 20
3749 #define IMAP_PRETEND_SIZEOF_SIZE 20
3750 #define IMAP_PRETEND_SIZEOF_HEADERS 2000
3752 static const gchar *tm_months[] = {
3753 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
3754 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
3758 decode_time (const guchar **in,
3763 register const guchar *inptr;
3764 gint *val, colons = 0;
3766 *hour = *min = *sec = 0;
3769 for (inptr = *in; *inptr && !isspace ((gint) *inptr); inptr++) {
3770 if (*inptr == ':') {
3782 } else if (!isdigit ((gint) *inptr))
3785 *val = (*val * 10) + (*inptr - '0');
3794 decode_internaldate (const guchar *in)
3796 const guchar *inptr = in;
3797 gint hour, min, sec, n;
3802 memset ((gpointer) &tm, 0, sizeof (struct tm));
3804 tm.tm_mday = strtoul ((gchar *) inptr, (gchar **) &buf, 10);
3805 if (buf == inptr || *buf != '-')
3809 if (inptr[3] != '-')
3812 for (n = 0; n < 12; n++) {
3813 if (!g_ascii_strncasecmp ((gchar *) inptr, tm_months[n], 3))
3824 n = strtoul ((gchar *) inptr, (gchar **) &buf, 10);
3825 if (buf == inptr || *buf != ' ')
3828 tm.tm_year = n - 1900;
3831 if (!decode_time (&inptr, &hour, &min, &sec))
3838 n = strtol ((gchar *) inptr, NULL, 10);
3840 date = camel_mktime_utc (&tm);
3842 /* date is now GMT of the time we want, but not offset by the timezone ... */
3844 /* this should convert the time to the GMT equiv time */
3845 date -= ((n / 100) * 60 * 60) + (n % 100) * 60;
3851 add_message_from_data (CamelFolder *folder,
3852 GPtrArray *messages,
3855 GCancellable *cancellable)
3857 CamelMimeMessage *msg;
3858 CamelStream *stream;
3859 CamelImapMessageInfo *mi;
3861 const gchar *bodystructure;
3864 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
3867 stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
3871 if (seq - first >= messages->len)
3872 g_ptr_array_set_size (messages, seq - first + 1);
3874 msg = camel_mime_message_new ();
3875 if (!camel_data_wrapper_construct_from_stream_sync (
3876 CAMEL_DATA_WRAPPER (msg), stream, cancellable, NULL)) {
3877 g_object_unref (msg);
3881 bodystructure = g_datalist_get_data (&data, "BODY");
3883 mi = (CamelImapMessageInfo *)
3884 camel_folder_summary_info_new_from_message (
3885 folder->summary, msg, bodystructure);
3886 g_object_unref (msg);
3888 if ((idate = g_datalist_get_data (&data, "INTERNALDATE")))
3889 mi->info.date_received = decode_internaldate ((const guchar *) idate);
3891 if (mi->info.date_received == -1)
3892 mi->info.date_received = mi->info.date_sent;
3894 messages->pdata[seq - first] = mi;
3899 CamelMessageInfoBase *mi;
3903 construct_junk_headers (gchar *header,
3905 struct _junk_data *jdata)
3907 gchar *bs, *es, *flag = NULL;
3908 gchar *bdata = g_datalist_get_data (&(jdata->data), "BODY_PART_DATA");
3909 struct _camel_header_param *node;
3911 /* FIXME: This can be written in a much clever way.
3912 * We can create HEADERS file or carry all headers till filtering so
3913 * that header based filtering can be much faster. But all that later. */
3914 bs = camel_strstrcase (bdata ? bdata:"", header);
3916 bs += strlen (header);
3917 bs = strchr (bs, ':');
3922 es = strchr (bs, '\n');
3924 flag = g_strndup (bs, es - bs);
3932 node = g_new (struct _camel_header_param, 1);
3933 node->name = g_strdup (header);
3935 node->next = jdata->mi->headers;
3936 jdata->mi->headers = node;
3940 #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 "
3942 /* FIXME: this needs to be kept in sync with camel-mime-utils.c's list
3943 * of mailing-list headers and so might be best if this were
3944 * auto-generated? */
3945 #define MAILING_LIST_HEADERS "X-MAILING-LIST X-LOOP LIST-ID LIST-POST MAILING-LIST ORIGINATOR X-LIST SENDER RETURN-PATH X-BEENTHERE "
3948 imap_update_summary (CamelFolder *folder,
3950 CamelFolderChangeInfo *changes,
3951 GCancellable *cancellable,
3954 CamelStore *parent_store;
3955 CamelService *service;
3956 CamelSettings *settings;
3957 CamelImapStore *store;
3958 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
3959 GPtrArray *fetch_data = NULL, *messages = NULL, *needheaders;
3960 CamelFetchHeadersType fetch_headers;
3961 gchar **extra_headers;
3962 guint32 flags, uidval;
3963 gint i, seq, first, size, got;
3964 CamelImapResponseType type;
3965 GString *header_spec = NULL;
3966 CamelImapMessageInfo *mi;
3967 CamelStream *stream;
3968 gchar *uid, *resp, *tempuid;
3972 parent_store = camel_folder_get_parent_store (folder);
3973 store = CAMEL_IMAP_STORE (parent_store);
3974 service = CAMEL_SERVICE (parent_store);
3976 if (!camel_imap_store_connected (store, error))
3979 settings = camel_service_ref_settings (service);
3981 fetch_headers = camel_imap_settings_get_fetch_headers (
3982 CAMEL_IMAP_SETTINGS (settings));
3984 extra_headers = camel_imap_settings_dup_fetch_headers_extra (
3985 CAMEL_IMAP_SETTINGS (settings));
3987 g_object_unref (settings);
3989 if (store->server_level >= IMAP_LEVEL_IMAP4REV1) {
3990 if (fetch_headers == CAMEL_FETCH_HEADERS_ALL)
3991 header_spec = g_string_new ("HEADER");
3994 header_spec = g_string_new ("HEADER.FIELDS (");
3995 g_string_append (header_spec, CAMEL_MESSAGE_INFO_HEADERS);
3996 if (fetch_headers == CAMEL_FETCH_HEADERS_BASIC_AND_MAILING_LIST)
3997 g_string_append (header_spec, MAILING_LIST_HEADERS);
3998 if (extra_headers != NULL) {
4001 length = g_strv_length ((gchar **) extra_headers);
4002 for (ii = 0; ii < length; ii++) {
4003 g_string_append (header_spec, extra_headers[ii]);
4004 if (ii + 1 < length)
4005 g_string_append_c (header_spec, ' ');
4009 temp = g_string_free (header_spec, FALSE);
4010 temp = g_strstrip (temp);
4011 header_spec = g_string_new (temp);
4013 g_string_append (header_spec, ")");
4016 header_spec = g_string_new ("0");
4018 g_strfreev (extra_headers);
4020 d(printf("Header is : %s", header_spec->str));
4022 /* Figure out if any of the new messages are already cached (which
4023 * may be the case if we're re-syncing after disconnected operation).
4024 * If so, get their UIDs, FLAGS, and SIZEs. If not, get all that
4025 * and ask for the headers too at the same time.
4027 seq = camel_folder_summary_count (folder->summary);
4030 GPtrArray *known_uids;
4032 known_uids = camel_folder_summary_get_array (folder->summary);
4034 camel_folder_sort_uids (folder, known_uids);
4036 tempuid = g_ptr_array_index (known_uids, seq - 1);
4038 uidval = strtoul (tempuid, NULL, 10);
4042 camel_folder_summary_free_array (known_uids);
4049 if (!camel_imap_command_start (store, folder, cancellable, error,
4050 "UID FETCH %d:* (FLAGS RFC822.SIZE INTERNALDATE BODYSTRUCTURE BODY.PEEK[%s])",
4051 uidval + 1, header_spec->str)) {
4052 g_string_free (header_spec, TRUE);
4056 camel_operation_push_message (
4058 _("Fetching summary information for new messages in %s"),
4059 camel_folder_get_display_name (folder));
4061 /* Parse the responses. We can't add a message to the summary
4062 * until we've gotten its headers, and there's no guarantee
4063 * the server will send the responses in a useful order...
4065 fetch_data = g_ptr_array_new ();
4066 messages = g_ptr_array_new ();
4068 while ((type = camel_imap_command_response (store, folder, &resp, cancellable, error)) ==
4069 CAMEL_IMAP_RESPONSE_UNTAGGED && !camel_application_is_exiting) {
4070 data = parse_fetch_response (imap_folder, resp);
4076 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
4078 g_datalist_clear (&data);
4082 if (g_datalist_get_data (&data, "FLAGS"))
4083 got += IMAP_PRETEND_SIZEOF_FLAGS;
4084 if (g_datalist_get_data (&data, "RFC822.SIZE"))
4085 got += IMAP_PRETEND_SIZEOF_SIZE;
4086 stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
4088 got += IMAP_PRETEND_SIZEOF_HEADERS;
4090 /* Use the stream now so we don't tie up many
4091 * many fds if we're fetching many many messages.
4093 add_message_from_data (
4094 folder, messages, first, data, cancellable);
4095 g_datalist_set_data (&data, "BODY_PART_STREAM", NULL);
4098 camel_operation_progress (cancellable, k * 100 / ct);
4100 g_ptr_array_add (fetch_data, data);
4103 camel_operation_pop_message (cancellable);
4105 if (type == CAMEL_IMAP_RESPONSE_ERROR || camel_application_is_exiting) {
4106 g_string_free (header_spec, TRUE);
4110 /* Free the final tagged response */
4113 /* Figure out which headers we still need to fetch. */
4114 needheaders = g_ptr_array_new ();
4116 for (i = 0; i < fetch_data->len; i++) {
4117 data = fetch_data->pdata[i];
4118 if (g_datalist_get_data (&data, "BODY_PART_LEN"))
4121 uid = g_datalist_get_data (&data, "UID");
4123 g_ptr_array_add (needheaders, uid);
4124 size += IMAP_PRETEND_SIZEOF_HEADERS;
4128 /* And fetch them */
4129 if (needheaders->len) {
4133 qsort (needheaders->pdata, needheaders->len,
4134 sizeof (gpointer), uid_compar);
4136 camel_operation_push_message (
4138 _("Fetching summary information for new messages in %s"),
4139 camel_folder_get_display_name (folder));
4141 while (uid < needheaders->len && !camel_application_is_exiting) {
4142 uidset = imap_uid_array_to_set (folder->summary, needheaders, uid, UID_SET_LIMIT, &uid);
4143 if (!camel_imap_command_start (store, folder, cancellable, error,
4144 "UID FETCH %s BODYSTRUCTURE BODY.PEEK[%s]",
4145 uidset, header_spec->str)) {
4146 g_ptr_array_free (needheaders, TRUE);
4147 camel_operation_pop_message (cancellable);
4149 g_string_free (header_spec, TRUE);
4154 while ((type = camel_imap_command_response (store, folder, &resp, cancellable, error))
4155 == CAMEL_IMAP_RESPONSE_UNTAGGED && !camel_application_is_exiting) {
4156 data = parse_fetch_response (imap_folder, resp);
4161 stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
4163 add_message_from_data (
4164 folder, messages, first,
4166 got += IMAP_PRETEND_SIZEOF_HEADERS;
4167 camel_operation_progress (
4168 cancellable, got * 100 / size);
4170 g_datalist_clear (&data);
4173 if (type == CAMEL_IMAP_RESPONSE_ERROR || camel_application_is_exiting) {
4174 g_ptr_array_free (needheaders, TRUE);
4175 g_string_free (header_spec, TRUE);
4176 camel_operation_pop_message (cancellable);
4181 camel_operation_pop_message (cancellable);
4184 g_ptr_array_free (needheaders, TRUE);
4185 g_string_free (header_spec, TRUE);
4187 /* Now finish up summary entries (fix UIDs, set flags and size) */
4188 for (i = 0; i < fetch_data->len; i++) {
4189 struct _junk_data jdata;
4190 data = fetch_data->pdata[i];
4192 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
4193 if (seq >= first + messages->len) {
4194 g_datalist_clear (&data);
4198 mi = messages->pdata[seq - first];
4200 CamelMessageInfo *pmi = NULL;
4203 /* This is a kludge around a bug in Exchange
4204 * 5.5 that sometimes claims multiple messages
4205 * have the same UID. See bug #17694 for
4206 * details. The "solution" is to create a fake
4207 * message-info with the same details as the
4208 * previously valid message. Yes, the user
4209 * will have a clone in his/her message-list,
4210 * but at least we don't crash.
4213 /* find the previous valid message info */
4214 for (j = seq - first - 1; j >= 0; j--) {
4215 pmi = messages->pdata[j];
4224 mi = (CamelImapMessageInfo *) camel_message_info_clone (pmi);
4227 uid = g_datalist_get_data (&data, "UID");
4229 mi->info.uid = camel_pstring_strdup (uid);
4230 flags = GPOINTER_TO_INT (g_datalist_get_data (&data, "FLAGS"));
4232 gchar *custom_flags = NULL;
4234 ((CamelImapMessageInfo *) mi)->server_flags = flags;
4235 /* "or" them in with the existing flags that may
4236 * have been set by summary_info_new_from_message.
4238 mi->info.flags |= flags;
4240 custom_flags = g_datalist_get_data (&data, "CUSTOM.FLAGS");
4242 fillup_custom_flags ((CamelMessageInfo *) mi, custom_flags);
4244 size = GPOINTER_TO_INT (g_datalist_get_data (&data, "RFC822.SIZE"));
4246 mi->info.size = size;
4248 /* Just do this to build the junk required headers to be built*/
4250 jdata.mi = (CamelMessageInfoBase *) mi;
4251 g_hash_table_foreach ((GHashTable *) camel_session_get_junk_headers (camel_service_get_session (service)), (GHFunc) construct_junk_headers, &jdata);
4252 g_datalist_clear (&data);
4254 g_ptr_array_free (fetch_data, TRUE);
4256 if (camel_application_is_exiting) {
4257 /* it will hopefully update summary next time */
4262 /* And add the entries to the summary, etc. */
4263 for (i = 0; i < messages->len; i++) {
4264 mi = messages->pdata[i];
4266 g_warning ("No information for message %d", i + first);
4268 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
4269 _("Incomplete server response: "
4270 "no information provided for message %d"),
4274 uid = (gchar *) camel_message_info_uid (mi);
4276 g_warning("Server provided no uid: message %d", i + first);
4278 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
4279 _("Incomplete server response: "
4280 "no UID provided for message %d"),
4285 /* FIXME: If it enters if (info) it will always match the exception. So stupid */
4286 /* FIXME[disk-summary] Use a db query to see if the DB exists */
4287 /* info = (CamelImapMessageInfo *)camel_folder_summary_get (folder->summary, uid); */
4289 /* for (seq = 0; seq < camel_folder_summary_count (folder->summary); seq++) { */
4290 /* if (folder->summary->messages->pdata[seq] == info) */
4294 ((CamelMessageInfoBase *) mi)->dirty = TRUE;
4295 if (((CamelMessageInfoBase *) mi)->summary)
4296 camel_folder_summary_touch (((CamelMessageInfoBase *) mi)->summary);
4297 camel_folder_summary_add (folder->summary, (CamelMessageInfo *) mi);
4298 camel_folder_change_info_add_uid (changes, camel_message_info_uid (mi));
4300 /* Report all new messages as recent, even without that flag, thus new
4301 * messages will be filtered even after saw by other software earlier.
4302 * Only skip those which we added ourself, like after drag&drop to this folder. */
4303 if (!imap_folder_uid_in_ignore_recent (imap_folder, camel_message_info_uid (mi))
4304 && ((mi->info.flags & CAMEL_IMAP_MESSAGE_RECENT) != 0 || getenv ("FILTER_RECENT") == NULL))
4305 camel_folder_change_info_recent_uid (changes, camel_message_info_uid (mi));
4309 g_ptr_array_free (messages, TRUE);
4311 if (imap_folder->priv->ignore_recent) {
4312 g_hash_table_unref (imap_folder->priv->ignore_recent);
4313 imap_folder->priv->ignore_recent = NULL;
4320 for (i = 0; i < fetch_data->len; i++) {
4321 data = fetch_data->pdata[i];
4322 g_datalist_clear (&data);
4324 g_ptr_array_free (fetch_data, TRUE);
4327 for (i = 0; i < messages->len; i++) {
4328 if (messages->pdata[i])
4329 camel_message_info_free (messages->pdata[i]);
4331 g_ptr_array_free (messages, TRUE);
4334 if (imap_folder->priv->ignore_recent) {
4335 g_hash_table_unref (imap_folder->priv->ignore_recent);
4336 imap_folder->priv->ignore_recent = NULL;
4342 /* Called with the store's connect_lock locked */
4344 camel_imap_folder_changed (CamelFolder *folder,
4347 GCancellable *cancellable,
4350 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
4351 CamelFolderChangeInfo *changes;
4353 gboolean success = TRUE;
4355 changes = camel_folder_change_info_new ();
4357 CamelStore *parent_store;
4359 GList *deleted = NULL;
4360 const gchar *full_name;
4362 GPtrArray *known_uids;
4364 known_uids = camel_folder_summary_get_array (folder->summary);
4365 camel_folder_sort_uids (folder, known_uids);
4366 for (i = 0; i < expunged->len; i++) {
4367 CamelMessageInfo *mi;
4369 id = g_array_index (expunged, int, i);
4370 uid = id - 1 + i >= 0 && id - 1 + i < known_uids->len ? g_ptr_array_index (known_uids, id - 1 + i) : NULL;
4372 /* FIXME: danw: does this mean that the summary is corrupt? */
4373 /* I guess a message that we never retrieved got expunged? */
4377 deleted = g_list_prepend (deleted, (gpointer) uid);
4378 camel_folder_change_info_remove_uid (changes, uid);
4379 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
4380 camel_imap_message_cache_remove (imap_folder->cache, uid);
4381 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
4383 mi = camel_folder_summary_peek_loaded (folder->summary, uid);
4385 camel_folder_summary_remove (folder->summary, mi);
4386 camel_message_info_free (mi);
4388 camel_folder_summary_remove_uid (folder->summary, uid);
4392 /* Delete all in one transaction */
4393 full_name = camel_folder_get_full_name (folder);
4394 parent_store = camel_folder_get_parent_store (folder);
4395 camel_db_delete_uids (parent_store->cdb_w, full_name, deleted, NULL);
4396 g_list_free (deleted);
4398 camel_folder_summary_free_array (known_uids);
4401 len = camel_folder_summary_count (folder->summary);
4402 if (exists > len && !camel_application_is_exiting)
4403 success = imap_update_summary (
4404 folder, exists, changes, cancellable, error);
4406 camel_folder_summary_save_to_db (folder->summary, NULL);
4407 if (camel_folder_change_info_changed (changes))
4408 camel_folder_changed (folder, changes);
4410 camel_folder_change_info_free (changes);
4416 imap_thaw (CamelFolder *folder)
4418 CamelImapFolder *imap_folder;
4420 CAMEL_FOLDER_CLASS (camel_imap_folder_parent_class)->thaw (folder);
4421 if (camel_folder_is_frozen (folder))
4424 /* FIXME imap_refresh_info_sync() may block, but camel_folder_thaw()
4425 * is not supposed to block. Potential hang here. */
4426 imap_folder = CAMEL_IMAP_FOLDER (folder);
4427 if (imap_folder->need_refresh) {
4428 imap_folder->need_refresh = FALSE;
4429 imap_refresh_info_sync (folder, NULL, NULL);
4434 camel_imap_folder_fetch_data (CamelImapFolder *imap_folder,
4436 const gchar *section_text,
4437 gboolean cache_only,
4438 GCancellable *cancellable,
4441 CamelFolder *folder = CAMEL_FOLDER (imap_folder);
4442 CamelStore *parent_store;
4443 CamelImapStore *store;
4444 CamelImapResponse *response;
4445 CamelStream *stream;
4450 parent_store = camel_folder_get_parent_store (folder);
4451 store = CAMEL_IMAP_STORE (parent_store);
4453 if (!camel_imap_store_connected (store, error))
4456 /* EXPUNGE responses have to modify the cache, which means
4457 * they have to grab the cache_lock while holding the
4460 * Because getting the service lock may cause MUCH unecessary
4461 * delay when we already have the data locally, we do the
4462 * locking separately. This could cause a race
4463 * getting the same data from the cache, but that is only
4464 * an inefficiency, and bad luck.
4466 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
4467 stream = camel_imap_message_cache_get (imap_folder->cache, uid, section_text, NULL);
4468 if (!stream && (!strcmp (section_text, "HEADER") || !strcmp (section_text, "0"))) {
4469 stream = camel_imap_message_cache_get (imap_folder->cache, uid, "", NULL);
4471 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
4473 if (stream || cache_only)
4476 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
4478 if (!camel_imap_store_connected (store, NULL)) {
4481 CAMEL_SERVICE_ERROR_UNAVAILABLE,
4482 _("This message is not currently available"));
4483 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_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);
4503 for (i = 0; i < response->untagged->len; i++) {
4504 fetch_data = parse_fetch_response (imap_folder, response->untagged->pdata[i]);
4505 found_uid = g_datalist_get_data (&fetch_data, "UID");
4506 stream = g_datalist_get_data (&fetch_data, "BODY_PART_STREAM");
4507 if (found_uid && stream && !strcmp (uid, found_uid))
4510 g_datalist_clear (&fetch_data);
4513 camel_imap_response_free (store, response);
4514 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
4518 CAMEL_SERVICE_ERROR_UNAVAILABLE,
4519 _("Could not find message body in FETCH response."));
4521 g_object_ref (stream);
4522 g_datalist_clear (&fetch_data);
4529 parse_fetch_response (CamelImapFolder *imap_folder,
4533 gchar *start, *part_spec = NULL, *body = NULL, *uid = NULL, *idate = NULL;
4534 gboolean cache_header = TRUE, header = FALSE;
4537 if (*response != '(') {
4540 if (*response != '*' || *(response + 1) != ' ')
4542 seq = strtoul (response + 2, &response, 10);
4545 if (g_ascii_strncasecmp (response, " FETCH (", 8) != 0)
4549 g_datalist_set_data (&data, "SEQUENCE", GINT_TO_POINTER (seq));
4553 /* Skip the initial '(' or the ' ' between elements */
4556 if (!g_ascii_strncasecmp (response, "FLAGS ", 6)) {
4557 CamelMessageFlags flags;
4558 gchar *custom_flags = NULL;
4562 if (imap_parse_flag_list (&response, &flags, &custom_flags)) {
4563 g_datalist_set_data (&data, "FLAGS", GUINT_TO_POINTER (flags));
4566 g_datalist_set_data_full (&data, "CUSTOM.FLAGS", custom_flags, g_free);
4568 } else if (!g_ascii_strncasecmp (response, "RFC822.SIZE ", 12)) {
4572 size = strtoul (response, &response, 10);
4573 g_datalist_set_data (&data, "RFC822.SIZE", GUINT_TO_POINTER (size));
4574 } else if (!g_ascii_strncasecmp (response, "BODY[", 5) ||
4575 !g_ascii_strncasecmp (response, "RFC822 ", 7)) {
4578 if (*response == 'B') {
4581 /* HEADER], HEADER.FIELDS (...)], or 0] */
4582 if (!g_ascii_strncasecmp (response, "HEADER", 6)) {
4584 if (!g_ascii_strncasecmp (response + 6, ".FIELDS", 7))
4585 cache_header = FALSE;
4586 } else if (!g_ascii_strncasecmp (response, "0]", 2))
4589 p = strchr (response, ']');
4590 if (!p || *(p + 1) != ' ')
4594 part_spec = g_strndup (response, p - response);
4596 part_spec = g_strdup ("HEADER.FIELDS");
4600 part_spec = g_strdup ("");
4603 if (!g_ascii_strncasecmp (response, "HEADER", 6))
4607 body = imap_parse_nstring ((const gchar **) &response, &body_len);
4614 body = g_strdup ("");
4615 g_datalist_set_data_full (&data, "BODY_PART_SPEC", part_spec, g_free);
4616 g_datalist_set_data_full (&data, "BODY_PART_DATA", body, g_free);
4617 g_datalist_set_data (&data, "BODY_PART_LEN", GINT_TO_POINTER (body_len));
4618 } else if (!g_ascii_strncasecmp (response, "BODY ", 5) ||
4619 !g_ascii_strncasecmp (response, "BODYSTRUCTURE ", 14)) {
4620 response = strchr (response, ' ') + 1;
4622 imap_skip_list ((const gchar **) &response);
4623 if (response && (response != start)) {
4624 /* To handle IMAP Server brokenness, Returning empty body, etc. See #355640 */
4625 g_datalist_set_data_full (&data, "BODY", g_strndup (start, response - start), g_free);
4627 } else if (!g_ascii_strncasecmp (response, "UID ", 4)) {
4630 len = strcspn (response + 4, " )");
4631 uid = g_strndup (response + 4, len);
4632 g_datalist_set_data_full (&data, "UID", uid, g_free);
4633 response += 4 + len;
4634 } else if (!g_ascii_strncasecmp (response, "INTERNALDATE ", 13)) {
4638 if (*response == '"') {
4640 len = strcspn (response, "\"");
4641 idate = g_strndup (response, len);
4642 g_datalist_set_data_full (&data, "INTERNALDATE", idate, g_free);
4643 response += len + 1;
4646 g_warning ("Unexpected FETCH response from server: (%s", response);
4649 } while (response && *response != ')');
4651 if (!response || *response != ')') {
4652 g_datalist_clear (&data);
4657 CamelStream *stream;
4659 if (header && !cache_header) {
4660 stream = camel_stream_mem_new_with_buffer (body, body_len);
4662 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
4663 stream = camel_imap_message_cache_insert (imap_folder->cache,
4665 body, body_len, NULL, NULL);
4666 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
4668 stream = camel_stream_mem_new_with_buffer (body, body_len);
4672 g_datalist_set_data_full (&data, "BODY_PART_STREAM", stream,
4673 (GDestroyNotify) g_object_unref);
4679 /* it uses connect_lock, thus be sure it doesn't run in main thread */
4680 static CamelFolderQuotaInfo *
4681 imap_get_quota_info_sync (CamelFolder *folder,
4682 GCancellable *cancellable,
4685 CamelStore *parent_store;
4686 CamelImapStore *imap_store;
4687 CamelImapResponse *response;
4688 CamelFolderQuotaInfo *res = NULL, *last = NULL;
4690 parent_store = camel_folder_get_parent_store (folder);
4691 imap_store = CAMEL_IMAP_STORE (parent_store);
4693 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (imap_store)))
4696 if (!camel_imap_store_connected (imap_store, NULL))
4699 if (imap_store->capabilities & IMAP_CAPABILITY_QUOTA) {
4700 const gchar *full_name = camel_folder_get_full_name (folder);
4701 CamelImapStoreNamespace *ns = camel_imap_store_summary_namespace_find_full (imap_store->summary, full_name);
4702 gchar *folder_name = camel_imap_store_summary_path_to_full (imap_store->summary, full_name, ns ? ns->sep : '/');
4704 response = camel_imap_command (imap_store, NULL, cancellable, error, "GETQUOTAROOT \"%s\"", folder_name);
4709 for (i = 0; i < response->untagged->len; i++) {
4710 const gchar *resp = response->untagged->pdata[i];
4712 if (resp && g_str_has_prefix (resp, "* QUOTA ")) {
4713 gboolean skipped = TRUE;
4718 astr = imap_parse_astring (&resp, &sz);
4721 while (resp && *resp && *resp != '(')
4724 if (resp && *resp == '(') {
4726 const gchar *used = NULL, *total = NULL;
4729 name = imap_parse_astring (&resp, &sz);
4732 used = imap_next_word (resp);
4734 total = imap_next_word (used);
4736 while (resp && *resp && *resp != ')')
4739 if (resp && *resp == ')' && used && total) {
4742 u = strtoull (used, NULL, 10);
4743 t = strtoull (total, NULL, 10);
4746 CamelFolderQuotaInfo *info = camel_folder_quota_info_new (name, u, t);
4762 g_debug ("Unexpected quota response '%s'; skipping it...", (const gchar *)response->untagged->pdata[i]);
4765 camel_imap_response_free (imap_store, response);
4768 g_free (folder_name);
4775 * Scan for messages that are local and return the rest.
4778 imap_get_uncached_uids (CamelFolder *folder,
4783 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
4785 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
4787 result = camel_imap_message_cache_filter_cached (
4788 imap_folder->cache, uids, error);
4790 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);