1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  * This library is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Michael Zucchi <notzed@ximian.com>
18  */
19 
20 #include "evolution-data-server-config.h"
21 
22 #include <ctype.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 
31 #include <glib/gstdio.h>
32 
33 #include "camel-file-utils.h"
34 #include "camel-folder-summary.h"
35 #include "camel-store-summary.h"
36 #include "camel-url.h"
37 #include "camel-win32.h"
38 
39 #define d(x)
40 #define io(x)			/* io debug */
41 
42 /* possible versions, for versioning changes */
43 #define CAMEL_STORE_SUMMARY_VERSION_0 (1)
44 #define CAMEL_STORE_SUMMARY_VERSION_2 (2)
45 
46 /* current version */
47 #define CAMEL_STORE_SUMMARY_VERSION (2)
48 
49 struct _CamelStoreSummaryPrivate {
50 	GRecMutex summary_lock;	/* for the summary hashtable/array */
51 	GRecMutex io_lock;	/* load/save lock, for access to saved_count, etc */
52 
53 	gboolean dirty;		/* summary has unsaved changes */
54 
55 	gchar *summary_path;
56 
57 	/* header info */
58 	guint32 version;	/* version of base part of file */
59 	guint32 count;		/* how many were saved/loaded */
60 	time_t time;		/* timestamp for this summary (for implementors to use) */
61 
62 	GHashTable *folder_summaries; /* CamelFolderSummary->path; doesn't add reference to CamelFolderSummary */
63 
64 	guint scheduled_save_id;
65 
66 	GPtrArray *folders;	/* CamelStoreInfo's */
67 	GHashTable *folders_path; /* CamelStoreInfo's by path name */
68 };
69 
G_DEFINE_TYPE_WITH_PRIVATE(CamelStoreSummary,camel_store_summary,G_TYPE_OBJECT)70 G_DEFINE_TYPE_WITH_PRIVATE (CamelStoreSummary, camel_store_summary, G_TYPE_OBJECT)
71 
72 static void
73 store_summary_finalize (GObject *object)
74 {
75 	CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (object);
76 	guint ii;
77 
78 	for (ii = 0; ii < summary->priv->folders->len; ii++) {
79 		CamelStoreInfo *info;
80 
81 		info = g_ptr_array_index (summary->priv->folders, ii);
82 		camel_store_summary_info_unref (summary, info);
83 	}
84 
85 	g_ptr_array_free (summary->priv->folders, TRUE);
86 	g_hash_table_destroy (summary->priv->folders_path);
87 	g_hash_table_destroy (summary->priv->folder_summaries);
88 
89 	g_free (summary->priv->summary_path);
90 
91 	g_rec_mutex_clear (&summary->priv->summary_lock);
92 	g_rec_mutex_clear (&summary->priv->io_lock);
93 
94 	/* Chain up to parent's finalize() method. */
95 	G_OBJECT_CLASS (camel_store_summary_parent_class)->finalize (object);
96 }
97 
98 static void
store_summary_dispose(GObject * object)99 store_summary_dispose (GObject *object)
100 {
101 	CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (object);
102 
103 	g_rec_mutex_lock (&summary->priv->summary_lock);
104 
105 	if (summary->priv->scheduled_save_id != 0) {
106 		g_source_remove (summary->priv->scheduled_save_id);
107 		summary->priv->scheduled_save_id = 0;
108 		camel_store_summary_save (summary);
109 	}
110 
111 	g_rec_mutex_unlock (&summary->priv->summary_lock);
112 
113 	G_OBJECT_CLASS (camel_store_summary_parent_class)->dispose (object);
114 }
115 
116 static gint
store_summary_summary_header_load(CamelStoreSummary * summary,FILE * in)117 store_summary_summary_header_load (CamelStoreSummary *summary,
118                                    FILE *in)
119 {
120 	gint32 version, flags, count;
121 	time_t time;
122 
123 	fseek (in, 0, SEEK_SET);
124 
125 	io (printf ("Loading header\n"));
126 
127 	/* XXX The flags value is legacy; not used for anything. */
128 	if (camel_file_util_decode_fixed_int32 (in, &version) == -1
129 	    || camel_file_util_decode_fixed_int32 (in, &flags) == -1
130 	    || camel_file_util_decode_time_t (in, &time) == -1
131 	    || camel_file_util_decode_fixed_int32 (in, &count) == -1) {
132 		return -1;
133 	}
134 
135 	summary->priv->time = time;
136 	summary->priv->count = count;
137 	summary->priv->version = version;
138 
139 	if (version < CAMEL_STORE_SUMMARY_VERSION_0) {
140 		g_warning ("Store summary header version too low");
141 		return -1;
142 	}
143 
144 	return 0;
145 }
146 
147 static gint
store_summary_summary_header_save(CamelStoreSummary * summary,FILE * out)148 store_summary_summary_header_save (CamelStoreSummary *summary,
149                                    FILE *out)
150 {
151 	fseek (out, 0, SEEK_SET);
152 
153 	io (printf ("Savining header\n"));
154 
155 	/* always write latest version */
156 	camel_file_util_encode_fixed_int32 (out, CAMEL_STORE_SUMMARY_VERSION);
157 	camel_file_util_encode_fixed_int32 (out, 0);  /* flags (unused) */
158 	camel_file_util_encode_time_t (out, summary->priv->time);
159 
160 	return camel_file_util_encode_fixed_int32 (
161 		out, camel_store_summary_count (summary));
162 }
163 
164 static CamelStoreInfo *
store_summary_store_info_new(CamelStoreSummary * summary,const gchar * path)165 store_summary_store_info_new (CamelStoreSummary *summary,
166                               const gchar *path)
167 {
168 	CamelStoreInfo *info;
169 
170 	info = camel_store_summary_info_new (summary);
171 
172 	info->path = g_strdup (path);
173 	info->unread = CAMEL_STORE_INFO_FOLDER_UNKNOWN;
174 	info->total = CAMEL_STORE_INFO_FOLDER_UNKNOWN;
175 
176 	return info;
177 }
178 
179 static CamelStoreInfo *
store_summary_store_info_load(CamelStoreSummary * summary,FILE * in)180 store_summary_store_info_load (CamelStoreSummary *summary,
181                                FILE *in)
182 {
183 	CamelStoreInfo *info;
184 
185 	info = camel_store_summary_info_new (summary);
186 
187 	io (printf ("Loading folder info\n"));
188 
189 	if (camel_file_util_decode_string (in, &info->path) == -1 ||
190 	    camel_file_util_decode_uint32 (in, &info->flags) == -1 ||
191 	    camel_file_util_decode_uint32 (in, &info->unread) == -1 ||
192 	    camel_file_util_decode_uint32 (in, &info->total) == -1) {
193 		camel_store_summary_info_unref (summary, info);
194 
195 		return NULL;
196 	}
197 
198 	if (!ferror (in))
199 		return info;
200 
201 	camel_store_summary_info_unref (summary, info);
202 
203 	return NULL;
204 }
205 
206 static gint
store_summary_store_info_save(CamelStoreSummary * summary,FILE * out,CamelStoreInfo * info)207 store_summary_store_info_save (CamelStoreSummary *summary,
208                                FILE *out,
209                                CamelStoreInfo *info)
210 {
211 	io (printf ("Saving folder info\n"));
212 
213 	if (camel_file_util_encode_string (out, camel_store_info_path (summary, info)) == -1 ||
214 	    camel_file_util_encode_uint32 (out, info->flags) == -1 ||
215 	    camel_file_util_encode_uint32 (out, info->unread) == -1 ||
216 	    camel_file_util_encode_uint32 (out, info->total) == -1)
217 		return -1;
218 
219 	return ferror (out);
220 }
221 
222 static void
store_summary_store_info_free(CamelStoreSummary * summary,CamelStoreInfo * info)223 store_summary_store_info_free (CamelStoreSummary *summary,
224                                CamelStoreInfo *info)
225 {
226 	CamelStoreSummaryClass *class;
227 
228 	class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
229 	g_return_if_fail (class != NULL);
230 
231 	g_free (info->path);
232 	g_slice_free1 (class->store_info_size, info);
233 }
234 
235 static void
store_summary_store_info_set_string(CamelStoreSummary * summary,CamelStoreInfo * info,gint type,const gchar * str)236 store_summary_store_info_set_string (CamelStoreSummary *summary,
237                                      CamelStoreInfo *info,
238                                      gint type,
239                                      const gchar *str)
240 {
241 	switch (type) {
242 	case CAMEL_STORE_INFO_PATH:
243 		g_hash_table_remove (summary->priv->folders_path, (gchar *) camel_store_info_path (summary, info));
244 		g_free (info->path);
245 		info->path = g_strdup (str);
246 		g_hash_table_insert (summary->priv->folders_path, (gchar *) camel_store_info_path (summary, info), info);
247 		summary->priv->dirty = TRUE;
248 		break;
249 	}
250 }
251 
252 static void
camel_store_summary_class_init(CamelStoreSummaryClass * class)253 camel_store_summary_class_init (CamelStoreSummaryClass *class)
254 {
255 	GObjectClass *object_class;
256 
257 	object_class = G_OBJECT_CLASS (class);
258 	object_class->dispose = store_summary_dispose;
259 	object_class->finalize = store_summary_finalize;
260 
261 	class->store_info_size = sizeof (CamelStoreInfo);
262 	class->summary_header_load = store_summary_summary_header_load;
263 	class->summary_header_save = store_summary_summary_header_save;
264 	class->store_info_new = store_summary_store_info_new;
265 	class->store_info_load = store_summary_store_info_load;
266 	class->store_info_save = store_summary_store_info_save;
267 	class->store_info_free = store_summary_store_info_free;
268 	class->store_info_set_string = store_summary_store_info_set_string;
269 }
270 
271 static void
camel_store_summary_init(CamelStoreSummary * summary)272 camel_store_summary_init (CamelStoreSummary *summary)
273 {
274 	summary->priv = camel_store_summary_get_instance_private (summary);
275 
276 	summary->priv->version = CAMEL_STORE_SUMMARY_VERSION;
277 
278 	summary->priv->folders = g_ptr_array_new ();
279 	summary->priv->folders_path = g_hash_table_new (g_str_hash, g_str_equal);
280 	summary->priv->folder_summaries = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
281 	summary->priv->scheduled_save_id = 0;
282 
283 	g_rec_mutex_init (&summary->priv->summary_lock);
284 	g_rec_mutex_init (&summary->priv->io_lock);
285 }
286 
287 /**
288  * camel_store_summary_new:
289  *
290  * Create a new #CamelStoreSummary object.
291  *
292  * Returns: a new #CamelStoreSummary object
293  **/
294 CamelStoreSummary *
camel_store_summary_new(void)295 camel_store_summary_new (void)
296 {
297 	return g_object_new (CAMEL_TYPE_STORE_SUMMARY, NULL);
298 }
299 
300 /**
301  * camel_store_summary_set_filename:
302  * @summary: a #CamelStoreSummary
303  * @filename: a filename
304  *
305  * Set the filename where the summary will be loaded to/saved from.
306  **/
307 void
camel_store_summary_set_filename(CamelStoreSummary * summary,const gchar * name)308 camel_store_summary_set_filename (CamelStoreSummary *summary,
309                                   const gchar *name)
310 {
311 	g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
312 
313 	g_rec_mutex_lock (&summary->priv->summary_lock);
314 
315 	g_free (summary->priv->summary_path);
316 	summary->priv->summary_path = g_strdup (name);
317 
318 	g_rec_mutex_unlock (&summary->priv->summary_lock);
319 }
320 
321 /**
322  * camel_store_summary_count:
323  * @summary: a #CamelStoreSummary object
324  *
325  * Get the number of summary items stored in this summary.
326  *
327  * Returns: the number of items gint he summary.
328  **/
329 gint
camel_store_summary_count(CamelStoreSummary * summary)330 camel_store_summary_count (CamelStoreSummary *summary)
331 {
332 	g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1);
333 
334 	return summary->priv->folders->len;
335 }
336 
337 /**
338  * camel_store_summary_array:
339  * @summary: a #CamelStoreSummary object
340  *
341  * Obtain a copy of the summary array.  This is done atomically,
342  * so cannot contain empty entries.
343  *
344  * It must be freed using camel_store_summary_array_free().
345  *
346  * Returns: (element-type CamelStoreInfo) (transfer full): the summary array
347  **/
348 GPtrArray *
camel_store_summary_array(CamelStoreSummary * summary)349 camel_store_summary_array (CamelStoreSummary *summary)
350 {
351 	CamelStoreInfo *info;
352 	GPtrArray *res;
353 	gint i;
354 
355 	g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
356 
357 	g_rec_mutex_lock (&summary->priv->summary_lock);
358 
359 	res = g_ptr_array_sized_new (summary->priv->folders->len);
360 	for (i = 0; i < summary->priv->folders->len; i++) {
361 		info = g_ptr_array_index (summary->priv->folders, i);
362 		camel_store_summary_info_ref (summary, info);
363 		g_ptr_array_add (res, info);
364 	}
365 
366 	g_rec_mutex_unlock (&summary->priv->summary_lock);
367 
368 	return res;
369 }
370 
371 /**
372  * camel_store_summary_array_free:
373  * @summary: a #CamelStoreSummary object
374  * @array: (element-type CamelStoreInfo): the summary array as gotten from camel_store_summary_array()
375  *
376  * Free the folder summary array.
377  **/
378 void
camel_store_summary_array_free(CamelStoreSummary * summary,GPtrArray * array)379 camel_store_summary_array_free (CamelStoreSummary *summary,
380                                 GPtrArray *array)
381 {
382 	gint i;
383 
384 	g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
385 	g_return_if_fail (array != NULL);
386 
387 	for (i = 0; i < array->len; i++)
388 		camel_store_summary_info_unref (summary, array->pdata[i]);
389 
390 	g_ptr_array_free (array, TRUE);
391 }
392 
393 /**
394  * camel_store_summary_path: (skip)
395  * @summary: a #CamelStoreSummary object
396  * @path: path to the item
397  *
398  * Retrieve a summary item by path name.
399  *
400  * The returned #CamelStoreInfo is referenced for thread-safety and should be
401  * unreferenced with camel_store_summary_info_unref() when finished with it.
402  *
403  * Returns: the summary item, or %NULL if the @path name is not
404  * available
405  **/
406 CamelStoreInfo *
camel_store_summary_path(CamelStoreSummary * summary,const gchar * path)407 camel_store_summary_path (CamelStoreSummary *summary,
408                           const gchar *path)
409 {
410 	CamelStoreInfo *info;
411 
412 	g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
413 	g_return_val_if_fail (path != NULL, NULL);
414 
415 	g_rec_mutex_lock (&summary->priv->summary_lock);
416 
417 	info = g_hash_table_lookup (summary->priv->folders_path, path);
418 
419 	if (info != NULL)
420 		camel_store_summary_info_ref (summary, info);
421 
422 	g_rec_mutex_unlock (&summary->priv->summary_lock);
423 
424 	return info;
425 }
426 
427 /**
428  * camel_store_summary_load:
429  * @summary: a #CamelStoreSummary object
430  *
431  * Load the summary off disk.
432  *
433  * Returns: 0 on success or -1 on fail
434  **/
435 gint
camel_store_summary_load(CamelStoreSummary * summary)436 camel_store_summary_load (CamelStoreSummary *summary)
437 {
438 	CamelStoreSummaryClass *class;
439 	CamelStoreInfo *info;
440 	FILE *in;
441 	gint i;
442 
443 	g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1);
444 	g_return_val_if_fail (summary->priv->summary_path != NULL, -1);
445 
446 	class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
447 	g_return_val_if_fail (class != NULL, -1);
448 	g_return_val_if_fail (class->store_info_load != NULL, -1);
449 
450 	in = g_fopen (summary->priv->summary_path, "rb");
451 	if (in == NULL)
452 		return -1;
453 
454 	g_rec_mutex_lock (&summary->priv->io_lock);
455 
456 	if (class->summary_header_load (summary, in) == -1)
457 		goto error;
458 
459 	/* now read in each message ... */
460 	for (i = 0; i < summary->priv->count; i++) {
461 		info = class->store_info_load (summary, in);
462 
463 		if (info == NULL)
464 			goto error;
465 
466 		camel_store_summary_add (summary, info);
467 	}
468 
469 	g_rec_mutex_unlock (&summary->priv->io_lock);
470 
471 	if (fclose (in) != 0)
472 		return -1;
473 
474 	summary->priv->dirty = FALSE;
475 
476 	return 0;
477 
478 error:
479 	i = ferror (in);
480 	g_warning ("Cannot load summary file '%s': %s", summary->priv->summary_path, i == 0 ? "Unknown error" : g_strerror (i));
481 	g_rec_mutex_unlock (&summary->priv->io_lock);
482 	fclose (in);
483 	summary->priv->dirty = FALSE;
484 	errno = i;
485 
486 	return -1;
487 }
488 
489 /**
490  * camel_store_summary_save:
491  * @summary: a #CamelStoreSummary object
492  *
493  * Writes the summary to disk.  The summary is only written if changes
494  * have occurred.
495  *
496  * Returns: 0 on succes or -1 on fail
497  **/
498 gint
camel_store_summary_save(CamelStoreSummary * summary)499 camel_store_summary_save (CamelStoreSummary *summary)
500 {
501 	CamelStoreSummaryClass *class;
502 	CamelStoreInfo *info;
503 	FILE *out;
504 	gint fd;
505 	gint i;
506 	guint32 count;
507 
508 	g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), -1);
509 	g_return_val_if_fail (summary->priv->summary_path != NULL, -1);
510 
511 	class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
512 	g_return_val_if_fail (class != NULL, -1);
513 	g_return_val_if_fail (class->summary_header_save != NULL, -1);
514 
515 	io (printf ("** saving summary\n"));
516 
517 	if (!summary->priv->dirty) {
518 		io (printf ("**  summary clean no save\n"));
519 		return 0;
520 	}
521 
522 	fd = g_open (
523 		summary->priv->summary_path,
524 		O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600);
525 	if (fd == -1) {
526 		io (printf ("**  open error: %s\n", g_strerror (errno)));
527 		return -1;
528 	}
529 
530 	out = fdopen (fd, "wb");
531 	if (out == NULL) {
532 		i = errno;
533 		printf ("**  fdopen error: %s\n", g_strerror (errno));
534 		close (fd);
535 		errno = i;
536 		return -1;
537 	}
538 
539 	io (printf ("saving header\n"));
540 
541 	g_rec_mutex_lock (&summary->priv->io_lock);
542 
543 	if (class->summary_header_save (summary, out) == -1) {
544 		i = errno;
545 		fclose (out);
546 		g_rec_mutex_unlock (&summary->priv->io_lock);
547 		errno = i;
548 		return -1;
549 	}
550 
551 	/* now write out each message ... */
552 
553 	/* FIXME: Locking? */
554 
555 	count = summary->priv->folders->len;
556 	for (i = 0; i < count; i++) {
557 		info = summary->priv->folders->pdata[i];
558 		class->store_info_save (summary, out, info);
559 	}
560 
561 	g_rec_mutex_unlock (&summary->priv->io_lock);
562 
563 	if (fflush (out) != 0 || fsync (fileno (out)) == -1) {
564 		i = errno;
565 		fclose (out);
566 		errno = i;
567 		return -1;
568 	}
569 
570 	if (fclose (out) != 0)
571 		return -1;
572 
573 	summary->priv->dirty = FALSE;
574 
575 	return 0;
576 }
577 
578 /**
579  * camel_store_summary_add:
580  * @summary: a #CamelStoreSummary object
581  * @info: a #CamelStoreInfo
582  *
583  * The @info record should have been generated by calling one of the
584  * info_new_*() functions, as it will be free'd based on the summary
585  * class.  And MUST NOT be allocated directly using malloc.
586  **/
587 void
camel_store_summary_add(CamelStoreSummary * summary,CamelStoreInfo * info)588 camel_store_summary_add (CamelStoreSummary *summary,
589                          CamelStoreInfo *info)
590 {
591 	g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
592 
593 	if (info == NULL)
594 		return;
595 
596 	if (camel_store_info_path (summary, info) == NULL) {
597 		g_warning ("Trying to add a folder info with missing required path name\n");
598 		return;
599 	}
600 
601 	g_rec_mutex_lock (&summary->priv->summary_lock);
602 
603 	g_ptr_array_add (summary->priv->folders, info);
604 	g_hash_table_insert (summary->priv->folders_path, (gchar *) camel_store_info_path (summary, info), info);
605 	summary->priv->dirty = TRUE;
606 
607 	g_rec_mutex_unlock (&summary->priv->summary_lock);
608 }
609 
610 /**
611  * camel_store_summary_add_from_path: (skip)
612  * @summary: a #CamelStoreSummary object
613  * @path: item path
614  *
615  * Build a new info record based on the name, and add it to the summary.
616  *
617  * Returns: (transfer none): the newly added record
618  **/
619 CamelStoreInfo *
camel_store_summary_add_from_path(CamelStoreSummary * summary,const gchar * path)620 camel_store_summary_add_from_path (CamelStoreSummary *summary,
621                                    const gchar *path)
622 {
623 	CamelStoreInfo *info;
624 
625 	g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
626 	g_return_val_if_fail (path != NULL, NULL);
627 
628 	g_rec_mutex_lock (&summary->priv->summary_lock);
629 
630 	info = g_hash_table_lookup (summary->priv->folders_path, path);
631 	if (info != NULL) {
632 		g_warning ("Trying to add folder '%s' to summary that already has it", path);
633 		info = NULL;
634 	} else {
635 		CamelStoreSummaryClass *class;
636 
637 		class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
638 		g_return_val_if_fail (class != NULL, NULL);
639 		g_return_val_if_fail (class->store_info_new != NULL, NULL);
640 
641 		info = class->store_info_new (summary, path);
642 
643 		g_ptr_array_add (summary->priv->folders, info);
644 		g_hash_table_insert (summary->priv->folders_path, (gchar *) camel_store_info_path (summary, info), info);
645 		summary->priv->dirty = TRUE;
646 	}
647 
648 	g_rec_mutex_unlock (&summary->priv->summary_lock);
649 
650 	return info;
651 }
652 
653 /**
654  * camel_store_summary_info_ref: (skip)
655  * @summary: a #CamelStoreSummary object
656  * @info: a #CamelStoreInfo
657  *
658  * Add an extra reference to @info.
659  *
660  * Returns: (transfer full): the @info argument
661  **/
662 CamelStoreInfo *
camel_store_summary_info_ref(CamelStoreSummary * summary,CamelStoreInfo * info)663 camel_store_summary_info_ref (CamelStoreSummary *summary,
664                               CamelStoreInfo *info)
665 {
666 	g_return_val_if_fail (info != NULL, NULL);
667 	g_return_val_if_fail (info->refcount > 0, NULL);
668 
669 	g_atomic_int_inc (&info->refcount);
670 
671 	return info;
672 }
673 
674 /**
675  * camel_store_summary_info_unref:
676  * @summary: a #CamelStoreSummary object
677  * @info: a #CamelStoreInfo
678  *
679  * Unref and potentially free @info, and all associated memory.
680  **/
681 void
camel_store_summary_info_unref(CamelStoreSummary * summary,CamelStoreInfo * info)682 camel_store_summary_info_unref (CamelStoreSummary *summary,
683                                 CamelStoreInfo *info)
684 {
685 	g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
686 	g_return_if_fail (info != NULL);
687 	g_return_if_fail (info->refcount > 0);
688 
689 	if (g_atomic_int_dec_and_test (&info->refcount)) {
690 		CamelStoreSummaryClass *class;
691 
692 		class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
693 		g_return_if_fail (class != NULL);
694 		g_return_if_fail (class->store_info_free != NULL);
695 
696 		class->store_info_free (summary, info);
697 	}
698 }
699 
700 /**
701  * camel_store_summary_touch:
702  * @summary: a #CamelStoreSummary object
703  *
704  * Mark the summary as changed, so that a save will force it to be
705  * written back to disk.
706  **/
707 void
camel_store_summary_touch(CamelStoreSummary * summary)708 camel_store_summary_touch (CamelStoreSummary *summary)
709 {
710 	g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
711 
712 	g_rec_mutex_lock (&summary->priv->summary_lock);
713 	summary->priv->dirty = TRUE;
714 	g_rec_mutex_unlock (&summary->priv->summary_lock);
715 }
716 
717 /**
718  * camel_store_summary_remove:
719  * @summary: a #CamelStoreSummary object
720  * @info: a #CamelStoreInfo
721  *
722  * Remove a specific @info record from the summary.
723  **/
724 void
camel_store_summary_remove(CamelStoreSummary * summary,CamelStoreInfo * info)725 camel_store_summary_remove (CamelStoreSummary *summary,
726                             CamelStoreInfo *info)
727 {
728 	g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
729 	g_return_if_fail (info != NULL);
730 
731 	g_rec_mutex_lock (&summary->priv->summary_lock);
732 	g_hash_table_remove (summary->priv->folders_path, camel_store_info_path (summary, info));
733 	g_ptr_array_remove (summary->priv->folders, info);
734 	summary->priv->dirty = TRUE;
735 	g_rec_mutex_unlock (&summary->priv->summary_lock);
736 
737 	camel_store_summary_info_unref (summary, info);
738 }
739 
740 /**
741  * camel_store_summary_remove_path:
742  * @summary: a #CamelStoreSummary object
743  * @path: item path
744  *
745  * Remove a specific info record from the summary, by @path.
746  **/
747 void
camel_store_summary_remove_path(CamelStoreSummary * summary,const gchar * path)748 camel_store_summary_remove_path (CamelStoreSummary *summary,
749                                  const gchar *path)
750 {
751 	CamelStoreInfo *oldinfo;
752 	gchar *oldpath;
753 
754 	g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
755 	g_return_if_fail (path != NULL);
756 
757 	g_rec_mutex_lock (&summary->priv->summary_lock);
758 	if (g_hash_table_lookup_extended (summary->priv->folders_path, path, (gpointer) &oldpath, (gpointer) &oldinfo)) {
759 		/* make sure it doesn't vanish while we're removing it */
760 		camel_store_summary_info_ref (summary, oldinfo);
761 		g_rec_mutex_unlock (&summary->priv->summary_lock);
762 		camel_store_summary_remove (summary, oldinfo);
763 		camel_store_summary_info_unref (summary, oldinfo);
764 	} else {
765 		g_rec_mutex_unlock (&summary->priv->summary_lock);
766 	}
767 }
768 
769 /**
770  * camel_store_summary_info_new: (skip)
771  * @summary: a #CamelStoreSummary object
772  *
773  * Allocate a new #CamelStoreInfo, suitable for adding to this
774  * summary.
775  *
776  * Returns: the newly allocated #CamelStoreInfo
777  **/
778 CamelStoreInfo *
camel_store_summary_info_new(CamelStoreSummary * summary)779 camel_store_summary_info_new (CamelStoreSummary *summary)
780 {
781 	CamelStoreSummaryClass *class;
782 	CamelStoreInfo *info;
783 
784 	g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
785 
786 	class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
787 	g_return_val_if_fail (class != NULL, NULL);
788 	g_return_val_if_fail (class->store_info_size > 0, NULL);
789 
790 	info = g_slice_alloc0 (class->store_info_size);
791 	info->refcount = 1;
792 
793 	return info;
794 }
795 
796 /**
797  * camel_store_info_set_string:
798  * @summary: a #CamelStoreSummary object
799  * @info: a #CamelStoreInfo
800  * @type: specific string being set
801  * @value: string value to set
802  *
803  * Set a specific string on the @info.
804  **/
805 void
camel_store_info_set_string(CamelStoreSummary * summary,CamelStoreInfo * info,gint type,const gchar * value)806 camel_store_info_set_string (CamelStoreSummary *summary,
807                              CamelStoreInfo *info,
808                              gint type,
809                              const gchar *value)
810 {
811 	CamelStoreSummaryClass *class;
812 
813 	g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
814 	g_return_if_fail (info != NULL);
815 
816 	class = CAMEL_STORE_SUMMARY_GET_CLASS (summary);
817 	g_return_if_fail (class != NULL);
818 	g_return_if_fail (class->store_info_set_string != NULL);
819 
820 	g_rec_mutex_lock (&summary->priv->summary_lock);
821 
822 	class->store_info_set_string (summary, info, type, value);
823 
824 	g_rec_mutex_unlock (&summary->priv->summary_lock);
825 }
826 
827 /**
828  * camel_store_info_path:
829  * @summary: a #CamelStoreSummary
830  * @info: a #CamelStoreInfo
831  *
832  * Returns the path string from @info.
833  *
834  * Returns: the path string from @info
835  **/
836 const gchar *
camel_store_info_path(CamelStoreSummary * summary,CamelStoreInfo * info)837 camel_store_info_path (CamelStoreSummary *summary,
838                        CamelStoreInfo *info)
839 {
840 	g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
841 	g_return_val_if_fail (info != NULL, NULL);
842 
843 	/* XXX Not thread-safe; should return a duplicate. */
844 	return info->path;
845 }
846 
847 /**
848  * camel_store_info_name:
849  * @summary: a #CamelStoreSummary
850  * @info: a #CamelStoreInfo
851  *
852  * Returns the last segment of the path string from @info.
853  *
854  * Returns: the last segment of the path string from @info
855  **/
856 const gchar *
camel_store_info_name(CamelStoreSummary * summary,CamelStoreInfo * info)857 camel_store_info_name (CamelStoreSummary *summary,
858                        CamelStoreInfo *info)
859 {
860 	const gchar *cp;
861 
862 	g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), NULL);
863 	g_return_val_if_fail (info != NULL, NULL);
864 
865 	cp = strrchr (info->path, '/');
866 
867 	/* XXX Not thread-safe; should return a duplicate. */
868 	return (cp != NULL) ? cp + 1 : info->path;
869 }
870 
871 /**
872  * camel_store_summary_sort:
873  * @summary: a #CamelStoreSummary
874  * @compare_func: (scope call) (closure user_data): a compare function
875  * @user_data: user data passed to the @compare_func
876  *
877  * Sorts the array of the folders using the @compare_func.
878  *
879  * Since: 3.24
880  **/
881 void
camel_store_summary_sort(CamelStoreSummary * summary,GCompareDataFunc compare_func,gpointer user_data)882 camel_store_summary_sort (CamelStoreSummary *summary,
883 			  GCompareDataFunc compare_func,
884 			  gpointer user_data)
885 {
886 	g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
887 	g_return_if_fail (compare_func != NULL);
888 
889 	g_ptr_array_sort_with_data (summary->priv->folders, compare_func, user_data);
890 }
891 
892 static gboolean
store_summary_save_timeout(gpointer user_data)893 store_summary_save_timeout (gpointer user_data)
894 {
895 	CamelStoreSummary *summary = CAMEL_STORE_SUMMARY (user_data);
896 
897 	g_return_val_if_fail (summary != NULL, FALSE);
898 
899 	g_rec_mutex_lock (&summary->priv->summary_lock);
900 
901 	if (summary->priv->scheduled_save_id) {
902 		summary->priv->scheduled_save_id = 0;
903 		camel_store_summary_save (summary);
904 	}
905 
906 	g_rec_mutex_unlock (&summary->priv->summary_lock);
907 
908 	return FALSE;
909 }
910 
911 static void
store_summary_schedule_save(CamelStoreSummary * summary)912 store_summary_schedule_save (CamelStoreSummary *summary)
913 {
914 	g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
915 
916 	if (summary->priv->scheduled_save_id != 0)
917 		g_source_remove (summary->priv->scheduled_save_id);
918 
919 	summary->priv->scheduled_save_id = g_timeout_add_seconds (
920 		5, store_summary_save_timeout, summary);
921 	g_source_set_name_by_id (
922 		summary->priv->scheduled_save_id,
923 		"[camel] store_summary_save_timeout");
924 }
925 
926 static void
store_summary_sync_folder_summary_count_cb(CamelFolderSummary * folder_summary,GParamSpec * param,CamelStoreSummary * summary)927 store_summary_sync_folder_summary_count_cb (CamelFolderSummary *folder_summary,
928                                             GParamSpec *param,
929                                             CamelStoreSummary *summary)
930 {
931 	gint new_count;
932 	const gchar *path;
933 	CamelStoreInfo *si;
934 
935 	g_return_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary));
936 	g_return_if_fail (param != NULL);
937 	g_return_if_fail (CAMEL_IS_STORE_SUMMARY (summary));
938 
939 	path = g_hash_table_lookup (summary->priv->folder_summaries, folder_summary);
940 	g_return_if_fail (path != NULL);
941 
942 	g_rec_mutex_lock (&summary->priv->summary_lock);
943 	si = camel_store_summary_path (summary, path);
944 	if (!si) {
945 		g_rec_mutex_unlock (&summary->priv->summary_lock);
946 		g_warning ("%s: Store summary %p doesn't hold path '%s'", G_STRFUNC, summary, path);
947 		return;
948 	}
949 
950 	if (g_strcmp0 (g_param_spec_get_name (param), "saved-count") == 0) {
951 		new_count = camel_folder_summary_get_saved_count (folder_summary);
952 		if (si->total != new_count) {
953 			si->total = new_count;
954 			camel_store_summary_touch (summary);
955 			store_summary_schedule_save (summary);
956 		}
957 	} else if (g_strcmp0 (g_param_spec_get_name (param), "unread-count") == 0) {
958 		new_count = camel_folder_summary_get_unread_count (folder_summary);
959 		if (si->unread != new_count) {
960 			si->unread = new_count;
961 			camel_store_summary_touch (summary);
962 			store_summary_schedule_save (summary);
963 		}
964 	} else {
965 		g_warn_if_reached ();
966 	}
967 
968 	camel_store_summary_info_unref (summary, si);
969 
970 	g_rec_mutex_unlock (&summary->priv->summary_lock);
971 }
972 
973 /**
974  * camel_store_summary_connect_folder_summary:
975  * @summary: a #CamelStoreSummary object
976  * @path: used path for @folder_summary
977  * @folder_summary: a #CamelFolderSummary object
978  *
979  * Connects listeners for count changes on @folder_summary to keep
980  * CamelStoreInfo.total and CamelStoreInfo.unread in sync transparently.
981  * The @folder_summary is stored in @summary as @path. Use
982  * camel_store_summary_disconnect_folder_summary() to disconnect from
983  * listening.
984  *
985  * Returns: Whether successfully connect callbacks for count change
986  * notifications.
987  *
988  * Since: 3.4
989  **/
990 gboolean
camel_store_summary_connect_folder_summary(CamelStoreSummary * summary,const gchar * path,CamelFolderSummary * folder_summary)991 camel_store_summary_connect_folder_summary (CamelStoreSummary *summary,
992                                             const gchar *path,
993                                             CamelFolderSummary *folder_summary)
994 {
995 	CamelStoreInfo *si;
996 
997 	g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), FALSE);
998 	g_return_val_if_fail (path != NULL, FALSE);
999 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary), FALSE);
1000 
1001 	g_rec_mutex_lock (&summary->priv->summary_lock);
1002 
1003 	si = camel_store_summary_path (summary, path);
1004 	if (!si) {
1005 		g_rec_mutex_unlock (&summary->priv->summary_lock);
1006 		g_warning ("%s: Store summary %p doesn't hold path '%s'", G_STRFUNC, summary, path);
1007 		return FALSE;
1008 	}
1009 
1010 	camel_store_summary_info_unref (summary, si);
1011 
1012 	if (g_hash_table_lookup (summary->priv->folder_summaries, folder_summary)) {
1013 		g_rec_mutex_unlock (&summary->priv->summary_lock);
1014 		g_warning ("%s: Store summary %p already listens on folder summary %p", G_STRFUNC, summary, folder_summary);
1015 		return FALSE;
1016 	}
1017 
1018 	g_hash_table_insert (summary->priv->folder_summaries, folder_summary, g_strdup (path));
1019 	g_signal_connect (folder_summary, "notify::saved-count", G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary);
1020 	g_signal_connect (folder_summary, "notify::unread-count", G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary);
1021 
1022 	g_rec_mutex_unlock (&summary->priv->summary_lock);
1023 
1024 	return TRUE;
1025 }
1026 
1027 /**
1028  * camel_store_summary_disconnect_folder_summary:
1029  * @summary: a #CamelStoreSummary object
1030  * @folder_summary: a #CamelFolderSummary object
1031  *
1032  * Diconnects count change listeners previously connected
1033  * by camel_store_summary_connect_folder_summary().
1034  *
1035  * Returns: Whether such connection existed and whether was successfully
1036  * removed.
1037  *
1038  * Since: 3.4
1039  **/
1040 gboolean
camel_store_summary_disconnect_folder_summary(CamelStoreSummary * summary,CamelFolderSummary * folder_summary)1041 camel_store_summary_disconnect_folder_summary (CamelStoreSummary *summary,
1042                                                CamelFolderSummary *folder_summary)
1043 {
1044 	g_return_val_if_fail (CAMEL_IS_STORE_SUMMARY (summary), FALSE);
1045 	g_return_val_if_fail (CAMEL_IS_FOLDER_SUMMARY (folder_summary), FALSE);
1046 
1047 	g_rec_mutex_lock (&summary->priv->summary_lock);
1048 
1049 	if (!g_hash_table_lookup (summary->priv->folder_summaries, folder_summary)) {
1050 		g_rec_mutex_unlock (&summary->priv->summary_lock);
1051 		g_warning ("%s: Store summary %p is not connected to folder summary %p", G_STRFUNC, summary, folder_summary);
1052 		return FALSE;
1053 	}
1054 
1055 	g_signal_handlers_disconnect_by_func (folder_summary, G_CALLBACK (store_summary_sync_folder_summary_count_cb), summary);
1056 	g_hash_table_remove (summary->priv->folder_summaries, folder_summary);
1057 
1058 	if (summary->priv->scheduled_save_id != 0) {
1059 		g_source_remove (summary->priv->scheduled_save_id);
1060 		summary->priv->scheduled_save_id = 0;
1061 	}
1062 
1063 	camel_store_summary_save (summary);
1064 
1065 	g_rec_mutex_unlock (&summary->priv->summary_lock);
1066 
1067 	return TRUE;
1068 }
1069