/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Authors: Michael Zucchi * * 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 #endif #include #include #include #include #include #include #include #include #include #include #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 = NULL; xmlNodePtr root, work; char *name, *val; struct stat st; if (stat (p->path, &st) == -1 || !S_ISREG (st.st_mode)) return -1; 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); }