/**
* bonobo-config-xmldirdb.c: xml configuration database implementation.
*
* Author:
* Dietmar Maurer (dietmar@ximian.com)
*
* Copyright 2000 Ximian, Inc.
*
* Description:
*
* This backend uses a separate file to store the data of each configuration
* directory. Unlike GConf, all files are stored in a single file system
* directory. There is a 1:1 mapping between the configuration key and the
* filename, for example the value "/Applications/Gnumeric/autosave" is stored
* in the file "%Application:Gnumeric.xmldb". All files start with a "%" sign
* and ends in ".xmldb", and all slashes are replaced with colons.
*
* The stored files have the same format as the xmldb backend, but only
* contains one section tag.
*
* Performance should be quite good because we keep most things in
* memory. Memory usage is also limited because we use a cache and unload
* unused directories. But maybe we should use a fully associative cache with
* LRU replacement instead of a direct mapped cache.
*
**/
#include <config.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <bonobo/bonobo-arg.h>
#include <bonobo/bonobo-property-bag-xml.h>
#include <bonobo/bonobo-moniker-util.h>
#include <bonobo/bonobo-exception.h>
#include <bonobo-conf/bonobo-config-utils.h>
#include <gnome-xml/xmlmemory.h>
#include <gtk/gtkmain.h>
#include "bonobo-config-dirdb.h"
static GtkObjectClass *parent_class = NULL;
#define CLASS(o) BONOBO_CONFIG_DIRDB_CLASS (GTK_OBJECT(o)->klass)
#define PARENT_TYPE BONOBO_CONFIG_DATABASE_TYPE
#define CSIZE 37 /* should be a prime number */
#define FLUSH_INTERVAL 30 /* 30 seconds */
typedef struct _DirData DirData;
struct _DirData {
char *name;
GSList *entries;
xmlDocPtr doc;
gboolean dirty;
};
typedef struct {
char *name;
CORBA_any *value;
xmlNodePtr node;
DirData *dir;
} DirEntry;
typedef GNode DTree;
typedef struct _BonoboConfigDIRDBPriv DirCache;
struct _BonoboConfigDIRDBPriv {
char *basedir;
DirData *data [CSIZE];
DTree *dtree;
BonoboEventSource *es;
guint time_id;
};
/**
* returns the first directory name, rest points to the rest of the string. We
* use colon and slashes as delimiters
**/
static char *
split_name (const char *dir, const char **rest)
{
char *name;
int i = 0, j, l;
while (dir [i] == '/' || dir [i] == ':')
i++;
j = i;
while (dir [j] && dir [j] != '/' && dir [j] != ':')
j++;
l = j - i;
name = g_strndup (&dir [i], l);
if (dir [j])
*rest = &dir [j];
else
*rest = NULL;
return name;
}
/**
* remove unnecessary slashes from a key name and check if key is valid. All
* leading, trailing and sequences of slashes are removed.
**/
static char *
normalize_key (const char *key)
{
char *nkey;
int l, i = 0, j = 0;
if (!key)
return NULL;
l = strlen (key);
nkey = g_new0 (char, l + 1);
while (key [i] == '/')
i++;
while ((nkey [j++] = key [i])) {
if (key [i] == ':') {
g_free (nkey);
return NULL;
}
if (key [++i] == '/') {
nkey [j++] = key [i];
while (key [++i] == '/');
}
}
if (--j && nkey [j-1] == '/')
nkey [j-1] = '\0';
return nkey;
}
/**
* implements the mapping between directory names and filesystem paths
**/
static char *
dir_to_path (const char *prefix, const char *dir)
{
char *fn, *path;
int i = 0;
fn = g_strconcat ("%", dir, ".xmldb", NULL);
while (fn [i]) {
if (fn [i] == '/')
fn [i] = ':';
i++;
}
path = g_strconcat (prefix, "/", fn, NULL);
g_free (fn);
return path;
}
/**
* We use a tree to store all directory names in memory. This function adds a
* directory to the tree. A DTree is simply a GNode, and node->data contains
* the directory name.
**/
static void
dtree_add_dir (DTree *dt, const char *dir)
{
char *name;
const char *rest;
GNode *n;
name = split_name (dir, &rest);
for (n = dt->children; n; n = n->next) {
if (!strcmp (n->data, name)) {
g_free (name);
break;
}
}
if (!n) {
n = g_node_new (name);
g_node_prepend (dt, n);
}
if (rest)
dtree_add_dir (n, rest);
}
/**
* check if directory dir exists, and returns a ponter to the corresponding
* GNode.
**/
static GNode *
dtree_lookup_dir (DTree *dt, const char *dir)
{
char *name;
const char *rest;
GNode *n;
name = split_name (dir, &rest);
for (n = dt->children; n; n = n->next)
if (!strcmp (n->data, name))
break;
g_free (name);
if (!n)
return NULL;
if (rest)
return dtree_lookup_dir (n, rest);
else
return n;
}
/**
* remove the directory from the tree, but only if there are no more subdirs.
**/
static void
dtree_remove_dir (DTree *dt, const char *dir)
{
GNode *n;
if ((n = dtree_lookup_dir (dt, dir)) && !n->children) {
g_free (n->data);
g_node_destroy (n);
}
}
/**
* read the contents of basedir, and create the corresponding directory tree.
**/
static DTree *
dtree_create (const char *basedir)
{
DIR *d;
struct dirent *e;
DTree *dt;
int l;
dt = g_node_new (NULL);
d = opendir (basedir);
while ((e = readdir (d))) {
l = strlen (e->d_name);
if (l < 6 || e->d_name [0] != '%' ||
strcmp (e->d_name + l - 6, ".xmldb"))
continue;
e->d_name [l - 6] = '\0';
dtree_add_dir (dt, &e->d_name [1]);
}
return dt;
}
/**
* free all directory tree memory.
**/
static void
dtree_destroy (DTree *dt)
{
GNode *n;
if (!dt)
return;
for (n = dt->children; n; n = n->next) {
dtree_destroy (n);
}
g_free (dt->data);
}
/**
* try to find entry #name inside #dd. If #create is TRUE the entry is
* allocated if it does not exist.
**/
static DirEntry *
dir_lookup_entry (DirData *dd, const char *name, gboolean create)
{
GSList *l;
DirEntry *de;
for (l = dd->entries; l; l = l->next) {
de = (DirEntry *)l->data;
if (!strcmp (de->name, name))
return de;
}
if (create) {
de = g_new0 (DirEntry, 1);
de->dir = dd;
de->name = g_strdup (name);
dd->entries = g_slist_prepend (dd->entries, de);
return de;
}
return NULL;
}
/**
* loads the corresponding .xmldb file into a DirData structure. If create is
* TRUE the structure is allocated if the file does not exist.
**/
static DirData *
dcache_load_dir (DirCache *cache, const char *dir, gboolean create)
{
CORBA_Environment ev;
DirData *dd;
DirEntry *de;
xmlDocPtr doc;
xmlNodePtr n;
char *path, *ndir, *name;
if (!(ndir = normalize_key (dir)))
return NULL;
path = dir_to_path (cache->basedir, ndir);
if (!(doc = xmlParseFile (path)) && !create) {
g_free (ndir);
g_free (path);
return NULL;
}
g_free (path);
if (!doc || strcmp (doc->root->name, "bonobo-config")) {
xmlFreeDoc (doc);
doc = xmlNewDoc("1.0");
doc->root = xmlNewDocNode (doc, NULL, "bonobo-config", NULL);
xmlNewChild (doc->root, NULL, "section", NULL);
}
n = doc->root->childs;
if (!n || n->type != XML_ELEMENT_NODE || strcmp (n->name, "section")) {
xmlFreeDoc (doc);
g_free (ndir);
g_free (path);
return NULL;
}
dd = g_new0 (DirData, 1);
dd->name = ndir;
dd->doc = doc;
CORBA_exception_init (&ev);
for (n = n->childs; n; n = n->next) {
if (n->type == XML_ELEMENT_NODE &&
!strcmp (n->name, "entry") &&
(name = xmlGetProp(n, "name"))) {
de = dir_lookup_entry (dd, name, TRUE);
de->node = n;
/* we only decode it if it is a single value,
* multiple (localized) values are decoded in the
* get_value function */
if (!(n->childs && n->childs->next))
de->value = bonobo_config_xml_decode_any
((BonoboUINode *)n, NULL, &ev);
xmlFree (name);
}
}
CORBA_exception_free (&ev);
dtree_add_dir (cache->dtree, dd->name);
return dd;
}
/**
* writes the contents of #dd back to the file system (atomic save).
**/
static gboolean
dcache_save (DirCache *cache, DirData *dd)
{
char *path;
char *tmp_name;
gboolean rval = FALSE;
path = dir_to_path (cache->basedir, dd->name);
tmp_name = g_strdup_printf ("%s.tmp.%d\n", path, getpid ());
if (xmlSaveFile(tmp_name, dd->doc) >= 0 &&
rename (tmp_name, path) >= 0) {
rval = TRUE;
dd->dirty = FALSE;
}
g_free (path);
g_free (tmp_name);
return rval;
}
/**
* free a DirEntry structure.
**/
static void
dcache_free_entry (DirEntry *de)
{
CORBA_free (de->value);
if (de->node) {
xmlUnlinkNode (de->node);
xmlFreeNode (de->node);
}
g_free (de->name);
g_free (de);
}
/**
* free a whole DirData structure.
**/
static void
dcache_free_dir (DirData *dd)
{
GSList *l;
for (l = dd->entries; l; l = l->next)
dcache_free_entry ((DirEntry *)l->data);
g_slist_free (dd->entries);
g_free (dd->name);
xmlFreeDoc (dd->doc);
g_free (dd);
}
/**
* free the whole cache.
**/
static void
dcache_free_all (DirCache *cache)
{
int i;
for (i = 0; i < CSIZE; i++) {
if (cache->data [i])
dcache_free_dir (cache->data [i]);
cache->data [i] = NULL;
}
}
/**
* return the DirData for directory #dir. We use a direct mapped cache to speed
* up things. It creates a new directory if #create is TRUE and the directory
* dose not already exist.
**/
static DirData *
dcache_lookup_dir (DirCache *cache, const char *dir, gboolean create)
{
guint pos;
DirData *dd, *odd;
pos = g_str_hash (dir) % CSIZE;
if ((dd = cache->data [pos]) && !strcmp (dd->name, dir))
return dd;
if (!(dd = dcache_load_dir (cache, dir, create)))
return NULL;
if ((odd = cache->data [pos])) {
if (odd->dirty)
dcache_save (cache, odd);
dcache_free_dir (odd);
}
cache->data [pos] = dd;
return dd;
}
/**
* removes a directory from the cache. All changed data will be lost.
**/
static void
dcache_remove_dir (DirCache *cache, const char *dir)
{
guint pos;
DirData *dd;
pos = g_str_hash (dir) % CSIZE;
if (!(dd = cache->data [pos]) || strcmp (dd->name, dir))
return;
dcache_free_dir (dd);
cache->data [pos] = NULL;
}
/**
* return the DirEntry for #key. If #key does not exist it will be created if
* #create is TRUE.
**/
static DirEntry *
dcache_lookup_entry (DirCache *cache,
const char *key,
gboolean create)
{
char *dir_name;
char *leaf_name;
DirEntry *de;
DirData *dd;
if (!(dir_name = bonobo_config_dir_name (key)))
dir_name = g_strdup ("");
dd = dcache_lookup_dir (cache, dir_name, create);
g_free (dir_name);
if (!dd)
return NULL;
if (!(leaf_name = bonobo_config_leaf_name (key)))
return NULL;
de = dir_lookup_entry (dd, leaf_name, create);
g_free (leaf_name);
return de;
}
static CORBA_any *
real_get_value (BonoboConfigDatabase *db,
const CORBA_char *key,
const CORBA_char *locale,
CORBA_Environment *ev)
{
BonoboConfigDIRDB *dirdb = BONOBO_CONFIG_DIRDB (db);
DirEntry *de;
CORBA_any *value = NULL;
char *ckey;
if (!(ckey = normalize_key (key)) ||
!(de = dcache_lookup_entry (dirdb->priv, ckey, FALSE))) {
bonobo_exception_set (ev, ex_Bonobo_ConfigDatabase_NotFound);
g_free (ckey);
return NULL;
}
g_free (ckey);
if (de->value)
return bonobo_arg_copy (de->value);
/* localized value */
if (de->node && de->node->childs && de->node->childs->next)
value = bonobo_config_xml_decode_any ((BonoboUINode *)de->node,
locale, ev);
if (!value)
bonobo_exception_set (ev, ex_Bonobo_ConfigDatabase_NotFound);
return value;
}
static void
real_sync (BonoboConfigDatabase *db,
CORBA_Environment *ev)
{
BonoboConfigDIRDB *dirdb = BONOBO_CONFIG_DIRDB (db);
DirData *dd;
int i;
for (i = 0; i < CSIZE; i++) {
if ((dd = dirdb->priv->data [i]) && dd->dirty)
dcache_save (dirdb->priv, dd);
}
}
static gint
timeout_cb (gpointer data)
{
BonoboConfigDIRDB *dirdb = BONOBO_CONFIG_DIRDB (data);
CORBA_Environment ev;
CORBA_exception_init(&ev);
real_sync (BONOBO_CONFIG_DATABASE (data), &ev);
CORBA_exception_free (&ev);
dirdb->priv->time_id = 0;
/* remove the timeout */
return 0;
}
static void
notify_listeners (BonoboConfigDIRDB *dirdb,
const char *key,
const CORBA_any *value)
{
CORBA_Environment ev;
char *dir_name;
char *leaf_name;
char *ename;
if (!key)
return;
CORBA_exception_init(&ev);
ename = g_strconcat ("Bonobo/Property:change:", key, NULL);
bonobo_event_source_notify_listeners(dirdb->priv->es, ename, value,
&ev);
g_free (ename);
if (!(dir_name = bonobo_config_dir_name (key)))
dir_name = g_strdup ("");
if (!(leaf_name = bonobo_config_leaf_name (key)))
leaf_name = g_strdup ("");
ename = g_strconcat ("Bonobo/ConfigDatabase:change", dir_name, ":",
leaf_name, NULL);
bonobo_event_source_notify_listeners (dirdb->priv->es, ename, value,
&ev);
CORBA_exception_free (&ev);
g_free (ename);
g_free (dir_name);
g_free (leaf_name);
}
static void
real_set_value (BonoboConfigDatabase *db,
const CORBA_char *key,
const CORBA_any *value,
CORBA_Environment *ev)
{
BonoboConfigDIRDB *dirdb = BONOBO_CONFIG_DIRDB (db);
xmlNodePtr n;
DirEntry *de;
char *name, *ckey;
if (!(ckey = normalize_key (key)) ||
!(de = dcache_lookup_entry (dirdb->priv, ckey, TRUE))) {
bonobo_exception_set (ev, ex_Bonobo_ConfigDatabase_NotFound);
g_free (ckey);
return;
}
CORBA_free (de->value);
de->value = bonobo_arg_copy (value);
if (de->node) {
xmlUnlinkNode (de->node);
xmlFreeNode (de->node);
}
name = bonobo_config_leaf_name (ckey);
de->node = (xmlNodePtr) bonobo_config_xml_encode_any (value, name, ev);
g_free (name);
n = de->dir->doc->root->childs;
bonobo_ui_node_add_child ((BonoboUINode *)n, (BonoboUINode *)de->node);
de->dir->dirty = TRUE;
notify_listeners (dirdb, ckey, value);
g_free (ckey);
if (!dirdb->priv->time_id)
dirdb->priv->time_id = gtk_timeout_add (FLUSH_INTERVAL * 1000,
(GtkFunction)timeout_cb, dirdb);
}
static Bonobo_KeyList *
real_list_dirs (BonoboConfigDatabase *db,
const CORBA_char *dir,
CORBA_Environment *ev)
{
BonoboConfigDIRDB *dirdb = BONOBO_CONFIG_DIRDB (db);
Bonobo_KeyList *key_list;
char *ndir;
GNode *dn, *n;
int len = 0;
if (!(ndir = normalize_key (dir)) ||
!(dn = dtree_lookup_dir (dirdb->priv->dtree, ndir))) {
bonobo_exception_set (ev, ex_Bonobo_ConfigDatabase_NotFound);
return NULL;
}
g_free (ndir);
key_list = Bonobo_KeyList__alloc ();
if (!dn->children)
return key_list;
for (n = dn->children; n; n = n->next)
len++;
key_list->_buffer = CORBA_sequence_CORBA_string_allocbuf (len);
CORBA_sequence_set_release (key_list, TRUE);
for (n = dn->children; n; n = n->next) {
key_list->_buffer [key_list->_length] =
CORBA_string_dup (n->data);
key_list->_length++;
}
return key_list;
}
static Bonobo_KeyList *
real_list_keys (BonoboConfigDatabase *db,
const CORBA_char *dir,
CORBA_Environment *ev)
{
BonoboConfigDIRDB *dirdb = BONOBO_CONFIG_DIRDB (db);
Bonobo_KeyList *key_list;
DirData *dd;
char *ndir;
DirEntry *de;
GSList *l;
int len;
if (!(ndir = normalize_key (dir)) ||
!(dd = dcache_lookup_dir (dirdb->priv, ndir, FALSE))) {
g_free (ndir);
bonobo_exception_set (ev, ex_Bonobo_ConfigDatabase_NotFound);
return NULL;
}
g_free (ndir);
key_list = Bonobo_KeyList__alloc ();
if (!(len = g_slist_length (dd->entries)))
return key_list;
key_list->_buffer = CORBA_sequence_CORBA_string_allocbuf (len);
CORBA_sequence_set_release (key_list, TRUE);
for (l = dd->entries; l ; l = l->next) {
de = (DirEntry *)l->data;
key_list->_buffer [key_list->_length] =
CORBA_string_dup (de->name);
key_list->_length++;
}
return key_list;
}
static CORBA_boolean
real_dir_exists (BonoboConfigDatabase *db,
const CORBA_char *dir,
CORBA_Environment *ev)
{
BonoboConfigDIRDB *dirdb = BONOBO_CONFIG_DIRDB (db);
char *ndir;
GNode *n;
CORBA_boolean rval = FALSE;
if ((ndir = normalize_key (dir)) &&
(n = dtree_lookup_dir (dirdb->priv->dtree, ndir)))
rval = TRUE;
g_free (ndir);
return rval;
}
static void
real_remove_value (BonoboConfigDatabase *db,
const CORBA_char *key,
CORBA_Environment *ev)
{
BonoboConfigDIRDB *dirdb = BONOBO_CONFIG_DIRDB (db);
DirEntry *de;
char *ckey;
if (!(ckey = normalize_key (key)) ||
!(de = dcache_lookup_entry (dirdb->priv, ckey, FALSE))) {
g_free (ckey);
return;
}
g_free (ckey);
de->dir->entries = g_slist_remove (de->dir->entries, de);
de->dir->dirty = TRUE;
dcache_free_entry (de);
return;
}
static void
real_remove_dir (BonoboConfigDatabase *db,
const CORBA_char *dir,
CORBA_Environment *ev)
{
BonoboConfigDIRDB *dirdb = BONOBO_CONFIG_DIRDB (db);
char *path, *ndir;
if ((ndir = normalize_key (dir))) {
dcache_remove_dir (dirdb->priv, ndir);
dtree_remove_dir (dirdb->priv->dtree, ndir);
path = dir_to_path (dirdb->priv->basedir, ndir);
unlink (path);
g_free (path);
g_free (ndir);
}
}
static void
bonobo_config_dirdb_destroy (GtkObject *object)
{
BonoboConfigDIRDB *dirdb = BONOBO_CONFIG_DIRDB (object);
BonoboConfigDIRDBPriv *priv = dirdb->priv;
CORBA_Environment ev;
if (!priv)
return;
dirdb->priv = NULL;
CORBA_exception_init (&ev);
bonobo_url_unregister ("BONOBO_CONF:XLMDIRDB", priv->basedir, &ev);
CORBA_exception_free (&ev);
dcache_free_all (priv);
dtree_destroy (priv->dtree);
g_free (priv->basedir);
g_free (priv);
parent_class->destroy (object);
}
static void
bonobo_config_dirdb_class_init (BonoboConfigDatabaseClass *class)
{
GtkObjectClass *object_class = (GtkObjectClass *) class;
BonoboConfigDatabaseClass *cd_class;
parent_class = gtk_type_class (PARENT_TYPE);
object_class->destroy = bonobo_config_dirdb_destroy;
cd_class = BONOBO_CONFIG_DATABASE_CLASS (class);
cd_class->get_value = real_get_value;
cd_class->set_value = real_set_value;
cd_class->list_dirs = real_list_dirs;
cd_class->list_keys = real_list_keys;
cd_class->dir_exists = real_dir_exists;
cd_class->remove_value = real_remove_value;
cd_class->remove_dir = real_remove_dir;
cd_class->sync = real_sync;
}
static void
bonobo_config_dirdb_init (BonoboConfigDIRDB *dirdb)
{
dirdb->priv = g_new0 (BonoboConfigDIRDBPriv, 1);
}
BONOBO_X_TYPE_FUNC (BonoboConfigDIRDB, PARENT_TYPE, bonobo_config_dirdb);
Bonobo_ConfigDatabase
bonobo_config_dirdb_new (const char *basedir)
{
BonoboConfigDIRDB *dirdb;
Bonobo_ConfigDatabase db;
struct stat fs;
CORBA_Environment ev;
char *real_name;
int l;
g_return_val_if_fail (basedir != NULL, NULL);
if (basedir [0] == '~' && basedir [1] == '/')
real_name = g_strconcat (g_get_home_dir (), &basedir [1],
NULL);
else
real_name = g_strdup (basedir);
while ((l = strlen (real_name)) && real_name [l] == '/')
real_name [l] = '\0';
CORBA_exception_init (&ev);
db = bonobo_url_lookup ("BONOBO_CONF:XLMDIRDB", real_name, &ev);
if (BONOBO_EX (&ev)) {
CORBA_exception_init (&ev);
db = CORBA_OBJECT_NIL;
}
if (db) {
g_free (real_name);
CORBA_exception_free (&ev);
return bonobo_object_dup_ref (db, NULL);
}
mkdir (real_name, 0755);
if (stat (real_name, &fs) || !S_ISDIR (fs.st_mode)) {
g_free (real_name);
CORBA_exception_free (&ev);
return CORBA_OBJECT_NIL;
}
if (!(dirdb = gtk_type_new (BONOBO_CONFIG_DIRDB_TYPE))) {
g_free (real_name);
CORBA_exception_free (&ev);
return CORBA_OBJECT_NIL;
}
dirdb->priv->basedir = real_name;
dirdb->priv->dtree = dtree_create (real_name);
if (fs.st_uid == getuid () && (fs.st_mode & S_IWUSR))
BONOBO_CONFIG_DATABASE (dirdb)->writeable = TRUE;
else
BONOBO_CONFIG_DATABASE (dirdb)->writeable = FALSE;
dirdb->priv->es = bonobo_event_source_new ();
bonobo_object_add_interface (BONOBO_OBJECT (dirdb),
BONOBO_OBJECT (dirdb->priv->es));
db = CORBA_Object_duplicate (BONOBO_OBJREF (dirdb), NULL);
bonobo_url_register ("BONOBO_CONF:XLMDIRDB", real_name, NULL, db, &ev);
CORBA_exception_free (&ev);
return db;
}
syntax highlighted by Code2HTML, v. 0.9.1