[couchdb-glib] Added changes notifications:
- From: Rodrigo Moya <rodrigo src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [couchdb-glib] Added changes notifications:
- Date: Mon, 31 Aug 2009 15:49:11 +0000 (UTC)
commit c785237017f9fd152e85f6e13d42c23adeffe2f4
Author: Rodrigo Moya <rodrigo gnome-db org>
Date: Mon Aug 31 17:48:46 2009 +0200
Added changes notifications:
* Generate marshallers for CouchDB signals
* Emit modification signals when doing changes to dbs/documents
* Added couchdb_listen_for_changes API
* Added DBWatch object to do the actual watching of changes
* Added tests for modification API and change signals
configure.ac | 4 +
couchdb-glib/Makefile.am | 16 ++++-
couchdb-glib/couchdb-document.c | 15 +++-
couchdb-glib/couchdb-glib.h | 12 +++-
couchdb-glib/couchdb-marshal.list | 2 +
couchdb-glib/couchdb.c | 114 ++++++++++++++++++++++++++++++
couchdb-glib/dbwatch.c | 137 +++++++++++++++++++++++++++++++++++++
couchdb-glib/dbwatch.h | 37 ++++++++++
couchdb-glib/utils.h | 2 +
tests/test-couchdb-glib.c | 87 +++++++++++++++++++++++-
10 files changed, 419 insertions(+), 7 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 79de145..b00bfca 100644
--- a/configure.ac
+++ b/configure.ac
@@ -15,6 +15,10 @@ AC_DISABLE_STATIC
AC_PROG_CC
AC_PROG_LIBTOOL
+dnl glib-genmarshal
+AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal)
+
+dnl Look for needed modules
PKG_CHECK_MODULES(COUCHDB_GLIB, glib-2.0 gobject-2.0 json-glib-1.0 >= 0.7.4 libsoup-2.4 libsoup-gnome-2.4 uuid)
AC_SUBST(COUCHDB_GLIB_CFLAGS)
AC_SUBST(COUCHDB_GLIB_LIBS)
diff --git a/couchdb-glib/Makefile.am b/couchdb-glib/Makefile.am
index f16616f..8fc2209 100644
--- a/couchdb-glib/Makefile.am
+++ b/couchdb-glib/Makefile.am
@@ -3,11 +3,23 @@ INCLUDES = \
lib_LTLIBRARIES = libcouchdb-glib-1.0.la
+# Marshallers
+MARSHAL_GENERATED = couchdb-marshal.c couchdb-marshal.h
+
+couchdb-marshal.h: couchdb-marshal.list $(GLIB_GENMARSHAL)
+ $(GLIB_GENMARSHAL) $< --header --prefix=_couchdb_marshal > $@
+
+couchdb-marshal.c: couchdb-marshal.list $(GLIB_GENMARSHAL)
+ $(GLIB_GENMARSHAL) $< --body --prefix=_couchdb_marshal > $@
+
libcouchdb_glib_1_0_la_SOURCES = \
+ $(MARSHAL_GENERATED) \
couchdb.c \
couchdb-document.c \
couchdb-document-contact.c \
couchdb-types.c \
+ dbwatch.c \
+ dbwatch.h \
utils.c \
utils.h
libcouchdb_glib_1_0_la_LIBADD = \
@@ -22,4 +34,6 @@ h_DATA = \
couchdb-document-contact.h \
couchdb-types.h
-EXTRA_DIST = $(h_DATA)
+EXTRA_DIST = $(h_DATA) $(MARSHAL_GENERATED)
+BUILT_SOURCES = $(MARSHAL_GENERATED)
+CLEANFILES = $(BUILT_SOURCES)
diff --git a/couchdb-glib/couchdb-document.c b/couchdb-glib/couchdb-document.c
index e5dbd36..ffef220 100644
--- a/couchdb-glib/couchdb-document.c
+++ b/couchdb-glib/couchdb-document.c
@@ -137,6 +137,11 @@ couchdb_document_put (CouchDBDocument *document,
document->dbname = g_strdup (dbname);
}
+ if (id)
+ g_signal_emit_by_name (document->couchdb, "document_updated", dbname, document);
+ else
+ g_signal_emit_by_name (document->couchdb, "document_created", dbname, document);
+
result = TRUE;
}
@@ -167,7 +172,7 @@ couchdb_document_delete (CouchDBDocument *document, GError **error)
if (parser) {
g_object_unref (G_OBJECT (parser));
- result = TRUE;
+ g_signal_emit_by_name (document->couchdb, "document_deleted", document->dbname, id);
}
g_free (url);
@@ -182,9 +187,11 @@ couchdb_document_get_id (CouchDBDocument *document)
if (document->root_node &&
json_node_get_node_type (document->root_node) == JSON_NODE_OBJECT) {
- return json_object_get_string_member (
- json_node_get_object (document->root_node),
- "_id");
+ if (json_object_has_member (json_node_get_object (document->root_node),
+ "_id"))
+ return json_object_get_string_member (
+ json_node_get_object (document->root_node),
+ "_id");
}
return NULL;
diff --git a/couchdb-glib/couchdb-glib.h b/couchdb-glib/couchdb-glib.h
index bc8dc6a..43a84c5 100644
--- a/couchdb-glib/couchdb-glib.h
+++ b/couchdb-glib/couchdb-glib.h
@@ -31,9 +31,18 @@
#define COUCHDB_IS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), COUCHDB_TYPE))
#define COUCHDB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), COUCHDB_TYPE, CouchDBClass))
+typedef struct _CouchDBDocument CouchDBDocument;
+
typedef struct _CouchDB CouchDB;
typedef struct {
GObjectClass parent_class;
+
+ void (* database_created) (CouchDB *couchdb, const char *dbname);
+ void (* database_deleted) (CouchDB *couchdb, const char *dbname);
+
+ void (* document_created) (CouchDB *couchdb, const char *dbname, CouchDBDocument *document);
+ void (* document_updated) (CouchDB *couchdb, const char *dbname, CouchDBDocument *document);
+ void (* document_deleted) (CouchDB *couchdb, const char *dbname, const char *docid);
} CouchDBClass;
GType couchdb_get_type (void);
@@ -53,6 +62,8 @@ CouchDBDatabaseInfo *couchdb_get_database_info (CouchDB *couchdb, const char *db
gboolean couchdb_create_database (CouchDB *couchdb, const char *dbname, GError **error);
gboolean couchdb_delete_database (CouchDB *couchdb, const char *dbname, GError **error);
+void couchdb_listen_for_changes (CouchDB *couchdb, const char *dbname);
+
/*
* Documents API
*/
@@ -64,7 +75,6 @@ gboolean couchdb_delete_database (CouchDB *couchdb, const char *dbna
#define COUCHDB_IS_DOCUMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), COUCHDB_TYPE_DOCUMENT))
#define COUCHDB_DOCUMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), COUCHDB_TYPE_DOCUMENT, CouchDBDocumentClass))
-typedef struct _CouchDBDocument CouchDBDocument;
typedef struct {
GObjectClass parent_class;
} CouchDBDocumentClass;
diff --git a/couchdb-glib/couchdb-marshal.list b/couchdb-glib/couchdb-marshal.list
new file mode 100644
index 0000000..991e86b
--- /dev/null
+++ b/couchdb-glib/couchdb-marshal.list
@@ -0,0 +1,2 @@
+NONE:STRING,OBJECT
+NONE:STRING,STRING
diff --git a/couchdb-glib/couchdb.c b/couchdb-glib/couchdb.c
index decc59f..db69c22 100644
--- a/couchdb-glib/couchdb.c
+++ b/couchdb-glib/couchdb.c
@@ -23,15 +23,29 @@
#include <libsoup/soup-gnome.h>
#include <json-glib/json-glib.h>
#include "couchdb-glib.h"
+#include "couchdb-marshal.h"
+#include "dbwatch.h"
#include "utils.h"
G_DEFINE_TYPE(CouchDB, couchdb, G_TYPE_OBJECT)
+enum {
+ DATABASE_CREATED,
+ DATABASE_DELETED,
+ DOCUMENT_CREATED,
+ DOCUMENT_UPDATED,
+ DOCUMENT_DELETED,
+ LAST_SIGNAL
+};
+static guint couchdb_signals[LAST_SIGNAL];
+
static void
couchdb_finalize (GObject *object)
{
CouchDB *couchdb = COUCHDB (object);
+ g_hash_table_destroy (couchdb->db_watchlist);
+
g_free (couchdb->hostname);
g_object_unref (couchdb->http_session);
@@ -44,11 +58,64 @@ couchdb_class_init (CouchDBClass *klass)
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = couchdb_finalize;
+
+ /* Signals */
+ couchdb_signals[DATABASE_CREATED] =
+ g_signal_new ("database_created",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CouchDBClass, database_created),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+ couchdb_signals[DATABASE_DELETED] =
+ g_signal_new ("database_deleted",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CouchDBClass, database_deleted),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+ couchdb_signals[DOCUMENT_CREATED] =
+ g_signal_new ("document_created",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CouchDBClass, document_created),
+ NULL, NULL,
+ _couchdb_marshal_VOID__STRING_OBJECT,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_OBJECT);
+ couchdb_signals[DOCUMENT_UPDATED] =
+ g_signal_new ("document_updated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CouchDBClass, document_updated),
+ NULL, NULL,
+ _couchdb_marshal_VOID__STRING_OBJECT,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_OBJECT);
+ couchdb_signals[DOCUMENT_DELETED] =
+ g_signal_new ("document_deleted",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CouchDBClass, document_deleted),
+ NULL, NULL,
+ _couchdb_marshal_VOID__STRING_STRING,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
}
static void
couchdb_init (CouchDB *couchdb)
{
+ couchdb->db_watchlist = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) dbwatch_free);
}
CouchDB *
@@ -168,6 +235,9 @@ couchdb_create_database (CouchDB *couchdb, const char *dbname, GError **error)
g_free (url);
+ if (result)
+ g_signal_emit_by_name (couchdb, "database_created", dbname);
+
return result;
}
@@ -196,6 +266,14 @@ couchdb_delete_database (CouchDB *couchdb, const char *dbname, GError **error)
g_free (url);
+ if (result) {
+ /* If we're listening for changes on this database, stop doing so */
+ if (g_hash_table_lookup (couchdb->db_watchlist, dbname))
+ g_hash_table_remove (couchdb->db_watchlist, dbname);
+
+ g_signal_emit_by_name (couchdb, "database_deleted", dbname);
+ }
+
return result;
}
@@ -261,3 +339,39 @@ couchdb_free_document_list (GSList *doclist)
g_slist_foreach (doclist, (GFunc) couchdb_document_info_unref, NULL);
g_slist_free (doclist);
}
+
+void
+couchdb_listen_for_changes (CouchDB *couchdb, const char *dbname)
+{
+ DBWatch *watch;
+ CouchDBDatabaseInfo *db_info;
+ GError *error = NULL;
+
+ g_return_if_fail (COUCHDB_IS (couchdb));
+ g_return_if_fail (dbname != NULL);
+
+ watch = g_hash_table_lookup (couchdb->db_watchlist, dbname);
+ if (watch) {
+ g_warning ("Already listening for changes in '%s' database", dbname);
+ return;
+ }
+
+ /* Retrieve information for database, to know the last_update_sequence */
+ db_info = couchdb_get_database_info (couchdb, dbname, &error);
+ if (!db_info) {
+ g_warning ("Could not retrieve information for '%s' database: %s",
+ dbname, error->message);
+ g_error_free (error);
+
+ return;
+ }
+
+ watch = dbwatch_new (couchdb,
+ dbname,
+ couchdb_database_info_get_update_sequence (db_info));
+ if (watch)
+ g_hash_table_insert (couchdb->db_watchlist, g_strdup (dbname), watch);
+
+ /* Free memory */
+ couchdb_database_info_unref (db_info);
+}
diff --git a/couchdb-glib/dbwatch.c b/couchdb-glib/dbwatch.c
new file mode 100644
index 0000000..bb9adc0
--- /dev/null
+++ b/couchdb-glib/dbwatch.c
@@ -0,0 +1,137 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2009 Canonical Services Ltd (www.canonical.com)
+ *
+ * Authors: Rodrigo Moya <rodrigo moya canonical com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser 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 Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "couchdb-glib.h"
+#include "dbwatch.h"
+#include "utils.h"
+
+#define TIMEOUT_SECONDS 300
+
+static void
+process_change (DBWatch *watch, JsonNode *node)
+{
+ JsonObject *this_change;
+ const gchar *id;
+ CouchDBDocument *document;
+ GError *error = NULL;
+
+ if (json_node_get_node_type (node) != JSON_NODE_OBJECT)
+ return;
+
+ this_change = json_node_get_object (node);
+ if (!json_object_has_member (this_change, "id"))
+ return;
+
+ id = json_object_get_string_member (this_change, "id");
+
+ /* We need to try retrieving the document, to check if it's removed or not */
+ document = couchdb_document_get (watch->couchdb, watch->dbname, id, &error);
+ if (document) {
+ const gchar *revision;
+
+ revision = couchdb_document_get_revision (document);
+ if (revision != NULL) {
+ if (revision[0] == '1')
+ g_signal_emit_by_name (watch->couchdb, "document_created",
+ watch->dbname, document);
+ else
+ g_signal_emit_by_name (watch->couchdb, "document_updated",
+ watch->dbname, document);
+ }
+
+ g_object_unref (G_OBJECT (document));
+ } else {
+ if (error != NULL) {
+ g_warning ("Error retrieving document '%s': %s", id, error->message);
+ g_error_free (error);
+ } else {
+ /* The document is no longer in the DB, notify */
+ g_signal_emit_by_name (watch->couchdb, "document_deleted", watch->dbname, id);
+ }
+ }
+}
+
+static gboolean
+watch_timeout_cb (gpointer user_data)
+{
+ char *url;
+ JsonParser *parser;
+ GError *error = NULL;
+ DBWatch *watch = (DBWatch *) user_data;
+
+ url = g_strdup_printf ("%s/%s/_changes?since=%d",
+ watch->couchdb->hostname,
+ watch->dbname,
+ watch->last_update_seq);
+ parser = send_message_and_parse (watch->couchdb, SOUP_METHOD_GET, url, NULL, &error);
+ if (parser != NULL) {
+ JsonNode *root_node;
+
+ root_node = json_parser_get_root (parser);
+ if (json_node_get_node_type (root_node) == JSON_NODE_OBJECT) {
+ JsonObject *root_object;
+ JsonArray *results;
+
+ root_object = json_node_get_object (root_node);
+ results = json_object_get_array_member (root_object, "results");
+ if (results) {
+ GList *json_elements, *sl;
+
+ json_elements = json_array_get_elements (results);
+ for (sl = json_elements; sl != NULL; sl = sl->next)
+ process_change (watch, (JsonNode *) sl->data);
+ }
+
+ if (json_object_has_member (root_object, "last_seq"))
+ watch->last_update_seq = json_object_get_int_member (root_object, "last_seq");
+ }
+
+ g_object_unref (G_OBJECT (parser));
+ }
+
+ /* Free memory */
+ g_free (url);
+}
+
+DBWatch *
+dbwatch_new (CouchDB *couchdb, const gchar *dbname, gint update_seq)
+{
+ DBWatch *watch;
+
+ watch = g_new0 (DBWatch, 1);
+ watch->couchdb = couchdb;
+ watch->dbname = g_strdup (dbname);
+ watch->last_update_seq = update_seq;
+
+ /* Set timeout to check for changes every 5 minutes*/
+ watch->timeout_id = g_timeout_add (TIMEOUT_SECONDS * 1000, (GSourceFunc) watch_timeout_cb, watch);
+
+ return watch;
+}
+
+void
+dbwatch_free (DBWatch *watch)
+{
+ g_free (watch->dbname);
+ g_source_remove (watch->timeout_id);
+
+ g_free (watch);
+}
diff --git a/couchdb-glib/dbwatch.h b/couchdb-glib/dbwatch.h
new file mode 100644
index 0000000..9e9771e
--- /dev/null
+++ b/couchdb-glib/dbwatch.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2009 Canonical Services Ltd (www.canonical.com)
+ *
+ * Authors: Rodrigo Moya <rodrigo moya canonical com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser 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 Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __DBWATCH_H__
+#define __DBWATCH_H__
+
+#include "utils.h"
+
+typedef struct {
+ CouchDB *couchdb;
+ gchar *dbname;
+ gint last_update_seq;
+ guint timeout_id;
+} DBWatch;
+
+DBWatch *dbwatch_new (CouchDB *couchdb, const gchar *dbname, gint update_seq);
+void dbwatch_free (DBWatch *watch);
+
+#endif
diff --git a/couchdb-glib/utils.h b/couchdb-glib/utils.h
index e79be3f..7157bdf 100644
--- a/couchdb-glib/utils.h
+++ b/couchdb-glib/utils.h
@@ -30,6 +30,8 @@ struct _CouchDB {
char *hostname;
SoupSession *http_session;
+
+ GHashTable *db_watchlist;
};
struct _CouchDBDocument {
diff --git a/tests/test-couchdb-glib.c b/tests/test-couchdb-glib.c
index 5a6ecd2..277b437 100644
--- a/tests/test-couchdb-glib.c
+++ b/tests/test-couchdb-glib.c
@@ -20,6 +20,7 @@
*/
#include <couchdb-glib.h>
+#include <utils.h>
static CouchDB *couchdb;
@@ -47,7 +48,8 @@ test_list_databases (void)
doclist = couchdb_list_documents (couchdb, (const char *) dblist->data, &error);
g_assert (error == NULL);
g_assert (g_slist_length (doclist) == couchdb_database_info_get_documents_count (dbinfo));
- couchdb_free_document_list (doclist);
+ if (doclist)
+ couchdb_free_document_list (doclist);
dblist = g_slist_remove (dblist, dblist->data);
couchdb_database_info_unref (dbinfo);
@@ -98,6 +100,82 @@ test_list_documents (void)
}
}
+static void
+test_change_databases (void)
+{
+ char *dbname;
+ gint i;
+ GError *error = NULL;
+
+ dbname = generate_uuid ();
+ g_assert (dbname != NULL);
+
+ /* Create database */
+ couchdb_create_database (couchdb, dbname, &error);
+ g_assert (error == NULL);
+
+ couchdb_listen_for_changes (couchdb, dbname);
+
+ /* Create some documents */
+ for (i = 0; i < 10; i++) {
+ CouchDBDocument *document;
+ char *str;
+
+ document = couchdb_document_new (couchdb);
+ g_assert (document != NULL);
+
+ couchdb_document_set_boolean_field (document, "boolean", TRUE);
+ couchdb_document_set_int_field (document, "int", i);
+ couchdb_document_set_double_field (document, "double", (gdouble) i);
+
+ str = g_strdup_printf ("value%d", i);
+ couchdb_document_set_string_field (document, "string", str);
+ g_free (str);
+
+ g_assert (couchdb_document_put (document, dbname, &error));
+ g_assert (error == NULL);
+ }
+
+ /* Delete database */
+ g_assert (couchdb_delete_database (couchdb, dbname, &error));
+ g_assert (error == NULL);
+
+ /* Free memory */
+ g_free (dbname);
+}
+
+static void
+db_created_cb (CouchDB *couchdb, const char *dbname, gpointer user_data)
+{
+ g_print ("Database %s has been created\n", dbname);
+}
+
+static void
+db_deleted_cb (CouchDB *couchdb, const char *dbname, gpointer user_data)
+{
+ g_print ("Database %s has been deleted\n", dbname);
+}
+
+static void
+doc_changed_cb (CouchDB *couchdb, const char *dbname, CouchDBDocument *document, gpointer user_data)
+{
+ char *doc_str;
+
+ doc_str = couchdb_document_to_string (document);
+ g_print ("Document %s has been %s: %s\n",
+ couchdb_document_get_id (document),
+ (const gchar *) user_data,
+ doc_str);
+
+ g_free (doc_str);
+}
+
+static void
+doc_deleted_cb (CouchDB *couchdb, const char *dbname, const char *docid, gpointer user_data)
+{
+ g_print ("Document %s in database %s has been deleted\n", docid, dbname);
+}
+
int
main (int argc, char *argv[])
{
@@ -112,9 +190,16 @@ main (int argc, char *argv[])
return -1;
}
+ g_signal_connect (G_OBJECT (couchdb), "database_created", G_CALLBACK (db_created_cb), NULL);
+ g_signal_connect (G_OBJECT (couchdb), "database_deleted", G_CALLBACK (db_deleted_cb), NULL);
+ g_signal_connect (G_OBJECT (couchdb), "document_created", G_CALLBACK (doc_changed_cb), "created");
+ g_signal_connect (G_OBJECT (couchdb), "document_updated", G_CALLBACK (doc_changed_cb), "updated");
+ g_signal_connect (G_OBJECT (couchdb), "document_deleted", G_CALLBACK (doc_deleted_cb), NULL);
+
/* Setup test functions */
g_test_add_func ("/testcouchdbglib/ListDatabases", test_list_databases);
g_test_add_func ("/testcouchdbglib/ListDocuments", test_list_documents);
+ g_test_add_func ("/testcouchdbglib/ChangeDatabases", test_change_databases);
return g_test_run ();
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]