Below is the file 'sqlite-backend.c' from this revision. You can also download the file.

/* GConf sqlite backend
 * Copyright (C) 1999, 2000, 2002 Red Hat Inc.
 * Copyright (C) 2005 Grahame Bowland.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/*
 * designed for correctness, speed. uses sqlite3 for local storage.
 */

#include <gconf/gconf-backend.h>
#include <gconf/gconf-internals.h>
#include <gconf/gconf.h>
#include <glib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sqlite3.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pwd.h>

#define ROOT_DIRECTORY_PARENT			-1
#define ROOT_DIRECTORY_NAME			""

/* used for columns, they indicate which gconf type we're actually storing */
#define T_STRING				"s"
#define T_INT					"i"
#define T_FLOAT					"f"
#define T_BOOL					"b"
#define T_SCHEMA				"c"
#define T_LIST					"l"
#define T_PAIR					"p"

/*
 * VTable functions
 */

 /* shutdown() is a BSD libc function */
static void           x_shutdown      (GError           **err);
static GConfSource*   resolve_address (const char        *address,
                                       GError           **err);
static void           lock            (GConfSource       *source,
                                       GError           **err);
static void           unlock          (GConfSource       *source,
                                       GError           **err);
static gboolean       readable        (GConfSource       *source,
                                       const char        *key,
                                       GError           **err);
static gboolean       writable        (GConfSource       *source,
                                       const char        *key,
                                       GError           **err);
static GConfValue*    query_value     (GConfSource       *source,
                                       const char        *key,
                                       const char       **locales,
                                       char             **schema_name,
                                       GError           **err);
static GConfMetaInfo* query_metainfo  (GConfSource       *source,
                                       const char        *key,
                                       GError           **err);
static void           set_value       (GConfSource       *source,
                                       const char        *key,
                                       const GConfValue  *value,
                                       GError           **err);
static GSList*        all_entries     (GConfSource       *source,
                                       const char        *dir,
                                       const char       **locales,
                                       GError           **err);
static GSList*        all_subdirs     (GConfSource       *source,
                                       const char        *dir,
                                       GError           **err);
static void           unset_value     (GConfSource       *source,
                                       const char        *key,
                                       const char        *locale,
                                       GError           **err);
static gboolean       dir_exists      (GConfSource       *source,
                                       const char        *dir,
                                       GError           **err);
static void           remove_dir      (GConfSource       *source,
                                       const char        *dir,
                                       GError           **err);
static void           set_schema      (GConfSource       *source,
                                       const char        *key,
                                       const char        *schema_key,
                                       GError           **err);
static gboolean       sync_all        (GConfSource       *source,
                                       GError           **err);
static void           destroy_source  (GConfSource       *source);
static void           clear_cache     (GConfSource       *source);
static void           blow_away_locks (const char        *address);

typedef struct
{
	long long int id;
} SqliteDirectory;

typedef struct
{
	GConfSource source; /* inherit from GConfSource */
	gchar *dbfile;
	GConfLock* lock;
	sqlite3 *db;
	SqliteDirectory root_dir;
} SqliteSource;

/* to be used whenever appropriate (stock error message) */
static void
update_failure (GError **err)
{
	return; // TODO: don't leave this commented out!
	g_set_error (err, GCONF_ERROR,
			GCONF_ERROR_FAILED,
			_("Failed to write some configuration data to disk\n"));
}

/* returns TRUE if the directory exists; in this case dir will be filled in with the
 * details of the directory.
 *
 * returns FALSE otherwise
 */
static gboolean
get_directory (SqliteSource *ss, SqliteDirectory *parent, const char *name, SqliteDirectory *dir)
{
	long long int id;
	char *query, *tail;
	gboolean rv;
	long long int nrow;
	int res;
	sqlite3_stmt *statement;

	/* normal sub-directory case */
	g_return_val_if_fail (parent != NULL, FALSE);
	g_return_val_if_fail (name != NULL, FALSE);

	/* NB: there should only be one row in the result (especially with the table constraints set */
	query = sqlite3_mprintf ("SELECT id FROM directories WHERE parent_id=%lld AND name='%s'", dir->id, name);

	if (sqlite3_prepare (ss->db, query, strlen(query), &statement, (const char **)&tail) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(get_directory) Unable to compile query: %s", query);
		sqlite3_free (query);
		return FALSE;
	}

	nrow = 0;
	rv = TRUE;

	for (;;) {
		res = sqlite3_step (statement);
		if (res == SQLITE_ROW) {
			nrow++;
			id = sqlite3_column_int64(statement, 0);
		} else if (res == SQLITE_DONE) {
			break;
		} else {
			/* ought to be one of:
				SQLITE_BUSY || res == SQLITE_ERROR || res == SQLITE_MISUSE
			*/
			gconf_log (GCL_ERR, "(get_directory) query error\n");
			rv = FALSE;
			break;
		}
	}

	if (nrow != 1)
		rv = FALSE;

	if (rv)
		dir->id = id;

	sqlite3_finalize (statement);
	sqlite3_free (query);

	return rv;
}

static gboolean
create_directory (SqliteSource *ss, SqliteDirectory *parent, const char *name, SqliteDirectory *dir)
{
	char *query, *err_mesg;
	gboolean rv;

	/* normal subdirectory */
	g_return_val_if_fail (parent != NULL, FALSE);
	g_return_val_if_fail (name != NULL, FALSE);

	query = sqlite3_mprintf ("INSERT INTO directories (name,parent_id) VALUES ('%s',%d)", name, parent->id);

	if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(create_directory) SQL error: %s", err_mesg);
		sqlite3_free (err_mesg);
		rv = FALSE;
	} else {
		dir->id = sqlite3_last_insert_rowid (ss->db);
		rv = TRUE;
	}

	sqlite3_free (query);

	return rv;
}

/* make sure we have a root directory in the directories table */
static gboolean
ensure_has_root_directory (SqliteSource *ss)
{
	gboolean rv;

	ss->root_dir.id = -1;
	if (!get_directory (ss, &ss->root_dir, ROOT_DIRECTORY_NAME, &ss->root_dir)) {
		gconf_log (GCL_DEBUG, "(ensure_has_root_directory) no root directory\n");
		/* okay then, better create it */
		rv = create_directory (ss, &ss->root_dir, ROOT_DIRECTORY_NAME, &ss->root_dir);
	} else {
		rv = TRUE;
	}
	return rv;
}

static SqliteDirectory *
traverse_to_directory (SqliteSource *ss, const char *path, gboolean do_create, GError **error)
{
	SqliteDirectory *rv;
	char **components;
	int i;

	g_assert (ss != NULL);
	g_assert (path != NULL);

	g_return_val_if_fail (*path == '/', NULL);

	rv = g_new0 (SqliteDirectory, 1);
	components = g_strsplit (path + 1, "/", -1);

	/* we know that we have a root directory entry, so start there */
	rv->id = ss->root_dir.id;

	if (!components) {
		/* they are just after the root directory */
		return rv;
	}

	/* loop, finding and then creating directories as needed */
	i = 0;
	while (rv && components[i]) {
		if (!get_directory (ss, rv, components[i], rv)) {
			/* not there, so create it.. */
			if (!do_create || !create_directory (ss, rv, components[i], rv)) {
				g_free (rv);
				rv = NULL;
				break;
			}
		}
		i++;
	}

	g_strfreev (components);
	return rv;
}

static char *
get_username (void)
{
	uid_t uid;
	struct passwd *pw;

	uid = getuid();
	if (uid < 0)
		return NULL;
	pw = getpwuid(uid);
	if (!pw)
		return NULL;
	return g_strdup(pw->pw_name);
}

static gboolean
start_transaction (SqliteSource *ss)
{
	gboolean rv;
	char *query, *err_mesg;

	rv = TRUE;
	query = "BEGIN";
	if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(start_transaction) SQL error: %s", err_mesg);
		sqlite3_free (err_mesg);
		rv = FALSE;
	}
	return rv;
}

static gboolean
abort_transaction (SqliteSource *ss)
{
	gboolean rv;
	char *query, *err_mesg;

	rv = TRUE;
	query = "ROLLBACK";
	if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(abort_transaction) SQL error: %s", err_mesg);
		sqlite3_free (err_mesg);
		rv = FALSE;
	}
	return rv;
}

static gboolean
commit_transaction (SqliteSource *ss)
{
	gboolean rv;
	char *query, *err_mesg;

	rv = TRUE;
	query = "COMMIT";
	if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(commit_transaction) SQL error: %s", err_mesg);
		sqlite3_free (err_mesg);
		rv = FALSE;
	}
	return rv;
}

static gboolean
get_value_id_in_directory (SqliteSource *ss, SqliteDirectory *dir, const char *name, long long int *id)
{
	long long int possible_id, nrow;
	char *query, *tail;
	gboolean rv;
	int res;
	sqlite3_stmt *statement;

	g_return_val_if_fail (ss != NULL, FALSE);
	g_return_val_if_fail (dir != NULL, FALSE);
	g_return_val_if_fail (name != NULL, FALSE);

	query = sqlite3_mprintf ("SELECT id FROM keys WHERE directory_id=%lld AND name='%q'", dir->id, name);
	if (sqlite3_prepare (ss->db, query, strlen(query), &statement, (const char **)&tail) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(get_value_id_in_directory) Unable to compile query: %s", query);
		sqlite3_free (query);
		return FALSE;
	}

	nrow = 0;
	rv = TRUE;

	for (;;) {
		res = sqlite3_step (statement);
		if (res == SQLITE_ROW) {
			nrow++;
			possible_id = sqlite3_column_int64 (statement, 0);
		} else if (res == SQLITE_DONE) {
			break;
		} else {
			/* ought to be one of:
				SQLITE_BUSY || res == SQLITE_ERROR || res == SQLITE_MISUSE
			*/
			gconf_log (GCL_ERR, "(get_directory) query error\n");
			break;
		}
	}

	if (nrow != 1)
		rv = FALSE;

	if (rv)
		*id = possible_id;

	sqlite3_free (query);
	sqlite3_finalize (statement);
	return rv;
}

static gboolean
delete_old_list (SqliteSource *ss, long long int id)
{
	gboolean rv;
	char *query, *err_mesg;

	rv = TRUE;
	query = sqlite3_mprintf ("DELETE FROM lists WHERE key_id=%lld", id);
	if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(delete_old_list) SQL error: %s", err_mesg);
		sqlite3_free (err_mesg);
		rv = FALSE;
	}

	sqlite3_free (query);
	return rv;
}

static gboolean
delete_old_pair (SqliteSource *ss, long long int id)
{
	gboolean rv;
	char *query, *err_mesg;

	rv = TRUE;
	query = sqlite3_mprintf ("DELETE FROM pairs WHERE key_id=%lld", id);
	if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(delete_old_pair) SQL error: %s", err_mesg);
		sqlite3_free (err_mesg);
		rv = FALSE;
	}

	sqlite3_free (query);
	return rv;
}

static const char *
get_type_for_column (GConfValueType t)
{
	const char *lt;

	if (t == GCONF_VALUE_STRING) {
		lt = T_STRING;
	} else if (t == GCONF_VALUE_INT) {
		lt = T_INT;
	} else if (t == GCONF_VALUE_FLOAT) {
		lt = T_FLOAT;
	} else if (t == GCONF_VALUE_BOOL) {
		lt = T_BOOL;
	} else {
		lt = NULL;
	}

	return lt;
}

static gboolean
insert_or_update_value (SqliteSource *ss, SqliteDirectory *dir, const char *name, const GConfValue *value)
{
	gchar *mod_user;
	GTime mod_time;
	gboolean rv;
	GConfValueType list_type;
	char *query, *err_mesg;
	const char *lt;

	g_return_val_if_fail (ss != NULL, FALSE);
	g_return_val_if_fail (dir != NULL, FALSE);
	g_return_val_if_fail (name != NULL, FALSE);

	/* we want to update these at the same time */
	mod_user = get_username ();
	mod_time = time (NULL);

#define BASIC_INSERT	"INSERT OR REPLACE INTO keys (name, directory_id, mod_user, mod_time, value_type, value) VALUES "

	rv = TRUE;
	query = NULL;
	switch (value->type) {
		case GCONF_VALUE_STRING:
			query = sqlite3_mprintf (BASIC_INSERT "('%q', %lld, '%q', %d, '%q', '%q')",
					name, dir->id, mod_user, mod_time, T_STRING, gconf_value_get_string (value));
			break;
		case GCONF_VALUE_INT:
			query = sqlite3_mprintf (BASIC_INSERT "('%q', %lld, '%q', %d, '%q', %d)",
					name, dir->id, mod_user, mod_time, T_INT, gconf_value_get_int (value));
			break;
		case GCONF_VALUE_FLOAT:
			query = sqlite3_mprintf (BASIC_INSERT "('%q', %lld, '%q', %d, '%q', %f)",
					name, dir->id, mod_user, mod_time, T_FLOAT, gconf_value_get_float (value));
			break;
		case GCONF_VALUE_BOOL:
			query = sqlite3_mprintf (BASIC_INSERT "('%q', %lld, '%q', %d, '%q', %d)",
					name, dir->id, mod_user, mod_time, T_BOOL, gconf_value_get_bool (value));
			break;
		case GCONF_VALUE_SCHEMA:
			query = sqlite3_mprintf (BASIC_INSERT "('%q', %lld, '%q', %d, '%q', NULL)",
					name, dir->id, mod_user, mod_time, T_SCHEMA);
			break;
		case GCONF_VALUE_LIST:
			list_type = gconf_value_get_list_type (value);
			lt = get_type_for_column (list_type);
			if (!lt) {
				gconf_log (GCL_ERR, "(insert_or_update_value) List is of invalid type.");
				rv = FALSE;
				break;
			}
			query = sqlite3_mprintf (BASIC_INSERT "('%q', %lld, '%q', %d, '%q', '%q')",
					name, dir->id, mod_user, mod_time, T_LIST, lt);
			break;
		case GCONF_VALUE_PAIR:
			query = sqlite3_mprintf (BASIC_INSERT "('%q', %lld, '%q', %d, '%q', NULL)",
					name, dir->id, mod_user, mod_time, T_PAIR);
			break;
		case GCONF_VALUE_INVALID:
		default:
			gconf_log (GCL_ERR, "(insert_or_update_value) Attempt to insert item with invalid type.");
			rv = FALSE;
			break;
	}

	if (!rv) {
		if (query)
			sqlite3_free (query);
		return rv;
	}

	/* okay, so we have a query that does what we want. execute it. */
	if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(insert_or_update_value) SQL error: %s", err_mesg);
		sqlite3_free (err_mesg);
		rv = FALSE;
	}

	sqlite3_free (query);

	return rv;
}

#define BASIC_LIST_INSERT	"INSERT INTO lists (key_id, list_idx, value) "

static inline char *
list_sql_string (long long int key, long long int idx, const void *v)
{
	return sqlite3_mprintf (BASIC_LIST_INSERT "VALUES (%lld, %lld, '%q')", key, idx, *(const char *)v);
}

static inline char *
list_sql_int (long long int key, long long int idx, const void *v)
{
	return sqlite3_mprintf (BASIC_LIST_INSERT "VALUES (%lld, %lld, %d)", key, idx, *(const int *)v);
}

static inline char *
list_sql_float (long long int key, long long int idx, const void *v)
{
	return sqlite3_mprintf (BASIC_LIST_INSERT "VALUES (%lld, %lld, %f)", key, idx, *(const double *)v);
}

static inline char *
list_sql_bool (long long int key, long long int idx, const void *v)
{
	return sqlite3_mprintf (BASIC_LIST_INSERT "VALUES (%lld, %lld, %d)", key, idx, *(const gboolean *)v);
}

static gboolean
insert_list_for_key (SqliteSource *ss, const GConfValue *value, long long int key)
{
	GConfValueType list_type;
	long long int idx;
	GSList *list;
	gboolean rv;
	char *query;
	char *err_mesg;
	char *(*list_sql_func)(long long int, long long int, const void *);

	g_return_val_if_fail (ss != NULL, FALSE);
	g_return_val_if_fail (value != NULL, FALSE);

	list_type = gconf_value_get_list_type (value);
	/* overly paranoid perhaps? */
	g_return_val_if_fail (get_type_for_column (list_type) != NULL, FALSE);

	switch (list_type) {
		case GCONF_VALUE_STRING: list_sql_func = list_sql_string; break;
		case GCONF_VALUE_INT: list_sql_func = list_sql_int; break;
		case GCONF_VALUE_FLOAT: list_sql_func = list_sql_float; break;
		case GCONF_VALUE_BOOL: list_sql_func = list_sql_bool; break;
		default: list_sql_func= NULL;
	}
	g_return_val_if_fail (list_sql_func != NULL, FALSE);

	rv = TRUE;
	idx = 0;
	list = gconf_value_get_list (value);
	while (list) {
		query = list_sql_func (key, idx, list->data);

		if (sqlite3_exec (ss->db, query, NULL, NULL, &err_mesg) != SQLITE_OK) {
			gconf_log (GCL_ERR, "(insert_list_for_key) SQL error: %s", err_mesg);
			sqlite3_free (query);
			sqlite3_free (err_mesg);
			rv = FALSE;
			break;
		}

		sqlite3_free (query);
		list = g_slist_next (list);
		idx++;
	}

	return rv;
}

static gboolean
insert_pair_for_key (SqliteSource *ss, const GConfValue *value, long long int key)
{
	g_return_val_if_fail (ss != NULL, FALSE);
	g_return_val_if_fail (value != NULL, FALSE);

	return FALSE;
}

static gboolean
insert_schema_for_key (SqliteSource *ss, const GConfValue *value, long long int key)
{
	g_return_val_if_fail (ss != NULL, FALSE);
	g_return_val_if_fail (value != NULL, FALSE);

	return FALSE;
}

static gboolean
set_value_in_directory (SqliteSource *ss, SqliteDirectory *dir, const char *name, const GConfValue *value, GError **error)
{
	char *query = NULL;
	gboolean rv;
	long long int existing_id;

	g_return_val_if_fail (ss != NULL, FALSE);
	g_return_val_if_fail (dir != NULL, FALSE);
	g_return_val_if_fail (name != NULL, FALSE);

	/*
	 * we need to handle several things here
	 *
	 * first consideration; this might be a new key, or it might be an update to an existing key
	 * if we're updating, we need to handle non-scalar types correctly. this basically means removing
	 * the old list / pair and then adding a new one in.
	 *
	 * second consideration; need to do clean insert of non-scalar types.
	 *
	 * third consideration; make sure we do it all in a transaction block, and abort the transaction
	 * appropriately if something goes wrong.
	 */

	rv = TRUE;

	do {
		if (!start_transaction (ss)) {
			rv = FALSE;
			break;
		}
		/* if there was a value in the past, clean up any possible
		 * lists or pair. This could be more elegant, but this way is
		 * the best; it's slower (eg. might do a pair of DELETEs we don't need)
		 * but it does mean that there's no chance of us leaving a
		 * pair or list dangling by mistake
		 *
		 * Note: old schema values are fine
		 * (they are in the key_to_schema_name table)
		 */
		if (get_value_id_in_directory (ss, dir, name, &existing_id)) {
			if (!delete_old_list (ss, existing_id)) {
				rv = FALSE;
				break;
			}
			if (!delete_old_pair (ss, existing_id)) {
				rv = FALSE;
				break;
			}
		}
		/* whatever type we're inserting, we need to have a row in the keys
		 * table. if we're inserting a GCONF_VALUE_LIST,  GCONF_VALUE_PAIR,
		 * or GCONF_VALUE_SCHEMA another row in a seperate table will be
		 * needed. this row will reference the row in keys.
		 */
		if (!insert_or_update_value (ss, dir, name, value)) {
			rv = FALSE;
			break;
		}
		if (value->type == GCONF_VALUE_LIST) {
			long long int last_rowid;
			last_rowid = sqlite3_last_insert_rowid (ss->db);
			if (!insert_list_for_key (ss, value, last_rowid)) {
				rv = FALSE;
				break;
			}
		} else if (value->type == GCONF_VALUE_PAIR) {
			long long int last_rowid;
			last_rowid = sqlite3_last_insert_rowid (ss->db);
			if (!insert_pair_for_key (ss, value, last_rowid)) {
				rv = FALSE;
				break;
			}
		} else if (value->type == GCONF_VALUE_SCHEMA) {
			long long int last_rowid;
			last_rowid = sqlite3_last_insert_rowid (ss->db);
			if (!insert_schema_for_key (ss, value, last_rowid)) {
				rv = FALSE;
				break;
			}
		}
	} while (0);

	if (rv) {
		commit_transaction (ss);
	} else {
		abort_transaction (ss);
	}


	if (!query)
		return FALSE;

	printf("query is: %s\n", query);

	sqlite3_free (query);

	return FALSE;
}

/* check that the database looks about right */
static gboolean
verify_database (SqliteSource *ss)
{
	char *query, *tail;
	gboolean rv;
	long long int nrow;
	int res;
	sqlite3_stmt *statement;

	g_return_val_if_fail (ss != NULL, FALSE);

	query = sqlite3_mprintf ("SELECT * FROM gconf");
	if (sqlite3_prepare (ss->db, query, strlen(query), &statement, (const char **)&tail) != SQLITE_OK) {
		gconf_log (GCL_ERR, "(verify_database) Unable to compile query: %s", query);
		sqlite3_free (query);
		return FALSE;
	}

	nrow = 0;
	rv = TRUE;
	for (;;) {
		res = sqlite3_step (statement);
		if (res == SQLITE_ROW) {
			nrow++;
			if (sqlite3_column_int64(statement, 0) != 1)
				rv = FALSE;
		} else if (res == SQLITE_DONE) {
			break;
		} else {
			/* ought to be one of:
				SQLITE_BUSY || res == SQLITE_ERROR || res == SQLITE_MISUSE
			*/
			gconf_log (GCL_ERR, "(verify_database) query error\n");
			break;
		}
	}

	if (nrow != 1)
		rv = FALSE;

	sqlite3_finalize (statement);
	sqlite3_free (query);

	return rv;
}

/* create a new SqliteSource, if possible.
 * otherwise, return NULL
 */
static SqliteSource *
ss_new (const char *dbfile, GConfLock *lock)
{
	SqliteSource *ss;
	mode_t m;
	int rv;

	g_return_val_if_fail (dbfile != NULL, NULL);
	ss = g_new0 (SqliteSource, 1);
	ss->dbfile = g_strdup (dbfile);
	ss->lock = lock;
	/* if the database does not exist, sqlite will create it. we must make sure
	 * it is created in with secure permissions
	 */
	m = umask(077);
	rv = sqlite3_open (ss->dbfile, &ss->db);
	umask(m);
	if (rv) {
		gconf_log (GCL_ERR, _("Couldn't open database \"%s\": %s\n"), ss->dbfile, sqlite3_errmsg(ss->db));
		sqlite3_close (ss->db);
		g_free (ss->dbfile);
		g_free (ss);
		return NULL;
	}
	if (!verify_database(ss)) {
		return NULL;
	}
	if (!ensure_has_root_directory (ss)) {
		return NULL;
	}
	return ss;
}

static void
ss_destroy (SqliteSource *ss)
{
	GError* error = NULL;
	g_return_if_fail (ss != NULL);

	if (ss->lock != NULL && !gconf_release_lock (ss->lock, &error)) {
		gconf_log (GCL_ERR, _("Failed to give up lock on database \"%s\" : %s"),
				ss->dbfile, error->message);
		g_error_free (error);
		error = NULL;
	}

	sqlite3_close (ss->db);
	g_free (ss->dbfile);
	g_free (ss);
}

/* public methods are all below this line */

static GConfBackendVTable markup_vtable = {
  sizeof (GConfBackendVTable),
  x_shutdown,
  resolve_address,
  lock,
  unlock,
  readable,
  writable,
  query_value,
  query_metainfo,
  set_value,
  all_entries,
  all_subdirs,
  unset_value,
  dir_exists,
  remove_dir,
  set_schema,
  sync_all,
  destroy_source,
  clear_cache,
  blow_away_locks,
  NULL, /* set_notify_func */
  NULL, /* add_listener    */
  NULL  /* remove_listener */
};

static void
x_shutdown (GError **err)
{
  gconf_log (GCL_DEBUG, _("Unloading sqlite backend module."));
}

/* possible problem here; according to the SQLite documentation,
 * the library is threadsafe (except for sqlite3_global_recover)
 * but it is a bug to use a sqlite3* in a thread other than that
 * in which it was created
 *
 * not sure if that's going to be a problem - I doubt it though.
 */
static void
lock (GConfSource *source,
      GError **err)
{

}

static void
unlock (GConfSource *source,
        GError **err)
{


}

static gboolean
readable (GConfSource *source,
          const char  *key,
          GError     **err)
{
  return TRUE;
}

static gboolean
writable (GConfSource  *source,
          const char   *key,
          GError      **err)
{
  return TRUE;
}

static GConfValue*
query_value (GConfSource *source,
             const char  *key,
             const char **locales,
             char       **schema_name,
             GError     **err)
{
	gconf_log (GCL_DEBUG, "query_value: %s", key);

	return NULL;
}

static GConfMetaInfo*
query_metainfo (GConfSource *source,
                const char  *key,
                GError     **err)
{
	gconf_log (GCL_DEBUG, "query_metainfo: %s", key);

	return NULL;
}

static void
set_value (GConfSource      *source,
           const char       *key,
           const GConfValue *value,
           GError          **err)
{
	const char *relative_key;
	char *parent;
	SqliteDirectory *dir;

	gconf_log (GCL_DEBUG, "set_value: %s", key);

	parent = gconf_key_directory (key);
	relative_key = gconf_key_key (key);
	dir = traverse_to_directory ((SqliteSource *)source, parent, TRUE, err);
	do {
		if (!dir) {
			update_failure (err);
			break;
		}
		if (!set_value_in_directory ((SqliteSource *)source, dir, relative_key, value, err)) {
			update_failure (err);
			break;
		}
	} while (0);

	g_free (parent);
	g_free (dir);
	g_assert (parent != NULL);
}

/* all entries in the directory pointed to by key */
static GSList*
all_entries (GConfSource *source,
             const char  *key,
             const char **locales,
             GError     **err)
{
	gconf_log (GCL_DEBUG, "all_entries: %s", key);

	return NULL;
}

/* all subdirectories of the directory pointed to by key */
static GSList*
all_subdirs (GConfSource *source,
             const char  *key,
             GError     **err)
{
	gconf_log (GCL_DEBUG, "all_subdirs: %s", key);

	return NULL;
}

static void
unset_value (GConfSource *source,
             const char  *key,
             const char  *locale,
             GError     **err)
{
	gconf_log (GCL_DEBUG, "unset_value: %s", key);

}

static gboolean
dir_exists (GConfSource *source,
            const char  *key,
            GError     **err)
{
	gconf_log (GCL_DEBUG, "dir_exists: %s", key);

	return FALSE;
}

static void
remove_dir (GConfSource *source,
            const char  *key,
            GError     **err)
{
	gconf_log (GCL_DEBUG, "remove_dir: %s", key);

}

static void
set_schema (GConfSource *source,
            const char  *key,
            const char  *schema_name,
            GError     **err)
{
	gconf_log (GCL_DEBUG, "set_schema: %s", key);

}

static gboolean
sync_all (GConfSource *source,
          GError     **err)
{

	return FALSE;
}

static void
destroy_source (GConfSource *source)
{
	ss_destroy ((SqliteSource *)source);
}

static void
clear_cache (GConfSource *source)
{

}

static void
blow_away_locks (const char *address)
{

}

static GConfSource*
resolve_address (const char *address,
                 GError    **err)
{
	SqliteSource *new_source;
	GConfLock *lock;
	char *dbfile;

	gconf_log (GCL_DEBUG, "sqlite module initialising, address=%s", address);

	dbfile = gconf_address_resource (address);
	if (!dbfile)
		return NULL;

	/* for now, let's ignore locking; sqlite does it anyway
	 * and probably better than we'd manage
	 */
	lock = NULL;

	new_source = ss_new (dbfile, lock);
	if (!new_source) {
		return NULL;
	}
	((GConfSource *)new_source)->flags = 0;
	gconf_log (GCL_ERR, "sqlite dbfile at: %s\n", dbfile);

	g_free (dbfile);

	return (GConfSource *)new_source;
}

/**************** EXPORT *********************/

/* Initializer */

G_MODULE_EXPORT const char*
g_module_check_init (GModule *module)
{
  gconf_log (GCL_DEBUG, _("Initializing Markup backend module"));

  return NULL;
}

G_MODULE_EXPORT GConfBackendVTable*
gconf_backend_get_vtable (void)
{
  return &markup_vtable;
}