From 7dea6c9dc22d7be2f2069bb2372d2df9df2abdd5 Mon Sep 17 00:00:00 2001
From: Not Zed <NotZed@Ximian.com>
Date: Wed, 27 Aug 2003 19:50:25 +0000
Subject: implement PERSISTENT_PROPERTIES, for index mode.

2003-08-27  Not Zed  <NotZed@Ximian.com>

        * providers/local/camel-local-folder.c (local_getv): implement
        PERSISTENT_PROPERTIES, for index mode.

        * camel-object.c (cobject_state_read): Also add property reading,
        and bump version to 1.
        (cobject_state_write): add persistent property writing.

2003-08-26  Not Zed  <NotZed@Ximian.com>

        * camel-folder.c (folder_getv): chain up properly.

        * camel-file-utils.c (camel_file_util_savename): helper to create
        a .#filename filename.

        * providers/local/camel-local-folder.c
        (camel_local_folder_construct): init meta-data for local folders.
        (local_getv): chain up properly, if args are not processed, rather
        than don't if they aren't.

2003-08-23  Not Zed  <NotZed@Ximian.com>

        * camel-object.c (cobject_class_init): added a new event,
        meta_changed.
        (camel_object_meta_set, camel_object_meta_get): meta-data api.
        (camel_object_free_hooks): Free meta-data if it is set on the
        object.

        * providers/local/camel-local-folder.c
        (camel_local_folder_get_type): setup a property list for local
        folders, just 'index_body' at present.

svn path=/trunk/; revision=22388
---
 camel/ChangeLog                            |  33 ++
 camel/camel-arg.h                          |   9 +
 camel/camel-file-utils.c                   |  30 ++
 camel/camel-file-utils.h                   |   2 +
 camel/camel-folder.c                       |  16 +-
 camel/camel-folder.h                       |   5 +
 camel/camel-object.c                       | 545 ++++++++++++++++++++++++++++-
 camel/camel-object.h                       |  40 ++-
 camel/providers/local/camel-local-folder.c |  84 ++++-
 camel/providers/local/camel-local-folder.h |  10 +
 10 files changed, 752 insertions(+), 22 deletions(-)

diff --git a/camel/ChangeLog b/camel/ChangeLog
index 717c3938b9..5e081058d0 100644
--- a/camel/ChangeLog
+++ b/camel/ChangeLog
@@ -1,3 +1,36 @@
+2003-08-27  Not Zed  <NotZed@Ximian.com>
+
+	* providers/local/camel-local-folder.c (local_getv): implement
+	PERSISTENT_PROPERTIES, for index mode.
+
+	* camel-object.c (cobject_state_read): Also add property reading,
+	and bump version to 1.
+	(cobject_state_write): add persistent property writing.
+
+2003-08-26  Not Zed  <NotZed@Ximian.com>
+
+	* camel-folder.c (folder_getv): chain up properly.
+
+	* camel-file-utils.c (camel_file_util_savename): helper to create
+	a .#filename filename.
+
+	* providers/local/camel-local-folder.c
+	(camel_local_folder_construct): init meta-data for local folders.
+	(local_getv): chain up properly, if args are not processed, rather
+	than don't if they aren't.
+
+2003-08-23  Not Zed  <NotZed@Ximian.com>
+
+	* camel-object.c (cobject_class_init): added a new event,
+	meta_changed.
+	(camel_object_meta_set, camel_object_meta_get): meta-data api.
+	(camel_object_free_hooks): Free meta-data if it is set on the
+	object.
+
+	* providers/local/camel-local-folder.c
+	(camel_local_folder_get_type): setup a property list for local
+	folders, just 'index_body' at present.
+
 2003-08-25  Jeffrey Stedfast  <fejj@ximian.com>
 
 	* camel-filter-driver.c (pipe_to_system): Added some more error
diff --git a/camel/camel-arg.h b/camel/camel-arg.h
index 4c13b25559..1213596457 100644
--- a/camel/camel-arg.h
+++ b/camel/camel-arg.h
@@ -102,6 +102,15 @@ int camel_arggetv_build(CamelArgGetV *tv);
 /* set an arg ignored */
 #define camel_argv_ignore(tv, i) ((tv)->argv[i].tag = ((tv)->argv[i].tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE)
 
+/* 'self-describing' property list */
+typedef struct _CamelProperty CamelProperty;
+
+struct _CamelProperty {
+	guint32 tag;
+	char *name;
+	char *description;
+};
+
 #ifdef __cplusplus
 }
 #endif				/* __cplusplus */
diff --git a/camel/camel-file-utils.c b/camel/camel-file-utils.c
index c6e2a0fea2..540fef2739 100644
--- a/camel/camel-file-utils.c
+++ b/camel/camel-file-utils.c
@@ -514,3 +514,33 @@ camel_write (int fd, const char *buf, size_t n)
 	
 	return written;
 }
+
+/**
+ * camel_file_util_savename:
+ * @filename: 
+ * 
+ * Builds a filename of the form ".#" + @filename, used to create
+ * a two-stage commit file write.
+ * 
+ * Return value: ".#" + filename.  It must be free'd with g_free().
+ **/
+char *
+camel_file_util_savename(const char *filename)
+{
+	char *name, *slash;
+	int off;
+
+	name = g_malloc(strlen(filename)+3);
+	slash = strrchr(filename, '/');
+	if (slash) {
+		off = slash-filename;
+
+		memcpy(name, filename, off+1);
+		memcpy(name + off+1, ".#", 2);
+		strcpy(name + off+3, filename+off+1);
+	} else {
+		sprintf(name, ".#%s", filename);
+	}
+
+	return name;
+}
diff --git a/camel/camel-file-utils.h b/camel/camel-file-utils.h
index f1c3079b67..15022d99b3 100644
--- a/camel/camel-file-utils.h
+++ b/camel/camel-file-utils.h
@@ -54,6 +54,8 @@ char *camel_file_util_safe_filename (const char *name);
 ssize_t camel_read (int fd, char *buf, size_t n);
 ssize_t camel_write (int fd, const char *buf, size_t n);
 
+char *camel_file_util_savename(const char *filename);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/camel/camel-folder.c b/camel/camel-folder.c
index 9a20e81ffd..00106ca86a 100644
--- a/camel/camel-folder.c
+++ b/camel/camel-folder.c
@@ -245,7 +245,7 @@ camel_folder_construct (CamelFolder *folder, CamelStore *parent_store,
 
 	folder->parent_store = parent_store;
 	if (parent_store)
-		camel_object_ref (CAMEL_OBJECT (parent_store));
+		camel_object_ref(parent_store);
 
 	folder->name = g_strdup (name);
 	folder->full_name = g_strdup (full_name);
@@ -311,7 +311,7 @@ static int
 folder_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args)
 {
 	CamelFolder *folder = (CamelFolder *)object;
-	int i, count=args->argc;
+	int i;
 	guint32 tag;
 
 	for (i=0;i<args->argc;i++) {
@@ -377,18 +377,17 @@ folder_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args)
 		case CAMEL_FOLDER_ARG_INFO_ARRAY:
 			*arg->ca_ptr = camel_folder_summary_array(folder->summary);
 			break;
+		case CAMEL_FOLDER_ARG_PROPERTIES:
+			*arg->ca_ptr = NULL;
+			break;
 		default:
-			count--;
 			continue;
 		}
 
 		arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
 	}
 
-	if (count)
-		return parent_class->getv(object, ex, args);
-
-	return 0;
+	return parent_class->getv(object, ex, args);
 }
 
 static void
@@ -408,6 +407,9 @@ folder_free(CamelObject *o, guint32 tag, void *val)
 	case CAMEL_FOLDER_ARG_INFO_ARRAY:
 		camel_folder_summary_array_free(folder->summary, val);
 		break;
+	case CAMEL_FOLDER_ARG_PROPERTIES:
+		g_slist_free(val);
+		break;
 	default:
 		parent_class->free(o, tag, val);
 	}
diff --git a/camel/camel-folder.h b/camel/camel-folder.h
index a1bb7baf66..f1fe9bfb63 100644
--- a/camel/camel-folder.h
+++ b/camel/camel-folder.h
@@ -52,6 +52,8 @@ enum {
 	CAMEL_FOLDER_ARG_UNREAD,
 	CAMEL_FOLDER_ARG_UID_ARRAY,
 	CAMEL_FOLDER_ARG_INFO_ARRAY,
+	CAMEL_FOLDER_ARG_PROPERTIES,
+	CAMEL_FOLDER_ARG_LAST = CAMEL_ARG_FIRST + 0x2000,
 };
 
 enum {
@@ -64,6 +66,9 @@ enum {
 	/* should we only get static data?  not stuff that needs to be free'd? */
 	CAMEL_FOLDER_UID_ARRAY = CAMEL_FOLDER_ARG_UID_ARRAY | CAMEL_ARG_PTR,
 	CAMEL_FOLDER_INFO_ARRAY = CAMEL_FOLDER_ARG_INFO_ARRAY | CAMEL_ARG_PTR,
+
+	/* GSList of settable folder properties */
+	CAMEL_FOLDER_PROPERTIES = CAMEL_FOLDER_ARG_PROPERTIES | CAMEL_ARG_PTR,
 };
 
 struct _CamelFolderChangeInfo {
diff --git a/camel/camel-object.c b/camel/camel-object.c
index e5dbbb463f..02fd3861bf 100644
--- a/camel/camel-object.c
+++ b/camel/camel-object.c
@@ -30,6 +30,7 @@
 #include <semaphore.h>
 
 #include "camel-object.h"
+#include "camel-file-utils.h"
 
 #include <e-util/e-memory.h>
 #include <e-util/e-msgport.h>
@@ -74,6 +75,7 @@ typedef struct _CamelHookPair
 	union {
 		CamelObjectEventHookFunc event;
 		CamelObjectEventPrepFunc prep;
+		char *filename;
 	} func;
 	void *data;
 } CamelHookPair;
@@ -90,6 +92,14 @@ struct _CamelObjectBag {
 /* used to tag a bag hookpair */
 static const char *bag_name = "object:bag";
 
+/* meta-data stuff */
+static void co_metadata_free(CamelObject *obj, CamelObjectMeta *meta);
+static CamelObjectMeta *co_metadata_get(CamelObject *obj);
+static CamelHookPair *co_metadata_pair(CamelObject *obj, int create);
+
+static const char *meta_name = "object:meta";
+#define CAMEL_OBJECT_STATE_FILE_MAGIC "CLMD"
+
 /* ********************************************************************** */
 
 static CamelHookList *camel_object_get_hooks(CamelObject *o);
@@ -226,6 +236,19 @@ cobject_getv(CamelObject *o, CamelException *ex, CamelArgGetV *args)
 		case CAMEL_OBJECT_ARG_DESCRIPTION:
 			*arg->ca_str = (char *)o->klass->name;
 			break;
+		case CAMEL_OBJECT_ARG_METADATA:
+			*arg->ca_ptr = co_metadata_get(o);
+			break;
+		case CAMEL_OBJECT_ARG_STATE_FILE: {
+			CamelHookPair *pair = co_metadata_pair(o, FALSE);
+
+			printf("getting state file\n");
+			if (pair) {
+				printf(" -> '%s'\n", pair->func.filename);
+				*arg->ca_str = g_strdup(pair->func.filename);
+				camel_object_unget_hooks(o);
+			}
+			break; }
 		}
 	}
 
@@ -236,6 +259,29 @@ cobject_getv(CamelObject *o, CamelException *ex, CamelArgGetV *args)
 static int
 cobject_setv(CamelObject *o, CamelException *ex, CamelArgV *args)
 {
+	int i;
+	guint32 tag;
+
+	for (i=0;i<args->argc;i++) {
+		CamelArg *arg = &args->argv[i];
+
+		tag = arg->tag;
+
+		switch (tag & CAMEL_ARG_TAG) {
+		case CAMEL_OBJECT_ARG_STATE_FILE: {
+			CamelHookPair *pair;
+
+			printf("setting state file to '%s'\n", arg->ca_str);
+
+			/* We store the filename on the meta-data hook-pair */
+			pair = co_metadata_pair(o, TRUE);
+			g_free(pair->func.filename);
+			pair->func.filename = g_strdup(arg->ca_str);
+			camel_object_unget_hooks(o);
+			break; }
+		}
+	}
+
 	/* could have flags or stuff here? */
 	return 0;
 }
@@ -243,9 +289,287 @@ cobject_setv(CamelObject *o, CamelException *ex, CamelArgV *args)
 static void
 cobject_free(CamelObject *o, guint32 tag, void *value)
 {
-	/* do nothing */
+	switch(tag & CAMEL_ARG_TAG) {
+	case CAMEL_OBJECT_ARG_METADATA:
+		co_metadata_free(o, value);
+		break;
+	case CAMEL_OBJECT_ARG_STATE_FILE:
+		g_free(value);
+		break;
+	case CAMEL_OBJECT_ARG_PERSISTENT_PROPERTIES:
+		g_slist_free((GSList *)value);
+		break;
+	}
+}
+
+static char *
+cobject_meta_get(CamelObject *obj, const char * name)
+{
+	CamelHookPair *pair;
+	CamelObjectMeta *meta;
+	char *res = NULL;
+
+	g_return_val_if_fail(CAMEL_IS_OBJECT (obj), 0);
+	g_return_val_if_fail(name != NULL, 0);
+
+	pair = co_metadata_pair(obj, FALSE);
+	if (pair) {
+		meta = pair->data;
+		while (meta) {
+			if (!strcmp(meta->name, name)) {
+				res = g_strdup(meta->value);
+				break;
+			}
+			meta = meta->next;
+		}
+		camel_object_unget_hooks(obj);
+	}
+	
+	return res;
+}
+
+static gboolean
+cobject_meta_set(CamelObject *obj, const char * name, const char *value)
+{
+	CamelHookPair *pair;
+	int changed = FALSE;
+	CamelObjectMeta *meta, *metap;
+
+	g_return_val_if_fail(CAMEL_IS_OBJECT (obj), FALSE);
+	g_return_val_if_fail(name != NULL, FALSE);
+
+	if (obj->hooks == NULL && value == NULL)
+		return FALSE;
+
+	pair = co_metadata_pair(obj, TRUE);
+	meta = pair->data;
+	metap = (CamelObjectMeta *)&pair->data;
+	while (meta) {
+		if (!strcmp(meta->name, name))
+			break;
+		metap = meta;
+		meta = meta->next;
+	}
+
+	/* TODO: The camelobjectmeta structure is identical to
+	   CamelTag, they could be merged or share common code */
+	if (meta == NULL) {
+		if (value == NULL)
+			goto done;
+		meta = g_malloc(sizeof(*meta) + strlen(name));
+		meta->next = pair->data;
+		pair->data = meta;
+		strcpy(meta->name, name);
+		meta->value = g_strdup(value);
+		changed = TRUE;
+	} else if (value == NULL) {
+		metap->next = meta->next;
+		g_free(meta->value);
+		g_free(meta);
+		changed = TRUE;
+	} else if (strcmp(meta->value, value) != 0) {
+		g_free(meta->value);
+		meta->value = g_strdup(value);
+		changed = TRUE;
+	}
+
+done:
+	camel_object_unget_hooks(obj);
+
+	return changed;
+}
+
+/* State file for CamelObject data.  Any later versions should only append data.
+
+   version:uint32
+
+   Version 0 of the file:
+
+   version:uint32 = 0
+   count:uint32  				-- count of meta-data items
+   ( name:string value:string ) *count		-- meta-data items
+
+   Version 1 of the file adds:
+   count:uint32					-- count of persistent properties
+   ( tag:uing32 value:tagtype ) *count		-- persistent properties
+
+*/
+
+static int
+cobject_state_read(CamelObject *obj, FILE *fp)
+{
+	guint32 i, count, version;
+
+	/* NB: for later versions, just check the version is 1 .. known version */
+	if (camel_file_util_decode_uint32(fp, &version) == -1
+	    || version > 1
+	    || camel_file_util_decode_uint32(fp, &count) == -1)
+		return -1;
+
+	printf("loading persistent meta-data\n");
+
+	for (i=0;i<count;i++) {
+		char *name = NULL, *value = NULL;
+			
+		if (camel_file_util_decode_string(fp, &name) == 0
+		    && camel_file_util_decode_string(fp, &value) == 0) {
+			camel_object_meta_set(obj, name, value);
+			g_free(name);
+			g_free(value);
+		} else {
+			g_free(name);
+			g_free(value);
+
+			return -1;
+		}
+	}
+
+	if (version > 0) {
+		CamelArgV *argv;
+
+		printf("loading persistent properties\n");
+
+		if (camel_file_util_decode_uint32(fp, &count) == -1
+			|| count == 0) {
+			/* maybe it was just version 0 afterall */
+			return 0;
+		}
+
+		/* we batch up the properties and set them in one go */
+		argv = g_malloc(sizeof(*argv) + (count - CAMEL_ARGV_MAX) * sizeof(argv->argv[0]));
+		argv->argc = 0;
+		for (i=0;i<count;i++) {
+			if (camel_file_util_decode_uint32(fp, &argv->argv[argv->argc].tag) == -1)
+				goto cleanup;
+
+			/* so far,only do strings and ints, doubles could be added,
+			   object's would require a serialisation interface */
+
+			switch(argv->argv[argv->argc].tag & CAMEL_ARG_TYPE) {
+			case CAMEL_ARG_INT:
+				if (camel_file_util_decode_uint32(fp, &argv->argv[argv->argc].ca_int) == -1)
+					goto cleanup;
+				break;
+			case CAMEL_ARG_STR:
+				if (camel_file_util_decode_string(fp, &argv->argv[argv->argc].ca_str) == -1)
+					goto cleanup;
+				break;
+			default:
+				goto cleanup;
+			}
+
+			argv->argc++;
+		}
+
+		camel_object_setv(obj, NULL, argv);
+	cleanup:
+		for (i=0;i<argv->argc;i++) {
+			if ((argv->argv[i].tag & CAMEL_ARG_TYPE) == CAMEL_ARG_STR)
+				g_free(argv->argv[i].ca_str);
+		}
+		g_free(argv);
+	}
+
+	return 0;
 }
 
+/* TODO: should pass exception around */
+static int
+cobject_state_write(CamelObject *obj, FILE *fp)
+{
+	gint32 count, i;
+	CamelObjectMeta *meta = NULL, *scan;
+	int res = -1;
+	GSList *props = NULL, *l;
+	CamelArgGetV *arggetv = NULL;
+	CamelArgV *argv = NULL;
+
+	camel_object_get(obj, NULL, CAMEL_OBJECT_METADATA, &meta, NULL);
+
+	count = 0;
+	scan = meta;
+	while (scan) {
+		count++;
+		scan = scan->next;
+	}
+
+	/* current version is 1 */
+	if (camel_file_util_encode_uint32(fp, 1) == -1
+	    || camel_file_util_encode_uint32(fp, count) == -1)
+		goto abort;
+
+	scan = meta;
+	while (scan) {
+		if (camel_file_util_encode_string(fp, meta->name) == -1
+		    || camel_file_util_encode_string(fp, meta->value) == -1)
+			goto abort;
+		scan = scan->next;
+	}
+
+	camel_object_get(obj, NULL, CAMEL_OBJECT_PERSISTENT_PROPERTIES, &props, NULL);
+
+	/* we build an arggetv to query the object atomically,
+	   we also need an argv to store the results - bit messy */
+
+	count = g_slist_length(props);
+
+	printf("saving persistent properties, count = %d\n", count);
+
+	arggetv = g_malloc0(sizeof(*arggetv) + (count - CAMEL_ARGV_MAX) * sizeof(arggetv->argv[0]));
+	argv = g_malloc0(sizeof(*argv) + (count - CAMEL_ARGV_MAX) * sizeof(argv->argv[0]));
+	l = props;
+	i = 0;
+	while (l) {
+		CamelProperty *prop = l->data;
+
+		argv->argv[i].tag = prop->tag;
+		arggetv->argv[i].tag = prop->tag;
+		arggetv->argv[i].ca_ptr = &argv->argv[i].ca_ptr;
+
+		i++;
+		l = l->next;
+	}
+	arggetv->argc = i;
+	argv->argc = i;
+
+	camel_object_getv(obj, NULL, arggetv);
+
+	if (camel_file_util_encode_uint32(fp, count) == -1)
+		goto abort;
+
+	for (i=0;i<argv->argc;i++) {
+		CamelArg *arg = &argv->argv[i];
+
+		if (camel_file_util_encode_uint32(fp, arg->tag) == -1)
+			goto abort;
+
+		switch (arg->tag & CAMEL_ARG_TYPE) {
+		case CAMEL_ARG_INT:
+			if (camel_file_util_encode_uint32(fp, arg->ca_int) == -1)
+				goto abort;
+			break;
+		case CAMEL_ARG_STR:
+			if (camel_file_util_encode_string(fp, arg->ca_str) == -1)
+				goto abort;
+			break;
+		}
+	}
+
+	res = 0;
+abort:
+	g_free(argv);
+	g_free(arggetv);
+
+	if (props)
+		camel_object_free(obj, CAMEL_OBJECT_PERSISTENT_PROPERTIES, props);
+
+	if (meta)
+		camel_object_free(obj, CAMEL_OBJECT_METADATA, meta);
+
+	return res;
+}
+
+
 static void
 cobject_class_init(CamelObjectClass *klass)
 {
@@ -255,7 +579,13 @@ cobject_class_init(CamelObjectClass *klass)
 	klass->setv = cobject_setv;
 	klass->free = cobject_free;
 
+	klass->meta_get = cobject_meta_get;
+	klass->meta_set = cobject_meta_set;
+	klass->state_read = cobject_state_read;
+	klass->state_write = cobject_state_write;
+
 	camel_object_class_add_event(klass, "finalize", NULL);
+	camel_object_class_add_event(klass, "meta_changed", NULL);
 }
 
 static void
@@ -675,6 +1005,12 @@ camel_object_free_hooks (CamelObject *o)
 		pair = o->hooks->list;
 		while (pair) {
 			next = pair->next;
+
+			if (pair->name == meta_name) {
+				co_metadata_free(o, pair->data);
+				g_free(pair->func.filename);
+			}
+
 			pair_free(pair);
 			pair = next;
 		}
@@ -981,6 +1317,213 @@ int camel_object_getv(void *vo, CamelException *ex, CamelArgGetV *args)
 	return ((CamelObject *)vo)->klass->getv(vo, ex, args);
 }
 
+/* NB: If this doesn't return NULL, then you must unget_hooks when done */
+static CamelHookPair *
+co_metadata_pair(CamelObject *obj, int create)
+{
+	CamelHookPair *pair;
+	CamelHookList *hooks;
+
+	if (obj->hooks == NULL && !create)
+		return NULL;
+
+	hooks = camel_object_get_hooks(obj);
+	pair = hooks->list;
+	while (pair) {
+		if (pair->name == meta_name)
+			return pair;
+
+		pair = pair->next;
+	}
+
+	if (create) {
+		pair = pair_alloc();
+		pair->name = meta_name;
+		pair->data = NULL;
+		pair->flags = 0;
+		pair->func.filename = NULL;
+		pair->next = hooks->list;
+		hooks->list = pair;
+		hooks->list_length++;
+	} else {
+		camel_object_unget_hooks(obj);
+	}
+
+	return pair;
+}
+
+static CamelObjectMeta *
+co_metadata_get(CamelObject *obj)
+{
+	CamelHookPair *pair;
+	CamelObjectMeta *meta = NULL, *metaout = NULL, *metalast;
+
+	pair = co_metadata_pair(obj, FALSE);
+	if (pair) {
+		meta = pair->data;
+
+		while (meta) {
+			CamelObjectMeta *m;
+
+			m = g_malloc(sizeof(*metalast) + strlen(meta->name));
+			m->next = NULL;
+			strcpy(m->name, meta->name);
+			m->value = g_strdup(meta->value);
+			if (metaout == NULL)
+				metalast = metaout = m;
+			else {
+				metalast->next = m;
+				metalast = m;
+			}
+			meta = meta->next;
+		}
+
+		camel_object_unget_hooks(obj);
+	}
+
+	return metaout;
+}
+
+static void
+co_metadata_free(CamelObject *obj, CamelObjectMeta *meta)
+{
+	while (meta) {
+		CamelObjectMeta *metan = meta->next;
+
+		g_free(meta->value);
+		g_free(meta);
+		meta = metan;
+	}
+}
+
+/**
+ * camel_object_meta_get:
+ * @vo: 
+ * @name: 
+ * 
+ * Get a meta-data on an object.
+ * 
+ * Return value: NULL if the meta-data is not set.
+ **/
+char *
+camel_object_meta_get(void *vo, const char * name)
+{
+	CamelObject *obj = vo;
+
+	g_return_val_if_fail(CAMEL_IS_OBJECT (obj), 0);
+	g_return_val_if_fail(name != NULL, 0);
+
+	return obj->klass->meta_get(obj, name);
+}
+
+/**
+ * camel_object_meta_set:
+ * @vo: 
+ * @name: Name of meta-data.  Should be prefixed with class of setter.
+ * @value: Value to set.  If NULL, then the meta-data is removed.
+ * 
+ * Set a meta-data item on an object.  If the object supports persistent
+ * data, then the meta-data will be persistent across sessions.
+ *
+ * If the meta-data changes, is added, or removed, then a
+ * "meta_changed" event will be triggered with the name of the changed
+ * data.
+ *
+ * Return Value: TRUE if the setting caused a change to the object's
+ * metadata.
+ **/
+gboolean
+camel_object_meta_set(void *vo, const char * name, const char *value)
+{
+	CamelObject *obj = vo;
+
+	g_return_val_if_fail(CAMEL_IS_OBJECT (obj), FALSE);
+	g_return_val_if_fail(name != NULL, FALSE);
+
+	if (obj->klass->meta_set(obj, name, value)) {
+		camel_object_trigger_event(obj, "meta_changed", (void *)name);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/**
+ * camel_object_state_read:
+ * @vo: 
+ * 
+ * Read persistent object state from object_set(CAMEL_OBJECT_STATE_FILE).
+ * 
+ * Return value: -1 on error.
+ **/
+int camel_object_state_read(void *vo)
+{
+	CamelObject *obj = vo;
+	int res = -1;
+	char *file;
+	FILE *fp;
+	char magic[4];
+
+	camel_object_get(vo, NULL, CAMEL_OBJECT_STATE_FILE, &file, NULL);
+	if (file == NULL)
+		return 0;
+
+	fp = fopen(file, "r");
+	if (fp != NULL) {
+		if (fread(magic, 4, 1, fp) == 1
+		    && memcmp(magic, CAMEL_OBJECT_STATE_FILE_MAGIC, 4) == 0)
+			res = obj->klass->state_read(obj, fp);
+		else
+			res = -1;
+		fclose(fp);
+	}
+
+	camel_object_free(vo, CAMEL_OBJECT_STATE_FILE, file);
+
+	return res;
+}
+
+/**
+ * camel_object_state_write:
+ * @vo: 
+ * 
+ * Write persistent state to the file as set by object_set(CAMEL_OBJECT_STATE_FILE).
+ * 
+ * Return value: -1 on error.
+ **/
+int camel_object_state_write(void *vo)
+{
+	CamelObject *obj = vo;
+	int res = -1;
+	char *file, *savename;
+	FILE *fp;
+
+	camel_object_get(vo, NULL, CAMEL_OBJECT_STATE_FILE, &file, NULL);
+	if (file == NULL)
+		return 0;
+
+	printf("camel_object_state_write -> '%s'\n", file);
+
+	savename = camel_file_util_savename(file);
+	fp = fopen(savename, "w");
+	if (fp != NULL) {
+		if (fwrite(CAMEL_OBJECT_STATE_FILE_MAGIC, 4, 1, fp) == 1
+		    && obj->klass->state_write(obj, fp) == 0) {
+			if (fclose(fp) == 0) {
+				res = 0;
+				rename(savename, file);
+			}
+		} else {
+			fclose(fp);
+		}
+	}
+
+	g_free(savename);
+	camel_object_free(vo, CAMEL_OBJECT_STATE_FILE, file);
+
+	return res;
+}
+
 /* free an arg object, you can only free objects 1 at a time */
 void camel_object_free(void *vo, guint32 tag, void *value)
 {
diff --git a/camel/camel-object.h b/camel/camel-object.h
index 0bcdeaef0f..a13cf69325 100644
--- a/camel/camel-object.h
+++ b/camel/camel-object.h
@@ -33,6 +33,7 @@ extern "C" {
 #endif /* __cplusplus */
 
 #include <glib.h>
+#include <stdio.h>		/* FILE */
 #include <stdlib.h>		/* size_t */
 #include <stdarg.h>
 #include <pthread.h>
@@ -74,6 +75,7 @@ extern CamelType camel_object_type;
 typedef struct _CamelObjectClass CamelObjectClass;
 typedef struct _CamelObject CamelObject;
 typedef unsigned int CamelObjectHookID;
+typedef struct _CamelObjectMeta CamelObjectMeta;
 
 typedef void (*CamelObjectClassInitFunc) (CamelObjectClass *);
 typedef void (*CamelObjectClassFinalizeFunc) (CamelObjectClass *);
@@ -85,19 +87,37 @@ typedef void (*CamelObjectEventHookFunc) (CamelObject *, gpointer, gpointer);
 
 #define CAMEL_INVALID_TYPE (NULL)
 
-/* camel object args */
+/* camel object args. */
 enum {
-	CAMEL_OBJECT_ARG_DESCRIPTION = CAMEL_ARG_FIRST,
+	/* Get a description of the object. */
+	CAMEL_OBJECT_ARG_DESCRIPTION = CAMEL_ARG_FIRST,	/* Get a copy of the meta-data list (should be freed) */
+	CAMEL_OBJECT_ARG_METADATA,
+	CAMEL_OBJECT_ARG_STATE_FILE,
+	CAMEL_OBJECT_ARG_PERSISTENT_PROPERTIES,
 };
 
 enum {
 	CAMEL_OBJECT_DESCRIPTION = CAMEL_OBJECT_ARG_DESCRIPTION | CAMEL_ARG_STR,
+	/* Returns a CamelObjectMeta list */
+	CAMEL_OBJECT_METADATA = CAMEL_OBJECT_ARG_METADATA | CAMEL_ARG_PTR,
+	/* sets where the persistent data should reside, otherwise it isn't persistent */
+	CAMEL_OBJECT_STATE_FILE = CAMEL_OBJECT_ARG_STATE_FILE | CAMEL_ARG_STR,
+	/* returns a GSList CamelProperties of persistent properties */
+	CAMEL_OBJECT_PERSISTENT_PROPERTIES = CAMEL_OBJECT_ARG_PERSISTENT_PROPERTIES | CAMEL_ARG_PTR,
 };
 
 enum _CamelObjectFlags {
 	CAMEL_OBJECT_DESTROY = (1<<0),
 };
 
+/* returned by get::CAMEL_OBJECT_METADATA */
+struct _CamelObjectMeta {
+	struct _CamelObjectMeta *next;
+
+	char *value;
+	char name[1];		/* allocated as part of structure */
+};
+
 /* TODO: create a simpleobject which has no events on it, or an interface for events */
 struct _CamelObject {
 	struct _CamelObjectClass *klass;
@@ -155,6 +175,14 @@ struct _CamelObjectClass
 	int (*getv)(struct _CamelObject *, struct _CamelException *ex, CamelArgGetV *args);
 	/* we only free 1 at a time, and only pointer types, obviously */
 	void (*free)(struct _CamelObject *, guint32 tag, void *ptr);
+
+	/* get/set meta-data interface */
+	char *(*meta_get)(struct _CamelObject *, const char * name);
+	gboolean (*meta_set)(struct _CamelObject *, const char * name, const char *value);
+
+	/* persistence stuff */
+	int (*state_read)(struct _CamelObject *, FILE *fp);
+	int (*state_write)(struct _CamelObject *, FILE *fp);
 };
 
 /* The type system .... it's pretty simple..... */
@@ -209,6 +237,14 @@ int camel_object_setv(void *obj, struct _CamelException *ex, CamelArgV *);
 int camel_object_get(void *obj, struct _CamelException *ex, ...);
 int camel_object_getv(void *obj, struct _CamelException *ex, CamelArgGetV *);
 
+/* meta-data for user-specific data */
+char *camel_object_meta_get(void *vo, const char * name);
+gboolean camel_object_meta_set(void *vo, const char * name, const char *value);
+
+/* reads/writes the state from/to the CAMEL_OBJECT_STATE_FILE */
+int camel_object_state_read(void *vo);
+int camel_object_state_write(void *vo);
+
 /* free a bunch of objects, list must be 0 terminated */
 void camel_object_free(void *vo, guint32 tag, void *value);
 
diff --git a/camel/providers/local/camel-local-folder.c b/camel/providers/local/camel-local-folder.c
index 0945f92370..91d001ccfc 100644
--- a/camel/providers/local/camel-local-folder.c
+++ b/camel/providers/local/camel-local-folder.c
@@ -59,7 +59,8 @@
 #define PATH_MAX _POSIX_PATH_MAX
 #endif
 
-static CamelFolderClass *parent_class = NULL;
+static CamelFolderClass *parent_class;
+static GSList *local_folder_properties;
 
 /* Returns the class for a CamelLocalFolder */
 #define CLOCALF_CLASS(so) CAMEL_LOCAL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
@@ -67,6 +68,7 @@ static CamelFolderClass *parent_class = NULL;
 #define CLOCALS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
 
 static int local_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args);
+static int local_setv(CamelObject *object, CamelException *ex, CamelArgV *args);
 
 static int local_lock(CamelLocalFolder *lf, CamelLockType type, CamelException *ex);
 static void local_unlock(CamelLocalFolder *lf);
@@ -91,12 +93,11 @@ camel_local_folder_class_init(CamelLocalFolderClass * camel_local_folder_class)
 	CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS(camel_local_folder_class);
 	CamelObjectClass *oklass = (CamelObjectClass *)camel_local_folder_class;
 
-	parent_class = CAMEL_FOLDER_CLASS(camel_type_get_global_classfuncs(camel_folder_get_type()));
-
 	/* virtual method definition */
 
 	/* virtual method overload */
 	oklass->getv = local_getv;
+	oklass->setv = local_setv;
 
 	camel_folder_class->refresh_info = local_refresh_info;
 	camel_folder_class->sync = local_sync;
@@ -168,19 +169,31 @@ local_finalize(CamelObject * object)
 	g_free(local_folder->priv);
 }
 
+static CamelProperty local_property_list[] = {
+	{ CAMEL_LOCAL_FOLDER_INDEX_BODY, "index_body", N_("Index message body data") },
+};
+
 CamelType
 camel_local_folder_get_type(void)
 {
 	static CamelType camel_local_folder_type = CAMEL_INVALID_TYPE;
 
 	if (camel_local_folder_type == CAMEL_INVALID_TYPE) {
-		camel_local_folder_type = camel_type_register(CAMEL_FOLDER_TYPE, "CamelLocalFolder",
+		int i;
+
+		parent_class = (CamelFolderClass *)camel_folder_get_type();
+		camel_local_folder_type = camel_type_register(camel_folder_get_type(), "CamelLocalFolder",
 							     sizeof(CamelLocalFolder),
 							     sizeof(CamelLocalFolderClass),
 							     (CamelObjectClassInitFunc) camel_local_folder_class_init,
 							     NULL,
 							     (CamelObjectInitFunc) local_init,
 							     (CamelObjectFinalizeFunc) local_finalize);
+
+		for (i=0;i<sizeof(local_property_list)/sizeof(local_property_list[0]);i++) {
+			local_property_list[i].description = _(local_property_list[i].description);
+			local_folder_properties = g_slist_prepend(local_folder_properties, &local_property_list[i]);
+		}
 	}
 
 	return camel_local_folder_type;
@@ -192,7 +205,7 @@ camel_local_folder_construct(CamelLocalFolder *lf, CamelStore *parent_store, con
 	CamelFolderInfo *fi;
 	CamelFolder *folder;
 	const char *root_dir_path, *name;
-	char *tmp;
+	char *tmp, *statepath;
 	char folder_path[PATH_MAX];
 	struct stat st;
 	int forceindex, len;
@@ -223,12 +236,20 @@ camel_local_folder_construct(CamelLocalFolder *lf, CamelStore *parent_store, con
 		/* not really sure to do with these for now? */
 		lf->summary_path = g_strdup_printf("%s.ev-summary", tmp);
 		lf->index_path = g_strdup_printf("%s.ibex", tmp);
+		statepath = alloca(strlen(tmp)+7);
+		sprintf(statepath, "%s.cmeta", tmp);
 	} else {
 		lf->folder_path = g_strdup_printf("%s/%s", root_dir_path, full_name);
 		lf->summary_path = g_strdup_printf("%s/%s.ev-summary", root_dir_path, full_name);
 		lf->index_path = g_strdup_printf("%s/%s.ibex", root_dir_path, full_name);
+		statepath = alloca(strlen(full_name)+strlen(root_dir_path)+8);
+		sprintf(statepath, "%s/%s.cmeta", root_dir_path, full_name);
 	}
-	
+	camel_object_set(lf, NULL, CAMEL_OBJECT_STATE_FILE, statepath, NULL);
+	if (camel_object_state_read(lf) == -1) {
+		/* FIXME: load defaults? */
+	}
+
 	/* follow any symlinks to the mailbox */
 	if (lstat (lf->folder_path, &st) != -1 && S_ISLNK (st.st_mode) &&
 	    realpath (lf->folder_path, folder_path) != NULL) {
@@ -326,7 +347,7 @@ static int
 local_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args)
 {
 	CamelFolder *folder = (CamelFolder *)object;
-	int i, count=args->argc;
+	int i;
 	guint32 tag;
 
 	for (i=0;i<args->argc;i++) {
@@ -335,7 +356,6 @@ local_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args)
 		tag = arg->tag;
 
 		switch (tag & CAMEL_ARG_TAG) {
-			/* CamelObject args */
 		case CAMEL_OBJECT_ARG_DESCRIPTION:
 			if (folder->description == NULL) {
 				char *tmp, *path;
@@ -366,18 +386,58 @@ local_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args)
 			}
 			*arg->ca_str = folder->description;
 			break;
+
+		case CAMEL_OBJECT_ARG_PERSISTENT_PROPERTIES:
+		case CAMEL_FOLDER_ARG_PROPERTIES: {
+			CamelArgGetV props;
+
+			props.argc = 1;
+			props.argv[0] = *arg;
+			((CamelObjectClass *)parent_class)->getv(object, ex, &props);
+			*arg->ca_ptr = g_slist_concat(*arg->ca_ptr, local_folder_properties);
+
+			break; }
+
+		case CAMEL_LOCAL_FOLDER_INDEX_BODY:
+			/* FIXME: remove this from sotre flags */
+			*arg->ca_int = (((CamelLocalFolder *)folder)->flags & CAMEL_STORE_FOLDER_BODY_INDEX) != 0;
+			break;
+
 		default: skip:
-			count--;
 			continue;
 		}
 
 		arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
 	}
 
-	if (count)
-		return ((CamelObjectClass *)parent_class)->getv(object, ex, args);
+	return ((CamelObjectClass *)parent_class)->getv(object, ex, args);
+}
 
-	return 0;
+static int
+local_setv(CamelObject *object, CamelException *ex, CamelArgV *args)
+{
+	CamelFolder *folder = (CamelFolder *)object;
+	int i;
+	guint32 tag;
+
+	for (i=0;i<args->argc;i++) {
+		CamelArg *arg = &args->argv[i];
+
+		tag = arg->tag;
+
+		switch (tag & CAMEL_ARG_TAG) {
+		case CAMEL_LOCAL_FOLDER_INDEX_BODY:
+			/* FIXME: implement */
+			printf("setting folder indexing %s\n", arg->ca_int?"on":"off");
+			break;
+		default:
+			continue;
+		}
+
+		arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
+	}
+
+	return ((CamelObjectClass *)parent_class)->setv(object, ex, args);
 }
 
 static int
diff --git a/camel/providers/local/camel-local-folder.h b/camel/providers/local/camel-local-folder.h
index c958bde835..63a6870793 100644
--- a/camel/providers/local/camel-local-folder.h
+++ b/camel/providers/local/camel-local-folder.h
@@ -40,6 +40,16 @@ extern "C" {
 #define CAMEL_LOCAL_FOLDER_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_LOCAL_FOLDER_TYPE, CamelLocalFolderClass))
 #define CAMEL_IS_LOCAL_FOLDER(o)    (CAMEL_CHECK_TYPE((o), CAMEL_LOCAL_FOLDER_TYPE))
 
+enum {
+	CAMEL_LOCAL_FOLDER_ARG_INDEX_BODY = CAMEL_FOLDER_ARG_LAST,
+
+	CAMEL_LOCAL_FOLDER_ARG_LAST = CAMEL_FOLDER_ARG_LAST + 0x100
+};
+
+enum {
+	CAMEL_LOCAL_FOLDER_INDEX_BODY = CAMEL_LOCAL_FOLDER_ARG_INDEX_BODY | CAMEL_ARG_INT,
+};
+
 typedef struct {
 	CamelFolder parent_object;
 	struct _CamelLocalFolderPrivate *priv;
-- 
cgit