[RFC PATCH 13/13] First crack at a CouchDB mailstore
authorEthan Glasser-Camp <glasse@cs.rpi.edu>
Wed, 15 Feb 2012 22:02:06 +0000 (17:02 +1900)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:44:34 +0000 (09:44 -0800)
99/be38b67de357deb4e8c42f895aba42040a143b [new file with mode: 0644]

diff --git a/99/be38b67de357deb4e8c42f895aba42040a143b b/99/be38b67de357deb4e8c42f895aba42040a143b
new file mode 100644 (file)
index 0000000..938888a
--- /dev/null
@@ -0,0 +1,627 @@
+Return-Path: <glasse@cs.rpi.edu>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+       by olra.theworths.org (Postfix) with ESMTP id B8CC8429E4B\r
+       for <notmuch@notmuchmail.org>; Wed, 15 Feb 2012 14:14:24 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -0.54\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-0.54 tagged_above=-999 required=5\r
+       tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\r
+       RCVD_IN_BL_SPAMCOP_NET=1.246, RCVD_IN_DNSWL_MED=-2.3,\r
+       RCVD_IN_SORBS_WEB=0.614] autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+       with ESMTP id QVD6xA9JVspE for <notmuch@notmuchmail.org>;\r
+       Wed, 15 Feb 2012 14:14:21 -0800 (PST)\r
+Received: from cliffclavin.cs.rpi.edu (cliffclavin.cs.rpi.edu\r
+ [128.113.126.25])     (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))\r
+       (No client certificate requested)       by olra.theworths.org (Postfix) with ESMTPS\r
+ id 08E39429E48        for <notmuch@notmuchmail.org>; Wed, 15 Feb 2012 14:14:20 -0800\r
+ (PST)\r
+X-Hash:\r
+ S|9d0a70cf09b4d94fc531de6b69290ddde7b9d232|4287d7537ed9cdc3a91eb958ef996dd2\r
+X-Countries: Cameroon, United States\r
+X-SMTP-From: accepted <glasse@cs.rpi.edu> [195.24.209.20] [195.24.209.20]\r
+       (localhost) {Cameroon}\r
+DKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=cs.rpi.edu; h=from\r
+       :to:cc:subject:date:message-id:in-reply-to:references; s=\r
+       default; i=glasse@cs.rpi.edu; t=1329344058; x=1329948858; l=17033;\r
+       bh=e8Cxd0KCuBnxMSP/RNsbHyyPFZI=; b=hb6rfQltD4z2WLFV6LX0NO\r
+       ahG6qF8SAQ0WRadpVixFA/hhRwgFptsAJblnZUXzJ8Ad9HWZt71J9AOBy/C1J7U4\r
+       yFzSjq3iI35Gjq7LRRg5PHNXwusjQf+0XWtkfEXPhxmePQhYSNXYX7m4TvKM6Ub2\r
+       aqTwemkujepZYbc4Tezak=\r
+DomainKey-Signature: a=rsa-sha1; c=nofws; d=cs.rpi.edu; h=from:to:cc\r
+       :subject:date:message-id:in-reply-to:references; q=dns; s=\r
+       default; b=j2zcLiGUqE7Nw5LOENLSOWn7AMC2wp5Wm2D6AJ/eE+KJ3WaJdkbwy\r
+       8Jjbm9usaKc4hRy2jLXWRIIeMhVY3jP+02xjPROevwuTTArJZV187ODtmdgkIRlk\r
+       xxPjBs41j0TGIcYQb/UAwCxBZjrsUuhKr0SVm4dhqxAD8Be/ro9xRI=\r
+X-Spam-Info: -2.7; ALL_TRUSTED,AWL,BAYES_00\r
+X-Spam-Scanned-By: cliffclavin.cs.rpi.edu using SpamAssassin 3.2.5 (hard limit\r
+       15)\r
+Authentication-Results: cliffclavin.cs.rpi.edu;\r
+       DKIM=neutral (none) header.from=glasse@cs.rpi.edu;\r
+       SPF=neutral (mfrom;\r
+       Mechanism '?all' matched) smtp.mail=glasse@cs.rpi.edu\r
+X-Auth-Passed: cliffclavin.cs.rpi.edu:q1FMDOMs008891 Auth:glasse\r
+X-Virus-Scanned-By: cliffclavin.cs.rpi.edu\r
+Received: from localhost ([195.24.209.20]) (authenticated bits=0)\r
+       by cliffclavin.cs.rpi.edu (8.14.3/8.14.3) with ESMTP id q1FMDOMs008891\r
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES128-SHA bits=128 verify=NO);\r
+       Wed, 15 Feb 2012 17:13:36 -0500 (EST)\r
+       (envelope-from glasse@cs.rpi.edu)\r
+From: Ethan Glasser-Camp <glasse@cs.rpi.edu>\r
+To: notmuch@notmuchmail.org\r
+Subject: [RFC PATCH 13/13] First crack at a CouchDB mailstore\r
+Date: Wed, 15 Feb 2012 17:02:06 -0500\r
+Message-Id: <1329343326-16410-14-git-send-email-glasse@cs.rpi.edu>\r
+X-Mailer: git-send-email 1.7.5.4\r
+In-Reply-To: <1329343326-16410-1-git-send-email-glasse@cs.rpi.edu>\r
+References: <1329343326-16410-1-git-send-email-glasse@cs.rpi.edu>\r
+X-Scanned-By: MIMEDefang 2.67 on 128.113.126.25\r
+Cc: Ethan Glasser-Camp <ethan@betacantrips.com>\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+       <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Wed, 15 Feb 2012 22:14:24 -0000\r
+\r
+From: Ethan Glasser-Camp <ethan@betacantrips.com>\r
+\r
+This introduces new parameters to notmuch-config to store the CouchDB\r
+URL and the "name" of the database.\r
+\r
+Signed-off-by: Ethan Glasser-Camp <ethan@betacantrips.com>\r
+---\r
+ Makefile.local   |    3 +\r
+ lib/mailstore.c  |  109 ++++++++++++++++++++++++++++++++\r
+ notmuch-client.h |   14 ++++\r
+ notmuch-config.c |   91 +++++++++++++++++++++++++-\r
+ notmuch-new.c    |  184 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\r
+ 5 files changed, 397 insertions(+), 4 deletions(-)\r
+\r
+diff --git a/Makefile.local b/Makefile.local\r
+index 1131dea..a105e58 100644\r
+--- a/Makefile.local\r
++++ b/Makefile.local\r
+@@ -27,6 +27,9 @@ endif\r
\r
+ UPSTREAM_TAG=$(subst ~,_,$(VERSION))\r
+ DEB_TAG=debian/$(UPSTREAM_TAG)-1\r
++# FIXME: Where should this really go?\r
++LDFLAGS += $(shell pkg-config --libs couchdb-glib-1.0 libsoup-2.4)\r
++extra_cflags += $(shell pkg-config --cflags couchdb-glib-1.0 libsoup-2.4)\r
\r
+ RELEASE_HOST=notmuchmail.org\r
+ RELEASE_DIR=/srv/notmuchmail.org/www/releases\r
+diff --git a/lib/mailstore.c b/lib/mailstore.c\r
+index 51c2710..4d7cc79 100644\r
+--- a/lib/mailstore.c\r
++++ b/lib/mailstore.c\r
+@@ -18,6 +18,10 @@\r
\r
+ #include <stdio.h>\r
+ #include <stdarg.h>\r
++#include <couchdb-session.h>\r
++#include <couchdb-database.h>\r
++#include <couchdb-document.h>\r
++#include <glib.h>\r
\r
+ #include "notmuch-private.h"\r
\r
+@@ -58,6 +62,101 @@ _maildir_rename_function (unused (notmuch_mailstore_t *mailstore),\r
+     return rename (old_filename, new_filename);\r
+ }\r
\r
++struct _couchdb_data {\r
++    char *db_path;\r
++    CouchdbDatabase *database;\r
++    GHashTable *files_to_documents;\r
++};\r
++\r
++/* CouchDB mailstore */\r
++static notmuch_status_t\r
++_couchdb_constructor (void **data, va_list ap)\r
++{\r
++    CouchdbSession *session = NULL;\r
++    CouchdbDatabase *database = NULL;\r
++    GError *error = NULL;\r
++    char *uri = NULL;\r
++    char *db_name = NULL;\r
++    struct _couchdb_data *my_data = NULL;\r
++\r
++    uri = va_arg (ap, char*);\r
++    session = couchdb_session_new (uri);\r
++\r
++    db_name = va_arg (ap, char*);\r
++    database = couchdb_session_get_database (session, db_name, &error);\r
++    if (database == NULL) {\r
++      fprintf (stderr, "Couldn't access database %s: %s\n", db_name,\r
++               error->message);\r
++      return NOTMUCH_STATUS_FILE_ERROR;\r
++    }\r
++\r
++    my_data = talloc_size (NULL, sizeof (struct _couchdb_data));\r
++    my_data->database = database;\r
++    my_data->db_path  = va_arg (ap, char*);\r
++    my_data->files_to_documents = g_hash_table_new (NULL, NULL);\r
++    (*data) = (void*)my_data;\r
++\r
++    return NOTMUCH_STATUS_SUCCESS;\r
++}\r
++\r
++static FILE *\r
++_couchdb_open_function (notmuch_mailstore_t *mailstore,\r
++                      const char *filename)\r
++{\r
++    CouchdbDatabase *database = NULL;\r
++    CouchdbDocument *document = NULL;\r
++    GError *error = NULL;\r
++    const char *text = NULL;\r
++    const char *relative = NULL;\r
++    struct _couchdb_data *data = (struct _couchdb_data *)mailstore->data;\r
++    FILE *ret = NULL;\r
++    database = data->database;\r
++    /* message assumes all files should be contained within db_path.\r
++     * This isn't true for us, so remove the db_path.\r
++     * I'd like to use _notmuch_database_relative_path but I don't have\r
++     * a notmuch_database_t*.\r
++     */\r
++    relative = filename;\r
++    if (strncmp (filename, data->db_path, strlen (data->db_path)) == 0) {\r
++      relative = filename + strlen (data->db_path);\r
++      while (*relative == '/' && *(relative+1) == '/')\r
++          relative++;\r
++    }\r
++\r
++    document = couchdb_database_get_document (database, relative, &error);\r
++    if (document == NULL)\r
++      /* file doesn't exist. Maybe it got deleted? */\r
++      return NULL;\r
++\r
++    text = couchdb_document_get_string_field (document, "text");\r
++    /* FIXME: null bytes in the mail file? */\r
++    ret = fmemopen ((char *)text, strlen(text), "r");\r
++    g_hash_table_insert (data->files_to_documents, ret, document);\r
++    return ret;\r
++}\r
++\r
++static int\r
++_couchdb_close_function (notmuch_mailstore_t *mailstore, FILE *file)\r
++{\r
++    struct _couchdb_data *data = (struct _couchdb_data *)mailstore->data;\r
++    GHashTable *hash = data->files_to_documents;\r
++    CouchdbDocument *document;\r
++    document = g_hash_table_lookup (hash, file);\r
++    g_object_unref (document);\r
++    fclose (file); /* just to be polite ;) */\r
++    g_hash_table_remove (hash, file);\r
++    return 0;\r
++}\r
++\r
++static int\r
++_couchdb_rename_function (unused (notmuch_mailstore_t *mailstore),\r
++                        unused (const char *old_filename),\r
++                        unused (const char *new_filename))\r
++{\r
++    /* Pass for now. */\r
++    return 0;\r
++}\r
++\r
+ /* A mailstore is defined as:\r
+  *\r
+  * - A function used to "open" a mail message. This takes the\r
+@@ -80,12 +179,22 @@ notmuch_mailstore_maildir = { _maildir_constructor,\r
+                             _maildir_rename_function,\r
+                             NULL };\r
\r
++_notmuch_mailstore\r
++notmuch_mailstore_couchdb = { _couchdb_constructor,\r
++                            _couchdb_open_function, _couchdb_close_function,\r
++                            _couchdb_rename_function,\r
++                            NULL};\r
++\r
++\r
+ _notmuch_mailstore *\r
+ notmuch_mailstore_get_by_name (const char *name)\r
+ {\r
+     if (strcmp (name, "maildir") == 0)\r
+       return &notmuch_mailstore_maildir;\r
\r
++    if (strcmp (name, "couchdb") == 0)\r
++      return &notmuch_mailstore_couchdb;\r
++\r
+     return NULL;\r
+ }\r
\r
+diff --git a/notmuch-client.h b/notmuch-client.h\r
+index 405aad7..12dc868 100644\r
+--- a/notmuch-client.h\r
++++ b/notmuch-client.h\r
+@@ -230,6 +230,20 @@ void\r
+ notmuch_config_set_database_type (notmuch_config_t *config,\r
+                                 const char *database_type);\r
\r
++const char *\r
++notmuch_config_get_database_uri (notmuch_config_t *config);\r
++\r
++void\r
++notmuch_config_set_database_uri (notmuch_config_t *config,\r
++                               const char *database_uri);\r
++\r
++const char *\r
++notmuch_config_get_database_name (notmuch_config_t *config);\r
++\r
++void\r
++notmuch_config_set_database_name (notmuch_config_t *config,\r
++                                const char *database_name);\r
++\r
+ notmuch_mailstore_t *\r
+ notmuch_config_get_mailstore (notmuch_config_t *config);\r
\r
+diff --git a/notmuch-config.c b/notmuch-config.c\r
+index 99f872d..6090150 100644\r
+--- a/notmuch-config.c\r
++++ b/notmuch-config.c\r
+@@ -37,8 +37,8 @@ static const char database_config_comment[] =\r
+     "\n"\r
+     " The following options are supported here:\n"\r
+     "\n"\r
+-    "\ttype   The type of mail backend. The only currently supported\n"\r
+-    "\t       value is \"maildir\".\n"\r
++    "\ttype   The type of mail backend. The currently supported\n"\r
++    "\t       values are \"maildir\" and \"couchdb\".\n"\r
+     "\tpath   For the maildir backend, the top-level maildir directory.\n"\r
+     "\t       For all backends, the location where notmuch should store its\n"\r
+     "\t       database. Notmuch will store its database within a sub-directory\n"\r
+@@ -49,7 +49,14 @@ static const char database_config_comment[] =\r
+     " This backend reads mail from a directory tree where files are\n"\r
+     " individual email messages.\n"\r
+     " The only configuration option is 'path' which should be the top-level\n"\r
+-    " directory.\n";\r
++    " directory.\n"\r
++    " CouchDB backend\n"\r
++    "\n"\r
++    " This backend reads mail from a CouchDB database via HTTP.\n"\r
++    " For more details on the setup of such a database, please see the help\n"\r
++    " files.\n"\r
++    " The configuration options are 'uri' and 'name', which specify the URI\n"\r
++    " of the CouchDB instance and the database name of the mail store.\n";\r
\r
+ static const char new_config_comment[] =\r
+     " Configuration for \"notmuch new\"\n"\r
+@@ -113,6 +120,8 @@ struct _notmuch_config {\r
\r
+     char *database_path;\r
+     char *database_type;\r
++    char *database_uri;\r
++    char *database_name;\r
+     char *user_name;\r
+     char *user_primary_email;\r
+     const char **user_other_email;\r
+@@ -273,6 +282,8 @@ notmuch_config_open (void *ctx,\r
\r
+     config->database_path = NULL;\r
+     config->database_type = NULL;\r
++    config->database_uri = NULL;\r
++    config->database_name = NULL;\r
+     config->user_name = NULL;\r
+     config->user_primary_email = NULL;\r
+     config->user_other_email = NULL;\r
+@@ -339,6 +350,12 @@ notmuch_config_open (void *ctx,\r
+       notmuch_config_set_database_type (config, "maildir");\r
+     }\r
\r
++    if (notmuch_config_get_database_uri (config) == NULL)\r
++      notmuch_config_set_database_uri (config, "");\r
++\r
++    if (notmuch_config_get_database_name (config) == NULL)\r
++      notmuch_config_set_database_name (config, "");\r
++\r
+     if (notmuch_config_get_user_name (config) == NULL) {\r
+       char *name = get_name_from_passwd_file (config);\r
+       notmuch_config_set_user_name (config, name);\r
+@@ -584,6 +601,62 @@ notmuch_config_set_database_type (notmuch_config_t *config,\r
+     config->database_type = NULL;\r
+ }\r
\r
++const char *\r
++notmuch_config_get_database_uri (notmuch_config_t *config)\r
++{\r
++    char *uri;\r
++\r
++    if (config->database_uri == NULL) {\r
++      uri = g_key_file_get_string (config->key_file,\r
++                                    "database", "uri", NULL);\r
++      if (uri) {\r
++          config->database_uri = talloc_strdup (config, uri);\r
++          free (uri);\r
++      }\r
++    }\r
++\r
++    return config->database_uri;\r
++}\r
++\r
++void\r
++notmuch_config_set_database_uri (notmuch_config_t *config,\r
++                                const char *database_uri)\r
++{\r
++    g_key_file_set_string (config->key_file,\r
++                         "database", "uri", database_uri);\r
++\r
++    talloc_free (config->database_uri);\r
++    config->database_uri = NULL;\r
++}\r
++\r
++const char *\r
++notmuch_config_get_database_name (notmuch_config_t *config)\r
++{\r
++    char *name;\r
++\r
++    if (config->database_name == NULL) {\r
++      name = g_key_file_get_string (config->key_file,\r
++                                    "database", "name", NULL);\r
++      if (name) {\r
++          config->database_name = talloc_strdup (config, name);\r
++          free (name);\r
++      }\r
++    }\r
++\r
++    return config->database_name;\r
++}\r
++\r
++void\r
++notmuch_config_set_database_name (notmuch_config_t *config,\r
++                                const char *database_name)\r
++{\r
++    g_key_file_set_string (config->key_file,\r
++                         "database", "name", database_name);\r
++\r
++    talloc_free (config->database_name);\r
++    config->database_name = NULL;\r
++}\r
++\r
+ notmuch_mailstore_t *\r
+ notmuch_config_get_mailstore (notmuch_config_t *config)\r
+ {\r
+@@ -595,7 +668,17 @@ notmuch_config_get_mailstore (notmuch_config_t *config)\r
+     notmuch_status_t status;\r
+     const char *type = notmuch_config_get_database_type (config);\r
+     notmuch_mailstore_t *mailstore = notmuch_mailstore_get_by_name (type);\r
+-    status = notmuch_mailstore_construct (mailstore);\r
++    if (strcmp (type, "maildir") == 0)\r
++      status = notmuch_mailstore_construct (mailstore);\r
++    else if (strcmp (type, "couchdb") == 0)\r
++      status = notmuch_mailstore_construct (mailstore,\r
++                                            notmuch_config_get_database_uri (config),\r
++                                            notmuch_config_get_database_name (config),\r
++                                            notmuch_config_get_database_path (config));\r
++    else\r
++      /* Doomed, doomed, doomed */\r
++      status = NOTMUCH_STATUS_FILE_ERROR;\r
++\r
+     if (status != NOTMUCH_STATUS_SUCCESS) {\r
+       /* abort messily? */\r
+     }\r
+diff --git a/notmuch-new.c b/notmuch-new.c\r
+index d30fba1..3c1acb2 100644\r
+--- a/notmuch-new.c\r
++++ b/notmuch-new.c\r
+@@ -21,6 +21,11 @@\r
+ #include "notmuch-client.h"\r
\r
+ #include <unistd.h>\r
++#include <libsoup/soup-method.h>\r
++#include <couchdb-session.h>\r
++#include <couchdb-database.h>\r
++#include <couchdb-document.h>\r
++#include <json-glib/json-glib.h>\r
\r
+ typedef struct _filename_node {\r
+     char *filename;\r
+@@ -297,6 +302,182 @@ _add_message (add_files_state_t *state, notmuch_database_t *notmuch,\r
+     return ret;\r
+ }\r
\r
++/* Send an unsupported message to a couchdb instance.\r
++ *\r
++ * This function is "supposed" to be "part" of the "public API",\r
++ * but it isn't declared in couchdb-glib's header files. See:\r
++ * https://bugs.launchpad.net/couchdb-glib/+bug/927847\r
++ */\r
++gboolean\r
++couchdb_session_send_message (CouchdbSession *session, const char *method, const char *url, const char *body, JsonParser *output, GError **error);\r
++\r
++/* Process a JSON "change" object and either add or delete the "file".\r
++ *\r
++ * This is based on code from couchdb-glib, which is why it's a weird\r
++ * melange of glib style and notmuch style.\r
++ *\r
++ * As with Maildir, we assume that message objects never change.\r
++ */\r
++static void\r
++couchdb_process_change (add_files_state_t *state,\r
++                      notmuch_database_t *notmuch,\r
++                      CouchdbDatabase *database,\r
++                      JsonNode *node)\r
++{\r
++    JsonObject *this_change;\r
++    const gchar *id;\r
++    CouchdbDocument *document;\r
++    GError *error = NULL;\r
++\r
++    if (json_node_get_node_type (node) != JSON_NODE_OBJECT)\r
++      return;\r
++\r
++    this_change = json_node_get_object (node);\r
++    if (!json_object_has_member (this_change, "id"))\r
++      return;\r
++\r
++    id = json_object_get_string_member (this_change, "id");\r
++\r
++    /* We need to try retrieving the document, to check if it's removed or not */\r
++    document = couchdb_database_get_document (database, id, &error);\r
++    if (document) {\r
++      /* We got a document, dump it into Notmuch */\r
++      _report_before_adding_file (state, id);\r
++      _add_message (state, notmuch, id);\r
++      _report_added_file (state);\r
++      g_object_unref (G_OBJECT (document));\r
++    }\r
++    else {\r
++      if (error != NULL) {\r
++          g_warning ("Error retrieving document '%s': %s", id, error->message);\r
++          g_error_free (error);\r
++      } else {\r
++          /* The document is no longer in the DB, notify */\r
++          id = talloc_strdup (state->removed_files, id);\r
++          _filename_list_add (state->removed_files, id);\r
++      }\r
++    }\r
++}\r
++\r
++/* Fetch a batch of database updates from couch's "changes" feed.\r
++ *\r
++ * This is essentially a copied and modified version of code from\r
++ * couchdb-glib. There's code to "watch a feed of changes", but I just\r
++ * want to do it once and synchronously.\r
++ */\r
++static notmuch_status_t\r
++couchdb_add_messages_batch (add_files_state_t *state,\r
++                          notmuch_database_t *notmuch,\r
++                          CouchdbDatabase *database,\r
++                          guint32 *last_seq, int limit)\r
++{\r
++    char *url;\r
++    JsonParser *parser;\r
++    GError *error = NULL;\r
++\r
++    url = g_strdup_printf ("%s/%s/_changes?since=%d&limit=%d",\r
++                         couchdb_session_get_uri (couchdb_database_get_session (database)),\r
++                         couchdb_database_get_name (database),\r
++                         *last_seq, limit);\r
++    parser = json_parser_new ();\r
++\r
++    if (couchdb_session_send_message (couchdb_database_get_session (database),\r
++                                    SOUP_METHOD_GET, url, NULL, parser,\r
++                                    &error)) {\r
++      JsonNode *root_node;\r
++\r
++      root_node = json_parser_get_root (parser);\r
++      if (json_node_get_node_type (root_node) == JSON_NODE_OBJECT) {\r
++          JsonObject *root_object;\r
++          JsonArray *results;\r
++\r
++          root_object = json_node_get_object (root_node);\r
++          results = json_object_get_array_member (root_object, "results");\r
++          if (results) {\r
++              GList *json_elements, *sl;\r
++\r
++              json_elements = json_array_get_elements (results);\r
++              for (sl = json_elements; !interrupted && sl != NULL; sl = sl->next)\r
++                  couchdb_process_change (state, notmuch, database,\r
++                                          (JsonNode *) sl->data);\r
++              g_list_free (json_elements);\r
++          }\r
++\r
++          if (json_object_has_member (root_object, "last_seq"))\r
++              *last_seq = json_object_get_int_member (root_object, "last_seq");\r
++      }\r
++    }\r
++\r
++    /* Free memory */\r
++    g_object_unref (G_OBJECT (parser));\r
++    g_free (url);\r
++\r
++    return NOTMUCH_STATUS_SUCCESS;\r
++}\r
++\r
++/* Couchdb add_files function.\r
++ *\r
++ * Use the Couchdb _changes API to just ask what files have been added or deleted.\r
++ *\r
++ * We use a dummy "/" directory to store the last change we got from couch.\r
++ */\r
++static notmuch_status_t\r
++couchdb_add_files (notmuch_database_t *notmuch,\r
++                 notmuch_config_t *config,\r
++                 add_files_state_t *state)\r
++{\r
++    CouchdbSession *session;\r
++    CouchdbDatabase *database;\r
++    GError *error;\r
++    notmuch_directory_t *directory;\r
++    notmuch_status_t status;\r
++    time_t db_mtime;\r
++    guint32 last_seq = 0;\r
++    guint32 old_last_seq = 0;\r
++    const char *db_name;\r
++    const char *uri;\r
++\r
++    /* These are probably abstraction-breaking hacks. Life is tough. */\r
++    uri = notmuch_config_get_database_uri (config);\r
++    db_name = notmuch_config_get_database_name (config);\r
++\r
++    /* FIXME: is this necessary? I think probably not? */\r
++    /*\r
++    db_name = talloc_strdup (config, db_name);\r
++    uri = talloc_strdup (config, uri);\r
++    */\r
++\r
++    session = couchdb_session_new (uri);\r
++    database = couchdb_session_get_database (session, db_name, &error);\r
++    if (database == NULL) {\r
++      fprintf (stderr, "Error: couldn't access couchdb database %s, %s: %s",\r
++               uri, db_name, error->message);\r
++      return NOTMUCH_STATUS_FILE_ERROR;\r
++    }\r
++\r
++    /* Store a dummy directory at / that just contains last_seq as its mtime. */\r
++    directory = notmuch_database_get_directory (notmuch, "/");\r
++    db_mtime = notmuch_directory_get_mtime (directory);\r
++    last_seq = (int)db_mtime;\r
++\r
++    /* Grab updates in sets of 100 just to be safe with memory. */\r
++    do {\r
++      if (interrupted)\r
++          break;\r
++      old_last_seq = last_seq;\r
++      status = couchdb_add_messages_batch (state, notmuch, database,\r
++                                           &last_seq, 100);\r
++      if (status != NOTMUCH_STATUS_SUCCESS) {\r
++          return status;\r
++      }\r
++    } while (last_seq == old_last_seq + 100);\r
++\r
++    notmuch_directory_set_mtime (directory, last_seq);\r
++    g_object_unref (database);\r
++    g_object_unref (session);\r
++    notmuch_directory_destroy (directory);\r
++    return NOTMUCH_STATUS_SUCCESS;\r
++}\r
\r
+ /* Examine 'path' recursively as follows:\r
+  *\r
+@@ -678,6 +859,9 @@ add_files (notmuch_database_t *notmuch, notmuch_config_t *config,\r
+     if (strcmp (notmuch_config_get_database_type (config), "maildir") == 0)\r
+       return maildir_add_files (notmuch, path, state);\r
\r
++    else if (strcmp (notmuch_config_get_database_type (config), "couchdb") == 0)\r
++      return couchdb_add_files (notmuch, config, state);\r
++\r
+     /* Default case */\r
+     fprintf (stderr, "Could not add files for mailstore %s: unknown mailstore\n",\r
+            notmuch_config_get_database_type (config));\r
+-- \r
+1.7.5.4\r
+\r