/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * 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 * Jeffrey Stedfast */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "camel-db.h" #include "camel-debug.h" #include "camel-folder-search.h" #include "camel-mime-message.h" #include "camel-session.h" #include "camel-store.h" #include "camel-vee-folder.h" #include "camel-vee-store.h" /* for open flags */ #include "camel-vee-summary.h" #include "camel-string-utils.h" #include "camel-vtrash-folder.h" #define d(x) #define dd(x) (camel_debug ("vfolder")?(x):0) typedef struct _FolderChangedData FolderChangedData; #define CAMEL_VEE_FOLDER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), CAMEL_TYPE_VEE_FOLDER, CamelVeeFolderPrivate)) struct _CamelVeeFolderPrivate { gboolean destroyed; GList *subfolders; /* lock using subfolder_lock before changing/accessing */ GHashTable *ignore_changed; /* hash of subfolder pointers to ignore the next folder's 'changed' signal */ GHashTable *skipped_changes; /* CamelFolder -> CamelFolderChangeInfo accumulating ignored changes */ GHashTable *unmatched_add_changed; /* CamelVeeMessageInfoData -> 1, for unmatched folder, postponed additions from camel_vee_folder_add_vuid () */ GHashTable *unmatched_remove_changed; /* CamelVeeMessageInfoData -> 1, for unmatched folder, postponed removal from camel_vee_folder_remove_vuid () */ gboolean auto_update; /* Processing queue for folder changes. */ GAsyncQueue *change_queue; gboolean change_queue_busy; GRecMutex subfolder_lock; /* for locking the subfolder list */ GRecMutex changed_lock; /* for locking the folders-changed list */ gchar *expression; /* query expression */ /* only set-up if our parent is a vee-store, used also as a flag to * say that this folder is part of the unmatched folder */ CamelVeeStore *parent_vee_store; CamelVeeDataCache *vee_data_cache; }; /* The custom property ID is a CamelArg artifact. * It still identifies the property in state files. */ enum { PROP_0, PROP_AUTO_UPDATE = 0x2401 }; G_DEFINE_TYPE (CamelVeeFolder, camel_vee_folder, CAMEL_TYPE_FOLDER) struct _FolderChangedData { CamelFolderChangeInfo *changes; CamelFolder *subfolder; }; static FolderChangedData * vee_folder_changed_data_new (CamelFolder *subfolder, CamelFolderChangeInfo *changes) { FolderChangedData *data; data = g_slice_new0 (FolderChangedData); data->changes = camel_folder_change_info_new (); camel_folder_change_info_cat (data->changes, changes); data->subfolder = g_object_ref (subfolder); return data; } static void vee_folder_changed_data_free (FolderChangedData *data) { camel_folder_change_info_free (data->changes); g_object_unref (data->subfolder); g_slice_free (FolderChangedData, data); } static CamelVeeDataCache * vee_folder_get_data_cache (CamelVeeFolder *vfolder) { g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (vfolder), NULL); if (vfolder->priv->parent_vee_store) return camel_vee_store_get_vee_data_cache (vfolder->priv->parent_vee_store); return vfolder->priv->vee_data_cache; } static gboolean vee_folder_is_unmatched (CamelVeeFolder *vfolder) { g_return_val_if_fail (vfolder != NULL, FALSE); return vfolder->priv->parent_vee_store && vfolder == camel_vee_store_get_unmatched_folder (vfolder->priv->parent_vee_store); } static void vee_folder_note_added_uid (CamelVeeFolder *vfolder, CamelVeeSummary *vsummary, CamelVeeMessageInfoData *added_mi_data, CamelFolderChangeInfo *changes, gboolean included_as_changed) { const gchar *vuid; vuid = camel_vee_message_info_data_get_vee_message_uid (added_mi_data); if (!camel_folder_summary_check_uid (&vsummary->summary, vuid)) { /* add it only if it wasn't in yet */ CamelVeeMessageInfo *vmi; vmi = camel_vee_summary_add (vsummary, added_mi_data); if (vmi) { if (changes) camel_folder_change_info_add_uid (changes, vuid); camel_message_info_unref (vmi); if (vfolder->priv->parent_vee_store) camel_vee_store_note_vuid_used (vfolder->priv->parent_vee_store, added_mi_data, vfolder); } } else { camel_vee_summary_replace_flags (vsummary, vuid); if (included_as_changed && changes) camel_folder_change_info_change_uid (changes, vuid); } } static void vee_folder_note_unmatch_uid (CamelVeeFolder *vfolder, CamelVeeSummary *vsummary, CamelFolder *subfolder, CamelVeeDataCache *data_cache, CamelVeeMessageInfoData *unmatched_mi_data, CamelFolderChangeInfo *changes) { const gchar *vuid; vuid = camel_vee_message_info_data_get_vee_message_uid (unmatched_mi_data); if (camel_folder_summary_check_uid (&vsummary->summary, vuid)) { g_object_ref (unmatched_mi_data); /* this one doesn't belong to us anymore */ if (changes) camel_folder_change_info_remove_uid (changes, vuid); camel_vee_summary_remove (vsummary, vuid, subfolder); if (vfolder->priv->parent_vee_store) camel_vee_store_note_vuid_unused (vfolder->priv->parent_vee_store, unmatched_mi_data, vfolder); else camel_vee_data_cache_remove_message_info_data (data_cache, unmatched_mi_data); g_object_unref (unmatched_mi_data); } } static void vee_folder_remove_unmatched (CamelVeeFolder *vfolder, CamelVeeSummary *vsummary, CamelVeeDataCache *data_cache, CamelFolderChangeInfo *changes, CamelFolder *subfolder, const gchar *orig_message_uid, gboolean is_orig_message_uid) /* if not, then it's 'vee_message_uid' */ { CamelVeeMessageInfoData *mi_data; if (is_orig_message_uid) { /* camel_vee_data_cache_get_message_info_data() auto-adds items if not there, * thus check whether the cache has it already, and if not, then skip the action. * This can happen for virtual Junk/Trash folders. */ if (!camel_vee_data_cache_contains_message_info_data (data_cache, subfolder, orig_message_uid)) return; mi_data = camel_vee_data_cache_get_message_info_data (data_cache, subfolder, orig_message_uid); } else mi_data = camel_vee_data_cache_get_message_info_data_by_vuid (data_cache, orig_message_uid); if (!mi_data) return; vee_folder_note_unmatch_uid (vfolder, vsummary, subfolder, data_cache, mi_data, changes); g_object_unref (mi_data); } struct RemoveUnmatchedData { CamelVeeFolder *vfolder; CamelVeeSummary *vsummary; CamelFolder *subfolder; CamelVeeDataCache *data_cache; CamelFolderChangeInfo *changes; gboolean is_orig_message_uid; }; static void vee_folder_remove_unmatched_cb (gpointer key, gpointer value, gpointer user_data) { struct RemoveUnmatchedData *rud = user_data; const gchar *uid = key; g_return_if_fail (rud != NULL); vee_folder_remove_unmatched (rud->vfolder, rud->vsummary, rud->data_cache, rud->changes, rud->subfolder, uid, rud->is_orig_message_uid); } static void vee_folder_merge_matching (CamelVeeFolder *vfolder, CamelFolder *subfolder, GHashTable *all_uids, GPtrArray *match, CamelFolderChangeInfo *changes, gboolean included_as_changed) { CamelVeeDataCache *data_cache; CamelVeeMessageInfoData *mi_data; CamelFolder *folder; CamelVeeSummary *vsummary; struct RemoveUnmatchedData rud; gint ii; g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); g_return_if_fail (CAMEL_IS_FOLDER (subfolder)); g_return_if_fail (all_uids != NULL); g_return_if_fail (match != NULL); folder = CAMEL_FOLDER (vfolder); g_return_if_fail (folder != NULL); vsummary = CAMEL_VEE_SUMMARY (folder->summary); g_return_if_fail (vsummary != NULL); data_cache = vee_folder_get_data_cache (vfolder); for (ii = 0; ii < match->len; ii++) { const gchar *uid = match->pdata[ii]; mi_data = camel_vee_data_cache_get_message_info_data (data_cache, subfolder, uid); if (!mi_data) continue; g_hash_table_remove (all_uids, uid); vee_folder_note_added_uid (vfolder, vsummary, mi_data, changes, included_as_changed); g_object_unref (mi_data); } rud.vfolder = vfolder; rud.vsummary = vsummary; rud.subfolder = subfolder; rud.data_cache = data_cache; rud.changes = changes; rud.is_orig_message_uid = TRUE; /* in 'all_uids' left only those which are not part of the folder anymore */ g_hash_table_foreach (all_uids, vee_folder_remove_unmatched_cb, &rud); } static void vee_folder_rebuild_folder_with_changes (CamelVeeFolder *vfolder, CamelFolder *subfolder, CamelFolderChangeInfo *changes, GCancellable *cancellable) { GPtrArray *match = NULL; g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); g_return_if_fail (CAMEL_IS_FOLDER (subfolder)); /* Unmatched folder cannot be rebuilt */ if (vee_folder_is_unmatched (vfolder)) return; /* if we have no expression, or its been cleared, then act as if no matches */ if (vfolder->priv->expression == NULL) { match = g_ptr_array_new (); } else { match = camel_folder_search_by_expression (subfolder, vfolder->priv->expression, cancellable, NULL); if (!match) return; } if (!g_cancellable_is_cancelled (cancellable)) { GHashTable *all_uids; all_uids = camel_folder_summary_get_hash (subfolder->summary); vee_folder_merge_matching (vfolder, subfolder, all_uids, match, changes, FALSE); g_hash_table_destroy (all_uids); } camel_folder_search_free (subfolder, match); } static void vee_folder_rebuild_all (CamelVeeFolder *vfolder, GCancellable *cancellable) { CamelFolderChangeInfo *changes; GList *iter; g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); /* Unmatched folder cannot be rebuilt */ if (vee_folder_is_unmatched (vfolder)) return; changes = camel_folder_change_info_new (); g_rec_mutex_lock (&vfolder->priv->subfolder_lock); for (iter = vfolder->priv->subfolders; iter && !g_cancellable_is_cancelled (cancellable); iter = iter->next) { CamelFolder *subfolder = iter->data; vee_folder_rebuild_folder_with_changes (vfolder, subfolder, changes, cancellable); } g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); if (camel_folder_change_info_changed (changes)) camel_folder_changed (CAMEL_FOLDER (vfolder), changes); camel_folder_change_info_free (changes); } static void vee_folder_subfolder_changed (CamelVeeFolder *vfolder, CamelFolder *subfolder, CamelFolderChangeInfo *subfolder_changes, GCancellable *cancellable, GError **error) { CamelVeeDataCache *data_cache; CamelFolderChangeInfo *changes; CamelFolder *v_folder; CamelVeeSummary *vsummary; gint ii; g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); g_return_if_fail (CAMEL_IS_FOLDER (subfolder)); g_return_if_fail (subfolder_changes != NULL); g_rec_mutex_lock (&vfolder->priv->subfolder_lock); if (!g_list_find (vfolder->priv->subfolders, subfolder)) { g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); return; } g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); changes = camel_folder_change_info_new (); data_cache = vee_folder_get_data_cache (vfolder); v_folder = CAMEL_FOLDER (vfolder); vsummary = CAMEL_VEE_SUMMARY (v_folder->summary); camel_folder_freeze (v_folder); for (ii = 0; ii < subfolder_changes->uid_removed->len; ii++) { const gchar *orig_message_uid = subfolder_changes->uid_removed->pdata[ii]; vee_folder_remove_unmatched (vfolder, vsummary, data_cache, changes, subfolder, orig_message_uid, TRUE); } if (subfolder_changes->uid_added->len + subfolder_changes->uid_changed->len > 0) { GPtrArray *test_uids, *match; gboolean my_match = FALSE; test_uids = g_ptr_array_sized_new (subfolder_changes->uid_added->len + subfolder_changes->uid_changed->len); for (ii = 0; ii < subfolder_changes->uid_added->len; ii++) { g_ptr_array_add (test_uids, subfolder_changes->uid_added->pdata[ii]); } for (ii = 0; ii < subfolder_changes->uid_changed->len; ii++) { g_ptr_array_add (test_uids, subfolder_changes->uid_changed->pdata[ii]); } if (!vfolder->priv->expression) { my_match = TRUE; match = g_ptr_array_new (); if (vee_folder_is_unmatched (vfolder)) { CamelVeeMessageInfoData *mi_data; const gchar *vuid; /* all common from test_uids and stored uids * in the unmatched folder should be updated */ for (ii = 0; ii < test_uids->len; ii++) { mi_data = camel_vee_data_cache_get_message_info_data (data_cache, subfolder, test_uids->pdata[ii]); if (!mi_data) continue; vuid = camel_vee_message_info_data_get_vee_message_uid (mi_data); if (camel_folder_summary_check_uid (v_folder->summary, vuid)) g_ptr_array_add (match, (gpointer) camel_pstring_strdup (test_uids->pdata[ii])); g_object_unref (mi_data); } } } else { /* sadly, if there are threads involved, then searching by uids doesn't work, * because just changed uids can be brought in by the thread condition */ if (strstr (vfolder->priv->expression, "match-threads") != NULL) match = camel_folder_search_by_expression (subfolder, vfolder->priv->expression, cancellable, NULL); else match = camel_folder_search_by_uids (subfolder, vfolder->priv->expression, test_uids, cancellable, NULL); } if (match) { GHashTable *with_uids; /* uids are taken from the string pool, thus use direct hashes */ with_uids = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify) camel_pstring_free, NULL); for (ii = 0; ii < test_uids->len; ii++) { g_hash_table_insert (with_uids, (gpointer) camel_pstring_strdup (test_uids->pdata[ii]), GINT_TO_POINTER (1)); } vee_folder_merge_matching (vfolder, subfolder, with_uids, match, changes, TRUE); g_hash_table_destroy (with_uids); if (my_match) { g_ptr_array_foreach (match, (GFunc) camel_pstring_free, NULL); g_ptr_array_free (match, TRUE); } else { camel_folder_search_free (subfolder, match); } } g_ptr_array_free (test_uids, TRUE); } camel_folder_thaw (v_folder); if (camel_folder_change_info_changed (changes)) camel_folder_changed (v_folder, changes); camel_folder_change_info_free (changes); } static void vee_folder_process_changes (CamelSession *session, GCancellable *cancellable, CamelVeeFolder *vee_folder, GError **error) { CamelFolder *folder; FolderChangedData *data; GAsyncQueue *change_queue; const gchar *display_name; const gchar *message; folder = CAMEL_FOLDER (vee_folder); change_queue = vee_folder->priv->change_queue; message = _("Updating folder '%s'"); display_name = camel_folder_get_display_name (folder); camel_operation_push_message (cancellable, message, display_name); while ((data = g_async_queue_try_pop (change_queue)) != NULL) { vee_folder_subfolder_changed (vee_folder, data->subfolder, data->changes, cancellable, error); vee_folder_changed_data_free (data); if (g_cancellable_is_cancelled (cancellable)) break; } vee_folder->priv->change_queue_busy = FALSE; camel_operation_pop_message (cancellable); } static void subfolder_changed (CamelFolder *subfolder, CamelFolderChangeInfo *changes, CamelVeeFolder *vfolder) { g_return_if_fail (vfolder != NULL); g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); g_rec_mutex_lock (&vfolder->priv->changed_lock); if (g_hash_table_lookup (vfolder->priv->ignore_changed, subfolder) || !camel_vee_folder_get_auto_update (vfolder)) { CamelFolderChangeInfo *my_changes; g_hash_table_remove (vfolder->priv->ignore_changed, subfolder); my_changes = g_hash_table_lookup (vfolder->priv->skipped_changes, subfolder); if (!my_changes) my_changes = camel_folder_change_info_new (); camel_folder_change_info_cat (my_changes, changes); g_hash_table_insert (vfolder->priv->skipped_changes, subfolder, my_changes); g_rec_mutex_unlock (&vfolder->priv->changed_lock); return; } g_rec_mutex_unlock (&vfolder->priv->changed_lock); CAMEL_VEE_FOLDER_GET_CLASS (vfolder)->folder_changed (vfolder, subfolder, changes); } /* track vanishing folders */ static void subfolder_deleted (CamelFolder *subfolder, CamelVeeFolder *vfolder) { camel_vee_folder_remove_folder (vfolder, subfolder, NULL); } static void vee_folder_dispose (GObject *object) { CamelFolder *folder; folder = CAMEL_FOLDER (object); /* parent's class frees summary on dispose, thus depend on it */ if (folder->summary) { CamelVeeFolder *vfolder; vfolder = CAMEL_VEE_FOLDER (object); vfolder->priv->destroyed = TRUE; camel_folder_freeze ((CamelFolder *) vfolder); while (vfolder->priv->subfolders) { CamelFolder *subfolder = vfolder->priv->subfolders->data; camel_vee_folder_remove_folder (vfolder, subfolder, NULL); } camel_folder_thaw ((CamelFolder *) vfolder); } /* Chain up to parent's dispose () method. */ G_OBJECT_CLASS (camel_vee_folder_parent_class)->dispose (object); } static void free_change_info_cb (gpointer folder, gpointer change_info, gpointer user_data) { camel_folder_change_info_free (change_info); } static void vee_folder_finalize (GObject *object) { CamelVeeFolder *vf; vf = CAMEL_VEE_FOLDER (object); g_free (vf->priv->expression); g_list_free (vf->priv->subfolders); g_hash_table_foreach (vf->priv->skipped_changes, free_change_info_cb, NULL); g_rec_mutex_clear (&vf->priv->subfolder_lock); g_rec_mutex_clear (&vf->priv->changed_lock); g_hash_table_destroy (vf->priv->ignore_changed); g_hash_table_destroy (vf->priv->skipped_changes); g_hash_table_destroy (vf->priv->unmatched_add_changed); g_hash_table_destroy (vf->priv->unmatched_remove_changed); g_async_queue_unref (vf->priv->change_queue); if (vf->priv->vee_data_cache) g_object_unref (vf->priv->vee_data_cache); vf->priv->vee_data_cache = NULL; /* Chain up to parent's finalize () method. */ G_OBJECT_CLASS (camel_vee_folder_parent_class)->finalize (object); } static void vee_folder_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_AUTO_UPDATE: g_value_set_boolean ( value, camel_vee_folder_get_auto_update ( CAMEL_VEE_FOLDER (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void vee_folder_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_AUTO_UPDATE: camel_vee_folder_set_auto_update ( CAMEL_VEE_FOLDER (object), g_value_get_boolean (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void vee_folder_propagate_skipped_changes (CamelVeeFolder *vf) { CamelVeeFolderClass *class; CamelFolderChangeInfo *changes = NULL; GHashTableIter iter; gpointer psub, pchanges; g_return_if_fail (vf != NULL); class = CAMEL_VEE_FOLDER_GET_CLASS (vf); g_rec_mutex_lock (&vf->priv->changed_lock); /* this is for Unmatched folder only, other folders have unmatched_remove_changed always empty */ if (g_hash_table_size (vf->priv->unmatched_add_changed) + g_hash_table_size (vf->priv->unmatched_remove_changed) > 0) { gpointer pkey, pvalue; CamelVeeSummary *vsummary; CamelFolder *v_folder; CamelVeeDataCache *data_cache; changes = camel_folder_change_info_new (); data_cache = vee_folder_get_data_cache (vf); v_folder = CAMEL_FOLDER (vf); vsummary = CAMEL_VEE_SUMMARY (v_folder->summary); /* first remove ... */ g_hash_table_iter_init (&iter, vf->priv->unmatched_remove_changed); while (g_hash_table_iter_next (&iter, &pkey, &pvalue)) { CamelVeeMessageInfoData *mi_data = pkey; CamelVeeSubfolderData *sf_data; CamelFolder *subfolder; sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data); subfolder = camel_vee_subfolder_data_get_folder (sf_data); vee_folder_note_unmatch_uid (vf, vsummary, subfolder, data_cache, mi_data, changes); } g_hash_table_remove_all (vf->priv->unmatched_remove_changed); /* ... then add */ g_hash_table_iter_init (&iter, vf->priv->unmatched_add_changed); while (g_hash_table_iter_next (&iter, &pkey, &pvalue)) { CamelVeeMessageInfoData *mi_data = pkey; vee_folder_note_added_uid (vf, vsummary, mi_data, changes, FALSE); } g_hash_table_remove_all (vf->priv->unmatched_add_changed); } g_hash_table_iter_init (&iter, vf->priv->skipped_changes); while (g_hash_table_iter_next (&iter, &psub, &pchanges)) { g_warn_if_fail (pchanges != NULL); if (!pchanges) continue; if (g_list_find (vf->priv->subfolders, psub) != NULL) class->folder_changed (vf, psub, pchanges); camel_folder_change_info_free (pchanges); } g_hash_table_remove_all (vf->priv->skipped_changes); g_rec_mutex_unlock (&vf->priv->changed_lock); if (changes) { if (camel_folder_change_info_changed (changes)) camel_folder_changed (CAMEL_FOLDER (vf), changes); camel_folder_change_info_free (changes); } } static GPtrArray * vee_folder_search_by_expression (CamelFolder *folder, const gchar *expression, GCancellable *cancellable, GError **error) { CamelFolderSearch *search; GPtrArray *matches; search = camel_folder_search_new (); camel_folder_search_set_folder (search, folder); matches = camel_folder_search_search (search, expression, NULL, cancellable, error); g_object_unref (search); return matches; } static GPtrArray * vee_folder_search_by_uids (CamelFolder *folder, const gchar *expression, GPtrArray *uids, GCancellable *cancellable, GError **error) { CamelFolderSearch *search; GPtrArray *matches; if (!uids || uids->len == 0) return g_ptr_array_new (); search = camel_folder_search_new (); camel_folder_search_set_folder (search, folder); matches = camel_folder_search_search (search, expression, uids, cancellable, error); g_object_unref (search); return matches; } static guint32 vee_folder_count_by_expression (CamelFolder *folder, const gchar *expression, GCancellable *cancellable, GError **error) { CamelFolderSearch *search; guint32 count; search = camel_folder_search_new (); camel_folder_search_set_folder (search, folder); count = camel_folder_search_count (search, expression, cancellable, error); g_object_unref (search); return count; } static void vee_folder_search_free (CamelFolder *folder, GPtrArray *result) { camel_folder_search_free_result (NULL, result); } static void vee_folder_delete (CamelFolder *folder) { CamelVeeFolder *vfolder; g_return_if_fail (CAMEL_IS_VEE_FOLDER (folder)); vfolder = CAMEL_VEE_FOLDER (folder); g_rec_mutex_lock (&vfolder->priv->subfolder_lock); while (vfolder->priv->subfolders) { CamelFolder *subfolder = vfolder->priv->subfolders->data; g_object_ref (subfolder); g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); camel_vee_folder_remove_folder (vfolder, subfolder, NULL); g_object_unref (subfolder); g_rec_mutex_lock (&vfolder->priv->subfolder_lock); } g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); ((CamelFolderClass *) camel_vee_folder_parent_class)->delete_ (folder); } static void vee_folder_freeze (CamelFolder *folder) { CamelVeeFolder *vfolder = CAMEL_VEE_FOLDER (folder); GList *link; if (vfolder->priv->parent_vee_store && !vee_folder_is_unmatched (vfolder)) { CamelVeeFolder *unmatched_folder; unmatched_folder = camel_vee_store_get_unmatched_folder (vfolder->priv->parent_vee_store); if (unmatched_folder) camel_folder_freeze (CAMEL_FOLDER (unmatched_folder)); } g_rec_mutex_lock (&vfolder->priv->subfolder_lock); for (link = vfolder->priv->subfolders; link; link = g_list_next (link)) { CamelFolder *subfolder = link->data; camel_folder_freeze (subfolder); } g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); /* call parent implementation */ CAMEL_FOLDER_CLASS (camel_vee_folder_parent_class)->freeze (folder); } static void vee_folder_thaw (CamelFolder *folder) { CamelVeeFolder *vfolder = CAMEL_VEE_FOLDER (folder); GList *link; if (vfolder->priv->parent_vee_store && !vee_folder_is_unmatched (vfolder)) { CamelVeeFolder *unmatched_folder; unmatched_folder = camel_vee_store_get_unmatched_folder (vfolder->priv->parent_vee_store); if (unmatched_folder) camel_folder_thaw (CAMEL_FOLDER (unmatched_folder)); } g_rec_mutex_lock (&vfolder->priv->subfolder_lock); for (link = vfolder->priv->subfolders; link; link = g_list_next (link)) { CamelFolder *subfolder = link->data; camel_folder_thaw (subfolder); } g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); /* call parent implementation */ CAMEL_FOLDER_CLASS (camel_vee_folder_parent_class)->thaw (folder); } static gboolean vee_folder_append_message_sync (CamelFolder *folder, CamelMimeMessage *message, CamelMessageInfo *info, gchar **appended_uid, GCancellable *cancellable, GError **error) { g_set_error ( error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Cannot copy or move messages into a Virtual Folder")); return FALSE; } static gboolean vee_folder_expunge_sync (CamelFolder *folder, GCancellable *cancellable, GError **error) { return CAMEL_FOLDER_GET_CLASS (folder)-> synchronize_sync (folder, TRUE, cancellable, error); } static CamelMimeMessage * vee_folder_get_message_sync (CamelFolder *folder, const gchar *uid, GCancellable *cancellable, GError **error) { CamelVeeMessageInfo *mi; CamelMimeMessage *msg = NULL; mi = (CamelVeeMessageInfo *) camel_folder_summary_get (folder->summary, uid); if (mi) { msg = camel_folder_get_message_sync ( camel_folder_summary_get_folder (mi->orig_summary), camel_message_info_uid (mi) + 8, cancellable, error); camel_message_info_unref (mi); } else { g_set_error ( error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID_UID, _("No such message %s in %s"), uid, camel_folder_get_display_name (folder)); } return msg; } static gboolean vee_folder_refresh_info_sync (CamelFolder *folder, GCancellable *cancellable, GError **error) { CamelVeeFolder *vf = (CamelVeeFolder *) folder; vee_folder_propagate_skipped_changes (vf); vee_folder_rebuild_all (vf, cancellable); return TRUE; } static gboolean vee_folder_synchronize_sync (CamelFolder *folder, gboolean expunge, GCancellable *cancellable, GError **error) { CamelVeeFolder *vfolder = (CamelVeeFolder *) folder; gboolean res = TRUE; GList *iter; g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (folder), FALSE); vee_folder_propagate_skipped_changes (vfolder); /* basically no-op here, especially do not call synchronize on subfolders * if not expunging, they are responsible for themselfs */ if (!expunge || vee_folder_is_unmatched (vfolder)) return TRUE; g_rec_mutex_lock (&vfolder->priv->subfolder_lock); for (iter = vfolder->priv->subfolders; iter && !g_cancellable_is_cancelled (cancellable); iter = iter->next) { GError *local_error = NULL; CamelFolder *subfolder = iter->data; if (!camel_folder_synchronize_sync (subfolder, expunge, cancellable, &local_error)) { if (local_error && strncmp (local_error->message, "no such table", 13) != 0 && error && !*error) { const gchar *desc; desc = camel_folder_get_description (subfolder); g_propagate_prefixed_error ( error, local_error, _("Error storing '%s': "), desc); res = FALSE; } else g_clear_error (&local_error); } } g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); return res; } static gboolean vee_folder_transfer_messages_to_sync (CamelFolder *folder, GPtrArray *uids, CamelFolder *dest, gboolean delete_originals, GPtrArray **transferred_uids, GCancellable *cancellable, GError **error) { g_set_error ( error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Cannot copy or move messages into a Virtual Folder")); return FALSE; } static void vee_folder_set_expression (CamelVeeFolder *vee_folder, const gchar *query) { g_rec_mutex_lock (&vee_folder->priv->subfolder_lock); /* no change, do nothing */ if ((vee_folder->priv->expression && query && strcmp (vee_folder->priv->expression, query) == 0) || (vee_folder->priv->expression == NULL && query == NULL)) { g_rec_mutex_unlock (&vee_folder->priv->subfolder_lock); return; } g_free (vee_folder->priv->expression); if (query) vee_folder->priv->expression = g_strdup (query); vee_folder_rebuild_all (vee_folder, NULL); g_rec_mutex_unlock (&vee_folder->priv->subfolder_lock); } static void vee_folder_rebuild_folder (CamelVeeFolder *vfolder, CamelFolder *subfolder, GCancellable *cancellable) { CamelFolderChangeInfo *changes; CamelFolder *v_folder; v_folder = CAMEL_FOLDER (vfolder); changes = camel_folder_change_info_new (); camel_folder_freeze (v_folder); vee_folder_rebuild_folder_with_changes (vfolder, subfolder, changes, cancellable); camel_folder_thaw (v_folder); if (camel_folder_change_info_changed (changes)) camel_folder_changed (CAMEL_FOLDER (vfolder), changes); camel_folder_change_info_free (changes); } static void vee_folder_add_folder (CamelVeeFolder *vfolder, CamelFolder *subfolder, GCancellable *cancellable) { if (vfolder->priv->parent_vee_store) camel_vee_store_note_subfolder_used (vfolder->priv->parent_vee_store, subfolder, vfolder); vee_folder_rebuild_folder (vfolder, subfolder, cancellable); } static gboolean vee_folder_remove_from_unmatched_changed_cb (gpointer key, gpointer value, gpointer user_data) { CamelVeeMessageInfoData *mi_data = key; CamelFolder *subfolder = user_data; CamelVeeSubfolderData *sf_data; g_return_val_if_fail (mi_data != NULL, TRUE); sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data); return subfolder == camel_vee_subfolder_data_get_folder (sf_data); } static void vee_folder_remove_folder (CamelVeeFolder *vfolder, CamelFolder *subfolder, GCancellable *cancellable) { CamelFolderChangeInfo *changes; CamelFolder *v_folder; GHashTable *uids; v_folder = CAMEL_FOLDER (vfolder); changes = camel_folder_change_info_new (); camel_folder_freeze (v_folder); uids = camel_vee_summary_get_uids_for_subfolder (CAMEL_VEE_SUMMARY (v_folder->summary), subfolder); if (uids) { struct RemoveUnmatchedData rud; rud.vfolder = vfolder; rud.vsummary = CAMEL_VEE_SUMMARY (v_folder->summary); rud.subfolder = subfolder; rud.data_cache = vee_folder_get_data_cache (vfolder); rud.changes = changes; rud.is_orig_message_uid = FALSE; g_hash_table_foreach (uids, vee_folder_remove_unmatched_cb, &rud); if (vee_folder_is_unmatched (vfolder) && !camel_vee_folder_get_auto_update (vfolder) && g_hash_table_size (vfolder->priv->unmatched_add_changed) + g_hash_table_size (vfolder->priv->unmatched_remove_changed) > 0) { /* forget about these in cached updates */ g_hash_table_foreach_remove (vfolder->priv->unmatched_add_changed, vee_folder_remove_from_unmatched_changed_cb, subfolder); g_hash_table_foreach_remove (vfolder->priv->unmatched_remove_changed, vee_folder_remove_from_unmatched_changed_cb, subfolder); } g_hash_table_destroy (uids); } if (vfolder->priv->parent_vee_store) camel_vee_store_note_subfolder_unused (vfolder->priv->parent_vee_store, subfolder, vfolder); camel_folder_thaw (v_folder); /* do not notify about changes in vfolder which * is removing its subfolders in dispose */ if (!vfolder->priv->destroyed && camel_folder_change_info_changed (changes)) camel_folder_changed (CAMEL_FOLDER (vfolder), changes); camel_folder_change_info_free (changes); } static void vee_folder_folder_changed (CamelVeeFolder *vee_folder, CamelFolder *subfolder, CamelFolderChangeInfo *changes) { CamelVeeFolderPrivate *p = vee_folder->priv; FolderChangedData *data; CamelFolder *folder; CamelStore *parent_store; CamelSession *session; if (p->destroyed) return; folder = CAMEL_FOLDER (vee_folder); parent_store = camel_folder_get_parent_store (folder); session = camel_service_ref_session (CAMEL_SERVICE (parent_store)); g_async_queue_lock (vee_folder->priv->change_queue); data = vee_folder_changed_data_new (subfolder, changes); g_async_queue_push_unlocked (vee_folder->priv->change_queue, data); if (!vee_folder->priv->change_queue_busy) { gchar *description; description = g_strdup_printf ("Updating search folder '%s'", camel_folder_get_full_name (CAMEL_FOLDER (vee_folder))); camel_session_submit_job ( session, description, (CamelSessionCallback) vee_folder_process_changes, g_object_ref (vee_folder), (GDestroyNotify) g_object_unref); vee_folder->priv->change_queue_busy = TRUE; g_free (description); } g_async_queue_unlock (vee_folder->priv->change_queue); g_object_unref (session); } static void camel_vee_folder_class_init (CamelVeeFolderClass *class) { GObjectClass *object_class; CamelFolderClass *folder_class; g_type_class_add_private (class, sizeof (CamelVeeFolderPrivate)); object_class = G_OBJECT_CLASS (class); object_class->dispose = vee_folder_dispose; object_class->finalize = vee_folder_finalize; object_class->get_property = vee_folder_get_property; object_class->set_property = vee_folder_set_property; folder_class = CAMEL_FOLDER_CLASS (class); folder_class->search_by_expression = vee_folder_search_by_expression; folder_class->search_by_uids = vee_folder_search_by_uids; folder_class->count_by_expression = vee_folder_count_by_expression; folder_class->search_free = vee_folder_search_free; folder_class->delete_ = vee_folder_delete; folder_class->freeze = vee_folder_freeze; folder_class->thaw = vee_folder_thaw; folder_class->append_message_sync = vee_folder_append_message_sync; folder_class->expunge_sync = vee_folder_expunge_sync; folder_class->get_message_sync = vee_folder_get_message_sync; folder_class->refresh_info_sync = vee_folder_refresh_info_sync; folder_class->synchronize_sync = vee_folder_synchronize_sync; folder_class->transfer_messages_to_sync = vee_folder_transfer_messages_to_sync; class->set_expression = vee_folder_set_expression; class->add_folder = vee_folder_add_folder; class->remove_folder = vee_folder_remove_folder; class->rebuild_folder = vee_folder_rebuild_folder; class->folder_changed = vee_folder_folder_changed; g_object_class_install_property ( object_class, PROP_AUTO_UPDATE, g_param_spec_boolean ( "auto-update", "Auto Update", _("Automatically _update on change in source folders"), TRUE, G_PARAM_READWRITE | CAMEL_PARAM_PERSISTENT)); } static void camel_vee_folder_init (CamelVeeFolder *vee_folder) { CamelFolder *folder = CAMEL_FOLDER (vee_folder); vee_folder->priv = CAMEL_VEE_FOLDER_GET_PRIVATE (vee_folder); folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY; /* FIXME: what to do about user flags if the subfolder doesn't support them? */ folder->permanent_flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN; g_rec_mutex_init (&vee_folder->priv->subfolder_lock); g_rec_mutex_init (&vee_folder->priv->changed_lock); vee_folder->priv->auto_update = TRUE; vee_folder->priv->ignore_changed = g_hash_table_new (g_direct_hash, g_direct_equal); vee_folder->priv->skipped_changes = g_hash_table_new (g_direct_hash, g_direct_equal); vee_folder->priv->unmatched_add_changed = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); vee_folder->priv->unmatched_remove_changed = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); vee_folder->priv->change_queue = g_async_queue_new_full ( (GDestroyNotify) vee_folder_changed_data_free); } void camel_vee_folder_construct (CamelVeeFolder *vf, guint32 flags) { CamelFolder *folder = (CamelFolder *) vf; CamelStore *parent_store; vf->flags = flags; parent_store = camel_folder_get_parent_store (CAMEL_FOLDER (vf)); if (CAMEL_IS_VEE_STORE (parent_store)) vf->priv->parent_vee_store = CAMEL_VEE_STORE (parent_store); else vf->priv->vee_data_cache = camel_vee_data_cache_new (); folder->summary = camel_vee_summary_new (folder); /* only for subfolders of vee-store */ if (vf->priv->parent_vee_store) { const gchar *user_data_dir; gchar *state_file, *folder_name, *filename; user_data_dir = camel_service_get_user_data_dir (CAMEL_SERVICE (parent_store)); folder_name = g_uri_escape_string (camel_folder_get_full_name (folder), NULL, TRUE); filename = g_strconcat (folder_name, ".cmeta", NULL); state_file = g_build_filename (user_data_dir, filename, NULL); camel_object_set_state_filename (CAMEL_OBJECT (vf), state_file); g_free (state_file); g_free (filename); g_free (folder_name); /* set/load persistent state */ camel_object_state_read (CAMEL_OBJECT (vf)); } } /** * camel_vee_folder_new: * @parent_store: the parent CamelVeeStore * @full: the full path to the vfolder. * @flags: flags of some kind * * Create a new CamelVeeFolder object. * * Returns: A new CamelVeeFolder widget. **/ CamelFolder * camel_vee_folder_new (CamelStore *parent_store, const gchar *full, guint32 flags) { CamelVeeFolder *vf; g_return_val_if_fail (CAMEL_IS_STORE (parent_store), NULL); g_return_val_if_fail (full != NULL, NULL); if (CAMEL_IS_VEE_STORE (parent_store) && strcmp (full, CAMEL_UNMATCHED_NAME) == 0) { vf = camel_vee_store_get_unmatched_folder (CAMEL_VEE_STORE (parent_store)); if (vf) g_object_ref (vf); } else { const gchar *name = strrchr (full, '/'); if (name == NULL) name = full; else name++; vf = g_object_new ( CAMEL_TYPE_VEE_FOLDER, "display-name", name, "full-name", full, "parent-store", parent_store, NULL); camel_vee_folder_construct (vf, flags); } d (printf ("returning folder %s %p, count = %d\n", full, vf, camel_folder_get_message_count ((CamelFolder *) vf))); return (CamelFolder *) vf; } void camel_vee_folder_set_expression (CamelVeeFolder *vfolder, const gchar *expr) { CAMEL_VEE_FOLDER_GET_CLASS (vfolder)->set_expression (vfolder, expr); } /** * camel_vee_folder_get_expression: * * FIXME Document me! * * Since: 3.6 **/ const gchar * camel_vee_folder_get_expression (CamelVeeFolder *vfolder) { g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (vfolder), NULL); return vfolder->priv->expression; } /** * camel_vee_folder_add_folder: * @vfolder: Virtual Folder object * @subfolder: source CamelFolder to add to @vfolder * * Adds @subfolder as a source folder to @vfolder. **/ void camel_vee_folder_add_folder (CamelVeeFolder *vfolder, CamelFolder *subfolder, GCancellable *cancellable) { g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); if (vfolder == (CamelVeeFolder *) subfolder) { g_warning ("Adding a virtual folder to itself as source, ignored"); return; } g_rec_mutex_lock (&vfolder->priv->subfolder_lock); if (g_list_find (vfolder->priv->subfolders, subfolder) == NULL) { gint freeze_count; vfolder->priv->subfolders = g_list_append (vfolder->priv->subfolders, g_object_ref (subfolder)); freeze_count = camel_folder_get_frozen_count (CAMEL_FOLDER (vfolder)); while (freeze_count > 0) { camel_folder_freeze (subfolder); freeze_count--; } } else { /* nothing to do, it's already there */ g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); return; } g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); g_signal_connect ( subfolder, "changed", G_CALLBACK (subfolder_changed), vfolder); g_signal_connect ( subfolder, "deleted", G_CALLBACK (subfolder_deleted), vfolder); CAMEL_VEE_FOLDER_GET_CLASS (vfolder)->add_folder (vfolder, subfolder, cancellable); } /** * camel_vee_folder_remove_folder: * @vfolder: Virtual Folder object * @subfolder: source CamelFolder to remove from @vfolder * * Removed the source folder, @subfolder, from the virtual folder, @vfolder. **/ void camel_vee_folder_remove_folder (CamelVeeFolder *vfolder, CamelFolder *subfolder, GCancellable *cancellable) { gint freeze_count; g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); g_rec_mutex_lock (&vfolder->priv->subfolder_lock); if (g_list_find (vfolder->priv->subfolders, subfolder) == NULL) { g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); return; } g_signal_handlers_disconnect_by_func (subfolder, subfolder_changed, vfolder); g_signal_handlers_disconnect_by_func (subfolder, subfolder_deleted, vfolder); vfolder->priv->subfolders = g_list_remove (vfolder->priv->subfolders, subfolder); freeze_count = camel_folder_get_frozen_count (CAMEL_FOLDER (vfolder)); while (freeze_count > 0) { camel_folder_thaw (subfolder); freeze_count--; } g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); CAMEL_VEE_FOLDER_GET_CLASS (vfolder)->remove_folder (vfolder, subfolder, cancellable); g_object_unref (subfolder); } /** * camel_vee_folder_rebuild_folder: * @vfolder: Virtual Folder object * @subfolder: source CamelFolder to add to @vfolder * @cancellable: * * Rebuild the folder @subfolder, if it should be. **/ void camel_vee_folder_rebuild_folder (CamelVeeFolder *vfolder, CamelFolder *subfolder, GCancellable *cancellable) { vee_folder_propagate_skipped_changes (vfolder); CAMEL_VEE_FOLDER_GET_CLASS (vfolder)->rebuild_folder (vfolder, subfolder, cancellable); } static void remove_folders (CamelFolder *folder, CamelFolder *foldercopy, CamelVeeFolder *vf) { camel_vee_folder_remove_folder (vf, folder, NULL); g_object_unref (folder); } /** * camel_vee_folder_set_folders: * @vf: * @folders: * * Set the whole list of folder sources on a vee folder. **/ void camel_vee_folder_set_folders (CamelVeeFolder *vf, GList *folders, GCancellable *cancellable) { CamelVeeFolderPrivate *p = CAMEL_VEE_FOLDER_GET_PRIVATE (vf); GHashTable *remove = g_hash_table_new (NULL, NULL); GList *l, *to_add = NULL; CamelFolder *folder; /* setup a table of all folders we have currently */ g_rec_mutex_lock (&vf->priv->subfolder_lock); l = p->subfolders; while (l) { g_hash_table_insert (remove, l->data, l->data); g_object_ref (l->data); l = l->next; } g_rec_mutex_unlock (&vf->priv->subfolder_lock); camel_folder_freeze (CAMEL_FOLDER (vf)); /* if we already have the folder, ignore it, otherwise mark to add it */ l = folders; while (l) { if ((folder = g_hash_table_lookup (remove, l->data))) { g_hash_table_remove (remove, folder); g_object_unref (folder); } else { to_add = g_list_prepend (to_add, g_object_ref (l->data)); } l = l->next; } /* first remove any we still have */ g_hash_table_foreach (remove, (GHFunc) remove_folders, vf); g_hash_table_destroy (remove); /* then add those new */ for (l = to_add; l; l = l->next) { camel_vee_folder_add_folder (vf, l->data, cancellable); } g_list_free_full (to_add, g_object_unref); camel_folder_thaw (CAMEL_FOLDER (vf)); } /** * camel_vee_folder_add_vuid: * * FIXME Document me! * * Since: 3.6 **/ void camel_vee_folder_add_vuid (CamelVeeFolder *vfolder, CamelVeeMessageInfoData *mi_data, CamelFolderChangeInfo *changes) { CamelVeeSummary *vsummary; CamelVeeSubfolderData *sf_data; CamelFolder *subfolder; g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); g_return_if_fail (mi_data != NULL); g_return_if_fail (vee_folder_is_unmatched (vfolder)); sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data); subfolder = camel_vee_subfolder_data_get_folder (sf_data); g_rec_mutex_lock (&vfolder->priv->changed_lock); if (!camel_vee_folder_get_auto_update (vfolder) || g_hash_table_lookup (vfolder->priv->ignore_changed, subfolder) || g_hash_table_lookup (vfolder->priv->skipped_changes, subfolder)) { g_hash_table_remove (vfolder->priv->unmatched_remove_changed, mi_data); g_rec_mutex_lock (&vfolder->priv->subfolder_lock); if (g_list_find (vfolder->priv->subfolders, subfolder)) { /* postpone addition to the Unmatched folder, if the change was done * in the Unmatched folder itself or auto-update is disabled */ g_hash_table_insert ( vfolder->priv->unmatched_add_changed, g_object_ref (mi_data), GINT_TO_POINTER (1)); } g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); g_rec_mutex_unlock (&vfolder->priv->changed_lock); return; } g_rec_mutex_unlock (&vfolder->priv->changed_lock); vsummary = CAMEL_VEE_SUMMARY (CAMEL_FOLDER (vfolder)->summary); vee_folder_note_added_uid (vfolder, vsummary, mi_data, changes, FALSE); } /** * camel_vee_folder_remove_vuid: * * FIXME Document me! * * Since: 3.6 **/ void camel_vee_folder_remove_vuid (CamelVeeFolder *vfolder, CamelVeeMessageInfoData *mi_data, CamelFolderChangeInfo *changes) { CamelVeeSummary *vsummary; CamelVeeSubfolderData *sf_data; CamelVeeDataCache *data_cache; CamelFolder *subfolder; g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); g_return_if_fail (mi_data != NULL); g_return_if_fail (vee_folder_is_unmatched (vfolder)); sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data); subfolder = camel_vee_subfolder_data_get_folder (sf_data); g_rec_mutex_lock (&vfolder->priv->changed_lock); if (!camel_vee_folder_get_auto_update (vfolder) || g_hash_table_lookup (vfolder->priv->ignore_changed, subfolder) || g_hash_table_lookup (vfolder->priv->skipped_changes, subfolder)) { g_hash_table_remove (vfolder->priv->unmatched_add_changed, mi_data); g_rec_mutex_lock (&vfolder->priv->subfolder_lock); if (g_list_find (vfolder->priv->subfolders, subfolder)) { /* postpone removal from the Unmatched folder, if the change was done * in the Unmatched folder itself or auto-update is disabled */ g_hash_table_insert ( vfolder->priv->unmatched_remove_changed, g_object_ref (mi_data), GINT_TO_POINTER (1)); } g_rec_mutex_unlock (&vfolder->priv->subfolder_lock); g_rec_mutex_unlock (&vfolder->priv->changed_lock); return; } g_rec_mutex_unlock (&vfolder->priv->changed_lock); vsummary = CAMEL_VEE_SUMMARY (CAMEL_FOLDER (vfolder)->summary); data_cache = vee_folder_get_data_cache (vfolder); vee_folder_note_unmatch_uid (vfolder, vsummary, subfolder, data_cache, mi_data, changes); } /** * camel_vee_folder_get_location: * @vf: * @vinfo: * @realuid: if not NULL, set to the uid of the real message, must be * g_free'd by caller. * * Find the real folder (and uid) * * Returns: **/ CamelFolder * camel_vee_folder_get_location (CamelVeeFolder *vf, const CamelVeeMessageInfo *vinfo, gchar **realuid) { CamelFolder *folder; g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (vf), NULL); g_return_val_if_fail (vinfo != NULL, NULL); folder = camel_folder_summary_get_folder (vinfo->orig_summary); /* locking? yes? no? although the vfolderinfo is valid when obtained * the folder in it might not necessarily be so ...? */ if (CAMEL_IS_VEE_FOLDER (folder)) { CamelFolder *res; const CamelVeeMessageInfo *vfinfo; vfinfo = (CamelVeeMessageInfo *) camel_folder_get_message_info (folder, camel_message_info_uid (vinfo) + 8); res = camel_vee_folder_get_location ((CamelVeeFolder *) folder, vfinfo, realuid); camel_message_info_unref ((CamelMessageInfo *) vfinfo); return res; } else { if (realuid) *realuid = g_strdup (camel_message_info_uid (vinfo)+8); return folder; } } /** * camel_vee_folder_get_vee_uid_folder: * * FIXME Document me! * * Since: 3.6 **/ CamelFolder * camel_vee_folder_get_vee_uid_folder (CamelVeeFolder *vf, const gchar *vee_message_uid) { CamelFolder *res; CamelVeeDataCache *data_cache; CamelVeeMessageInfoData *mi_data; CamelVeeSubfolderData *sf_data; g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (vf), NULL); g_return_val_if_fail (vee_message_uid, NULL); res = NULL; data_cache = vee_folder_get_data_cache (vf); g_return_val_if_fail (data_cache != NULL, NULL); mi_data = camel_vee_data_cache_get_message_info_data_by_vuid (data_cache, vee_message_uid); if (mi_data) { sf_data = camel_vee_message_info_data_get_subfolder_data (mi_data); res = camel_vee_subfolder_data_get_folder (sf_data); g_object_unref (mi_data); } return res; } /** * camel_vee_folder_set_auto_update: * * FIXME Document me! * * Since: 3.6 **/ void camel_vee_folder_set_auto_update (CamelVeeFolder *vfolder, gboolean auto_update) { g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); if (vfolder->priv->auto_update == auto_update) return; vfolder->priv->auto_update = auto_update; g_object_notify (G_OBJECT (vfolder), "auto-update"); } /** * camel_vee_folder_get_auto_update: * * FIXME Document me! * * Since: 3.6 **/ gboolean camel_vee_folder_get_auto_update (CamelVeeFolder *vfolder) { g_return_val_if_fail (CAMEL_IS_VEE_FOLDER (vfolder), FALSE); return vfolder->priv->auto_update; } /** * camel_vee_folder_ignore_next_changed_event: * @vfolder: a #CamelVeeFolder * @subfolder: a #CamelFolder folder * * The next @subfolder-'s 'changed' event will be silently ignored. This * is usually used in virtual folders when the change was done in them, * but it is neither vTrash nor vJunk folder. Doing this avoids unnecessary * removals of messages which don't satisfy search criteria anymore, * which could be done on asynchronous delivery of folder's 'changed' signal. * These ignored changes are accumulated and used on folder refresh. * * Since: 3.2 **/ void camel_vee_folder_ignore_next_changed_event (CamelVeeFolder *vfolder, CamelFolder *subfolder) { g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); g_return_if_fail (subfolder != NULL); g_rec_mutex_lock (&vfolder->priv->changed_lock); g_hash_table_insert (vfolder->priv->ignore_changed, subfolder, GINT_TO_POINTER (1)); g_rec_mutex_unlock (&vfolder->priv->changed_lock); } /** * camel_vee_folder_remove_from_ignore_changed_event: * @vfolder: a #CamelVeeFolder * @subfolder: a #CamelFolder folder * * Make sure the next @subfolder-'s 'changed' event will not be silently ignored. * This is a counter-part function of camel_vee_folder_ignore_next_changed_event(), * when there was expected a change, which did not happen, to take back the previous * ignore event request. * * Since: 3.12 **/ void camel_vee_folder_remove_from_ignore_changed_event (CamelVeeFolder *vfolder, CamelFolder *subfolder) { g_return_if_fail (CAMEL_IS_VEE_FOLDER (vfolder)); g_return_if_fail (subfolder != NULL); g_rec_mutex_lock (&vfolder->priv->changed_lock); g_hash_table_remove (vfolder->priv->ignore_changed, subfolder); g_rec_mutex_unlock (&vfolder->priv->changed_lock); }