The previously used g_hash_table_insert() replaces only value for keys which are already included in the hash table, but as the key is owned by the value and freed together with the value, then here should be used g_hash_table_replace(), which replaces both key and value, thus avoids the use-after-free on the hash table's key.
2198 lines
58 KiB
C
2198 lines
58 KiB
C
/*
|
|
* 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.
|
|
*
|
|
* 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 General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
* Authors:
|
|
* Peter Williams <peterw@ximian.com>
|
|
* Michael Zucchi <notzed@ximian.com>
|
|
* Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
|
|
*
|
|
* 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 <config.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <glib/gi18n.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
#include <libedataserver/libedataserver.h>
|
|
|
|
#include <libemail-engine/mail-mt.h>
|
|
|
|
#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;
|
|
|
|
/* 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 */
|
|
GSList *pending_folder_notes; /* Gather note_folder calls during first_update period */
|
|
|
|
/* 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_slist_free_full (store_info->pending_folder_notes, g_object_unref);
|
|
|
|
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;
|
|
|
|
/* Replace both key and value, because the key gets freed as soon as the value */
|
|
g_hash_table_replace (
|
|
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 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 && errno != ENOENT) {
|
|
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 && errno != ENOENT) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
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;
|
|
GSList *folders, *iter;
|
|
|
|
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);
|
|
|
|
g_mutex_lock (&store_info->lock);
|
|
store_info->first_update = FALSE;
|
|
folders = store_info->pending_folder_notes;
|
|
store_info->pending_folder_notes = NULL;
|
|
g_mutex_unlock (&store_info->lock);
|
|
|
|
for (iter = folders; iter; iter = g_slist_next (iter)) {
|
|
mail_folder_cache_note_folder (cache, iter->data);
|
|
}
|
|
|
|
g_slist_free_full (folders, g_object_unref);
|
|
}
|
|
|
|
/* 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. */
|
|
g_mutex_lock (&store_info->lock);
|
|
if (store_info->first_update) {
|
|
g_mutex_unlock (&store_info->lock);
|
|
mail_folder_cache_first_update (cache, store_info);
|
|
} else {
|
|
g_mutex_unlock (&store_info->lock);
|
|
}
|
|
|
|
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) {
|
|
StoreInfo *store_info;
|
|
gboolean retry = FALSE;
|
|
|
|
store_info = mail_folder_cache_ref_store_info (cache, parent_store);
|
|
if (!store_info)
|
|
return;
|
|
|
|
g_mutex_lock (&store_info->lock);
|
|
if (store_info->first_update) {
|
|
/* The first update did not finish yet, thus add this as a pending
|
|
folder to be noted once the first update finishes */
|
|
store_info->pending_folder_notes = g_slist_prepend (
|
|
store_info->pending_folder_notes, g_object_ref (folder));
|
|
} else {
|
|
/* It can be that certain threading interleaving made
|
|
the first store update finished before we reached
|
|
this place, thus retry to get the folder info */
|
|
retry = TRUE;
|
|
}
|
|
g_mutex_unlock (&store_info->lock);
|
|
|
|
store_info_unref (store_info);
|
|
|
|
if (retry)
|
|
folder_info = mail_folder_cache_ref_folder_info (
|
|
cache, parent_store, full_name);
|
|
|
|
if (!folder_info)
|
|
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);
|
|
}
|