/* -*- 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 */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "camel-file-utils.h" #include "camel-store-summary.h" #include "camel-folder-summary.h" #include "camel-url.h" #include "camel-win32.h" #define d(x) #define io(x) /* io debug */ /* possible versions, for versioning changes */ #define CAMEL_STORE_SUMMARY_VERSION_0 (1) #define CAMEL_STORE_SUMMARY_VERSION_2 (2) /* current version */ #define CAMEL_STORE_SUMMARY_VERSION (2) #define CAMEL_STORE_SUMMARY_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), CAMEL_TYPE_STORE_SUMMARY, CamelStoreSummaryPrivate)) struct _CamelStoreSummaryPrivate { GRecMutex summary_lock; /* for the summary hashtable/array */ GRecMutex io_lock; /* load/save lock, for access to saved_count, etc */ gboolean dirty; /* summary has unsaved changes */ gchar *summary_path; /* header info */ guint32 version; /* version of base part of file */ guint32 count; /* how many were saved/loaded */ time_t time; /* timestamp for this summary (for implementors to use) */ GHashTable *folder_summaries; /* CamelFolderSummary->path; doesn't add reference to CamelFolderSummary */ guint scheduled_save_id; }; G_DEFINE_TYPE (CamelStoreSummary, camel_store_summary, G_TYPE_OBJECT) static void store_summary_finalize (GObject *object) { CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (object); guint ii; for (ii = 0; ii < summary->folders->len; ii++) { CamelStoreInfo *info; info = g_ptr_array_index (summary->folders, ii); camel_store_summary_info_unref (summary, info); } g_ptr_array_free (summary->folders, TRUE); g_hash_table_destroy (summary->folders_path); g_hash_table_destroy (summary->priv->folder_summaries); g_free (summary->priv->summary_path); g_rec_mutex_clear (&summary->priv->summary_lock); g_rec_mutex_clear (&summary->priv->io_lock); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (camel_store_summary_parent_class)->finalize (object); } static void store_summary_dispose (GObject *object) { CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (object); g_rec_mutex_lock (&summary->priv->summary_lock); if (summary->priv->scheduled_save_id != 0) { g_source_remove (summary->priv->scheduled_save_id); summary->priv->scheduled_save_id = 0; camel_store_summary_save (summary); } g_rec_mutex_unlock (&summary->priv->summary_lock); G_OBJECT_CLASS (camel_store_summary_parent_class)->dispose (object); } static gint store_summary_summary_header_load (CamelStoreSummary *summary, FILE *in) { gint32 version, flags, count; time_t time; fseek (in, 0, SEEK_SET); io (printf ("Loading header\n")); /* XXX The flags value is legacy; not used for anything. */ if (camel_file_util_decode_fixed_int32 (in, &version) == -1 || camel_file_util_decode_fixed_int32 (in, &flags) == -1 || camel_file_util_decode_time_t (in, &time) == -1 || camel_file_util_decode_fixed_int32 (in, &count) == -1) { return -1; } summary->priv->time = time; summary->priv->count = count; summary->priv->version = version; if (version < CAMEL_STORE_SUMMARY_VERSION_0) { g_warning ("Store summary header version too low"); return -1; } return 0; } static gint store_summary_summary_header_save (CamelStoreSummary *summary, FILE *out) { fseek (out, 0, SEEK_SET); io (printf ("Savining header\n")); /* always write latest version */ camel_file_util_encode_fixed_int32 (out, CAMEL_STORE_SUMMARY_VERSION); camel_file_util_encode_fixed_int32 (out, 0); /* flags (unused) */ camel_file_util_encode_time_t (out, summary->priv->time); return camel_file_util_encode_fixed_int32 ( out, camel_store_summary_count (summary)); } static CamelStoreInfo * store_summary_store_info_new (CamelStoreSummary *summary, const gchar *path) { CamelStoreInfo *info; info = camel_store_summary_info_new (summary); info->path = g_strdup (path); info->unread = CAMEL_STORE_INFO_FOLDER_UNKNOWN; info->total = CAMEL_STORE_INFO_FOLDER_UNKNOWN; return info; } static CamelStoreInfo * store_summary_store_info_load (CamelStoreSummary *summary, FILE *in) { CamelStoreInfo *info; info = camel_store_summary_info_new (summary); io (printf ("Loading folder info\n")); if (camel_file_util_decode_string (in, &info->path) == -1 || camel_file_util_decode_uint32 (in, &info->flags) == -1 || camel_file_util_decode_uint32 (in, &info->unread) == -1 || camel_file_util_decode_uint32 (in, &info->total) == -1) { camel_store_summary_info_unref (summary, info); return NULL; } if (!ferror (in)) return info; camel_store_summary_info_unref (summary, info); return NULL; } static gint store_summary_store_info_save (CamelStoreSummary *summary, FILE *out, CamelStoreInfo *info) { io (printf ("Saving folder info\n")); if (camel_file_util_encode_string (out, camel_store_info_path (summary, info)) == -1 || camel_file_util_encode_uint32 (out, info->flags) == -1 || camel_file_util_encode_uint32 (out, info->unread) == -1 || camel_file_util_encode_uint32 (out, info->total) == -1) return -1; return ferror (out); } static void store_summary_store_info_free (CamelStoreSummary *summary, CamelStoreInfo *info) { CamelStoreSummaryClass *class; class = CAMEL_STORE_SUMMARY_GET_CLASS (summary); g_free (info->path); g_slice_free1 (class->store_info_size, info); } static void store_summary_store_info_set_string (CamelStoreSummary *summary, CamelStoreInfo *info, gint type, const gchar *str) { switch (type) { case CAMEL_STORE_INFO_PATH: g_hash_table_remove (summary->folders_path, (gchar *) camel_store_info_path (summary, info)); g_free (info->path); info->path = g_strdup (str); g_hash_table_insert (summary->folders_path, (gchar *) camel_store_info_path (summary, info), info); summary->priv->dirty = TRUE; break; } } static void camel_store_summary_class_init (CamelStoreSummaryClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (CamelStoreSummaryPrivate)); object_class = G_OBJECT_CLASS (class); object_class->dispose = store_summary_dispose; object_class->finalize = store_summary_finalize; class->store_info_size = sizeof (CamelStoreInfo); class->summary_header_load = store_summary_summary_header_load; class->summary_header_save = store_summary_summary_header_save; class->store_info_new = store_summary_store_info_new; class->store_info_load = store_summary_store_info_load; class->store_info_save = store_summary_store_info_save; class->store_info_free = store_summary_store_info_free; class->store_info_set_string = store_summary_store_info_set_string; } static void camel_store_summary_init (CamelStoreSummary *summary) { summary->priv = CAMEL_STORE_SUMMARY_GET_PRIVATE (summary); summary->priv->version = CAMEL_STORE_SUMMARY_VERSION; summary->folders = g_ptr_array_new (); summary->folders_path = g_hash_table_new (g_str_hash, g_str_equal); summary->priv->folder_summaries = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); summary->priv->scheduled_save_id = 0; g_rec_mutex_init (&summary->priv->summary_lock); g_rec_mutex_init (&summary->priv->io_lock); } /** * camel_store_summary_new: * * Create a new #CamelStoreSummary object. * * Returns: a new #CamelStoreSummary object **/ CamelStoreSummary * camel_store_summary_new (void) { return g_object_new (CAMEL_TYPE_STORE_SUMMARY, NULL); } /** * camel_store_summary_set_filename: * @summary: a #CamelStoreSummary * @filename: a filename * * Set the filename where the summary will be loaded to/saved from. **/ void camel_store_summary_set_filename (CamelStoreSummary *summary, const gchar *name) { g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary)); g_rec_mutex_lock (&summary->priv->summary_lock); g_free (summary->priv->summary_path); summary->priv->summary_path = g_strdup (name); g_rec_mutex_unlock (&summary->priv->summary_lock); } /** * camel_store_summary_count: * @summary: a #CamelStoreSummary object * * Get the number of summary items stored in this summary. * * Returns: the number of items gint he summary. **/ gint camel_store_summary_count (CamelStoreSummary *summary) { g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1); return summary->folders->len; } /** * camel_store_summary_array: * @summary: a #CamelStoreSummary object * * Obtain a copy of the summary array. This is done atomically, * so cannot contain empty entries. * * It must be freed using camel_store_summary_array_free(). * * Returns: the summary array **/ GPtrArray * camel_store_summary_array (CamelStoreSummary *summary) { CamelStoreInfo *info; GPtrArray *res; gint i; g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL); g_rec_mutex_lock (&summary->priv->summary_lock); res = g_ptr_array_sized_new (summary->folders->len); for (i = 0; i < summary->folders->len; i++) { info = g_ptr_array_index (summary->folders, i); camel_store_summary_info_ref (summary, info); g_ptr_array_add (res, info); } g_rec_mutex_unlock (&summary->priv->summary_lock); return res; } /** * camel_store_summary_array_free: * @summary: a #CamelStoreSummary object * @array: the summary array as gotten from camel_store_summary_array() * * Free the folder summary array. **/ void camel_store_summary_array_free (CamelStoreSummary *summary, GPtrArray *array) { gint i; g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary)); g_return_if_fail (array != NULL); for (i = 0; i < array->len; i++) camel_store_summary_info_unref (summary, array->pdata[i]); g_ptr_array_free (array, TRUE); } /** * camel_store_summary_path: * @summary: a #CamelStoreSummary object * @path: path to the item * * Retrieve a summary item by path name. * * The returned #CamelStoreInfo is referenced for thread-safety and should be * unreferenced with camel_store_summary_info_unref() when finished with it. * * Returns: the summary item, or %NULL if the @path name is not * available **/ CamelStoreInfo * camel_store_summary_path (CamelStoreSummary *summary, const gchar *path) { CamelStoreInfo *info; g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL); g_return_val_if_fail (path != NULL, NULL); g_rec_mutex_lock (&summary->priv->summary_lock); info = g_hash_table_lookup (summary->folders_path, path); if (info != NULL) camel_store_summary_info_ref (summary, info); g_rec_mutex_unlock (&summary->priv->summary_lock); return info; } /** * camel_store_summary_load: * @summary: a #CamelStoreSummary object * * Load the summary off disk. * * Returns: %0 on success or %-1 on fail **/ gint camel_store_summary_load (CamelStoreSummary *summary) { CamelStoreSummaryClass *class; CamelStoreInfo *info; FILE *in; gint i; g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1); g_return_val_if_fail (summary->priv->summary_path != NULL, -1); class = CAMEL_STORE_SUMMARY_GET_CLASS (summary); g_return_val_if_fail (class->store_info_load != NULL, -1); in = g_fopen (summary->priv->summary_path, "rb"); if (in == NULL) return -1; g_rec_mutex_lock (&summary->priv->io_lock); if (class->summary_header_load (summary, in) == -1) goto error; /* now read in each message ... */ for (i = 0; i < summary->priv->count; i++) { info = class->store_info_load (summary, in); if (info == NULL) goto error; camel_store_summary_add (summary, info); } g_rec_mutex_unlock (&summary->priv->io_lock); if (fclose (in) != 0) return -1; summary->priv->dirty = FALSE; return 0; error: i = ferror (in); g_warning ("Cannot load summary file '%s': %s", summary->priv->summary_path, i == 0 ? "Unknown error" : g_strerror (i)); g_rec_mutex_unlock (&summary->priv->io_lock); fclose (in); summary->priv->dirty = FALSE; errno = i; return -1; } /** * camel_store_summary_save: * @summary: a #CamelStoreSummary object * * Writes the summary to disk. The summary is only written if changes * have occurred. * * Returns: %0 on succes or %-1 on fail **/ gint camel_store_summary_save (CamelStoreSummary *summary) { CamelStoreSummaryClass *class; CamelStoreInfo *info; FILE *out; gint fd; gint i; guint32 count; g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1); g_return_val_if_fail (summary->priv->summary_path != NULL, -1); class = CAMEL_STORE_SUMMARY_GET_CLASS (summary); g_return_val_if_fail (class->summary_header_save != NULL, -1); io (printf ("** saving summary\n")); if (!summary->priv->dirty) { io (printf ("** summary clean no save\n")); return 0; } fd = g_open ( summary->priv->summary_path, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600); if (fd == -1) { io (printf ("** open error: %s\n", g_strerror (errno))); return -1; } out = fdopen (fd, "wb"); if (out == NULL) { i = errno; printf ("** fdopen error: %s\n", g_strerror (errno)); close (fd); errno = i; return -1; } io (printf ("saving header\n")); g_rec_mutex_lock (&summary->priv->io_lock); if (class->summary_header_save (summary, out) == -1) { i = errno; fclose (out); g_rec_mutex_unlock (&summary->priv->io_lock); errno = i; return -1; } /* now write out each message ... */ /* FIXME: Locking? */ count = summary->folders->len; for (i = 0; i < count; i++) { info = summary->folders->pdata[i]; class->store_info_save (summary, out, info); } g_rec_mutex_unlock (&summary->priv->io_lock); if (fflush (out) != 0 || fsync (fileno (out)) == -1) { i = errno; fclose (out); errno = i; return -1; } if (fclose (out) != 0) return -1; summary->priv->dirty = FALSE; return 0; } /** * camel_store_summary_add: * @summary: a #CamelStoreSummary object * @info: a #CamelStoreInfo * * Adds a new @info record to the summary. If @info->uid is %NULL, * then a new uid is automatically re-assigned by calling * camel_store_summary_next_uid_string(). * * The @info record should have been generated by calling one of the * info_new_*() functions, as it will be free'd based on the summary * class. And MUST NOT be allocated directly using malloc. **/ void camel_store_summary_add (CamelStoreSummary *summary, CamelStoreInfo *info) { g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary)); if (info == NULL) return; if (camel_store_info_path (summary, info) == NULL) { g_warning ("Trying to add a folder info with missing required path name\n"); return; } g_rec_mutex_lock (&summary->priv->summary_lock); g_ptr_array_add (summary->folders, info); g_hash_table_insert (summary->folders_path, (gchar *) camel_store_info_path (summary, info), info); summary->priv->dirty = TRUE; g_rec_mutex_unlock (&summary->priv->summary_lock); } /** * camel_store_summary_add_from_path: * @summary: a #CamelStoreSummary object * @path: item path * * Build a new info record based on the name, and add it to the summary. * * Returns: the newly added record **/ CamelStoreInfo * camel_store_summary_add_from_path (CamelStoreSummary *summary, const gchar *path) { CamelStoreInfo *info; g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL); g_return_val_if_fail (path != NULL, NULL); g_rec_mutex_lock (&summary->priv->summary_lock); info = g_hash_table_lookup (summary->folders_path, path); if (info != NULL) { g_warning ("Trying to add folder '%s' to summary that already has it", path); info = NULL; } else { CamelStoreSummaryClass *class; class = CAMEL_STORE_SUMMARY_GET_CLASS (summary); g_return_val_if_fail (class->store_info_new != NULL, NULL); info = class->store_info_new (summary, path); g_ptr_array_add (summary->folders, info); g_hash_table_insert (summary->folders_path, (gchar *) camel_store_info_path (summary, info), info); summary->priv->dirty = TRUE; } g_rec_mutex_unlock (&summary->priv->summary_lock); return info; } /** * camel_store_summary_info_ref: * @summary: a #CamelStoreSummary object * @info: a #CamelStoreInfo * * Add an extra reference to @info. * * Returns: the @info argument **/ CamelStoreInfo * camel_store_summary_info_ref (CamelStoreSummary *summary, CamelStoreInfo *info) { g_return_val_if_fail (info != NULL, NULL); g_return_val_if_fail (info->refcount > 0, NULL); g_atomic_int_inc (&info->refcount); return info; } /** * camel_store_summary_info_unref: * @summary: a #CamelStoreSummary object * @info: a #CamelStoreInfo * * Unref and potentially free @info, and all associated memory. **/ void camel_store_summary_info_unref (CamelStoreSummary *summary, CamelStoreInfo *info) { g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary)); g_return_if_fail (info != NULL); g_return_if_fail (info->refcount > 0); if (g_atomic_int_dec_and_test (&info->refcount)) { CamelStoreSummaryClass *class; class = CAMEL_STORE_SUMMARY_GET_CLASS (summary); g_return_if_fail (class->store_info_free != NULL); class->store_info_free (summary, info); } } /** * camel_store_summary_touch: * @summary: a #CamelStoreSummary object * * Mark the summary as changed, so that a save will force it to be * written back to disk. **/ void camel_store_summary_touch (CamelStoreSummary *summary) { g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary)); g_rec_mutex_lock (&summary->priv->summary_lock); summary->priv->dirty = TRUE; g_rec_mutex_unlock (&summary->priv->summary_lock); } /** * camel_store_summary_remove: * @summary: a #CamelStoreSummary object * @info: a #CamelStoreInfo * * Remove a specific @info record from the summary. **/ void camel_store_summary_remove (CamelStoreSummary *summary, CamelStoreInfo *info) { g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary)); g_return_if_fail (info != NULL); g_rec_mutex_lock (&summary->priv->summary_lock); g_hash_table_remove (summary->folders_path, camel_store_info_path (summary, info)); g_ptr_array_remove (summary->folders, info); summary->priv->dirty = TRUE; g_rec_mutex_unlock (&summary->priv->summary_lock); camel_store_summary_info_unref (summary, info); } /** * camel_store_summary_remove_path: * @summary: a #CamelStoreSummary object * @path: item path * * Remove a specific info record from the summary, by @path. **/ void camel_store_summary_remove_path (CamelStoreSummary *summary, const gchar *path) { CamelStoreInfo *oldinfo; gchar *oldpath; g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary)); g_return_if_fail (path != NULL); g_rec_mutex_lock (&summary->priv->summary_lock); if (g_hash_table_lookup_extended (summary->folders_path, path, (gpointer) &oldpath, (gpointer) &oldinfo)) { /* make sure it doesn't vanish while we're removing it */ camel_store_summary_info_ref (summary, oldinfo); g_rec_mutex_unlock (&summary->priv->summary_lock); camel_store_summary_remove (summary, oldinfo); camel_store_summary_info_unref (summary, oldinfo); } else { g_rec_mutex_unlock (&summary->priv->summary_lock); } } /** * camel_store_summary_info_new: * @summary: a #CamelStoreSummary object * * Allocate a new #CamelStoreInfo, suitable for adding to this * summary. * * Returns: the newly allocated #CamelStoreInfo **/ CamelStoreInfo * camel_store_summary_info_new (CamelStoreSummary *summary) { CamelStoreSummaryClass *class; CamelStoreInfo *info; g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL); class = CAMEL_STORE_SUMMARY_GET_CLASS (summary); g_return_val_if_fail (class->store_info_size > 0, NULL); info = g_slice_alloc0 (class->store_info_size); info->refcount = 1; return info; } /** * camel_store_info_set_string: * @summary: a #CamelStoreSummary object * @info: a #CamelStoreInfo * @type: specific string being set * @value: string value to set * * Set a specific string on the @info. **/ void camel_store_info_set_string (CamelStoreSummary *summary, CamelStoreInfo *info, gint type, const gchar *value) { CamelStoreSummaryClass *class; g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary)); g_return_if_fail (info != NULL); class = CAMEL_STORE_SUMMARY_GET_CLASS (summary); g_return_if_fail (class->store_info_set_string != NULL); g_rec_mutex_lock (&summary->priv->summary_lock); class->store_info_set_string (summary, info, type, value); g_rec_mutex_unlock (&summary->priv->summary_lock); } /** * camel_store_info_path: * @summary: a #CamelStoreSummary * @info: a #CamelStoreInfo * * Returns the path string from @info. * * Returns: the path string from @info **/ const gchar * camel_store_info_path (CamelStoreSummary *summary, CamelStoreInfo *info) { g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL); g_return_val_if_fail (info != NULL, NULL); /* XXX Not thread-safe; should return a duplicate. */ return info->path; } /** * camel_store_info_name: * @summary: a #CamelStoreSummary * @info: a #CamelStoreInfo * * Returns the last segment of the path string from @info. * * Returns: the last segment of the path string from @info **/ const gchar * camel_store_info_name (CamelStoreSummary *summary, CamelStoreInfo *info) { const gchar *cp; g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL); g_return_val_if_fail (info != NULL, NULL); cp = strrchr (info->path, '/'); /* XXX Not thread-safe; should return a duplicate. */ return (cp != NULL) ? cp + 1 : info->path; } static gboolean store_summary_save_timeout (gpointer user_data) { CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (user_data); g_return_val_if_fail (summary != NULL, FALSE); g_rec_mutex_lock (&summary->priv->summary_lock); if (summary->priv->scheduled_save_id) { summary->priv->scheduled_save_id = 0; camel_store_summary_save (summary); } g_rec_mutex_unlock (&summary->priv->summary_lock); return FALSE; } static void store_summary_schedule_save (CamelStoreSummary *summary) { g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary)); if (summary->priv->scheduled_save_id != 0) g_source_remove (summary->priv->scheduled_save_id); summary->priv->scheduled_save_id = g_timeout_add_seconds ( 5, store_summary_save_timeout, summary); g_source_set_name_by_id ( summary->priv->scheduled_save_id, "[camel] store_summary_save_timeout"); } static void store_summary_sync_folder_summary_count_cb (CamelFolderSummary *folder_summary, GParamSpec *param, CamelStoreSummary *summary) { gint new_count; const gchar *path; CamelStoreInfo *si; g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary)); g_return_if_fail (param != NULL); g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary)); path = g_hash_table_lookup (summary->priv->folder_summaries, folder_summary); g_return_if_fail (path != NULL); g_rec_mutex_lock (&summary->priv->summary_lock); si = camel_store_summary_path (summary, path); if (!si) { g_rec_mutex_unlock (&summary->priv->summary_lock); g_warning ("%s: Store summary %p doesn't hold path '%s'", G_STRFUNC, summary, path); return; } if (g_strcmp0 (g_param_spec_get_name (param), "saved-count") == 0) { new_count = camel_folder_summary_get_saved_count (folder_summary); if (si->total != new_count) { si->total = new_count; camel_store_summary_touch (summary); store_summary_schedule_save (summary); } } else if (g_strcmp0 (g_param_spec_get_name (param), "unread-count") == 0) { new_count = camel_folder_summary_get_unread_count (folder_summary); if (si->unread != new_count) { si->unread = new_count; camel_store_summary_touch (summary); store_summary_schedule_save (summary); } } else { g_warn_if_reached (); } camel_store_summary_info_unref (summary, si); g_rec_mutex_unlock (&summary->priv->summary_lock); } /** * camel_store_summary_connect_folder_summary: * @summary: a #CamelStoreSummary object * @path: used path for @folder_summary * @folder_summary: a #CamelFolderSummary object * * Connects listeners for count changes on @folder_summary to keep * CamelStoreInfo.total and CamelStoreInfo.unread in sync transparently. * The @folder_summary is stored in @summary as @path. Use * camel_store_summary_disconnect_folder_summary() to disconnect from * listening. * * Returns: Whether successfully connect callbacks for count change * notifications. * * Since: 3.4 **/ gboolean camel_store_summary_connect_folder_summary (CamelStoreSummary *summary, const gchar *path, CamelFolderSummary *folder_summary) { CamelStoreInfo *si; g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), FALSE); g_return_val_if_fail (path != NULL, FALSE); g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary), FALSE); g_rec_mutex_lock (&summary->priv->summary_lock); si = camel_store_summary_path (summary, path); if (!si) { g_rec_mutex_unlock (&summary->priv->summary_lock); g_warning ("%s: Store summary %p doesn't hold path '%s'", G_STRFUNC, summary, path); return FALSE; } camel_store_summary_info_unref (summary, si); if (g_hash_table_lookup (summary->priv->folder_summaries, folder_summary)) { g_rec_mutex_unlock (&summary->priv->summary_lock); g_warning ("%s: Store summary %p already listens on folder summary %p", G_STRFUNC, summary, folder_summary); return FALSE; } g_hash_table_insert (summary->priv->folder_summaries, folder_summary, g_strdup (path)); g_signal_connect (folder_summary, "notify::saved-count", G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary); g_signal_connect (folder_summary, "notify::unread-count", G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary); g_rec_mutex_unlock (&summary->priv->summary_lock); return TRUE; } /** * camel_store_summary_disconnect_folder_summary: * @summary: a #CamelStoreSummary object * @folder_summary: a #CamelFolderSummary object * * Diconnects count change listeners previously connected * by camel_store_summary_connect_folder_summary(). * * Returns: Whether such connection existed and whether was successfully * removed. * * Since: 3.4 **/ gboolean camel_store_summary_disconnect_folder_summary (CamelStoreSummary *summary, CamelFolderSummary *folder_summary) { g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), FALSE); g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary), FALSE); g_rec_mutex_lock (&summary->priv->summary_lock); if (!g_hash_table_lookup (summary->priv->folder_summaries, folder_summary)) { g_rec_mutex_unlock (&summary->priv->summary_lock); g_warning ("%s: Store summary %p is not connected to folder summary %p", G_STRFUNC, summary, folder_summary); return FALSE; } g_signal_handlers_disconnect_by_func (folder_summary, G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary); g_hash_table_remove (summary->priv->folder_summaries, folder_summary); if (summary->priv->scheduled_save_id != 0) { g_source_remove (summary->priv->scheduled_save_id); summary->priv->scheduled_save_id = 0; } camel_store_summary_save (summary); g_rec_mutex_unlock (&summary->priv->summary_lock); return TRUE; }