/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Peter Williams * Michael Zucchi * Jonathon Jongsma * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * Copyright (C) 2009 Intel Corporation * */ /** * SECTION: mail-folder-cache * @short_description: Stores information about open folders **/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "mail-folder-cache.h" #include "e-mail-utils.h" #include "e-mail-folder-utils.h" #include "e-mail-session.h" #include "e-mail-store-utils.h" #define w(x) #define d(x) #define MAIL_FOLDER_CACHE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), MAIL_TYPE_FOLDER_CACHE, MailFolderCachePrivate)) typedef struct _StoreInfo StoreInfo; typedef struct _FolderInfo FolderInfo; typedef struct _AsyncContext AsyncContext; typedef struct _UpdateClosure UpdateClosure; struct _MailFolderCachePrivate { GMainContext *main_context; /* source id for the ping timeout callback */ guint ping_id; /* Store to storeinfo table, active stores */ GHashTable *store_info_ht; GMutex store_info_ht_lock; /* hack for people who LIKE to have unsent count */ gint count_sent; gint count_trash; GQueue local_folder_uris; GQueue remote_folder_uris; }; enum { PROP_0, PROP_MAIN_CONTEXT, }; enum { FOLDER_AVAILABLE, FOLDER_UNAVAILABLE, FOLDER_DELETED, FOLDER_RENAMED, FOLDER_UNREAD_UPDATED, FOLDER_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; struct _StoreInfo { volatile gint ref_count; GMutex lock; CamelStore *store; gulong folder_opened_handler_id; gulong folder_created_handler_id; gulong folder_deleted_handler_id; gulong folder_renamed_handler_id; gulong folder_subscribed_handler_id; gulong folder_unsubscribed_handler_id; GHashTable *folder_info_ht; /* by full_name */ gboolean first_update; /* TRUE, then FALSE forever */ /* Hold a reference to keep them alive. */ CamelFolder *vjunk; CamelFolder *vtrash; /* Outstanding folderinfo requests */ GQueue folderinfo_updates; }; struct _FolderInfo { volatile gint ref_count; GMutex lock; CamelStore *store; gchar *full_name; CamelFolderInfoFlags flags; GWeakRef folder; gulong folder_changed_handler_id; }; struct _AsyncContext { StoreInfo *store_info; CamelFolderInfo *info; }; struct _UpdateClosure { GWeakRef cache; CamelStore *store; /* Signal ID for one of: * AVAILABLE, DELETED, RENAMED, UNAVAILABLE */ guint signal_id; gboolean new_messages; gchar *full_name; gchar *oldfull; gint unread; /* for only one new message... */ gchar *msg_uid; gchar *msg_sender; gchar *msg_subject; }; /* Forward Declarations */ static void store_folder_created_cb (CamelStore *store, CamelFolderInfo *info, MailFolderCache *cache); static void store_folder_deleted_cb (CamelStore *store, CamelFolderInfo *info, MailFolderCache *cache); static void store_folder_opened_cb (CamelStore *store, CamelFolder *folder, MailFolderCache *cache); static void store_folder_renamed_cb (CamelStore *store, const gchar *old_name, CamelFolderInfo *info, MailFolderCache *cache); static void store_folder_subscribed_cb (CamelStore *store, CamelFolderInfo *info, MailFolderCache *cache); static void store_folder_unsubscribed_cb (CamelStore *store, CamelFolderInfo *info, MailFolderCache *cache); G_DEFINE_TYPE (MailFolderCache, mail_folder_cache, G_TYPE_OBJECT) static FolderInfo * folder_info_new (CamelStore *store, const gchar *full_name, CamelFolderInfoFlags flags) { FolderInfo *folder_info; folder_info = g_slice_new0 (FolderInfo); folder_info->ref_count = 1; folder_info->store = g_object_ref (store); folder_info->full_name = g_strdup (full_name); folder_info->flags = flags; g_mutex_init (&folder_info->lock); return folder_info; } static FolderInfo * folder_info_ref (FolderInfo *folder_info) { g_return_val_if_fail (folder_info != NULL, NULL); g_return_val_if_fail (folder_info->ref_count > 0, NULL); g_atomic_int_inc (&folder_info->ref_count); return folder_info; } static void folder_info_clear_folder (FolderInfo *folder_info) { CamelFolder *folder; g_return_if_fail (folder_info != NULL); g_mutex_lock (&folder_info->lock); folder = g_weak_ref_get (&folder_info->folder); if (folder != NULL) { g_signal_handler_disconnect ( folder, folder_info->folder_changed_handler_id); g_weak_ref_set (&folder_info->folder, NULL); folder_info->folder_changed_handler_id = 0; g_object_unref (folder); } g_mutex_unlock (&folder_info->lock); } static void folder_info_unref (FolderInfo *folder_info) { g_return_if_fail (folder_info != NULL); g_return_if_fail (folder_info->ref_count > 0); if (g_atomic_int_dec_and_test (&folder_info->ref_count)) { folder_info_clear_folder (folder_info); g_clear_object (&folder_info->store); g_free (folder_info->full_name); g_mutex_clear (&folder_info->lock); g_slice_free (FolderInfo, folder_info); } } static StoreInfo * store_info_new (CamelStore *store) { StoreInfo *store_info; store_info = g_slice_new0 (StoreInfo); store_info->ref_count = 1; store_info->store = g_object_ref (store); store_info->first_update = TRUE; store_info->folder_info_ht = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) NULL, (GDestroyNotify) folder_info_unref); g_mutex_init (&store_info->lock); /* If these are vfolders then they need to be opened * now, otherwise they won't keep track of all folders. */ if (store->flags & CAMEL_STORE_VJUNK) store_info->vjunk = camel_store_get_junk_folder_sync ( store, NULL, NULL); if (store->flags & CAMEL_STORE_VTRASH) store_info->vtrash = camel_store_get_trash_folder_sync ( store, NULL, NULL); return store_info; } static StoreInfo * store_info_ref (StoreInfo *store_info) { g_return_val_if_fail (store_info != NULL, NULL); g_return_val_if_fail (store_info->ref_count > 0, NULL); g_atomic_int_inc (&store_info->ref_count); return store_info; } static void store_info_unref (StoreInfo *store_info) { g_return_if_fail (store_info != NULL); g_return_if_fail (store_info->ref_count > 0); if (g_atomic_int_dec_and_test (&store_info->ref_count)) { g_warn_if_fail ( g_queue_is_empty ( &store_info->folderinfo_updates)); if (store_info->folder_opened_handler_id > 0) { g_signal_handler_disconnect ( store_info->store, store_info->folder_opened_handler_id); } if (store_info->folder_created_handler_id > 0) { g_signal_handler_disconnect ( store_info->store, store_info->folder_created_handler_id); } if (store_info->folder_deleted_handler_id > 0) { g_signal_handler_disconnect ( store_info->store, store_info->folder_deleted_handler_id); } if (store_info->folder_subscribed_handler_id > 0) { g_signal_handler_disconnect ( store_info->store, store_info->folder_subscribed_handler_id); } if (store_info->folder_unsubscribed_handler_id > 0) { g_signal_handler_disconnect ( store_info->store, store_info->folder_unsubscribed_handler_id); } g_hash_table_destroy (store_info->folder_info_ht); g_clear_object (&store_info->store); g_clear_object (&store_info->vjunk); g_clear_object (&store_info->vtrash); g_mutex_clear (&store_info->lock); g_slice_free (StoreInfo, store_info); } } static FolderInfo * store_info_ref_folder_info (StoreInfo *store_info, const gchar *folder_name) { GHashTable *folder_info_ht; FolderInfo *folder_info; g_return_val_if_fail (store_info != NULL, NULL); g_return_val_if_fail (folder_name != NULL, NULL); g_mutex_lock (&store_info->lock); folder_info_ht = store_info->folder_info_ht; folder_info = g_hash_table_lookup (folder_info_ht, folder_name); if (folder_info != NULL) folder_info_ref (folder_info); g_mutex_unlock (&store_info->lock); return folder_info; } static void store_info_insert_folder_info (StoreInfo *store_info, FolderInfo *folder_info) { GHashTable *folder_info_ht; g_return_if_fail (store_info != NULL); g_return_if_fail (folder_info != NULL); g_return_if_fail (folder_info->full_name != NULL); g_mutex_lock (&store_info->lock); folder_info_ht = store_info->folder_info_ht; g_hash_table_insert ( folder_info_ht, folder_info->full_name, folder_info_ref (folder_info)); g_mutex_unlock (&store_info->lock); } static GList * store_info_list_folder_info (StoreInfo *store_info) { GList *list; g_return_val_if_fail (store_info != NULL, NULL); g_mutex_lock (&store_info->lock); list = g_hash_table_get_values (store_info->folder_info_ht); g_list_foreach (list, (GFunc) folder_info_ref, NULL); g_mutex_unlock (&store_info->lock); return list; } static FolderInfo * store_info_steal_folder_info (StoreInfo *store_info, const gchar *folder_name) { GHashTable *folder_info_ht; FolderInfo *folder_info; g_return_val_if_fail (store_info != NULL, NULL); g_return_val_if_fail (folder_name != NULL, NULL); g_mutex_lock (&store_info->lock); folder_info_ht = store_info->folder_info_ht; folder_info = g_hash_table_lookup (folder_info_ht, folder_name); if (folder_info != NULL) { folder_info_ref (folder_info); g_hash_table_remove (folder_info_ht, folder_name); } g_mutex_unlock (&store_info->lock); return folder_info; } static void async_context_free (AsyncContext *async_context) { if (async_context->info != NULL) camel_folder_info_free (async_context->info); store_info_unref (async_context->store_info); g_slice_free (AsyncContext, async_context); } static UpdateClosure * update_closure_new (MailFolderCache *cache, CamelStore *store) { UpdateClosure *closure; closure = g_slice_new0 (UpdateClosure); g_weak_ref_set (&closure->cache, cache); closure->store = g_object_ref (store); return closure; } static void update_closure_free (UpdateClosure *closure) { g_weak_ref_set (&closure->cache, NULL); g_clear_object (&closure->store); g_free (closure->full_name); g_free (closure->oldfull); g_free (closure->msg_uid); g_free (closure->msg_sender); g_free (closure->msg_subject); g_slice_free (UpdateClosure, closure); } static StoreInfo * mail_folder_cache_new_store_info (MailFolderCache *cache, CamelStore *store) { StoreInfo *store_info; gulong handler_id; g_return_val_if_fail (store != NULL, NULL); store_info = store_info_new (store); handler_id = g_signal_connect ( store, "folder-opened", G_CALLBACK (store_folder_opened_cb), cache); store_info->folder_opened_handler_id = handler_id; handler_id = g_signal_connect ( store, "folder-created", G_CALLBACK (store_folder_created_cb), cache); store_info->folder_created_handler_id = handler_id; handler_id = g_signal_connect ( store, "folder-deleted", G_CALLBACK (store_folder_deleted_cb), cache); store_info->folder_deleted_handler_id = handler_id; handler_id = g_signal_connect ( store, "folder-renamed", G_CALLBACK (store_folder_renamed_cb), cache); store_info->folder_renamed_handler_id = handler_id; if (CAMEL_IS_SUBSCRIBABLE (store)) { handler_id = g_signal_connect ( store, "folder-subscribed", G_CALLBACK (store_folder_subscribed_cb), cache); store_info->folder_subscribed_handler_id = handler_id; handler_id = g_signal_connect ( store, "folder-unsubscribed", G_CALLBACK (store_folder_unsubscribed_cb), cache); store_info->folder_unsubscribed_handler_id = handler_id; } g_mutex_lock (&cache->priv->store_info_ht_lock); g_hash_table_insert ( cache->priv->store_info_ht, g_object_ref (store), store_info_ref (store_info)); g_mutex_unlock (&cache->priv->store_info_ht_lock); return store_info; } static StoreInfo * mail_folder_cache_ref_store_info (MailFolderCache *cache, CamelStore *store) { GHashTable *store_info_ht; StoreInfo *store_info; g_return_val_if_fail (store != NULL, NULL); g_mutex_lock (&cache->priv->store_info_ht_lock); store_info_ht = cache->priv->store_info_ht; store_info = g_hash_table_lookup (store_info_ht, store); if (store_info != NULL) store_info_ref (store_info); g_mutex_unlock (&cache->priv->store_info_ht_lock); return store_info; } static GList * mail_folder_cache_list_stores (MailFolderCache *cache) { GHashTable *store_info_ht; GList *list; g_mutex_lock (&cache->priv->store_info_ht_lock); store_info_ht = cache->priv->store_info_ht; list = g_hash_table_get_keys (store_info_ht); g_list_foreach (list, (GFunc) g_object_ref, NULL); g_mutex_unlock (&cache->priv->store_info_ht_lock); return list; } static StoreInfo * mail_folder_cache_steal_store_info (MailFolderCache *cache, CamelStore *store) { GHashTable *store_info_ht; StoreInfo *store_info; g_return_val_if_fail (store != NULL, NULL); g_mutex_lock (&cache->priv->store_info_ht_lock); store_info_ht = cache->priv->store_info_ht; store_info = g_hash_table_lookup (store_info_ht, store); if (store_info != NULL) { store_info_ref (store_info); g_hash_table_remove (store_info_ht, store); } g_mutex_unlock (&cache->priv->store_info_ht_lock); return store_info; } static FolderInfo * mail_folder_cache_ref_folder_info (MailFolderCache *cache, CamelStore *store, const gchar *folder_name) { StoreInfo *store_info; FolderInfo *folder_info = NULL; store_info = mail_folder_cache_ref_store_info (cache, store); if (store_info != NULL) { folder_info = store_info_ref_folder_info ( store_info, folder_name); store_info_unref (store_info); } return folder_info; } static FolderInfo * mail_folder_cache_steal_folder_info (MailFolderCache *cache, CamelStore *store, const gchar *folder_name) { StoreInfo *store_info; FolderInfo *folder_info = NULL; store_info = mail_folder_cache_ref_store_info (cache, store); if (store_info != NULL) { folder_info = store_info_steal_folder_info ( store_info, folder_name); store_info_unref (store_info); } return folder_info; } static gboolean mail_folder_cache_update_idle_cb (gpointer user_data) { MailFolderCache *cache; UpdateClosure *closure; closure = (UpdateClosure *) user_data; /* Sanity checks. */ g_return_val_if_fail (closure->full_name != NULL, FALSE); cache = g_weak_ref_get (&closure->cache); if (cache != NULL) { if (closure->signal_id == signals[FOLDER_DELETED]) { g_signal_emit ( cache, closure->signal_id, 0, closure->store, closure->full_name); } if (closure->signal_id == signals[FOLDER_UNAVAILABLE]) { g_signal_emit ( cache, closure->signal_id, 0, closure->store, closure->full_name); } if (closure->signal_id == signals[FOLDER_AVAILABLE]) { g_signal_emit ( cache, closure->signal_id, 0, closure->store, closure->full_name); } if (closure->signal_id == signals[FOLDER_RENAMED]) { g_signal_emit ( cache, closure->signal_id, 0, closure->store, closure->oldfull, closure->full_name); } /* update unread counts */ g_signal_emit ( cache, signals[FOLDER_UNREAD_UPDATED], 0, closure->store, closure->full_name, closure->unread); /* XXX The old code excluded this on FOLDER_RENAMED. * Not sure if that was intentional (if so it was * very subtle!) but we'll preserve the behavior. * If it turns out to be a bug then just remove * the signal_id check. */ if (closure->signal_id != signals[FOLDER_RENAMED]) { g_signal_emit ( cache, signals[FOLDER_CHANGED], 0, closure->store, closure->full_name, closure->new_messages, closure->msg_uid, closure->msg_sender, closure->msg_subject); } if (CAMEL_IS_VEE_STORE (closure->store) && (closure->signal_id == signals[FOLDER_AVAILABLE] || closure->signal_id == signals[FOLDER_RENAMED])) { /* Normally the vfolder store takes care of the * folder_opened event itself, but we add folder to * the noting system later, thus we do not know about * search folders to update them in a tree, thus * ensure their changes will be tracked correctly. */ CamelFolder *folder; /* FIXME camel_store_get_folder_sync() may block. */ folder = camel_store_get_folder_sync ( closure->store, closure->full_name, 0, NULL, NULL); if (folder != NULL) { mail_folder_cache_note_folder (cache, folder); g_object_unref (folder); } } g_object_unref (cache); } return FALSE; } static void mail_folder_cache_submit_update (UpdateClosure *closure) { GMainContext *main_context; MailFolderCache *cache; GSource *idle_source; g_return_if_fail (closure != NULL); cache = g_weak_ref_get (&closure->cache); g_return_if_fail (cache != NULL); main_context = mail_folder_cache_ref_main_context (cache); idle_source = g_idle_source_new (); g_source_set_callback ( idle_source, mail_folder_cache_update_idle_cb, closure, (GDestroyNotify) update_closure_free); g_source_attach (idle_source, main_context); g_source_unref (idle_source); g_main_context_unref (main_context); g_object_unref (cache); } /* This is how unread counts work (and don't work): * * camel_folder_unread_message_count() only gives a correct answer if * the store is paying attention to the folder. (Some stores always * pay attention to all folders, but IMAP can only pay attention to * one folder at a time.) But it doesn't have any way to know when * it's lying, so it's only safe to call it when you know for sure * that the store is paying attention to the folder, such as when it's * just been created, or you get a folder_changed signal on it. * * camel_store_get_folder_info() always gives correct answers for the * folders it checks, but it can also return -1 for a folder, meaning * it didn't check, and so you should stick with your previous answer. * * update_1folder is called from three places: with info != NULL when * the folder is created (or get_folder_info), with info == NULL when * a folder changed event is emitted. * * So if info is NULL, camel_folder_unread_message_count is correct, * and if it's not NULL and its unread_message_count isn't -1, then * it's correct. */ static void update_1folder (MailFolderCache *cache, FolderInfo *folder_info, gint new_messages, const gchar *msg_uid, const gchar *msg_sender, const gchar *msg_subject, CamelFolderInfo *info) { ESourceRegistry *registry; CamelService *service; CamelSession *session; CamelFolder *folder; gint unread = -1; gint deleted; /* XXX This is a dirty way to obtain the ESourceRegistry, * but it avoids MailFolderCache requiring it up front * in mail_folder_cache_new(), which just complicates * application startup even more. */ service = CAMEL_SERVICE (folder_info->store); session = camel_service_ref_session (service); registry = e_mail_session_get_registry (E_MAIL_SESSION (session)); g_object_unref (session); g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); folder = g_weak_ref_get (&folder_info->folder); if (folder != NULL) { gboolean folder_is_sent; gboolean folder_is_drafts; gboolean folder_is_outbox; gboolean folder_is_vtrash; gboolean special_case; folder_is_sent = em_utils_folder_is_sent (registry, folder); folder_is_drafts = em_utils_folder_is_drafts (registry, folder); folder_is_outbox = em_utils_folder_is_outbox (registry, folder); folder_is_vtrash = CAMEL_IS_VTRASH_FOLDER (folder); special_case = (cache->priv->count_trash && folder_is_vtrash) || (cache->priv->count_sent && folder_is_sent) || folder_is_drafts || folder_is_outbox; if (special_case) { d (printf (" total count\n")); unread = camel_folder_get_message_count (folder); if (folder_is_drafts || folder_is_outbox) { guint32 junked = 0; deleted = camel_folder_get_deleted_message_count (folder); if (deleted > 0) unread -= deleted; junked = camel_folder_summary_get_junk_count (folder->summary); if (junked > 0) unread -= junked; } } else { d (printf (" unread count\n")); if (info) unread = info->unread; else unread = camel_folder_get_unread_message_count (folder); } g_object_unref (folder); } d (printf ( "folder updated: unread %d: '%s'\n", unread, folder_info->full_name)); if (unread >= 0) { UpdateClosure *up; up = update_closure_new (cache, folder_info->store); up->full_name = g_strdup (folder_info->full_name); up->unread = unread; up->new_messages = new_messages; up->msg_uid = g_strdup (msg_uid); up->msg_sender = g_strdup (msg_sender); up->msg_subject = g_strdup (msg_subject); mail_folder_cache_submit_update (up); } } static void folder_changed_cb (CamelFolder *folder, CamelFolderChangeInfo *changes, MailFolderCache *cache) { static GHashTable *last_newmail_per_folder = NULL; time_t latest_received, new_latest_received; CamelFolder *local_drafts; CamelFolder *local_outbox; CamelFolder *local_sent; CamelSession *session; CamelStore *parent_store; CamelMessageInfo *info; FolderInfo *folder_info; const gchar *full_name; gint new = 0; gint i; guint32 flags; gchar *uid = NULL, *sender = NULL, *subject = NULL; full_name = camel_folder_get_full_name (folder); parent_store = camel_folder_get_parent_store (folder); session = camel_service_ref_session (CAMEL_SERVICE (parent_store)); if (last_newmail_per_folder == NULL) last_newmail_per_folder = g_hash_table_new ( g_direct_hash, g_direct_equal); /* it's fine to hash them by folder pointer here */ latest_received = GPOINTER_TO_INT ( g_hash_table_lookup (last_newmail_per_folder, folder)); new_latest_received = latest_received; local_drafts = e_mail_session_get_local_folder ( E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_DRAFTS); local_outbox = e_mail_session_get_local_folder ( E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_OUTBOX); local_sent = e_mail_session_get_local_folder ( E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_SENT); if (!CAMEL_IS_VEE_FOLDER (folder) && folder != local_drafts && folder != local_outbox && folder != local_sent && changes && (changes->uid_added->len > 0)) { /* for each added message, check to see that it is * brand new, not junk and not already deleted */ for (i = 0; i < changes->uid_added->len; i++) { info = camel_folder_get_message_info ( folder, changes->uid_added->pdata[i]); if (info) { flags = camel_message_info_flags (info); if (((flags & CAMEL_MESSAGE_SEEN) == 0) && ((flags & CAMEL_MESSAGE_JUNK) == 0) && ((flags & CAMEL_MESSAGE_DELETED) == 0) && (camel_message_info_date_received (info) > latest_received)) { if (camel_message_info_date_received (info) > new_latest_received) new_latest_received = camel_message_info_date_received (info); new++; if (new == 1) { uid = g_strdup (camel_message_info_uid (info)); sender = g_strdup (camel_message_info_from (info)); subject = g_strdup (camel_message_info_subject (info)); } else { g_free (uid); g_free (sender); g_free (subject); uid = NULL; sender = NULL; subject = NULL; } } camel_message_info_unref (info); } } } if (new > 0) g_hash_table_insert ( last_newmail_per_folder, folder, GINT_TO_POINTER (new_latest_received)); folder_info = mail_folder_cache_ref_folder_info ( cache, parent_store, full_name); if (folder_info != NULL) { update_1folder ( cache, folder_info, new, uid, sender, subject, NULL); folder_info_unref (folder_info); } g_free (uid); g_free (sender); g_free (subject); g_object_unref (session); } static void unset_folder_info (MailFolderCache *cache, FolderInfo *folder_info, gint delete) { d (printf ("unset folderinfo '%s'\n", folder_info->uri)); folder_info_clear_folder (folder_info); if ((folder_info->flags & CAMEL_FOLDER_NOSELECT) == 0) { UpdateClosure *up; up = update_closure_new (cache, folder_info->store); up->full_name = g_strdup (folder_info->full_name); if (delete) up->signal_id = signals[FOLDER_DELETED]; else up->signal_id = signals[FOLDER_UNAVAILABLE]; mail_folder_cache_submit_update (up); } } static void setup_folder (MailFolderCache *cache, CamelFolderInfo *fi, StoreInfo *store_info) { FolderInfo *folder_info; folder_info = store_info_ref_folder_info (store_info, fi->full_name); if (folder_info != NULL) { update_1folder (cache, folder_info, 0, NULL, NULL, NULL, fi); folder_info_unref (folder_info); } else { UpdateClosure *up; folder_info = folder_info_new ( store_info->store, fi->full_name, fi->flags); store_info_insert_folder_info (store_info, folder_info); up = update_closure_new (cache, store_info->store); up->full_name = g_strdup (fi->full_name); up->unread = fi->unread; if ((fi->flags & CAMEL_FOLDER_NOSELECT) == 0) up->signal_id = signals[FOLDER_AVAILABLE]; mail_folder_cache_submit_update (up); folder_info_unref (folder_info); } } static void create_folders (MailFolderCache *cache, CamelFolderInfo *fi, StoreInfo *store_info) { while (fi) { setup_folder (cache, fi, store_info); if (fi->child) create_folders (cache, fi->child, store_info); fi = fi->next; } } static void store_folder_subscribed_cb (CamelStore *store, CamelFolderInfo *info, MailFolderCache *cache) { StoreInfo *store_info; store_info = mail_folder_cache_ref_store_info (cache, store); if (store_info != NULL) { setup_folder (cache, info, store_info); store_info_unref (store_info); } } static void store_folder_created_cb (CamelStore *store, CamelFolderInfo *info, MailFolderCache *cache) { /* We only want created events to do more work * if we dont support subscriptions. */ if (!CAMEL_IS_SUBSCRIBABLE (store)) store_folder_subscribed_cb (store, info, cache); } static void store_folder_opened_cb (CamelStore *store, CamelFolder *folder, MailFolderCache *cache) { mail_folder_cache_note_folder (cache, folder); } static void store_folder_unsubscribed_cb (CamelStore *store, CamelFolderInfo *info, MailFolderCache *cache) { FolderInfo *folder_info; folder_info = mail_folder_cache_steal_folder_info ( cache, store, info->full_name); if (folder_info != NULL) { unset_folder_info (cache, folder_info, TRUE); folder_info_unref (folder_info); } } static void store_folder_deleted_cb (CamelStore *store, CamelFolderInfo *info, MailFolderCache *cache) { /* We only want deleted events to do more work * if we dont support subscriptions. */ if (!CAMEL_IS_SUBSCRIBABLE (store)) store_folder_unsubscribed_cb (store, info, cache); } static void rename_folders (MailFolderCache *cache, StoreInfo *store_info, const gchar *oldbase, const gchar *newbase, CamelFolderInfo *fi) { gchar *old, *olduri, *oldfile, *newuri, *newfile; FolderInfo *old_folder_info; FolderInfo *new_folder_info; UpdateClosure *up; const gchar *config_dir; up = update_closure_new (cache, store_info->store); up->signal_id = signals[FOLDER_AVAILABLE]; /* Form what was the old name, and try and look it up */ old = g_strdup_printf ("%s%s", oldbase, fi->full_name + strlen (newbase)); old_folder_info = store_info_steal_folder_info (store_info, old); if (old_folder_info != NULL) { up->oldfull = g_strdup (old_folder_info->full_name); up->signal_id = signals[FOLDER_RENAMED]; folder_info_unref (old_folder_info); } new_folder_info = folder_info_new ( store_info->store, fi->full_name, fi->flags); store_info_insert_folder_info (store_info, new_folder_info); folder_info_unref (new_folder_info); up->full_name = g_strdup (fi->full_name); up->unread = fi->unread==-1 ? 0 : fi->unread; /* No signal emission for NOSELECT folders. */ if ((fi->flags & CAMEL_FOLDER_NOSELECT) != 0) up->signal_id = 0; mail_folder_cache_submit_update (up); /* rename the meta-data we maintain ourselves */ config_dir = mail_session_get_config_dir (); olduri = e_mail_folder_uri_build (store_info->store, old); e_filename_make_safe (olduri); newuri = e_mail_folder_uri_build (store_info->store, fi->full_name); e_filename_make_safe (newuri); oldfile = g_strdup_printf ("%s/custom_view-%s.xml", config_dir, olduri); newfile = g_strdup_printf ("%s/custom_view-%s.xml", config_dir, newuri); if (g_rename (oldfile, newfile) == -1) { g_warning ("%s: Failed to rename '%s' to '%s': %s", G_STRFUNC, oldfile, newfile, g_strerror (errno)); } g_free (oldfile); g_free (newfile); oldfile = g_strdup_printf ("%s/current_view-%s.xml", config_dir, olduri); newfile = g_strdup_printf ("%s/current_view-%s.xml", config_dir, newuri); if (g_rename (oldfile, newfile) == -1) { g_warning ("%s: Failed to rename '%s' to '%s': %s", G_STRFUNC, oldfile, newfile, g_strerror (errno)); } g_free (oldfile); g_free (newfile); g_free (olduri); g_free (newuri); g_free (old); } static void get_folders (CamelFolderInfo *fi, GPtrArray *folders) { while (fi) { g_ptr_array_add (folders, fi); if (fi->child) get_folders (fi->child, folders); fi = fi->next; } } static gint folder_cmp (gconstpointer ap, gconstpointer bp) { const CamelFolderInfo *a = ((CamelFolderInfo **) ap)[0]; const CamelFolderInfo *b = ((CamelFolderInfo **) bp)[0]; return strcmp (a->full_name, b->full_name); } static void store_folder_renamed_cb (CamelStore *store, const gchar *old_name, CamelFolderInfo *info, MailFolderCache *cache) { StoreInfo *store_info; store_info = mail_folder_cache_ref_store_info (cache, store); if (store_info != NULL) { GPtrArray *folders = g_ptr_array_new (); CamelFolderInfo *top; gint ii; /* Ok, so for some reason the folderinfo we have comes in all * messed up from imap, should find out why ... this makes it * workable. * XXX This refers to the old IMAP backend, not IMAPX, and so * this may not be needed anymore. */ get_folders (info, folders); g_ptr_array_sort (folders, (GCompareFunc) folder_cmp); top = folders->pdata[0]; for (ii = 0; ii < folders->len; ii++) { rename_folders ( cache, store_info, old_name, top->full_name, folders->pdata[ii]); } g_ptr_array_free (folders, TRUE); store_info_unref (store_info); } } struct _ping_store_msg { MailMsg base; CamelStore *store; }; static gchar * ping_store_desc (struct _ping_store_msg *m) { gchar *service_name; gchar *msg; service_name = camel_service_get_name (CAMEL_SERVICE (m->store), TRUE); msg = g_strdup_printf (_("Pinging %s"), service_name); g_free (service_name); return msg; } static void ping_store_exec (struct _ping_store_msg *m, GCancellable *cancellable, GError **error) { CamelServiceConnectionStatus status; CamelService *service; gboolean online = FALSE; service = CAMEL_SERVICE (m->store); status = camel_service_get_connection_status (service); if (status == CAMEL_SERVICE_CONNECTED) { if (CAMEL_IS_OFFLINE_STORE (m->store) && camel_offline_store_get_online ( CAMEL_OFFLINE_STORE (m->store))) online = TRUE; } if (online) camel_store_noop_sync (m->store, cancellable, error); } static void ping_store_free (struct _ping_store_msg *m) { g_object_unref (m->store); } static MailMsgInfo ping_store_info = { sizeof (struct _ping_store_msg), (MailMsgDescFunc) ping_store_desc, (MailMsgExecFunc) ping_store_exec, (MailMsgDoneFunc) NULL, (MailMsgFreeFunc) ping_store_free }; static gboolean ping_cb (gpointer user_data) { MailFolderCache *cache; GList *list, *link; cache = MAIL_FOLDER_CACHE (user_data); list = mail_folder_cache_list_stores (cache); for (link = list; link != NULL; link = g_list_next (link)) { CamelService *service; CamelServiceConnectionStatus status; struct _ping_store_msg *m; service = CAMEL_SERVICE (link->data); status = camel_service_get_connection_status (service); if (status != CAMEL_SERVICE_CONNECTED) continue; m = mail_msg_new (&ping_store_info); m->store = g_object_ref (service); mail_msg_slow_ordered_push (m); } g_list_free_full (list, (GDestroyNotify) g_object_unref); return TRUE; } static gboolean store_has_folder_hierarchy (CamelStore *store) { CamelProvider *provider; g_return_val_if_fail (store != NULL, FALSE); provider = camel_service_get_provider (CAMEL_SERVICE (store)); g_return_val_if_fail (provider != NULL, FALSE); if (provider->flags & CAMEL_PROVIDER_IS_STORAGE) return TRUE; if (provider->flags & CAMEL_PROVIDER_IS_EXTERNAL) return TRUE; return FALSE; } static GList * find_folder_uri (GQueue *queue, CamelSession *session, const gchar *folder_uri) { GList *head, *link; head = g_queue_peek_head_link (queue); for (link = head; link != NULL; link = g_list_next (link)) if (e_mail_folder_uri_equal (session, link->data, folder_uri)) break; return link; } static void mail_folder_cache_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_MAIN_CONTEXT: g_value_take_boxed ( value, mail_folder_cache_ref_main_context ( MAIL_FOLDER_CACHE (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void mail_folder_cache_dispose (GObject *object) { MailFolderCachePrivate *priv; priv = MAIL_FOLDER_CACHE_GET_PRIVATE (object); g_hash_table_remove_all (priv->store_info_ht); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (mail_folder_cache_parent_class)->dispose (object); } static void mail_folder_cache_finalize (GObject *object) { MailFolderCachePrivate *priv; priv = MAIL_FOLDER_CACHE_GET_PRIVATE (object); g_main_context_unref (priv->main_context); g_hash_table_destroy (priv->store_info_ht); g_mutex_clear (&priv->store_info_ht_lock); if (priv->ping_id > 0) { g_source_remove (priv->ping_id); priv->ping_id = 0; } while (!g_queue_is_empty (&priv->local_folder_uris)) g_free (g_queue_pop_head (&priv->local_folder_uris)); while (!g_queue_is_empty (&priv->remote_folder_uris)) g_free (g_queue_pop_head (&priv->remote_folder_uris)); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (mail_folder_cache_parent_class)->finalize (object); } static void mail_folder_cache_folder_available (MailFolderCache *cache, CamelStore *store, const gchar *folder_name) { CamelService *service; CamelSession *session; CamelProvider *provider; GQueue *queue; gchar *folder_uri; /* Disregard virtual stores. */ if (CAMEL_IS_VEE_STORE (store)) return; /* Disregard virtual Junk folders. */ if (store->flags & CAMEL_STORE_VJUNK) if (g_strcmp0 (folder_name, CAMEL_VJUNK_NAME) == 0) return; /* Disregard virtual Trash folders. */ if (store->flags & CAMEL_STORE_VTRASH) if (g_strcmp0 (folder_name, CAMEL_VTRASH_NAME) == 0) return; service = CAMEL_SERVICE (store); session = camel_service_ref_session (service); provider = camel_service_get_provider (service); /* Reuse the store info lock just because it's handy. */ g_mutex_lock (&cache->priv->store_info_ht_lock); folder_uri = e_mail_folder_uri_build (store, folder_name); if (provider->flags & CAMEL_PROVIDER_IS_REMOTE) queue = &cache->priv->remote_folder_uris; else queue = &cache->priv->local_folder_uris; if (find_folder_uri (queue, session, folder_uri) == NULL) g_queue_push_tail (queue, folder_uri); else g_free (folder_uri); g_mutex_unlock (&cache->priv->store_info_ht_lock); g_object_unref (session); } static void mail_folder_cache_folder_unavailable (MailFolderCache *cache, CamelStore *store, const gchar *folder_name) { CamelService *service; CamelSession *session; CamelProvider *provider; GQueue *queue; GList *link; gchar *folder_uri; /* Disregard virtual stores. */ if (CAMEL_IS_VEE_STORE (store)) return; /* Disregard virtual Junk folders. */ if (store->flags & CAMEL_STORE_VJUNK) if (g_strcmp0 (folder_name, CAMEL_VJUNK_NAME) == 0) return; /* Disregard virtual Trash folders. */ if (store->flags & CAMEL_STORE_VTRASH) if (g_strcmp0 (folder_name, CAMEL_VTRASH_NAME) == 0) return; service = CAMEL_SERVICE (store); session = camel_service_ref_session (service); provider = camel_service_get_provider (service); /* Reuse the store info lock just because it's handy. */ g_mutex_lock (&cache->priv->store_info_ht_lock); folder_uri = e_mail_folder_uri_build (store, folder_name); if (provider->flags & CAMEL_PROVIDER_IS_REMOTE) queue = &cache->priv->remote_folder_uris; else queue = &cache->priv->local_folder_uris; link = find_folder_uri (queue, session, folder_uri); if (link != NULL) { g_free (link->data); g_queue_delete_link (queue, link); } g_free (folder_uri); g_mutex_unlock (&cache->priv->store_info_ht_lock); g_object_unref (session); } static void mail_folder_cache_folder_deleted (MailFolderCache *cache, CamelStore *store, const gchar *folder_name) { CamelService *service; CamelSession *session; GQueue *queue; GList *link; gchar *folder_uri; /* Disregard virtual stores. */ if (CAMEL_IS_VEE_STORE (store)) return; /* Disregard virtual Junk folders. */ if (store->flags & CAMEL_STORE_VJUNK) if (g_strcmp0 (folder_name, CAMEL_VJUNK_NAME) == 0) return; /* Disregard virtual Trash folders. */ if (store->flags & CAMEL_STORE_VTRASH) if (g_strcmp0 (folder_name, CAMEL_VTRASH_NAME) == 0) return; service = CAMEL_SERVICE (store); session = camel_service_ref_session (service); /* Reuse the store info lock just because it's handy. */ g_mutex_lock (&cache->priv->store_info_ht_lock); folder_uri = e_mail_folder_uri_build (store, folder_name); queue = &cache->priv->local_folder_uris; link = find_folder_uri (queue, session, folder_uri); if (link != NULL) { g_free (link->data); g_queue_delete_link (queue, link); } queue = &cache->priv->remote_folder_uris; link = find_folder_uri (queue, session, folder_uri); if (link != NULL) { g_free (link->data); g_queue_delete_link (queue, link); } g_free (folder_uri); g_mutex_unlock (&cache->priv->store_info_ht_lock); g_object_unref (session); } static void mail_folder_cache_class_init (MailFolderCacheClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (MailFolderCachePrivate)); object_class = G_OBJECT_CLASS (class); object_class->get_property = mail_folder_cache_get_property; object_class->dispose = mail_folder_cache_dispose; object_class->finalize = mail_folder_cache_finalize; class->folder_available = mail_folder_cache_folder_available; class->folder_unavailable = mail_folder_cache_folder_unavailable; class->folder_deleted = mail_folder_cache_folder_deleted; g_object_class_install_property ( object_class, PROP_MAIN_CONTEXT, g_param_spec_boxed ( "main-context", "Main Context", "The main loop context on " "which to attach event sources", G_TYPE_MAIN_CONTEXT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * MailFolderCache::folder-available * @store: the #CamelStore containing the folder * @folder_name: the name of the folder * * Emitted when a folder becomes available **/ signals[FOLDER_AVAILABLE] = g_signal_new ( "folder-available", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MailFolderCacheClass, folder_available), NULL, NULL, NULL, G_TYPE_NONE, 2, CAMEL_TYPE_STORE, G_TYPE_STRING); /** * MailFolderCache::folder-unavailable * @store: the #CamelStore containing the folder * @folder_name: the name of the folder * * Emitted when a folder becomes unavailable. This represents a * transient condition. See MailFolderCache::folder-deleted to be * notified when a folder is permanently removed. **/ signals[FOLDER_UNAVAILABLE] = g_signal_new ( "folder-unavailable", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MailFolderCacheClass, folder_unavailable), NULL, NULL, NULL, G_TYPE_NONE, 2, CAMEL_TYPE_STORE, G_TYPE_STRING); /** * MailFolderCache::folder-deleted * @store: the #CamelStore containing the folder * @folder_name: the name of the folder * * Emitted when a folder is deleted **/ signals[FOLDER_DELETED] = g_signal_new ( "folder-deleted", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MailFolderCacheClass, folder_deleted), NULL, NULL, /* accumulator */ NULL, G_TYPE_NONE, 2, CAMEL_TYPE_STORE, G_TYPE_STRING); /** * MailFolderCache::folder-renamed * @store: the #CamelStore containing the folder * @old_folder_name: the old name of the folder * @new_folder_name: the new name of the folder * * Emitted when a folder is renamed **/ signals[FOLDER_RENAMED] = g_signal_new ( "folder-renamed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MailFolderCacheClass, folder_renamed), NULL, NULL, NULL, G_TYPE_NONE, 3, CAMEL_TYPE_STORE, G_TYPE_STRING, G_TYPE_STRING); /** * MailFolderCache::folder-unread-updated * @store: the #CamelStore containing the folder * @folder_name: the name of the folder * @unread: the number of unread mails in the folder * * Emitted when a we receive an update to the unread count for a folder **/ signals[FOLDER_UNREAD_UPDATED] = g_signal_new ( "folder-unread-updated", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MailFolderCacheClass, folder_unread_updated), NULL, NULL, NULL, G_TYPE_NONE, 3, CAMEL_TYPE_STORE, G_TYPE_STRING, G_TYPE_INT); /** * MailFolderCache::folder-changed * @store: the #CamelStore containing the folder * @folder_name: the name of the folder * @new_messages: the number of new messages for the folder * @msg_uid: uid of the new message, or NULL * @msg_sender: sender of the new message, or NULL * @msg_subject: subject of the new message, or NULL * * Emitted when a folder has changed. If @new_messages is not * exactly 1, @msg_uid, @msg_sender, and @msg_subject will be NULL. **/ signals[FOLDER_CHANGED] = g_signal_new ( "folder-changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MailFolderCacheClass, folder_changed), NULL, NULL, NULL, G_TYPE_NONE, 6, CAMEL_TYPE_STORE, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); } static void mail_folder_cache_init (MailFolderCache *cache) { GHashTable *store_info_ht; const gchar *buf; guint timeout; store_info_ht = g_hash_table_new_full ( (GHashFunc) g_direct_hash, (GEqualFunc) g_direct_equal, (GDestroyNotify) g_object_unref, (GDestroyNotify) store_info_unref); cache->priv = MAIL_FOLDER_CACHE_GET_PRIVATE (cache); cache->priv->main_context = g_main_context_ref_thread_default (); cache->priv->store_info_ht = store_info_ht; g_mutex_init (&cache->priv->store_info_ht_lock); cache->priv->count_sent = getenv ("EVOLUTION_COUNT_SENT") != NULL; cache->priv->count_trash = getenv ("EVOLUTION_COUNT_TRASH") != NULL; buf = getenv ("EVOLUTION_PING_TIMEOUT"); timeout = buf ? strtoul (buf, NULL, 10) : 600; cache->priv->ping_id = e_named_timeout_add_seconds ( timeout, ping_cb, cache); g_queue_init (&cache->priv->local_folder_uris); g_queue_init (&cache->priv->remote_folder_uris); } MailFolderCache * mail_folder_cache_new (void) { return g_object_new (MAIL_TYPE_FOLDER_CACHE, NULL); } /** * mail_folder_cache_ref_main_context: * * Returns the #GMainContext on which event sources for @cache are to be * attached. * * The returned #GMainContext is referenced for thread-safety and should * be unreferenced with g_main_context_unref() when finished with it. * * Returns: a #GMainContext **/ GMainContext * mail_folder_cache_ref_main_context (MailFolderCache *cache) { g_return_val_if_fail (MAIL_IS_FOLDER_CACHE (cache), NULL); return g_main_context_ref (cache->priv->main_context); } /* Helper for mail_folder_cache_note_store() */ static void mail_folder_cache_first_update (MailFolderCache *cache, StoreInfo *store_info) { CamelService *service; CamelSession *session; const gchar *uid; service = CAMEL_SERVICE (store_info->store); session = camel_service_ref_session (service); uid = camel_service_get_uid (service); if (store_info->vjunk != NULL) mail_folder_cache_note_folder (cache, store_info->vjunk); if (store_info->vtrash != NULL) mail_folder_cache_note_folder (cache, store_info->vtrash); /* Some extra work for the "On This Computer" store. */ if (g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0) { CamelFolder *folder; gint ii; for (ii = 0; ii < E_MAIL_NUM_LOCAL_FOLDERS; ii++) { folder = e_mail_session_get_local_folder ( E_MAIL_SESSION (session), ii); mail_folder_cache_note_folder (cache, folder); } } g_object_unref (session); } /* Helper for mail_folder_cache_note_store() */ static void mail_folder_cache_note_store_thread (GSimpleAsyncResult *simple, GObject *source_object, GCancellable *cancellable) { MailFolderCache *cache; CamelService *service; CamelSession *session; StoreInfo *store_info; GQueue result_queue = G_QUEUE_INIT; AsyncContext *async_context; GError *local_error = NULL; cache = MAIL_FOLDER_CACHE (source_object); async_context = g_simple_async_result_get_op_res_gpointer (simple); store_info = async_context->store_info; service = CAMEL_SERVICE (store_info->store); session = camel_service_ref_session (service); /* We might get a race when setting up a store, such that it is * still left in offline mode, after we've gone online. This * catches and fixes it up when the shell opens us. * * XXX This is a Bonobo-era artifact. Do we really still need * to do this? */ if (camel_session_get_online (session)) { gboolean store_online = TRUE; if (CAMEL_IS_OFFLINE_STORE (service)) { store_online = camel_offline_store_get_online ( CAMEL_OFFLINE_STORE (service)); } if (!store_online) { e_mail_store_go_online_sync ( CAMEL_STORE (service), cancellable, &local_error); if (local_error != NULL) { g_simple_async_result_take_error ( simple, local_error); goto exit; } } } /* No folder hierarchy means we're done. */ if (!store_has_folder_hierarchy (store_info->store)) goto exit; /* XXX This can return NULL without setting a GError if no * folders match the search criteria or the store does * not support folders. * * The function signature should be changed to return a * boolean with the CamelFolderInfo returned through an * "out" parameter so it's easier to distinguish errors * from empty results. */ async_context->info = camel_store_get_folder_info_sync ( store_info->store, NULL, CAMEL_STORE_FOLDER_INFO_FAST | CAMEL_STORE_FOLDER_INFO_RECURSIVE | CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, cancellable, &local_error); if (local_error != NULL) { g_warn_if_fail (async_context->info == NULL); g_simple_async_result_take_error (simple, local_error); goto exit; } create_folders (cache, async_context->info, store_info); /* Do some extra work for the first update. */ if (store_info->first_update) { mail_folder_cache_first_update (cache, store_info); store_info->first_update = FALSE; } exit: /* We don't want finish() functions being invoked while holding a * locked mutex, so flush the StoreInfo's queue to a local queue. */ g_mutex_lock (&store_info->lock); e_queue_transfer (&store_info->folderinfo_updates, &result_queue); g_mutex_unlock (&store_info->lock); while (!g_queue_is_empty (&result_queue)) { GSimpleAsyncResult *queued_result; queued_result = g_queue_pop_head (&result_queue); /* Skip the GSimpleAsyncResult passed into this function. * g_simple_async_result_run_in_thread() will complete it * for us, and we don't want to complete it twice. */ if (queued_result != simple) g_simple_async_result_complete_in_idle (queued_result); g_clear_object (&queued_result); } g_object_unref (session); } /** * mail_folder_cache_note_store: * * Add a store whose folders should appear in the shell The folders are scanned * from the store, and/or added at runtime via the folder_created event. The * @done function returns if we can free folder info. */ void mail_folder_cache_note_store (MailFolderCache *cache, CamelStore *store, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { StoreInfo *store_info; GSimpleAsyncResult *simple; AsyncContext *async_context; g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache)); g_return_if_fail (CAMEL_IS_STORE (store)); store_info = mail_folder_cache_ref_store_info (cache, store); if (store_info == NULL) store_info = mail_folder_cache_new_store_info (cache, store); async_context = g_slice_new0 (AsyncContext); async_context->store_info = store_info_ref (store_info); simple = g_simple_async_result_new ( G_OBJECT (cache), callback, user_data, mail_folder_cache_note_store); g_simple_async_result_set_check_cancellable (simple, cancellable); g_simple_async_result_set_op_res_gpointer ( simple, async_context, (GDestroyNotify) async_context_free); g_mutex_lock (&store_info->lock); g_queue_push_tail ( &store_info->folderinfo_updates, g_object_ref (simple)); /* Queue length > 1 means there's already an operation for * this store in progress so we'll just pick up the result * when it finishes. */ if (g_queue_get_length (&store_info->folderinfo_updates) == 1) g_simple_async_result_run_in_thread ( simple, mail_folder_cache_note_store_thread, G_PRIORITY_DEFAULT, cancellable); g_mutex_unlock (&store_info->lock); g_object_unref (simple); store_info_unref (store_info); } gboolean mail_folder_cache_note_store_finish (MailFolderCache *cache, GAsyncResult *result, CamelFolderInfo **out_info, GError **error) { GSimpleAsyncResult *simple; AsyncContext *async_context; g_return_val_if_fail ( g_simple_async_result_is_valid ( result, G_OBJECT (cache), mail_folder_cache_note_store), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); async_context = g_simple_async_result_get_op_res_gpointer (simple); if (g_simple_async_result_propagate_error (simple, error)) return FALSE; if (out_info != NULL) { if (async_context->info != NULL) *out_info = camel_folder_info_clone ( async_context->info); else *out_info = NULL; } return TRUE; } /** * mail_folder_cache_note_folder: * * When a folder has been opened, notify it for watching. The folder must have * already been created on the store (which has already been noted) before the * folder can be opened */ void mail_folder_cache_note_folder (MailFolderCache *cache, CamelFolder *folder) { CamelStore *parent_store; CamelFolder *cached_folder; FolderInfo *folder_info; const gchar *full_name; g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache)); g_return_if_fail (CAMEL_IS_FOLDER (folder)); full_name = camel_folder_get_full_name (folder); parent_store = camel_folder_get_parent_store (folder); folder_info = mail_folder_cache_ref_folder_info ( cache, parent_store, full_name); /* XXX Not sure we should just be returning quietly here, but * the old code did. Using g_return_if_fail() causes a few * warnings on startup which might be worth tracking down. */ if (folder_info == NULL) return; g_mutex_lock (&folder_info->lock); cached_folder = g_weak_ref_get (&folder_info->folder); if (cached_folder != NULL) { g_signal_handler_disconnect ( cached_folder, folder_info->folder_changed_handler_id); g_object_unref (cached_folder); } g_weak_ref_set (&folder_info->folder, folder); update_1folder (cache, folder_info, 0, NULL, NULL, NULL, NULL); folder_info->folder_changed_handler_id = g_signal_connect ( folder, "changed", G_CALLBACK (folder_changed_cb), cache); g_mutex_unlock (&folder_info->lock); folder_info_unref (folder_info); } /** * mail_folder_cache_has_folder_info: * @cache: a #MailFolderCache * @store: a #CamelStore * @folder_name: a folder name * * Returns whether @cache has information about the folder described by * @store and @folder_name. This does not necessarily mean it has the * #CamelFolder instance, but it at least has some meta-data about it. * * You can use this function as a folder existence test. * * Returns: %TRUE if @cache has folder info, %FALSE otherwise **/ gboolean mail_folder_cache_has_folder_info (MailFolderCache *cache, CamelStore *store, const gchar *folder_name) { FolderInfo *folder_info; gboolean has_info = FALSE; g_return_val_if_fail (MAIL_IS_FOLDER_CACHE (cache), FALSE); g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE); g_return_val_if_fail (folder_name != NULL, FALSE); folder_info = mail_folder_cache_ref_folder_info ( cache, store, folder_name); if (folder_info != NULL) { folder_info_unref (folder_info); has_info = TRUE; } return has_info; } /** * mail_folder_cache_ref_folder: * @cache: a #MailFolderCache * @store: a #CamelStore * @folder_name: a folder name * * Returns the #CamelFolder for @store and @folder_name if available, or * else %NULL if a #CamelFolder instance is not yet cached. This function * does not block. * * The returned #CamelFolder is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: a #CamelFolder, or %NULL **/ CamelFolder * mail_folder_cache_ref_folder (MailFolderCache *cache, CamelStore *store, const gchar *folder_name) { FolderInfo *folder_info; CamelFolder *folder = NULL; g_return_val_if_fail (MAIL_IS_FOLDER_CACHE (cache), NULL); g_return_val_if_fail (CAMEL_IS_STORE (store), NULL); g_return_val_if_fail (folder_name != NULL, NULL); folder_info = mail_folder_cache_ref_folder_info ( cache, store, folder_name); if (folder_info != NULL) { folder = g_weak_ref_get (&folder_info->folder); folder_info_unref (folder_info); } return folder; } /** * mail_folder_cache_get_folder_info_flags: * @cache: a #MailFolderCache * @store: a #CamelStore * @folder_name: a folder name * @flags: return location for #CamelFolderInfoFlags * * Obtains #CamelFolderInfoFlags for @store and @folder_name if available, * and returns %TRUE to indicate @flags was set. If no folder information * is available for @store and @folder_name, the function returns %FALSE. * * Returns: whether @flags was set **/ gboolean mail_folder_cache_get_folder_info_flags (MailFolderCache *cache, CamelStore *store, const gchar *folder_name, CamelFolderInfoFlags *flags) { FolderInfo *folder_info; gboolean flags_set = FALSE; g_return_val_if_fail (MAIL_IS_FOLDER_CACHE (cache), FALSE); g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE); g_return_val_if_fail (folder_name != NULL, FALSE); g_return_val_if_fail (flags != NULL, FALSE); folder_info = mail_folder_cache_ref_folder_info ( cache, store, folder_name); if (folder_info != NULL) { *flags = folder_info->flags; folder_info_unref (folder_info); flags_set = TRUE; } return flags_set; } void mail_folder_cache_get_local_folder_uris (MailFolderCache *cache, GQueue *out_queue) { GList *head, *link; g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache)); g_return_if_fail (out_queue != NULL); /* Reuse the store_info_ht_lock just because it's handy. */ g_mutex_lock (&cache->priv->store_info_ht_lock); head = g_queue_peek_head_link (&cache->priv->local_folder_uris); for (link = head; link != NULL; link = g_list_next (link)) g_queue_push_tail (out_queue, g_strdup (link->data)); g_mutex_unlock (&cache->priv->store_info_ht_lock); } void mail_folder_cache_get_remote_folder_uris (MailFolderCache *cache, GQueue *out_queue) { GList *head, *link; g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache)); g_return_if_fail (out_queue != NULL); /* Reuse the store_info_ht_lock just because it's handy. */ g_mutex_lock (&cache->priv->store_info_ht_lock); head = g_queue_peek_head_link (&cache->priv->remote_folder_uris); for (link = head; link != NULL; link = g_list_next (link)) g_queue_push_tail (out_queue, g_strdup (link->data)); g_mutex_unlock (&cache->priv->store_info_ht_lock); } void mail_folder_cache_service_removed (MailFolderCache *cache, CamelService *service) { StoreInfo *store_info; g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache)); g_return_if_fail (CAMEL_IS_SERVICE (service)); if (!CAMEL_IS_STORE (service)) return; store_info = mail_folder_cache_steal_store_info ( cache, CAMEL_STORE (service)); if (store_info != NULL) { GList *list, *link; list = store_info_list_folder_info (store_info); for (link = list; link != NULL; link = g_list_next (link)) unset_folder_info (cache, link->data, FALSE); g_list_free_full (list, (GDestroyNotify) folder_info_unref); store_info_unref (store_info); } } void mail_folder_cache_service_enabled (MailFolderCache *cache, CamelService *service) { g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache)); g_return_if_fail (CAMEL_IS_SERVICE (service)); /* XXX This has no callback and it swallows errors. Maybe * we don't want a service_enabled() function after all? * Call mail_folder_cache_note_store() directly instead * and handle errors appropriately. */ mail_folder_cache_note_store ( cache, CAMEL_STORE (service), NULL, NULL, NULL); } void mail_folder_cache_service_disabled (MailFolderCache *cache, CamelService *service) { g_return_if_fail (MAIL_IS_FOLDER_CACHE (cache)); g_return_if_fail (CAMEL_IS_SERVICE (service)); /* To the folder cache, disabling a service is the same as * removing it. We keep a separate callback function only * to use as a breakpoint target in a debugger. */ mail_folder_cache_service_removed (cache, service); }