notmuch show: Initial implementation (headers only)
authorCarl Worth <cworth@cworth.org>
Thu, 29 Oct 2009 15:51:12 +0000 (08:51 -0700)
committerCarl Worth <cworth@cworth.org>
Thu, 29 Oct 2009 15:51:12 +0000 (08:51 -0700)
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
message.cc
notmuch-private.h
notmuch.c
notmuch.h
query.cc

index 18275fbf853fc89ecb4c5b703c34a5a9b084e995..e8e2843e225be9b3f45a0dcd823d72446e6635a8 100644 (file)
@@ -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;
+}
index 169b20c193695c16744744233aaac041292e2dfb..dfd55d596e21af0cac4d79416f9029644a5f4d75 100644 (file)
@@ -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);
+}
+
+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_header (message->message_file,
-                                           "subject");
+    return notmuch_message_file_get_all_headers (message->message_file);
 }
 
 const char *
index da36c30098ed0fce9f8b2f9017ff1e81dd165d81..39d5e7579e7c88e2baab30d20044409b845678f1 100644 (file)
@@ -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.
index 757f09d797b6df0f51bb6b6ef2cc51315884f93e..66b615b2285605cd712852e3eb2060365028d43d 100644 (file)
--- 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
index 165fe85224e0c7b9dd48e176541fda7f74a4cbf7..a17c7020177a5cff0844db28abe30b952e919542 100644 (file)
--- 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.
  *
index 5fac024e7a23be7f17d2d5eed4852b48445e27b4..170ab7523957de7ba317ea86b311281e4be76981 100644 (file)
--- 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);