X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=database.cc;h=5049b47e9ef593d44b586bc67d37064f79dc0a84;hp=7ea1f4168fb42bea820e147a0bfed14654258364;hb=302d54834d090d7e62377883f36dc709c95e5673;hpb=0e777a8f800af062aba39a95a003f3e1d8f33793 diff --git a/database.cc b/database.cc index 7ea1f416..5049b47e 100644 --- a/database.cc +++ b/database.cc @@ -18,84 +18,34 @@ * Author: Carl Worth */ -#include "notmuch-private.h" +#include "database-private.h" #include #include -#include +#include /* g_strdup_printf, g_free, GPtrArray, GHashTable */ using namespace std; -struct _notmuch_database { - char *path; - Xapian::WritableDatabase *xapian_db; - Xapian::TermGenerator *term_gen; -}; - -#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0])) - -/* Xapian complains if we provide a term longer than this. */ -#define NOTMUCH_MAX_TERM 245 - -/* These prefix values are specifically chosen to be compatible - * with sup, (http://sup.rubyforge.org), written by - * William Morgan , and released - * under the GNU GPL v2. - */ - -typedef struct { - const char *name; - const char *prefix; -} prefix_t; - -prefix_t NORMAL_PREFIX[] = { - { "subject", "S" }, - { "body", "B" }, - { "from_name", "FN" }, - { "to_name", "TN" }, - { "name", "N" }, - { "attachment", "A" } -}; - -prefix_t BOOLEAN_PREFIX[] = { - { "type", "K" }, - { "from_email", "FE" }, - { "to_email", "TE" }, - { "email", "E" }, - { "date", "D" }, - { "label", "L" }, - { "source_id", "I" }, - { "attachment_extension", "O" }, - { "msgid", "Q" }, - { "thread", "H" }, - { "ref", "R" } -}; - -/* Similarly, these value numbers are also chosen to be sup - * compatible. */ - -typedef enum { - NOTMUCH_VALUE_MESSAGE_ID = 0, - NOTMUCH_VALUE_THREAD = 1, - NOTMUCH_VALUE_DATE = 2 -} notmuch_value_t; - -static const char * -find_prefix (const char *name) +const char * +notmuch_status_to_string (notmuch_status_t status) { - unsigned int i; - - for (i = 0; i < ARRAY_SIZE (NORMAL_PREFIX); i++) - if (strcmp (name, NORMAL_PREFIX[i].name) == 0) - return NORMAL_PREFIX[i].prefix; - - for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX); i++) - if (strcmp (name, BOOLEAN_PREFIX[i].name) == 0) - return BOOLEAN_PREFIX[i].prefix; - - return ""; + switch (status) { + case NOTMUCH_STATUS_SUCCESS: + return "No error occurred"; + case NOTMUCH_STATUS_XAPIAN_EXCEPTION: + return "A Xapian exception occurred"; + case NOTMUCH_STATUS_FILE_NOT_EMAIL: + return "File is not an email"; + case NOTMUCH_STATUS_NULL_POINTER: + return "Erroneous NULL pointer"; + case NOTMUCH_STATUS_TAG_TOO_LONG: + return "Tag value is too long"; + default: + case NOTMUCH_STATUS_LAST_STATUS: + return "Unknown error status value"; + } } /* "128 bits of thread-id ought to be enough for anybody" */ @@ -134,6 +84,8 @@ thread_id_generate (thread_id_t *thread_id) } } +/* XXX: We should drop this function and convert all callers to call + * _notmuch_message_add_term instead. */ static void add_term (Xapian::Document doc, const char *prefix_name, @@ -145,11 +97,11 @@ add_term (Xapian::Document doc, if (value == NULL) return; - prefix = find_prefix (prefix_name); + prefix = _find_prefix (prefix_name); term = g_strdup_printf ("%s%s", prefix, value); - if (strlen (term) <= NOTMUCH_MAX_TERM) + if (strlen (term) <= NOTMUCH_TERM_MAX) doc.add_term (term); g_free (term); @@ -165,7 +117,7 @@ find_messages_by_term (Xapian::Database *db, Xapian::PostingIterator i; char *term; - term = g_strdup_printf ("%s%s", find_prefix (prefix_name), value); + term = g_strdup_printf ("%s%s", _find_prefix (prefix_name), value); *begin = db->postlist_begin (term); @@ -181,40 +133,19 @@ find_message_by_docid (Xapian::Database *db, Xapian::docid docid) return db->get_document (docid); } -Xapian::Document -find_message_by_message_id (Xapian::Database *db, const char *message_id) +notmuch_message_t * +notmuch_database_find_message (notmuch_database_t *notmuch, + const char *message_id) { Xapian::PostingIterator i, end; - find_messages_by_term (db, "msgid", message_id, &i, &end); + find_messages_by_term (notmuch->xapian_db, + "msgid", message_id, &i, &end); - if (i != end) - return find_message_by_docid (db, *i); - else - return Xapian::Document (); -} + if (i == end) + return NULL; -static void -insert_thread_id (GHashTable *thread_ids, Xapian::Document doc) -{ - string value_string; - const char *value, *id, *comma; - - value_string = doc.get_value (NOTMUCH_VALUE_THREAD); - value = value_string.c_str(); - if (strlen (value)) { - id = value; - while (*id) { - comma = strchr (id, ','); - if (comma == NULL) - comma = id + strlen (id); - g_hash_table_insert (thread_ids, - strndup (id, comma - id), NULL); - id = comma; - if (*id) - id++; - } - } + return _notmuch_message_create (notmuch, notmuch, *i); } /* Return one or more thread_ids, (as a GPtrArray of strings), for the @@ -225,10 +156,11 @@ insert_thread_id (GHashTable *thread_ids, Xapian::Document doc) * Caller should free all strings in the array and the array itself, * (g_ptr_array_free) when done. */ static GPtrArray * -find_thread_ids (Xapian::Database *db, +find_thread_ids (notmuch_database_t *notmuch, GPtrArray *parents, const char *message_id) { + Xapian::WritableDatabase *db = notmuch->xapian_db; Xapian::PostingIterator child, children_end; Xapian::Document doc; GHashTable *thread_ids; @@ -242,14 +174,38 @@ find_thread_ids (Xapian::Database *db, find_messages_by_term (db, "ref", message_id, &child, &children_end); for ( ; child != children_end; child++) { + const char *thread_id; doc = find_message_by_docid (db, *child); - insert_thread_id (thread_ids, doc); + + thread_id = doc.get_value (NOTMUCH_VALUE_THREAD).c_str (); + if (strlen (thread_id) == 0) { + fprintf (stderr, "Database error: Message with doc_id %u has empty thread-id value (value index %d)\n", + *child, NOTMUCH_VALUE_THREAD); + } else { + g_hash_table_insert (thread_ids, strdup (thread_id), NULL); + } } for (i = 0; i < parents->len; i++) { + notmuch_message_t *parent; + notmuch_thread_ids_t *ids; + parent_message_id = (char *) g_ptr_array_index (parents, i); - doc = find_message_by_message_id (db, parent_message_id); - insert_thread_id (thread_ids, doc); + parent = notmuch_database_find_message (notmuch, parent_message_id); + if (parent == NULL) + continue; + + for (ids = notmuch_message_get_thread_ids (parent); + notmuch_thread_ids_has_more (ids); + notmuch_thread_ids_advance (ids)) + { + const char *id; + + id = notmuch_thread_ids_get (ids); + g_hash_table_insert (thread_ids, strdup (id), NULL); + } + + notmuch_message_destroy (parent); } result = g_ptr_array_new (); @@ -320,6 +276,7 @@ static char * parse_message_id (const char *message_id, const char **next) { const char *s, *end; + char *result; if (message_id == NULL) return NULL; @@ -354,10 +311,23 @@ parse_message_id (const char *message_id, const char **next) if (end > s && *end == '>') end--; - if (end > s) - return strndup (s, end - s + 1); - else + if (end <= s) return NULL; + + result = strndup (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 copy of each referenced @@ -379,24 +349,38 @@ parse_references (GPtrArray *array, } } +char * +notmuch_database_default_path (void) +{ + if (getenv ("NOTMUCH_BASE")) + return strdup (getenv ("NOTMUCH_BASE")); + + return g_strdup_printf ("%s/mail", getenv ("HOME")); +} + notmuch_database_t * notmuch_database_create (const char *path) { - char *notmuch_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)); - return NULL; + goto DONE; } if (! S_ISDIR (st.st_mode)) { fprintf (stderr, "Error: Cannot create database at %s: Not a directory.\n", path); - return NULL; + goto DONE; } notmuch_path = g_strdup_printf ("%s/%s", path, ".notmuch"); @@ -406,50 +390,64 @@ notmuch_database_create (const char *path) if (err) { fprintf (stderr, "Error: Cannot create directory %s: %s.\n", notmuch_path, strerror (errno)); - free (notmuch_path); - return NULL; + goto DONE; } - free (notmuch_path); + notmuch = notmuch_database_open (path); + + DONE: + if (notmuch_path) + free (notmuch_path); + if (local_path) + free (local_path); - return notmuch_database_open (path); + return notmuch; } notmuch_database_t * notmuch_database_open (const char *path) { - notmuch_database_t *notmuch; - char *notmuch_path, *xapian_path; + notmuch_database_t *notmuch = NULL; + char *notmuch_path = NULL, *xapian_path = NULL; struct stat st; int err; + char *local_path = NULL; + + if (path == NULL) + path = local_path = notmuch_database_default_path (); notmuch_path = g_strdup_printf ("%s/%s", path, ".notmuch"); err = stat (notmuch_path, &st); if (err) { - fprintf (stderr, "Error: Cannot stat %s: %s\n", - notmuch_path, strerror (err)); - free (notmuch_path); - return NULL; + fprintf (stderr, "Error opening database at %s: %s\n", + notmuch_path, strerror (errno)); + goto DONE; } xapian_path = g_strdup_printf ("%s/%s", notmuch_path, "xapian"); - free (notmuch_path); - /* C++ is so nasty in requiring these casts. I'm almost tempted to - * write a C wrapper for Xapian... */ - notmuch = (notmuch_database_t *) xmalloc (sizeof (notmuch_database_t)); - notmuch->path = xstrdup (path); + notmuch = talloc (NULL, notmuch_database_t); + notmuch->path = talloc_strdup (notmuch, path); try { notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path, Xapian::DB_CREATE_OR_OPEN); + notmuch->query_parser = new Xapian::QueryParser; + notmuch->query_parser->set_default_op (Xapian::Query::OP_AND); + notmuch->query_parser->set_database (*notmuch->xapian_db); } catch (const Xapian::Error &error) { fprintf (stderr, "A Xapian exception occurred: %s\n", error.get_msg().c_str()); } - free (xapian_path); + DONE: + if (local_path) + free (local_path); + if (notmuch_path) + free (notmuch_path); + if (xapian_path) + free (xapian_path); return notmuch; } @@ -457,9 +455,9 @@ notmuch_database_open (const char *path) void notmuch_database_close (notmuch_database_t *notmuch) { + delete notmuch->query_parser; delete notmuch->xapian_db; - free (notmuch->path); - free (notmuch); + talloc_free (notmuch); } const char * @@ -474,35 +472,45 @@ notmuch_database_add_message (notmuch_database_t *notmuch, { Xapian::WritableDatabase *db = notmuch->xapian_db; Xapian::Document doc; - notmuch_message_t *message; + notmuch_message_file_t *message; GPtrArray *parents, *thread_ids; const char *refs, *in_reply_to, *date, *header; + const char *from, *to, *subject; char *message_id; time_t time_value; unsigned int i; - message = notmuch_message_open (filename); + message = notmuch_message_file_open (filename); - try { - doc = Xapian::Document (); + notmuch_message_file_restrict_headers (message, + "date", + "from", + "in-reply-to", + "message-id", + "references", + "subject", + (char *) NULL); + try { doc.set_data (filename); + add_term (doc, "type", "mail"); + parents = g_ptr_array_new (); - refs = notmuch_message_get_header (message, "references"); + refs = notmuch_message_file_get_header (message, "references"); parse_references (parents, refs); - in_reply_to = notmuch_message_get_header (message, "in-reply-to"); + in_reply_to = notmuch_message_file_get_header (message, "in-reply-to"); parse_references (parents, in_reply_to); for (i = 0; i < parents->len; i++) add_term (doc, "ref", (char *) g_ptr_array_index (parents, i)); - header = notmuch_message_get_header (message, "message-id"); + header = notmuch_message_file_get_header (message, "message-id"); if (header) { message_id = parse_message_id (header, NULL); /* So the header value isn't RFC-compliant, but it's @@ -515,7 +523,7 @@ notmuch_database_add_message (notmuch_database_t *notmuch, message_id = NULL; } - thread_ids = find_thread_ids (db, parents, message_id); + thread_ids = find_thread_ids (notmuch, parents, message_id); for (i = 0; i < parents->len; i++) g_free (g_ptr_array_index (parents, i)); @@ -540,7 +548,6 @@ notmuch_database_add_message (notmuch_database_t *notmuch, free (id); } - g_ptr_array_free (thread_ids, TRUE); doc.add_value (NOTMUCH_VALUE_THREAD, thread_id->str); g_string_free (thread_id, TRUE); } else if (message_id) { @@ -552,24 +559,36 @@ notmuch_database_add_message (notmuch_database_t *notmuch, doc.add_value (NOTMUCH_VALUE_THREAD, thread_id.str); } + g_ptr_array_free (thread_ids, TRUE); + free (message_id); -/* - date = notmuch_message_get_header (message, "date"); + date = notmuch_message_file_get_header (message, "date"); time_value = notmuch_parse_date (date, NULL); doc.add_value (NOTMUCH_VALUE_DATE, Xapian::sortable_serialise (time_value)); -*/ - db->add_document (doc); + from = notmuch_message_file_get_header (message, "from"); + subject = notmuch_message_file_get_header (message, "subject"); + to = notmuch_message_file_get_header (message, "to"); + + if (from == NULL && + subject == NULL && + to == NULL) + { + notmuch_message_file_close (message); + return NOTMUCH_STATUS_FILE_NOT_EMAIL; + } else { + db->add_document (doc); + } } catch (const Xapian::Error &error) { fprintf (stderr, "A Xapian exception occurred: %s.\n", error.get_msg().c_str()); return NOTMUCH_STATUS_XAPIAN_EXCEPTION; } - notmuch_message_close (message); + notmuch_message_file_close (message); return NOTMUCH_STATUS_SUCCESS; }