/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Authors: Michael Zucchi <notzed@ximian.com>
*
* Copyright 2003 Ximian, Inc. (www.ximian.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif
#include <glib.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlmemory.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <gal/util/e-xml-utils.h>
#include <gal/util/e-util.h>
#include "e-meta.h"
static GObjectClass *e_meta_parent_class;
struct _meta_data {
struct _meta_data *next;
char *md_key;
char *md_value;
};
struct _EMetaPrivate {
char *path;
struct _meta_data *data;
gulong sync_id;
/* if set, we wont try and save/etc */
unsigned int deleted:1;
};
static int meta_save(EMeta *em)
{
struct _EMetaPrivate *p = em->priv;
xmlDocPtr doc;
xmlNodePtr root, work;
struct _meta_data *md;
int res;
char *dir;
struct stat st;
if (p->deleted)
return 0;
/* since we can, build the full path if we need to */
dir = g_path_get_dirname(p->path);
if (stat(dir, &st) == -1) {
e_mkdir_hier(dir, 0777);
g_free(dir);
}
/* of course, saving in xml is overkill, but everyone loves this shit ... */
doc = xmlNewDoc("1.0");
root = xmlNewDocNode(doc, NULL, "e-meta-data", NULL);
xmlDocSetRootElement(doc, root);
md = p->data;
while (md) {
work = xmlNewChild(root, NULL, "item", NULL);
xmlSetProp(work, "name", md->md_key);
xmlSetProp(work, "value", md->md_value);
md = md->next;
}
res = e_xml_save_file(p->path, doc);
if (res != 0)
g_warning("Could not save folder meta-data `%s': %s", p->path, g_strerror(errno));
xmlFreeDoc(doc);
return res;
}
static int meta_load(EMeta *em)
{
struct _EMetaPrivate *p = em->priv;
struct _meta_data *tail, *md;
xmlDocPtr doc;
xmlNodePtr root, work;
char *name, *val;
doc = xmlParseFile(p->path);
if (doc == NULL)
return -1;
root = xmlDocGetRootElement(doc);
if (root == NULL || strcmp(root->name, "e-meta-data")) {
xmlFreeDoc(doc);
errno = EINVAL;
return -1;
}
work = root->children;
tail = (struct _meta_data *)&p->data;
while (work) {
if (strcmp(work->name, "item") == 0) {
name = xmlGetProp(work, "name");
val = xmlGetProp(work, "value");
if (name && val) {
md = g_malloc(sizeof(*md));
md->md_key = g_strdup(name);
md->md_value = g_strdup(val);
md->next = NULL;
tail->next = md;
tail = md;
}
if (name)
xmlFree(name);
if (val)
xmlFree(val);
}
work = work->next;
}
xmlFreeDoc(doc);
return 0;
}
static struct _meta_data *meta_find(EMeta *em, const char *key, struct _meta_data **mpp)
{
struct _meta_data *mp = (struct _meta_data *)&em->priv->data;
struct _meta_data *md = mp->next;
while (md && strcmp(md->md_key, key) != 0) {
mp = md;
md = md->next;
}
*mpp = mp;
return md;
}
static void meta_free(struct _meta_data *md)
{
g_free(md->md_key);
g_free(md->md_value);
g_free(md);
}
static void
e_meta_init (EMeta *em)
{
em->priv = g_malloc0(sizeof(*em->priv));
}
static void
e_meta_finalise(GObject *crap)
{
EMeta *em = (EMeta *)crap;
struct _EMetaPrivate *p = em->priv;
struct _meta_data *md, *mn;
if (p->sync_id != 0)
e_meta_sync(em);
md = p->data;
while (md) {
mn = md->next;
meta_free(md);
md = mn;
}
g_free(p->path);
g_free(p);
e_meta_parent_class->finalize((GObject *)em);
}
static void
e_meta_class_init (EMetaClass *klass)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS(klass);
e_meta_parent_class = g_type_class_ref (G_TYPE_OBJECT);
((GObjectClass *)klass)->finalize = e_meta_finalise;
}
static GTypeInfo e_meta_type_info = {
sizeof (EMetaClass),
NULL, /* base_class_init */
NULL, /* base_class_finalize */
(GClassInitFunc) e_meta_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (EMeta),
0, /* n_preallocs */
(GInstanceInitFunc) e_meta_init
};
static GType e_meta_type;
GType
e_meta_get_type (void)
{
return e_meta_type?e_meta_type:(e_meta_type = g_type_register_static (G_TYPE_OBJECT, "EMeta", &e_meta_type_info, 0));
}
/**
* e_meta_new:
* @path: full path to meta-data storage object.
*
* Create a new meta-data storage object. Any existing meta-data stored for
* this key will be loaded.
*
* Return value:
**/
EMeta *
e_meta_new(const char *path)
{
EMeta *em;
em = g_object_new(e_meta_get_type(), NULL);
em->priv->path = g_strdup(path);
meta_load(em);
return em;
}
static gboolean
meta_flush(EMeta *em)
{
em->priv->sync_id = 0;
meta_save(em);
return FALSE;
}
/* returns TRUE if the value changed */
static int meta_set(EMeta *em, const char *key, const char *val)
{
struct _EMetaPrivate *p = em->priv;
struct _meta_data *md, *mp;
md = meta_find(em, key, &mp);
if (md == NULL) {
/* already unset / or new case */
if (val == NULL)
return FALSE;
md = g_malloc0(sizeof(*md));
md->md_key = g_strdup(key);
md->next = p->data;
p->data = md;
} else if (val == NULL) {
/* unset case */
mp->next = md->next;
meta_free(md);
return TRUE;
} else if (strcmp(md->md_value, val) == 0) {
/* unchanged value */
return FALSE;
} else {
/* changed value */
g_free(md->md_value);
}
md->md_value = g_strdup(val);
return TRUE;
}
/* get a value, returns NULL if it doesn't exist */
static const char *meta_get(EMeta *em, const char *key)
{
struct _meta_data *md, *mp;
md = meta_find(em, key, &mp);
return md?md->md_value:NULL;
}
/**
* e_meta_set:
* @em:
* @key:
* @...: value, key, value, ..., NULL.
*
* Set any number of meta-data key-value pairs.
* Unset a key by passing a value of NULL.
*
* If the meta-data set changes as a result of this
* call, then a sync will be implicitly queued for
* a later time.
**/
void
e_meta_set(EMeta *em, const char *key, ...)
{
struct _EMetaPrivate *p = em->priv;
const char *val;
va_list ap;
int changed = FALSE;
va_start(ap, key);
while (key != NULL) {
val = va_arg(ap, const char *);
changed = meta_set(em, key, val);
key = va_arg(ap, const char *);
}
va_end(ap);
/* todo: could do changed events ? */
if (changed && p->sync_id == 0)
p->sync_id = g_timeout_add(2000, (GSourceFunc)meta_flush, em);
}
/**
* e_meta_get:
* @em:
* @key:
* @...: value, key, value, ..., NULL.
*
* Get any number of meta-data key-value pairs.
**/
void
e_meta_get(EMeta *em, const char *key, ...)
{
const char **valp;
va_list ap;
va_start(ap, key);
while (key) {
valp = va_arg(ap, const char **);
*valp = meta_get(em, key);
key = va_arg(ap, const char *);
}
va_end(ap);
}
/**
* e_meta_get_bool:
* @em:
* @key:
* @def:
*
* Get a boolean value at @key, with a default fallback @def.
*
* If the default value is used, then it will become the persistent
* new value for the key.
*
* Return value: The value of the key, or if the key was not
* previously set, then the new value of the key, @def.
**/
gboolean
e_meta_get_bool(EMeta *em, const char *key, gboolean def)
{
const char *v;
v = meta_get(em, key);
/* this forces the value to become 'static' from first use */
if (v == NULL) {
e_meta_set_bool(em, key, def);
return def;
}
return atoi(v);
}
/**
* e_meta_set_bool:
* @em:
* @key:
* @val:
*
* Helper to set a boolean value. Boolean TRUE is mapped to
* the string "1", FALSE to "0".
**/
void
e_meta_set_bool(EMeta *em, const char *key, gboolean val)
{
e_meta_set(em, key, val?"1":"0", NULL);
}
/**
* e_meta_sync:
* @em:
*
* Force an explicit and immediate sync of the meta-data to disk.
*
* This is not normally required unless part of transactional
* processing, as updates will always be flushed to disk automatically.
*
* Return value: 0 on success.
**/
int
e_meta_sync(EMeta *em)
{
struct _EMetaPrivate *p = em->priv;
if (p->sync_id != 0) {
g_source_remove(p->sync_id);
p->sync_id = 0;
}
return meta_save(em);
}
static GHashTable *e_meta_table;
static char *meta_filename(const char *base, const char *key)
{
const char *p;
char *keyp, *o, c;
p = key;
o = keyp = alloca(strlen(key)+8);
while ( (c = *p++) ) {
if (c == '/')
c = '_';
*o++ = c;
}
strcpy(o, ".emeta");
o = g_build_filename(base, keyp, NULL);
return o;
}
static void
meta_weak_notify(char *path, void *o)
{
g_hash_table_remove(e_meta_table, path);
g_free(path);
}
/**
* e_meta_data_lookup:
* @base: Base storage directory.
* @key: key for file.
*
* Lookup a meta-data object from a storage directory.
*
* Return value: The metadata object.
**/
EMeta *e_meta_data_find(const char *base, const char *key)
{
EMeta *em;
char *path;
if (e_meta_table == NULL)
e_meta_table = g_hash_table_new(g_str_hash, g_str_equal);
path = meta_filename(base, key);
em = g_hash_table_lookup(e_meta_table, path);
if (em) {
g_free(path);
g_object_ref(em);
return em;
}
em = e_meta_new(path);
g_hash_table_insert(e_meta_table, path, em);
g_object_weak_ref((GObject *)em, (GWeakNotify)meta_weak_notify, path);
return em;
}
/**
* e_meta_data_delete:
* @base:
* @key:
*
* Delete a key from storage. If the key is still cached, it will be
* marked as deleted, and will not be saved from then on.
**/
void e_meta_data_delete(const char *base, const char *key)
{
EMeta *em;
char *path;
path = meta_filename(base, key);
if (e_meta_table && (em = g_hash_table_lookup(e_meta_table, path))) {
if (em->priv->sync_id) {
g_source_remove(em->priv->sync_id);
em->priv->sync_id = 0;
}
em->priv->deleted = TRUE;
}
unlink(path);
g_free(path);
}