From bf78a89196b251c2465f6cefa8198f22c87ff23d Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Thu, 29 Oct 2009 08:51:12 -0700 Subject: [PATCH] notmuch show: Initial implementation (headers only) We're using a delimiter syntax that Keith is optimistic about being able to easily parse in emacs. Note: We're not escaping any occurrence of the delimiters in the message yet, so we'll need to fix that. --- message-file.c | 68 ++++++++++++++++++++++++++++++++++++++++++----- message.cc | 43 +++++++++++++++++++----------- notmuch-private.h | 11 ++++++++ notmuch.c | 65 ++++++++++++++++++++++++++++++++++++++++++-- notmuch.h | 29 ++++++++++++++++++++ query.cc | 2 +- 6 files changed, 193 insertions(+), 25 deletions(-) diff --git a/message-file.c b/message-file.c index 18275fbf..e8e2843e 100644 --- a/message-file.c +++ b/message-file.c @@ -39,6 +39,7 @@ struct _notmuch_message_file { GHashTable *headers; int broken_headers; int good_headers; + size_t header_size; /* Length of full message header in bytes. */ /* Parsing state */ char *line; @@ -204,6 +205,9 @@ copy_header_unfolding (header_value_closure_t *value, } } +/* 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) @@ -215,9 +219,13 @@ notmuch_message_file_get_header (notmuch_message_file_t *message, message->parsing_started = 1; - contains = g_hash_table_lookup_extended (message->headers, - header_desired, NULL, - (gpointer *) &value); + if (header_desired == NULL) + contains = 0; + else + contains = g_hash_table_lookup_extended (message->headers, + header_desired, NULL, + (gpointer *) &value); + if (contains && value) return value; @@ -225,7 +233,7 @@ notmuch_message_file_get_header (notmuch_message_file_t *message, return NULL; #define NEXT_HEADER_LINE(closure) \ - do { \ + while (1) { \ ssize_t bytes_read = getline (&message->line, \ &message->line_size, \ message->file); \ @@ -242,7 +250,11 @@ notmuch_message_file_get_header (notmuch_message_file_t *message, { \ copy_header_unfolding ((closure), message->line); \ } \ - } while (*message->line == ' ' || *message->line == '\t'); + if (*message->line == ' ' || *message->line == '\t') \ + message->header_size += strlen (message->line); \ + else \ + break; \ + } if (message->line == NULL) NEXT_HEADER_LINE (NULL); @@ -268,6 +280,8 @@ notmuch_message_file_get_header (notmuch_message_file_t *message, continue; } + message->header_size += strlen (message->line); + message->good_headers++; header = xstrndup (message->line, colon - message->line); @@ -290,7 +304,10 @@ notmuch_message_file_get_header (notmuch_message_file_t *message, NEXT_HEADER_LINE (&message->value); - match = (strcasecmp (header, header_desired) == 0); + if (header_desired == 0) + match = 0; + else + match = (strcasecmp (header, header_desired) == 0); value = xstrdup (message->value.str); @@ -314,7 +331,7 @@ notmuch_message_file_get_header (notmuch_message_file_t *message, /* 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 && + if (message->restrict_headers && header_desired && ! g_hash_table_lookup_extended (message->headers, header_desired, NULL, NULL)) { @@ -325,3 +342,40 @@ notmuch_message_file_get_header (notmuch_message_file_t *message, return NULL; } + +static size_t +_notmuch_message_file_get_header_size (notmuch_message_file_t *message) +{ + if (! message->parsing_finished) + notmuch_message_file_get_header (message, NULL); + + if (! message->parsing_finished) + INTERNAL_ERROR ("Parsing for NULL header did not force parsing to finish.\n"); + + return message->header_size; +} + +const char * +notmuch_message_file_get_all_headers (notmuch_message_file_t *message) +{ + char *headers = NULL; + size_t header_size = _notmuch_message_file_get_header_size (message); + + if (header_size == 0) + return ""; + + headers = talloc_size (message, header_size + 1); + if (unlikely (headers == NULL)) + return NULL; + + rewind (message->file); + if (fread (headers, 1, header_size, message->file) != header_size) { + fprintf (stderr, "Error: Short read occurred trying to read message header.\n"); + talloc_free (headers); + return NULL; + } + + headers[header_size] = '\0'; + + return headers; +} diff --git a/message.cc b/message.cc index 169b20c1..dfd55d59 100644 --- a/message.cc +++ b/message.cc @@ -232,26 +232,39 @@ notmuch_message_get_message_id (notmuch_message_t *message) return message->message_id; } -const char * -_notmuch_message_get_subject (notmuch_message_t *message) +static void +_notmuch_message_ensure_message_file (notmuch_message_t *message) { - if (! message->message_file) { - notmuch_message_file_t *message_file; - const char *filename; + const char *filename; - filename = notmuch_message_get_filename (message); - if (unlikely (filename == NULL)) - return NULL; + if (message->message_file) + return; - message_file = _notmuch_message_file_open_ctx (message, filename); - if (unlikely (message_file == NULL)) - return NULL; + filename = notmuch_message_get_filename (message); + if (unlikely (filename == NULL)) + return; - message->message_file = message_file; - } + message->message_file = _notmuch_message_file_open_ctx (message, filename); +} - return notmuch_message_file_get_header (message->message_file, - "subject"); +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_all_headers (notmuch_message_t *message) +{ + _notmuch_message_ensure_message_file (message); + if (message->message_file == NULL) + return NULL; + + return notmuch_message_file_get_all_headers (message->message_file); } const char * diff --git a/notmuch-private.h b/notmuch-private.h index da36c300..39d5e757 100644 --- a/notmuch-private.h +++ b/notmuch-private.h @@ -280,6 +280,17 @@ const char * notmuch_message_file_get_header (notmuch_message_file_t *message, const char *header); +/* Get the entire set of headers from an email message as a string. + * + * 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 in the case of any error. + */ +const char * +notmuch_message_file_get_all_headers (notmuch_message_file_t *message); + /* date.c */ /* Parse an RFC 8222 date string to a time_t value. diff --git a/notmuch.c b/notmuch.c index 757f09d7..66b615b2 100644 --- a/notmuch.c +++ b/notmuch.c @@ -800,8 +800,69 @@ search_command (int argc, char *argv[]) static int show_command (unused (int argc), unused (char *argv[])) { - fprintf (stderr, "Error: show is not implemented yet.\n"); - return 1; + void *local = talloc_new (NULL); + char *query_string; + notmuch_database_t *notmuch = NULL; + notmuch_query_t *query = NULL; + notmuch_message_results_t *messages; + notmuch_message_t *message; + int ret = 0; + + if (argc != 1) { + fprintf (stderr, "Error: \"notmuch show\" requires exactly one thread-ID argument.\n"); + ret = 1; + goto DONE; + } + + notmuch = notmuch_database_open (NULL); + if (notmuch == NULL) { + ret = 1; + goto DONE; + } + + query_string = talloc_asprintf (local, "thread:%s", argv[0]); + if (query_string == NULL) { + fprintf (stderr, "Out of memory\n"); + ret = 1; + goto DONE; + } + + query = notmuch_query_create (notmuch, query_string); + if (query == NULL) { + fprintf (stderr, "Out of memory\n"); + ret = 1; + goto DONE; + } + + for (messages = notmuch_query_search_messages (query); + notmuch_message_results_has_more (messages); + notmuch_message_results_advance (messages)) + { + message = notmuch_message_results_get (messages); + + printf ("%%message{\n"); + + printf ("%%header{\n"); + + printf ("%s", notmuch_message_get_all_headers (message)); + + printf ("%%header}\n"); + printf ("%%message}\n"); + + notmuch_message_destroy (message); + } + + DONE: + if (local) + talloc_free (local); + + if (query) + notmuch_query_destroy (query); + + if (notmuch) + notmuch_database_close (notmuch); + + return ret; } static int diff --git a/notmuch.h b/notmuch.h index 165fe852..a17c7020 100644 --- a/notmuch.h +++ b/notmuch.h @@ -617,6 +617,35 @@ notmuch_message_get_thread_id (notmuch_message_t *message); const char * notmuch_message_get_filename (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 entire set of headers from an email message as a string. + * + * The value will be read from the actual message file, not from the + * notmuch database. + * + * 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 in the case of any error. + */ +const char * +notmuch_message_get_all_headers (notmuch_message_t *message); + /* Get the tags for 'message', returning a notmuch_tags_t object which * can be used to iterate over all tags. * diff --git a/query.cc b/query.cc index 5fac024e..170ab752 100644 --- a/query.cc +++ b/query.cc @@ -212,7 +212,7 @@ notmuch_query_search_threads (notmuch_query_t *query) thread = _notmuch_thread_create (query, query->notmuch, thread_id); - subject = _notmuch_message_get_subject (message); + subject = notmuch_message_get_header (message, "subject"); _notmuch_thread_set_subject (thread, subject);