2 * Copyright (C) 2001 Ximian Inc.
4 * Authors: Michael Zucchi <notzed@ximian.com>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of version 2 of the GNU General Public
8 * License as published by the Free Software Foundation.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
25 #include <sys/types.h>
34 #include "camel-store-summary.h"
36 #include "camel-file-utils.h"
38 #include "string-utils.h"
39 #include "e-util/md5-utils.h"
40 #include "e-util/e-memory.h"
42 #include "camel-private.h"
43 #include "camel-url.h"
46 #define io(x) /* io debug */
48 /* possible versions, for versioning changes */
49 #define CAMEL_STORE_SUMMARY_VERSION_0 (1)
52 #define CAMEL_STORE_SUMMARY_VERSION (1)
54 #define _PRIVATE(o) (((CamelStoreSummary *)(o))->priv)
56 static int summary_header_load(CamelStoreSummary *, FILE *);
57 static int summary_header_save(CamelStoreSummary *, FILE *);
59 static CamelStoreInfo * store_info_new(CamelStoreSummary *, const char *);
60 static CamelStoreInfo * store_info_load(CamelStoreSummary *, FILE *);
61 static int store_info_save(CamelStoreSummary *, FILE *, CamelStoreInfo *);
62 static void store_info_free(CamelStoreSummary *, CamelStoreInfo *);
64 static const char *store_info_string(CamelStoreSummary *, const CamelStoreInfo *, int);
65 static void store_info_set_string(CamelStoreSummary *, CamelStoreInfo *, int, const char *);
67 static void camel_store_summary_class_init (CamelStoreSummaryClass *klass);
68 static void camel_store_summary_init (CamelStoreSummary *obj);
69 static void camel_store_summary_finalise (CamelObject *obj);
71 static CamelObjectClass *camel_store_summary_parent;
74 camel_store_summary_class_init (CamelStoreSummaryClass *klass)
76 camel_store_summary_parent = camel_type_get_global_classfuncs (camel_object_get_type ());
78 klass->summary_header_load = summary_header_load;
79 klass->summary_header_save = summary_header_save;
81 klass->store_info_new = store_info_new;
82 klass->store_info_load = store_info_load;
83 klass->store_info_save = store_info_save;
84 klass->store_info_free = store_info_free;
86 klass->store_info_string = store_info_string;
87 klass->store_info_set_string = store_info_set_string;
91 camel_store_summary_init (CamelStoreSummary *s)
93 struct _CamelStoreSummaryPrivate *p;
95 p = _PRIVATE(s) = g_malloc0(sizeof(*p));
97 s->store_info_size = sizeof(CamelStoreInfo);
99 s->store_info_chunks = NULL;
101 s->version = CAMEL_STORE_SUMMARY_VERSION;
106 s->folders = g_ptr_array_new();
107 s->folders_path = g_hash_table_new(g_str_hash, g_str_equal);
109 #ifdef ENABLE_THREADS
110 p->summary_lock = g_mutex_new();
111 p->io_lock = g_mutex_new();
112 p->alloc_lock = g_mutex_new();
113 p->ref_lock = g_mutex_new();
118 camel_store_summary_finalise (CamelObject *obj)
120 struct _CamelStoreSummaryPrivate *p;
121 CamelStoreSummary *s = (CamelStoreSummary *)obj;
125 camel_store_summary_clear(s);
126 g_ptr_array_free(s->folders, TRUE);
127 g_hash_table_destroy(s->folders_path);
129 g_free(s->summary_path);
131 if (s->store_info_chunks)
132 e_memchunk_destroy(s->store_info_chunks);
134 #ifdef ENABLE_THREADS
135 g_mutex_free(p->summary_lock);
136 g_mutex_free(p->io_lock);
137 g_mutex_free(p->alloc_lock);
138 g_mutex_free(p->ref_lock);
145 camel_store_summary_get_type (void)
147 static CamelType type = CAMEL_INVALID_TYPE;
149 if (type == CAMEL_INVALID_TYPE) {
150 type = camel_type_register (camel_object_get_type (), "CamelStoreSummary",
151 sizeof (CamelStoreSummary),
152 sizeof (CamelStoreSummaryClass),
153 (CamelObjectClassInitFunc) camel_store_summary_class_init,
155 (CamelObjectInitFunc) camel_store_summary_init,
156 (CamelObjectFinalizeFunc) camel_store_summary_finalise);
163 * camel_store_summary_new:
165 * Create a new CamelStoreSummary object.
167 * Return value: A new CamelStoreSummary widget.
170 camel_store_summary_new (void)
172 CamelStoreSummary *new = CAMEL_STORE_SUMMARY ( camel_object_new (camel_store_summary_get_type ())); return new;
176 * camel_store_summary_set_filename:
180 * Set the filename where the summary will be loaded to/saved from.
182 void camel_store_summary_set_filename(CamelStoreSummary *s, const char *name)
184 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
186 g_free(s->summary_path);
187 s->summary_path = g_strdup(name);
189 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
192 void camel_store_summary_set_uri_base(CamelStoreSummary *s, CamelURL *base)
194 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
197 camel_url_free(s->uri_base);
198 s->uri_base = camel_url_new_with_base(base, "");
200 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
204 * camel_store_summary_count:
207 * Get the number of summary items stored in this summary.
209 * Return value: The number of items int he summary.
212 camel_store_summary_count(CamelStoreSummary *s)
214 return s->folders->len;
218 * camel_store_summary_index:
222 * Retrieve a summary item by index number.
224 * A referenced to the summary item is returned, which may be
225 * ref'd or free'd as appropriate.
227 * Return value: The summary item, or NULL if the index @i is out
229 * It must be freed using camel_store_summary_info_free().
232 camel_store_summary_index(CamelStoreSummary *s, int i)
234 CamelStoreInfo *info = NULL;
236 CAMEL_STORE_SUMMARY_LOCK(s, ref_lock);
237 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
239 if (i<s->folders->len)
240 info = g_ptr_array_index(s->folders, i);
242 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
247 CAMEL_STORE_SUMMARY_UNLOCK(s, ref_lock);
253 * camel_store_summary_index:
257 * Obtain a copy of the summary array. This is done atomically,
258 * so cannot contain empty entries.
260 * It must be freed using camel_store_summary_array_free().
263 camel_store_summary_array(CamelStoreSummary *s)
265 CamelStoreInfo *info;
266 GPtrArray *res = g_ptr_array_new();
269 CAMEL_STORE_SUMMARY_LOCK(s, ref_lock);
270 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
272 g_ptr_array_set_size(res, s->folders->len);
273 for (i=0;i<s->folders->len;i++) {
274 info = res->pdata[i] = g_ptr_array_index(s->folders, i);
278 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
279 CAMEL_STORE_SUMMARY_UNLOCK(s, ref_lock);
285 * camel_store_summary_array_free:
289 * Free the folder summary array.
292 camel_store_summary_array_free(CamelStoreSummary *s, GPtrArray *array)
296 for (i=0;i<array->len;i++)
297 camel_store_summary_info_free(s, array->pdata[i]);
299 g_ptr_array_free(array, TRUE);
303 * camel_store_summary_path:
307 * Retrieve a summary item by path name.
309 * A referenced to the summary item is returned, which may be
310 * ref'd or free'd as appropriate.
312 * Return value: The summary item, or NULL if the @path name
314 * It must be freed using camel_store_summary_info_free().
317 camel_store_summary_path(CamelStoreSummary *s, const char *path)
319 CamelStoreInfo *info;
321 CAMEL_STORE_SUMMARY_LOCK(s, ref_lock);
322 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
324 info = g_hash_table_lookup(s->folders_path, path);
326 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
331 CAMEL_STORE_SUMMARY_UNLOCK(s, ref_lock);
337 camel_store_summary_load(CamelStoreSummary *s)
343 g_assert(s->summary_path);
345 in = fopen(s->summary_path, "r");
349 CAMEL_STORE_SUMMARY_LOCK(s, io_lock);
350 if ( ((CamelStoreSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->summary_header_load(s, in) == -1)
353 /* now read in each message ... */
354 for (i=0;i<s->count;i++) {
355 mi = ((CamelStoreSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->store_info_load(s, in);
360 camel_store_summary_add(s, mi);
363 CAMEL_STORE_SUMMARY_UNLOCK(s, io_lock);
365 if (fclose (in) != 0)
368 s->flags &= ~CAMEL_STORE_SUMMARY_DIRTY;
374 g_warning ("Cannot load summary file: %s", strerror (ferror (in)));
375 CAMEL_STORE_SUMMARY_UNLOCK(s, io_lock);
377 s->flags |= ~CAMEL_STORE_SUMMARY_DIRTY;
384 * camel_store_summary_save:
387 * Writes the summary to disk. The summary is only written if changes
390 * Return value: Returns -1 on error.
393 camel_store_summary_save(CamelStoreSummary *s)
401 g_assert(s->summary_path);
403 io(printf("** saving summary\n"));
405 if ((s->flags & CAMEL_STORE_SUMMARY_DIRTY) == 0) {
406 io(printf("** summary clean no save\n"));
410 fd = open(s->summary_path, O_RDWR|O_CREAT|O_TRUNC, 0600);
412 io(printf("** open error: %s\n", strerror (errno)));
415 out = fdopen(fd, "w");
418 printf("** fdopen error: %s\n", strerror (errno));
424 io(printf("saving header\n"));
426 CAMEL_STORE_SUMMARY_LOCK(s, io_lock);
428 if ( ((CamelStoreSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->summary_header_save(s, out) == -1) {
431 CAMEL_STORE_SUMMARY_UNLOCK(s, io_lock);
436 /* now write out each message ... */
438 /* FIXME: Locking? */
440 count = s->folders->len;
441 for (i=0;i<count;i++) {
442 mi = s->folders->pdata[i];
443 ((CamelStoreSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->store_info_save(s, out, mi);
446 CAMEL_STORE_SUMMARY_UNLOCK(s, io_lock);
448 if (fflush (out) != 0 || fsync (fileno (out)) == -1) {
455 if (fclose (out) != 0)
458 s->flags &= ~CAMEL_STORE_SUMMARY_DIRTY;
463 * camel_store_summary_header_load:
464 * @s: Summary object.
466 * Only load the header information from the summary,
467 * keep the rest on disk. This should only be done on
468 * a fresh summary object.
470 * Return value: -1 on error.
472 int camel_store_summary_header_load(CamelStoreSummary *s)
477 g_assert(s->summary_path);
479 in = fopen(s->summary_path, "r");
483 CAMEL_STORE_SUMMARY_LOCK(s, io_lock);
484 ret = ((CamelStoreSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->summary_header_load(s, in);
485 CAMEL_STORE_SUMMARY_UNLOCK(s, io_lock);
488 s->flags &= ~CAMEL_STORE_SUMMARY_DIRTY;
493 * camel_store_summary_add:
497 * Adds a new @info record to the summary. If @info->uid is NULL, then a new
498 * uid is automatically re-assigned by calling :next_uid_string().
500 * The @info record should have been generated by calling one of the
501 * info_new_*() functions, as it will be free'd based on the summary
502 * class. And MUST NOT be allocated directly using malloc.
504 void camel_store_summary_add(CamelStoreSummary *s, CamelStoreInfo *info)
509 if (camel_store_info_path(s, info) == NULL) {
510 g_warning("Trying to add a folder info with missing required path name\n");
514 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
516 g_ptr_array_add(s->folders, info);
517 g_hash_table_insert(s->folders_path, (char *)camel_store_info_path(s, info), info);
518 s->flags |= CAMEL_STORE_SUMMARY_DIRTY;
520 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
524 * camel_store_summary_add_from_path:
528 * Build a new info record based on the name, and add it to the summary.
530 * Return value: The newly added record.
532 CamelStoreInfo *camel_store_summary_add_from_path(CamelStoreSummary *s, const char *path)
534 CamelStoreInfo *info;
536 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
538 info = g_hash_table_lookup(s->folders_path, path);
540 g_warning("Trying to add folder '%s' to summary that already has it", path);
543 info = camel_store_summary_info_new_from_path(s, path);
544 g_ptr_array_add(s->folders, info);
545 g_hash_table_insert(s->folders_path, (char *)camel_store_info_path(s, info), info);
546 s->flags |= CAMEL_STORE_SUMMARY_DIRTY;
549 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
555 * camel_store_summary_info_new_from_path:
559 * Create a new info record from a name.
561 * Return value: Guess? This info record MUST be freed using
562 * camel_store_summary_info_free(), camel_store_info_free() will not work.
564 CamelStoreInfo *camel_store_summary_info_new_from_path(CamelStoreSummary *s, const char *f)
566 return ((CamelStoreSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s))) -> store_info_new(s, f);
570 * camel_store_summary_info_free:
574 * Unref and potentially free the message info @mi, and all associated memory.
576 void camel_store_summary_info_free(CamelStoreSummary *s, CamelStoreInfo *mi)
581 CAMEL_STORE_SUMMARY_LOCK(s, ref_lock);
583 g_assert(mi->refcount >= 1);
586 if (mi->refcount > 0) {
587 CAMEL_STORE_SUMMARY_UNLOCK(s, ref_lock);
591 CAMEL_STORE_SUMMARY_UNLOCK(s, ref_lock);
593 ((CamelStoreSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->store_info_free(s, mi);
597 * camel_store_summary_info_ref:
601 * Add an extra reference to @mi.
603 void camel_store_summary_info_ref(CamelStoreSummary *s, CamelStoreInfo *mi)
608 CAMEL_STORE_SUMMARY_LOCK(s, ref_lock);
609 g_assert(mi->refcount >= 1);
611 CAMEL_STORE_SUMMARY_UNLOCK(s, ref_lock);
615 * camel_store_summary_touch:
618 * Mark the summary as changed, so that a save will save it.
621 camel_store_summary_touch(CamelStoreSummary *s)
623 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
624 s->flags |= CAMEL_STORE_SUMMARY_DIRTY;
625 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
629 * camel_store_summary_clear:
632 * Empty the summary contents.
635 camel_store_summary_clear(CamelStoreSummary *s)
639 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
640 if (camel_store_summary_count(s) == 0) {
641 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
645 for (i=0;i<s->folders->len;i++)
646 camel_store_summary_info_free(s, s->folders->pdata[i]);
648 g_ptr_array_set_size(s->folders, 0);
649 g_hash_table_destroy(s->folders_path);
650 s->folders_path = g_hash_table_new(g_str_hash, g_str_equal);
651 s->flags |= CAMEL_STORE_SUMMARY_DIRTY;
652 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
656 * camel_store_summary_remove:
660 * Remove a specific @info record from the summary.
662 void camel_store_summary_remove(CamelStoreSummary *s, CamelStoreInfo *info)
664 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
665 g_hash_table_remove(s->folders_path, camel_store_info_path(s, info));
666 g_ptr_array_remove(s->folders, info);
667 s->flags |= CAMEL_STORE_SUMMARY_DIRTY;
668 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
670 camel_store_summary_info_free(s, info);
674 * camel_store_summary_remove_uid:
678 * Remove a specific info record from the summary, by @path.
680 void camel_store_summary_remove_path(CamelStoreSummary *s, const char *path)
682 CamelStoreInfo *oldinfo;
685 CAMEL_STORE_SUMMARY_LOCK(s, ref_lock);
686 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
687 if (g_hash_table_lookup_extended(s->folders_path, path, (void *)&oldpath, (void *)&oldinfo)) {
688 /* make sure it doesn't vanish while we're removing it */
690 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
691 CAMEL_STORE_SUMMARY_UNLOCK(s, ref_lock);
692 camel_store_summary_remove(s, oldinfo);
693 camel_store_summary_info_free(s, oldinfo);
695 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
696 CAMEL_STORE_SUMMARY_UNLOCK(s, ref_lock);
701 * camel_store_summary_remove_index:
705 * Remove a specific info record from the summary, by index.
707 void camel_store_summary_remove_index(CamelStoreSummary *s, int index)
709 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
710 if (index < s->folders->len) {
711 CamelStoreInfo *info = s->folders->pdata[index];
713 g_hash_table_remove(s->folders_path, camel_store_info_path(s, info));
714 g_ptr_array_remove_index(s->folders, index);
715 s->flags |= CAMEL_STORE_SUMMARY_DIRTY;
717 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
718 camel_store_summary_info_free(s, info);
720 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
725 summary_header_load(CamelStoreSummary *s, FILE *in)
727 gint32 version, flags, count;
730 fseek(in, 0, SEEK_SET);
732 io(printf("Loading header\n"));
734 if (camel_file_util_decode_fixed_int32(in, &version) == -1
735 || camel_file_util_decode_fixed_int32(in, &flags) == -1
736 || camel_file_util_decode_time_t(in, &time) == -1
737 || camel_file_util_decode_fixed_int32(in, &count) == -1) {
744 s->version = version;
746 if (version < CAMEL_STORE_SUMMARY_VERSION_0) {
747 g_warning("Store summary header version too low");
755 summary_header_save(CamelStoreSummary *s, FILE *out)
757 fseek(out, 0, SEEK_SET);
759 io(printf("Savining header\n"));
761 /* always write latest version */
762 camel_file_util_encode_fixed_int32(out, CAMEL_STORE_SUMMARY_VERSION);
763 camel_file_util_encode_fixed_int32(out, s->flags);
764 camel_file_util_encode_time_t(out, s->time);
765 return camel_file_util_encode_fixed_int32(out, camel_store_summary_count(s));
769 * camel_store_summary_info_new:
772 * Allocate a new camel message info, suitable for adding
778 camel_store_summary_info_new(CamelStoreSummary *s)
782 CAMEL_STORE_SUMMARY_LOCK(s, alloc_lock);
783 if (s->store_info_chunks == NULL)
784 s->store_info_chunks = e_memchunk_new(32, s->store_info_size);
785 mi = e_memchunk_alloc0(s->store_info_chunks);
786 CAMEL_STORE_SUMMARY_UNLOCK(s, alloc_lock);
791 const char *camel_store_info_string(CamelStoreSummary *s, const CamelStoreInfo *mi, int type)
793 return ((CamelStoreSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->store_info_string(s, mi, type);
796 void camel_store_info_set_string(CamelStoreSummary *s, CamelStoreInfo *mi, int type, const char *value)
798 ((CamelStoreSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->store_info_set_string(s, mi, type, value);
801 static CamelStoreInfo *
802 store_info_new(CamelStoreSummary *s, const char *f)
806 mi = camel_store_summary_info_new(s);
808 mi->path = g_strdup(f);
809 mi->unread = CAMEL_STORE_INFO_FOLDER_UNKNOWN;
810 mi->total = CAMEL_STORE_INFO_FOLDER_UNKNOWN;
815 static CamelStoreInfo *
816 store_info_load(CamelStoreSummary *s, FILE *in)
820 mi = camel_store_summary_info_new(s);
822 io(printf("Loading folder info\n"));
824 camel_file_util_decode_string(in, &mi->path);
825 camel_file_util_decode_uint32(in, &mi->flags);
826 camel_file_util_decode_uint32(in, &mi->unread);
827 camel_file_util_decode_uint32(in, &mi->total);
832 camel_store_summary_info_free(s, mi);
838 store_info_save(CamelStoreSummary *s, FILE *out, CamelStoreInfo *mi)
840 io(printf("Saving folder info\n"));
842 camel_file_util_encode_string(out, camel_store_info_path(s, mi));
843 camel_file_util_encode_uint32(out, mi->flags);
844 camel_file_util_encode_uint32(out, mi->unread);
845 camel_file_util_encode_uint32(out, mi->total);
851 store_info_free(CamelStoreSummary *s, CamelStoreInfo *mi)
855 e_memchunk_free(s->store_info_chunks, mi);
859 store_info_string(CamelStoreSummary *s, const CamelStoreInfo *mi, int type)
865 g_assert (mi != NULL);
868 case CAMEL_STORE_INFO_PATH:
870 case CAMEL_STORE_INFO_NAME:
871 p = strrchr(mi->path, '/');
876 case CAMEL_STORE_INFO_URI:
877 if (mi->uri == NULL) {
880 uri = camel_url_new_with_base(s->uri_base, mi->path);
881 ((CamelStoreInfo *)mi)->uri = camel_url_to_string(uri, 0);
891 store_info_set_string (CamelStoreSummary *s, CamelStoreInfo *mi, int type, const char *str)
897 g_assert (mi != NULL);
900 case CAMEL_STORE_INFO_PATH:
901 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
902 g_hash_table_remove(s->folders_path, (char *)camel_store_info_path(s, mi));
905 mi->path = g_strdup(str);
906 g_hash_table_insert(s->folders_path, (char *)camel_store_info_path(s, mi), mi);
907 s->flags |= CAMEL_STORE_SUMMARY_DIRTY;
908 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
910 case CAMEL_STORE_INFO_NAME:
911 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
912 g_hash_table_remove(s->folders_path, (char *)camel_store_info_path(s, mi));
913 p = strrchr(mi->path, '/');
916 v = g_malloc(len+strlen(str)+1);
917 memcpy(v, mi->path, len);
924 g_hash_table_insert(s->folders_path, (char *)camel_store_info_path(s, mi), mi);
925 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
927 case CAMEL_STORE_INFO_URI:
928 g_warning("Cannot set store info uri, aborting");