libify: Move library sources down into lib directory.
authorCarl Worth <cworth@cworth.org>
Tue, 10 Nov 2009 00:12:28 +0000 (16:12 -0800)
committerCarl Worth <cworth@cworth.org>
Tue, 10 Nov 2009 00:24:03 +0000 (16:24 -0800)
A "make" invocation still works from the top-level, but not from
down inside the lib directory yet.

31 files changed:
Makefile
database-private.h [deleted file]
database.cc [deleted file]
index.cc [deleted file]
lib/database-private.h [new file with mode: 0644]
lib/database.cc [new file with mode: 0644]
lib/index.cc [new file with mode: 0644]
lib/libsha1.c [new file with mode: 0644]
lib/libsha1.h [new file with mode: 0644]
lib/message-file.c [new file with mode: 0644]
lib/message.cc [new file with mode: 0644]
lib/notmuch-private.h [new file with mode: 0644]
lib/notmuch.h [new file with mode: 0644]
lib/query.cc [new file with mode: 0644]
lib/sha1.c [new file with mode: 0644]
lib/tags.c [new file with mode: 0644]
lib/thread.cc [new file with mode: 0644]
lib/xutil.c [new file with mode: 0644]
lib/xutil.h [new file with mode: 0644]
libsha1.c [deleted file]
libsha1.h [deleted file]
message-file.c [deleted file]
message.cc [deleted file]
notmuch-private.h [deleted file]
notmuch.h [deleted file]
query.cc [deleted file]
sha1.c [deleted file]
tags.c [deleted file]
thread.cc [deleted file]
xutil.c [deleted file]
xutil.h [deleted file]

index 280b5566bb25e36239b280fbccba881c8e9c3fea..1e7f5f2c88a4e6d902573bf4cacc6d13caf03491 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ PROGS=notmuch
 
 WARN_FLAGS=-Wall -Wextra -Wmissing-declarations -Wwrite-strings -Wswitch-enum
 
-NOTMUCH_DEPENDS_FLAGS=`pkg-config --cflags glib-2.0 gmime-2.4 talloc`
+NOTMUCH_DEPENDS_FLAGS=-I./lib `pkg-config --cflags glib-2.0 gmime-2.4 talloc`
 NOTMUCH_CXX_DEPENDS_FLAGS=$(NOTMUCH_DEPENDS_FLAGS) `xapian-config --cxxflags`
 
 NOTMUCH_CFLAGS=$(WARN_FLAGS) -O0 -g $(NOTMUCH_DEPENDS_FLAGS)
@@ -11,17 +11,17 @@ NOTMUCH_CXXFLAGS=$(WARN_FLAGS) -O0 -g $(NOTMUCH_CXX_DEPENDS_FLAGS)
 NOTMUCH_LDFLAGS=`pkg-config --libs glib-2.0 gmime-2.4 talloc` \
                `xapian-config --libs`
 
-LIBRARY=               \
-       database.o      \
-       index.o         \
-       libsha1.o       \
-       message.o       \
-       message-file.o  \
-       query.o         \
-       sha1.o          \
-       tags.o          \
-       thread.o        \
-       xutil.o
+LIBRARY=                       \
+       lib/database.o          \
+       lib/index.o             \
+       lib/libsha1.o           \
+       lib/message.o           \
+       lib/message-file.o      \
+       lib/query.o             \
+       lib/sha1.o              \
+       lib/tags.o              \
+       lib/thread.o            \
+       lib/xutil.o
 
 MAIN=                  \
        notmuch.o
@@ -37,7 +37,7 @@ all: $(PROGS)
 notmuch: $(MAIN) $(LIBRARY)
        $(CC) $(NOTMUCH_LDFLAGS) $^ -o $@
 
-Makefile.dep: *.c *.cc
+Makefile.dep: *.c lib/*.c lib/*.cc
        $(CXX) -M $(CPPFLAGS) $(NOTMUCH_DEPENDS_FLAGS) \
        $(NOTMUCH_CXX_DEPENDS_FLAGS) $^ > $@
 -include Makefile.dep
@@ -52,4 +52,4 @@ install: all notmuch.1.gz
                $(DESTDIR)/etc/bash_completion.d/notmuch
 
 clean:
-       rm -f $(PROGS) *.o Makefile.dep
+       rm -f $(PROGS) lib/*.o *.o Makefile.dep
diff --git a/database-private.h b/database-private.h
deleted file mode 100644 (file)
index 76e26ce..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/* database-private.h - For peeking into the internals of notmuch_database_t
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#ifndef NOTMUCH_DATABASE_PRIVATE_H
-#define NOTMUCH_DATABASE_PRIVATE_H
-
-#include "notmuch-private.h"
-
-#include <xapian.h>
-
-struct _notmuch_database {
-    char *path;
-    Xapian::WritableDatabase *xapian_db;
-    Xapian::QueryParser *query_parser;
-    Xapian::TermGenerator *term_gen;
-};
-
-#endif
diff --git a/database.cc b/database.cc
deleted file mode 100644 (file)
index 4524016..0000000
+++ /dev/null
@@ -1,987 +0,0 @@
-/* database.cc - The database interfaces of the notmuch mail library
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#include "database-private.h"
-
-#include <iostream>
-
-#include <xapian.h>
-
-#include <glib.h> /* g_free, GPtrArray, GHashTable */
-
-using namespace std;
-
-#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
-
-typedef struct {
-    const char *name;
-    const char *prefix;
-} prefix_t;
-
-/* Here's the current schema for our database:
- *
- * We currently have two different types of documents: mail and timestamps.
- *
- * Mail document
- * -------------
- * A mail document is associated with a particular email message file
- * on disk. It is indexed with the following prefixed terms:
- *
- *    Single terms of given prefix:
- *
- *     type:   mail
- *
- *     id:     Unique ID of mail, (from Message-ID header or generated
- *             as "notmuch-sha1-<sha1_sum_of_entire_file>.
- *
- *     thread: The ID of the thread to which the mail belongs
- *
- *    Multiple terms of given prefix:
- *
- *     ref:    All unresolved message IDs from In-Reply-To and
- *             References headers in the message. (Once a referenced
- *             message is added to the database and the thread IDs
- *             are linked the corresponding "ref" term is dropped
- *             from the message document.)
- *
- *     tag:    Any tags associated with this message by the user.
- *
- *    A mail document also has two values:
- *
- *     TIMESTAMP:      The time_t value corresponding to the message's
- *                     Date header.
- *
- *     MESSAGE_ID:     The unique ID of the mail mess (see "id" above)
- *
- * Timestamp document
- * ------------------
- * A timestamp document is used by a client of the notmuch library to
- * maintain data necessary to allow for efficient polling of mail
- * directories. The notmuch library does no interpretation of
- * timestamps, but merely allows the user to store and retrieve
- * timestamps as name/value pairs.
- *
- * The timestamp document is indexed with a single prefixed term:
- *
- *     timestamp:      The user's key value (likely a directory name)
- *
- * and has a single value:
- *
- *     TIMESTAMP:      The time_t value from the user.
- */
-
-/* With these prefix values we follow the conventions published here:
- *
- * http://xapian.org/docs/omega/termprefixes.html
- *
- * as much as makes sense. Note that I took some liberty in matching
- * the reserved prefix values to notmuch concepts, (for example, 'G'
- * is documented as "newsGroup (or similar entity - e.g. a web forum
- * name)", for which I think the thread is the closest analogue in
- * notmuch. This in spite of the fact that we will eventually be
- * storing mailing-list messages where 'G' for "mailing list name"
- * might be even a closer analogue. I'm treating the single-character
- * prefixes preferentially for core notmuch concepts (which will be
- * nearly universal to all mail messages).
- */
-
-prefix_t BOOLEAN_PREFIX_INTERNAL[] = {
-    { "type", "T" },
-    { "ref", "XREFERENCE" },
-    { "replyto", "XREPLYTO" },
-    { "timestamp", "XTIMESTAMP" },
-    { "contact", "XCONTACT" }
-};
-
-prefix_t BOOLEAN_PREFIX_EXTERNAL[] = {
-    { "thread", "G" },
-    { "tag", "K" },
-    { "id", "Q" }
-};
-
-prefix_t PROBABILISTIC_PREFIX[]= {
-    { "from", "XFROM" },
-    { "to", "XTO" },
-    { "attachment", "XATTACHMENT" },
-    { "subject", "XSUBJECT"}
-};
-
-int
-_internal_error (const char *format, ...)
-{
-    va_list va_args;
-
-    va_start (va_args, format);
-
-    fprintf (stderr, "Internal error: ");
-    vfprintf (stderr, format, va_args);
-
-    exit (1);
-
-    return 1;
-}
-
-const char *
-_find_prefix (const char *name)
-{
-    unsigned int i;
-
-    for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_INTERNAL); i++)
-       if (strcmp (name, BOOLEAN_PREFIX_INTERNAL[i].name) == 0)
-           return BOOLEAN_PREFIX_INTERNAL[i].prefix;
-
-    for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_EXTERNAL); i++)
-       if (strcmp (name, BOOLEAN_PREFIX_EXTERNAL[i].name) == 0)
-           return BOOLEAN_PREFIX_EXTERNAL[i].prefix;
-
-    for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++)
-       if (strcmp (name, PROBABILISTIC_PREFIX[i].name) == 0)
-           return PROBABILISTIC_PREFIX[i].prefix;
-
-    INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
-
-    return "";
-}
-
-const char *
-notmuch_status_to_string (notmuch_status_t status)
-{
-    switch (status) {
-    case NOTMUCH_STATUS_SUCCESS:
-       return "No error occurred";
-    case NOTMUCH_STATUS_OUT_OF_MEMORY:
-       return "Out of memory";
-    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
-       return "A Xapian exception occurred";
-    case NOTMUCH_STATUS_FILE_ERROR:
-       return "Something went wrong trying to read or write a file";
-    case NOTMUCH_STATUS_FILE_NOT_EMAIL:
-       return "File is not an email";
-    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
-       return "Message ID is identical to a message in database";
-    case NOTMUCH_STATUS_NULL_POINTER:
-       return "Erroneous NULL pointer";
-    case NOTMUCH_STATUS_TAG_TOO_LONG:
-       return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)";
-    case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
-       return "Unblanced number of calls to notmuch_message_freeze/thaw";
-    default:
-    case NOTMUCH_STATUS_LAST_STATUS:
-       return "Unknown error status value";
-    }
-}
-
-static void
-find_doc_ids (notmuch_database_t *notmuch,
-             const char *prefix_name,
-             const char *value,
-             Xapian::PostingIterator *begin,
-             Xapian::PostingIterator *end)
-{
-    Xapian::PostingIterator i;
-    char *term;
-
-    term = talloc_asprintf (notmuch, "%s%s",
-                           _find_prefix (prefix_name), value);
-
-    *begin = notmuch->xapian_db->postlist_begin (term);
-
-    *end = notmuch->xapian_db->postlist_end (term);
-
-    talloc_free (term);
-}
-
-static notmuch_private_status_t
-find_unique_doc_id (notmuch_database_t *notmuch,
-                   const char *prefix_name,
-                   const char *value,
-                   unsigned int *doc_id)
-{
-    Xapian::PostingIterator i, end;
-
-    find_doc_ids (notmuch, prefix_name, value, &i, &end);
-
-    if (i == end) {
-       *doc_id = 0;
-       return NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
-    } else {
-       *doc_id = *i;
-       return NOTMUCH_PRIVATE_STATUS_SUCCESS;
-    }
-}
-
-static Xapian::Document
-find_document_for_doc_id (notmuch_database_t *notmuch, unsigned doc_id)
-{
-    return notmuch->xapian_db->get_document (doc_id);
-}
-
-static notmuch_private_status_t
-find_unique_document (notmuch_database_t *notmuch,
-                     const char *prefix_name,
-                     const char *value,
-                     Xapian::Document *document,
-                     unsigned int *doc_id)
-{
-    notmuch_private_status_t status;
-
-    status = find_unique_doc_id (notmuch, prefix_name, value, doc_id);
-
-    if (status) {
-       *document = Xapian::Document ();
-       return status;
-    }
-
-    *document = find_document_for_doc_id (notmuch, *doc_id);
-    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
-}
-
-notmuch_message_t *
-notmuch_database_find_message (notmuch_database_t *notmuch,
-                              const char *message_id)
-{
-    notmuch_private_status_t status;
-    unsigned int doc_id;
-
-    status = find_unique_doc_id (notmuch, "id", message_id, &doc_id);
-
-    if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
-       return NULL;
-
-    return _notmuch_message_create (notmuch, notmuch, doc_id, NULL);
-}
-
-/* Advance 'str' past any whitespace or RFC 822 comments. A comment is
- * a (potentially nested) parenthesized sequence with '\' used to
- * escape any character (including parentheses).
- *
- * If the sequence to be skipped continues to the end of the string,
- * then 'str' will be left pointing at the final terminating '\0'
- * character.
- */
-static void
-skip_space_and_comments (const char **str)
-{
-    const char *s;
-
-    s = *str;
-    while (*s && (isspace (*s) || *s == '(')) {
-       while (*s && isspace (*s))
-           s++;
-       if (*s == '(') {
-           int nesting = 1;
-           s++;
-           while (*s && nesting) {
-               if (*s == '(')
-                   nesting++;
-               else if (*s == ')')
-                   nesting--;
-               else if (*s == '\\')
-                   if (*(s+1))
-                       s++;
-               s++;
-           }
-       }
-    }
-
-    *str = s;
-}
-
-/* Parse an RFC 822 message-id, discarding whitespace, any RFC 822
- * comments, and the '<' and '>' delimeters.
- *
- * If not NULL, then *next will be made to point to the first character
- * not parsed, (possibly pointing to the final '\0' terminator.
- *
- * Returns a newly talloc'ed string belonging to 'ctx'.
- *
- * Returns NULL if there is any error parsing the message-id. */
-static char *
-parse_message_id (void *ctx, const char *message_id, const char **next)
-{
-    const char *s, *end;
-    char *result;
-
-    if (message_id == NULL)
-       return NULL;
-
-    s = message_id;
-
-    skip_space_and_comments (&s);
-
-    /* Skip any unstructured text as well. */
-    while (*s && *s != '<')
-       s++;
-
-    if (*s == '<') {
-       s++;
-    } else {
-       if (next)
-           *next = s;
-       return NULL;
-    }
-
-    skip_space_and_comments (&s);
-
-    end = s;
-    while (*end && *end != '>')
-       end++;
-    if (next) {
-       if (*end)
-           *next = end + 1;
-       else
-           *next = end;
-    }
-
-    if (end > s && *end == '>')
-       end--;
-    if (end <= s)
-       return NULL;
-
-    result = talloc_strndup (ctx, s, end - s + 1);
-
-    /* Finally, collapse any whitespace that is within the message-id
-     * itself. */
-    {
-       char *r;
-       int len;
-
-       for (r = result, len = strlen (r); *r; r++, len--)
-           if (*r == ' ' || *r == '\t')
-               memmove (r, r+1, len);
-    }
-
-    return result;
-}
-
-/* Parse a References header value, putting a (talloc'ed under 'ctx')
- * copy of each referenced message-id into 'hash'. */
-static void
-parse_references (void *ctx,
-                 GHashTable *hash,
-                 const char *refs)
-{
-    char *ref;
-
-    if (refs == NULL)
-       return;
-
-    while (*refs) {
-       ref = parse_message_id (ctx, refs, &refs);
-
-       if (ref)
-           g_hash_table_insert (hash, ref, NULL);
-    }
-}
-
-char *
-notmuch_database_default_path (void)
-{
-    char *path;
-
-    if (getenv ("NOTMUCH_BASE"))
-       return strdup (getenv ("NOTMUCH_BASE"));
-
-    if (asprintf (&path, "%s/mail", getenv ("HOME")) == -1) {
-       fprintf (stderr, "Out of memory.\n");
-       return xstrdup("");
-    }
-
-    return path;
-}
-
-notmuch_database_t *
-notmuch_database_create (const char *path)
-{
-    notmuch_database_t *notmuch = NULL;
-    char *notmuch_path = NULL;
-    struct stat st;
-    int err;
-    char *local_path = NULL;
-
-    if (path == NULL)
-       path = local_path = notmuch_database_default_path ();
-
-    err = stat (path, &st);
-    if (err) {
-       fprintf (stderr, "Error: Cannot create database at %s: %s.\n",
-                path, strerror (errno));
-       goto DONE;
-    }
-
-    if (! S_ISDIR (st.st_mode)) {
-       fprintf (stderr, "Error: Cannot create database at %s: Not a directory.\n",
-                path);
-       goto DONE;
-    }
-
-    notmuch_path = talloc_asprintf (NULL, "%s/%s", path, ".notmuch");
-
-    err = mkdir (notmuch_path, 0755);
-
-    if (err) {
-       fprintf (stderr, "Error: Cannot create directory %s: %s.\n",
-                notmuch_path, strerror (errno));
-       goto DONE;
-    }
-
-    notmuch = notmuch_database_open (path);
-
-  DONE:
-    if (notmuch_path)
-       talloc_free (notmuch_path);
-    if (local_path)
-       free (local_path);
-
-    return notmuch;
-}
-
-notmuch_database_t *
-notmuch_database_open (const char *path)
-{
-    notmuch_database_t *notmuch = NULL;
-    char *notmuch_path = NULL, *xapian_path = NULL;
-    struct stat st;
-    int err;
-    char *local_path = NULL;
-    unsigned int i;
-
-    if (path == NULL)
-       path = local_path = notmuch_database_default_path ();
-
-    if (asprintf (&notmuch_path, "%s/%s", path, ".notmuch") == -1) {
-       notmuch_path = NULL;
-       fprintf (stderr, "Out of memory\n");
-       goto DONE;
-    }
-
-    err = stat (notmuch_path, &st);
-    if (err) {
-       fprintf (stderr, "Error opening database at %s: %s\n",
-                notmuch_path, strerror (errno));
-       goto DONE;
-    }
-
-    if (asprintf (&xapian_path, "%s/%s", notmuch_path, "xapian") == -1) {
-       xapian_path = NULL;
-       fprintf (stderr, "Out of memory\n");
-       goto DONE;
-    }
-
-    notmuch = talloc (NULL, notmuch_database_t);
-    notmuch->path = talloc_strdup (notmuch, path);
-
-    if (notmuch->path[strlen (notmuch->path) - 1] == '/')
-       notmuch->path[strlen (notmuch->path) - 1] = '\0';
-
-    try {
-       notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
-                                                          Xapian::DB_CREATE_OR_OPEN);
-       notmuch->query_parser = new Xapian::QueryParser;
-       notmuch->term_gen = new Xapian::TermGenerator;
-       notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
-
-       notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
-       notmuch->query_parser->set_database (*notmuch->xapian_db);
-       notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
-       notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
-
-       for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_EXTERNAL); i++) {
-           prefix_t *prefix = &BOOLEAN_PREFIX_EXTERNAL[i];
-           notmuch->query_parser->add_boolean_prefix (prefix->name,
-                                                      prefix->prefix);
-       }
-
-       for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++) {
-           prefix_t *prefix = &PROBABILISTIC_PREFIX[i];
-           notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
-       }
-    } catch (const Xapian::Error &error) {
-       fprintf (stderr, "A Xapian exception occurred: %s\n",
-                error.get_msg().c_str());
-       notmuch = NULL;
-    }
-    
-  DONE:
-    if (local_path)
-       free (local_path);
-    if (notmuch_path)
-       free (notmuch_path);
-    if (xapian_path)
-       free (xapian_path);
-
-    return notmuch;
-}
-
-void
-notmuch_database_close (notmuch_database_t *notmuch)
-{
-    notmuch->xapian_db->flush ();
-
-    delete notmuch->term_gen;
-    delete notmuch->query_parser;
-    delete notmuch->xapian_db;
-    talloc_free (notmuch);
-}
-
-const char *
-notmuch_database_get_path (notmuch_database_t *notmuch)
-{
-    return notmuch->path;
-}
-
-static notmuch_private_status_t
-find_timestamp_document (notmuch_database_t *notmuch, const char *db_key,
-                        Xapian::Document *doc, unsigned int *doc_id)
-{
-    return find_unique_document (notmuch, "timestamp", db_key, doc, doc_id);
-}
-
-/* We allow the user to use arbitrarily long keys for timestamps,
- * (they're for filesystem paths after all, which have no limit we
- * know about). But we have a term-length limit. So if we exceed that,
- * we'll use the SHA-1 of the user's key as the actual key for
- * constructing a database term.
- *
- * Caution: This function returns a newly allocated string which the
- * caller should free() when finished.
- */
-static char *
-timestamp_db_key (const char *key)
-{
-    int term_len = strlen (_find_prefix ("timestamp")) + strlen (key);
-
-    if (term_len > NOTMUCH_TERM_MAX)
-       return notmuch_sha1_of_string (key);
-    else
-       return strdup (key);
-}
-
-notmuch_status_t
-notmuch_database_set_timestamp (notmuch_database_t *notmuch,
-                               const char *key, time_t timestamp)
-{
-    Xapian::Document doc;
-    unsigned int doc_id;
-    notmuch_private_status_t status;
-    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-    char *db_key = NULL;
-
-    db_key = timestamp_db_key (key);
-
-    try {
-       status = find_timestamp_document (notmuch, db_key, &doc, &doc_id);
-
-       doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
-                      Xapian::sortable_serialise (timestamp));
-
-       if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
-           char *term = talloc_asprintf (NULL, "%s%s",
-                                         _find_prefix ("timestamp"), db_key);
-           doc.add_term (term);
-           talloc_free (term);
-
-           notmuch->xapian_db->add_document (doc);
-       } else {
-           notmuch->xapian_db->replace_document (doc_id, doc);
-       }
-
-    } catch (Xapian::Error &error) {
-       fprintf (stderr, "A Xapian exception occurred: %s.\n",
-                error.get_msg().c_str());
-       ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
-    }
-
-    if (db_key)
-       free (db_key);
-
-    return ret;
-}
-
-time_t
-notmuch_database_get_timestamp (notmuch_database_t *notmuch, const char *key)
-{
-    Xapian::Document doc;
-    unsigned int doc_id;
-    notmuch_private_status_t status;
-    char *db_key = NULL;
-    time_t ret = 0;
-
-    db_key = timestamp_db_key (key);
-
-    try {
-       status = find_timestamp_document (notmuch, db_key, &doc, &doc_id);
-
-       if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
-           goto DONE;
-
-       ret =  Xapian::sortable_unserialise (doc.get_value (NOTMUCH_VALUE_TIMESTAMP));
-    } catch (Xapian::Error &error) {
-       goto DONE;
-    }
-
-  DONE:
-    if (db_key)
-       free (db_key);
-
-    return ret;
-}
-
-/* Find the thread ID to which the message with 'message_id' belongs.
- *
- * Returns NULL if no message with message ID 'message_id' is in the
- * database.
- *
- * Otherwise, returns a newly talloced string belonging to 'ctx'.
- */
-static const char *
-_resolve_message_id_to_thread_id (notmuch_database_t *notmuch,
-                                 void *ctx,
-                                 const char *message_id)
-{
-    notmuch_message_t *message;
-    const char *ret = NULL;
-
-    message = notmuch_database_find_message (notmuch, message_id);
-    if (message == NULL)
-       goto DONE;
-
-    ret = talloc_steal (ctx, notmuch_message_get_thread_id (message));
-
-  DONE:
-    if (message)
-       notmuch_message_destroy (message);
-
-    return ret;
-}
-
-static notmuch_status_t
-_merge_threads (notmuch_database_t *notmuch,
-               const char *winner_thread_id,
-               const char *loser_thread_id)
-{
-    Xapian::PostingIterator loser, loser_end;
-    notmuch_message_t *message = NULL;
-    notmuch_private_status_t private_status;
-    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-
-    find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end);
-
-    for ( ; loser != loser_end; loser++) {
-       message = _notmuch_message_create (notmuch, notmuch,
-                                          *loser, &private_status);
-       if (message == NULL) {
-           ret = COERCE_STATUS (private_status,
-                                "Cannot find document for doc_id from query");
-           goto DONE;
-       }
-
-       _notmuch_message_remove_term (message, "thread", loser_thread_id);
-       _notmuch_message_add_term (message, "thread", winner_thread_id);
-       _notmuch_message_sync (message);
-
-       notmuch_message_destroy (message);
-       message = NULL;
-    }
-
-  DONE:
-    if (message)
-       notmuch_message_destroy (message);
-
-    return ret;
-}
-
-static void
-_my_talloc_free_for_g_hash (void *ptr)
-{
-    talloc_free (ptr);
-}
-
-static notmuch_status_t
-_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
-                                          notmuch_message_t *message,
-                                          notmuch_message_file_t *message_file,
-                                          const char **thread_id)
-{
-    GHashTable *parents = NULL;
-    const char *refs, *in_reply_to;
-    GList *l, *keys = NULL;
-    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-
-    parents = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                    _my_talloc_free_for_g_hash, NULL);
-
-    refs = notmuch_message_file_get_header (message_file, "references");
-    parse_references (message, parents, refs);
-
-    in_reply_to = notmuch_message_file_get_header (message_file, "in-reply-to");
-    parse_references (message, parents, in_reply_to);
-    _notmuch_message_add_term (message, "replyto",
-                              parse_message_id (message, in_reply_to, NULL));
-
-    keys = g_hash_table_get_keys (parents);
-    for (l = keys; l; l = l->next) {
-       char *parent_message_id;
-       const char *parent_thread_id;
-
-       parent_message_id = (char *) l->data;
-       parent_thread_id = _resolve_message_id_to_thread_id (notmuch,
-                                                            message,
-                                                            parent_message_id);
-
-       if (parent_thread_id == NULL) {
-           _notmuch_message_add_term (message, "ref", parent_message_id);
-       } else {
-           if (*thread_id == NULL) {
-               *thread_id = talloc_strdup (message, parent_thread_id);
-               _notmuch_message_add_term (message, "thread", *thread_id);
-           } else if (strcmp (*thread_id, parent_thread_id)) {
-               ret = _merge_threads (notmuch, *thread_id, parent_thread_id);
-               if (ret)
-                   goto DONE;
-           }
-       }
-    }
-
-  DONE:
-    if (keys)
-       g_list_free (keys);
-    if (parents)
-       g_hash_table_unref (parents);
-
-    return ret;
-}
-
-static notmuch_status_t
-_notmuch_database_link_message_to_children (notmuch_database_t *notmuch,
-                                           notmuch_message_t *message,
-                                           const char **thread_id)
-{
-    const char *message_id = notmuch_message_get_message_id (message);
-    Xapian::PostingIterator child, children_end;
-    notmuch_message_t *child_message = NULL;
-    const char *child_thread_id;
-    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-    notmuch_private_status_t private_status;
-
-    find_doc_ids (notmuch, "ref", message_id, &child, &children_end);
-
-    for ( ; child != children_end; child++) {
-
-       child_message = _notmuch_message_create (message, notmuch,
-                                                *child, &private_status);
-       if (child_message == NULL) {
-           ret = COERCE_STATUS (private_status,
-                                "Cannot find document for doc_id from query");
-           goto DONE;
-       }
-
-       child_thread_id = notmuch_message_get_thread_id (child_message);
-       if (*thread_id == NULL) {
-           *thread_id = talloc_strdup (message, child_thread_id);
-           _notmuch_message_add_term (message, "thread", *thread_id);
-       } else if (strcmp (*thread_id, child_thread_id)) {
-           _notmuch_message_remove_term (child_message, "ref",
-                                         message_id);
-           _notmuch_message_sync (child_message);
-           ret = _merge_threads (notmuch, *thread_id, child_thread_id);
-           if (ret)
-               goto DONE;
-       }
-
-       notmuch_message_destroy (child_message);
-       child_message = NULL;
-    }
-
-  DONE:
-    if (child_message)
-       notmuch_message_destroy (child_message);
-
-    return ret;
-}
-
-/* Given a (mostly empty) 'message' and its corresponding
- * 'message_file' link it to existing threads in the database.
- *
- * We first looke at 'message_file' and its link-relevant headers
- * (References and In-Reply-To) for message IDs. We also look in the
- * database for existing message that reference 'message'.p
- *
- * The end result is to call _notmuch_message_add_thread_id with one
- * or more thread IDs to which this message belongs, (including
- * generating a new thread ID if necessary if the message doesn't
- * connect to any existing threads).
- */
-static notmuch_status_t
-_notmuch_database_link_message (notmuch_database_t *notmuch,
-                               notmuch_message_t *message,
-                               notmuch_message_file_t *message_file)
-{
-    notmuch_status_t status;
-    const char *thread_id = NULL;
-
-    status = _notmuch_database_link_message_to_parents (notmuch, message,
-                                                       message_file,
-                                                       &thread_id);
-    if (status)
-       return status;
-
-    status = _notmuch_database_link_message_to_children (notmuch, message,
-                                                        &thread_id);
-    if (status)
-       return status;
-
-    if (thread_id == NULL)
-       _notmuch_message_ensure_thread_id (message);
-
-    return NOTMUCH_STATUS_SUCCESS;
-}
-
-notmuch_status_t
-notmuch_database_add_message (notmuch_database_t *notmuch,
-                             const char *filename,
-                             notmuch_message_t **message_ret)
-{
-    notmuch_message_file_t *message_file;
-    notmuch_message_t *message = NULL;
-    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-    notmuch_private_status_t private_status;
-
-    const char *date, *header;
-    const char *from, *to, *subject;
-    char *message_id;
-
-    if (message_ret)
-       *message_ret = NULL;
-
-    message_file = notmuch_message_file_open (filename);
-    if (message_file == NULL) {
-       ret = NOTMUCH_STATUS_FILE_ERROR;
-       goto DONE;
-    }
-
-    notmuch_message_file_restrict_headers (message_file,
-                                          "date",
-                                          "from",
-                                          "in-reply-to",
-                                          "message-id",
-                                          "references",
-                                          "subject",
-                                          "to",
-                                          (char *) NULL);
-
-    try {
-       /* Before we do any real work, (especially before doing a
-        * potential SHA-1 computation on the entire file's contents),
-        * let's make sure that what we're looking at looks like an
-        * actual email message.
-        */
-       from = notmuch_message_file_get_header (message_file, "from");
-       subject = notmuch_message_file_get_header (message_file, "subject");
-       to = notmuch_message_file_get_header (message_file, "to");
-
-       if (from == NULL &&
-           subject == NULL &&
-           to == NULL)
-       {
-           ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
-           goto DONE;
-       }
-
-       /* Now that we're sure it's mail, the first order of business
-        * is to find a message ID (or else create one ourselves). */
-
-       header = notmuch_message_file_get_header (message_file, "message-id");
-       if (header) {
-           message_id = parse_message_id (message_file, header, NULL);
-           /* So the header value isn't RFC-compliant, but it's
-            * better than no message-id at all. */
-           if (message_id == NULL)
-               message_id = talloc_strdup (message_file, header);
-       } else {
-           /* No message-id at all, let's generate one by taking a
-            * hash over the file's contents. */
-           char *sha1 = notmuch_sha1_of_file (filename);
-
-           /* If that failed too, something is really wrong. Give up. */
-           if (sha1 == NULL) {
-               ret = NOTMUCH_STATUS_FILE_ERROR;
-               goto DONE;
-           }
-
-           message_id = talloc_asprintf (message_file,
-                                         "notmuch-sha1-%s", sha1);
-           free (sha1);
-       }
-
-       /* Now that we have a message ID, we get a message object,
-        * (which may or may not reference an existing document in the
-        * database). */
-
-       /* Use NULL for owner since we want to free this locally. */
-       message = _notmuch_message_create_for_message_id (NULL,
-                                                         notmuch,
-                                                         message_id,
-                                                         &private_status);
-
-       talloc_free (message_id);
-
-       if (message == NULL)
-           goto DONE;
-
-       /* Is this a newly created message object? */
-       if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
-           _notmuch_message_set_filename (message, filename);
-           _notmuch_message_add_term (message, "type", "mail");
-       } else {
-           ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
-           goto DONE;
-       }
-
-       ret = _notmuch_database_link_message (notmuch, message, message_file);
-       if (ret)
-           goto DONE;
-
-       date = notmuch_message_file_get_header (message_file, "date");
-       _notmuch_message_set_date (message, date);
-
-       _notmuch_message_index_file (message, filename);
-
-       _notmuch_message_sync (message);
-    } catch (const Xapian::Error &error) {
-       fprintf (stderr, "A Xapian exception occurred: %s.\n",
-                error.get_msg().c_str());
-       ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
-       goto DONE;
-    }
-
-  DONE:
-    if (message) {
-       if (ret == NOTMUCH_STATUS_SUCCESS && message_ret)
-           *message_ret = message;
-       else
-           notmuch_message_destroy (message);
-    }
-
-    if (message_file)
-       notmuch_message_file_close (message_file);
-
-    return ret;
-}
diff --git a/index.cc b/index.cc
deleted file mode 100644 (file)
index 747a4e6..0000000
--- a/index.cc
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#include "notmuch-private.h"
-
-#include <gmime/gmime.h>
-
-#include <xapian.h>
-
-/* We're finally down to a single (NAME + address) email "mailbox". */
-static void
-_index_address_mailbox (notmuch_message_t *message,
-                       const char *prefix_name,
-                       InternetAddress *address)
-{
-    InternetAddressMailbox *mailbox = INTERNET_ADDRESS_MAILBOX (address);
-    const char *name, *addr;
-    char *contact;
-    int own_name = 0;
-
-    name = internet_address_get_name (address);
-    addr = internet_address_mailbox_get_addr (mailbox);
-
-    if (addr) {
-       if (name) {
-           contact = talloc_asprintf (message, "\"%s\" <%s>",
-                                      name, addr);
-           _notmuch_message_add_term (message, "contact", contact);
-           talloc_free (contact);
-       } else {
-           _notmuch_message_add_term (message, "contact", addr);
-       }
-    }
-
-    /* In the absence of a name, we'll strip the part before the @
-     * from the address. */
-    if (! name) {
-       const char *at;
-
-       at = strchr (addr, '@');
-       if (at) {
-           name = strndup (addr, at - addr);
-           own_name = 1;
-       }
-    }
-
-    if (name)
-       _notmuch_message_gen_terms (message, prefix_name, name);
-    if (addr)
-       _notmuch_message_gen_terms (message, prefix_name, addr);
-}
-
-static void
-_index_address_list (notmuch_message_t *message,
-                    const char *prefix_name,
-                    InternetAddressList *addresses);
-
-/* The outer loop over the InternetAddressList wasn't quite enough.
- * There can actually be a tree here where a single member of the list
- * is a "group" containing another list. Recurse please.
- */
-static void
-_index_address_group (notmuch_message_t *message,
-                     const char *prefix_name,
-                     InternetAddress *address)
-{
-    InternetAddressGroup *group;
-    InternetAddressList *list;
-
-    group = INTERNET_ADDRESS_GROUP (address);
-    list = internet_address_group_get_members (group);
-
-    if (! list)
-       return;
-
-    _index_address_list (message, prefix_name, list);
-}
-
-static void
-_index_address_list (notmuch_message_t *message,
-                    const char *prefix_name,
-                    InternetAddressList *addresses)
-{
-    int i;
-    InternetAddress *address;
-
-    if (addresses == NULL)
-       return;
-
-    for (i = 0; i < internet_address_list_length (addresses); i++) {
-       address = internet_address_list_get_address (addresses, i);
-       if (INTERNET_ADDRESS_IS_MAILBOX (address)) {
-           _index_address_mailbox (message, prefix_name, address);
-       } else if (INTERNET_ADDRESS_IS_GROUP (address)) {
-           _index_address_group (message, prefix_name, address);
-       } else {
-           INTERNAL_ERROR ("GMime InternetAddress is neither a mailbox nor a group.\n");
-       }
-    }
-}
-
-static const char *
-skip_re_in_subject (const char *subject)
-{
-    const char *s = subject;
-
-    if (subject == NULL)
-       return NULL;
-
-    while (*s) {
-       while (*s && isspace (*s))
-           s++;
-       if (strncasecmp (s, "re:", 3) == 0)
-           s += 3;
-       else
-           break;
-    }
-
-    return s;
-}
-
-/* Given a string representing the body of a message, generate terms
- * for it, (skipping quoted portions and signatures).
- *
- * This function is evil in that it modifies the string passed to it,
- * (changing some newlines into '\0').
- */
-static void
-_index_body_text (notmuch_message_t *message, char *body)
-{
-    char *line, *line_end, *next_line;
-
-    if (body == NULL)
-       return;
-
-    next_line = body;
-
-    while (1) {
-       line = next_line;
-       if (*line == '\0')
-           break;
-
-       next_line = strchr (line, '\n');
-       if (next_line == NULL) {
-           next_line = line + strlen (line);
-       }
-       line_end = next_line - 1;
-
-       /* Get to the next non-blank line. */
-       while (*next_line == '\n')
-           next_line++;
-
-       /* Skip blank lines. */
-       if (line_end < line)
-           continue;
-
-       /* Skip lines that are quotes. */
-       if (*line == '>')
-           continue;
-
-       /* Also skip lines introducing a quote on the next line. */
-       if (*line_end == ':' && *next_line == '>')
-           continue;
-
-       /* Finally, bail as soon as we see a signature. */
-       /* XXX: Should only do this if "near" the end of the message. */
-       if (strncmp (line, "-- ", 3) == 0)
-           break;
-
-       *(line_end + 1) = '\0';
-
-       _notmuch_message_gen_terms (message, NULL, line);
-    }
-}
-
-/* Callback to generate terms for each mime part of a message. */
-static void
-_index_mime_part (notmuch_message_t *message,
-                 GMimeObject *part)
-{
-    GMimeStream *stream;
-    GMimeDataWrapper *wrapper;
-    GByteArray *byte_array;
-    GMimeContentDisposition *disposition;
-    char *body;
-
-    if (GMIME_IS_MULTIPART (part)) {
-       GMimeMultipart *multipart = GMIME_MULTIPART (part);
-       int i;
-
-       for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
-           if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
-               /* Don't index the signature. */
-               if (i == 1)
-                   continue;
-               if (i > 1)
-                   fprintf (stderr, "Warning: Unexpected extra parts of mutlipart/signed. Indexing anyway.\n");
-           }
-           _index_mime_part (message,
-                             g_mime_multipart_get_part (multipart, i));
-       }
-       return;
-    }
-
-    if (GMIME_IS_MESSAGE_PART (part)) {
-       GMimeMessage *mime_message;
-
-       mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
-
-       _index_mime_part (message, g_mime_message_get_mime_part (mime_message));
-
-       return;
-    }
-
-    if (! (GMIME_IS_PART (part))) {
-       fprintf (stderr, "Warning: Not indexing unknown mime part: %s.\n",
-                g_type_name (G_OBJECT_TYPE (part)));
-       return;
-    }
-
-    disposition = g_mime_object_get_content_disposition (part);
-    if (disposition &&
-       strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
-    {
-       const char *filename = g_mime_part_get_filename (GMIME_PART (part));
-
-       _notmuch_message_add_term (message, "tag", "attachment");
-       _notmuch_message_gen_terms (message, "attachment", filename);
-
-       /* XXX: Would be nice to call out to something here to parse
-        * the attachment into text and then index that. */
-       return;
-    }
-
-    byte_array = g_byte_array_new ();
-
-    stream = g_mime_stream_mem_new_with_byte_array (byte_array);
-    g_mime_stream_mem_set_owner (GMIME_STREAM_MEM (stream), FALSE);
-    wrapper = g_mime_part_get_content_object (GMIME_PART (part));
-    if (wrapper)
-       g_mime_data_wrapper_write_to_stream (wrapper, stream);
-
-    g_object_unref (stream);
-
-    g_byte_array_append (byte_array, (guint8 *) "\0", 1);
-    body = (char *) g_byte_array_free (byte_array, FALSE);
-
-    _index_body_text (message, body);
-
-    free (body);
-}
-
-notmuch_status_t
-_notmuch_message_index_file (notmuch_message_t *message,
-                            const char *filename)
-{
-    GMimeStream *stream = NULL;
-    GMimeParser *parser = NULL;
-    GMimeMessage *mime_message = NULL;
-    InternetAddressList *addresses;
-    FILE *file = NULL;
-    const char *from, *subject;
-    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-    static int initialized = 0;
-
-    if (! initialized) {
-       g_mime_init (0);
-       initialized = 1;
-    }
-
-    file = fopen (filename, "r");
-    if (! file) {
-       fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
-       ret = NOTMUCH_STATUS_FILE_ERROR;
-       goto DONE;
-    }
-
-    /* Evil GMime steals my FILE* here so I won't fclose it. */
-    stream = g_mime_stream_file_new (file);
-
-    parser = g_mime_parser_new_with_stream (stream);
-
-    mime_message = g_mime_parser_construct_message (parser);
-
-    from = g_mime_message_get_sender (mime_message);
-    addresses = internet_address_list_parse_string (from);
-
-    _index_address_list (message, "from", addresses);
-
-    addresses = g_mime_message_get_all_recipients (mime_message);
-    _index_address_list (message, "to", addresses);
-
-    subject = g_mime_message_get_subject (mime_message);
-    subject = skip_re_in_subject (subject);
-    _notmuch_message_gen_terms (message, "subject", subject);
-
-    _index_mime_part (message, g_mime_message_get_mime_part (mime_message));
-
-  DONE:
-    if (mime_message)
-       g_object_unref (mime_message);
-
-    if (parser)
-       g_object_unref (parser);
-
-    if (stream)
-       g_object_unref (stream);
-
-    return ret;
-}
diff --git a/lib/database-private.h b/lib/database-private.h
new file mode 100644 (file)
index 0000000..76e26ce
--- /dev/null
@@ -0,0 +1,35 @@
+/* database-private.h - For peeking into the internals of notmuch_database_t
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#ifndef NOTMUCH_DATABASE_PRIVATE_H
+#define NOTMUCH_DATABASE_PRIVATE_H
+
+#include "notmuch-private.h"
+
+#include <xapian.h>
+
+struct _notmuch_database {
+    char *path;
+    Xapian::WritableDatabase *xapian_db;
+    Xapian::QueryParser *query_parser;
+    Xapian::TermGenerator *term_gen;
+};
+
+#endif
diff --git a/lib/database.cc b/lib/database.cc
new file mode 100644 (file)
index 0000000..4524016
--- /dev/null
@@ -0,0 +1,987 @@
+/* database.cc - The database interfaces of the notmuch mail library
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "database-private.h"
+
+#include <iostream>
+
+#include <xapian.h>
+
+#include <glib.h> /* g_free, GPtrArray, GHashTable */
+
+using namespace std;
+
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
+typedef struct {
+    const char *name;
+    const char *prefix;
+} prefix_t;
+
+/* Here's the current schema for our database:
+ *
+ * We currently have two different types of documents: mail and timestamps.
+ *
+ * Mail document
+ * -------------
+ * A mail document is associated with a particular email message file
+ * on disk. It is indexed with the following prefixed terms:
+ *
+ *    Single terms of given prefix:
+ *
+ *     type:   mail
+ *
+ *     id:     Unique ID of mail, (from Message-ID header or generated
+ *             as "notmuch-sha1-<sha1_sum_of_entire_file>.
+ *
+ *     thread: The ID of the thread to which the mail belongs
+ *
+ *    Multiple terms of given prefix:
+ *
+ *     ref:    All unresolved message IDs from In-Reply-To and
+ *             References headers in the message. (Once a referenced
+ *             message is added to the database and the thread IDs
+ *             are linked the corresponding "ref" term is dropped
+ *             from the message document.)
+ *
+ *     tag:    Any tags associated with this message by the user.
+ *
+ *    A mail document also has two values:
+ *
+ *     TIMESTAMP:      The time_t value corresponding to the message's
+ *                     Date header.
+ *
+ *     MESSAGE_ID:     The unique ID of the mail mess (see "id" above)
+ *
+ * Timestamp document
+ * ------------------
+ * A timestamp document is used by a client of the notmuch library to
+ * maintain data necessary to allow for efficient polling of mail
+ * directories. The notmuch library does no interpretation of
+ * timestamps, but merely allows the user to store and retrieve
+ * timestamps as name/value pairs.
+ *
+ * The timestamp document is indexed with a single prefixed term:
+ *
+ *     timestamp:      The user's key value (likely a directory name)
+ *
+ * and has a single value:
+ *
+ *     TIMESTAMP:      The time_t value from the user.
+ */
+
+/* With these prefix values we follow the conventions published here:
+ *
+ * http://xapian.org/docs/omega/termprefixes.html
+ *
+ * as much as makes sense. Note that I took some liberty in matching
+ * the reserved prefix values to notmuch concepts, (for example, 'G'
+ * is documented as "newsGroup (or similar entity - e.g. a web forum
+ * name)", for which I think the thread is the closest analogue in
+ * notmuch. This in spite of the fact that we will eventually be
+ * storing mailing-list messages where 'G' for "mailing list name"
+ * might be even a closer analogue. I'm treating the single-character
+ * prefixes preferentially for core notmuch concepts (which will be
+ * nearly universal to all mail messages).
+ */
+
+prefix_t BOOLEAN_PREFIX_INTERNAL[] = {
+    { "type", "T" },
+    { "ref", "XREFERENCE" },
+    { "replyto", "XREPLYTO" },
+    { "timestamp", "XTIMESTAMP" },
+    { "contact", "XCONTACT" }
+};
+
+prefix_t BOOLEAN_PREFIX_EXTERNAL[] = {
+    { "thread", "G" },
+    { "tag", "K" },
+    { "id", "Q" }
+};
+
+prefix_t PROBABILISTIC_PREFIX[]= {
+    { "from", "XFROM" },
+    { "to", "XTO" },
+    { "attachment", "XATTACHMENT" },
+    { "subject", "XSUBJECT"}
+};
+
+int
+_internal_error (const char *format, ...)
+{
+    va_list va_args;
+
+    va_start (va_args, format);
+
+    fprintf (stderr, "Internal error: ");
+    vfprintf (stderr, format, va_args);
+
+    exit (1);
+
+    return 1;
+}
+
+const char *
+_find_prefix (const char *name)
+{
+    unsigned int i;
+
+    for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_INTERNAL); i++)
+       if (strcmp (name, BOOLEAN_PREFIX_INTERNAL[i].name) == 0)
+           return BOOLEAN_PREFIX_INTERNAL[i].prefix;
+
+    for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_EXTERNAL); i++)
+       if (strcmp (name, BOOLEAN_PREFIX_EXTERNAL[i].name) == 0)
+           return BOOLEAN_PREFIX_EXTERNAL[i].prefix;
+
+    for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++)
+       if (strcmp (name, PROBABILISTIC_PREFIX[i].name) == 0)
+           return PROBABILISTIC_PREFIX[i].prefix;
+
+    INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
+
+    return "";
+}
+
+const char *
+notmuch_status_to_string (notmuch_status_t status)
+{
+    switch (status) {
+    case NOTMUCH_STATUS_SUCCESS:
+       return "No error occurred";
+    case NOTMUCH_STATUS_OUT_OF_MEMORY:
+       return "Out of memory";
+    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
+       return "A Xapian exception occurred";
+    case NOTMUCH_STATUS_FILE_ERROR:
+       return "Something went wrong trying to read or write a file";
+    case NOTMUCH_STATUS_FILE_NOT_EMAIL:
+       return "File is not an email";
+    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+       return "Message ID is identical to a message in database";
+    case NOTMUCH_STATUS_NULL_POINTER:
+       return "Erroneous NULL pointer";
+    case NOTMUCH_STATUS_TAG_TOO_LONG:
+       return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)";
+    case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
+       return "Unblanced number of calls to notmuch_message_freeze/thaw";
+    default:
+    case NOTMUCH_STATUS_LAST_STATUS:
+       return "Unknown error status value";
+    }
+}
+
+static void
+find_doc_ids (notmuch_database_t *notmuch,
+             const char *prefix_name,
+             const char *value,
+             Xapian::PostingIterator *begin,
+             Xapian::PostingIterator *end)
+{
+    Xapian::PostingIterator i;
+    char *term;
+
+    term = talloc_asprintf (notmuch, "%s%s",
+                           _find_prefix (prefix_name), value);
+
+    *begin = notmuch->xapian_db->postlist_begin (term);
+
+    *end = notmuch->xapian_db->postlist_end (term);
+
+    talloc_free (term);
+}
+
+static notmuch_private_status_t
+find_unique_doc_id (notmuch_database_t *notmuch,
+                   const char *prefix_name,
+                   const char *value,
+                   unsigned int *doc_id)
+{
+    Xapian::PostingIterator i, end;
+
+    find_doc_ids (notmuch, prefix_name, value, &i, &end);
+
+    if (i == end) {
+       *doc_id = 0;
+       return NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
+    } else {
+       *doc_id = *i;
+       return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+    }
+}
+
+static Xapian::Document
+find_document_for_doc_id (notmuch_database_t *notmuch, unsigned doc_id)
+{
+    return notmuch->xapian_db->get_document (doc_id);
+}
+
+static notmuch_private_status_t
+find_unique_document (notmuch_database_t *notmuch,
+                     const char *prefix_name,
+                     const char *value,
+                     Xapian::Document *document,
+                     unsigned int *doc_id)
+{
+    notmuch_private_status_t status;
+
+    status = find_unique_doc_id (notmuch, prefix_name, value, doc_id);
+
+    if (status) {
+       *document = Xapian::Document ();
+       return status;
+    }
+
+    *document = find_document_for_doc_id (notmuch, *doc_id);
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
+notmuch_message_t *
+notmuch_database_find_message (notmuch_database_t *notmuch,
+                              const char *message_id)
+{
+    notmuch_private_status_t status;
+    unsigned int doc_id;
+
+    status = find_unique_doc_id (notmuch, "id", message_id, &doc_id);
+
+    if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
+       return NULL;
+
+    return _notmuch_message_create (notmuch, notmuch, doc_id, NULL);
+}
+
+/* Advance 'str' past any whitespace or RFC 822 comments. A comment is
+ * a (potentially nested) parenthesized sequence with '\' used to
+ * escape any character (including parentheses).
+ *
+ * If the sequence to be skipped continues to the end of the string,
+ * then 'str' will be left pointing at the final terminating '\0'
+ * character.
+ */
+static void
+skip_space_and_comments (const char **str)
+{
+    const char *s;
+
+    s = *str;
+    while (*s && (isspace (*s) || *s == '(')) {
+       while (*s && isspace (*s))
+           s++;
+       if (*s == '(') {
+           int nesting = 1;
+           s++;
+           while (*s && nesting) {
+               if (*s == '(')
+                   nesting++;
+               else if (*s == ')')
+                   nesting--;
+               else if (*s == '\\')
+                   if (*(s+1))
+                       s++;
+               s++;
+           }
+       }
+    }
+
+    *str = s;
+}
+
+/* Parse an RFC 822 message-id, discarding whitespace, any RFC 822
+ * comments, and the '<' and '>' delimeters.
+ *
+ * If not NULL, then *next will be made to point to the first character
+ * not parsed, (possibly pointing to the final '\0' terminator.
+ *
+ * Returns a newly talloc'ed string belonging to 'ctx'.
+ *
+ * Returns NULL if there is any error parsing the message-id. */
+static char *
+parse_message_id (void *ctx, const char *message_id, const char **next)
+{
+    const char *s, *end;
+    char *result;
+
+    if (message_id == NULL)
+       return NULL;
+
+    s = message_id;
+
+    skip_space_and_comments (&s);
+
+    /* Skip any unstructured text as well. */
+    while (*s && *s != '<')
+       s++;
+
+    if (*s == '<') {
+       s++;
+    } else {
+       if (next)
+           *next = s;
+       return NULL;
+    }
+
+    skip_space_and_comments (&s);
+
+    end = s;
+    while (*end && *end != '>')
+       end++;
+    if (next) {
+       if (*end)
+           *next = end + 1;
+       else
+           *next = end;
+    }
+
+    if (end > s && *end == '>')
+       end--;
+    if (end <= s)
+       return NULL;
+
+    result = talloc_strndup (ctx, s, end - s + 1);
+
+    /* Finally, collapse any whitespace that is within the message-id
+     * itself. */
+    {
+       char *r;
+       int len;
+
+       for (r = result, len = strlen (r); *r; r++, len--)
+           if (*r == ' ' || *r == '\t')
+               memmove (r, r+1, len);
+    }
+
+    return result;
+}
+
+/* Parse a References header value, putting a (talloc'ed under 'ctx')
+ * copy of each referenced message-id into 'hash'. */
+static void
+parse_references (void *ctx,
+                 GHashTable *hash,
+                 const char *refs)
+{
+    char *ref;
+
+    if (refs == NULL)
+       return;
+
+    while (*refs) {
+       ref = parse_message_id (ctx, refs, &refs);
+
+       if (ref)
+           g_hash_table_insert (hash, ref, NULL);
+    }
+}
+
+char *
+notmuch_database_default_path (void)
+{
+    char *path;
+
+    if (getenv ("NOTMUCH_BASE"))
+       return strdup (getenv ("NOTMUCH_BASE"));
+
+    if (asprintf (&path, "%s/mail", getenv ("HOME")) == -1) {
+       fprintf (stderr, "Out of memory.\n");
+       return xstrdup("");
+    }
+
+    return path;
+}
+
+notmuch_database_t *
+notmuch_database_create (const char *path)
+{
+    notmuch_database_t *notmuch = NULL;
+    char *notmuch_path = NULL;
+    struct stat st;
+    int err;
+    char *local_path = NULL;
+
+    if (path == NULL)
+       path = local_path = notmuch_database_default_path ();
+
+    err = stat (path, &st);
+    if (err) {
+       fprintf (stderr, "Error: Cannot create database at %s: %s.\n",
+                path, strerror (errno));
+       goto DONE;
+    }
+
+    if (! S_ISDIR (st.st_mode)) {
+       fprintf (stderr, "Error: Cannot create database at %s: Not a directory.\n",
+                path);
+       goto DONE;
+    }
+
+    notmuch_path = talloc_asprintf (NULL, "%s/%s", path, ".notmuch");
+
+    err = mkdir (notmuch_path, 0755);
+
+    if (err) {
+       fprintf (stderr, "Error: Cannot create directory %s: %s.\n",
+                notmuch_path, strerror (errno));
+       goto DONE;
+    }
+
+    notmuch = notmuch_database_open (path);
+
+  DONE:
+    if (notmuch_path)
+       talloc_free (notmuch_path);
+    if (local_path)
+       free (local_path);
+
+    return notmuch;
+}
+
+notmuch_database_t *
+notmuch_database_open (const char *path)
+{
+    notmuch_database_t *notmuch = NULL;
+    char *notmuch_path = NULL, *xapian_path = NULL;
+    struct stat st;
+    int err;
+    char *local_path = NULL;
+    unsigned int i;
+
+    if (path == NULL)
+       path = local_path = notmuch_database_default_path ();
+
+    if (asprintf (&notmuch_path, "%s/%s", path, ".notmuch") == -1) {
+       notmuch_path = NULL;
+       fprintf (stderr, "Out of memory\n");
+       goto DONE;
+    }
+
+    err = stat (notmuch_path, &st);
+    if (err) {
+       fprintf (stderr, "Error opening database at %s: %s\n",
+                notmuch_path, strerror (errno));
+       goto DONE;
+    }
+
+    if (asprintf (&xapian_path, "%s/%s", notmuch_path, "xapian") == -1) {
+       xapian_path = NULL;
+       fprintf (stderr, "Out of memory\n");
+       goto DONE;
+    }
+
+    notmuch = talloc (NULL, notmuch_database_t);
+    notmuch->path = talloc_strdup (notmuch, path);
+
+    if (notmuch->path[strlen (notmuch->path) - 1] == '/')
+       notmuch->path[strlen (notmuch->path) - 1] = '\0';
+
+    try {
+       notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
+                                                          Xapian::DB_CREATE_OR_OPEN);
+       notmuch->query_parser = new Xapian::QueryParser;
+       notmuch->term_gen = new Xapian::TermGenerator;
+       notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
+
+       notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
+       notmuch->query_parser->set_database (*notmuch->xapian_db);
+       notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
+       notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
+
+       for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_EXTERNAL); i++) {
+           prefix_t *prefix = &BOOLEAN_PREFIX_EXTERNAL[i];
+           notmuch->query_parser->add_boolean_prefix (prefix->name,
+                                                      prefix->prefix);
+       }
+
+       for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++) {
+           prefix_t *prefix = &PROBABILISTIC_PREFIX[i];
+           notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
+       }
+    } catch (const Xapian::Error &error) {
+       fprintf (stderr, "A Xapian exception occurred: %s\n",
+                error.get_msg().c_str());
+       notmuch = NULL;
+    }
+    
+  DONE:
+    if (local_path)
+       free (local_path);
+    if (notmuch_path)
+       free (notmuch_path);
+    if (xapian_path)
+       free (xapian_path);
+
+    return notmuch;
+}
+
+void
+notmuch_database_close (notmuch_database_t *notmuch)
+{
+    notmuch->xapian_db->flush ();
+
+    delete notmuch->term_gen;
+    delete notmuch->query_parser;
+    delete notmuch->xapian_db;
+    talloc_free (notmuch);
+}
+
+const char *
+notmuch_database_get_path (notmuch_database_t *notmuch)
+{
+    return notmuch->path;
+}
+
+static notmuch_private_status_t
+find_timestamp_document (notmuch_database_t *notmuch, const char *db_key,
+                        Xapian::Document *doc, unsigned int *doc_id)
+{
+    return find_unique_document (notmuch, "timestamp", db_key, doc, doc_id);
+}
+
+/* We allow the user to use arbitrarily long keys for timestamps,
+ * (they're for filesystem paths after all, which have no limit we
+ * know about). But we have a term-length limit. So if we exceed that,
+ * we'll use the SHA-1 of the user's key as the actual key for
+ * constructing a database term.
+ *
+ * Caution: This function returns a newly allocated string which the
+ * caller should free() when finished.
+ */
+static char *
+timestamp_db_key (const char *key)
+{
+    int term_len = strlen (_find_prefix ("timestamp")) + strlen (key);
+
+    if (term_len > NOTMUCH_TERM_MAX)
+       return notmuch_sha1_of_string (key);
+    else
+       return strdup (key);
+}
+
+notmuch_status_t
+notmuch_database_set_timestamp (notmuch_database_t *notmuch,
+                               const char *key, time_t timestamp)
+{
+    Xapian::Document doc;
+    unsigned int doc_id;
+    notmuch_private_status_t status;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    char *db_key = NULL;
+
+    db_key = timestamp_db_key (key);
+
+    try {
+       status = find_timestamp_document (notmuch, db_key, &doc, &doc_id);
+
+       doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
+                      Xapian::sortable_serialise (timestamp));
+
+       if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+           char *term = talloc_asprintf (NULL, "%s%s",
+                                         _find_prefix ("timestamp"), db_key);
+           doc.add_term (term);
+           talloc_free (term);
+
+           notmuch->xapian_db->add_document (doc);
+       } else {
+           notmuch->xapian_db->replace_document (doc_id, doc);
+       }
+
+    } catch (Xapian::Error &error) {
+       fprintf (stderr, "A Xapian exception occurred: %s.\n",
+                error.get_msg().c_str());
+       ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+    if (db_key)
+       free (db_key);
+
+    return ret;
+}
+
+time_t
+notmuch_database_get_timestamp (notmuch_database_t *notmuch, const char *key)
+{
+    Xapian::Document doc;
+    unsigned int doc_id;
+    notmuch_private_status_t status;
+    char *db_key = NULL;
+    time_t ret = 0;
+
+    db_key = timestamp_db_key (key);
+
+    try {
+       status = find_timestamp_document (notmuch, db_key, &doc, &doc_id);
+
+       if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
+           goto DONE;
+
+       ret =  Xapian::sortable_unserialise (doc.get_value (NOTMUCH_VALUE_TIMESTAMP));
+    } catch (Xapian::Error &error) {
+       goto DONE;
+    }
+
+  DONE:
+    if (db_key)
+       free (db_key);
+
+    return ret;
+}
+
+/* Find the thread ID to which the message with 'message_id' belongs.
+ *
+ * Returns NULL if no message with message ID 'message_id' is in the
+ * database.
+ *
+ * Otherwise, returns a newly talloced string belonging to 'ctx'.
+ */
+static const char *
+_resolve_message_id_to_thread_id (notmuch_database_t *notmuch,
+                                 void *ctx,
+                                 const char *message_id)
+{
+    notmuch_message_t *message;
+    const char *ret = NULL;
+
+    message = notmuch_database_find_message (notmuch, message_id);
+    if (message == NULL)
+       goto DONE;
+
+    ret = talloc_steal (ctx, notmuch_message_get_thread_id (message));
+
+  DONE:
+    if (message)
+       notmuch_message_destroy (message);
+
+    return ret;
+}
+
+static notmuch_status_t
+_merge_threads (notmuch_database_t *notmuch,
+               const char *winner_thread_id,
+               const char *loser_thread_id)
+{
+    Xapian::PostingIterator loser, loser_end;
+    notmuch_message_t *message = NULL;
+    notmuch_private_status_t private_status;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+    find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end);
+
+    for ( ; loser != loser_end; loser++) {
+       message = _notmuch_message_create (notmuch, notmuch,
+                                          *loser, &private_status);
+       if (message == NULL) {
+           ret = COERCE_STATUS (private_status,
+                                "Cannot find document for doc_id from query");
+           goto DONE;
+       }
+
+       _notmuch_message_remove_term (message, "thread", loser_thread_id);
+       _notmuch_message_add_term (message, "thread", winner_thread_id);
+       _notmuch_message_sync (message);
+
+       notmuch_message_destroy (message);
+       message = NULL;
+    }
+
+  DONE:
+    if (message)
+       notmuch_message_destroy (message);
+
+    return ret;
+}
+
+static void
+_my_talloc_free_for_g_hash (void *ptr)
+{
+    talloc_free (ptr);
+}
+
+static notmuch_status_t
+_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
+                                          notmuch_message_t *message,
+                                          notmuch_message_file_t *message_file,
+                                          const char **thread_id)
+{
+    GHashTable *parents = NULL;
+    const char *refs, *in_reply_to;
+    GList *l, *keys = NULL;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+    parents = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                    _my_talloc_free_for_g_hash, NULL);
+
+    refs = notmuch_message_file_get_header (message_file, "references");
+    parse_references (message, parents, refs);
+
+    in_reply_to = notmuch_message_file_get_header (message_file, "in-reply-to");
+    parse_references (message, parents, in_reply_to);
+    _notmuch_message_add_term (message, "replyto",
+                              parse_message_id (message, in_reply_to, NULL));
+
+    keys = g_hash_table_get_keys (parents);
+    for (l = keys; l; l = l->next) {
+       char *parent_message_id;
+       const char *parent_thread_id;
+
+       parent_message_id = (char *) l->data;
+       parent_thread_id = _resolve_message_id_to_thread_id (notmuch,
+                                                            message,
+                                                            parent_message_id);
+
+       if (parent_thread_id == NULL) {
+           _notmuch_message_add_term (message, "ref", parent_message_id);
+       } else {
+           if (*thread_id == NULL) {
+               *thread_id = talloc_strdup (message, parent_thread_id);
+               _notmuch_message_add_term (message, "thread", *thread_id);
+           } else if (strcmp (*thread_id, parent_thread_id)) {
+               ret = _merge_threads (notmuch, *thread_id, parent_thread_id);
+               if (ret)
+                   goto DONE;
+           }
+       }
+    }
+
+  DONE:
+    if (keys)
+       g_list_free (keys);
+    if (parents)
+       g_hash_table_unref (parents);
+
+    return ret;
+}
+
+static notmuch_status_t
+_notmuch_database_link_message_to_children (notmuch_database_t *notmuch,
+                                           notmuch_message_t *message,
+                                           const char **thread_id)
+{
+    const char *message_id = notmuch_message_get_message_id (message);
+    Xapian::PostingIterator child, children_end;
+    notmuch_message_t *child_message = NULL;
+    const char *child_thread_id;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    notmuch_private_status_t private_status;
+
+    find_doc_ids (notmuch, "ref", message_id, &child, &children_end);
+
+    for ( ; child != children_end; child++) {
+
+       child_message = _notmuch_message_create (message, notmuch,
+                                                *child, &private_status);
+       if (child_message == NULL) {
+           ret = COERCE_STATUS (private_status,
+                                "Cannot find document for doc_id from query");
+           goto DONE;
+       }
+
+       child_thread_id = notmuch_message_get_thread_id (child_message);
+       if (*thread_id == NULL) {
+           *thread_id = talloc_strdup (message, child_thread_id);
+           _notmuch_message_add_term (message, "thread", *thread_id);
+       } else if (strcmp (*thread_id, child_thread_id)) {
+           _notmuch_message_remove_term (child_message, "ref",
+                                         message_id);
+           _notmuch_message_sync (child_message);
+           ret = _merge_threads (notmuch, *thread_id, child_thread_id);
+           if (ret)
+               goto DONE;
+       }
+
+       notmuch_message_destroy (child_message);
+       child_message = NULL;
+    }
+
+  DONE:
+    if (child_message)
+       notmuch_message_destroy (child_message);
+
+    return ret;
+}
+
+/* Given a (mostly empty) 'message' and its corresponding
+ * 'message_file' link it to existing threads in the database.
+ *
+ * We first looke at 'message_file' and its link-relevant headers
+ * (References and In-Reply-To) for message IDs. We also look in the
+ * database for existing message that reference 'message'.p
+ *
+ * The end result is to call _notmuch_message_add_thread_id with one
+ * or more thread IDs to which this message belongs, (including
+ * generating a new thread ID if necessary if the message doesn't
+ * connect to any existing threads).
+ */
+static notmuch_status_t
+_notmuch_database_link_message (notmuch_database_t *notmuch,
+                               notmuch_message_t *message,
+                               notmuch_message_file_t *message_file)
+{
+    notmuch_status_t status;
+    const char *thread_id = NULL;
+
+    status = _notmuch_database_link_message_to_parents (notmuch, message,
+                                                       message_file,
+                                                       &thread_id);
+    if (status)
+       return status;
+
+    status = _notmuch_database_link_message_to_children (notmuch, message,
+                                                        &thread_id);
+    if (status)
+       return status;
+
+    if (thread_id == NULL)
+       _notmuch_message_ensure_thread_id (message);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_database_add_message (notmuch_database_t *notmuch,
+                             const char *filename,
+                             notmuch_message_t **message_ret)
+{
+    notmuch_message_file_t *message_file;
+    notmuch_message_t *message = NULL;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    notmuch_private_status_t private_status;
+
+    const char *date, *header;
+    const char *from, *to, *subject;
+    char *message_id;
+
+    if (message_ret)
+       *message_ret = NULL;
+
+    message_file = notmuch_message_file_open (filename);
+    if (message_file == NULL) {
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    notmuch_message_file_restrict_headers (message_file,
+                                          "date",
+                                          "from",
+                                          "in-reply-to",
+                                          "message-id",
+                                          "references",
+                                          "subject",
+                                          "to",
+                                          (char *) NULL);
+
+    try {
+       /* Before we do any real work, (especially before doing a
+        * potential SHA-1 computation on the entire file's contents),
+        * let's make sure that what we're looking at looks like an
+        * actual email message.
+        */
+       from = notmuch_message_file_get_header (message_file, "from");
+       subject = notmuch_message_file_get_header (message_file, "subject");
+       to = notmuch_message_file_get_header (message_file, "to");
+
+       if (from == NULL &&
+           subject == NULL &&
+           to == NULL)
+       {
+           ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
+           goto DONE;
+       }
+
+       /* Now that we're sure it's mail, the first order of business
+        * is to find a message ID (or else create one ourselves). */
+
+       header = notmuch_message_file_get_header (message_file, "message-id");
+       if (header) {
+           message_id = parse_message_id (message_file, header, NULL);
+           /* So the header value isn't RFC-compliant, but it's
+            * better than no message-id at all. */
+           if (message_id == NULL)
+               message_id = talloc_strdup (message_file, header);
+       } else {
+           /* No message-id at all, let's generate one by taking a
+            * hash over the file's contents. */
+           char *sha1 = notmuch_sha1_of_file (filename);
+
+           /* If that failed too, something is really wrong. Give up. */
+           if (sha1 == NULL) {
+               ret = NOTMUCH_STATUS_FILE_ERROR;
+               goto DONE;
+           }
+
+           message_id = talloc_asprintf (message_file,
+                                         "notmuch-sha1-%s", sha1);
+           free (sha1);
+       }
+
+       /* Now that we have a message ID, we get a message object,
+        * (which may or may not reference an existing document in the
+        * database). */
+
+       /* Use NULL for owner since we want to free this locally. */
+       message = _notmuch_message_create_for_message_id (NULL,
+                                                         notmuch,
+                                                         message_id,
+                                                         &private_status);
+
+       talloc_free (message_id);
+
+       if (message == NULL)
+           goto DONE;
+
+       /* Is this a newly created message object? */
+       if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+           _notmuch_message_set_filename (message, filename);
+           _notmuch_message_add_term (message, "type", "mail");
+       } else {
+           ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+           goto DONE;
+       }
+
+       ret = _notmuch_database_link_message (notmuch, message, message_file);
+       if (ret)
+           goto DONE;
+
+       date = notmuch_message_file_get_header (message_file, "date");
+       _notmuch_message_set_date (message, date);
+
+       _notmuch_message_index_file (message, filename);
+
+       _notmuch_message_sync (message);
+    } catch (const Xapian::Error &error) {
+       fprintf (stderr, "A Xapian exception occurred: %s.\n",
+                error.get_msg().c_str());
+       ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+       goto DONE;
+    }
+
+  DONE:
+    if (message) {
+       if (ret == NOTMUCH_STATUS_SUCCESS && message_ret)
+           *message_ret = message;
+       else
+           notmuch_message_destroy (message);
+    }
+
+    if (message_file)
+       notmuch_message_file_close (message_file);
+
+    return ret;
+}
diff --git a/lib/index.cc b/lib/index.cc
new file mode 100644 (file)
index 0000000..747a4e6
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+
+#include <gmime/gmime.h>
+
+#include <xapian.h>
+
+/* We're finally down to a single (NAME + address) email "mailbox". */
+static void
+_index_address_mailbox (notmuch_message_t *message,
+                       const char *prefix_name,
+                       InternetAddress *address)
+{
+    InternetAddressMailbox *mailbox = INTERNET_ADDRESS_MAILBOX (address);
+    const char *name, *addr;
+    char *contact;
+    int own_name = 0;
+
+    name = internet_address_get_name (address);
+    addr = internet_address_mailbox_get_addr (mailbox);
+
+    if (addr) {
+       if (name) {
+           contact = talloc_asprintf (message, "\"%s\" <%s>",
+                                      name, addr);
+           _notmuch_message_add_term (message, "contact", contact);
+           talloc_free (contact);
+       } else {
+           _notmuch_message_add_term (message, "contact", addr);
+       }
+    }
+
+    /* In the absence of a name, we'll strip the part before the @
+     * from the address. */
+    if (! name) {
+       const char *at;
+
+       at = strchr (addr, '@');
+       if (at) {
+           name = strndup (addr, at - addr);
+           own_name = 1;
+       }
+    }
+
+    if (name)
+       _notmuch_message_gen_terms (message, prefix_name, name);
+    if (addr)
+       _notmuch_message_gen_terms (message, prefix_name, addr);
+}
+
+static void
+_index_address_list (notmuch_message_t *message,
+                    const char *prefix_name,
+                    InternetAddressList *addresses);
+
+/* The outer loop over the InternetAddressList wasn't quite enough.
+ * There can actually be a tree here where a single member of the list
+ * is a "group" containing another list. Recurse please.
+ */
+static void
+_index_address_group (notmuch_message_t *message,
+                     const char *prefix_name,
+                     InternetAddress *address)
+{
+    InternetAddressGroup *group;
+    InternetAddressList *list;
+
+    group = INTERNET_ADDRESS_GROUP (address);
+    list = internet_address_group_get_members (group);
+
+    if (! list)
+       return;
+
+    _index_address_list (message, prefix_name, list);
+}
+
+static void
+_index_address_list (notmuch_message_t *message,
+                    const char *prefix_name,
+                    InternetAddressList *addresses)
+{
+    int i;
+    InternetAddress *address;
+
+    if (addresses == NULL)
+       return;
+
+    for (i = 0; i < internet_address_list_length (addresses); i++) {
+       address = internet_address_list_get_address (addresses, i);
+       if (INTERNET_ADDRESS_IS_MAILBOX (address)) {
+           _index_address_mailbox (message, prefix_name, address);
+       } else if (INTERNET_ADDRESS_IS_GROUP (address)) {
+           _index_address_group (message, prefix_name, address);
+       } else {
+           INTERNAL_ERROR ("GMime InternetAddress is neither a mailbox nor a group.\n");
+       }
+    }
+}
+
+static const char *
+skip_re_in_subject (const char *subject)
+{
+    const char *s = subject;
+
+    if (subject == NULL)
+       return NULL;
+
+    while (*s) {
+       while (*s && isspace (*s))
+           s++;
+       if (strncasecmp (s, "re:", 3) == 0)
+           s += 3;
+       else
+           break;
+    }
+
+    return s;
+}
+
+/* Given a string representing the body of a message, generate terms
+ * for it, (skipping quoted portions and signatures).
+ *
+ * This function is evil in that it modifies the string passed to it,
+ * (changing some newlines into '\0').
+ */
+static void
+_index_body_text (notmuch_message_t *message, char *body)
+{
+    char *line, *line_end, *next_line;
+
+    if (body == NULL)
+       return;
+
+    next_line = body;
+
+    while (1) {
+       line = next_line;
+       if (*line == '\0')
+           break;
+
+       next_line = strchr (line, '\n');
+       if (next_line == NULL) {
+           next_line = line + strlen (line);
+       }
+       line_end = next_line - 1;
+
+       /* Get to the next non-blank line. */
+       while (*next_line == '\n')
+           next_line++;
+
+       /* Skip blank lines. */
+       if (line_end < line)
+           continue;
+
+       /* Skip lines that are quotes. */
+       if (*line == '>')
+           continue;
+
+       /* Also skip lines introducing a quote on the next line. */
+       if (*line_end == ':' && *next_line == '>')
+           continue;
+
+       /* Finally, bail as soon as we see a signature. */
+       /* XXX: Should only do this if "near" the end of the message. */
+       if (strncmp (line, "-- ", 3) == 0)
+           break;
+
+       *(line_end + 1) = '\0';
+
+       _notmuch_message_gen_terms (message, NULL, line);
+    }
+}
+
+/* Callback to generate terms for each mime part of a message. */
+static void
+_index_mime_part (notmuch_message_t *message,
+                 GMimeObject *part)
+{
+    GMimeStream *stream;
+    GMimeDataWrapper *wrapper;
+    GByteArray *byte_array;
+    GMimeContentDisposition *disposition;
+    char *body;
+
+    if (GMIME_IS_MULTIPART (part)) {
+       GMimeMultipart *multipart = GMIME_MULTIPART (part);
+       int i;
+
+       for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
+           if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
+               /* Don't index the signature. */
+               if (i == 1)
+                   continue;
+               if (i > 1)
+                   fprintf (stderr, "Warning: Unexpected extra parts of mutlipart/signed. Indexing anyway.\n");
+           }
+           _index_mime_part (message,
+                             g_mime_multipart_get_part (multipart, i));
+       }
+       return;
+    }
+
+    if (GMIME_IS_MESSAGE_PART (part)) {
+       GMimeMessage *mime_message;
+
+       mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
+
+       _index_mime_part (message, g_mime_message_get_mime_part (mime_message));
+
+       return;
+    }
+
+    if (! (GMIME_IS_PART (part))) {
+       fprintf (stderr, "Warning: Not indexing unknown mime part: %s.\n",
+                g_type_name (G_OBJECT_TYPE (part)));
+       return;
+    }
+
+    disposition = g_mime_object_get_content_disposition (part);
+    if (disposition &&
+       strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
+    {
+       const char *filename = g_mime_part_get_filename (GMIME_PART (part));
+
+       _notmuch_message_add_term (message, "tag", "attachment");
+       _notmuch_message_gen_terms (message, "attachment", filename);
+
+       /* XXX: Would be nice to call out to something here to parse
+        * the attachment into text and then index that. */
+       return;
+    }
+
+    byte_array = g_byte_array_new ();
+
+    stream = g_mime_stream_mem_new_with_byte_array (byte_array);
+    g_mime_stream_mem_set_owner (GMIME_STREAM_MEM (stream), FALSE);
+    wrapper = g_mime_part_get_content_object (GMIME_PART (part));
+    if (wrapper)
+       g_mime_data_wrapper_write_to_stream (wrapper, stream);
+
+    g_object_unref (stream);
+
+    g_byte_array_append (byte_array, (guint8 *) "\0", 1);
+    body = (char *) g_byte_array_free (byte_array, FALSE);
+
+    _index_body_text (message, body);
+
+    free (body);
+}
+
+notmuch_status_t
+_notmuch_message_index_file (notmuch_message_t *message,
+                            const char *filename)
+{
+    GMimeStream *stream = NULL;
+    GMimeParser *parser = NULL;
+    GMimeMessage *mime_message = NULL;
+    InternetAddressList *addresses;
+    FILE *file = NULL;
+    const char *from, *subject;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    static int initialized = 0;
+
+    if (! initialized) {
+       g_mime_init (0);
+       initialized = 1;
+    }
+
+    file = fopen (filename, "r");
+    if (! file) {
+       fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
+       ret = NOTMUCH_STATUS_FILE_ERROR;
+       goto DONE;
+    }
+
+    /* Evil GMime steals my FILE* here so I won't fclose it. */
+    stream = g_mime_stream_file_new (file);
+
+    parser = g_mime_parser_new_with_stream (stream);
+
+    mime_message = g_mime_parser_construct_message (parser);
+
+    from = g_mime_message_get_sender (mime_message);
+    addresses = internet_address_list_parse_string (from);
+
+    _index_address_list (message, "from", addresses);
+
+    addresses = g_mime_message_get_all_recipients (mime_message);
+    _index_address_list (message, "to", addresses);
+
+    subject = g_mime_message_get_subject (mime_message);
+    subject = skip_re_in_subject (subject);
+    _notmuch_message_gen_terms (message, "subject", subject);
+
+    _index_mime_part (message, g_mime_message_get_mime_part (mime_message));
+
+  DONE:
+    if (mime_message)
+       g_object_unref (mime_message);
+
+    if (parser)
+       g_object_unref (parser);
+
+    if (stream)
+       g_object_unref (stream);
+
+    return ret;
+}
diff --git a/lib/libsha1.c b/lib/libsha1.c
new file mode 100644 (file)
index 0000000..c39a5a1
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ ---------------------------------------------------------------------------
+ Copyright (c) 2002, Dr Brian Gladman, Worcester, UK.   All rights reserved.
+
+ LICENSE TERMS
+
+ The free distribution and use of this software in both source and binary
+ form is allowed (with or without changes) provided that:
+
+   1. distributions of this source code include the above copyright
+      notice, this list of conditions and the following disclaimer;
+
+   2. distributions in binary form include the above copyright
+      notice, this list of conditions and the following disclaimer
+      in the documentation and/or other associated materials;
+
+   3. the copyright holder's name is not used to endorse products
+      built using this software without specific written permission.
+
+ ALTERNATIVELY, provided that this notice is retained in full, this product
+ may be distributed under the terms of the GNU General Public License (GPL),
+ in which case the provisions of the GPL apply INSTEAD OF those given above.
+
+ DISCLAIMER
+
+ This software is provided 'as is' with no explicit or implied warranties
+ in respect of its properties, including, but not limited to, correctness
+ and/or fitness for purpose.
+ ---------------------------------------------------------------------------
+ Issue Date: 01/08/2005
+
+ This is a byte oriented version of SHA1 that operates on arrays of bytes
+ stored in memory.
+*/
+
+#include <string.h>     /* for memcpy() etc.        */
+
+#include "libsha1.h"
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+
+#define SHA1_BLOCK_SIZE  64
+
+#define rotl32(x,n)   (((x) << n) | ((x) >> (32 - n)))
+#define rotr32(x,n)   (((x) >> n) | ((x) << (32 - n)))
+
+#define bswap_32(x) ((rotr32((x), 24) & 0x00ff00ff) | (rotr32((x), 8) & 0xff00ff00))
+
+#if (PLATFORM_BYTE_ORDER == IS_LITTLE_ENDIAN)
+#define bsw_32(p,n) \
+    { int _i = (n); while(_i--) ((uint32_t*)p)[_i] = bswap_32(((uint32_t*)p)[_i]); }
+#else
+#define bsw_32(p,n)
+#endif
+
+#define SHA1_MASK   (SHA1_BLOCK_SIZE - 1)
+
+#if 0
+
+#define ch(x,y,z)       (((x) & (y)) ^ (~(x) & (z)))
+#define parity(x,y,z)   ((x) ^ (y) ^ (z))
+#define maj(x,y,z)      (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+
+#else   /* Discovered by Rich Schroeppel and Colin Plumb   */
+
+#define ch(x,y,z)       ((z) ^ ((x) & ((y) ^ (z))))
+#define parity(x,y,z)   ((x) ^ (y) ^ (z))
+#define maj(x,y,z)      (((x) & (y)) | ((z) & ((x) ^ (y))))
+
+#endif
+
+/* Compile 64 bytes of hash data into SHA1 context. Note    */
+/* that this routine assumes that the byte order in the     */
+/* ctx->wbuf[] at this point is in such an order that low   */
+/* address bytes in the ORIGINAL byte stream will go in     */
+/* this buffer to the high end of 32-bit words on BOTH big  */
+/* and little endian systems                                */
+
+#ifdef ARRAY
+#define q(v,n)  v[n]
+#else
+#define q(v,n)  v##n
+#endif
+
+#define one_cycle(v,a,b,c,d,e,f,k,h)            \
+    q(v,e) += rotr32(q(v,a),27) +               \
+              f(q(v,b),q(v,c),q(v,d)) + k + h;  \
+    q(v,b)  = rotr32(q(v,b), 2)
+
+#define five_cycle(v,f,k,i)                 \
+    one_cycle(v, 0,1,2,3,4, f,k,hf(i  ));   \
+    one_cycle(v, 4,0,1,2,3, f,k,hf(i+1));   \
+    one_cycle(v, 3,4,0,1,2, f,k,hf(i+2));   \
+    one_cycle(v, 2,3,4,0,1, f,k,hf(i+3));   \
+    one_cycle(v, 1,2,3,4,0, f,k,hf(i+4))
+
+static void sha1_compile(sha1_ctx ctx[1])
+{   uint32_t    *w = ctx->wbuf;
+
+#ifdef ARRAY
+    uint32_t    v[5];
+    memcpy(v, ctx->hash, 5 * sizeof(uint32_t));
+#else
+    uint32_t    v0, v1, v2, v3, v4;
+    v0 = ctx->hash[0]; v1 = ctx->hash[1];
+    v2 = ctx->hash[2]; v3 = ctx->hash[3];
+    v4 = ctx->hash[4];
+#endif
+
+#define hf(i)   w[i]
+
+    five_cycle(v, ch, 0x5a827999,  0);
+    five_cycle(v, ch, 0x5a827999,  5);
+    five_cycle(v, ch, 0x5a827999, 10);
+    one_cycle(v,0,1,2,3,4, ch, 0x5a827999, hf(15)); \
+
+#undef  hf
+#define hf(i) (w[(i) & 15] = rotl32(                    \
+                 w[((i) + 13) & 15] ^ w[((i) + 8) & 15] \
+               ^ w[((i) +  2) & 15] ^ w[(i) & 15], 1))
+
+    one_cycle(v,4,0,1,2,3, ch, 0x5a827999, hf(16));
+    one_cycle(v,3,4,0,1,2, ch, 0x5a827999, hf(17));
+    one_cycle(v,2,3,4,0,1, ch, 0x5a827999, hf(18));
+    one_cycle(v,1,2,3,4,0, ch, 0x5a827999, hf(19));
+
+    five_cycle(v, parity, 0x6ed9eba1,  20);
+    five_cycle(v, parity, 0x6ed9eba1,  25);
+    five_cycle(v, parity, 0x6ed9eba1,  30);
+    five_cycle(v, parity, 0x6ed9eba1,  35);
+
+    five_cycle(v, maj, 0x8f1bbcdc,  40);
+    five_cycle(v, maj, 0x8f1bbcdc,  45);
+    five_cycle(v, maj, 0x8f1bbcdc,  50);
+    five_cycle(v, maj, 0x8f1bbcdc,  55);
+
+    five_cycle(v, parity, 0xca62c1d6,  60);
+    five_cycle(v, parity, 0xca62c1d6,  65);
+    five_cycle(v, parity, 0xca62c1d6,  70);
+    five_cycle(v, parity, 0xca62c1d6,  75);
+
+#ifdef ARRAY
+    ctx->hash[0] += v[0]; ctx->hash[1] += v[1];
+    ctx->hash[2] += v[2]; ctx->hash[3] += v[3];
+    ctx->hash[4] += v[4];
+#else
+    ctx->hash[0] += v0; ctx->hash[1] += v1;
+    ctx->hash[2] += v2; ctx->hash[3] += v3;
+    ctx->hash[4] += v4;
+#endif
+}
+
+void sha1_begin(sha1_ctx ctx[1])
+{
+    ctx->count[0] = ctx->count[1] = 0;
+    ctx->hash[0] = 0x67452301;
+    ctx->hash[1] = 0xefcdab89;
+    ctx->hash[2] = 0x98badcfe;
+    ctx->hash[3] = 0x10325476;
+    ctx->hash[4] = 0xc3d2e1f0;
+}
+
+/* SHA1 hash data in an array of bytes into hash buffer and */
+/* call the hash_compile function as required.              */
+
+void sha1_hash(const unsigned char data[], unsigned long len, sha1_ctx ctx[1])
+{   uint32_t pos = (uint32_t)(ctx->count[0] & SHA1_MASK),
+            space = SHA1_BLOCK_SIZE - pos;
+    const unsigned char *sp = data;
+
+    if((ctx->count[0] += len) < len)
+        ++(ctx->count[1]);
+
+    while(len >= space)     /* tranfer whole blocks if possible  */
+    {
+        memcpy(((unsigned char*)ctx->wbuf) + pos, sp, space);
+        sp += space; len -= space; space = SHA1_BLOCK_SIZE; pos = 0;
+        bsw_32(ctx->wbuf, SHA1_BLOCK_SIZE >> 2);
+        sha1_compile(ctx);
+    }
+
+    memcpy(((unsigned char*)ctx->wbuf) + pos, sp, len);
+}
+
+/* SHA1 final padding and digest calculation  */
+
+void sha1_end(unsigned char hval[], sha1_ctx ctx[1])
+{   uint32_t    i = (uint32_t)(ctx->count[0] & SHA1_MASK);
+
+    /* put bytes in the buffer in an order in which references to   */
+    /* 32-bit words will put bytes with lower addresses into the    */
+    /* top of 32 bit words on BOTH big and little endian machines   */
+    bsw_32(ctx->wbuf, (i + 3) >> 2);
+
+    /* we now need to mask valid bytes and add the padding which is */
+    /* a single 1 bit and as many zero bits as necessary. Note that */
+    /* we can always add the first padding byte here because the    */
+    /* buffer always has at least one empty slot                    */
+    ctx->wbuf[i >> 2] &= 0xffffff80 << 8 * (~i & 3);
+    ctx->wbuf[i >> 2] |= 0x00000080 << 8 * (~i & 3);
+
+    /* we need 9 or more empty positions, one for the padding byte  */
+    /* (above) and eight for the length count. If there is not      */
+    /* enough space, pad and empty the buffer                       */
+    if(i > SHA1_BLOCK_SIZE - 9)
+    {
+        if(i < 60) ctx->wbuf[15] = 0;
+        sha1_compile(ctx);
+        i = 0;
+    }
+    else    /* compute a word index for the empty buffer positions  */
+        i = (i >> 2) + 1;
+
+    while(i < 14) /* and zero pad all but last two positions        */
+        ctx->wbuf[i++] = 0;
+
+    /* the following 32-bit length fields are assembled in the      */
+    /* wrong byte order on little endian machines but this is       */
+    /* corrected later since they are only ever used as 32-bit      */
+    /* word values.                                                 */
+    ctx->wbuf[14] = (ctx->count[1] << 3) | (ctx->count[0] >> 29);
+    ctx->wbuf[15] = ctx->count[0] << 3;
+    sha1_compile(ctx);
+
+    /* extract the hash value as bytes in case the hash buffer is   */
+    /* misaligned for 32-bit words                                  */
+    for(i = 0; i < SHA1_DIGEST_SIZE; ++i)
+        hval[i] = (unsigned char)(ctx->hash[i >> 2] >> (8 * (~i & 3)));
+}
+
+void sha1(unsigned char hval[], const unsigned char data[], unsigned long len)
+{   sha1_ctx    cx[1];
+
+    sha1_begin(cx); sha1_hash(data, len, cx); sha1_end(hval, cx);
+}
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/lib/libsha1.h b/lib/libsha1.h
new file mode 100644 (file)
index 0000000..b4dca93
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ ---------------------------------------------------------------------------
+ Copyright (c) 2002, Dr Brian Gladman, Worcester, UK.   All rights reserved.
+
+ LICENSE TERMS
+
+ The free distribution and use of this software in both source and binary
+ form is allowed (with or without changes) provided that:
+
+   1. distributions of this source code include the above copyright
+      notice, this list of conditions and the following disclaimer;
+
+   2. distributions in binary form include the above copyright
+      notice, this list of conditions and the following disclaimer
+      in the documentation and/or other associated materials;
+
+   3. the copyright holder's name is not used to endorse products
+      built using this software without specific written permission.
+
+ ALTERNATIVELY, provided that this notice is retained in full, this product
+ may be distributed under the terms of the GNU General Public License (GPL),
+ in which case the provisions of the GPL apply INSTEAD OF those given above.
+
+ DISCLAIMER
+
+ This software is provided 'as is' with no explicit or implied warranties
+ in respect of its properties, including, but not limited to, correctness
+ and/or fitness for purpose.
+ ---------------------------------------------------------------------------
+ Issue Date: 01/08/2005
+*/
+
+#ifndef _SHA1_H
+#define _SHA1_H
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+#if 0
+} /* Appleasing Emacs */
+#endif
+
+#include <stdint.h>
+
+/* Size of SHA1 digest */
+
+#define SHA1_DIGEST_SIZE 20
+
+/* type to hold the SHA1 context  */
+
+typedef struct
+{   uint32_t count[2];
+    uint32_t hash[5];
+    uint32_t wbuf[16];
+} sha1_ctx;
+
+void sha1_begin(sha1_ctx ctx[1]);
+void sha1_hash(const unsigned char data[], unsigned long len, sha1_ctx ctx[1]);
+void sha1_end(unsigned char hval[], sha1_ctx ctx[1]);
+void sha1(unsigned char hval[], const unsigned char data[], unsigned long len);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/lib/message-file.c b/lib/message-file.c
new file mode 100644 (file)
index 0000000..75caba6
--- /dev/null
@@ -0,0 +1,352 @@
+/* message.c - Utility functions for parsing an email message for notmuch.
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include <stdarg.h>
+
+#include "notmuch-private.h"
+
+#include <gmime/gmime.h>
+
+#include <glib.h> /* GHashTable */
+
+typedef struct {
+    char *str;
+    size_t size;
+    size_t len;
+} header_value_closure_t;
+
+struct _notmuch_message_file {
+    /* File object */
+    FILE *file;
+
+    /* Header storage */
+    int restrict_headers;
+    GHashTable *headers;
+    int broken_headers;
+    int good_headers;
+    size_t header_size; /* Length of full message header in bytes. */
+
+    /* Parsing state */
+    char *line;
+    size_t line_size;
+    header_value_closure_t value;
+
+    int parsing_started;
+    int parsing_finished;
+};
+
+static int
+strcase_equal (const void *a, const void *b)
+{
+    return strcasecmp (a, b) == 0;
+}
+
+static unsigned int
+strcase_hash (const void *ptr)
+{
+    const char *s = ptr;
+
+    /* This is the djb2 hash. */
+    unsigned int hash = 5381;
+    while (s && *s) {
+       hash = ((hash << 5) + hash) + tolower (*s);
+       s++;
+    }
+
+    return hash;
+}
+
+static int
+_notmuch_message_file_destructor (notmuch_message_file_t *message)
+{
+    if (message->line)
+       free (message->line);
+
+    if (message->value.size)
+       free (message->value.str);
+
+    if (message->headers)
+       g_hash_table_destroy (message->headers);
+
+    if (message->file)
+       fclose (message->file);
+
+    return 0;
+}
+
+/* Create a new notmuch_message_file_t for 'filename' with 'ctx' as
+ * the talloc owner. */
+notmuch_message_file_t *
+_notmuch_message_file_open_ctx (void *ctx, const char *filename)
+{
+    notmuch_message_file_t *message;
+
+    message = talloc_zero (ctx, notmuch_message_file_t);
+    if (unlikely (message == NULL))
+       return NULL;
+
+    talloc_set_destructor (message, _notmuch_message_file_destructor);
+
+    message->file = fopen (filename, "r");
+    if (message->file == NULL)
+       goto FAIL;
+
+    message->headers = g_hash_table_new_full (strcase_hash,
+                                             strcase_equal,
+                                             free,
+                                             free);
+
+    message->parsing_started = 0;
+    message->parsing_finished = 0;
+
+    return message;
+
+  FAIL:
+    fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
+    notmuch_message_file_close (message);
+
+    return NULL;
+}
+
+notmuch_message_file_t *
+notmuch_message_file_open (const char *filename)
+{
+    return _notmuch_message_file_open_ctx (NULL, filename);
+}
+
+void
+notmuch_message_file_close (notmuch_message_file_t *message)
+{
+    talloc_free (message);
+}
+
+void
+notmuch_message_file_restrict_headersv (notmuch_message_file_t *message,
+                                       va_list va_headers)
+{
+    char *header;
+
+    if (message->parsing_started)
+       INTERNAL_ERROR ("notmuch_message_file_restrict_headers called after parsing has started");
+
+    while (1) {
+       header = va_arg (va_headers, char*);
+       if (header == NULL)
+           break;
+       g_hash_table_insert (message->headers,
+                            xstrdup (header), NULL);
+    }
+
+    message->restrict_headers = 1;
+}
+
+void
+notmuch_message_file_restrict_headers (notmuch_message_file_t *message, ...)
+{
+    va_list va_headers;
+
+    va_start (va_headers, message);
+
+    notmuch_message_file_restrict_headersv (message, va_headers);
+}
+
+static void
+copy_header_unfolding (header_value_closure_t *value,
+                      const char *chunk)
+{
+    char *last;
+
+    if (chunk == NULL)
+       return;
+
+    while (*chunk == ' ' || *chunk == '\t')
+       chunk++;
+
+    if (value->len + 1 + strlen (chunk) + 1 > value->size) {
+       unsigned int new_size = value->size;
+       if (value->size == 0)
+           new_size = strlen (chunk) + 1;
+       else
+           while (value->len + 1 + strlen (chunk) + 1 > new_size)
+               new_size *= 2;
+       value->str = xrealloc (value->str, new_size);
+       value->size = new_size;
+    }
+
+    last = value->str + value->len;
+    if (value->len) {
+       *last = ' ';
+       last++;
+       value->len++;
+    }
+
+    strcpy (last, chunk);
+    value->len += strlen (chunk);
+
+    last = value->str + value->len - 1;
+    if (*last == '\n') {
+       *last = '\0';
+       value->len--;
+    }
+}
+
+/* As a special-case, a value of NULL for header_desired will force
+ * the entire header to be parsed if it is not parsed already. This is
+ * used by the _notmuch_message_file_get_headers_end function. */
+const char *
+notmuch_message_file_get_header (notmuch_message_file_t *message,
+                                const char *header_desired)
+{
+    int contains;
+    char *header, *decoded_value;
+    const char *s, *colon;
+    int match;
+    static int initialized = 0;
+
+    if (! initialized) {
+       g_mime_init (0);
+       initialized = 1;
+    }
+
+    message->parsing_started = 1;
+
+    if (header_desired == NULL)
+       contains = 0;
+    else
+       contains = g_hash_table_lookup_extended (message->headers,
+                                                header_desired, NULL,
+                                                (gpointer *) &decoded_value);
+
+    if (contains && decoded_value)
+       return decoded_value;
+
+    if (message->parsing_finished)
+       return NULL;
+
+#define NEXT_HEADER_LINE(closure)                              \
+    while (1) {                                                        \
+       ssize_t bytes_read = getline (&message->line,           \
+                                     &message->line_size,      \
+                                     message->file);           \
+       if (bytes_read == -1) {                                 \
+           message->parsing_finished = 1;                      \
+           break;                                              \
+       }                                                       \
+       if (*message->line == '\n') {                           \
+           message->parsing_finished = 1;                      \
+           break;                                              \
+       }                                                       \
+       if (closure &&                                          \
+           (*message->line == ' ' || *message->line == '\t'))  \
+       {                                                       \
+           copy_header_unfolding ((closure), message->line);   \
+       }                                                       \
+       if (*message->line == ' ' || *message->line == '\t')    \
+           message->header_size += strlen (message->line);     \
+       else                                                    \
+           break;                                              \
+    }
+
+    if (message->line == NULL)
+       NEXT_HEADER_LINE (NULL);
+
+    while (1) {
+
+       if (message->parsing_finished)
+           break;
+
+       colon = strchr (message->line, ':');
+
+       if (colon == NULL) {
+           message->broken_headers++;
+           /* A simple heuristic for giving up on things that just
+            * don't look like mail messages. */
+           if (message->broken_headers >= 10 &&
+               message->good_headers < 5)
+           {
+               message->parsing_finished = 1;
+               continue;
+           }
+           NEXT_HEADER_LINE (NULL);
+           continue;
+       }
+
+       message->header_size += strlen (message->line);
+
+       message->good_headers++;
+
+       header = xstrndup (message->line, colon - message->line);
+
+       if (message->restrict_headers &&
+           ! g_hash_table_lookup_extended (message->headers,
+                                           header, NULL, NULL))
+       {
+           free (header);
+           NEXT_HEADER_LINE (NULL);
+           continue;
+       }
+
+       s = colon + 1;
+       while (*s == ' ' || *s == '\t')
+           s++;
+
+       message->value.len = 0;
+       copy_header_unfolding (&message->value, s);
+
+       NEXT_HEADER_LINE (&message->value);
+
+       if (header_desired == 0)
+           match = 0;
+       else
+           match = (strcasecmp (header, header_desired) == 0);
+
+       decoded_value = g_mime_utils_header_decode_text (message->value.str);
+
+       g_hash_table_insert (message->headers, header, decoded_value);
+
+       if (match)
+           return decoded_value;
+    }
+
+    if (message->line)
+       free (message->line);
+    message->line = NULL;
+
+    if (message->value.size) {
+       free (message->value.str);
+       message->value.str = NULL;
+       message->value.size = 0;
+       message->value.len = 0;
+    }
+
+    /* We've parsed all headers and never found the one we're looking
+     * for. It's probably just not there, but let's check that we
+     * didn't make a mistake preventing us from seeing it. */
+    if (message->restrict_headers && header_desired &&
+       ! g_hash_table_lookup_extended (message->headers,
+                                       header_desired, NULL, NULL))
+    {
+       INTERNAL_ERROR ("Attempt to get header \"%s\" which was not\n"
+                       "included in call to notmuch_message_file_restrict_headers\n",
+                       header_desired);
+    }
+
+    return NULL;
+}
diff --git a/lib/message.cc b/lib/message.cc
new file mode 100644 (file)
index 0000000..28f19a8
--- /dev/null
@@ -0,0 +1,658 @@
+/* message.cc - Results of message-based searches from a notmuch database
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+#include "database-private.h"
+
+#include <gmime/gmime.h>
+
+#include <xapian.h>
+
+struct _notmuch_message {
+    notmuch_database_t *notmuch;
+    Xapian::docid doc_id;
+    int frozen;
+    char *message_id;
+    char *thread_id;
+    char *filename;
+    notmuch_message_file_t *message_file;
+    Xapian::Document doc;
+};
+
+/* "128 bits of thread-id ought to be enough for anybody" */
+#define NOTMUCH_THREAD_ID_BITS  128
+#define NOTMUCH_THREAD_ID_DIGITS (NOTMUCH_THREAD_ID_BITS / 4)
+typedef struct _thread_id {
+    char str[NOTMUCH_THREAD_ID_DIGITS + 1];
+} thread_id_t;
+
+/* We end up having to call the destructor explicitly because we had
+ * to use "placement new" in order to initialize C++ objects within a
+ * block that we allocated with talloc. So C++ is making talloc
+ * slightly less simple to use, (we wouldn't need
+ * talloc_set_destructor at all otherwise).
+ */
+static int
+_notmuch_message_destructor (notmuch_message_t *message)
+{
+    message->doc.~Document ();
+
+    return 0;
+}
+
+/* Create a new notmuch_message_t object for an existing document in
+ * the database.
+ *
+ * Here, 'talloc owner' is an optional talloc context to which the new
+ * message will belong. This allows for the caller to not bother
+ * calling notmuch_message_destroy on the message, and no that all
+ * memory will be reclaimed with 'talloc_owner' is free. The caller
+ * still can call notmuch_message_destroy when finished with the
+ * message if desired.
+ *
+ * The 'talloc_owner' argument can also be NULL, in which case the
+ * caller *is* responsible for calling notmuch_message_destroy.
+ *
+ * If no document exists in the database with document ID of 'doc_id'
+ * then this function returns NULL and optionally sets *status to
+ * NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND.
+ *
+ * This function can also fail to due lack of available memory,
+ * returning NULL and optionally setting *status to
+ * NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY.
+ *
+ * The caller can pass NULL for status if uninterested in
+ * distinguishing these two cases.
+ */
+notmuch_message_t *
+_notmuch_message_create (const void *talloc_owner,
+                        notmuch_database_t *notmuch,
+                        unsigned int doc_id,
+                        notmuch_private_status_t *status)
+{
+    notmuch_message_t *message;
+
+    if (status)
+       *status = NOTMUCH_PRIVATE_STATUS_SUCCESS;
+
+    message = talloc (talloc_owner, notmuch_message_t);
+    if (unlikely (message == NULL)) {
+       if (status)
+           *status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
+       return NULL;
+    }
+
+    message->notmuch = notmuch;
+    message->doc_id = doc_id;
+
+    message->frozen = 0;
+
+    /* Each of these will be lazily created as needed. */
+    message->message_id = NULL;
+    message->thread_id = NULL;
+    message->filename = NULL;
+    message->message_file = NULL;
+
+    /* This is C++'s creepy "placement new", which is really just an
+     * ugly way to call a constructor for a pre-allocated object. So
+     * it's really not an error to not be checking for OUT_OF_MEMORY
+     * here, since this "new" isn't actually allocating memory. This
+     * is language-design comedy of the wrong kind. */
+
+    new (&message->doc) Xapian::Document;
+
+    talloc_set_destructor (message, _notmuch_message_destructor);
+
+    try {
+       message->doc = notmuch->xapian_db->get_document (doc_id);
+    } catch (const Xapian::DocNotFoundError &error) {
+       talloc_free (message);
+       if (status)
+           *status = NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
+       return NULL;
+    }
+
+    return message;
+}
+
+/* Create a new notmuch_message_t object for a specific message ID,
+ * (which may or may not already exist in the databas).
+ *
+ * Here, 'talloc owner' is an optional talloc context to which the new
+ * message will belong. This allows for the caller to not bother
+ * calling notmuch_message_destroy on the message, and no that all
+ * memory will be reclaimed with 'talloc_owner' is free. The caller
+ * still can call notmuch_message_destroy when finished with the
+ * message if desired.
+ *
+ * The 'talloc_owner' argument can also be NULL, in which case the
+ * caller *is* responsible for calling notmuch_message_destroy.
+ *
+ * If there is already a document with message ID 'message_id' in the
+ * database, then the returned message can be used to query/modify the
+ * document. Otherwise, a new document will be inserted into the
+ * database before this function returns, (and *status will be set
+ * to NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND).
+ *
+ * If an error occurs, this function will return NULL and *status
+ * will be set as appropriate. (The status pointer argument must
+ * not be NULL.)
+ */
+notmuch_message_t *
+_notmuch_message_create_for_message_id (const void *talloc_owner,
+                                       notmuch_database_t *notmuch,
+                                       const char *message_id,
+                                       notmuch_private_status_t *status_ret)
+{
+    notmuch_message_t *message;
+    Xapian::Document doc;
+    unsigned int doc_id;
+    char *term;
+
+    *status_ret = NOTMUCH_PRIVATE_STATUS_SUCCESS;
+
+    message = notmuch_database_find_message (notmuch, message_id);
+    if (message)
+       return talloc_steal (talloc_owner, message);
+
+    term = talloc_asprintf (NULL, "%s%s",
+                           _find_prefix ("id"), message_id);
+    if (term == NULL) {
+       *status_ret = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
+       return NULL;
+    }
+
+    try {
+       doc.add_term (term);
+       talloc_free (term);
+
+       doc.add_value (NOTMUCH_VALUE_MESSAGE_ID, message_id);
+
+       doc_id = notmuch->xapian_db->add_document (doc);
+    } catch (const Xapian::Error &error) {
+       *status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
+       return NULL;
+    }
+
+    message = _notmuch_message_create (talloc_owner, notmuch,
+                                      doc_id, status_ret);
+
+    /* We want to inform the caller that we had to create a new
+     * document. */
+    if (*status_ret == NOTMUCH_PRIVATE_STATUS_SUCCESS)
+       *status_ret = NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
+
+    return message;
+}
+
+const char *
+notmuch_message_get_message_id (notmuch_message_t *message)
+{
+    Xapian::TermIterator i;
+
+    if (message->message_id)
+       return message->message_id;
+
+    i = message->doc.termlist_begin ();
+    i.skip_to (_find_prefix ("id"));
+
+    if (i == message->doc.termlist_end ())
+       INTERNAL_ERROR ("Message with document ID of %d has no message ID.\n",
+                       message->doc_id);
+
+    message->message_id = talloc_strdup (message, (*i).c_str () + 1);
+
+#if DEBUG_DATABASE_SANITY
+    i++;
+
+    if (i != message->doc.termlist_end () &&
+       strncmp ((*i).c_str (), _find_prefix ("id"),
+                strlen (_find_prefix ("id"))) == 0)
+    {
+       INTERNAL_ERROR ("Mail (doc_id: %d) has duplicate message IDs",
+                       message->doc_id);
+    }
+#endif
+
+    return message->message_id;
+}
+
+static void
+_notmuch_message_ensure_message_file (notmuch_message_t *message)
+{
+    const char *filename;
+
+    if (message->message_file)
+       return;
+
+    filename = notmuch_message_get_filename (message);
+    if (unlikely (filename == NULL))
+       return;
+
+    message->message_file = _notmuch_message_file_open_ctx (message, filename);
+}
+
+const char *
+notmuch_message_get_header (notmuch_message_t *message, const char *header)
+{
+    _notmuch_message_ensure_message_file (message);
+    if (message->message_file == NULL)
+       return NULL;
+
+    return notmuch_message_file_get_header (message->message_file, header);
+}
+
+const char *
+notmuch_message_get_thread_id (notmuch_message_t *message)
+{
+    Xapian::TermIterator i;
+
+    if (message->thread_id)
+       return message->thread_id;
+
+    i = message->doc.termlist_begin ();
+    i.skip_to (_find_prefix ("thread"));
+
+    if (i == message->doc.termlist_end ())
+       INTERNAL_ERROR ("Message with document ID of %d has no thread ID.\n",
+                       message->doc_id);
+
+    message->thread_id = talloc_strdup (message, (*i).c_str () + 1);
+
+#if DEBUG_DATABASE_SANITY
+    i++;
+
+    if (i != message->doc.termlist_end () &&
+       strncmp ((*i).c_str (), _find_prefix ("thread"),
+                strlen (_find_prefix ("thread"))) == 0)
+    {
+       INTERNAL_ERROR ("Message %s has duplicate thread IDs: %s and %s\n",
+                       notmuch_message_get_message_id (message),
+                       message->thread_id,
+                       (*i).c_str () + 1);
+    }
+#endif
+
+    return message->thread_id;
+}
+
+/* Set the filename for 'message' to 'filename'.
+ *
+ * XXX: We should still figure out if we think it's important to store
+ * multiple filenames for email messages with identical message IDs.
+ *
+ * This change will not be reflected in the database until the next
+ * call to _notmuch_message_set_sync. */
+void
+_notmuch_message_set_filename (notmuch_message_t *message,
+                              const char *filename)
+{
+    const char *s;
+    const char *db_path;
+    unsigned int db_path_len;
+
+    if (message->filename) {
+       talloc_free (message->filename);
+       message->filename = NULL;
+    }
+
+    if (filename == NULL)
+       INTERNAL_ERROR ("Message filename cannot be NULL.");
+
+    s = filename;
+
+    db_path = notmuch_database_get_path (message->notmuch);
+    db_path_len = strlen (db_path);
+
+    if (*s == '/' && strncmp (s, db_path, db_path_len) == 0
+       && strlen (s) > db_path_len)
+    {
+       s += db_path_len + 1;
+    }
+
+    message->doc.set_data (s);
+}
+
+const char *
+notmuch_message_get_filename (notmuch_message_t *message)
+{
+    std::string filename_str;
+    const char *db_path;
+
+    if (message->filename)
+       return message->filename;
+
+    filename_str = message->doc.get_data ();
+    db_path = notmuch_database_get_path (message->notmuch);
+
+    if (filename_str[0] != '/')
+       message->filename = talloc_asprintf (message, "%s/%s", db_path,
+                                            filename_str.c_str ());
+    else
+       message->filename = talloc_strdup (message, filename_str.c_str ());
+
+    return message->filename;
+}
+
+time_t
+notmuch_message_get_date (notmuch_message_t *message)
+{
+    std::string value;
+
+    try {
+       value = message->doc.get_value (NOTMUCH_VALUE_TIMESTAMP);
+    } catch (Xapian::Error &error) {
+       INTERNAL_ERROR ("Failed to read timestamp value from document.");
+       return 0;
+    }
+
+    return Xapian::sortable_unserialise (value);
+}
+
+notmuch_tags_t *
+notmuch_message_get_tags (notmuch_message_t *message)
+{
+    const char *prefix = _find_prefix ("tag");
+    Xapian::TermIterator i, end;
+    notmuch_tags_t *tags;
+    std::string tag;
+
+    /* Currently this iteration is written with the assumption that
+     * "tag" has a single-character prefix. */
+    assert (strlen (prefix) == 1);
+
+    tags = _notmuch_tags_create (message);
+    if (unlikely (tags == NULL))
+       return NULL;
+
+    i = message->doc.termlist_begin ();
+    end = message->doc.termlist_end ();
+
+    i.skip_to (prefix);
+
+    while (1) {
+       tag = *i;
+
+       if (tag.empty () || tag[0] != *prefix)
+           break;
+
+       _notmuch_tags_add_tag (tags, tag.c_str () + 1);
+
+       i++;
+    }
+
+    _notmuch_tags_prepare_iterator (tags);
+
+    return tags;
+}
+
+void
+_notmuch_message_set_date (notmuch_message_t *message,
+                          const char *date)
+{
+    time_t time_value;
+
+    /* GMime really doesn't want to see a NULL date, so protect its
+     * sensibilities. */
+    if (date == NULL)
+       time_value = 0;
+    else
+       time_value = g_mime_utils_header_decode_date (date, NULL);
+
+    message->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
+                           Xapian::sortable_serialise (time_value));
+}
+
+static void
+thread_id_generate (thread_id_t *thread_id)
+{
+    static int seeded = 0;
+    FILE *dev_random;
+    uint32_t value;
+    char *s;
+    int i;
+
+    if (! seeded) {
+       dev_random = fopen ("/dev/random", "r");
+       if (dev_random == NULL) {
+           srand (time (NULL));
+       } else {
+           fread ((void *) &value, sizeof (value), 1, dev_random);
+           srand (value);
+           fclose (dev_random);
+       }
+       seeded = 1;
+    }
+
+    s = thread_id->str;
+    for (i = 0; i < NOTMUCH_THREAD_ID_DIGITS; i += 8) {
+       value = rand ();
+       sprintf (s, "%08x", value);
+       s += 8;
+    }
+}
+
+void
+_notmuch_message_ensure_thread_id (notmuch_message_t *message)
+{
+    /* If not part of any existing thread, generate a new thread_id. */
+    thread_id_t thread_id;
+
+    thread_id_generate (&thread_id);
+    _notmuch_message_add_term (message, "thread", thread_id.str);
+}
+
+/* Synchronize changes made to message->doc out into the database. */
+void
+_notmuch_message_sync (notmuch_message_t *message)
+{
+    Xapian::WritableDatabase *db = message->notmuch->xapian_db;
+
+    db->replace_document (message->doc_id, message->doc);
+}
+
+/* Add a name:value term to 'message', (the actual term will be
+ * encoded by prefixing the value with a short prefix). See
+ * NORMAL_PREFIX and BOOLEAN_PREFIX arrays for the mapping of term
+ * names to prefix values.
+ *
+ * This change will not be reflected in the database until the next
+ * call to _notmuch_message_set_sync. */
+notmuch_private_status_t
+_notmuch_message_add_term (notmuch_message_t *message,
+                          const char *prefix_name,
+                          const char *value)
+{
+
+    char *term;
+
+    if (value == NULL)
+       return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
+
+    term = talloc_asprintf (message, "%s%s",
+                           _find_prefix (prefix_name), value);
+
+    if (strlen (term) > NOTMUCH_TERM_MAX)
+       return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
+
+    message->doc.add_term (term);
+
+    talloc_free (term);
+
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
+/* Parse 'text' and add a term to 'message' for each parsed word. Each
+ * term will be added both prefixed (if prefix_name is not NULL) and
+ * also unprefixed). */
+notmuch_private_status_t
+_notmuch_message_gen_terms (notmuch_message_t *message,
+                           const char *prefix_name,
+                           const char *text)
+{
+    Xapian::TermGenerator *term_gen = message->notmuch->term_gen;
+
+    if (text == NULL)
+       return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
+
+    term_gen->set_document (message->doc);
+
+    if (prefix_name) {
+       const char *prefix = _find_prefix (prefix_name);
+
+       term_gen->index_text (text, 1, prefix);
+    }
+
+    term_gen->index_text (text);
+
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
+/* Remove a name:value term from 'message', (the actual term will be
+ * encoded by prefixing the value with a short prefix). See
+ * NORMAL_PREFIX and BOOLEAN_PREFIX arrays for the mapping of term
+ * names to prefix values.
+ *
+ * This change will not be reflected in the database until the next
+ * call to _notmuch_message_set_sync. */
+notmuch_private_status_t
+_notmuch_message_remove_term (notmuch_message_t *message,
+                             const char *prefix_name,
+                             const char *value)
+{
+    char *term;
+
+    if (value == NULL)
+       return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
+
+    term = talloc_asprintf (message, "%s%s",
+                           _find_prefix (prefix_name), value);
+
+    if (strlen (term) > NOTMUCH_TERM_MAX)
+       return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
+
+    try {
+       message->doc.remove_term (term);
+    } catch (const Xapian::InvalidArgumentError) {
+       /* We'll let the philosopher's try to wrestle with the
+        * question of whether failing to remove that which was not
+        * there in the first place is failure. For us, we'll silently
+        * consider it all good. */
+    }
+
+    talloc_free (term);
+
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
+{
+    notmuch_private_status_t status;
+
+    if (tag == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (strlen (tag) > NOTMUCH_TAG_MAX)
+       return NOTMUCH_STATUS_TAG_TOO_LONG;
+
+    status = _notmuch_message_add_term (message, "tag", tag);
+    if (status) {
+       INTERNAL_ERROR ("_notmuch_message_add_term return unexpected value: %d\n",
+                       status);
+    }
+
+    if (! message->frozen)
+       _notmuch_message_sync (message);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
+{
+    notmuch_private_status_t status;
+
+    if (tag == NULL)
+       return NOTMUCH_STATUS_NULL_POINTER;
+
+    if (strlen (tag) > NOTMUCH_TAG_MAX)
+       return NOTMUCH_STATUS_TAG_TOO_LONG;
+
+    status = _notmuch_message_remove_term (message, "tag", tag);
+    if (status) {
+       INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
+                       status);
+    }
+
+    if (! message->frozen)
+       _notmuch_message_sync (message);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+void
+notmuch_message_remove_all_tags (notmuch_message_t *message)
+{
+    notmuch_private_status_t status;
+    notmuch_tags_t *tags;
+    const char *tag;
+
+    for (tags = notmuch_message_get_tags (message);
+        notmuch_tags_has_more (tags);
+        notmuch_tags_advance (tags))
+    {
+       tag = notmuch_tags_get (tags);
+
+       status = _notmuch_message_remove_term (message, "tag", tag);
+       if (status) {
+           INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
+                           status);
+       }
+    }
+
+    if (! message->frozen)
+       _notmuch_message_sync (message);
+}
+
+void
+notmuch_message_freeze (notmuch_message_t *message)
+{
+    message->frozen++;
+}
+
+notmuch_status_t
+notmuch_message_thaw (notmuch_message_t *message)
+{
+    if (message->frozen > 0) {
+       message->frozen--;
+       if (message->frozen == 0)
+           _notmuch_message_sync (message);
+       return NOTMUCH_STATUS_SUCCESS;
+    } else {
+       return NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW;
+    }
+}
+
+void
+notmuch_message_destroy (notmuch_message_t *message)
+{
+    talloc_free (message);
+}
diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h
new file mode 100644 (file)
index 0000000..92b4634
--- /dev/null
@@ -0,0 +1,314 @@
+/* notmuch-private.h - Internal interfaces for notmuch.
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#ifndef NOTMUCH_PRIVATE_H
+#define NOTMUCH_PRIVATE_H
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* For getline and asprintf */
+#endif
+#include <stdio.h>
+
+#include "notmuch.h"
+
+NOTMUCH_BEGIN_DECLS
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <talloc.h>
+
+#include "xutil.h"
+
+#ifdef DEBUG
+# define DEBUG_DATABASE_SANITY 1
+# define DEBUG_QUERY 1
+#endif
+
+#define COMPILE_TIME_ASSERT(pred) ((void)sizeof(char[1 - 2*!(pred)]))
+
+/* There's no point in continuing when we've detected that we've done
+ * something wrong internally (as opposed to the user passing in a
+ * bogus value).
+ *
+ * Note that PRINTF_ATTRIBUTE comes from talloc.h
+ */
+int
+_internal_error (const char *format, ...) PRINTF_ATTRIBUTE (1, 2);
+
+/* There's no point in continuing when we've detected that we've done
+ * something wrong internally (as opposed to the user passing in a
+ * bogus value).
+ *
+ * Note that __location__ comes from talloc.h.
+ */
+#define INTERNAL_ERROR(format, ...)                    \
+    _internal_error (format " (%s).\n",                        \
+                    ##__VA_ARGS__, __location__)
+
+#define unused(x) x __attribute__ ((unused))
+
+/* Thanks to Andrew Tridgell's (SAMBA's) talloc for this definition of
+ * unlikely. The talloc source code comes to us via the GNU LGPL v. 3.
+ */
+/* these macros gain us a few percent of speed on gcc */
+#if (__GNUC__ >= 3)
+/* the strange !! is to ensure that __builtin_expect() takes either 0 or 1
+   as its first argument */
+#ifndef likely
+#define likely(x)   __builtin_expect(!!(x), 1)
+#endif
+#ifndef unlikely
+#define unlikely(x) __builtin_expect(!!(x), 0)
+#endif
+#else
+#ifndef likely
+#define likely(x) (x)
+#endif
+#ifndef unlikely
+#define unlikely(x) (x)
+#endif
+#endif
+
+typedef enum {
+    NOTMUCH_VALUE_TIMESTAMP = 0,
+    NOTMUCH_VALUE_MESSAGE_ID
+} notmuch_value_t;
+
+/* Xapian (with flint backend) complains if we provide a term longer
+ * than this, but I haven't yet found a way to query the limit
+ * programmatically. */
+#define NOTMUCH_TERM_MAX 245
+
+typedef enum _notmuch_private_status {
+    /* First, copy all the public status values. */
+    NOTMUCH_PRIVATE_STATUS_SUCCESS = NOTMUCH_STATUS_SUCCESS,
+    NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY = NOTMUCH_STATUS_OUT_OF_MEMORY,
+    NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION = NOTMUCH_STATUS_XAPIAN_EXCEPTION,
+    NOTMUCH_PRIVATE_STATUS_FILE_NOT_EMAIL = NOTMUCH_STATUS_FILE_NOT_EMAIL,
+    NOTMUCH_PRIVATE_STATUS_NULL_POINTER = NOTMUCH_STATUS_NULL_POINTER,
+    NOTMUCH_PRIVATE_STATUS_TAG_TOO_LONG = NOTMUCH_STATUS_TAG_TOO_LONG,
+
+    /* Then add our own private values. */
+    NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG,
+    NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND,
+
+    NOTMUCH_PRIVATE_STATUS_LAST_STATUS
+} notmuch_private_status_t;
+
+/* Coerce a notmuch_private_status_t value to a notmuch_status_t
+ * value, generating an internal error if the private value is equal
+ * to or greater than NOTMUCH_STATUS_LAST_STATUS. (The idea here is
+ * that the caller has previously handled any expected
+ * notmuch_private_status_t values.)
+ */
+#define COERCE_STATUS(private_status, format, ...)                     \
+    ((private_status >= (notmuch_private_status_t) NOTMUCH_STATUS_LAST_STATUS)\
+     ?                                                                 \
+     (notmuch_status_t) _internal_error (format " (%s).\n",            \
+                                         ##__VA_ARGS__,                        \
+                                         __location__)                 \
+     :                                                                 \
+     (notmuch_status_t) private_status)
+
+/* database.cc */
+
+/* Lookup a prefix value by name.
+ *
+ * XXX: This should really be static inside of message.cc, and we can
+ * do that once we convert database.cc to use the
+ * _notmuch_message_add/remove_term functions. */
+const char *
+_find_prefix (const char *name);
+
+/* thread.cc */
+
+notmuch_thread_t *
+_notmuch_thread_create (const void *talloc_owner,
+                       notmuch_database_t *notmuch,
+                       const char *thread_id);
+
+void
+_notmuch_thread_add_message (notmuch_thread_t *thread,
+                            notmuch_message_t *message);
+
+/* message.cc */
+
+notmuch_message_t *
+_notmuch_message_create (const void *talloc_owner,
+                        notmuch_database_t *notmuch,
+                        unsigned int doc_id,
+                        notmuch_private_status_t *status);
+
+notmuch_message_t *
+_notmuch_message_create_for_message_id (const void *talloc_owner,
+                                       notmuch_database_t *notmuch,
+                                       const char *message_id,
+                                       notmuch_private_status_t *status);
+
+const char *
+_notmuch_message_get_subject (notmuch_message_t *message);
+
+notmuch_private_status_t
+_notmuch_message_add_term (notmuch_message_t *message,
+                          const char *prefix_name,
+                          const char *value);
+
+notmuch_private_status_t
+_notmuch_message_remove_term (notmuch_message_t *message,
+                             const char *prefix_name,
+                             const char *value);
+
+notmuch_private_status_t
+_notmuch_message_gen_terms (notmuch_message_t *message,
+                           const char *prefix_name,
+                           const char *text);
+
+void
+_notmuch_message_set_filename (notmuch_message_t *message,
+                              const char *filename);
+
+void
+_notmuch_message_add_thread_id (notmuch_message_t *message,
+                               const char *thread_id);
+
+void
+_notmuch_message_ensure_thread_id (notmuch_message_t *message);
+
+void
+_notmuch_message_set_date (notmuch_message_t *message,
+                          const char *date);
+
+void
+_notmuch_message_sync (notmuch_message_t *message);
+
+/* index.cc */
+
+notmuch_status_t
+_notmuch_message_index_file (notmuch_message_t *message,
+                            const char *filename);
+
+/* message-file.c */
+
+/* XXX: I haven't decided yet whether these will actually get exported
+ * into the public interface in notmuch.h
+ */
+
+typedef struct _notmuch_message_file notmuch_message_file_t;
+
+/* Open a file containing a single email message.
+ *
+ * The caller should call notmuch_message_close when done with this.
+ *
+ * Returns NULL if any error occurs.
+ */
+notmuch_message_file_t *
+notmuch_message_file_open (const char *filename);
+
+/* Like notmuch_message_file_open but with 'ctx' as the talloc owner. */
+notmuch_message_file_t *
+_notmuch_message_file_open_ctx (void *ctx, const char *filename);
+
+/* Close a notmuch message preivously opened with notmuch_message_open. */
+void
+notmuch_message_file_close (notmuch_message_file_t *message);
+
+/* Restrict 'message' to only save the named headers.
+ *
+ * When the caller is only interested in a short list of headers,
+ * known in advance, calling this function can avoid wasted time and
+ * memory parsing/saving header values that will never be needed.
+ *
+ * The variable arguments should be a list of const char * with a
+ * final '(const char *) NULL' to terminate the list.
+ *
+ * If this function is called, it must be called before any calls to
+ * notmuch_message_get_header for this message.
+ *
+ * After calling this function, if notmuch_message_get_header is
+ * called with a header name not in this list, then NULL will be
+ * returned even if that header exists in the actual message.
+ */
+void
+notmuch_message_file_restrict_headers (notmuch_message_file_t *message, ...);
+
+/* Identical to notmuch_message_restrict_headers but accepting a va_list. */
+void
+notmuch_message_file_restrict_headersv (notmuch_message_file_t *message,
+                                       va_list va_headers);
+
+/* Get the value of the specified header from the message.
+ *
+ * The header name is case insensitive.
+ *
+ * The returned value is owned by the notmuch message and is valid
+ * only until the message is closed. The caller should copy it if
+ * needing to modify the value or to hold onto it for longer.
+ *
+ * Returns NULL if the message does not contain a header line matching
+ * 'header'.
+ */
+const char *
+notmuch_message_file_get_header (notmuch_message_file_t *message,
+                                const char *header);
+
+/* date.c */
+
+/* Parse an RFC 8222 date string to a time_t value.
+ *
+ * The tz_offset argument can be used to also obtain the time-zone
+ * offset, (but can be NULL if the call is not interested in that).
+ *
+ * Returns 0 on error.
+ */
+time_t
+notmuch_parse_date (const char *str, int *tz_offset);
+
+/* sha1.c */
+
+char *
+notmuch_sha1_of_string (const char *str);
+
+char *
+notmuch_sha1_of_file (const char *filename);
+
+/* tags.c */
+
+notmuch_tags_t *
+_notmuch_tags_create (void *ctx);
+
+void
+_notmuch_tags_add_tag (notmuch_tags_t *tags, const char *tag);
+
+void
+_notmuch_tags_prepare_iterator (notmuch_tags_t *tags);
+
+NOTMUCH_END_DECLS
+
+#endif
diff --git a/lib/notmuch.h b/lib/notmuch.h
new file mode 100644 (file)
index 0000000..bab573d
--- /dev/null
@@ -0,0 +1,834 @@
+/* notmuch - Not much of an email library, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#ifndef NOTMUCH_H
+#define NOTMUCH_H
+
+#ifdef  __cplusplus
+# define NOTMUCH_BEGIN_DECLS  extern "C" {
+# define NOTMUCH_END_DECLS    }
+#else
+# define NOTMUCH_BEGIN_DECLS
+# define NOTMUCH_END_DECLS
+#endif
+
+NOTMUCH_BEGIN_DECLS
+
+#include <time.h>
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+typedef int notmuch_bool_t;
+
+/* Status codes used for the return values of most functions.
+ *
+ * A zero value (NOTMUCH_STATUS_SUCCESS) indicates that the function
+ * completed without error. Any other value indicates an error as
+ * follows:
+ *
+ * NOTMUCH_STATUS_SUCCESS: No error occurred.
+ *
+ * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory
+ *
+ * XXX: We don't really want to expose this lame XAPIAN_EXCEPTION
+ * value. Instead we should map to things like DATABASE_LOCKED or
+ * whatever.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred
+ *
+ * NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to read or
+ *     write to a file (this could be file not found, permission
+ *     denied, etc.)
+ *
+ * NOTMUCH_STATUS_FILE_NOT_EMAIL: A file was presented that doesn't
+ *     appear to be an email message.
+ *
+ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: A file contains a message ID
+ *     that is identical to a message already in the database.
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The user erroneously passed a NULL
+ *     pointer to a notmuch function.
+ *
+ * NOTMUCH_STATUS_TAG_TOO_LONG: A tag value is too long (exceeds
+ *     NOTMUCH_TAG_MAX)
+ *
+ * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: The notmuch_message_thaw
+ *     function has been called more times than notmuch_message_freeze.
+ *
+ * And finally:
+ *
+ * NOTMUCH_STATUS_LAST_STATUS: Not an actual status value. Just a way
+ *     to find out how many valid status values there are.
+ */
+typedef enum _notmuch_status {
+    NOTMUCH_STATUS_SUCCESS = 0,
+    NOTMUCH_STATUS_OUT_OF_MEMORY,
+    NOTMUCH_STATUS_XAPIAN_EXCEPTION,
+    NOTMUCH_STATUS_FILE_ERROR,
+    NOTMUCH_STATUS_FILE_NOT_EMAIL,
+    NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID,
+    NOTMUCH_STATUS_NULL_POINTER,
+    NOTMUCH_STATUS_TAG_TOO_LONG,
+    NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW,
+
+    NOTMUCH_STATUS_LAST_STATUS
+} notmuch_status_t;
+
+/* Get a string representation of a notmuch_status_t value.
+ *
+ * The result is readonly.
+ */
+const char *
+notmuch_status_to_string (notmuch_status_t status);
+
+/* Various opaque data types. For each notmuch_<foo>_t see the various
+ * notmuch_<foo> functions below. */
+typedef struct _notmuch_database notmuch_database_t;
+typedef struct _notmuch_query notmuch_query_t;
+typedef struct _notmuch_threads notmuch_threads_t;
+typedef struct _notmuch_thread notmuch_thread_t;
+typedef struct _notmuch_messages notmuch_messages_t;
+typedef struct _notmuch_message notmuch_message_t;
+typedef struct _notmuch_tags notmuch_tags_t;
+
+/* Lookup the default database path.
+ *
+ * This is the path that will be used by notmuch_database_create and
+ * notmuch_database_open if given a NULL path. Specifically it will be
+ * the value of the NOTMUCH_BASE environment variable if set,
+ * otherwise ${HOME}/mail
+ *
+ * Returns a newly allocated string which the caller should free()
+ * when finished with it.
+ */
+char *
+notmuch_database_default_path (void);
+
+/* Create a new, empty notmuch database located at 'path'.
+ *
+ * The path should be a top-level directory to a collection of
+ * plain-text email messages (one message per file). This call will
+ * create a new ".notmuch" directory within 'path' where notmuch will
+ * store its data.
+ *
+ * Passing a value of NULL for 'path' will cause notmuch to open the
+ * default database. The default database path can be specified by the
+ * NOTMUCH_BASE environment variable, and is equivalent to
+ * ${HOME}/mail if NOTMUCH_BASE is not set.
+ *
+ * After a successful call to notmuch_database_create, the returned
+ * database will be open so the caller should call
+ * notmuch_database_close when finished with it.
+ *
+ * The database will not yet have any data in it
+ * (notmuch_database_create itself is a very cheap function). Messages
+ * contained within 'path' can be added to the database by calling
+ * notmuch_database_add_message.
+ *
+ * In case of any failure, this function returns NULL, (after printing
+ * an error message on stderr).
+ */
+notmuch_database_t *
+notmuch_database_create (const char *path);
+
+/* XXX: I think I'd like this to take an extra argument of
+ * notmuch_status_t* for returning a status value on failure. */
+
+/* Open a an existing notmuch database located at 'path'.
+ *
+ * The database should have been created at some time in the past,
+ * (not necessarily by this process), by calling
+ * notmuch_database_create with 'path'.
+ *
+ * An existing notmuch database can be identified by the presence of a
+ * directory named ".notmuch" below 'path'.
+ *
+ * Passing a value of NULL for 'path' will cause notmuch to open the
+ * default database. The default database path can be specified by the
+ * NOTMUCH_BASE environment variable, and is equivalent to
+ * ${HOME}/mail if NOTMUCH_BASE is not set.
+ *
+ * The caller should call notmuch_database_close when finished with
+ * this database.
+ *
+ * In case of any failure, this function returns NULL, (after printing
+ * an error message on stderr).
+ */
+notmuch_database_t *
+notmuch_database_open (const char *path);
+
+/* Close the given notmuch database, freeing all associated
+ * resources. See notmuch_database_open. */
+void
+notmuch_database_close (notmuch_database_t *database);
+
+/* Return the database path of the given database.
+ *
+ * The return value is a string owned by notmuch so should not be
+ * modified nor freed by the caller. */
+const char *
+notmuch_database_get_path (notmuch_database_t *database);
+
+/* Store a timestamp within the database.
+ *
+ * The Notmuch database will not interpret this key nor the timestamp
+ * values at all. It will merely store them together and return the
+ * timestamp when notmuch_database_get_timestamp is called with the
+ * same value for 'key'.
+ *
+ * The intention is for the caller to use the timestamp to allow
+ * efficient identification of new messages to be added to the
+ * database. The recommended usage is as follows:
+ *
+ *   o Read the mtime of a directory from the filesystem
+ *
+ *   o Call add_message for all mail files in the directory
+ *
+ *   o Call notmuch_database_set_timestamp with the path of the
+ *     directory as 'key' and the originally read mtime as 'value'.
+ *
+ * Then, when wanting to check for updates to the directory in the
+ * future, the client can call notmuch_database_get_timestamp and know
+ * that it only needs to add files if the mtime of the directory and
+ * files are newer than the stored timestamp.
+ *
+ * Note: The notmuch_database_get_timestamp function does not allow
+ * the caller to distinguish a timestamp of 0 from a non-existent
+ * timestamp. So don't store a timestamp of 0 unless you are
+ * comfortable with that.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Timestamp successfully stored in database.
+ *
+ * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception
+ *     occurred. Timestamp not stored.
+ */
+notmuch_status_t
+notmuch_database_set_timestamp (notmuch_database_t *database,
+                               const char *key, time_t timestamp);
+
+/* Retrieve a timestamp from the database.
+ *
+ * Returns the timestamp value previously stored by calling
+ * notmuch_database_set_timestamp with the same value for 'key'.
+ *
+ * Returns 0 if no timestamp is stored for 'key' or if any error
+ * occurred querying the database.
+ */
+time_t
+notmuch_database_get_timestamp (notmuch_database_t *database,
+                               const char *key);
+
+/* Add a new message to the given notmuch database.
+ *
+ * Here,'filename' should be a path relative to the the path of
+ * 'database' (see notmuch_database_get_path), or else should be an
+ * absolute filename with initial components that match the path of
+ * 'database'.
+ *
+ * The file should be a single mail message (not a multi-message mbox)
+ * that is expected to remain at its current location, (since the
+ * notmuch database will reference the filename, and will not copy the
+ * entire contents of the file.
+ *
+ * If 'message' is not NULL, then, on successful return '*message'
+ * will be initialized to a message object that can be used for things
+ * such as adding tags to the just-added message. The user should call
+ * notmuch_message_destroy when done with the message. On any failure
+ * '*message' will be set to NULL.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully added to database.
+ *
+ * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: Message has the same message
+ *     ID as another message already in the database. Nothing added
+ *     to the database.
+ *
+ * NOTMUCH_STATUS_FILE_ERROR: an error occurred trying to open the
+ *     file, (such as permission denied, or file not found,
+ *     etc.). Nothing added to the database.
+ *
+ * NOTMUCH_STATUS_FILE_NOT_EMAIL: the contents of filename don't look
+ *     like an email message. Nothing added to the database.
+ */
+notmuch_status_t
+notmuch_database_add_message (notmuch_database_t *database,
+                             const char *filename,
+                             notmuch_message_t **message);
+
+/* Find a message with the given messsage_id.
+ *
+ * If the database contains a message with the given message_id, then
+ * a new notmuch_message_t object is returned. The caller should call
+ * notmuch_message_destroy when done with the message.
+ *
+ * If no message is found with the given message_id or if an
+ * out-of-memory situation occurs, this function returns NULL.
+ */
+notmuch_message_t *
+notmuch_database_find_message (notmuch_database_t *database,
+                              const char *message_id);
+
+/* Create a new query for 'database'.
+ *
+ * Here, 'database' should be an open database, (see
+ * notmuch_database_open and notmuch_database_create).
+ *
+ * For the query string, we'll document the syntax here more
+ * completely in the future, but it's likely to be a specialized
+ * version of the general Xapian query syntax:
+ *
+ * http://xapian.org/docs/queryparser.html
+ *
+ * As a special case, passing a length-zero string, (that is ""), will
+ * result in a query that returns all messages in the database.
+ *
+ * See notmuch_query_set_sort for controlling the order of results and
+ * notmuch_query_search to actually execute the query.
+ *
+ * User should call notmuch_query_destroy when finished with this
+ * query.
+ *
+ * Will return NULL if insufficient memory is available.
+ */
+notmuch_query_t *
+notmuch_query_create (notmuch_database_t *database,
+                     const char *query_string);
+
+/* Sort values for notmuch_query_set_sort */
+typedef enum {
+    NOTMUCH_SORT_DATE_OLDEST_FIRST,
+    NOTMUCH_SORT_DATE_NEWEST_FIRST,
+    NOTMUCH_SORT_MESSAGE_ID
+} notmuch_sort_t;
+
+/* Specify the sorting desired for this query. */
+void
+notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort);
+
+/* Execute a query for threads, returning a notmuch_threads_t object
+ * which can be used to iterate over the results. The returned threads
+ * object is owned by the query and as such, will only be valid until
+ * notmuch_query_destroy.
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_query_t *query;
+ *     notmuch_threads_t *threads;
+ *     notmuch_thread_t *thread;
+ *
+ *     query = notmuch_query_create (database, query_string);
+ *
+ *     for (threads = notmuch_query_search_threads (query);
+ *          notmuch_threads_has_more (threads);
+ *          notmuch_threads_advance (threads))
+ *     {
+ *         thread = notmuch_threads_get (threads);
+ *         ....
+ *         notmuch_thread_destroy (thread);
+ *     }
+ *
+ *     notmuch_query_destroy (query);
+ *
+ * Note: If you are finished with a thread before its containing
+ * query, you can call notmuch_thread_destroy to clean up some memory
+ * sooner (as in the above example). Otherwise, if your thread objects
+ * are long-lived, then you don't need to call notmuch_thread_destroy
+ * and all the memory will still be reclaimed when the query is
+ * destroyed.
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_threads_t object. (For consistency, we do provide a
+ * notmuch_threads_destroy function, but there's no good reason
+ * to call it if the query is about to be destroyed).
+ */
+notmuch_threads_t *
+notmuch_query_search_threads (notmuch_query_t *query);
+
+/* Execute a query for messages, returning a notmuch_messages_t object
+ * which can be used to iterate over the results. The returned
+ * messages object is owned by the query and as such, will only be
+ * valid until notmuch_query_destroy.
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_query_t *query;
+ *     notmuch_messages_t *messages;
+ *     notmuch_message_t *message;
+ *
+ *     query = notmuch_query_create (database, query_string);
+ *
+ *     for (messages = notmuch_query_search_messages (query);
+ *          notmuch_messages_has_more (messages);
+ *          notmuch_messages_advance (messages))
+ *     {
+ *         message = notmuch_messages_get (messages);
+ *         ....
+ *         notmuch_message_destroy (message);
+ *     }
+ *
+ *     notmuch_query_destroy (query);
+ *
+ * Note: If you are finished with a message before its containing
+ * query, you can call notmuch_message_destroy to clean up some memory
+ * sooner (as in the above example). Otherwise, if your message
+ * objects are long-lived, then you don't need to call
+ * notmuch_message_destroy and all the memory will still be reclaimed
+ * when the query is destroyed.
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_messages_t object. (For consistency, we do provide a
+ * notmuch_messages_destroy function, but there's no good
+ * reason to call it if the query is about to be destroyed).
+ */
+notmuch_messages_t *
+notmuch_query_search_messages (notmuch_query_t *query);
+
+/* Destroy a notmuch_query_t along with any associated resources.
+ *
+ * This will in turn destroy any notmuch_threads_t and
+ * notmuch_messages_t objects generated by this query, (and in
+ * turn any notmuch_thrad_t and notmuch_message_t objects generated
+ * from those results, etc.), if such objects haven't already been
+ * destroyed.
+ */
+void
+notmuch_query_destroy (notmuch_query_t *query);
+
+/* Does the given notmuch_threads_t object contain any more
+ * results.
+ *
+ * When this function returns TRUE, notmuch_threads_get will
+ * return a valid object. Whereas when this function returns FALSE,
+ * notmuch_threads_get will return NULL.
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ */
+notmuch_bool_t
+notmuch_threads_has_more (notmuch_threads_t *threads);
+
+/* Get the current thread from 'threads' as a notmuch_thread_t.
+ *
+ * Note: The returned thread belongs to 'threads' and has a lifetime
+ * identical to it (and the query to which it belongs).
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ *
+ * If an out-of-memory situation occurs, this function will return
+ * NULL.
+ */
+notmuch_thread_t *
+notmuch_threads_get (notmuch_threads_t *threads);
+
+/* Advance the 'threads' iterator to the next thread.
+ *
+ * See the documentation of notmuch_query_search_threads for example
+ * code showing how to iterate over a notmuch_threads_t object.
+ */
+void
+notmuch_threads_advance (notmuch_threads_t *threads);
+
+/* Destroy a notmuch_threads_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_threads_t object will be reclaimed when the
+ * containg query object is destroyed.
+ */
+void
+notmuch_threads_destroy (notmuch_threads_t *threads);
+
+/* Get the thread ID of 'thread'.
+ *
+ * The returned string belongs to 'thread' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * thread is valid, (which is until notmuch_thread_destroy or until
+ * the query from which it derived is destroyed).
+ */
+const char *
+notmuch_thread_get_thread_id (notmuch_thread_t *thread);
+
+/* Get the subject of 'thread'
+ *
+ * The subject is taken from the first message (according to the query
+ * order---see notmuch_query_set_sort) in the query results that
+ * belongs to this thread.
+ *
+ * The returned string belongs to 'thread' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * thread is valid, (which is until notmuch_thread_destroy or until
+ * the query from which it derived is destroyed).
+ */
+const char *
+notmuch_thread_get_subject (notmuch_thread_t *thread);
+
+/* Get the date of the oldest message in 'thread' as a time_t value.
+ */
+time_t
+notmuch_thread_get_oldest_date (notmuch_thread_t *thread);
+
+/* Get the date of the oldest message in 'thread' as a time_t value.
+ */
+time_t
+notmuch_thread_get_newest_date (notmuch_thread_t *thread);
+
+/* Get the tags for 'thread', returning a notmuch_tags_t object which
+ * can be used to iterate over all tags.
+ *
+ * Note: In the Notmuch database, tags are stored on individual
+ * messages, not on threads. So the tags returned here will be all
+ * tags of the messages which matched the search and which belong to
+ * this thread.
+ *
+ * The tags object is owned by the thread and as such, will only be
+ * valid for as long as the thread is valid, (for example, until
+ * notmuch_thread_destroy or until the query from which it derived is
+ * destroyed).
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_thread_t *thread;
+ *     notmuch_tags_t *tags;
+ *     const char *tag;
+ *
+ *     thread = notmuch_threads_get (threads);
+ *
+ *     for (tags = notmuch_thread_get_tags (thread);
+ *          notmuch_tags_has_more (tags);
+ *          notmuch_result_advance (tags))
+ *     {
+ *         tag = notmuch_tags_get (tags);
+ *         ....
+ *     }
+ *
+ *     notmuch_thread_destroy (thread);
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_tags_t object. (For consistency, we do provide a
+ * notmuch_tags_destroy function, but there's no good reason to call
+ * it if the message is about to be destroyed).
+ */
+notmuch_tags_t *
+notmuch_thread_get_tags (notmuch_thread_t *thread);
+
+/* Destroy a notmuch_thread_t object. */
+void
+notmuch_thread_destroy (notmuch_thread_t *thread);
+
+/* Does the given notmuch_messages_t object contain any more
+ * messages.
+ *
+ * When this function returns TRUE, notmuch_messages_get will return a
+ * valid object. Whereas when this function returns FALSE,
+ * notmuch_messages_get will return NULL.
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ */
+notmuch_bool_t
+notmuch_messages_has_more (notmuch_messages_t *messages);
+
+/* Get the current message from 'messages' as a notmuch_message_t.
+ *
+ * Note: The returned message belongs to 'messages' and has a lifetime
+ * identical to it (and the query to which it belongs).
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ *
+ * If an out-of-memory situation occurs, this function will return
+ * NULL.
+ */
+notmuch_message_t *
+notmuch_messages_get (notmuch_messages_t *messages);
+
+/* Advance the 'messages' iterator to the next result.
+ *
+ * See the documentation of notmuch_query_search_messages for example
+ * code showing how to iterate over a notmuch_messages_t object.
+ */
+void
+notmuch_messages_advance (notmuch_messages_t *messages);
+
+/* Destroy a notmuch_messages_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_messages_t object will be reclaimed when the containg
+ * query object is destroyed.
+ */
+void
+notmuch_messages_destroy (notmuch_messages_t *messages);
+
+/* Get the message ID of 'message'.
+ *
+ * The returned string belongs to 'message' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * message is valid, (which is until the query from which it derived
+ * is destroyed).
+ *
+ * This function will not return NULL since Notmuch ensures that every
+ * message has a unique message ID, (Notmuch will generate an ID for a
+ * message if the original file does not contain one).
+ */
+const char *
+notmuch_message_get_message_id (notmuch_message_t *message);
+
+/* Get the thread ID of 'message'.
+ *
+ * The returned string belongs to 'message' and as such, should not be
+ * modified by the caller and will only be valid for as long as the
+ * message is valid, (for example, until the user calls
+ * notmuch_message_destroy on 'message' or until a query from which it
+ * derived is destroyed).
+ *
+ * This function will not return NULL since Notmuch ensures that every
+ * message belongs to a single thread.
+ */
+const char *
+notmuch_message_get_thread_id (notmuch_message_t *message);
+
+/* Get the filename for the email corresponding to 'message'.
+ *
+ * The returned filename is an absolute filename, (the initial
+ * component will match notmuch_database_get_path() ).
+ *
+ * The returned string belongs to the message so should not be
+ * modified or freed by the caller (nor should it be referenced after
+ * the message is destroyed). */
+const char *
+notmuch_message_get_filename (notmuch_message_t *message);
+
+/* Get the date of 'message' as a time_t value.
+ *
+ * For the original textual representation of the Date header from the
+ * message call notmuch_message_get_header() with a header value of
+ * "date". */
+time_t
+notmuch_message_get_date  (notmuch_message_t *message);
+
+/* Get the value of the specified header from 'message'.
+ *
+ * The value will be read from the actual message file, not from the
+ * notmuch database. The header name is case insensitive.
+ *
+ * The returned string belongs to the message so should not be
+ * modified or freed by the caller (nor should it be referenced after
+ * the message is destroyed).
+ *
+ * Returns NULL if the message does not contain a header line matching
+ * 'header' of if any error occurs.
+ */
+const char *
+notmuch_message_get_header (notmuch_message_t *message, const char *header);
+
+/* Get the tags for 'message', returning a notmuch_tags_t object which
+ * can be used to iterate over all tags.
+ *
+ * The tags object is owned by the message and as such, will only be
+ * valid for as long as the message is valid, (which is until the
+ * query from which it derived is destroyed).
+ *
+ * Typical usage might be:
+ *
+ *     notmuch_message_t *message;
+ *     notmuch_tags_t *tags;
+ *     const char *tag;
+ *
+ *     message = notmuch_database_find_message (database, message_id);
+ *
+ *     for (tags = notmuch_message_get_tags (message);
+ *          notmuch_tags_has_more (tags);
+ *          notmuch_result_advance (tags))
+ *     {
+ *         tag = notmuch_tags_get (tags);
+ *         ....
+ *     }
+ *
+ *     notmuch_message_destroy (message);
+ *
+ * Note that there's no explicit destructor needed for the
+ * notmuch_tags_t object. (For consistency, we do provide a
+ * notmuch_tags_destroy function, but there's no good reason to call
+ * it if the message is about to be destroyed).
+ */
+notmuch_tags_t *
+notmuch_message_get_tags (notmuch_message_t *message);
+
+/* The longest possible tag value. */
+#define NOTMUCH_TAG_MAX 200
+
+/* Add a tag to the given message.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Tag successfully added to message
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
+ *
+ * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
+ *     (exceeds NOTMUCH_TAG_MAX)
+ */
+notmuch_status_t
+notmuch_message_add_tag (notmuch_message_t *message, const char *tag);
+
+/* Remove a tag from the given message.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Tag successfully removed from message
+ *
+ * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
+ *
+ * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
+ *     (exceeds NOTMUCH_TAG_MAX)
+ */
+notmuch_status_t
+notmuch_message_remove_tag (notmuch_message_t *message, const char *tag);
+
+/* Remove all tags from the given message.
+ *
+ * See notmuch_message_freeze for an example showing how to safely
+ * replace tag values.
+ */
+void
+notmuch_message_remove_all_tags (notmuch_message_t *message);
+
+/* Freeze the current state of 'message' within the database.
+ *
+ * This means that changes to the message state, (via
+ * notmuch_message_add_tag, notmuch_message_remove_tag, and
+ * notmuch_message_remove_all_tags), will not be committed to the
+ * database until the message is thawed with notmuch_message_thaw.
+ *
+ * Multiple calls to freeze/thaw are valid and these calls with
+ * "stack". That is there must be as many calls to thaw as to freeze
+ * before a message is actually thawed.
+ *
+ * The ability to do freeze/thaw allows for safe transactions to
+ * change tag values. For example, explicitly setting a message to
+ * have a given set of tags might look like this:
+ *
+ *    notmuch_message_freeze (message);
+ *
+ *    notmuch_message_remove_all_tags (message);
+ *
+ *    for (i = 0; i < NUM_TAGS; i++)
+ *        notmuch_message_add_tag (message, tags[i]);
+ *
+ *    notmuch_message_thaw (message);
+ *
+ * With freeze/thaw used like this, the message in the database is
+ * guaranteed to have either the full set of original tag value, or
+ * the full set of new tag values, but nothing in between.
+ *
+ * Imagine the example above without freeze/thaw and the operation
+ * somehow getting interrupted. This could result in the message being
+ * left with no tags if the interruption happened after
+ * notmuch_message_remove_all_tags but before notmuch_message_add_tag.
+ */
+void
+notmuch_message_freeze (notmuch_message_t *message);
+
+/* Thaw the current 'message', synchronizing any changes that may have
+ * occurred while 'message' was frozen into the notmuch database.
+ *
+ * See notmuch_message_freeze for an example of how to use this
+ * function to safely provide tag changes.
+ *
+ * Multiple calls to freeze/thaw are valid and these calls with
+ * "stack". That is there must be as many calls to thaw as to freeze
+ * before a message is actually thawed.
+ *
+ * Return value:
+ *
+ * NOTMUCH_STATUS_SUCCESS: Message successfully thawed, (or at least
+ *     its frozen count has successfully been reduced by 1).
+ *
+ * NOTMUCH_STATUS_UNBALANCE_FREEZE_THAW: An attempt was made to thaw
+ *     an unfrozen message. That is, there have been an unbalanced
+ *     number of calls to notmuch_message_freeze and
+ *     notmuch_message_thaw.
+ */
+notmuch_status_t
+notmuch_message_thaw (notmuch_message_t *message);
+
+/* Destroy a notmuch_message_t object.
+ *
+ * It can be useful to call this function in the case of a single
+ * query object with many messages in the result, (such as iterating
+ * over the entire database). Otherwise, it's fine to never call this
+ * function and there will still be no memory leaks. (The memory from
+ * the messages get reclaimed when the containing query is destroyed.)
+ */
+void
+notmuch_message_destroy (notmuch_message_t *message);
+
+/* Does the given notmuch_tags_t object contain any more tags.
+ *
+ * When this function returns TRUE, notmuch_tags_get will return a
+ * valid string. Whereas when this function returns FALSE,
+ * notmuch_tags_get will return NULL.
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+notmuch_bool_t
+notmuch_tags_has_more (notmuch_tags_t *tags);
+
+/* Get the current tag from 'tags' as a string.
+ *
+ * Note: The returned string belongs to 'tags' and has a lifetime
+ * identical to it (and the query to which it utlimately belongs).
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+const char *
+notmuch_tags_get (notmuch_tags_t *tags);
+
+/* Advance the 'tags' iterator to the next tag.
+ *
+ * See the documentation of notmuch_message_get_tags for example code
+ * showing how to iterate over a notmuch_tags_t object.
+ */
+void
+notmuch_tags_advance (notmuch_tags_t *tags);
+
+/* Destroy a notmuch_tags_t object.
+ *
+ * It's not strictly necessary to call this function. All memory from
+ * the notmuch_tags_t object will be reclaimed when the containg
+ * message or query objects are destroyed.
+ */
+void
+notmuch_tags_destroy (notmuch_tags_t *tags);
+
+NOTMUCH_END_DECLS
+
+#endif
diff --git a/lib/query.cc b/lib/query.cc
new file mode 100644 (file)
index 0000000..ed57661
--- /dev/null
@@ -0,0 +1,301 @@
+/* query.cc - Support for searching a notmuch database
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+#include "database-private.h"
+
+#include <glib.h> /* GHashTable, GPtrArray */
+
+#include <xapian.h>
+
+struct _notmuch_query {
+    notmuch_database_t *notmuch;
+    const char *query_string;
+    notmuch_sort_t sort;
+};
+
+struct _notmuch_messages {
+    notmuch_database_t *notmuch;
+    Xapian::MSetIterator iterator;
+    Xapian::MSetIterator iterator_end;
+};
+
+struct _notmuch_threads {
+    notmuch_database_t *notmuch;
+    GPtrArray *threads;
+    unsigned int index;
+};
+
+notmuch_query_t *
+notmuch_query_create (notmuch_database_t *notmuch,
+                     const char *query_string)
+{
+    notmuch_query_t *query;
+
+#ifdef DEBUG_QUERY
+    fprintf (stderr, "Query string is:\n%s\n", query_string);
+#endif
+
+    query = talloc (NULL, notmuch_query_t);
+    if (unlikely (query == NULL))
+       return NULL;
+
+    query->notmuch = notmuch;
+
+    query->query_string = talloc_strdup (query, query_string);
+
+    query->sort = NOTMUCH_SORT_DATE_OLDEST_FIRST;
+
+    return query;
+}
+
+void
+notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort)
+{
+    query->sort = sort;
+}
+
+/* We end up having to call the destructors explicitly because we had
+ * to use "placement new" in order to initialize C++ objects within a
+ * block that we allocated with talloc. So C++ is making talloc
+ * slightly less simple to use, (we wouldn't need
+ * talloc_set_destructor at all otherwise).
+ */
+static int
+_notmuch_messages_destructor (notmuch_messages_t *messages)
+{
+    messages->iterator.~MSetIterator ();
+    messages->iterator_end.~MSetIterator ();
+
+    return 0;
+}
+
+notmuch_messages_t *
+notmuch_query_search_messages (notmuch_query_t *query)
+{
+    notmuch_database_t *notmuch = query->notmuch;
+    const char *query_string = query->query_string;
+    notmuch_messages_t *messages;
+
+    messages = talloc (query, notmuch_messages_t);
+    if (unlikely (messages == NULL))
+       return NULL;
+
+    try {
+       Xapian::Enquire enquire (*notmuch->xapian_db);
+       Xapian::Query mail_query (talloc_asprintf (query, "%s%s",
+                                                  _find_prefix ("type"),
+                                                  "mail"));
+       Xapian::Query string_query, final_query;
+       Xapian::MSet mset;
+       unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN |
+                             Xapian::QueryParser::FLAG_PHRASE |
+                             Xapian::QueryParser::FLAG_LOVEHATE |
+                             Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE |
+                             Xapian::QueryParser::FLAG_WILDCARD);
+
+       if (strcmp (query_string, "") == 0) {
+           final_query = mail_query;
+       } else {
+           string_query = notmuch->query_parser->
+               parse_query (query_string, flags);
+           final_query = Xapian::Query (Xapian::Query::OP_AND,
+                                        mail_query, string_query);
+       }
+
+       switch (query->sort) {
+       case NOTMUCH_SORT_DATE_OLDEST_FIRST:
+           enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, FALSE);
+           break;
+       case NOTMUCH_SORT_DATE_NEWEST_FIRST:
+           enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, TRUE);
+           break;
+       case NOTMUCH_SORT_MESSAGE_ID:
+           enquire.set_sort_by_value (NOTMUCH_VALUE_MESSAGE_ID, FALSE);
+           break;
+       }
+
+#if DEBUG_QUERY
+       fprintf (stderr, "Final query is:\n%s\n", final_query.get_description().c_str());
+#endif
+
+       enquire.set_query (final_query);
+
+       mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ());
+
+       messages->notmuch = notmuch;
+
+       new (&messages->iterator) Xapian::MSetIterator ();
+       new (&messages->iterator_end) Xapian::MSetIterator ();
+
+       talloc_set_destructor (messages, _notmuch_messages_destructor);
+
+       messages->iterator = mset.begin ();
+       messages->iterator_end = mset.end ();
+
+    } catch (const Xapian::Error &error) {
+       fprintf (stderr, "A Xapian exception occurred: %s\n",
+                error.get_msg().c_str());
+    }
+
+    return messages;
+}
+
+/* Glib objects force use to use a talloc destructor as well, (but not
+ * nearly as ugly as the for messages due to C++ objects). At
+ * this point, I'd really like to have some talloc-friendly
+ * equivalents for the few pieces of glib that I'm using. */
+static int
+_notmuch_threads_destructor (notmuch_threads_t *threads)
+{
+    g_ptr_array_free (threads->threads, TRUE);
+
+    return 0;
+}
+
+notmuch_threads_t *
+notmuch_query_search_threads (notmuch_query_t *query)
+{
+    notmuch_threads_t *threads;
+    notmuch_thread_t *thread;
+    const char *thread_id;
+    notmuch_messages_t *messages;
+    notmuch_message_t *message;
+    GHashTable *seen;
+
+    threads = talloc (query, notmuch_threads_t);
+    if (threads == NULL)
+       return NULL;
+
+    threads->notmuch = query->notmuch;
+    threads->threads = g_ptr_array_new ();
+    threads->index = 0;
+
+    talloc_set_destructor (threads, _notmuch_threads_destructor);
+
+    seen = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                 free, NULL);
+
+    for (messages = notmuch_query_search_messages (query);
+        notmuch_messages_has_more (messages);
+        notmuch_messages_advance (messages))
+    {
+       message = notmuch_messages_get (messages);
+
+       thread_id = notmuch_message_get_thread_id (message);
+
+       if (! g_hash_table_lookup_extended (seen,
+                                           thread_id, NULL,
+                                           (void **) &thread))
+       {
+           thread = _notmuch_thread_create (query, query->notmuch,
+                                            thread_id);
+
+           g_hash_table_insert (seen, xstrdup (thread_id), thread);
+
+           g_ptr_array_add (threads->threads, thread);
+       }
+
+       _notmuch_thread_add_message (thread, message);
+
+       notmuch_message_destroy (message);
+    }
+
+    g_hash_table_unref (seen);
+
+    return threads;
+}
+
+void
+notmuch_query_destroy (notmuch_query_t *query)
+{
+    talloc_free (query);
+}
+
+notmuch_bool_t
+notmuch_messages_has_more (notmuch_messages_t *messages)
+{
+    return (messages->iterator != messages->iterator_end);
+}
+
+notmuch_message_t *
+notmuch_messages_get (notmuch_messages_t *messages)
+{
+    notmuch_message_t *message;
+    Xapian::docid doc_id;
+    notmuch_private_status_t status;
+
+    if (! notmuch_messages_has_more (messages))
+       return NULL;
+
+    doc_id = *messages->iterator;
+
+    message = _notmuch_message_create (messages,
+                                      messages->notmuch, doc_id,
+                                      &status);
+
+    if (message == NULL &&
+       status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
+    {
+       INTERNAL_ERROR ("a messages iterator contains a non-existent document ID.\n");
+    }
+
+    return message;
+}
+
+void
+notmuch_messages_advance (notmuch_messages_t *messages)
+{
+    messages->iterator++;
+}
+
+void
+notmuch_messages_destroy (notmuch_messages_t *messages)
+{
+    talloc_free (messages);
+}
+
+notmuch_bool_t
+notmuch_threads_has_more (notmuch_threads_t *threads)
+{
+    return (threads->index < threads->threads->len);
+}
+
+notmuch_thread_t *
+notmuch_threads_get (notmuch_threads_t *threads)
+{
+    if (! notmuch_threads_has_more (threads))
+       return NULL;
+
+    return (notmuch_thread_t *) g_ptr_array_index (threads->threads,
+                                                  threads->index);
+}
+
+void
+notmuch_threads_advance (notmuch_threads_t *threads)
+{
+    threads->index++;
+}
+
+void
+notmuch_threads_destroy (notmuch_threads_t *threads)
+{
+    talloc_free (threads);
+}
diff --git a/lib/sha1.c b/lib/sha1.c
new file mode 100644 (file)
index 0000000..ff4dd16
--- /dev/null
@@ -0,0 +1,115 @@
+/* sha1.c - Interfaces to SHA-1 hash for the notmuch mail system
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+
+#include "libsha1.h"
+
+/* Just some simple interfaces on top of libsha1 so that we can leave
+ * libsha1 as untouched as possible. */
+
+static char *
+_hex_of_sha1_digest (const unsigned char digest[SHA1_DIGEST_SIZE])
+{
+    char *result, *r;
+    int i;
+
+    result = xcalloc (SHA1_DIGEST_SIZE * 2 + 1, 1);
+
+    for (r = result, i = 0;
+        i < SHA1_DIGEST_SIZE;
+        r += 2, i++)
+    {
+       sprintf (r, "%02x", digest[i]);
+    }
+
+    return result;
+}
+
+/* Create a hexadcimal string version of the SHA-1 digest of 'str'
+ * (including its null terminating character).
+ *
+ * This function returns a newly allocated string which the caller
+ * should free() when finished.
+ */
+char *
+notmuch_sha1_of_string (const char *str)
+{
+    sha1_ctx sha1;
+    unsigned char digest[SHA1_DIGEST_SIZE];
+
+    sha1_begin (&sha1);
+
+    sha1_hash ((unsigned char *) str, strlen (str) + 1, &sha1);
+
+    sha1_end (digest, &sha1);
+
+    return _hex_of_sha1_digest (digest);
+}
+
+/* Create a hexadecimal string version of the SHA-1 digest of the
+ * contents of the named file.
+ *
+ * This function returns a newly allocated string which the caller
+ * should free() when finished.
+ *
+ * If any error occurs while reading the file, (permission denied,
+ * file not found, etc.), this function returns NULL.
+ */
+char *
+notmuch_sha1_of_file (const char *filename)
+{
+    FILE *file;
+#define BLOCK_SIZE 4096
+    unsigned char block[BLOCK_SIZE];
+    size_t bytes_read;
+    sha1_ctx sha1;
+    unsigned char digest[SHA1_DIGEST_SIZE];
+    char *result;
+
+    file = fopen (filename, "r");
+    if (file == NULL)
+       return NULL;
+
+    sha1_begin (&sha1);
+
+    while (1) {
+       bytes_read = fread (block, 1, 4096, file);
+       if (bytes_read == 0) {
+           if (feof (file)) {
+               break;
+           } else if (ferror (file)) {
+               fclose (file);
+               return NULL;
+           }
+       } else {
+           sha1_hash (block, bytes_read, &sha1);
+       }
+    }
+
+    sha1_end (digest, &sha1);
+
+    result = _hex_of_sha1_digest (digest);
+
+    fclose (file);
+
+    return result;
+}
+
diff --git a/lib/tags.c b/lib/tags.c
new file mode 100644 (file)
index 0000000..afc132c
--- /dev/null
@@ -0,0 +1,116 @@
+/* tags.c - Iterator for tags returned from message or thread
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+
+#include <glib.h> /* GList */
+
+struct _notmuch_tags {
+    int sorted;
+    GList *tags;
+    GList *iterator;
+};
+
+/* XXX: Should write some talloc-friendly list to avoid the need for
+ * this. */
+static int
+_notmuch_tags_destructor (notmuch_tags_t *tags)
+{
+    g_list_free (tags->tags);
+
+    return 0;
+}
+
+/* Create a new notmuch_tags_t object, with 'ctx' as its talloc owner.
+ *
+ * This function can return NULL in case of out-of-memory.
+ */
+notmuch_tags_t *
+_notmuch_tags_create (void *ctx)
+{
+    notmuch_tags_t *tags;
+
+    tags = talloc (ctx, notmuch_tags_t);
+    if (unlikely (tags == NULL))
+       return NULL;
+
+    talloc_set_destructor (tags, _notmuch_tags_destructor);
+
+    tags->sorted = 1;
+    tags->tags = NULL;
+    tags->iterator = NULL;
+
+    return tags;
+}
+
+/* Add a new tag to 'tags'. The tags object will create its own copy
+ * of the string.
+ *
+ * Note: The tags object will not do anything to prevent duplicate
+ * tags being stored, so the caller really shouldn't pass
+ * duplicates. */
+void
+_notmuch_tags_add_tag (notmuch_tags_t *tags, const char *tag)
+{
+    tags->tags = g_list_prepend (tags->tags, talloc_strdup (tags, tag));
+    tags->sorted = 0;
+}
+
+/* Prepare 'tag' for iteration.
+ *
+ * The internal creator of 'tags' should call this function before
+ * returning 'tags' to the user to call the public functions such as
+ * notmuch_tags_has_more, notmuch_tags_get, and notmuch_tags_advance. */
+void
+_notmuch_tags_prepare_iterator (notmuch_tags_t *tags)
+{
+    if (! tags->sorted)
+       tags->tags = g_list_sort (tags->tags, (GCompareFunc) strcmp);
+    tags->sorted = 1;
+
+    tags->iterator = tags->tags;
+}
+
+notmuch_bool_t
+notmuch_tags_has_more (notmuch_tags_t *tags)
+{
+    return tags->iterator != NULL;
+}
+
+const char *
+notmuch_tags_get (notmuch_tags_t *tags)
+{
+    if (tags->iterator)
+       return (char *) tags->iterator->data;
+    else
+       return NULL;
+}
+
+void
+notmuch_tags_advance (notmuch_tags_t *tags)
+{
+    tags->iterator = tags->iterator->next;
+}
+
+void
+notmuch_tags_destroy (notmuch_tags_t *tags)
+{
+    talloc_free (tags);
+}
diff --git a/lib/thread.cc b/lib/thread.cc
new file mode 100644 (file)
index 0000000..b67dfad
--- /dev/null
@@ -0,0 +1,171 @@
+/* thread.cc - Results of thread-based searches from a notmuch database
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-private.h"
+#include "database-private.h"
+
+#include <xapian.h>
+
+#include <glib.h> /* GHashTable */
+
+struct _notmuch_thread {
+    notmuch_database_t *notmuch;
+    char *thread_id;
+    char *subject;
+    GHashTable *tags;
+
+    notmuch_bool_t has_message;
+    time_t oldest;
+    time_t newest;
+};
+
+static int
+_notmuch_thread_destructor (notmuch_thread_t *thread)
+{
+    g_hash_table_unref (thread->tags);
+
+    return 0;
+}
+
+/* Create a new notmuch_thread_t object for an existing document in
+ * the database.
+ *
+ * Here, 'talloc owner' is an optional talloc context to which the new
+ * thread will belong. This allows for the caller to not bother
+ * calling notmuch_thread_destroy on the thread, and know that all
+ * memory will be reclaimed with 'talloc_owner' is freed. The caller
+ * still can call notmuch_thread_destroy when finished with the
+ * thread if desired.
+ *
+ * The 'talloc_owner' argument can also be NULL, in which case the
+ * caller *is* responsible for calling notmuch_thread_destroy.
+ *
+ * This function returns NULL in the case of any error.
+ */
+notmuch_thread_t *
+_notmuch_thread_create (const void *talloc_owner,
+                       notmuch_database_t *notmuch,
+                       const char *thread_id)
+{
+    notmuch_thread_t *thread;
+
+    thread = talloc (talloc_owner, notmuch_thread_t);
+    if (unlikely (thread == NULL))
+       return NULL;
+
+    talloc_set_destructor (thread, _notmuch_thread_destructor);
+
+    thread->notmuch = notmuch;
+    thread->thread_id = talloc_strdup (thread, thread_id);
+    thread->subject = NULL;
+    thread->tags = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                         free, NULL);
+
+    thread->has_message = 0;
+    thread->oldest = 0;
+    thread->newest = 0;
+
+    return thread;
+}
+
+const char *
+notmuch_thread_get_thread_id (notmuch_thread_t *thread)
+{
+    return thread->thread_id;
+}
+
+void
+_notmuch_thread_add_message (notmuch_thread_t *thread,
+                            notmuch_message_t *message)
+{
+    notmuch_tags_t *tags;
+    const char *tag;
+    time_t date;
+
+    if (! thread->subject) {
+       const char *subject;
+       subject = notmuch_message_get_header (message, "subject");
+       thread->subject = talloc_strdup (thread, subject);
+    }
+
+    for (tags = notmuch_message_get_tags (message);
+        notmuch_tags_has_more (tags);
+        notmuch_tags_advance (tags))
+    {
+       tag = notmuch_tags_get (tags);
+       g_hash_table_insert (thread->tags, xstrdup (tag), NULL);
+    }
+
+    date = notmuch_message_get_date (message);
+
+    if (date < thread->oldest || ! thread->has_message)
+       thread->oldest = date;
+
+    if (date > thread->newest || ! thread->has_message)
+       thread->newest = date;
+
+    thread->has_message = 1;
+}
+
+const char *
+notmuch_thread_get_subject (notmuch_thread_t *thread)
+{
+    return thread->subject;
+}
+
+time_t
+notmuch_thread_get_oldest_date (notmuch_thread_t *thread)
+{
+    return thread->oldest;
+}
+
+time_t
+notmuch_thread_get_newest_date (notmuch_thread_t *thread)
+{
+    return thread->newest;
+}
+
+notmuch_tags_t *
+notmuch_thread_get_tags (notmuch_thread_t *thread)
+{
+    notmuch_tags_t *tags;
+    GList *keys, *l;
+
+    tags = _notmuch_tags_create (thread);
+    if (unlikely (tags == NULL))
+       return NULL;
+
+    keys = g_hash_table_get_keys (thread->tags);
+
+    for (l = keys; l; l = l->next)
+       _notmuch_tags_add_tag (tags, (char *) l->data);
+
+    g_list_free (keys);
+
+    _notmuch_tags_prepare_iterator (tags);
+
+    return tags;
+}
+
+void
+notmuch_thread_destroy (notmuch_thread_t *thread)
+{
+    talloc_free (thread);
+}
diff --git a/lib/xutil.c b/lib/xutil.c
new file mode 100644 (file)
index 0000000..6fa5eb0
--- /dev/null
@@ -0,0 +1,130 @@
+/* xutil.c - Various wrapper functions to abort on error.
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#define _GNU_SOURCE /* For strndup */
+#include "notmuch-private.h"
+
+#include <stdio.h>
+
+void *
+xcalloc (size_t nmemb, size_t size)
+{
+    void *ret;
+
+    ret = calloc (nmemb, size);
+    if (ret == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+
+    return ret;
+}
+
+void *
+xmalloc (size_t size)
+{
+    void *ret;
+
+    ret = malloc (size);
+    if (ret == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+
+    return ret;
+}
+
+void *
+xrealloc (void *ptr, size_t size)
+{
+    void *ret;
+
+    ret = realloc (ptr, size);
+    if (ret == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+
+    return ret;
+}
+
+char *
+xstrdup (const char *s)
+{
+    char *ret;
+
+    ret = strdup (s);
+    if (ret == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+
+    return ret;
+}
+
+char *
+xstrndup (const char *s, size_t n)
+{
+    char *ret;
+
+    ret = strndup (s, n);
+    if (ret == NULL) {
+       fprintf (stderr, "Out of memory.\n");
+       exit (1);
+    }
+
+    return ret;
+}
+
+void
+xregcomp (regex_t *preg, const char *regex, int cflags)
+{
+    int rerr;
+
+    rerr = regcomp (preg, regex, cflags);
+    if (rerr) {
+       size_t error_size = regerror (rerr, preg, NULL, 0);
+       char *error = xmalloc (error_size);
+
+       regerror (rerr, preg, error, error_size);
+       INTERNAL_ERROR ("compiling regex %s: %s\n",
+                       regex, error);
+    }
+}
+
+int
+xregexec (const regex_t *preg, const char *string,
+         size_t nmatch, regmatch_t pmatch[], int eflags)
+{
+    unsigned int i;
+    int rerr;
+
+    rerr = regexec (preg, string, nmatch, pmatch, eflags);
+    if (rerr)
+       return rerr;
+
+    for (i = 0; i < nmatch; i++) {
+       if (pmatch[i].rm_so == -1)
+           INTERNAL_ERROR ("matching regex against %s: Sub-match %d not found\n",
+                           string, i);
+    }
+
+    return 0;
+}
diff --git a/lib/xutil.h b/lib/xutil.h
new file mode 100644 (file)
index 0000000..b973f7d
--- /dev/null
@@ -0,0 +1,51 @@
+/* xutil.h - Various wrapper functions to abort on error.
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#ifndef NOTMUCH_XUTIL_H
+#define NOTMUCH_XUTIL_H
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <regex.h>
+
+/* xutil.c */
+void *
+xcalloc (size_t nmemb, size_t size);
+
+void *
+xmalloc (size_t size);
+
+void *
+xrealloc (void *ptrr, size_t size);
+
+char *
+xstrdup (const char *s);
+
+char *
+xstrndup (const char *s, size_t n);
+
+void
+xregcomp (regex_t *preg, const char *regex, int cflags);
+
+int
+xregexec (const regex_t *preg, const char *string,
+         size_t nmatch, regmatch_t pmatch[], int eflags);
+
+#endif
diff --git a/libsha1.c b/libsha1.c
deleted file mode 100644 (file)
index c39a5a1..0000000
--- a/libsha1.c
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- ---------------------------------------------------------------------------
- Copyright (c) 2002, Dr Brian Gladman, Worcester, UK.   All rights reserved.
-
- LICENSE TERMS
-
- The free distribution and use of this software in both source and binary
- form is allowed (with or without changes) provided that:
-
-   1. distributions of this source code include the above copyright
-      notice, this list of conditions and the following disclaimer;
-
-   2. distributions in binary form include the above copyright
-      notice, this list of conditions and the following disclaimer
-      in the documentation and/or other associated materials;
-
-   3. the copyright holder's name is not used to endorse products
-      built using this software without specific written permission.
-
- ALTERNATIVELY, provided that this notice is retained in full, this product
- may be distributed under the terms of the GNU General Public License (GPL),
- in which case the provisions of the GPL apply INSTEAD OF those given above.
-
- DISCLAIMER
-
- This software is provided 'as is' with no explicit or implied warranties
- in respect of its properties, including, but not limited to, correctness
- and/or fitness for purpose.
- ---------------------------------------------------------------------------
- Issue Date: 01/08/2005
-
- This is a byte oriented version of SHA1 that operates on arrays of bytes
- stored in memory.
-*/
-
-#include <string.h>     /* for memcpy() etc.        */
-
-#include "libsha1.h"
-
-#if defined(__cplusplus)
-extern "C"
-{
-#endif
-
-#define SHA1_BLOCK_SIZE  64
-
-#define rotl32(x,n)   (((x) << n) | ((x) >> (32 - n)))
-#define rotr32(x,n)   (((x) >> n) | ((x) << (32 - n)))
-
-#define bswap_32(x) ((rotr32((x), 24) & 0x00ff00ff) | (rotr32((x), 8) & 0xff00ff00))
-
-#if (PLATFORM_BYTE_ORDER == IS_LITTLE_ENDIAN)
-#define bsw_32(p,n) \
-    { int _i = (n); while(_i--) ((uint32_t*)p)[_i] = bswap_32(((uint32_t*)p)[_i]); }
-#else
-#define bsw_32(p,n)
-#endif
-
-#define SHA1_MASK   (SHA1_BLOCK_SIZE - 1)
-
-#if 0
-
-#define ch(x,y,z)       (((x) & (y)) ^ (~(x) & (z)))
-#define parity(x,y,z)   ((x) ^ (y) ^ (z))
-#define maj(x,y,z)      (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
-
-#else   /* Discovered by Rich Schroeppel and Colin Plumb   */
-
-#define ch(x,y,z)       ((z) ^ ((x) & ((y) ^ (z))))
-#define parity(x,y,z)   ((x) ^ (y) ^ (z))
-#define maj(x,y,z)      (((x) & (y)) | ((z) & ((x) ^ (y))))
-
-#endif
-
-/* Compile 64 bytes of hash data into SHA1 context. Note    */
-/* that this routine assumes that the byte order in the     */
-/* ctx->wbuf[] at this point is in such an order that low   */
-/* address bytes in the ORIGINAL byte stream will go in     */
-/* this buffer to the high end of 32-bit words on BOTH big  */
-/* and little endian systems                                */
-
-#ifdef ARRAY
-#define q(v,n)  v[n]
-#else
-#define q(v,n)  v##n
-#endif
-
-#define one_cycle(v,a,b,c,d,e,f,k,h)            \
-    q(v,e) += rotr32(q(v,a),27) +               \
-              f(q(v,b),q(v,c),q(v,d)) + k + h;  \
-    q(v,b)  = rotr32(q(v,b), 2)
-
-#define five_cycle(v,f,k,i)                 \
-    one_cycle(v, 0,1,2,3,4, f,k,hf(i  ));   \
-    one_cycle(v, 4,0,1,2,3, f,k,hf(i+1));   \
-    one_cycle(v, 3,4,0,1,2, f,k,hf(i+2));   \
-    one_cycle(v, 2,3,4,0,1, f,k,hf(i+3));   \
-    one_cycle(v, 1,2,3,4,0, f,k,hf(i+4))
-
-static void sha1_compile(sha1_ctx ctx[1])
-{   uint32_t    *w = ctx->wbuf;
-
-#ifdef ARRAY
-    uint32_t    v[5];
-    memcpy(v, ctx->hash, 5 * sizeof(uint32_t));
-#else
-    uint32_t    v0, v1, v2, v3, v4;
-    v0 = ctx->hash[0]; v1 = ctx->hash[1];
-    v2 = ctx->hash[2]; v3 = ctx->hash[3];
-    v4 = ctx->hash[4];
-#endif
-
-#define hf(i)   w[i]
-
-    five_cycle(v, ch, 0x5a827999,  0);
-    five_cycle(v, ch, 0x5a827999,  5);
-    five_cycle(v, ch, 0x5a827999, 10);
-    one_cycle(v,0,1,2,3,4, ch, 0x5a827999, hf(15)); \
-
-#undef  hf
-#define hf(i) (w[(i) & 15] = rotl32(                    \
-                 w[((i) + 13) & 15] ^ w[((i) + 8) & 15] \
-               ^ w[((i) +  2) & 15] ^ w[(i) & 15], 1))
-
-    one_cycle(v,4,0,1,2,3, ch, 0x5a827999, hf(16));
-    one_cycle(v,3,4,0,1,2, ch, 0x5a827999, hf(17));
-    one_cycle(v,2,3,4,0,1, ch, 0x5a827999, hf(18));
-    one_cycle(v,1,2,3,4,0, ch, 0x5a827999, hf(19));
-
-    five_cycle(v, parity, 0x6ed9eba1,  20);
-    five_cycle(v, parity, 0x6ed9eba1,  25);
-    five_cycle(v, parity, 0x6ed9eba1,  30);
-    five_cycle(v, parity, 0x6ed9eba1,  35);
-
-    five_cycle(v, maj, 0x8f1bbcdc,  40);
-    five_cycle(v, maj, 0x8f1bbcdc,  45);
-    five_cycle(v, maj, 0x8f1bbcdc,  50);
-    five_cycle(v, maj, 0x8f1bbcdc,  55);
-
-    five_cycle(v, parity, 0xca62c1d6,  60);
-    five_cycle(v, parity, 0xca62c1d6,  65);
-    five_cycle(v, parity, 0xca62c1d6,  70);
-    five_cycle(v, parity, 0xca62c1d6,  75);
-
-#ifdef ARRAY
-    ctx->hash[0] += v[0]; ctx->hash[1] += v[1];
-    ctx->hash[2] += v[2]; ctx->hash[3] += v[3];
-    ctx->hash[4] += v[4];
-#else
-    ctx->hash[0] += v0; ctx->hash[1] += v1;
-    ctx->hash[2] += v2; ctx->hash[3] += v3;
-    ctx->hash[4] += v4;
-#endif
-}
-
-void sha1_begin(sha1_ctx ctx[1])
-{
-    ctx->count[0] = ctx->count[1] = 0;
-    ctx->hash[0] = 0x67452301;
-    ctx->hash[1] = 0xefcdab89;
-    ctx->hash[2] = 0x98badcfe;
-    ctx->hash[3] = 0x10325476;
-    ctx->hash[4] = 0xc3d2e1f0;
-}
-
-/* SHA1 hash data in an array of bytes into hash buffer and */
-/* call the hash_compile function as required.              */
-
-void sha1_hash(const unsigned char data[], unsigned long len, sha1_ctx ctx[1])
-{   uint32_t pos = (uint32_t)(ctx->count[0] & SHA1_MASK),
-            space = SHA1_BLOCK_SIZE - pos;
-    const unsigned char *sp = data;
-
-    if((ctx->count[0] += len) < len)
-        ++(ctx->count[1]);
-
-    while(len >= space)     /* tranfer whole blocks if possible  */
-    {
-        memcpy(((unsigned char*)ctx->wbuf) + pos, sp, space);
-        sp += space; len -= space; space = SHA1_BLOCK_SIZE; pos = 0;
-        bsw_32(ctx->wbuf, SHA1_BLOCK_SIZE >> 2);
-        sha1_compile(ctx);
-    }
-
-    memcpy(((unsigned char*)ctx->wbuf) + pos, sp, len);
-}
-
-/* SHA1 final padding and digest calculation  */
-
-void sha1_end(unsigned char hval[], sha1_ctx ctx[1])
-{   uint32_t    i = (uint32_t)(ctx->count[0] & SHA1_MASK);
-
-    /* put bytes in the buffer in an order in which references to   */
-    /* 32-bit words will put bytes with lower addresses into the    */
-    /* top of 32 bit words on BOTH big and little endian machines   */
-    bsw_32(ctx->wbuf, (i + 3) >> 2);
-
-    /* we now need to mask valid bytes and add the padding which is */
-    /* a single 1 bit and as many zero bits as necessary. Note that */
-    /* we can always add the first padding byte here because the    */
-    /* buffer always has at least one empty slot                    */
-    ctx->wbuf[i >> 2] &= 0xffffff80 << 8 * (~i & 3);
-    ctx->wbuf[i >> 2] |= 0x00000080 << 8 * (~i & 3);
-
-    /* we need 9 or more empty positions, one for the padding byte  */
-    /* (above) and eight for the length count. If there is not      */
-    /* enough space, pad and empty the buffer                       */
-    if(i > SHA1_BLOCK_SIZE - 9)
-    {
-        if(i < 60) ctx->wbuf[15] = 0;
-        sha1_compile(ctx);
-        i = 0;
-    }
-    else    /* compute a word index for the empty buffer positions  */
-        i = (i >> 2) + 1;
-
-    while(i < 14) /* and zero pad all but last two positions        */
-        ctx->wbuf[i++] = 0;
-
-    /* the following 32-bit length fields are assembled in the      */
-    /* wrong byte order on little endian machines but this is       */
-    /* corrected later since they are only ever used as 32-bit      */
-    /* word values.                                                 */
-    ctx->wbuf[14] = (ctx->count[1] << 3) | (ctx->count[0] >> 29);
-    ctx->wbuf[15] = ctx->count[0] << 3;
-    sha1_compile(ctx);
-
-    /* extract the hash value as bytes in case the hash buffer is   */
-    /* misaligned for 32-bit words                                  */
-    for(i = 0; i < SHA1_DIGEST_SIZE; ++i)
-        hval[i] = (unsigned char)(ctx->hash[i >> 2] >> (8 * (~i & 3)));
-}
-
-void sha1(unsigned char hval[], const unsigned char data[], unsigned long len)
-{   sha1_ctx    cx[1];
-
-    sha1_begin(cx); sha1_hash(data, len, cx); sha1_end(hval, cx);
-}
-
-#if defined(__cplusplus)
-}
-#endif
diff --git a/libsha1.h b/libsha1.h
deleted file mode 100644 (file)
index b4dca93..0000000
--- a/libsha1.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- ---------------------------------------------------------------------------
- Copyright (c) 2002, Dr Brian Gladman, Worcester, UK.   All rights reserved.
-
- LICENSE TERMS
-
- The free distribution and use of this software in both source and binary
- form is allowed (with or without changes) provided that:
-
-   1. distributions of this source code include the above copyright
-      notice, this list of conditions and the following disclaimer;
-
-   2. distributions in binary form include the above copyright
-      notice, this list of conditions and the following disclaimer
-      in the documentation and/or other associated materials;
-
-   3. the copyright holder's name is not used to endorse products
-      built using this software without specific written permission.
-
- ALTERNATIVELY, provided that this notice is retained in full, this product
- may be distributed under the terms of the GNU General Public License (GPL),
- in which case the provisions of the GPL apply INSTEAD OF those given above.
-
- DISCLAIMER
-
- This software is provided 'as is' with no explicit or implied warranties
- in respect of its properties, including, but not limited to, correctness
- and/or fitness for purpose.
- ---------------------------------------------------------------------------
- Issue Date: 01/08/2005
-*/
-
-#ifndef _SHA1_H
-#define _SHA1_H
-
-#if defined(__cplusplus)
-extern "C"
-{
-#endif
-#if 0
-} /* Appleasing Emacs */
-#endif
-
-#include <stdint.h>
-
-/* Size of SHA1 digest */
-
-#define SHA1_DIGEST_SIZE 20
-
-/* type to hold the SHA1 context  */
-
-typedef struct
-{   uint32_t count[2];
-    uint32_t hash[5];
-    uint32_t wbuf[16];
-} sha1_ctx;
-
-void sha1_begin(sha1_ctx ctx[1]);
-void sha1_hash(const unsigned char data[], unsigned long len, sha1_ctx ctx[1]);
-void sha1_end(unsigned char hval[], sha1_ctx ctx[1]);
-void sha1(unsigned char hval[], const unsigned char data[], unsigned long len);
-
-#if defined(__cplusplus)
-}
-#endif
-
-#endif
diff --git a/message-file.c b/message-file.c
deleted file mode 100644 (file)
index 75caba6..0000000
+++ /dev/null
@@ -1,352 +0,0 @@
-/* message.c - Utility functions for parsing an email message for notmuch.
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#include <stdarg.h>
-
-#include "notmuch-private.h"
-
-#include <gmime/gmime.h>
-
-#include <glib.h> /* GHashTable */
-
-typedef struct {
-    char *str;
-    size_t size;
-    size_t len;
-} header_value_closure_t;
-
-struct _notmuch_message_file {
-    /* File object */
-    FILE *file;
-
-    /* Header storage */
-    int restrict_headers;
-    GHashTable *headers;
-    int broken_headers;
-    int good_headers;
-    size_t header_size; /* Length of full message header in bytes. */
-
-    /* Parsing state */
-    char *line;
-    size_t line_size;
-    header_value_closure_t value;
-
-    int parsing_started;
-    int parsing_finished;
-};
-
-static int
-strcase_equal (const void *a, const void *b)
-{
-    return strcasecmp (a, b) == 0;
-}
-
-static unsigned int
-strcase_hash (const void *ptr)
-{
-    const char *s = ptr;
-
-    /* This is the djb2 hash. */
-    unsigned int hash = 5381;
-    while (s && *s) {
-       hash = ((hash << 5) + hash) + tolower (*s);
-       s++;
-    }
-
-    return hash;
-}
-
-static int
-_notmuch_message_file_destructor (notmuch_message_file_t *message)
-{
-    if (message->line)
-       free (message->line);
-
-    if (message->value.size)
-       free (message->value.str);
-
-    if (message->headers)
-       g_hash_table_destroy (message->headers);
-
-    if (message->file)
-       fclose (message->file);
-
-    return 0;
-}
-
-/* Create a new notmuch_message_file_t for 'filename' with 'ctx' as
- * the talloc owner. */
-notmuch_message_file_t *
-_notmuch_message_file_open_ctx (void *ctx, const char *filename)
-{
-    notmuch_message_file_t *message;
-
-    message = talloc_zero (ctx, notmuch_message_file_t);
-    if (unlikely (message == NULL))
-       return NULL;
-
-    talloc_set_destructor (message, _notmuch_message_file_destructor);
-
-    message->file = fopen (filename, "r");
-    if (message->file == NULL)
-       goto FAIL;
-
-    message->headers = g_hash_table_new_full (strcase_hash,
-                                             strcase_equal,
-                                             free,
-                                             free);
-
-    message->parsing_started = 0;
-    message->parsing_finished = 0;
-
-    return message;
-
-  FAIL:
-    fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
-    notmuch_message_file_close (message);
-
-    return NULL;
-}
-
-notmuch_message_file_t *
-notmuch_message_file_open (const char *filename)
-{
-    return _notmuch_message_file_open_ctx (NULL, filename);
-}
-
-void
-notmuch_message_file_close (notmuch_message_file_t *message)
-{
-    talloc_free (message);
-}
-
-void
-notmuch_message_file_restrict_headersv (notmuch_message_file_t *message,
-                                       va_list va_headers)
-{
-    char *header;
-
-    if (message->parsing_started)
-       INTERNAL_ERROR ("notmuch_message_file_restrict_headers called after parsing has started");
-
-    while (1) {
-       header = va_arg (va_headers, char*);
-       if (header == NULL)
-           break;
-       g_hash_table_insert (message->headers,
-                            xstrdup (header), NULL);
-    }
-
-    message->restrict_headers = 1;
-}
-
-void
-notmuch_message_file_restrict_headers (notmuch_message_file_t *message, ...)
-{
-    va_list va_headers;
-
-    va_start (va_headers, message);
-
-    notmuch_message_file_restrict_headersv (message, va_headers);
-}
-
-static void
-copy_header_unfolding (header_value_closure_t *value,
-                      const char *chunk)
-{
-    char *last;
-
-    if (chunk == NULL)
-       return;
-
-    while (*chunk == ' ' || *chunk == '\t')
-       chunk++;
-
-    if (value->len + 1 + strlen (chunk) + 1 > value->size) {
-       unsigned int new_size = value->size;
-       if (value->size == 0)
-           new_size = strlen (chunk) + 1;
-       else
-           while (value->len + 1 + strlen (chunk) + 1 > new_size)
-               new_size *= 2;
-       value->str = xrealloc (value->str, new_size);
-       value->size = new_size;
-    }
-
-    last = value->str + value->len;
-    if (value->len) {
-       *last = ' ';
-       last++;
-       value->len++;
-    }
-
-    strcpy (last, chunk);
-    value->len += strlen (chunk);
-
-    last = value->str + value->len - 1;
-    if (*last == '\n') {
-       *last = '\0';
-       value->len--;
-    }
-}
-
-/* As a special-case, a value of NULL for header_desired will force
- * the entire header to be parsed if it is not parsed already. This is
- * used by the _notmuch_message_file_get_headers_end function. */
-const char *
-notmuch_message_file_get_header (notmuch_message_file_t *message,
-                                const char *header_desired)
-{
-    int contains;
-    char *header, *decoded_value;
-    const char *s, *colon;
-    int match;
-    static int initialized = 0;
-
-    if (! initialized) {
-       g_mime_init (0);
-       initialized = 1;
-    }
-
-    message->parsing_started = 1;
-
-    if (header_desired == NULL)
-       contains = 0;
-    else
-       contains = g_hash_table_lookup_extended (message->headers,
-                                                header_desired, NULL,
-                                                (gpointer *) &decoded_value);
-
-    if (contains && decoded_value)
-       return decoded_value;
-
-    if (message->parsing_finished)
-       return NULL;
-
-#define NEXT_HEADER_LINE(closure)                              \
-    while (1) {                                                        \
-       ssize_t bytes_read = getline (&message->line,           \
-                                     &message->line_size,      \
-                                     message->file);           \
-       if (bytes_read == -1) {                                 \
-           message->parsing_finished = 1;                      \
-           break;                                              \
-       }                                                       \
-       if (*message->line == '\n') {                           \
-           message->parsing_finished = 1;                      \
-           break;                                              \
-       }                                                       \
-       if (closure &&                                          \
-           (*message->line == ' ' || *message->line == '\t'))  \
-       {                                                       \
-           copy_header_unfolding ((closure), message->line);   \
-       }                                                       \
-       if (*message->line == ' ' || *message->line == '\t')    \
-           message->header_size += strlen (message->line);     \
-       else                                                    \
-           break;                                              \
-    }
-
-    if (message->line == NULL)
-       NEXT_HEADER_LINE (NULL);
-
-    while (1) {
-
-       if (message->parsing_finished)
-           break;
-
-       colon = strchr (message->line, ':');
-
-       if (colon == NULL) {
-           message->broken_headers++;
-           /* A simple heuristic for giving up on things that just
-            * don't look like mail messages. */
-           if (message->broken_headers >= 10 &&
-               message->good_headers < 5)
-           {
-               message->parsing_finished = 1;
-               continue;
-           }
-           NEXT_HEADER_LINE (NULL);
-           continue;
-       }
-
-       message->header_size += strlen (message->line);
-
-       message->good_headers++;
-
-       header = xstrndup (message->line, colon - message->line);
-
-       if (message->restrict_headers &&
-           ! g_hash_table_lookup_extended (message->headers,
-                                           header, NULL, NULL))
-       {
-           free (header);
-           NEXT_HEADER_LINE (NULL);
-           continue;
-       }
-
-       s = colon + 1;
-       while (*s == ' ' || *s == '\t')
-           s++;
-
-       message->value.len = 0;
-       copy_header_unfolding (&message->value, s);
-
-       NEXT_HEADER_LINE (&message->value);
-
-       if (header_desired == 0)
-           match = 0;
-       else
-           match = (strcasecmp (header, header_desired) == 0);
-
-       decoded_value = g_mime_utils_header_decode_text (message->value.str);
-
-       g_hash_table_insert (message->headers, header, decoded_value);
-
-       if (match)
-           return decoded_value;
-    }
-
-    if (message->line)
-       free (message->line);
-    message->line = NULL;
-
-    if (message->value.size) {
-       free (message->value.str);
-       message->value.str = NULL;
-       message->value.size = 0;
-       message->value.len = 0;
-    }
-
-    /* We've parsed all headers and never found the one we're looking
-     * for. It's probably just not there, but let's check that we
-     * didn't make a mistake preventing us from seeing it. */
-    if (message->restrict_headers && header_desired &&
-       ! g_hash_table_lookup_extended (message->headers,
-                                       header_desired, NULL, NULL))
-    {
-       INTERNAL_ERROR ("Attempt to get header \"%s\" which was not\n"
-                       "included in call to notmuch_message_file_restrict_headers\n",
-                       header_desired);
-    }
-
-    return NULL;
-}
diff --git a/message.cc b/message.cc
deleted file mode 100644 (file)
index 28f19a8..0000000
+++ /dev/null
@@ -1,658 +0,0 @@
-/* message.cc - Results of message-based searches from a notmuch database
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#include "notmuch-private.h"
-#include "database-private.h"
-
-#include <gmime/gmime.h>
-
-#include <xapian.h>
-
-struct _notmuch_message {
-    notmuch_database_t *notmuch;
-    Xapian::docid doc_id;
-    int frozen;
-    char *message_id;
-    char *thread_id;
-    char *filename;
-    notmuch_message_file_t *message_file;
-    Xapian::Document doc;
-};
-
-/* "128 bits of thread-id ought to be enough for anybody" */
-#define NOTMUCH_THREAD_ID_BITS  128
-#define NOTMUCH_THREAD_ID_DIGITS (NOTMUCH_THREAD_ID_BITS / 4)
-typedef struct _thread_id {
-    char str[NOTMUCH_THREAD_ID_DIGITS + 1];
-} thread_id_t;
-
-/* We end up having to call the destructor explicitly because we had
- * to use "placement new" in order to initialize C++ objects within a
- * block that we allocated with talloc. So C++ is making talloc
- * slightly less simple to use, (we wouldn't need
- * talloc_set_destructor at all otherwise).
- */
-static int
-_notmuch_message_destructor (notmuch_message_t *message)
-{
-    message->doc.~Document ();
-
-    return 0;
-}
-
-/* Create a new notmuch_message_t object for an existing document in
- * the database.
- *
- * Here, 'talloc owner' is an optional talloc context to which the new
- * message will belong. This allows for the caller to not bother
- * calling notmuch_message_destroy on the message, and no that all
- * memory will be reclaimed with 'talloc_owner' is free. The caller
- * still can call notmuch_message_destroy when finished with the
- * message if desired.
- *
- * The 'talloc_owner' argument can also be NULL, in which case the
- * caller *is* responsible for calling notmuch_message_destroy.
- *
- * If no document exists in the database with document ID of 'doc_id'
- * then this function returns NULL and optionally sets *status to
- * NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND.
- *
- * This function can also fail to due lack of available memory,
- * returning NULL and optionally setting *status to
- * NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY.
- *
- * The caller can pass NULL for status if uninterested in
- * distinguishing these two cases.
- */
-notmuch_message_t *
-_notmuch_message_create (const void *talloc_owner,
-                        notmuch_database_t *notmuch,
-                        unsigned int doc_id,
-                        notmuch_private_status_t *status)
-{
-    notmuch_message_t *message;
-
-    if (status)
-       *status = NOTMUCH_PRIVATE_STATUS_SUCCESS;
-
-    message = talloc (talloc_owner, notmuch_message_t);
-    if (unlikely (message == NULL)) {
-       if (status)
-           *status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
-       return NULL;
-    }
-
-    message->notmuch = notmuch;
-    message->doc_id = doc_id;
-
-    message->frozen = 0;
-
-    /* Each of these will be lazily created as needed. */
-    message->message_id = NULL;
-    message->thread_id = NULL;
-    message->filename = NULL;
-    message->message_file = NULL;
-
-    /* This is C++'s creepy "placement new", which is really just an
-     * ugly way to call a constructor for a pre-allocated object. So
-     * it's really not an error to not be checking for OUT_OF_MEMORY
-     * here, since this "new" isn't actually allocating memory. This
-     * is language-design comedy of the wrong kind. */
-
-    new (&message->doc) Xapian::Document;
-
-    talloc_set_destructor (message, _notmuch_message_destructor);
-
-    try {
-       message->doc = notmuch->xapian_db->get_document (doc_id);
-    } catch (const Xapian::DocNotFoundError &error) {
-       talloc_free (message);
-       if (status)
-           *status = NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
-       return NULL;
-    }
-
-    return message;
-}
-
-/* Create a new notmuch_message_t object for a specific message ID,
- * (which may or may not already exist in the databas).
- *
- * Here, 'talloc owner' is an optional talloc context to which the new
- * message will belong. This allows for the caller to not bother
- * calling notmuch_message_destroy on the message, and no that all
- * memory will be reclaimed with 'talloc_owner' is free. The caller
- * still can call notmuch_message_destroy when finished with the
- * message if desired.
- *
- * The 'talloc_owner' argument can also be NULL, in which case the
- * caller *is* responsible for calling notmuch_message_destroy.
- *
- * If there is already a document with message ID 'message_id' in the
- * database, then the returned message can be used to query/modify the
- * document. Otherwise, a new document will be inserted into the
- * database before this function returns, (and *status will be set
- * to NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND).
- *
- * If an error occurs, this function will return NULL and *status
- * will be set as appropriate. (The status pointer argument must
- * not be NULL.)
- */
-notmuch_message_t *
-_notmuch_message_create_for_message_id (const void *talloc_owner,
-                                       notmuch_database_t *notmuch,
-                                       const char *message_id,
-                                       notmuch_private_status_t *status_ret)
-{
-    notmuch_message_t *message;
-    Xapian::Document doc;
-    unsigned int doc_id;
-    char *term;
-
-    *status_ret = NOTMUCH_PRIVATE_STATUS_SUCCESS;
-
-    message = notmuch_database_find_message (notmuch, message_id);
-    if (message)
-       return talloc_steal (talloc_owner, message);
-
-    term = talloc_asprintf (NULL, "%s%s",
-                           _find_prefix ("id"), message_id);
-    if (term == NULL) {
-       *status_ret = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
-       return NULL;
-    }
-
-    try {
-       doc.add_term (term);
-       talloc_free (term);
-
-       doc.add_value (NOTMUCH_VALUE_MESSAGE_ID, message_id);
-
-       doc_id = notmuch->xapian_db->add_document (doc);
-    } catch (const Xapian::Error &error) {
-       *status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
-       return NULL;
-    }
-
-    message = _notmuch_message_create (talloc_owner, notmuch,
-                                      doc_id, status_ret);
-
-    /* We want to inform the caller that we had to create a new
-     * document. */
-    if (*status_ret == NOTMUCH_PRIVATE_STATUS_SUCCESS)
-       *status_ret = NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
-
-    return message;
-}
-
-const char *
-notmuch_message_get_message_id (notmuch_message_t *message)
-{
-    Xapian::TermIterator i;
-
-    if (message->message_id)
-       return message->message_id;
-
-    i = message->doc.termlist_begin ();
-    i.skip_to (_find_prefix ("id"));
-
-    if (i == message->doc.termlist_end ())
-       INTERNAL_ERROR ("Message with document ID of %d has no message ID.\n",
-                       message->doc_id);
-
-    message->message_id = talloc_strdup (message, (*i).c_str () + 1);
-
-#if DEBUG_DATABASE_SANITY
-    i++;
-
-    if (i != message->doc.termlist_end () &&
-       strncmp ((*i).c_str (), _find_prefix ("id"),
-                strlen (_find_prefix ("id"))) == 0)
-    {
-       INTERNAL_ERROR ("Mail (doc_id: %d) has duplicate message IDs",
-                       message->doc_id);
-    }
-#endif
-
-    return message->message_id;
-}
-
-static void
-_notmuch_message_ensure_message_file (notmuch_message_t *message)
-{
-    const char *filename;
-
-    if (message->message_file)
-       return;
-
-    filename = notmuch_message_get_filename (message);
-    if (unlikely (filename == NULL))
-       return;
-
-    message->message_file = _notmuch_message_file_open_ctx (message, filename);
-}
-
-const char *
-notmuch_message_get_header (notmuch_message_t *message, const char *header)
-{
-    _notmuch_message_ensure_message_file (message);
-    if (message->message_file == NULL)
-       return NULL;
-
-    return notmuch_message_file_get_header (message->message_file, header);
-}
-
-const char *
-notmuch_message_get_thread_id (notmuch_message_t *message)
-{
-    Xapian::TermIterator i;
-
-    if (message->thread_id)
-       return message->thread_id;
-
-    i = message->doc.termlist_begin ();
-    i.skip_to (_find_prefix ("thread"));
-
-    if (i == message->doc.termlist_end ())
-       INTERNAL_ERROR ("Message with document ID of %d has no thread ID.\n",
-                       message->doc_id);
-
-    message->thread_id = talloc_strdup (message, (*i).c_str () + 1);
-
-#if DEBUG_DATABASE_SANITY
-    i++;
-
-    if (i != message->doc.termlist_end () &&
-       strncmp ((*i).c_str (), _find_prefix ("thread"),
-                strlen (_find_prefix ("thread"))) == 0)
-    {
-       INTERNAL_ERROR ("Message %s has duplicate thread IDs: %s and %s\n",
-                       notmuch_message_get_message_id (message),
-                       message->thread_id,
-                       (*i).c_str () + 1);
-    }
-#endif
-
-    return message->thread_id;
-}
-
-/* Set the filename for 'message' to 'filename'.
- *
- * XXX: We should still figure out if we think it's important to store
- * multiple filenames for email messages with identical message IDs.
- *
- * This change will not be reflected in the database until the next
- * call to _notmuch_message_set_sync. */
-void
-_notmuch_message_set_filename (notmuch_message_t *message,
-                              const char *filename)
-{
-    const char *s;
-    const char *db_path;
-    unsigned int db_path_len;
-
-    if (message->filename) {
-       talloc_free (message->filename);
-       message->filename = NULL;
-    }
-
-    if (filename == NULL)
-       INTERNAL_ERROR ("Message filename cannot be NULL.");
-
-    s = filename;
-
-    db_path = notmuch_database_get_path (message->notmuch);
-    db_path_len = strlen (db_path);
-
-    if (*s == '/' && strncmp (s, db_path, db_path_len) == 0
-       && strlen (s) > db_path_len)
-    {
-       s += db_path_len + 1;
-    }
-
-    message->doc.set_data (s);
-}
-
-const char *
-notmuch_message_get_filename (notmuch_message_t *message)
-{
-    std::string filename_str;
-    const char *db_path;
-
-    if (message->filename)
-       return message->filename;
-
-    filename_str = message->doc.get_data ();
-    db_path = notmuch_database_get_path (message->notmuch);
-
-    if (filename_str[0] != '/')
-       message->filename = talloc_asprintf (message, "%s/%s", db_path,
-                                            filename_str.c_str ());
-    else
-       message->filename = talloc_strdup (message, filename_str.c_str ());
-
-    return message->filename;
-}
-
-time_t
-notmuch_message_get_date (notmuch_message_t *message)
-{
-    std::string value;
-
-    try {
-       value = message->doc.get_value (NOTMUCH_VALUE_TIMESTAMP);
-    } catch (Xapian::Error &error) {
-       INTERNAL_ERROR ("Failed to read timestamp value from document.");
-       return 0;
-    }
-
-    return Xapian::sortable_unserialise (value);
-}
-
-notmuch_tags_t *
-notmuch_message_get_tags (notmuch_message_t *message)
-{
-    const char *prefix = _find_prefix ("tag");
-    Xapian::TermIterator i, end;
-    notmuch_tags_t *tags;
-    std::string tag;
-
-    /* Currently this iteration is written with the assumption that
-     * "tag" has a single-character prefix. */
-    assert (strlen (prefix) == 1);
-
-    tags = _notmuch_tags_create (message);
-    if (unlikely (tags == NULL))
-       return NULL;
-
-    i = message->doc.termlist_begin ();
-    end = message->doc.termlist_end ();
-
-    i.skip_to (prefix);
-
-    while (1) {
-       tag = *i;
-
-       if (tag.empty () || tag[0] != *prefix)
-           break;
-
-       _notmuch_tags_add_tag (tags, tag.c_str () + 1);
-
-       i++;
-    }
-
-    _notmuch_tags_prepare_iterator (tags);
-
-    return tags;
-}
-
-void
-_notmuch_message_set_date (notmuch_message_t *message,
-                          const char *date)
-{
-    time_t time_value;
-
-    /* GMime really doesn't want to see a NULL date, so protect its
-     * sensibilities. */
-    if (date == NULL)
-       time_value = 0;
-    else
-       time_value = g_mime_utils_header_decode_date (date, NULL);
-
-    message->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
-                           Xapian::sortable_serialise (time_value));
-}
-
-static void
-thread_id_generate (thread_id_t *thread_id)
-{
-    static int seeded = 0;
-    FILE *dev_random;
-    uint32_t value;
-    char *s;
-    int i;
-
-    if (! seeded) {
-       dev_random = fopen ("/dev/random", "r");
-       if (dev_random == NULL) {
-           srand (time (NULL));
-       } else {
-           fread ((void *) &value, sizeof (value), 1, dev_random);
-           srand (value);
-           fclose (dev_random);
-       }
-       seeded = 1;
-    }
-
-    s = thread_id->str;
-    for (i = 0; i < NOTMUCH_THREAD_ID_DIGITS; i += 8) {
-       value = rand ();
-       sprintf (s, "%08x", value);
-       s += 8;
-    }
-}
-
-void
-_notmuch_message_ensure_thread_id (notmuch_message_t *message)
-{
-    /* If not part of any existing thread, generate a new thread_id. */
-    thread_id_t thread_id;
-
-    thread_id_generate (&thread_id);
-    _notmuch_message_add_term (message, "thread", thread_id.str);
-}
-
-/* Synchronize changes made to message->doc out into the database. */
-void
-_notmuch_message_sync (notmuch_message_t *message)
-{
-    Xapian::WritableDatabase *db = message->notmuch->xapian_db;
-
-    db->replace_document (message->doc_id, message->doc);
-}
-
-/* Add a name:value term to 'message', (the actual term will be
- * encoded by prefixing the value with a short prefix). See
- * NORMAL_PREFIX and BOOLEAN_PREFIX arrays for the mapping of term
- * names to prefix values.
- *
- * This change will not be reflected in the database until the next
- * call to _notmuch_message_set_sync. */
-notmuch_private_status_t
-_notmuch_message_add_term (notmuch_message_t *message,
-                          const char *prefix_name,
-                          const char *value)
-{
-
-    char *term;
-
-    if (value == NULL)
-       return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
-
-    term = talloc_asprintf (message, "%s%s",
-                           _find_prefix (prefix_name), value);
-
-    if (strlen (term) > NOTMUCH_TERM_MAX)
-       return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
-
-    message->doc.add_term (term);
-
-    talloc_free (term);
-
-    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
-}
-
-/* Parse 'text' and add a term to 'message' for each parsed word. Each
- * term will be added both prefixed (if prefix_name is not NULL) and
- * also unprefixed). */
-notmuch_private_status_t
-_notmuch_message_gen_terms (notmuch_message_t *message,
-                           const char *prefix_name,
-                           const char *text)
-{
-    Xapian::TermGenerator *term_gen = message->notmuch->term_gen;
-
-    if (text == NULL)
-       return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
-
-    term_gen->set_document (message->doc);
-
-    if (prefix_name) {
-       const char *prefix = _find_prefix (prefix_name);
-
-       term_gen->index_text (text, 1, prefix);
-    }
-
-    term_gen->index_text (text);
-
-    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
-}
-
-/* Remove a name:value term from 'message', (the actual term will be
- * encoded by prefixing the value with a short prefix). See
- * NORMAL_PREFIX and BOOLEAN_PREFIX arrays for the mapping of term
- * names to prefix values.
- *
- * This change will not be reflected in the database until the next
- * call to _notmuch_message_set_sync. */
-notmuch_private_status_t
-_notmuch_message_remove_term (notmuch_message_t *message,
-                             const char *prefix_name,
-                             const char *value)
-{
-    char *term;
-
-    if (value == NULL)
-       return NOTMUCH_PRIVATE_STATUS_NULL_POINTER;
-
-    term = talloc_asprintf (message, "%s%s",
-                           _find_prefix (prefix_name), value);
-
-    if (strlen (term) > NOTMUCH_TERM_MAX)
-       return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
-
-    try {
-       message->doc.remove_term (term);
-    } catch (const Xapian::InvalidArgumentError) {
-       /* We'll let the philosopher's try to wrestle with the
-        * question of whether failing to remove that which was not
-        * there in the first place is failure. For us, we'll silently
-        * consider it all good. */
-    }
-
-    talloc_free (term);
-
-    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
-}
-
-notmuch_status_t
-notmuch_message_add_tag (notmuch_message_t *message, const char *tag)
-{
-    notmuch_private_status_t status;
-
-    if (tag == NULL)
-       return NOTMUCH_STATUS_NULL_POINTER;
-
-    if (strlen (tag) > NOTMUCH_TAG_MAX)
-       return NOTMUCH_STATUS_TAG_TOO_LONG;
-
-    status = _notmuch_message_add_term (message, "tag", tag);
-    if (status) {
-       INTERNAL_ERROR ("_notmuch_message_add_term return unexpected value: %d\n",
-                       status);
-    }
-
-    if (! message->frozen)
-       _notmuch_message_sync (message);
-
-    return NOTMUCH_STATUS_SUCCESS;
-}
-
-notmuch_status_t
-notmuch_message_remove_tag (notmuch_message_t *message, const char *tag)
-{
-    notmuch_private_status_t status;
-
-    if (tag == NULL)
-       return NOTMUCH_STATUS_NULL_POINTER;
-
-    if (strlen (tag) > NOTMUCH_TAG_MAX)
-       return NOTMUCH_STATUS_TAG_TOO_LONG;
-
-    status = _notmuch_message_remove_term (message, "tag", tag);
-    if (status) {
-       INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
-                       status);
-    }
-
-    if (! message->frozen)
-       _notmuch_message_sync (message);
-
-    return NOTMUCH_STATUS_SUCCESS;
-}
-
-void
-notmuch_message_remove_all_tags (notmuch_message_t *message)
-{
-    notmuch_private_status_t status;
-    notmuch_tags_t *tags;
-    const char *tag;
-
-    for (tags = notmuch_message_get_tags (message);
-        notmuch_tags_has_more (tags);
-        notmuch_tags_advance (tags))
-    {
-       tag = notmuch_tags_get (tags);
-
-       status = _notmuch_message_remove_term (message, "tag", tag);
-       if (status) {
-           INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n",
-                           status);
-       }
-    }
-
-    if (! message->frozen)
-       _notmuch_message_sync (message);
-}
-
-void
-notmuch_message_freeze (notmuch_message_t *message)
-{
-    message->frozen++;
-}
-
-notmuch_status_t
-notmuch_message_thaw (notmuch_message_t *message)
-{
-    if (message->frozen > 0) {
-       message->frozen--;
-       if (message->frozen == 0)
-           _notmuch_message_sync (message);
-       return NOTMUCH_STATUS_SUCCESS;
-    } else {
-       return NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW;
-    }
-}
-
-void
-notmuch_message_destroy (notmuch_message_t *message)
-{
-    talloc_free (message);
-}
diff --git a/notmuch-private.h b/notmuch-private.h
deleted file mode 100644 (file)
index 92b4634..0000000
+++ /dev/null
@@ -1,314 +0,0 @@
-/* notmuch-private.h - Internal interfaces for notmuch.
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#ifndef NOTMUCH_PRIVATE_H
-#define NOTMUCH_PRIVATE_H
-
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE /* For getline and asprintf */
-#endif
-#include <stdio.h>
-
-#include "notmuch.h"
-
-NOTMUCH_BEGIN_DECLS
-
-#include <stdlib.h>
-#include <stdarg.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-#include <string.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <ctype.h>
-#include <assert.h>
-
-#include <talloc.h>
-
-#include "xutil.h"
-
-#ifdef DEBUG
-# define DEBUG_DATABASE_SANITY 1
-# define DEBUG_QUERY 1
-#endif
-
-#define COMPILE_TIME_ASSERT(pred) ((void)sizeof(char[1 - 2*!(pred)]))
-
-/* There's no point in continuing when we've detected that we've done
- * something wrong internally (as opposed to the user passing in a
- * bogus value).
- *
- * Note that PRINTF_ATTRIBUTE comes from talloc.h
- */
-int
-_internal_error (const char *format, ...) PRINTF_ATTRIBUTE (1, 2);
-
-/* There's no point in continuing when we've detected that we've done
- * something wrong internally (as opposed to the user passing in a
- * bogus value).
- *
- * Note that __location__ comes from talloc.h.
- */
-#define INTERNAL_ERROR(format, ...)                    \
-    _internal_error (format " (%s).\n",                        \
-                    ##__VA_ARGS__, __location__)
-
-#define unused(x) x __attribute__ ((unused))
-
-/* Thanks to Andrew Tridgell's (SAMBA's) talloc for this definition of
- * unlikely. The talloc source code comes to us via the GNU LGPL v. 3.
- */
-/* these macros gain us a few percent of speed on gcc */
-#if (__GNUC__ >= 3)
-/* the strange !! is to ensure that __builtin_expect() takes either 0 or 1
-   as its first argument */
-#ifndef likely
-#define likely(x)   __builtin_expect(!!(x), 1)
-#endif
-#ifndef unlikely
-#define unlikely(x) __builtin_expect(!!(x), 0)
-#endif
-#else
-#ifndef likely
-#define likely(x) (x)
-#endif
-#ifndef unlikely
-#define unlikely(x) (x)
-#endif
-#endif
-
-typedef enum {
-    NOTMUCH_VALUE_TIMESTAMP = 0,
-    NOTMUCH_VALUE_MESSAGE_ID
-} notmuch_value_t;
-
-/* Xapian (with flint backend) complains if we provide a term longer
- * than this, but I haven't yet found a way to query the limit
- * programmatically. */
-#define NOTMUCH_TERM_MAX 245
-
-typedef enum _notmuch_private_status {
-    /* First, copy all the public status values. */
-    NOTMUCH_PRIVATE_STATUS_SUCCESS = NOTMUCH_STATUS_SUCCESS,
-    NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY = NOTMUCH_STATUS_OUT_OF_MEMORY,
-    NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION = NOTMUCH_STATUS_XAPIAN_EXCEPTION,
-    NOTMUCH_PRIVATE_STATUS_FILE_NOT_EMAIL = NOTMUCH_STATUS_FILE_NOT_EMAIL,
-    NOTMUCH_PRIVATE_STATUS_NULL_POINTER = NOTMUCH_STATUS_NULL_POINTER,
-    NOTMUCH_PRIVATE_STATUS_TAG_TOO_LONG = NOTMUCH_STATUS_TAG_TOO_LONG,
-
-    /* Then add our own private values. */
-    NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG,
-    NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND,
-
-    NOTMUCH_PRIVATE_STATUS_LAST_STATUS
-} notmuch_private_status_t;
-
-/* Coerce a notmuch_private_status_t value to a notmuch_status_t
- * value, generating an internal error if the private value is equal
- * to or greater than NOTMUCH_STATUS_LAST_STATUS. (The idea here is
- * that the caller has previously handled any expected
- * notmuch_private_status_t values.)
- */
-#define COERCE_STATUS(private_status, format, ...)                     \
-    ((private_status >= (notmuch_private_status_t) NOTMUCH_STATUS_LAST_STATUS)\
-     ?                                                                 \
-     (notmuch_status_t) _internal_error (format " (%s).\n",            \
-                                         ##__VA_ARGS__,                        \
-                                         __location__)                 \
-     :                                                                 \
-     (notmuch_status_t) private_status)
-
-/* database.cc */
-
-/* Lookup a prefix value by name.
- *
- * XXX: This should really be static inside of message.cc, and we can
- * do that once we convert database.cc to use the
- * _notmuch_message_add/remove_term functions. */
-const char *
-_find_prefix (const char *name);
-
-/* thread.cc */
-
-notmuch_thread_t *
-_notmuch_thread_create (const void *talloc_owner,
-                       notmuch_database_t *notmuch,
-                       const char *thread_id);
-
-void
-_notmuch_thread_add_message (notmuch_thread_t *thread,
-                            notmuch_message_t *message);
-
-/* message.cc */
-
-notmuch_message_t *
-_notmuch_message_create (const void *talloc_owner,
-                        notmuch_database_t *notmuch,
-                        unsigned int doc_id,
-                        notmuch_private_status_t *status);
-
-notmuch_message_t *
-_notmuch_message_create_for_message_id (const void *talloc_owner,
-                                       notmuch_database_t *notmuch,
-                                       const char *message_id,
-                                       notmuch_private_status_t *status);
-
-const char *
-_notmuch_message_get_subject (notmuch_message_t *message);
-
-notmuch_private_status_t
-_notmuch_message_add_term (notmuch_message_t *message,
-                          const char *prefix_name,
-                          const char *value);
-
-notmuch_private_status_t
-_notmuch_message_remove_term (notmuch_message_t *message,
-                             const char *prefix_name,
-                             const char *value);
-
-notmuch_private_status_t
-_notmuch_message_gen_terms (notmuch_message_t *message,
-                           const char *prefix_name,
-                           const char *text);
-
-void
-_notmuch_message_set_filename (notmuch_message_t *message,
-                              const char *filename);
-
-void
-_notmuch_message_add_thread_id (notmuch_message_t *message,
-                               const char *thread_id);
-
-void
-_notmuch_message_ensure_thread_id (notmuch_message_t *message);
-
-void
-_notmuch_message_set_date (notmuch_message_t *message,
-                          const char *date);
-
-void
-_notmuch_message_sync (notmuch_message_t *message);
-
-/* index.cc */
-
-notmuch_status_t
-_notmuch_message_index_file (notmuch_message_t *message,
-                            const char *filename);
-
-/* message-file.c */
-
-/* XXX: I haven't decided yet whether these will actually get exported
- * into the public interface in notmuch.h
- */
-
-typedef struct _notmuch_message_file notmuch_message_file_t;
-
-/* Open a file containing a single email message.
- *
- * The caller should call notmuch_message_close when done with this.
- *
- * Returns NULL if any error occurs.
- */
-notmuch_message_file_t *
-notmuch_message_file_open (const char *filename);
-
-/* Like notmuch_message_file_open but with 'ctx' as the talloc owner. */
-notmuch_message_file_t *
-_notmuch_message_file_open_ctx (void *ctx, const char *filename);
-
-/* Close a notmuch message preivously opened with notmuch_message_open. */
-void
-notmuch_message_file_close (notmuch_message_file_t *message);
-
-/* Restrict 'message' to only save the named headers.
- *
- * When the caller is only interested in a short list of headers,
- * known in advance, calling this function can avoid wasted time and
- * memory parsing/saving header values that will never be needed.
- *
- * The variable arguments should be a list of const char * with a
- * final '(const char *) NULL' to terminate the list.
- *
- * If this function is called, it must be called before any calls to
- * notmuch_message_get_header for this message.
- *
- * After calling this function, if notmuch_message_get_header is
- * called with a header name not in this list, then NULL will be
- * returned even if that header exists in the actual message.
- */
-void
-notmuch_message_file_restrict_headers (notmuch_message_file_t *message, ...);
-
-/* Identical to notmuch_message_restrict_headers but accepting a va_list. */
-void
-notmuch_message_file_restrict_headersv (notmuch_message_file_t *message,
-                                       va_list va_headers);
-
-/* Get the value of the specified header from the message.
- *
- * The header name is case insensitive.
- *
- * The returned value is owned by the notmuch message and is valid
- * only until the message is closed. The caller should copy it if
- * needing to modify the value or to hold onto it for longer.
- *
- * Returns NULL if the message does not contain a header line matching
- * 'header'.
- */
-const char *
-notmuch_message_file_get_header (notmuch_message_file_t *message,
-                                const char *header);
-
-/* date.c */
-
-/* Parse an RFC 8222 date string to a time_t value.
- *
- * The tz_offset argument can be used to also obtain the time-zone
- * offset, (but can be NULL if the call is not interested in that).
- *
- * Returns 0 on error.
- */
-time_t
-notmuch_parse_date (const char *str, int *tz_offset);
-
-/* sha1.c */
-
-char *
-notmuch_sha1_of_string (const char *str);
-
-char *
-notmuch_sha1_of_file (const char *filename);
-
-/* tags.c */
-
-notmuch_tags_t *
-_notmuch_tags_create (void *ctx);
-
-void
-_notmuch_tags_add_tag (notmuch_tags_t *tags, const char *tag);
-
-void
-_notmuch_tags_prepare_iterator (notmuch_tags_t *tags);
-
-NOTMUCH_END_DECLS
-
-#endif
diff --git a/notmuch.h b/notmuch.h
deleted file mode 100644 (file)
index bab573d..0000000
--- a/notmuch.h
+++ /dev/null
@@ -1,834 +0,0 @@
-/* notmuch - Not much of an email library, (just index and search)
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#ifndef NOTMUCH_H
-#define NOTMUCH_H
-
-#ifdef  __cplusplus
-# define NOTMUCH_BEGIN_DECLS  extern "C" {
-# define NOTMUCH_END_DECLS    }
-#else
-# define NOTMUCH_BEGIN_DECLS
-# define NOTMUCH_END_DECLS
-#endif
-
-NOTMUCH_BEGIN_DECLS
-
-#include <time.h>
-
-#ifndef FALSE
-#define FALSE 0
-#endif
-
-#ifndef TRUE
-#define TRUE 1
-#endif
-
-typedef int notmuch_bool_t;
-
-/* Status codes used for the return values of most functions.
- *
- * A zero value (NOTMUCH_STATUS_SUCCESS) indicates that the function
- * completed without error. Any other value indicates an error as
- * follows:
- *
- * NOTMUCH_STATUS_SUCCESS: No error occurred.
- *
- * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory
- *
- * XXX: We don't really want to expose this lame XAPIAN_EXCEPTION
- * value. Instead we should map to things like DATABASE_LOCKED or
- * whatever.
- *
- * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred
- *
- * NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to read or
- *     write to a file (this could be file not found, permission
- *     denied, etc.)
- *
- * NOTMUCH_STATUS_FILE_NOT_EMAIL: A file was presented that doesn't
- *     appear to be an email message.
- *
- * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: A file contains a message ID
- *     that is identical to a message already in the database.
- *
- * NOTMUCH_STATUS_NULL_POINTER: The user erroneously passed a NULL
- *     pointer to a notmuch function.
- *
- * NOTMUCH_STATUS_TAG_TOO_LONG: A tag value is too long (exceeds
- *     NOTMUCH_TAG_MAX)
- *
- * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: The notmuch_message_thaw
- *     function has been called more times than notmuch_message_freeze.
- *
- * And finally:
- *
- * NOTMUCH_STATUS_LAST_STATUS: Not an actual status value. Just a way
- *     to find out how many valid status values there are.
- */
-typedef enum _notmuch_status {
-    NOTMUCH_STATUS_SUCCESS = 0,
-    NOTMUCH_STATUS_OUT_OF_MEMORY,
-    NOTMUCH_STATUS_XAPIAN_EXCEPTION,
-    NOTMUCH_STATUS_FILE_ERROR,
-    NOTMUCH_STATUS_FILE_NOT_EMAIL,
-    NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID,
-    NOTMUCH_STATUS_NULL_POINTER,
-    NOTMUCH_STATUS_TAG_TOO_LONG,
-    NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW,
-
-    NOTMUCH_STATUS_LAST_STATUS
-} notmuch_status_t;
-
-/* Get a string representation of a notmuch_status_t value.
- *
- * The result is readonly.
- */
-const char *
-notmuch_status_to_string (notmuch_status_t status);
-
-/* Various opaque data types. For each notmuch_<foo>_t see the various
- * notmuch_<foo> functions below. */
-typedef struct _notmuch_database notmuch_database_t;
-typedef struct _notmuch_query notmuch_query_t;
-typedef struct _notmuch_threads notmuch_threads_t;
-typedef struct _notmuch_thread notmuch_thread_t;
-typedef struct _notmuch_messages notmuch_messages_t;
-typedef struct _notmuch_message notmuch_message_t;
-typedef struct _notmuch_tags notmuch_tags_t;
-
-/* Lookup the default database path.
- *
- * This is the path that will be used by notmuch_database_create and
- * notmuch_database_open if given a NULL path. Specifically it will be
- * the value of the NOTMUCH_BASE environment variable if set,
- * otherwise ${HOME}/mail
- *
- * Returns a newly allocated string which the caller should free()
- * when finished with it.
- */
-char *
-notmuch_database_default_path (void);
-
-/* Create a new, empty notmuch database located at 'path'.
- *
- * The path should be a top-level directory to a collection of
- * plain-text email messages (one message per file). This call will
- * create a new ".notmuch" directory within 'path' where notmuch will
- * store its data.
- *
- * Passing a value of NULL for 'path' will cause notmuch to open the
- * default database. The default database path can be specified by the
- * NOTMUCH_BASE environment variable, and is equivalent to
- * ${HOME}/mail if NOTMUCH_BASE is not set.
- *
- * After a successful call to notmuch_database_create, the returned
- * database will be open so the caller should call
- * notmuch_database_close when finished with it.
- *
- * The database will not yet have any data in it
- * (notmuch_database_create itself is a very cheap function). Messages
- * contained within 'path' can be added to the database by calling
- * notmuch_database_add_message.
- *
- * In case of any failure, this function returns NULL, (after printing
- * an error message on stderr).
- */
-notmuch_database_t *
-notmuch_database_create (const char *path);
-
-/* XXX: I think I'd like this to take an extra argument of
- * notmuch_status_t* for returning a status value on failure. */
-
-/* Open a an existing notmuch database located at 'path'.
- *
- * The database should have been created at some time in the past,
- * (not necessarily by this process), by calling
- * notmuch_database_create with 'path'.
- *
- * An existing notmuch database can be identified by the presence of a
- * directory named ".notmuch" below 'path'.
- *
- * Passing a value of NULL for 'path' will cause notmuch to open the
- * default database. The default database path can be specified by the
- * NOTMUCH_BASE environment variable, and is equivalent to
- * ${HOME}/mail if NOTMUCH_BASE is not set.
- *
- * The caller should call notmuch_database_close when finished with
- * this database.
- *
- * In case of any failure, this function returns NULL, (after printing
- * an error message on stderr).
- */
-notmuch_database_t *
-notmuch_database_open (const char *path);
-
-/* Close the given notmuch database, freeing all associated
- * resources. See notmuch_database_open. */
-void
-notmuch_database_close (notmuch_database_t *database);
-
-/* Return the database path of the given database.
- *
- * The return value is a string owned by notmuch so should not be
- * modified nor freed by the caller. */
-const char *
-notmuch_database_get_path (notmuch_database_t *database);
-
-/* Store a timestamp within the database.
- *
- * The Notmuch database will not interpret this key nor the timestamp
- * values at all. It will merely store them together and return the
- * timestamp when notmuch_database_get_timestamp is called with the
- * same value for 'key'.
- *
- * The intention is for the caller to use the timestamp to allow
- * efficient identification of new messages to be added to the
- * database. The recommended usage is as follows:
- *
- *   o Read the mtime of a directory from the filesystem
- *
- *   o Call add_message for all mail files in the directory
- *
- *   o Call notmuch_database_set_timestamp with the path of the
- *     directory as 'key' and the originally read mtime as 'value'.
- *
- * Then, when wanting to check for updates to the directory in the
- * future, the client can call notmuch_database_get_timestamp and know
- * that it only needs to add files if the mtime of the directory and
- * files are newer than the stored timestamp.
- *
- * Note: The notmuch_database_get_timestamp function does not allow
- * the caller to distinguish a timestamp of 0 from a non-existent
- * timestamp. So don't store a timestamp of 0 unless you are
- * comfortable with that.
- *
- * Return value:
- *
- * NOTMUCH_STATUS_SUCCESS: Timestamp successfully stored in database.
- *
- * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception
- *     occurred. Timestamp not stored.
- */
-notmuch_status_t
-notmuch_database_set_timestamp (notmuch_database_t *database,
-                               const char *key, time_t timestamp);
-
-/* Retrieve a timestamp from the database.
- *
- * Returns the timestamp value previously stored by calling
- * notmuch_database_set_timestamp with the same value for 'key'.
- *
- * Returns 0 if no timestamp is stored for 'key' or if any error
- * occurred querying the database.
- */
-time_t
-notmuch_database_get_timestamp (notmuch_database_t *database,
-                               const char *key);
-
-/* Add a new message to the given notmuch database.
- *
- * Here,'filename' should be a path relative to the the path of
- * 'database' (see notmuch_database_get_path), or else should be an
- * absolute filename with initial components that match the path of
- * 'database'.
- *
- * The file should be a single mail message (not a multi-message mbox)
- * that is expected to remain at its current location, (since the
- * notmuch database will reference the filename, and will not copy the
- * entire contents of the file.
- *
- * If 'message' is not NULL, then, on successful return '*message'
- * will be initialized to a message object that can be used for things
- * such as adding tags to the just-added message. The user should call
- * notmuch_message_destroy when done with the message. On any failure
- * '*message' will be set to NULL.
- *
- * Return value:
- *
- * NOTMUCH_STATUS_SUCCESS: Message successfully added to database.
- *
- * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: Message has the same message
- *     ID as another message already in the database. Nothing added
- *     to the database.
- *
- * NOTMUCH_STATUS_FILE_ERROR: an error occurred trying to open the
- *     file, (such as permission denied, or file not found,
- *     etc.). Nothing added to the database.
- *
- * NOTMUCH_STATUS_FILE_NOT_EMAIL: the contents of filename don't look
- *     like an email message. Nothing added to the database.
- */
-notmuch_status_t
-notmuch_database_add_message (notmuch_database_t *database,
-                             const char *filename,
-                             notmuch_message_t **message);
-
-/* Find a message with the given messsage_id.
- *
- * If the database contains a message with the given message_id, then
- * a new notmuch_message_t object is returned. The caller should call
- * notmuch_message_destroy when done with the message.
- *
- * If no message is found with the given message_id or if an
- * out-of-memory situation occurs, this function returns NULL.
- */
-notmuch_message_t *
-notmuch_database_find_message (notmuch_database_t *database,
-                              const char *message_id);
-
-/* Create a new query for 'database'.
- *
- * Here, 'database' should be an open database, (see
- * notmuch_database_open and notmuch_database_create).
- *
- * For the query string, we'll document the syntax here more
- * completely in the future, but it's likely to be a specialized
- * version of the general Xapian query syntax:
- *
- * http://xapian.org/docs/queryparser.html
- *
- * As a special case, passing a length-zero string, (that is ""), will
- * result in a query that returns all messages in the database.
- *
- * See notmuch_query_set_sort for controlling the order of results and
- * notmuch_query_search to actually execute the query.
- *
- * User should call notmuch_query_destroy when finished with this
- * query.
- *
- * Will return NULL if insufficient memory is available.
- */
-notmuch_query_t *
-notmuch_query_create (notmuch_database_t *database,
-                     const char *query_string);
-
-/* Sort values for notmuch_query_set_sort */
-typedef enum {
-    NOTMUCH_SORT_DATE_OLDEST_FIRST,
-    NOTMUCH_SORT_DATE_NEWEST_FIRST,
-    NOTMUCH_SORT_MESSAGE_ID
-} notmuch_sort_t;
-
-/* Specify the sorting desired for this query. */
-void
-notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort);
-
-/* Execute a query for threads, returning a notmuch_threads_t object
- * which can be used to iterate over the results. The returned threads
- * object is owned by the query and as such, will only be valid until
- * notmuch_query_destroy.
- *
- * Typical usage might be:
- *
- *     notmuch_query_t *query;
- *     notmuch_threads_t *threads;
- *     notmuch_thread_t *thread;
- *
- *     query = notmuch_query_create (database, query_string);
- *
- *     for (threads = notmuch_query_search_threads (query);
- *          notmuch_threads_has_more (threads);
- *          notmuch_threads_advance (threads))
- *     {
- *         thread = notmuch_threads_get (threads);
- *         ....
- *         notmuch_thread_destroy (thread);
- *     }
- *
- *     notmuch_query_destroy (query);
- *
- * Note: If you are finished with a thread before its containing
- * query, you can call notmuch_thread_destroy to clean up some memory
- * sooner (as in the above example). Otherwise, if your thread objects
- * are long-lived, then you don't need to call notmuch_thread_destroy
- * and all the memory will still be reclaimed when the query is
- * destroyed.
- *
- * Note that there's no explicit destructor needed for the
- * notmuch_threads_t object. (For consistency, we do provide a
- * notmuch_threads_destroy function, but there's no good reason
- * to call it if the query is about to be destroyed).
- */
-notmuch_threads_t *
-notmuch_query_search_threads (notmuch_query_t *query);
-
-/* Execute a query for messages, returning a notmuch_messages_t object
- * which can be used to iterate over the results. The returned
- * messages object is owned by the query and as such, will only be
- * valid until notmuch_query_destroy.
- *
- * Typical usage might be:
- *
- *     notmuch_query_t *query;
- *     notmuch_messages_t *messages;
- *     notmuch_message_t *message;
- *
- *     query = notmuch_query_create (database, query_string);
- *
- *     for (messages = notmuch_query_search_messages (query);
- *          notmuch_messages_has_more (messages);
- *          notmuch_messages_advance (messages))
- *     {
- *         message = notmuch_messages_get (messages);
- *         ....
- *         notmuch_message_destroy (message);
- *     }
- *
- *     notmuch_query_destroy (query);
- *
- * Note: If you are finished with a message before its containing
- * query, you can call notmuch_message_destroy to clean up some memory
- * sooner (as in the above example). Otherwise, if your message
- * objects are long-lived, then you don't need to call
- * notmuch_message_destroy and all the memory will still be reclaimed
- * when the query is destroyed.
- *
- * Note that there's no explicit destructor needed for the
- * notmuch_messages_t object. (For consistency, we do provide a
- * notmuch_messages_destroy function, but there's no good
- * reason to call it if the query is about to be destroyed).
- */
-notmuch_messages_t *
-notmuch_query_search_messages (notmuch_query_t *query);
-
-/* Destroy a notmuch_query_t along with any associated resources.
- *
- * This will in turn destroy any notmuch_threads_t and
- * notmuch_messages_t objects generated by this query, (and in
- * turn any notmuch_thrad_t and notmuch_message_t objects generated
- * from those results, etc.), if such objects haven't already been
- * destroyed.
- */
-void
-notmuch_query_destroy (notmuch_query_t *query);
-
-/* Does the given notmuch_threads_t object contain any more
- * results.
- *
- * When this function returns TRUE, notmuch_threads_get will
- * return a valid object. Whereas when this function returns FALSE,
- * notmuch_threads_get will return NULL.
- *
- * See the documentation of notmuch_query_search_threads for example
- * code showing how to iterate over a notmuch_threads_t object.
- */
-notmuch_bool_t
-notmuch_threads_has_more (notmuch_threads_t *threads);
-
-/* Get the current thread from 'threads' as a notmuch_thread_t.
- *
- * Note: The returned thread belongs to 'threads' and has a lifetime
- * identical to it (and the query to which it belongs).
- *
- * See the documentation of notmuch_query_search_threads for example
- * code showing how to iterate over a notmuch_threads_t object.
- *
- * If an out-of-memory situation occurs, this function will return
- * NULL.
- */
-notmuch_thread_t *
-notmuch_threads_get (notmuch_threads_t *threads);
-
-/* Advance the 'threads' iterator to the next thread.
- *
- * See the documentation of notmuch_query_search_threads for example
- * code showing how to iterate over a notmuch_threads_t object.
- */
-void
-notmuch_threads_advance (notmuch_threads_t *threads);
-
-/* Destroy a notmuch_threads_t object.
- *
- * It's not strictly necessary to call this function. All memory from
- * the notmuch_threads_t object will be reclaimed when the
- * containg query object is destroyed.
- */
-void
-notmuch_threads_destroy (notmuch_threads_t *threads);
-
-/* Get the thread ID of 'thread'.
- *
- * The returned string belongs to 'thread' and as such, should not be
- * modified by the caller and will only be valid for as long as the
- * thread is valid, (which is until notmuch_thread_destroy or until
- * the query from which it derived is destroyed).
- */
-const char *
-notmuch_thread_get_thread_id (notmuch_thread_t *thread);
-
-/* Get the subject of 'thread'
- *
- * The subject is taken from the first message (according to the query
- * order---see notmuch_query_set_sort) in the query results that
- * belongs to this thread.
- *
- * The returned string belongs to 'thread' and as such, should not be
- * modified by the caller and will only be valid for as long as the
- * thread is valid, (which is until notmuch_thread_destroy or until
- * the query from which it derived is destroyed).
- */
-const char *
-notmuch_thread_get_subject (notmuch_thread_t *thread);
-
-/* Get the date of the oldest message in 'thread' as a time_t value.
- */
-time_t
-notmuch_thread_get_oldest_date (notmuch_thread_t *thread);
-
-/* Get the date of the oldest message in 'thread' as a time_t value.
- */
-time_t
-notmuch_thread_get_newest_date (notmuch_thread_t *thread);
-
-/* Get the tags for 'thread', returning a notmuch_tags_t object which
- * can be used to iterate over all tags.
- *
- * Note: In the Notmuch database, tags are stored on individual
- * messages, not on threads. So the tags returned here will be all
- * tags of the messages which matched the search and which belong to
- * this thread.
- *
- * The tags object is owned by the thread and as such, will only be
- * valid for as long as the thread is valid, (for example, until
- * notmuch_thread_destroy or until the query from which it derived is
- * destroyed).
- *
- * Typical usage might be:
- *
- *     notmuch_thread_t *thread;
- *     notmuch_tags_t *tags;
- *     const char *tag;
- *
- *     thread = notmuch_threads_get (threads);
- *
- *     for (tags = notmuch_thread_get_tags (thread);
- *          notmuch_tags_has_more (tags);
- *          notmuch_result_advance (tags))
- *     {
- *         tag = notmuch_tags_get (tags);
- *         ....
- *     }
- *
- *     notmuch_thread_destroy (thread);
- *
- * Note that there's no explicit destructor needed for the
- * notmuch_tags_t object. (For consistency, we do provide a
- * notmuch_tags_destroy function, but there's no good reason to call
- * it if the message is about to be destroyed).
- */
-notmuch_tags_t *
-notmuch_thread_get_tags (notmuch_thread_t *thread);
-
-/* Destroy a notmuch_thread_t object. */
-void
-notmuch_thread_destroy (notmuch_thread_t *thread);
-
-/* Does the given notmuch_messages_t object contain any more
- * messages.
- *
- * When this function returns TRUE, notmuch_messages_get will return a
- * valid object. Whereas when this function returns FALSE,
- * notmuch_messages_get will return NULL.
- *
- * See the documentation of notmuch_query_search_messages for example
- * code showing how to iterate over a notmuch_messages_t object.
- */
-notmuch_bool_t
-notmuch_messages_has_more (notmuch_messages_t *messages);
-
-/* Get the current message from 'messages' as a notmuch_message_t.
- *
- * Note: The returned message belongs to 'messages' and has a lifetime
- * identical to it (and the query to which it belongs).
- *
- * See the documentation of notmuch_query_search_messages for example
- * code showing how to iterate over a notmuch_messages_t object.
- *
- * If an out-of-memory situation occurs, this function will return
- * NULL.
- */
-notmuch_message_t *
-notmuch_messages_get (notmuch_messages_t *messages);
-
-/* Advance the 'messages' iterator to the next result.
- *
- * See the documentation of notmuch_query_search_messages for example
- * code showing how to iterate over a notmuch_messages_t object.
- */
-void
-notmuch_messages_advance (notmuch_messages_t *messages);
-
-/* Destroy a notmuch_messages_t object.
- *
- * It's not strictly necessary to call this function. All memory from
- * the notmuch_messages_t object will be reclaimed when the containg
- * query object is destroyed.
- */
-void
-notmuch_messages_destroy (notmuch_messages_t *messages);
-
-/* Get the message ID of 'message'.
- *
- * The returned string belongs to 'message' and as such, should not be
- * modified by the caller and will only be valid for as long as the
- * message is valid, (which is until the query from which it derived
- * is destroyed).
- *
- * This function will not return NULL since Notmuch ensures that every
- * message has a unique message ID, (Notmuch will generate an ID for a
- * message if the original file does not contain one).
- */
-const char *
-notmuch_message_get_message_id (notmuch_message_t *message);
-
-/* Get the thread ID of 'message'.
- *
- * The returned string belongs to 'message' and as such, should not be
- * modified by the caller and will only be valid for as long as the
- * message is valid, (for example, until the user calls
- * notmuch_message_destroy on 'message' or until a query from which it
- * derived is destroyed).
- *
- * This function will not return NULL since Notmuch ensures that every
- * message belongs to a single thread.
- */
-const char *
-notmuch_message_get_thread_id (notmuch_message_t *message);
-
-/* Get the filename for the email corresponding to 'message'.
- *
- * The returned filename is an absolute filename, (the initial
- * component will match notmuch_database_get_path() ).
- *
- * The returned string belongs to the message so should not be
- * modified or freed by the caller (nor should it be referenced after
- * the message is destroyed). */
-const char *
-notmuch_message_get_filename (notmuch_message_t *message);
-
-/* Get the date of 'message' as a time_t value.
- *
- * For the original textual representation of the Date header from the
- * message call notmuch_message_get_header() with a header value of
- * "date". */
-time_t
-notmuch_message_get_date  (notmuch_message_t *message);
-
-/* Get the value of the specified header from 'message'.
- *
- * The value will be read from the actual message file, not from the
- * notmuch database. The header name is case insensitive.
- *
- * The returned string belongs to the message so should not be
- * modified or freed by the caller (nor should it be referenced after
- * the message is destroyed).
- *
- * Returns NULL if the message does not contain a header line matching
- * 'header' of if any error occurs.
- */
-const char *
-notmuch_message_get_header (notmuch_message_t *message, const char *header);
-
-/* Get the tags for 'message', returning a notmuch_tags_t object which
- * can be used to iterate over all tags.
- *
- * The tags object is owned by the message and as such, will only be
- * valid for as long as the message is valid, (which is until the
- * query from which it derived is destroyed).
- *
- * Typical usage might be:
- *
- *     notmuch_message_t *message;
- *     notmuch_tags_t *tags;
- *     const char *tag;
- *
- *     message = notmuch_database_find_message (database, message_id);
- *
- *     for (tags = notmuch_message_get_tags (message);
- *          notmuch_tags_has_more (tags);
- *          notmuch_result_advance (tags))
- *     {
- *         tag = notmuch_tags_get (tags);
- *         ....
- *     }
- *
- *     notmuch_message_destroy (message);
- *
- * Note that there's no explicit destructor needed for the
- * notmuch_tags_t object. (For consistency, we do provide a
- * notmuch_tags_destroy function, but there's no good reason to call
- * it if the message is about to be destroyed).
- */
-notmuch_tags_t *
-notmuch_message_get_tags (notmuch_message_t *message);
-
-/* The longest possible tag value. */
-#define NOTMUCH_TAG_MAX 200
-
-/* Add a tag to the given message.
- *
- * Return value:
- *
- * NOTMUCH_STATUS_SUCCESS: Tag successfully added to message
- *
- * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
- *
- * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
- *     (exceeds NOTMUCH_TAG_MAX)
- */
-notmuch_status_t
-notmuch_message_add_tag (notmuch_message_t *message, const char *tag);
-
-/* Remove a tag from the given message.
- *
- * Return value:
- *
- * NOTMUCH_STATUS_SUCCESS: Tag successfully removed from message
- *
- * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL
- *
- * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long
- *     (exceeds NOTMUCH_TAG_MAX)
- */
-notmuch_status_t
-notmuch_message_remove_tag (notmuch_message_t *message, const char *tag);
-
-/* Remove all tags from the given message.
- *
- * See notmuch_message_freeze for an example showing how to safely
- * replace tag values.
- */
-void
-notmuch_message_remove_all_tags (notmuch_message_t *message);
-
-/* Freeze the current state of 'message' within the database.
- *
- * This means that changes to the message state, (via
- * notmuch_message_add_tag, notmuch_message_remove_tag, and
- * notmuch_message_remove_all_tags), will not be committed to the
- * database until the message is thawed with notmuch_message_thaw.
- *
- * Multiple calls to freeze/thaw are valid and these calls with
- * "stack". That is there must be as many calls to thaw as to freeze
- * before a message is actually thawed.
- *
- * The ability to do freeze/thaw allows for safe transactions to
- * change tag values. For example, explicitly setting a message to
- * have a given set of tags might look like this:
- *
- *    notmuch_message_freeze (message);
- *
- *    notmuch_message_remove_all_tags (message);
- *
- *    for (i = 0; i < NUM_TAGS; i++)
- *        notmuch_message_add_tag (message, tags[i]);
- *
- *    notmuch_message_thaw (message);
- *
- * With freeze/thaw used like this, the message in the database is
- * guaranteed to have either the full set of original tag value, or
- * the full set of new tag values, but nothing in between.
- *
- * Imagine the example above without freeze/thaw and the operation
- * somehow getting interrupted. This could result in the message being
- * left with no tags if the interruption happened after
- * notmuch_message_remove_all_tags but before notmuch_message_add_tag.
- */
-void
-notmuch_message_freeze (notmuch_message_t *message);
-
-/* Thaw the current 'message', synchronizing any changes that may have
- * occurred while 'message' was frozen into the notmuch database.
- *
- * See notmuch_message_freeze for an example of how to use this
- * function to safely provide tag changes.
- *
- * Multiple calls to freeze/thaw are valid and these calls with
- * "stack". That is there must be as many calls to thaw as to freeze
- * before a message is actually thawed.
- *
- * Return value:
- *
- * NOTMUCH_STATUS_SUCCESS: Message successfully thawed, (or at least
- *     its frozen count has successfully been reduced by 1).
- *
- * NOTMUCH_STATUS_UNBALANCE_FREEZE_THAW: An attempt was made to thaw
- *     an unfrozen message. That is, there have been an unbalanced
- *     number of calls to notmuch_message_freeze and
- *     notmuch_message_thaw.
- */
-notmuch_status_t
-notmuch_message_thaw (notmuch_message_t *message);
-
-/* Destroy a notmuch_message_t object.
- *
- * It can be useful to call this function in the case of a single
- * query object with many messages in the result, (such as iterating
- * over the entire database). Otherwise, it's fine to never call this
- * function and there will still be no memory leaks. (The memory from
- * the messages get reclaimed when the containing query is destroyed.)
- */
-void
-notmuch_message_destroy (notmuch_message_t *message);
-
-/* Does the given notmuch_tags_t object contain any more tags.
- *
- * When this function returns TRUE, notmuch_tags_get will return a
- * valid string. Whereas when this function returns FALSE,
- * notmuch_tags_get will return NULL.
- *
- * See the documentation of notmuch_message_get_tags for example code
- * showing how to iterate over a notmuch_tags_t object.
- */
-notmuch_bool_t
-notmuch_tags_has_more (notmuch_tags_t *tags);
-
-/* Get the current tag from 'tags' as a string.
- *
- * Note: The returned string belongs to 'tags' and has a lifetime
- * identical to it (and the query to which it utlimately belongs).
- *
- * See the documentation of notmuch_message_get_tags for example code
- * showing how to iterate over a notmuch_tags_t object.
- */
-const char *
-notmuch_tags_get (notmuch_tags_t *tags);
-
-/* Advance the 'tags' iterator to the next tag.
- *
- * See the documentation of notmuch_message_get_tags for example code
- * showing how to iterate over a notmuch_tags_t object.
- */
-void
-notmuch_tags_advance (notmuch_tags_t *tags);
-
-/* Destroy a notmuch_tags_t object.
- *
- * It's not strictly necessary to call this function. All memory from
- * the notmuch_tags_t object will be reclaimed when the containg
- * message or query objects are destroyed.
- */
-void
-notmuch_tags_destroy (notmuch_tags_t *tags);
-
-NOTMUCH_END_DECLS
-
-#endif
diff --git a/query.cc b/query.cc
deleted file mode 100644 (file)
index ed57661..0000000
--- a/query.cc
+++ /dev/null
@@ -1,301 +0,0 @@
-/* query.cc - Support for searching a notmuch database
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#include "notmuch-private.h"
-#include "database-private.h"
-
-#include <glib.h> /* GHashTable, GPtrArray */
-
-#include <xapian.h>
-
-struct _notmuch_query {
-    notmuch_database_t *notmuch;
-    const char *query_string;
-    notmuch_sort_t sort;
-};
-
-struct _notmuch_messages {
-    notmuch_database_t *notmuch;
-    Xapian::MSetIterator iterator;
-    Xapian::MSetIterator iterator_end;
-};
-
-struct _notmuch_threads {
-    notmuch_database_t *notmuch;
-    GPtrArray *threads;
-    unsigned int index;
-};
-
-notmuch_query_t *
-notmuch_query_create (notmuch_database_t *notmuch,
-                     const char *query_string)
-{
-    notmuch_query_t *query;
-
-#ifdef DEBUG_QUERY
-    fprintf (stderr, "Query string is:\n%s\n", query_string);
-#endif
-
-    query = talloc (NULL, notmuch_query_t);
-    if (unlikely (query == NULL))
-       return NULL;
-
-    query->notmuch = notmuch;
-
-    query->query_string = talloc_strdup (query, query_string);
-
-    query->sort = NOTMUCH_SORT_DATE_OLDEST_FIRST;
-
-    return query;
-}
-
-void
-notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort)
-{
-    query->sort = sort;
-}
-
-/* We end up having to call the destructors explicitly because we had
- * to use "placement new" in order to initialize C++ objects within a
- * block that we allocated with talloc. So C++ is making talloc
- * slightly less simple to use, (we wouldn't need
- * talloc_set_destructor at all otherwise).
- */
-static int
-_notmuch_messages_destructor (notmuch_messages_t *messages)
-{
-    messages->iterator.~MSetIterator ();
-    messages->iterator_end.~MSetIterator ();
-
-    return 0;
-}
-
-notmuch_messages_t *
-notmuch_query_search_messages (notmuch_query_t *query)
-{
-    notmuch_database_t *notmuch = query->notmuch;
-    const char *query_string = query->query_string;
-    notmuch_messages_t *messages;
-
-    messages = talloc (query, notmuch_messages_t);
-    if (unlikely (messages == NULL))
-       return NULL;
-
-    try {
-       Xapian::Enquire enquire (*notmuch->xapian_db);
-       Xapian::Query mail_query (talloc_asprintf (query, "%s%s",
-                                                  _find_prefix ("type"),
-                                                  "mail"));
-       Xapian::Query string_query, final_query;
-       Xapian::MSet mset;
-       unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN |
-                             Xapian::QueryParser::FLAG_PHRASE |
-                             Xapian::QueryParser::FLAG_LOVEHATE |
-                             Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE |
-                             Xapian::QueryParser::FLAG_WILDCARD);
-
-       if (strcmp (query_string, "") == 0) {
-           final_query = mail_query;
-       } else {
-           string_query = notmuch->query_parser->
-               parse_query (query_string, flags);
-           final_query = Xapian::Query (Xapian::Query::OP_AND,
-                                        mail_query, string_query);
-       }
-
-       switch (query->sort) {
-       case NOTMUCH_SORT_DATE_OLDEST_FIRST:
-           enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, FALSE);
-           break;
-       case NOTMUCH_SORT_DATE_NEWEST_FIRST:
-           enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, TRUE);
-           break;
-       case NOTMUCH_SORT_MESSAGE_ID:
-           enquire.set_sort_by_value (NOTMUCH_VALUE_MESSAGE_ID, FALSE);
-           break;
-       }
-
-#if DEBUG_QUERY
-       fprintf (stderr, "Final query is:\n%s\n", final_query.get_description().c_str());
-#endif
-
-       enquire.set_query (final_query);
-
-       mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ());
-
-       messages->notmuch = notmuch;
-
-       new (&messages->iterator) Xapian::MSetIterator ();
-       new (&messages->iterator_end) Xapian::MSetIterator ();
-
-       talloc_set_destructor (messages, _notmuch_messages_destructor);
-
-       messages->iterator = mset.begin ();
-       messages->iterator_end = mset.end ();
-
-    } catch (const Xapian::Error &error) {
-       fprintf (stderr, "A Xapian exception occurred: %s\n",
-                error.get_msg().c_str());
-    }
-
-    return messages;
-}
-
-/* Glib objects force use to use a talloc destructor as well, (but not
- * nearly as ugly as the for messages due to C++ objects). At
- * this point, I'd really like to have some talloc-friendly
- * equivalents for the few pieces of glib that I'm using. */
-static int
-_notmuch_threads_destructor (notmuch_threads_t *threads)
-{
-    g_ptr_array_free (threads->threads, TRUE);
-
-    return 0;
-}
-
-notmuch_threads_t *
-notmuch_query_search_threads (notmuch_query_t *query)
-{
-    notmuch_threads_t *threads;
-    notmuch_thread_t *thread;
-    const char *thread_id;
-    notmuch_messages_t *messages;
-    notmuch_message_t *message;
-    GHashTable *seen;
-
-    threads = talloc (query, notmuch_threads_t);
-    if (threads == NULL)
-       return NULL;
-
-    threads->notmuch = query->notmuch;
-    threads->threads = g_ptr_array_new ();
-    threads->index = 0;
-
-    talloc_set_destructor (threads, _notmuch_threads_destructor);
-
-    seen = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                 free, NULL);
-
-    for (messages = notmuch_query_search_messages (query);
-        notmuch_messages_has_more (messages);
-        notmuch_messages_advance (messages))
-    {
-       message = notmuch_messages_get (messages);
-
-       thread_id = notmuch_message_get_thread_id (message);
-
-       if (! g_hash_table_lookup_extended (seen,
-                                           thread_id, NULL,
-                                           (void **) &thread))
-       {
-           thread = _notmuch_thread_create (query, query->notmuch,
-                                            thread_id);
-
-           g_hash_table_insert (seen, xstrdup (thread_id), thread);
-
-           g_ptr_array_add (threads->threads, thread);
-       }
-
-       _notmuch_thread_add_message (thread, message);
-
-       notmuch_message_destroy (message);
-    }
-
-    g_hash_table_unref (seen);
-
-    return threads;
-}
-
-void
-notmuch_query_destroy (notmuch_query_t *query)
-{
-    talloc_free (query);
-}
-
-notmuch_bool_t
-notmuch_messages_has_more (notmuch_messages_t *messages)
-{
-    return (messages->iterator != messages->iterator_end);
-}
-
-notmuch_message_t *
-notmuch_messages_get (notmuch_messages_t *messages)
-{
-    notmuch_message_t *message;
-    Xapian::docid doc_id;
-    notmuch_private_status_t status;
-
-    if (! notmuch_messages_has_more (messages))
-       return NULL;
-
-    doc_id = *messages->iterator;
-
-    message = _notmuch_message_create (messages,
-                                      messages->notmuch, doc_id,
-                                      &status);
-
-    if (message == NULL &&
-       status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
-    {
-       INTERNAL_ERROR ("a messages iterator contains a non-existent document ID.\n");
-    }
-
-    return message;
-}
-
-void
-notmuch_messages_advance (notmuch_messages_t *messages)
-{
-    messages->iterator++;
-}
-
-void
-notmuch_messages_destroy (notmuch_messages_t *messages)
-{
-    talloc_free (messages);
-}
-
-notmuch_bool_t
-notmuch_threads_has_more (notmuch_threads_t *threads)
-{
-    return (threads->index < threads->threads->len);
-}
-
-notmuch_thread_t *
-notmuch_threads_get (notmuch_threads_t *threads)
-{
-    if (! notmuch_threads_has_more (threads))
-       return NULL;
-
-    return (notmuch_thread_t *) g_ptr_array_index (threads->threads,
-                                                  threads->index);
-}
-
-void
-notmuch_threads_advance (notmuch_threads_t *threads)
-{
-    threads->index++;
-}
-
-void
-notmuch_threads_destroy (notmuch_threads_t *threads)
-{
-    talloc_free (threads);
-}
diff --git a/sha1.c b/sha1.c
deleted file mode 100644 (file)
index ff4dd16..0000000
--- a/sha1.c
+++ /dev/null
@@ -1,115 +0,0 @@
-/* sha1.c - Interfaces to SHA-1 hash for the notmuch mail system
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#include "notmuch-private.h"
-
-#include "libsha1.h"
-
-/* Just some simple interfaces on top of libsha1 so that we can leave
- * libsha1 as untouched as possible. */
-
-static char *
-_hex_of_sha1_digest (const unsigned char digest[SHA1_DIGEST_SIZE])
-{
-    char *result, *r;
-    int i;
-
-    result = xcalloc (SHA1_DIGEST_SIZE * 2 + 1, 1);
-
-    for (r = result, i = 0;
-        i < SHA1_DIGEST_SIZE;
-        r += 2, i++)
-    {
-       sprintf (r, "%02x", digest[i]);
-    }
-
-    return result;
-}
-
-/* Create a hexadcimal string version of the SHA-1 digest of 'str'
- * (including its null terminating character).
- *
- * This function returns a newly allocated string which the caller
- * should free() when finished.
- */
-char *
-notmuch_sha1_of_string (const char *str)
-{
-    sha1_ctx sha1;
-    unsigned char digest[SHA1_DIGEST_SIZE];
-
-    sha1_begin (&sha1);
-
-    sha1_hash ((unsigned char *) str, strlen (str) + 1, &sha1);
-
-    sha1_end (digest, &sha1);
-
-    return _hex_of_sha1_digest (digest);
-}
-
-/* Create a hexadecimal string version of the SHA-1 digest of the
- * contents of the named file.
- *
- * This function returns a newly allocated string which the caller
- * should free() when finished.
- *
- * If any error occurs while reading the file, (permission denied,
- * file not found, etc.), this function returns NULL.
- */
-char *
-notmuch_sha1_of_file (const char *filename)
-{
-    FILE *file;
-#define BLOCK_SIZE 4096
-    unsigned char block[BLOCK_SIZE];
-    size_t bytes_read;
-    sha1_ctx sha1;
-    unsigned char digest[SHA1_DIGEST_SIZE];
-    char *result;
-
-    file = fopen (filename, "r");
-    if (file == NULL)
-       return NULL;
-
-    sha1_begin (&sha1);
-
-    while (1) {
-       bytes_read = fread (block, 1, 4096, file);
-       if (bytes_read == 0) {
-           if (feof (file)) {
-               break;
-           } else if (ferror (file)) {
-               fclose (file);
-               return NULL;
-           }
-       } else {
-           sha1_hash (block, bytes_read, &sha1);
-       }
-    }
-
-    sha1_end (digest, &sha1);
-
-    result = _hex_of_sha1_digest (digest);
-
-    fclose (file);
-
-    return result;
-}
-
diff --git a/tags.c b/tags.c
deleted file mode 100644 (file)
index afc132c..0000000
--- a/tags.c
+++ /dev/null
@@ -1,116 +0,0 @@
-/* tags.c - Iterator for tags returned from message or thread
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#include "notmuch-private.h"
-
-#include <glib.h> /* GList */
-
-struct _notmuch_tags {
-    int sorted;
-    GList *tags;
-    GList *iterator;
-};
-
-/* XXX: Should write some talloc-friendly list to avoid the need for
- * this. */
-static int
-_notmuch_tags_destructor (notmuch_tags_t *tags)
-{
-    g_list_free (tags->tags);
-
-    return 0;
-}
-
-/* Create a new notmuch_tags_t object, with 'ctx' as its talloc owner.
- *
- * This function can return NULL in case of out-of-memory.
- */
-notmuch_tags_t *
-_notmuch_tags_create (void *ctx)
-{
-    notmuch_tags_t *tags;
-
-    tags = talloc (ctx, notmuch_tags_t);
-    if (unlikely (tags == NULL))
-       return NULL;
-
-    talloc_set_destructor (tags, _notmuch_tags_destructor);
-
-    tags->sorted = 1;
-    tags->tags = NULL;
-    tags->iterator = NULL;
-
-    return tags;
-}
-
-/* Add a new tag to 'tags'. The tags object will create its own copy
- * of the string.
- *
- * Note: The tags object will not do anything to prevent duplicate
- * tags being stored, so the caller really shouldn't pass
- * duplicates. */
-void
-_notmuch_tags_add_tag (notmuch_tags_t *tags, const char *tag)
-{
-    tags->tags = g_list_prepend (tags->tags, talloc_strdup (tags, tag));
-    tags->sorted = 0;
-}
-
-/* Prepare 'tag' for iteration.
- *
- * The internal creator of 'tags' should call this function before
- * returning 'tags' to the user to call the public functions such as
- * notmuch_tags_has_more, notmuch_tags_get, and notmuch_tags_advance. */
-void
-_notmuch_tags_prepare_iterator (notmuch_tags_t *tags)
-{
-    if (! tags->sorted)
-       tags->tags = g_list_sort (tags->tags, (GCompareFunc) strcmp);
-    tags->sorted = 1;
-
-    tags->iterator = tags->tags;
-}
-
-notmuch_bool_t
-notmuch_tags_has_more (notmuch_tags_t *tags)
-{
-    return tags->iterator != NULL;
-}
-
-const char *
-notmuch_tags_get (notmuch_tags_t *tags)
-{
-    if (tags->iterator)
-       return (char *) tags->iterator->data;
-    else
-       return NULL;
-}
-
-void
-notmuch_tags_advance (notmuch_tags_t *tags)
-{
-    tags->iterator = tags->iterator->next;
-}
-
-void
-notmuch_tags_destroy (notmuch_tags_t *tags)
-{
-    talloc_free (tags);
-}
diff --git a/thread.cc b/thread.cc
deleted file mode 100644 (file)
index b67dfad..0000000
--- a/thread.cc
+++ /dev/null
@@ -1,171 +0,0 @@
-/* thread.cc - Results of thread-based searches from a notmuch database
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#include "notmuch-private.h"
-#include "database-private.h"
-
-#include <xapian.h>
-
-#include <glib.h> /* GHashTable */
-
-struct _notmuch_thread {
-    notmuch_database_t *notmuch;
-    char *thread_id;
-    char *subject;
-    GHashTable *tags;
-
-    notmuch_bool_t has_message;
-    time_t oldest;
-    time_t newest;
-};
-
-static int
-_notmuch_thread_destructor (notmuch_thread_t *thread)
-{
-    g_hash_table_unref (thread->tags);
-
-    return 0;
-}
-
-/* Create a new notmuch_thread_t object for an existing document in
- * the database.
- *
- * Here, 'talloc owner' is an optional talloc context to which the new
- * thread will belong. This allows for the caller to not bother
- * calling notmuch_thread_destroy on the thread, and know that all
- * memory will be reclaimed with 'talloc_owner' is freed. The caller
- * still can call notmuch_thread_destroy when finished with the
- * thread if desired.
- *
- * The 'talloc_owner' argument can also be NULL, in which case the
- * caller *is* responsible for calling notmuch_thread_destroy.
- *
- * This function returns NULL in the case of any error.
- */
-notmuch_thread_t *
-_notmuch_thread_create (const void *talloc_owner,
-                       notmuch_database_t *notmuch,
-                       const char *thread_id)
-{
-    notmuch_thread_t *thread;
-
-    thread = talloc (talloc_owner, notmuch_thread_t);
-    if (unlikely (thread == NULL))
-       return NULL;
-
-    talloc_set_destructor (thread, _notmuch_thread_destructor);
-
-    thread->notmuch = notmuch;
-    thread->thread_id = talloc_strdup (thread, thread_id);
-    thread->subject = NULL;
-    thread->tags = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                         free, NULL);
-
-    thread->has_message = 0;
-    thread->oldest = 0;
-    thread->newest = 0;
-
-    return thread;
-}
-
-const char *
-notmuch_thread_get_thread_id (notmuch_thread_t *thread)
-{
-    return thread->thread_id;
-}
-
-void
-_notmuch_thread_add_message (notmuch_thread_t *thread,
-                            notmuch_message_t *message)
-{
-    notmuch_tags_t *tags;
-    const char *tag;
-    time_t date;
-
-    if (! thread->subject) {
-       const char *subject;
-       subject = notmuch_message_get_header (message, "subject");
-       thread->subject = talloc_strdup (thread, subject);
-    }
-
-    for (tags = notmuch_message_get_tags (message);
-        notmuch_tags_has_more (tags);
-        notmuch_tags_advance (tags))
-    {
-       tag = notmuch_tags_get (tags);
-       g_hash_table_insert (thread->tags, xstrdup (tag), NULL);
-    }
-
-    date = notmuch_message_get_date (message);
-
-    if (date < thread->oldest || ! thread->has_message)
-       thread->oldest = date;
-
-    if (date > thread->newest || ! thread->has_message)
-       thread->newest = date;
-
-    thread->has_message = 1;
-}
-
-const char *
-notmuch_thread_get_subject (notmuch_thread_t *thread)
-{
-    return thread->subject;
-}
-
-time_t
-notmuch_thread_get_oldest_date (notmuch_thread_t *thread)
-{
-    return thread->oldest;
-}
-
-time_t
-notmuch_thread_get_newest_date (notmuch_thread_t *thread)
-{
-    return thread->newest;
-}
-
-notmuch_tags_t *
-notmuch_thread_get_tags (notmuch_thread_t *thread)
-{
-    notmuch_tags_t *tags;
-    GList *keys, *l;
-
-    tags = _notmuch_tags_create (thread);
-    if (unlikely (tags == NULL))
-       return NULL;
-
-    keys = g_hash_table_get_keys (thread->tags);
-
-    for (l = keys; l; l = l->next)
-       _notmuch_tags_add_tag (tags, (char *) l->data);
-
-    g_list_free (keys);
-
-    _notmuch_tags_prepare_iterator (tags);
-
-    return tags;
-}
-
-void
-notmuch_thread_destroy (notmuch_thread_t *thread)
-{
-    talloc_free (thread);
-}
diff --git a/xutil.c b/xutil.c
deleted file mode 100644 (file)
index 6fa5eb0..0000000
--- a/xutil.c
+++ /dev/null
@@ -1,130 +0,0 @@
-/* xutil.c - Various wrapper functions to abort on error.
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#define _GNU_SOURCE /* For strndup */
-#include "notmuch-private.h"
-
-#include <stdio.h>
-
-void *
-xcalloc (size_t nmemb, size_t size)
-{
-    void *ret;
-
-    ret = calloc (nmemb, size);
-    if (ret == NULL) {
-       fprintf (stderr, "Out of memory.\n");
-       exit (1);
-    }
-
-    return ret;
-}
-
-void *
-xmalloc (size_t size)
-{
-    void *ret;
-
-    ret = malloc (size);
-    if (ret == NULL) {
-       fprintf (stderr, "Out of memory.\n");
-       exit (1);
-    }
-
-    return ret;
-}
-
-void *
-xrealloc (void *ptr, size_t size)
-{
-    void *ret;
-
-    ret = realloc (ptr, size);
-    if (ret == NULL) {
-       fprintf (stderr, "Out of memory.\n");
-       exit (1);
-    }
-
-    return ret;
-}
-
-char *
-xstrdup (const char *s)
-{
-    char *ret;
-
-    ret = strdup (s);
-    if (ret == NULL) {
-       fprintf (stderr, "Out of memory.\n");
-       exit (1);
-    }
-
-    return ret;
-}
-
-char *
-xstrndup (const char *s, size_t n)
-{
-    char *ret;
-
-    ret = strndup (s, n);
-    if (ret == NULL) {
-       fprintf (stderr, "Out of memory.\n");
-       exit (1);
-    }
-
-    return ret;
-}
-
-void
-xregcomp (regex_t *preg, const char *regex, int cflags)
-{
-    int rerr;
-
-    rerr = regcomp (preg, regex, cflags);
-    if (rerr) {
-       size_t error_size = regerror (rerr, preg, NULL, 0);
-       char *error = xmalloc (error_size);
-
-       regerror (rerr, preg, error, error_size);
-       INTERNAL_ERROR ("compiling regex %s: %s\n",
-                       regex, error);
-    }
-}
-
-int
-xregexec (const regex_t *preg, const char *string,
-         size_t nmatch, regmatch_t pmatch[], int eflags)
-{
-    unsigned int i;
-    int rerr;
-
-    rerr = regexec (preg, string, nmatch, pmatch, eflags);
-    if (rerr)
-       return rerr;
-
-    for (i = 0; i < nmatch; i++) {
-       if (pmatch[i].rm_so == -1)
-           INTERNAL_ERROR ("matching regex against %s: Sub-match %d not found\n",
-                           string, i);
-    }
-
-    return 0;
-}
diff --git a/xutil.h b/xutil.h
deleted file mode 100644 (file)
index b973f7d..0000000
--- a/xutil.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/* xutil.h - Various wrapper functions to abort on error.
- *
- * Copyright © 2009 Carl Worth
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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, see http://www.gnu.org/licenses/ .
- *
- * Author: Carl Worth <cworth@cworth.org>
- */
-
-#ifndef NOTMUCH_XUTIL_H
-#define NOTMUCH_XUTIL_H
-
-#include <stdlib.h>
-#include <sys/types.h>
-#include <regex.h>
-
-/* xutil.c */
-void *
-xcalloc (size_t nmemb, size_t size);
-
-void *
-xmalloc (size_t size);
-
-void *
-xrealloc (void *ptrr, size_t size);
-
-char *
-xstrdup (const char *s);
-
-char *
-xstrndup (const char *s, size_t n);
-
-void
-xregcomp (regex_t *preg, const char *regex, int cflags);
-
-int
-xregexec (const regex_t *preg, const char *string,
-         size_t nmatch, regmatch_t pmatch[], int eflags);
-
-#endif