X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=database.cc;h=abae58205f897779871113199c703798d7cbfb8a;hp=36b1b5808f31c7786c40b14f381ed817307bf771;hb=e6236b88fd18231d0524b14723e0709a90b0572c;hpb=10c176ba0e6d71e920b72a3165c0e56f26b5e4b3 diff --git a/database.cc b/database.cc index 36b1b580..abae5820 100644 --- a/database.cc +++ b/database.cc @@ -20,20 +20,12 @@ #include "notmuch-private.h" -#include -#include -#include -#include -#include -#include -#include - #include -#include - #include +#include /* g_strdup_printf, g_free, GPtrArray, GHashTable */ + using namespace std; struct _notmuch_database { @@ -278,46 +270,147 @@ find_thread_ids (Xapian::Database *db, return result; } -/* Add a term for each message-id in the References header of the - * message. */ +/* 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 allocated string which the caller should free() + * when done with it. + * + * Returns NULL if there is any error parsing the message-id. */ +static char * +parse_message_id (const char *message_id, const char **next) +{ + const char *s, *end; + + 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 strndup (s, end - s + 1); + else + return NULL; +} + +/* Parse a References header value, putting a copy of each referenced + * message-id into 'array'. */ static void parse_references (GPtrArray *array, - const char *refs_str) + const char *refs) { - GMimeReferences *refs, *r; - const char *message_id; + char *ref; - if (refs_str == NULL) + if (refs == NULL) return; - refs = g_mime_references_decode (refs_str); + while (*refs) { + ref = parse_message_id (refs, &refs); - for (r = refs; r; r = r->next) { - message_id = g_mime_references_get_message_id (r); - g_ptr_array_add (array, g_strdup (message_id)); + if (ref) + g_ptr_array_add (array, ref); } +} - g_mime_references_free (refs); +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"); @@ -327,37 +420,42 @@ 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); - return notmuch_database_open (path); + DONE: + if (notmuch_path) + free (notmuch_path); + if (local_path) + free (local_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; - g_mime_init (0); + 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... */ @@ -372,7 +470,13 @@ notmuch_database_open (const char *path) 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; } @@ -397,31 +501,27 @@ notmuch_database_add_message (notmuch_database_t *notmuch, { Xapian::WritableDatabase *db = notmuch->xapian_db; Xapian::Document doc; + notmuch_message_t *message; - GMimeStream *stream; - GMimeParser *parser; - GMimeMessage *message; GPtrArray *parents, *thread_ids; - FILE *file; - - const char *refs, *in_reply_to; - const char *message_id; + const char *refs, *in_reply_to, *date, *header; + const char *from, *to, *subject; + char *message_id; - time_t time; + time_t time_value; unsigned int i; - file = fopen (filename, "r"); - if (! file) { - fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno)); - exit (1); - } - - stream = g_mime_stream_file_new (file); + message = notmuch_message_open (filename); - parser = g_mime_parser_new_with_stream (stream); - - message = g_mime_parser_construct_message (parser); + notmuch_message_restrict_headers (message, + "date", + "from", + "in-reply-to", + "message-id", + "references", + "subject", + (char *) NULL); try { doc = Xapian::Document (); @@ -430,16 +530,27 @@ notmuch_database_add_message (notmuch_database_t *notmuch, parents = g_ptr_array_new (); - refs = g_mime_object_get_header (GMIME_OBJECT (message), "references"); + refs = notmuch_message_get_header (message, "references"); parse_references (parents, refs); - in_reply_to = g_mime_object_get_header (GMIME_OBJECT (message), - "in-reply-to"); + in_reply_to = notmuch_message_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)); - message_id = g_mime_message_get_message_id (message); + header = notmuch_message_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 + * better than no message-id at all. */ + if (message_id == NULL) + message_id = xstrdup (header); + } else { + /* XXX: Should generate a message_id here, (such as a SHA1 + * sum of the message itself) */ + message_id = NULL; + } thread_ids = find_thread_ids (db, parents, message_id); @@ -478,19 +589,34 @@ notmuch_database_add_message (notmuch_database_t *notmuch, doc.add_value (NOTMUCH_VALUE_THREAD, thread_id.str); } - g_mime_message_get_date (message, &time, NULL); - doc.add_value (NOTMUCH_VALUE_DATE, Xapian::sortable_serialise (time)); + free (message_id); + + date = notmuch_message_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_get_header (message, "from"); + subject = notmuch_message_get_header (message, "subject"); + to = notmuch_message_get_header (message, "to"); + + if (from == NULL && + subject == NULL && + to == NULL) + { + notmuch_message_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; } - g_object_unref (message); - g_object_unref (parser); - g_object_unref (stream); + notmuch_message_close (message); return NOTMUCH_STATUS_SUCCESS; }