1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-folder.c : class for a imap folder */
4 * Authors: Michael Zucchi <notzed@ximian.com>
6 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of version 2 of the GNU Lesser General Public
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
28 #include <glib/gi18n-lib.h>
30 #include "camel-imapx-utils.h"
31 #include "camel-imapx-store.h"
32 #include "camel-imapx-folder.h"
33 #include "camel-imapx-summary.h"
34 #include "camel-imapx-server.h"
39 #define d(...) camel_imapx_debug(debug, '?', __VA_ARGS__)
41 /* The custom property ID is a CamelArg artifact.
42 * It still identifies the property in state files. */
45 PROP_APPLY_FILTERS = 0x2501
48 G_DEFINE_TYPE (CamelIMAPXFolder, camel_imapx_folder, CAMEL_TYPE_OFFLINE_FOLDER)
50 static gboolean imapx_folder_get_apply_filters (CamelIMAPXFolder *folder);
53 camel_imapx_folder_new (CamelStore *store,
54 const gchar *folder_dir,
55 const gchar *folder_name,
59 CamelService *service;
60 CamelSettings *settings;
61 CamelIMAPXFolder *ifolder;
62 const gchar *short_name;
65 gboolean filter_inbox;
67 gboolean filter_junk_inbox;
69 d("opening imap folder '%s'\n", folder_dir);
71 service = CAMEL_SERVICE (store);
73 settings = camel_service_ref_settings (service);
77 "filter-all", &filter_all,
78 "filter-inbox", &filter_inbox,
79 "filter-junk", &filter_junk,
80 "filter-junk-inbox", &filter_junk_inbox,
83 g_object_unref (settings);
85 short_name = strrchr (folder_name, '/');
89 short_name = folder_name;
91 folder = g_object_new (
92 CAMEL_TYPE_IMAPX_FOLDER,
93 "display-name", short_name,
94 "full_name", folder_name,
95 "parent-store", store, NULL);
96 ifolder = (CamelIMAPXFolder *) folder;
98 ((CamelIMAPXFolder *) folder)->raw_name = g_strdup (folder_name);
100 folder->summary = camel_imapx_summary_new (folder);
101 if (!folder->summary) {
103 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
104 _("Could not create folder summary for %s"),
109 ifolder->cache = camel_data_cache_new (folder_dir, error);
110 if (!ifolder->cache) {
112 error, _("Could not create cache for %s: "),
117 state_file = g_build_filename (folder_dir, "cmeta", NULL);
118 camel_object_set_state_filename (CAMEL_OBJECT (folder), state_file);
120 camel_object_state_read (CAMEL_OBJECT (folder));
122 ifolder->search = camel_folder_search_new ();
123 ifolder->search_lock = g_mutex_new ();
124 ifolder->stream_lock = g_mutex_new ();
125 ifolder->ignore_recent = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
126 ifolder->exists_on_server = 0;
127 ifolder->unread_on_server = 0;
128 ifolder->modseq_on_server = 0;
129 ifolder->uidnext_on_server = 0;
131 if (!g_ascii_strcasecmp (folder_name, "INBOX")) {
132 if (filter_inbox || filter_all)
133 folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
135 folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
137 if (filter_junk && !filter_junk_inbox)
138 folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
140 if (filter_all || imapx_folder_get_apply_filters (ifolder))
141 folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
144 camel_store_summary_connect_folder_summary (
145 (CamelStoreSummary *) ((CamelIMAPXStore *) store)->summary,
146 folder_name, folder->summary);
152 imapx_folder_get_apply_filters (CamelIMAPXFolder *folder)
154 g_return_val_if_fail (folder != NULL, FALSE);
155 g_return_val_if_fail (CAMEL_IS_IMAPX_FOLDER (folder), FALSE);
157 return folder->apply_filters;
161 imapx_folder_set_apply_filters (CamelIMAPXFolder *folder,
162 gboolean apply_filters)
164 g_return_if_fail (folder != NULL);
165 g_return_if_fail (CAMEL_IS_IMAPX_FOLDER (folder));
167 if ((folder->apply_filters ? 1 : 0) == (apply_filters ? 1 : 0))
170 folder->apply_filters = apply_filters;
172 g_object_notify (G_OBJECT (folder), "apply-filters");
176 imapx_folder_set_property (GObject *object,
181 switch (property_id) {
182 case PROP_APPLY_FILTERS:
183 imapx_folder_set_apply_filters (
184 CAMEL_IMAPX_FOLDER (object),
185 g_value_get_boolean (value));
189 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
193 imapx_folder_get_property (GObject *object,
198 switch (property_id) {
199 case PROP_APPLY_FILTERS:
200 g_value_set_boolean (
201 value, imapx_folder_get_apply_filters (
202 CAMEL_IMAPX_FOLDER (object)));
206 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
210 imapx_folder_dispose (GObject *object)
212 CamelIMAPXFolder *folder = CAMEL_IMAPX_FOLDER (object);
213 CamelStore *parent_store;
215 if (folder->cache != NULL) {
216 g_object_unref (folder->cache);
217 folder->cache = NULL;
220 if (folder->search != NULL) {
221 g_object_unref (folder->search);
222 folder->search = NULL;
225 parent_store = camel_folder_get_parent_store (CAMEL_FOLDER (folder));
227 camel_store_summary_disconnect_folder_summary (
228 (CamelStoreSummary *) ((CamelIMAPXStore *) parent_store)->summary,
229 CAMEL_FOLDER (folder)->summary);
232 /* Chain up to parent's dispose() method. */
233 G_OBJECT_CLASS (camel_imapx_folder_parent_class)->dispose (object);
237 imapx_folder_finalize (GObject *object)
239 CamelIMAPXFolder *folder = CAMEL_IMAPX_FOLDER (object);
241 if (folder->ignore_recent != NULL)
242 g_hash_table_unref (folder->ignore_recent);
244 g_mutex_free (folder->search_lock);
245 g_mutex_free (folder->stream_lock);
247 /* Chain up to parent's finalize() method. */
248 G_OBJECT_CLASS (camel_imapx_folder_parent_class)->finalize (object);
252 imapx_get_filename (CamelFolder *folder,
256 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
258 return camel_data_cache_get_filename (ifolder->cache, "cache", uid);
261 /* Algorithm for selecting a folder:
263 * - If uidvalidity == old uidvalidity
264 * and exsists == old exists
265 * and recent == old recent
266 * and unseen == old unseen
267 * Assume our summary is correct
268 * for each summary item
269 * mark the summary item as 'old/not updated'
271 * fetch flags from 1:*
272 * for each fetch response
273 * info = summary[index]
275 * info = summary_by_uid[uid]
278 * create new info @ index
284 * update based on header
287 * update based on imap body
291 * Async fetch response:
292 * info = summary[index]
295 * force resync/select?
296 * info = empty @ index
297 * else if uid && info.uid != uid
310 * info.state - 2 bit field in flags
311 * 0 = empty, nothing set
312 * 1 = uid & flags set
313 * 2 = update required
318 imapx_search_free (CamelFolder *folder,
321 CamelIMAPXFolder *ifolder = CAMEL_IMAPX_FOLDER (folder);
323 g_return_if_fail (ifolder->search);
325 g_mutex_lock (ifolder->search_lock);
327 camel_folder_search_free_result (ifolder->search, uids);
329 g_mutex_unlock (ifolder->search_lock);
333 imapx_search_by_uids (CamelFolder *folder,
334 const gchar *expression,
336 GCancellable *cancellable,
339 CamelIMAPXFolder *ifolder = CAMEL_IMAPX_FOLDER (folder);
343 return g_ptr_array_new ();
345 g_mutex_lock (ifolder->search_lock);
347 camel_folder_search_set_folder (ifolder->search, folder);
348 matches = camel_folder_search_search (ifolder->search, expression, uids, cancellable, error);
350 g_mutex_unlock (ifolder->search_lock);
356 imapx_count_by_expression (CamelFolder *folder,
357 const gchar *expression,
358 GCancellable *cancellable,
361 CamelIMAPXFolder *ifolder = CAMEL_IMAPX_FOLDER (folder);
364 g_mutex_lock (ifolder->search_lock);
366 camel_folder_search_set_folder (ifolder->search, folder);
367 matches = camel_folder_search_count (ifolder->search, expression, cancellable, error);
369 g_mutex_unlock (ifolder->search_lock);
375 imapx_search_by_expression (CamelFolder *folder,
376 const gchar *expression,
377 GCancellable *cancellable,
380 CamelIMAPXFolder *ifolder = CAMEL_IMAPX_FOLDER (folder);
383 g_mutex_lock (ifolder->search_lock);
385 camel_folder_search_set_folder (ifolder->search, folder);
386 matches = camel_folder_search_search (ifolder->search, expression, NULL, cancellable, error);
388 g_mutex_unlock (ifolder->search_lock);
394 imapx_append_message_sync (CamelFolder *folder,
395 CamelMimeMessage *message,
396 CamelMessageInfo *info,
397 gchar **appended_uid,
398 GCancellable *cancellable,
401 CamelStore *parent_store;
402 CamelIMAPXStore *istore;
403 CamelIMAPXServer *server;
404 gboolean success = FALSE;
406 parent_store = camel_folder_get_parent_store (folder);
407 istore = CAMEL_IMAPX_STORE (parent_store);
409 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (istore))) {
411 error, CAMEL_SERVICE_ERROR,
412 CAMEL_SERVICE_ERROR_UNAVAILABLE,
413 _("You must be working online to complete this operation"));
418 *appended_uid = NULL;
420 server = camel_imapx_store_get_server (istore, NULL, cancellable, error);
422 success = camel_imapx_server_append_message (
423 server, folder, message, info, appended_uid, cancellable, error);
424 g_object_unref (server);
431 imapx_expunge_sync (CamelFolder *folder,
432 GCancellable *cancellable,
435 CamelStore *parent_store;
436 CamelIMAPXStore *istore;
437 CamelIMAPXServer *server;
439 parent_store = camel_folder_get_parent_store (folder);
440 istore = CAMEL_IMAPX_STORE (parent_store);
442 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (istore))) {
444 error, CAMEL_SERVICE_ERROR,
445 CAMEL_SERVICE_ERROR_UNAVAILABLE,
446 _("You must be working online to complete this operation"));
450 server = camel_imapx_store_get_server (istore, camel_folder_get_full_name (folder), cancellable, error);
452 camel_imapx_server_expunge (server, folder, cancellable, error);
453 camel_imapx_store_op_done (istore, server, camel_folder_get_full_name (folder));
454 g_object_unref (server);
462 imapx_fetch_messages_sync (CamelFolder *folder,
465 GCancellable *cancellable,
468 CamelService *service;
469 CamelStore *parent_store;
470 CamelIMAPXStore *istore;
471 CamelIMAPXServer *server;
472 gboolean success = FALSE;
474 parent_store = camel_folder_get_parent_store (folder);
475 istore = CAMEL_IMAPX_STORE (parent_store);
476 service = CAMEL_SERVICE (parent_store);
478 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (istore))) {
480 error, CAMEL_SERVICE_ERROR,
481 CAMEL_SERVICE_ERROR_UNAVAILABLE,
482 _("You must be working online to complete this operation"));
486 if (!camel_service_connect_sync (service, cancellable, error))
489 server = camel_imapx_store_get_server (istore, camel_folder_get_full_name (folder), cancellable, error);
490 if (server != NULL) {
491 success = camel_imapx_server_fetch_messages (server, folder, type, limit, cancellable, error);
492 camel_imapx_store_op_done (istore, server, camel_folder_get_full_name (folder));
493 g_object_unref (server);
499 static CamelMimeMessage *
500 imapx_get_message_sync (CamelFolder *folder,
502 GCancellable *cancellable,
505 CamelMimeMessage *msg = NULL;
506 CamelStream *stream = NULL;
507 CamelStore *parent_store;
508 CamelIMAPXStore *istore;
509 CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) folder;
510 CamelIMAPXServer *server;
511 const gchar *path = NULL;
512 gboolean offline_message = FALSE;
514 parent_store = camel_folder_get_parent_store (folder);
515 istore = CAMEL_IMAPX_STORE (parent_store);
517 if (!strchr (uid, '-'))
521 offline_message = TRUE;
524 stream = camel_data_cache_get (ifolder->cache, path, uid, NULL);
526 if (offline_message) {
528 error, CAMEL_FOLDER_ERROR,
529 CAMEL_FOLDER_ERROR_INVALID_UID,
530 "Offline message vanished from disk: %s", uid);
534 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (istore))) {
536 error, CAMEL_SERVICE_ERROR,
537 CAMEL_SERVICE_ERROR_UNAVAILABLE,
538 _("You must be working online to complete this operation"));
542 server = camel_imapx_store_get_server (istore, camel_folder_get_full_name (folder), cancellable, error);
544 stream = camel_imapx_server_get_message (server, folder, uid, cancellable, error);
545 camel_imapx_store_op_done (istore, server, camel_folder_get_full_name (folder));
546 g_object_unref (server);
551 if (stream != NULL) {
552 msg = camel_mime_message_new ();
554 g_mutex_lock (ifolder->stream_lock);
555 if (!camel_data_wrapper_construct_from_stream_sync (
556 (CamelDataWrapper *) msg, stream, cancellable, error)) {
557 g_object_unref (msg);
560 g_mutex_unlock (ifolder->stream_lock);
561 g_object_unref (stream);
564 CamelMessageInfo *mi = camel_folder_summary_get (folder->summary, uid);
567 gboolean has_attachment;
569 has_attachment = camel_mime_message_has_attachment (msg);
570 if (((camel_message_info_flags (mi) & CAMEL_MESSAGE_ATTACHMENTS) && !has_attachment) ||
571 ((camel_message_info_flags (mi) & CAMEL_MESSAGE_ATTACHMENTS) == 0 && has_attachment)) {
572 camel_message_info_set_flags (mi,
573 CAMEL_MESSAGE_ATTACHMENTS, has_attachment ? CAMEL_MESSAGE_ATTACHMENTS : 0);
576 camel_message_info_free (mi);
585 imapx_purge_message_cache_sync (CamelFolder *folder,
588 GCancellable *cancellable,
591 /* Not Implemented for now. */
596 imapx_refresh_info_sync (CamelFolder *folder,
597 GCancellable *cancellable,
600 CamelService *service;
601 CamelStore *parent_store;
602 CamelIMAPXStore *istore;
603 CamelIMAPXServer *server;
604 gboolean success = FALSE;
606 parent_store = camel_folder_get_parent_store (folder);
607 istore = CAMEL_IMAPX_STORE (parent_store);
608 service = CAMEL_SERVICE (parent_store);
610 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (istore))) {
612 error, CAMEL_SERVICE_ERROR,
613 CAMEL_SERVICE_ERROR_UNAVAILABLE,
614 _("You must be working online to complete this operation"));
618 if (!camel_service_connect_sync (service, cancellable, error))
621 server = camel_imapx_store_get_server (istore, camel_folder_get_full_name (folder), cancellable, error);
622 if (server != NULL) {
623 success = camel_imapx_server_refresh_info (server, folder, cancellable, error);
624 camel_imapx_store_op_done (istore, server, camel_folder_get_full_name (folder));
625 g_object_unref (server);
632 imapx_synchronize_sync (CamelFolder *folder,
634 GCancellable *cancellable,
637 CamelStore *parent_store;
638 CamelIMAPXStore *istore;
639 CamelIMAPXServer *server;
641 parent_store = camel_folder_get_parent_store (folder);
642 istore = CAMEL_IMAPX_STORE (parent_store);
644 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (istore))) {
646 error, CAMEL_SERVICE_ERROR,
647 CAMEL_SERVICE_ERROR_UNAVAILABLE,
648 _("You must be working online to complete this operation"));
652 server = camel_imapx_store_get_server (istore, camel_folder_get_full_name (folder), cancellable, error);
656 camel_imapx_server_sync_changes (server, folder, cancellable, NULL);
658 /* Sync twice - make sure deleted flags are written out,
659 * then sync again incase expunge changed anything */
662 camel_imapx_server_expunge (server, folder, cancellable, NULL);
664 camel_imapx_store_op_done (istore, server, camel_folder_get_full_name (folder));
665 g_object_unref (server);
671 imapx_synchronize_message_sync (CamelFolder *folder,
673 GCancellable *cancellable,
676 CamelStore *parent_store;
677 CamelIMAPXStore *istore;
678 CamelIMAPXServer *server;
681 parent_store = camel_folder_get_parent_store (folder);
682 istore = CAMEL_IMAPX_STORE (parent_store);
684 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (istore))) {
686 error, CAMEL_SERVICE_ERROR,
687 CAMEL_SERVICE_ERROR_UNAVAILABLE,
688 _("You must be working online to complete this operation"));
692 server = camel_imapx_store_get_server (istore, camel_folder_get_full_name (folder), cancellable, error);
696 success = camel_imapx_server_sync_message (server, folder, uid, cancellable, error);
697 camel_imapx_store_op_done (istore, server, camel_folder_get_full_name (folder));
698 g_object_unref (server);
704 imapx_transfer_messages_to_sync (CamelFolder *source,
707 gboolean delete_originals,
708 GPtrArray **transferred_uids,
709 GCancellable *cancellable,
712 CamelStore *parent_store;
713 CamelIMAPXStore *istore;
714 CamelIMAPXServer *server;
715 gboolean success = FALSE;
717 parent_store = camel_folder_get_parent_store (source);
718 istore = CAMEL_IMAPX_STORE (parent_store);
720 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (istore))) {
722 error, CAMEL_SERVICE_ERROR,
723 CAMEL_SERVICE_ERROR_UNAVAILABLE,
724 _("You must be working online to complete this operation"));
728 server = camel_imapx_store_get_server (istore, camel_folder_get_full_name (source), cancellable, error);
730 success = camel_imapx_server_copy_message (server, source, dest, uids, delete_originals, cancellable, error);
731 camel_imapx_store_op_done (istore, server, camel_folder_get_full_name (source));
732 g_object_unref (server);
735 imapx_refresh_info_sync (dest, cancellable, NULL);
741 imapx_rename (CamelFolder *folder,
742 const gchar *new_name)
744 CamelStore *parent_store;
746 parent_store = camel_folder_get_parent_store (folder);
748 camel_store_summary_disconnect_folder_summary (
749 (CamelStoreSummary *) ((CamelIMAPXStore *) parent_store)->summary,
752 CAMEL_FOLDER_CLASS (camel_imapx_folder_parent_class)->rename (folder, new_name);
754 camel_store_summary_connect_folder_summary (
755 (CamelStoreSummary *) ((CamelIMAPXStore *) parent_store)->summary,
756 camel_folder_get_full_name (folder), folder->summary);
760 camel_imapx_folder_class_init (CamelIMAPXFolderClass *class)
762 GObjectClass *object_class;
763 CamelFolderClass *folder_class;
765 object_class = G_OBJECT_CLASS (class);
766 object_class->set_property = imapx_folder_set_property;
767 object_class->get_property = imapx_folder_get_property;
768 object_class->dispose = imapx_folder_dispose;
769 object_class->finalize = imapx_folder_finalize;
771 folder_class = CAMEL_FOLDER_CLASS (class);
772 folder_class->rename = imapx_rename;
773 folder_class->search_by_expression = imapx_search_by_expression;
774 folder_class->search_by_uids = imapx_search_by_uids;
775 folder_class->count_by_expression = imapx_count_by_expression;
776 folder_class->search_free = imapx_search_free;
777 folder_class->get_filename = imapx_get_filename;
778 folder_class->append_message_sync = imapx_append_message_sync;
779 folder_class->expunge_sync = imapx_expunge_sync;
780 folder_class->fetch_messages_sync = imapx_fetch_messages_sync;
781 folder_class->get_message_sync = imapx_get_message_sync;
782 folder_class->purge_message_cache_sync = imapx_purge_message_cache_sync;
783 folder_class->refresh_info_sync = imapx_refresh_info_sync;
784 folder_class->synchronize_sync = imapx_synchronize_sync;
785 folder_class->synchronize_message_sync = imapx_synchronize_message_sync;
786 folder_class->transfer_messages_to_sync = imapx_transfer_messages_to_sync;
788 g_object_class_install_property (
791 g_param_spec_boolean (
794 _("Apply message _filters to this folder"),
797 CAMEL_PARAM_PERSISTENT));
801 camel_imapx_folder_init (CamelIMAPXFolder *imapx_folder)
803 CamelFolder *folder = CAMEL_FOLDER (imapx_folder);
805 folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY;
807 folder->permanent_flags = CAMEL_MESSAGE_ANSWERED |
808 CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_DRAFT |
809 CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_USER;
811 camel_folder_set_lock_async (folder, TRUE);