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