mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-11-24 11:58:10 +01:00
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.
This commit is contained in:
parent
b39ebca8c9
commit
bf78a89196
6 changed files with 193 additions and 25 deletions
|
@ -39,6 +39,7 @@ struct _notmuch_message_file {
|
||||||
GHashTable *headers;
|
GHashTable *headers;
|
||||||
int broken_headers;
|
int broken_headers;
|
||||||
int good_headers;
|
int good_headers;
|
||||||
|
size_t header_size; /* Length of full message header in bytes. */
|
||||||
|
|
||||||
/* Parsing state */
|
/* Parsing state */
|
||||||
char *line;
|
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 *
|
const char *
|
||||||
notmuch_message_file_get_header (notmuch_message_file_t *message,
|
notmuch_message_file_get_header (notmuch_message_file_t *message,
|
||||||
const char *header_desired)
|
const char *header_desired)
|
||||||
|
@ -215,9 +219,13 @@ notmuch_message_file_get_header (notmuch_message_file_t *message,
|
||||||
|
|
||||||
message->parsing_started = 1;
|
message->parsing_started = 1;
|
||||||
|
|
||||||
|
if (header_desired == NULL)
|
||||||
|
contains = 0;
|
||||||
|
else
|
||||||
contains = g_hash_table_lookup_extended (message->headers,
|
contains = g_hash_table_lookup_extended (message->headers,
|
||||||
header_desired, NULL,
|
header_desired, NULL,
|
||||||
(gpointer *) &value);
|
(gpointer *) &value);
|
||||||
|
|
||||||
if (contains && value)
|
if (contains && value)
|
||||||
return value;
|
return value;
|
||||||
|
|
||||||
|
@ -225,7 +233,7 @@ notmuch_message_file_get_header (notmuch_message_file_t *message,
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
#define NEXT_HEADER_LINE(closure) \
|
#define NEXT_HEADER_LINE(closure) \
|
||||||
do { \
|
while (1) { \
|
||||||
ssize_t bytes_read = getline (&message->line, \
|
ssize_t bytes_read = getline (&message->line, \
|
||||||
&message->line_size, \
|
&message->line_size, \
|
||||||
message->file); \
|
message->file); \
|
||||||
|
@ -242,7 +250,11 @@ notmuch_message_file_get_header (notmuch_message_file_t *message,
|
||||||
{ \
|
{ \
|
||||||
copy_header_unfolding ((closure), message->line); \
|
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)
|
if (message->line == NULL)
|
||||||
NEXT_HEADER_LINE (NULL);
|
NEXT_HEADER_LINE (NULL);
|
||||||
|
@ -268,6 +280,8 @@ notmuch_message_file_get_header (notmuch_message_file_t *message,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message->header_size += strlen (message->line);
|
||||||
|
|
||||||
message->good_headers++;
|
message->good_headers++;
|
||||||
|
|
||||||
header = xstrndup (message->line, colon - message->line);
|
header = xstrndup (message->line, colon - message->line);
|
||||||
|
@ -290,6 +304,9 @@ notmuch_message_file_get_header (notmuch_message_file_t *message,
|
||||||
|
|
||||||
NEXT_HEADER_LINE (&message->value);
|
NEXT_HEADER_LINE (&message->value);
|
||||||
|
|
||||||
|
if (header_desired == 0)
|
||||||
|
match = 0;
|
||||||
|
else
|
||||||
match = (strcasecmp (header, header_desired) == 0);
|
match = (strcasecmp (header, header_desired) == 0);
|
||||||
|
|
||||||
value = xstrdup (message->value.str);
|
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
|
/* 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
|
* for. It's probably just not there, but let's check that we
|
||||||
* didn't make a mistake preventing us from seeing it. */
|
* 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,
|
! g_hash_table_lookup_extended (message->headers,
|
||||||
header_desired, NULL, NULL))
|
header_desired, NULL, NULL))
|
||||||
{
|
{
|
||||||
|
@ -325,3 +342,40 @@ notmuch_message_file_get_header (notmuch_message_file_t *message,
|
||||||
|
|
||||||
return NULL;
|
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;
|
||||||
|
}
|
||||||
|
|
35
message.cc
35
message.cc
|
@ -232,26 +232,39 @@ notmuch_message_get_message_id (notmuch_message_t *message)
|
||||||
return message->message_id;
|
return message->message_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
static void
|
||||||
_notmuch_message_get_subject (notmuch_message_t *message)
|
_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;
|
||||||
|
|
||||||
|
if (message->message_file)
|
||||||
|
return;
|
||||||
|
|
||||||
filename = notmuch_message_get_filename (message);
|
filename = notmuch_message_get_filename (message);
|
||||||
if (unlikely (filename == NULL))
|
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 NULL;
|
||||||
|
|
||||||
message_file = _notmuch_message_file_open_ctx (message, filename);
|
return notmuch_message_file_get_header (message->message_file, header);
|
||||||
if (unlikely (message_file == NULL))
|
}
|
||||||
|
|
||||||
|
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 NULL;
|
||||||
|
|
||||||
message->message_file = message_file;
|
return notmuch_message_file_get_all_headers (message->message_file);
|
||||||
}
|
|
||||||
|
|
||||||
return notmuch_message_file_get_header (message->message_file,
|
|
||||||
"subject");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
|
|
|
@ -280,6 +280,17 @@ const char *
|
||||||
notmuch_message_file_get_header (notmuch_message_file_t *message,
|
notmuch_message_file_get_header (notmuch_message_file_t *message,
|
||||||
const char *header);
|
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 */
|
/* date.c */
|
||||||
|
|
||||||
/* Parse an RFC 8222 date string to a time_t value.
|
/* Parse an RFC 8222 date string to a time_t value.
|
||||||
|
|
65
notmuch.c
65
notmuch.c
|
@ -800,8 +800,69 @@ search_command (int argc, char *argv[])
|
||||||
static int
|
static int
|
||||||
show_command (unused (int argc), unused (char *argv[]))
|
show_command (unused (int argc), unused (char *argv[]))
|
||||||
{
|
{
|
||||||
fprintf (stderr, "Error: show is not implemented yet.\n");
|
void *local = talloc_new (NULL);
|
||||||
return 1;
|
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
|
static int
|
||||||
|
|
29
notmuch.h
29
notmuch.h
|
@ -617,6 +617,35 @@ notmuch_message_get_thread_id (notmuch_message_t *message);
|
||||||
const char *
|
const char *
|
||||||
notmuch_message_get_filename (notmuch_message_t *message);
|
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
|
/* Get the tags for 'message', returning a notmuch_tags_t object which
|
||||||
* can be used to iterate over all tags.
|
* can be used to iterate over all tags.
|
||||||
*
|
*
|
||||||
|
|
2
query.cc
2
query.cc
|
@ -212,7 +212,7 @@ notmuch_query_search_threads (notmuch_query_t *query)
|
||||||
thread = _notmuch_thread_create (query, query->notmuch,
|
thread = _notmuch_thread_create (query, query->notmuch,
|
||||||
thread_id);
|
thread_id);
|
||||||
|
|
||||||
subject = _notmuch_message_get_subject (message);
|
subject = notmuch_message_get_header (message, "subject");
|
||||||
|
|
||||||
_notmuch_thread_set_subject (thread, subject);
|
_notmuch_thread_set_subject (thread, subject);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue