1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5 * Authors: Michael Zucchi <notzed@ximian.com>
7 * This library is free software you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation.
11 * This library is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this library; if not, see <http://www.gnu.org/licenses/>.
31 #include <sys/types.h>
33 #include <glib/gstdio.h>
35 #include "camel-file-utils.h"
36 #include "camel-store-summary.h"
37 #include "camel-folder-summary.h"
38 #include "camel-url.h"
39 #include "camel-win32.h"
42 #define io(x) /* io debug */
44 /* possible versions, for versioning changes */
45 #define CAMEL_STORE_SUMMARY_VERSION_0 (1)
46 #define CAMEL_STORE_SUMMARY_VERSION_2 (2)
49 #define CAMEL_STORE_SUMMARY_VERSION (2)
51 #define CAMEL_STORE_SUMMARY_GET_PRIVATE(obj) \
52 (G_TYPE_INSTANCE_GET_PRIVATE \
53 ((obj), CAMEL_TYPE_STORE_SUMMARY, CamelStoreSummaryPrivate))
55 struct _CamelStoreSummaryPrivate {
56 GRecMutex summary_lock; /* for the summary hashtable/array */
57 GRecMutex io_lock; /* load/save lock, for access to saved_count, etc */
59 gboolean dirty; /* summary has unsaved changes */
64 guint32 version; /* version of base part of file */
65 guint32 count; /* how many were saved/loaded */
66 time_t time; /* timestamp for this summary (for implementors to use) */
68 GHashTable *folder_summaries; /* CamelFolderSummary->path; doesn't add reference to CamelFolderSummary */
70 guint scheduled_save_id;
73 G_DEFINE_TYPE (CamelStoreSummary, camel_store_summary, G_TYPE_OBJECT)
76 store_summary_finalize (GObject *object)
78 CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (object);
81 for (ii = 0; ii < summary->folders->len; ii++) {
84 info = g_ptr_array_index (summary->folders, ii);
85 camel_store_summary_info_unref (summary, info);
88 g_ptr_array_free (summary->folders, TRUE);
89 g_hash_table_destroy (summary->folders_path);
90 g_hash_table_destroy (summary->priv->folder_summaries);
92 g_free (summary->priv->summary_path);
94 g_rec_mutex_clear (&summary->priv->summary_lock);
95 g_rec_mutex_clear (&summary->priv->io_lock);
97 /* Chain up to parent's finalize() method. */
98 G_OBJECT_CLASS (camel_store_summary_parent_class)->finalize (object);
102 store_summary_dispose (GObject *object)
104 CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (object);
106 g_rec_mutex_lock (&summary->priv->summary_lock);
108 if (summary->priv->scheduled_save_id != 0) {
109 g_source_remove (summary->priv->scheduled_save_id);
110 summary->priv->scheduled_save_id = 0;
111 camel_store_summary_save (summary);
114 g_rec_mutex_unlock (&summary->priv->summary_lock);
116 G_OBJECT_CLASS (camel_store_summary_parent_class)->dispose (object);
120 store_summary_summary_header_load (CamelStoreSummary *summary,
123 gint32 version, flags, count;
126 fseek (in, 0, SEEK_SET);
128 io (printf ("Loading header\n"));
130 /* XXX The flags value is legacy; not used for anything. */
131 if (camel_file_util_decode_fixed_int32 (in, &version) == -1
132 || camel_file_util_decode_fixed_int32 (in, &flags) == -1
133 || camel_file_util_decode_time_t (in, &time) == -1
134 || camel_file_util_decode_fixed_int32 (in, &count) == -1) {
138 summary->priv->time = time;
139 summary->priv->count = count;
140 summary->priv->version = version;
142 if (version < CAMEL_STORE_SUMMARY_VERSION_0) {
143 g_warning ("Store summary header version too low");
151 store_summary_summary_header_save (CamelStoreSummary *summary,
154 fseek (out, 0, SEEK_SET);
156 io (printf ("Savining header\n"));
158 /* always write latest version */
159 camel_file_util_encode_fixed_int32 (out, CAMEL_STORE_SUMMARY_VERSION);
160 camel_file_util_encode_fixed_int32 (out, 0); /* flags (unused) */
161 camel_file_util_encode_time_t (out, summary->priv->time);
163 return camel_file_util_encode_fixed_int32 (
164 out, camel_store_summary_count (summary));
167 static CamelStoreInfo *
168 store_summary_store_info_new (CamelStoreSummary *summary,
171 CamelStoreInfo *info;
173 info = camel_store_summary_info_new (summary);
175 info->path = g_strdup (path);
176 info->unread = CAMEL_STORE_INFO_FOLDER_UNKNOWN;
177 info->total = CAMEL_STORE_INFO_FOLDER_UNKNOWN;
182 static CamelStoreInfo *
183 store_summary_store_info_load (CamelStoreSummary *summary,
186 CamelStoreInfo *info;
188 info = camel_store_summary_info_new (summary);
190 io (printf ("Loading folder info\n"));
192 if (camel_file_util_decode_string (in, &info->path) == -1 ||
193 camel_file_util_decode_uint32 (in, &info->flags) == -1 ||
194 camel_file_util_decode_uint32 (in, &info->unread) == -1 ||
195 camel_file_util_decode_uint32 (in, &info->total) == -1) {
196 camel_store_summary_info_unref (summary, info);
204 camel_store_summary_info_unref (summary, info);
210 store_summary_store_info_save (CamelStoreSummary *summary,
212 CamelStoreInfo *info)
214 io (printf ("Saving folder info\n"));
216 if (camel_file_util_encode_string (out, camel_store_info_path (summary, info)) == -1 ||
217 camel_file_util_encode_uint32 (out, info->flags) == -1 ||
218 camel_file_util_encode_uint32 (out, info->unread) == -1 ||
219 camel_file_util_encode_uint32 (out, info->total) == -1)
226 store_summary_store_info_free (CamelStoreSummary *summary,
227 CamelStoreInfo *info)
229 CamelStoreSummaryClass *class;
231 class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
234 g_slice_free1 (class->store_info_size, info);
238 store_summary_store_info_set_string (CamelStoreSummary *summary,
239 CamelStoreInfo *info,
244 case CAMEL_STORE_INFO_PATH:
245 g_hash_table_remove (summary->folders_path, (gchar *) camel_store_info_path (summary, info));
247 info->path = g_strdup (str);
248 g_hash_table_insert (summary->folders_path, (gchar *) camel_store_info_path (summary, info), info);
249 summary->priv->dirty = TRUE;
255 camel_store_summary_class_init (CamelStoreSummaryClass *class)
257 GObjectClass *object_class;
259 g_type_class_add_private (class, sizeof (CamelStoreSummaryPrivate));
261 object_class = G_OBJECT_CLASS (class);
262 object_class->dispose = store_summary_dispose;
263 object_class->finalize = store_summary_finalize;
265 class->store_info_size = sizeof (CamelStoreInfo);
266 class->summary_header_load = store_summary_summary_header_load;
267 class->summary_header_save = store_summary_summary_header_save;
268 class->store_info_new = store_summary_store_info_new;
269 class->store_info_load = store_summary_store_info_load;
270 class->store_info_save = store_summary_store_info_save;
271 class->store_info_free = store_summary_store_info_free;
272 class->store_info_set_string = store_summary_store_info_set_string;
276 camel_store_summary_init (CamelStoreSummary *summary)
278 summary->priv = CAMEL_STORE_SUMMARY_GET_PRIVATE (summary);
280 summary->priv->version = CAMEL_STORE_SUMMARY_VERSION;
282 summary->folders = g_ptr_array_new ();
283 summary->folders_path = g_hash_table_new (g_str_hash, g_str_equal);
284 summary->priv->folder_summaries = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
285 summary->priv->scheduled_save_id = 0;
287 g_rec_mutex_init (&summary->priv->summary_lock);
288 g_rec_mutex_init (&summary->priv->io_lock);
292 * camel_store_summary_new:
294 * Create a new #CamelStoreSummary object.
296 * Returns: a new #CamelStoreSummary object
299 camel_store_summary_new (void)
301 return g_object_new (CAMEL_TYPE_STORE_SUMMARY, NULL);
305 * camel_store_summary_set_filename:
306 * @summary: a #CamelStoreSummary
307 * @filename: a filename
309 * Set the filename where the summary will be loaded to/saved from.
312 camel_store_summary_set_filename (CamelStoreSummary *summary,
315 g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
317 g_rec_mutex_lock (&summary->priv->summary_lock);
319 g_free (summary->priv->summary_path);
320 summary->priv->summary_path = g_strdup (name);
322 g_rec_mutex_unlock (&summary->priv->summary_lock);
326 * camel_store_summary_count:
327 * @summary: a #CamelStoreSummary object
329 * Get the number of summary items stored in this summary.
331 * Returns: the number of items gint he summary.
334 camel_store_summary_count (CamelStoreSummary *summary)
336 g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1);
338 return summary->folders->len;
342 * camel_store_summary_array:
343 * @summary: a #CamelStoreSummary object
345 * Obtain a copy of the summary array. This is done atomically,
346 * so cannot contain empty entries.
348 * It must be freed using camel_store_summary_array_free().
350 * Returns: the summary array
353 camel_store_summary_array (CamelStoreSummary *summary)
355 CamelStoreInfo *info;
359 g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
361 g_rec_mutex_lock (&summary->priv->summary_lock);
363 res = g_ptr_array_sized_new (summary->folders->len);
364 for (i = 0; i < summary->folders->len; i++) {
365 info = g_ptr_array_index (summary->folders, i);
366 camel_store_summary_info_ref (summary, info);
367 g_ptr_array_add (res, info);
370 g_rec_mutex_unlock (&summary->priv->summary_lock);
376 * camel_store_summary_array_free:
377 * @summary: a #CamelStoreSummary object
378 * @array: the summary array as gotten from camel_store_summary_array()
380 * Free the folder summary array.
383 camel_store_summary_array_free (CamelStoreSummary *summary,
388 g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
389 g_return_if_fail (array != NULL);
391 for (i = 0; i < array->len; i++)
392 camel_store_summary_info_unref (summary, array->pdata[i]);
394 g_ptr_array_free (array, TRUE);
398 * camel_store_summary_path:
399 * @summary: a #CamelStoreSummary object
400 * @path: path to the item
402 * Retrieve a summary item by path name.
404 * The returned #CamelStoreInfo is referenced for thread-safety and should be
405 * unreferenced with camel_store_summary_info_unref() when finished with it.
407 * Returns: the summary item, or %NULL if the @path name is not
411 camel_store_summary_path (CamelStoreSummary *summary,
414 CamelStoreInfo *info;
416 g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
417 g_return_val_if_fail (path != NULL, NULL);
419 g_rec_mutex_lock (&summary->priv->summary_lock);
421 info = g_hash_table_lookup (summary->folders_path, path);
424 camel_store_summary_info_ref (summary, info);
426 g_rec_mutex_unlock (&summary->priv->summary_lock);
432 * camel_store_summary_load:
433 * @summary: a #CamelStoreSummary object
435 * Load the summary off disk.
437 * Returns: %0 on success or %-1 on fail
440 camel_store_summary_load (CamelStoreSummary *summary)
442 CamelStoreSummaryClass *class;
443 CamelStoreInfo *info;
447 g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1);
448 g_return_val_if_fail (summary->priv->summary_path != NULL, -1);
450 class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
451 g_return_val_if_fail (class->store_info_load != NULL, -1);
453 in = g_fopen (summary->priv->summary_path, "rb");
457 g_rec_mutex_lock (&summary->priv->io_lock);
459 if (class->summary_header_load (summary, in) == -1)
462 /* now read in each message ... */
463 for (i = 0; i < summary->priv->count; i++) {
464 info = class->store_info_load (summary, in);
469 camel_store_summary_add (summary, info);
472 g_rec_mutex_unlock (&summary->priv->io_lock);
474 if (fclose (in) != 0)
477 summary->priv->dirty = FALSE;
483 g_warning ("Cannot load summary file '%s': %s", summary->priv->summary_path, i == 0 ? "Unknown error" : g_strerror (i));
484 g_rec_mutex_unlock (&summary->priv->io_lock);
486 summary->priv->dirty = FALSE;
493 * camel_store_summary_save:
494 * @summary: a #CamelStoreSummary object
496 * Writes the summary to disk. The summary is only written if changes
499 * Returns: %0 on succes or %-1 on fail
502 camel_store_summary_save (CamelStoreSummary *summary)
504 CamelStoreSummaryClass *class;
505 CamelStoreInfo *info;
511 g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1);
512 g_return_val_if_fail (summary->priv->summary_path != NULL, -1);
514 class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
515 g_return_val_if_fail (class->summary_header_save != NULL, -1);
517 io (printf ("** saving summary\n"));
519 if (!summary->priv->dirty) {
520 io (printf ("** summary clean no save\n"));
525 summary->priv->summary_path,
526 O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600);
528 io (printf ("** open error: %s\n", g_strerror (errno)));
532 out = fdopen (fd, "wb");
535 printf ("** fdopen error: %s\n", g_strerror (errno));
541 io (printf ("saving header\n"));
543 g_rec_mutex_lock (&summary->priv->io_lock);
545 if (class->summary_header_save (summary, out) == -1) {
548 g_rec_mutex_unlock (&summary->priv->io_lock);
553 /* now write out each message ... */
555 /* FIXME: Locking? */
557 count = summary->folders->len;
558 for (i = 0; i < count; i++) {
559 info = summary->folders->pdata[i];
560 class->store_info_save (summary, out, info);
563 g_rec_mutex_unlock (&summary->priv->io_lock);
565 if (fflush (out) != 0 || fsync (fileno (out)) == -1) {
572 if (fclose (out) != 0)
575 summary->priv->dirty = FALSE;
581 * camel_store_summary_add:
582 * @summary: a #CamelStoreSummary object
583 * @info: a #CamelStoreInfo
585 * Adds a new @info record to the summary. If @info->uid is %NULL,
586 * then a new uid is automatically re-assigned by calling
587 * camel_store_summary_next_uid_string().
589 * The @info record should have been generated by calling one of the
590 * info_new_*() functions, as it will be free'd based on the summary
591 * class. And MUST NOT be allocated directly using malloc.
594 camel_store_summary_add (CamelStoreSummary *summary,
595 CamelStoreInfo *info)
597 g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
602 if (camel_store_info_path (summary, info) == NULL) {
603 g_warning ("Trying to add a folder info with missing required path name\n");
607 g_rec_mutex_lock (&summary->priv->summary_lock);
609 g_ptr_array_add (summary->folders, info);
610 g_hash_table_insert (summary->folders_path, (gchar *) camel_store_info_path (summary, info), info);
611 summary->priv->dirty = TRUE;
613 g_rec_mutex_unlock (&summary->priv->summary_lock);
617 * camel_store_summary_add_from_path:
618 * @summary: a #CamelStoreSummary object
621 * Build a new info record based on the name, and add it to the summary.
623 * Returns: the newly added record
626 camel_store_summary_add_from_path (CamelStoreSummary *summary,
629 CamelStoreInfo *info;
631 g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
632 g_return_val_if_fail (path != NULL, NULL);
634 g_rec_mutex_lock (&summary->priv->summary_lock);
636 info = g_hash_table_lookup (summary->folders_path, path);
638 g_warning ("Trying to add folder '%s' to summary that already has it", path);
641 CamelStoreSummaryClass *class;
643 class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
644 g_return_val_if_fail (class->store_info_new != NULL, NULL);
646 info = class->store_info_new (summary, path);
648 g_ptr_array_add (summary->folders, info);
649 g_hash_table_insert (summary->folders_path, (gchar *) camel_store_info_path (summary, info), info);
650 summary->priv->dirty = TRUE;
653 g_rec_mutex_unlock (&summary->priv->summary_lock);
659 * camel_store_summary_info_ref:
660 * @summary: a #CamelStoreSummary object
661 * @info: a #CamelStoreInfo
663 * Add an extra reference to @info.
665 * Returns: the @info argument
668 camel_store_summary_info_ref (CamelStoreSummary *summary,
669 CamelStoreInfo *info)
671 g_return_val_if_fail (info != NULL, NULL);
672 g_return_val_if_fail (info->refcount > 0, NULL);
674 g_atomic_int_inc (&info->refcount);
680 * camel_store_summary_info_unref:
681 * @summary: a #CamelStoreSummary object
682 * @info: a #CamelStoreInfo
684 * Unref and potentially free @info, and all associated memory.
687 camel_store_summary_info_unref (CamelStoreSummary *summary,
688 CamelStoreInfo *info)
690 g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
691 g_return_if_fail (info != NULL);
692 g_return_if_fail (info->refcount > 0);
694 if (g_atomic_int_dec_and_test (&info->refcount)) {
695 CamelStoreSummaryClass *class;
697 class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
698 g_return_if_fail (class->store_info_free != NULL);
700 class->store_info_free (summary, info);
705 * camel_store_summary_touch:
706 * @summary: a #CamelStoreSummary object
708 * Mark the summary as changed, so that a save will force it to be
709 * written back to disk.
712 camel_store_summary_touch (CamelStoreSummary *summary)
714 g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
716 g_rec_mutex_lock (&summary->priv->summary_lock);
717 summary->priv->dirty = TRUE;
718 g_rec_mutex_unlock (&summary->priv->summary_lock);
722 * camel_store_summary_remove:
723 * @summary: a #CamelStoreSummary object
724 * @info: a #CamelStoreInfo
726 * Remove a specific @info record from the summary.
729 camel_store_summary_remove (CamelStoreSummary *summary,
730 CamelStoreInfo *info)
732 g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
733 g_return_if_fail (info != NULL);
735 g_rec_mutex_lock (&summary->priv->summary_lock);
736 g_hash_table_remove (summary->folders_path, camel_store_info_path (summary, info));
737 g_ptr_array_remove (summary->folders, info);
738 summary->priv->dirty = TRUE;
739 g_rec_mutex_unlock (&summary->priv->summary_lock);
741 camel_store_summary_info_unref (summary, info);
745 * camel_store_summary_remove_path:
746 * @summary: a #CamelStoreSummary object
749 * Remove a specific info record from the summary, by @path.
752 camel_store_summary_remove_path (CamelStoreSummary *summary,
755 CamelStoreInfo *oldinfo;
758 g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
759 g_return_if_fail (path != NULL);
761 g_rec_mutex_lock (&summary->priv->summary_lock);
762 if (g_hash_table_lookup_extended (summary->folders_path, path, (gpointer) &oldpath, (gpointer) &oldinfo)) {
763 /* make sure it doesn't vanish while we're removing it */
764 camel_store_summary_info_ref (summary, oldinfo);
765 g_rec_mutex_unlock (&summary->priv->summary_lock);
766 camel_store_summary_remove (summary, oldinfo);
767 camel_store_summary_info_unref (summary, oldinfo);
769 g_rec_mutex_unlock (&summary->priv->summary_lock);
774 * camel_store_summary_info_new:
775 * @summary: a #CamelStoreSummary object
777 * Allocate a new #CamelStoreInfo, suitable for adding to this
780 * Returns: the newly allocated #CamelStoreInfo
783 camel_store_summary_info_new (CamelStoreSummary *summary)
785 CamelStoreSummaryClass *class;
786 CamelStoreInfo *info;
788 g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
790 class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
791 g_return_val_if_fail (class->store_info_size > 0, NULL);
793 info = g_slice_alloc0 (class->store_info_size);
800 * camel_store_info_set_string:
801 * @summary: a #CamelStoreSummary object
802 * @info: a #CamelStoreInfo
803 * @type: specific string being set
804 * @value: string value to set
806 * Set a specific string on the @info.
809 camel_store_info_set_string (CamelStoreSummary *summary,
810 CamelStoreInfo *info,
814 CamelStoreSummaryClass *class;
816 g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
817 g_return_if_fail (info != NULL);
819 class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
820 g_return_if_fail (class->store_info_set_string != NULL);
822 g_rec_mutex_lock (&summary->priv->summary_lock);
824 class->store_info_set_string (summary, info, type, value);
826 g_rec_mutex_unlock (&summary->priv->summary_lock);
830 * camel_store_info_path:
831 * @summary: a #CamelStoreSummary
832 * @info: a #CamelStoreInfo
834 * Returns the path string from @info.
836 * Returns: the path string from @info
839 camel_store_info_path (CamelStoreSummary *summary,
840 CamelStoreInfo *info)
842 g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
843 g_return_val_if_fail (info != NULL, NULL);
845 /* XXX Not thread-safe; should return a duplicate. */
850 * camel_store_info_name:
851 * @summary: a #CamelStoreSummary
852 * @info: a #CamelStoreInfo
854 * Returns the last segment of the path string from @info.
856 * Returns: the last segment of the path string from @info
859 camel_store_info_name (CamelStoreSummary *summary,
860 CamelStoreInfo *info)
864 g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
865 g_return_val_if_fail (info != NULL, NULL);
867 cp = strrchr (info->path, '/');
869 /* XXX Not thread-safe; should return a duplicate. */
870 return (cp != NULL) ? cp + 1 : info->path;
874 store_summary_save_timeout (gpointer user_data)
876 CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (user_data);
878 g_return_val_if_fail (summary != NULL, FALSE);
880 g_rec_mutex_lock (&summary->priv->summary_lock);
882 if (summary->priv->scheduled_save_id) {
883 summary->priv->scheduled_save_id = 0;
884 camel_store_summary_save (summary);
887 g_rec_mutex_unlock (&summary->priv->summary_lock);
893 store_summary_schedule_save (CamelStoreSummary *summary)
895 g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
897 if (summary->priv->scheduled_save_id != 0)
898 g_source_remove (summary->priv->scheduled_save_id);
900 summary->priv->scheduled_save_id = g_timeout_add_seconds (
901 5, store_summary_save_timeout, summary);
902 g_source_set_name_by_id (
903 summary->priv->scheduled_save_id,
904 "[camel] store_summary_save_timeout");
908 store_summary_sync_folder_summary_count_cb (CamelFolderSummary *folder_summary,
910 CamelStoreSummary *summary)
916 g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary));
917 g_return_if_fail (param != NULL);
918 g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
920 path = g_hash_table_lookup (summary->priv->folder_summaries, folder_summary);
921 g_return_if_fail (path != NULL);
923 g_rec_mutex_lock (&summary->priv->summary_lock);
924 si = camel_store_summary_path (summary, path);
926 g_rec_mutex_unlock (&summary->priv->summary_lock);
927 g_warning ("%s: Store summary %p doesn't hold path '%s'", G_STRFUNC, summary, path);
931 if (g_strcmp0 (g_param_spec_get_name (param), "saved-count") == 0) {
932 new_count = camel_folder_summary_get_saved_count (folder_summary);
933 if (si->total != new_count) {
934 si->total = new_count;
935 camel_store_summary_touch (summary);
936 store_summary_schedule_save (summary);
938 } else if (g_strcmp0 (g_param_spec_get_name (param), "unread-count") == 0) {
939 new_count = camel_folder_summary_get_unread_count (folder_summary);
940 if (si->unread != new_count) {
941 si->unread = new_count;
942 camel_store_summary_touch (summary);
943 store_summary_schedule_save (summary);
946 g_warn_if_reached ();
949 camel_store_summary_info_unref (summary, si);
951 g_rec_mutex_unlock (&summary->priv->summary_lock);
955 * camel_store_summary_connect_folder_summary:
956 * @summary: a #CamelStoreSummary object
957 * @path: used path for @folder_summary
958 * @folder_summary: a #CamelFolderSummary object
960 * Connects listeners for count changes on @folder_summary to keep
961 * CamelStoreInfo.total and CamelStoreInfo.unread in sync transparently.
962 * The @folder_summary is stored in @summary as @path. Use
963 * camel_store_summary_disconnect_folder_summary() to disconnect from
966 * Returns: Whether successfully connect callbacks for count change
972 camel_store_summary_connect_folder_summary (CamelStoreSummary *summary,
974 CamelFolderSummary *folder_summary)
978 g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), FALSE);
979 g_return_val_if_fail (path != NULL, FALSE);
980 g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary), FALSE);
982 g_rec_mutex_lock (&summary->priv->summary_lock);
984 si = camel_store_summary_path (summary, path);
986 g_rec_mutex_unlock (&summary->priv->summary_lock);
987 g_warning ("%s: Store summary %p doesn't hold path '%s'", G_STRFUNC, summary, path);
991 camel_store_summary_info_unref (summary, si);
993 if (g_hash_table_lookup (summary->priv->folder_summaries, folder_summary)) {
994 g_rec_mutex_unlock (&summary->priv->summary_lock);
995 g_warning ("%s: Store summary %p already listens on folder summary %p", G_STRFUNC, summary, folder_summary);
999 g_hash_table_insert (summary->priv->folder_summaries, folder_summary, g_strdup (path));
1000 g_signal_connect (folder_summary, "notify::saved-count", G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary);
1001 g_signal_connect (folder_summary, "notify::unread-count", G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary);
1003 g_rec_mutex_unlock (&summary->priv->summary_lock);
1009 * camel_store_summary_disconnect_folder_summary:
1010 * @summary: a #CamelStoreSummary object
1011 * @folder_summary: a #CamelFolderSummary object
1013 * Diconnects count change listeners previously connected
1014 * by camel_store_summary_connect_folder_summary().
1016 * Returns: Whether such connection existed and whether was successfully
1022 camel_store_summary_disconnect_folder_summary (CamelStoreSummary *summary,
1023 CamelFolderSummary *folder_summary)
1025 g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), FALSE);
1026 g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary), FALSE);
1028 g_rec_mutex_lock (&summary->priv->summary_lock);
1030 if (!g_hash_table_lookup (summary->priv->folder_summaries, folder_summary)) {
1031 g_rec_mutex_unlock (&summary->priv->summary_lock);
1032 g_warning ("%s: Store summary %p is not connected to folder summary %p", G_STRFUNC, summary, folder_summary);
1036 g_signal_handlers_disconnect_by_func (folder_summary, G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary);
1037 g_hash_table_remove (summary->priv->folder_summaries, folder_summary);
1039 if (summary->priv->scheduled_save_id != 0) {
1040 g_source_remove (summary->priv->scheduled_save_id);
1041 summary->priv->scheduled_save_id = 0;
1044 camel_store_summary_save (summary);
1046 g_rec_mutex_unlock (&summary->priv->summary_lock);