/* * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * * This library 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 library 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 this library. If not, see . * * Authors: Michael Zucchi */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "camel-db.h" #include "camel-session.h" #include "camel-string-utils.h" #include "camel-vee-folder.h" #include "camel-vee-store.h" #define CAMEL_VEE_STORE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), CAMEL_TYPE_VEE_STORE, CamelVeeStorePrivate)) /* Translators: 'Unmatched' is a folder name under Search folders where are shown * all messages not belonging into any other configured search folder */ #define PRETTY_UNMATCHED_FOLDER_NAME _("Unmatched") #define d(x) /* flags * 1 = delete (0 = add) * 2 = noselect */ #define CHANGE_ADD (0) #define CHANGE_DELETE (1) #define CHANGE_NOSELECT (2) /* The custom property ID is a CamelArg artifact. * It still identifies the property in state files. */ enum { PROP_0, PROP_UNMATCHED_ENABLED = 0x2400 }; G_DEFINE_TYPE (CamelVeeStore, camel_vee_store, CAMEL_TYPE_STORE) struct _CamelVeeStorePrivate { CamelVeeDataCache *vee_data_cache; CamelVeeFolder *unmatched_folder; gboolean unmatched_enabled; GMutex sf_counts_mutex; GHashTable *subfolder_usage_counts; /* CamelFolder * (subfolder) => gint of usages, for unmatched_folder */ GMutex vu_counts_mutex; GHashTable *vuid_usage_counts; /* gchar * (vuid) => gint of usages, those with 0 comes to unmatched_folder */ }; static gint vee_folder_cmp (gconstpointer ap, gconstpointer bp) { const gchar *full_name_a; const gchar *full_name_b; full_name_a = camel_folder_get_full_name (((CamelFolder **) ap)[0]); full_name_b = camel_folder_get_full_name (((CamelFolder **) bp)[0]); return g_strcmp0 (full_name_a, full_name_b); } static void change_folder (CamelStore *store, const gchar *name, guint32 flags, gint count) { CamelFolderInfo *fi; const gchar *tmp; fi = camel_folder_info_new (); fi->full_name = g_strdup (name); tmp = strrchr (name, '/'); if (tmp == NULL) tmp = name; else tmp++; fi->display_name = g_strdup (tmp); fi->unread = count; fi->flags = CAMEL_FOLDER_VIRTUAL; if (!(flags & CHANGE_DELETE)) fi->flags |= CAMEL_FOLDER_NOCHILDREN; if (flags & CHANGE_NOSELECT) fi->flags |= CAMEL_FOLDER_NOSELECT; if (flags & CHANGE_DELETE) camel_store_folder_deleted (store, fi); else camel_store_folder_created (store, fi); camel_folder_info_free (fi); } static void vee_store_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_UNMATCHED_ENABLED: camel_vee_store_set_unmatched_enabled ( CAMEL_VEE_STORE (object), g_value_get_boolean (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void vee_store_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_UNMATCHED_ENABLED: g_value_set_boolean ( value, camel_vee_store_get_unmatched_enabled ( CAMEL_VEE_STORE (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void vee_store_finalize (GObject *object) { CamelVeeStorePrivate *priv; priv = CAMEL_VEE_STORE_GET_PRIVATE (object); g_object_unref (priv->unmatched_folder); g_object_unref (priv->vee_data_cache); g_hash_table_destroy (priv->subfolder_usage_counts); g_hash_table_destroy (priv->vuid_usage_counts); g_mutex_clear (&priv->sf_counts_mutex); g_mutex_clear (&priv->vu_counts_mutex); /* Chain up to parent's finalize () method. */ G_OBJECT_CLASS (camel_vee_store_parent_class)->finalize (object); } static void vee_store_constructed (GObject *object) { CamelVeeStore *vee_store; vee_store = CAMEL_VEE_STORE (object); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (camel_vee_store_parent_class)->constructed (object); /* Set up unmatched folder */ vee_store->priv->unmatched_folder = g_object_new ( CAMEL_TYPE_VEE_FOLDER, "full-name", CAMEL_UNMATCHED_NAME, "display-name", PRETTY_UNMATCHED_FOLDER_NAME, "parent-store", vee_store, NULL); camel_vee_folder_construct ( vee_store->priv->unmatched_folder, CAMEL_STORE_FOLDER_PRIVATE); vee_store->priv->subfolder_usage_counts = g_hash_table_new (g_direct_hash, g_direct_equal); vee_store->priv->vuid_usage_counts = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL); g_mutex_init (&vee_store->priv->sf_counts_mutex); g_mutex_init (&vee_store->priv->vu_counts_mutex); } static gchar * vee_store_get_name (CamelService *service, gboolean brief) { return g_strdup ("Virtual Folder Store"); } static CamelFolder * vee_store_get_folder_sync (CamelStore *store, const gchar *folder_name, CamelStoreGetFolderFlags flags, GCancellable *cancellable, GError **error) { CamelVeeFolder *vf; CamelFolder *folder; gchar *name, *p; gsize name_len; vf = (CamelVeeFolder *) camel_vee_folder_new (store, folder_name, flags); if (vf && ((vf->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0)) { const gchar *full_name; full_name = camel_folder_get_full_name (CAMEL_FOLDER (vf)); /* Check that parents exist, if not, create dummy ones */ name_len = strlen (full_name) + 1; name = alloca (name_len); g_strlcpy (name, full_name, name_len); p = name; while ( (p = strchr (p, '/'))) { *p = 0; folder = camel_object_bag_reserve (store->folders, name); if (folder == NULL) { /* create a dummy vFolder for this, makes get_folder_info simpler */ folder = camel_vee_folder_new (store, name, flags); camel_object_bag_add (store->folders, name, folder); change_folder (store, name, CHANGE_ADD | CHANGE_NOSELECT, 0); /* FIXME: this sort of leaks folder, nobody owns a ref to it but us */ } else { g_object_unref (folder); } *p++='/'; } change_folder (store, full_name, CHANGE_ADD, camel_folder_get_message_count ((CamelFolder *) vf)); } return (CamelFolder *) vf; } static CamelFolderInfo * vee_store_create_unmatched_fi (void) { CamelFolderInfo *info; info = camel_folder_info_new (); info->full_name = g_strdup (CAMEL_UNMATCHED_NAME); info->display_name = g_strdup (PRETTY_UNMATCHED_FOLDER_NAME); info->unread = -1; info->flags = CAMEL_FOLDER_NOCHILDREN | CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_VIRTUAL; return info; } static CamelFolderInfo * vee_store_get_folder_info_sync (CamelStore *store, const gchar *top, CamelStoreGetFolderInfoFlags flags, GCancellable *cancellable, GError **error) { CamelFolderInfo *info, *res = NULL, *tail; GPtrArray *folders; GHashTable *infos_hash; gint i; d (printf ("Get folder info '%s'\n", top ? top:"")); infos_hash = g_hash_table_new (g_str_hash, g_str_equal); folders = camel_object_bag_list (store->folders); qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), vee_folder_cmp); for (i = 0; i < folders->len; i++) { CamelVeeFolder *folder = folders->pdata[i]; const gchar *full_name; const gchar *display_name; gint add = FALSE; gchar *pname, *tmp; CamelFolderInfo *pinfo; full_name = camel_folder_get_full_name (CAMEL_FOLDER (folder)); display_name = camel_folder_get_display_name (CAMEL_FOLDER (folder)); /* check we have to include this one */ if (top) { gint namelen = strlen (full_name); gint toplen = strlen (top); add = ((namelen == toplen && strcmp (full_name, top) == 0) || ((namelen > toplen) && strncmp (full_name, top, toplen) == 0 && full_name[toplen] == '/' && ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) || strchr (full_name + toplen + 1, '/') == NULL))); } else { add = (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) || strchr (full_name, '/') == NULL; } if (add) { gint32 unread; unread = camel_folder_get_unread_message_count ( CAMEL_FOLDER (folder)); info = camel_folder_info_new (); info->full_name = g_strdup (full_name); info->display_name = g_strdup (display_name); info->unread = unread; info->flags = CAMEL_FOLDER_NOCHILDREN | CAMEL_FOLDER_VIRTUAL; g_hash_table_insert (infos_hash, info->full_name, info); if (res == NULL) res = info; } else { info = NULL; } /* check for parent, if present, update flags and if adding, update parent linkage */ pname = g_strdup (full_name); d (printf ("looking up parent of '%s'\n", pname)); tmp = strrchr (pname, '/'); if (tmp) { *tmp = 0; pinfo = g_hash_table_lookup (infos_hash, pname); } else pinfo = NULL; if (pinfo) { pinfo->flags = (pinfo->flags & ~(CAMEL_FOLDER_CHILDREN | CAMEL_FOLDER_NOCHILDREN)) | CAMEL_FOLDER_CHILDREN; d (printf ("updating parent flags for children '%s' %08x\n", pinfo->full_name, pinfo->flags)); tail = pinfo->child; if (tail == NULL) pinfo->child = info; } else if (info != res) { tail = res; } else { tail = NULL; } if (info && tail) { while (tail->next) tail = tail->next; tail->next = info; info->parent = pinfo; } g_free (pname); g_object_unref (folder); } g_ptr_array_free (folders, TRUE); g_hash_table_destroy (infos_hash); /* and add UNMATCHED, if scanning from top/etc and it's enabled */ if (camel_vee_store_get_unmatched_enabled (CAMEL_VEE_STORE (store)) && (top == NULL || top[0] == 0 || strncmp (top, CAMEL_UNMATCHED_NAME, strlen (CAMEL_UNMATCHED_NAME)) == 0)) { info = vee_store_create_unmatched_fi (); if (res == NULL) res = info; else { tail = res; while (tail->next) tail = tail->next; tail->next = info; } } return res; } static CamelFolder * vee_store_get_junk_folder_sync (CamelStore *store, GCancellable *cancellable, GError **error) { return NULL; } static CamelFolder * vee_store_get_trash_folder_sync (CamelStore *store, GCancellable *cancellable, GError **error) { return NULL; } static gboolean vee_store_delete_folder_sync (CamelStore *store, const gchar *folder_name, GCancellable *cancellable, GError **error) { CamelFolder *folder; if (strcmp (folder_name, CAMEL_UNMATCHED_NAME) == 0) { g_set_error ( error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER, _("Cannot delete folder: %s: Invalid operation"), folder_name); return FALSE; } folder = camel_object_bag_get (store->folders, folder_name); if (folder) { CamelObject *object = CAMEL_OBJECT (folder); const gchar *state_filename; state_filename = camel_object_get_state_filename (object); if (state_filename != NULL) { g_unlink (state_filename); camel_object_set_state_filename (object, NULL); } if ((((CamelVeeFolder *) folder)->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0) { /* what about now-empty parents? ignore? */ change_folder (store, folder_name, CHANGE_DELETE, -1); } g_object_unref (folder); } else { g_set_error ( error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER, _("Cannot delete folder: %s: No such folder"), folder_name); return FALSE; } return TRUE; } static gboolean vee_store_rename_folder_sync (CamelStore *store, const gchar *old, const gchar *new, GCancellable *cancellable, GError **error) { CamelFolder *folder, *oldfolder; gchar *p, *name; gsize name_len; d (printf ("vee rename folder '%s' '%s'\n", old, new)); if (strcmp (old, CAMEL_UNMATCHED_NAME) == 0) { g_set_error ( error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER, _("Cannot rename folder: %s: Invalid operation"), old); return FALSE; } /* See if it exists, for vfolders, all folders are in the folders hash */ oldfolder = camel_object_bag_get (store->folders, old); if (oldfolder == NULL) { g_set_error ( error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER, _("Cannot rename folder: %s: No such folder"), old); return FALSE; } /* Check that new parents exist, if not, create dummy ones */ name_len = strlen (new) + 1; name = alloca (name_len); g_strlcpy (name, new, name_len); p = name; while ( (p = strchr (p, '/'))) { *p = 0; folder = camel_object_bag_reserve (store->folders, name); if (folder == NULL) { /* create a dummy vFolder for this, makes get_folder_info simpler */ folder = camel_vee_folder_new (store, name, ((CamelVeeFolder *) oldfolder)->flags); camel_object_bag_add (store->folders, name, folder); change_folder (store, name, CHANGE_ADD | CHANGE_NOSELECT, 0); /* FIXME: this sort of leaks folder, nobody owns a ref to it but us */ } else { g_object_unref (folder); } *p++='/'; } g_object_unref (oldfolder); return TRUE; } static void camel_vee_store_class_init (CamelVeeStoreClass *class) { GObjectClass *object_class; CamelServiceClass *service_class; CamelStoreClass *store_class; g_type_class_add_private (class, sizeof (CamelVeeStorePrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = vee_store_set_property; object_class->get_property = vee_store_get_property; object_class->finalize = vee_store_finalize; object_class->constructed = vee_store_constructed; service_class = CAMEL_SERVICE_CLASS (class); service_class->get_name = vee_store_get_name; store_class = CAMEL_STORE_CLASS (class); store_class->get_folder_sync = vee_store_get_folder_sync; store_class->get_folder_info_sync = vee_store_get_folder_info_sync; store_class->get_junk_folder_sync = vee_store_get_junk_folder_sync; store_class->get_trash_folder_sync = vee_store_get_trash_folder_sync; store_class->delete_folder_sync = vee_store_delete_folder_sync; store_class->rename_folder_sync = vee_store_rename_folder_sync; g_object_class_install_property ( object_class, PROP_UNMATCHED_ENABLED, g_param_spec_boolean ( "unmatched-enabled", "Unmatched Enabled", _("Enable _Unmatched folder"), TRUE, G_PARAM_READWRITE)); } static void camel_vee_store_init (CamelVeeStore *vee_store) { CamelStore *store = CAMEL_STORE (vee_store); vee_store->priv = CAMEL_VEE_STORE_GET_PRIVATE (vee_store); vee_store->priv->vee_data_cache = camel_vee_data_cache_new (); vee_store->priv->unmatched_enabled = TRUE; /* we dont want a vtrash/vjunk on this one */ store->flags &= ~(CAMEL_STORE_VTRASH | CAMEL_STORE_VJUNK); } /** * camel_vee_store_new: * * Create a new #CamelVeeStore object. * * Returns: new #CamelVeeStore object **/ CamelVeeStore * camel_vee_store_new (void) { return g_object_new (CAMEL_TYPE_VEE_STORE, NULL); } /** * camel_vee_store_get_vee_data_cache: * * FIXME Document me! * * Since: 3.6 **/ CamelVeeDataCache * camel_vee_store_get_vee_data_cache (CamelVeeStore *vstore) { g_return_val_if_fail (CAMEL_IS_VEE_STORE (vstore), NULL); return vstore->priv->vee_data_cache; } /** * camel_vee_store_get_unmatched_folder: * * FIXME Document me! * * Since: 3.6 **/ CamelVeeFolder * camel_vee_store_get_unmatched_folder (CamelVeeStore *vstore) { g_return_val_if_fail (CAMEL_IS_VEE_STORE (vstore), NULL); if (!camel_vee_store_get_unmatched_enabled (vstore)) return NULL; return vstore->priv->unmatched_folder; } /** * camel_vee_store_get_unmatched_enabled: * * FIXME Document me! * * Since: 3.6 **/ gboolean camel_vee_store_get_unmatched_enabled (CamelVeeStore *vstore) { g_return_val_if_fail (CAMEL_IS_VEE_STORE (vstore), FALSE); return vstore->priv->unmatched_enabled; } /** * camel_vee_store_set_unmatched_enabled: * * FIXME Document me! * * Since: 3.6 **/ void camel_vee_store_set_unmatched_enabled (CamelVeeStore *vstore, gboolean is_enabled) { CamelFolderInfo *fi_unmatched; g_return_if_fail (CAMEL_IS_VEE_STORE (vstore)); if (vstore->priv->unmatched_enabled == is_enabled) return; vstore->priv->unmatched_enabled = is_enabled; g_object_notify (G_OBJECT (vstore), "unmatched-enabled"); fi_unmatched = vee_store_create_unmatched_fi (); if (is_enabled) { camel_store_folder_created (CAMEL_STORE (vstore), fi_unmatched); camel_vee_store_rebuild_unmatched_folder (vstore, NULL, NULL); } else { camel_store_folder_deleted (CAMEL_STORE (vstore), fi_unmatched); } camel_folder_info_free (fi_unmatched); } struct AddToUnmatchedData { CamelVeeFolder *unmatched_folder; CamelFolderChangeInfo *changes; gboolean unmatched_enabled; GHashTable *vuid_usage_counts; }; static void add_to_unmatched_folder_cb (CamelVeeMessageInfoData *mi_data, CamelFolder *subfolder, gpointer user_data) { struct AddToUnmatchedData *atud = user_data; const gchar *vuid; g_return_if_fail (atud != NULL); vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data); g_hash_table_insert ( atud->vuid_usage_counts, (gpointer) camel_pstring_strdup (vuid), GINT_TO_POINTER (0)); if (atud->unmatched_enabled) camel_vee_folder_add_vuid (atud->unmatched_folder, mi_data, atud->changes); } /** * camel_vee_store_note_subfolder_used: * * FIXME Document me! * * Since: 3.6 **/ void camel_vee_store_note_subfolder_used (CamelVeeStore *vstore, CamelFolder *subfolder, CamelVeeFolder *used_by) { gint counts; g_return_if_fail (CAMEL_IS_VEE_STORE (vstore)); g_return_if_fail (CAMEL_IS_FOLDER (subfolder)); g_return_if_fail (CAMEL_IS_VEE_FOLDER (used_by)); /* only real folders can be part of the unmatched folder */ if (CAMEL_IS_VEE_FOLDER (subfolder) || used_by == vstore->priv->unmatched_folder) return; g_mutex_lock (&vstore->priv->sf_counts_mutex); counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->subfolder_usage_counts, subfolder)); counts++; g_hash_table_insert ( vstore->priv->subfolder_usage_counts, subfolder, GINT_TO_POINTER (counts)); if (counts == 1) { struct AddToUnmatchedData atud; CamelFolder *unmatched_folder; camel_vee_data_cache_add_subfolder (vstore->priv->vee_data_cache, subfolder); g_mutex_lock (&vstore->priv->vu_counts_mutex); /* all messages from the folder are unmatched at the beginning */ atud.unmatched_folder = vstore->priv->unmatched_folder; atud.changes = camel_folder_change_info_new (); atud.unmatched_enabled = camel_vee_store_get_unmatched_enabled (vstore); atud.vuid_usage_counts = vstore->priv->vuid_usage_counts; if (atud.unmatched_enabled) camel_vee_folder_add_folder (vstore->priv->unmatched_folder, subfolder, NULL); unmatched_folder = CAMEL_FOLDER (atud.unmatched_folder); camel_folder_freeze (unmatched_folder); camel_vee_data_cache_foreach_message_info_data (vstore->priv->vee_data_cache, subfolder, add_to_unmatched_folder_cb, &atud); camel_folder_thaw (unmatched_folder); g_mutex_unlock (&vstore->priv->vu_counts_mutex); if (camel_folder_change_info_changed (atud.changes)) camel_folder_changed (unmatched_folder, atud.changes); camel_folder_change_info_free (atud.changes); } g_mutex_unlock (&vstore->priv->sf_counts_mutex); } static void remove_vuid_count_record_cb (CamelVeeMessageInfoData *mi_data, CamelFolder *subfolder, gpointer user_data) { GHashTable *vuid_usage_counts = user_data; g_return_if_fail (mi_data != NULL); g_return_if_fail (user_data != NULL); g_hash_table_remove (vuid_usage_counts, camel_vee_message_info_data_get_vee_message_uid (mi_data)); } /** * camel_vee_store_note_subfolder_unused: * * FIXME Document me! * * Since: 3.6 **/ void camel_vee_store_note_subfolder_unused (CamelVeeStore *vstore, CamelFolder *subfolder, CamelVeeFolder *unused_by) { gint counts; g_return_if_fail (CAMEL_IS_VEE_STORE (vstore)); g_return_if_fail (CAMEL_IS_FOLDER (subfolder)); g_return_if_fail (CAMEL_IS_VEE_FOLDER (unused_by)); /* only real folders can be part of the unmatched folder */ if (CAMEL_IS_VEE_FOLDER (subfolder) || unused_by == vstore->priv->unmatched_folder) return; g_mutex_lock (&vstore->priv->sf_counts_mutex); counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->subfolder_usage_counts, subfolder)); g_return_if_fail (counts > 0); counts--; if (counts == 0) { g_hash_table_remove (vstore->priv->subfolder_usage_counts, subfolder); if (camel_vee_store_get_unmatched_enabled (vstore)) camel_vee_folder_remove_folder (vstore->priv->unmatched_folder, subfolder, NULL); g_mutex_lock (&vstore->priv->vu_counts_mutex); camel_vee_data_cache_foreach_message_info_data (vstore->priv->vee_data_cache, subfolder, remove_vuid_count_record_cb, vstore->priv->vuid_usage_counts); g_mutex_unlock (&vstore->priv->vu_counts_mutex); camel_vee_data_cache_remove_subfolder (vstore->priv->vee_data_cache, subfolder); } else { g_hash_table_insert ( vstore->priv->subfolder_usage_counts, subfolder, GINT_TO_POINTER (counts)); } g_mutex_unlock (&vstore->priv->sf_counts_mutex); } /** * camel_vee_store_note_vuid_used: * * FIXME Document me! * * Since: 3.6 **/ void camel_vee_store_note_vuid_used (CamelVeeStore *vstore, CamelVeeMessageInfoData *mi_data, CamelVeeFolder *used_by) { gint counts; const gchar *vuid; CamelFolder *subfolder; CamelVeeSubfolderData *sf_data; g_return_if_fail (CAMEL_IS_VEE_STORE (vstore)); g_return_if_fail (used_by != NULL); g_return_if_fail (mi_data != NULL); /* these notifications are ignored from Unmatched folder */ if (used_by == vstore->priv->unmatched_folder) return; /* unmatched folder holds only real folders */ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data); subfolder = camel_vee_subfolder_data_get_folder (sf_data); if (CAMEL_IS_VEE_FOLDER (subfolder)) return; g_mutex_lock (&vstore->priv->vu_counts_mutex); vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data); counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->vuid_usage_counts, vuid)); counts++; g_hash_table_insert ( vstore->priv->vuid_usage_counts, (gpointer) camel_pstring_strdup (vuid), GINT_TO_POINTER (counts)); if (counts == 1 && camel_vee_store_get_unmatched_enabled (vstore)) { CamelFolderChangeInfo *changes; changes = camel_folder_change_info_new (); camel_vee_folder_remove_vuid (vstore->priv->unmatched_folder, mi_data, changes); if (camel_folder_change_info_changed (changes)) camel_folder_changed (CAMEL_FOLDER (vstore->priv->unmatched_folder), changes); camel_folder_change_info_free (changes); } g_mutex_unlock (&vstore->priv->vu_counts_mutex); } /** * camel_vee_store_note_vuid_unused: * * FIXME Document me! * * Since: 3.6 **/ void camel_vee_store_note_vuid_unused (CamelVeeStore *vstore, CamelVeeMessageInfoData *mi_data, CamelVeeFolder *unused_by) { gint counts; const gchar *vuid; CamelFolder *subfolder; CamelVeeSubfolderData *sf_data; g_return_if_fail (CAMEL_IS_VEE_STORE (vstore)); g_return_if_fail (unused_by != NULL); g_return_if_fail (mi_data != NULL); /* these notifications are ignored from Unmatched folder */ if (unused_by == vstore->priv->unmatched_folder) return; /* unmatched folder holds only real folders */ sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data); subfolder = camel_vee_subfolder_data_get_folder (sf_data); if (CAMEL_IS_VEE_FOLDER (subfolder)) return; g_mutex_lock (&vstore->priv->vu_counts_mutex); vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data); counts = GPOINTER_TO_INT (g_hash_table_lookup (vstore->priv->vuid_usage_counts, vuid)); counts--; if (counts < 0) { g_mutex_unlock (&vstore->priv->vu_counts_mutex); g_return_if_fail (counts >= 0); return; } g_hash_table_insert ( vstore->priv->vuid_usage_counts, (gpointer) camel_pstring_strdup (vuid), GINT_TO_POINTER (counts)); if (counts == 0 && camel_vee_store_get_unmatched_enabled (vstore)) { CamelFolderChangeInfo *changes; changes = camel_folder_change_info_new (); camel_vee_folder_add_vuid (vstore->priv->unmatched_folder, mi_data, changes); if (camel_folder_change_info_changed (changes)) camel_folder_changed (CAMEL_FOLDER (vstore->priv->unmatched_folder), changes); camel_folder_change_info_free (changes); } g_mutex_unlock (&vstore->priv->vu_counts_mutex); } struct RebuildUnmatchedData { CamelVeeDataCache *data_cache; CamelVeeFolder *unmatched_folder; CamelFolderChangeInfo *changes; GCancellable *cancellable; }; static void rebuild_unmatched_folder_cb (gpointer key, gpointer value, gpointer user_data) { const gchar *vuid = key; gint counts = GPOINTER_TO_INT (value); struct RebuildUnmatchedData *rud = user_data; CamelVeeSubfolderData *si_data; CamelVeeMessageInfoData *mi_data; g_return_if_fail (vuid != NULL); g_return_if_fail (rud != NULL); if (counts != 0 || g_cancellable_is_cancelled (rud->cancellable)) return; mi_data = camel_vee_data_cache_get_message_info_data_by_vuid (rud->data_cache, vuid); if (!mi_data) return; si_data = camel_vee_message_info_data_get_subfolder_data (mi_data); camel_vee_folder_add_folder (rud->unmatched_folder, camel_vee_subfolder_data_get_folder (si_data), NULL); camel_vee_folder_add_vuid (rud->unmatched_folder, mi_data, rud->changes); g_object_unref (mi_data); } static void vee_store_rebuild_unmatched_folder (CamelSession *session, GCancellable *cancellable, CamelVeeStore *vstore, GError **error) { struct RebuildUnmatchedData rud; CamelVeeFolder *vunmatched; CamelFolder *unmatched_folder; CamelFolderChangeInfo *changes; g_return_if_fail (CAMEL_IS_VEE_STORE (vstore)); vunmatched = camel_vee_store_get_unmatched_folder (vstore); /* someone could disable it meanwhile */ if (!vunmatched) return; unmatched_folder = CAMEL_FOLDER (vunmatched); g_return_if_fail (unmatched_folder != NULL); camel_folder_freeze (unmatched_folder); /* start from scratch, with empty folder */ camel_vee_folder_set_folders (vunmatched, NULL, cancellable); changes = camel_folder_change_info_new (); rud.data_cache = vstore->priv->vee_data_cache; rud.unmatched_folder = vunmatched; rud.changes = changes; rud.cancellable = cancellable; g_hash_table_foreach (vstore->priv->vuid_usage_counts, rebuild_unmatched_folder_cb, &rud); camel_folder_thaw (unmatched_folder); if (camel_folder_change_info_changed (changes)) camel_folder_changed (unmatched_folder, changes); camel_folder_change_info_free (changes); /* coverity[unchecked_value] */ g_cancellable_set_error_if_cancelled (cancellable, error); } /** * camel_vee_store_rebuild_unmatched_folder: * * FIXME Document me! * * Since: 3.6 **/ void camel_vee_store_rebuild_unmatched_folder (CamelVeeStore *vstore, GCancellable *cancellable, GError **error) { g_return_if_fail (CAMEL_IS_VEE_STORE (vstore)); /* this operation requires cancellable, thus if called * without it then run in a dedicated thread */ if (!cancellable) { CamelService *service; CamelSession *session; service = CAMEL_SERVICE (vstore); session = camel_service_ref_session (service); camel_session_submit_job ( session, _("Updating Unmatched search folder"), (CamelSessionCallback) vee_store_rebuild_unmatched_folder, g_object_ref (vstore), g_object_unref); g_object_unref (session); } else { vee_store_rebuild_unmatched_folder (NULL, cancellable, vstore, error); } }