From 2f8871df6ea3c0b44f85a0fc1b4f58a6b70b0a0e Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Mon, 23 May 2011 15:31:32 -0700 Subject: [PATCH] New part output handling as option to notmuch-show. Outputting of single MIME parts is moved to an option of notmuch show, instead of being handled in it's own sub-command. The recent rework of multipart mime allowed for this change but consolidating part handling into a single recursive function (show_message_part) that includes formatting. This allows for far simpler handling single output of a single part, including formatting. --- notmuch-client.h | 7 +-- notmuch-reply.c | 4 +- notmuch-show.c | 152 +++++++++++++++++++---------------------------- notmuch.1 | 17 +++++- notmuch.c | 20 +++---- show-message.c | 152 ++++++++++++++--------------------------------- 6 files changed, 134 insertions(+), 218 deletions(-) diff --git a/notmuch-client.h b/notmuch-client.h index 47a4e570..7221c681 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -78,6 +78,7 @@ typedef struct notmuch_show_format { typedef struct notmuch_show_params { int entire_thread; int raw; + int part; } notmuch_show_params_t; /* There's no point in continuing when we've detected that we've done @@ -139,9 +140,6 @@ notmuch_search_tags_command (void *ctx, int argc, char *argv[]); int notmuch_cat_command (void *ctx, int argc, char *argv[]); -int -notmuch_part_command (void *ctx, int argc, char *argv[]); - int notmuch_config_command (void *ctx, int argc, char *argv[]); @@ -159,7 +157,8 @@ query_string_from_args (void *ctx, int argc, char *argv[]); notmuch_status_t show_message_body (const char *filename, - const notmuch_show_format_t *format); + const notmuch_show_format_t *format, + notmuch_show_params_t *params); notmuch_status_t show_one_part (const char *filename, int part); diff --git a/notmuch-reply.c b/notmuch-reply.c index b5ca19c4..ab156508 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -461,6 +461,8 @@ notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_ const char *subject, *from_addr = NULL; const char *in_reply_to, *orig_references, *references; const notmuch_show_format_t *format = &format_reply; + notmuch_show_params_t params; + params.part = -1; for (messages = notmuch_query_search_messages (query); notmuch_messages_valid (messages); @@ -520,7 +522,7 @@ notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_ notmuch_message_get_header (message, "from")); show_message_body (notmuch_message_get_filename (message), - format); + format, ¶ms); notmuch_message_destroy (message); } diff --git a/notmuch-show.c b/notmuch-show.c index 27587f87..e128be54 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -82,6 +82,19 @@ static const notmuch_show_format_t format_mbox = { "" }; +static void +format_part_raw (GMimeObject *part, + unused (int *part_count)); + +static const notmuch_show_format_t format_raw = { + "", + "", NULL, + "", NULL, "", + "", format_part_raw, NULL, "", "", + "", "", + "" +}; + static const char * _get_tags_as_string (const void *ctx, notmuch_message_t *message) { @@ -329,6 +342,10 @@ show_part_content (GMimeObject *part, GMimeStream *stream_out) GMimeDataWrapper *wrapper; const char *charset; + /* do nothing if this is a multipart */ + if (GMIME_IS_MULTIPART (part) || GMIME_IS_MESSAGE_PART (part)) + return; + charset = g_mime_object_get_content_type_parameter (part, "charset"); if (stream_out) { @@ -489,31 +506,46 @@ format_part_end_json (GMimeObject *part) printf ("}"); } +static void +format_part_raw (GMimeObject *part, unused (int *part_count)) +{ + GMimeStream *stream_stdout = g_mime_stream_file_new (stdout); + g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); + show_part_content (part, stream_stdout); + g_object_unref(stream_stdout); +} + static void show_message (void *ctx, const notmuch_show_format_t *format, notmuch_message_t *message, - int indent) + int indent, + notmuch_show_params_t *params) { - fputs (format->message_start, stdout); - if (format->message) - format->message(ctx, message, indent); + if (params->part <= 0) { + fputs (format->message_start, stdout); + if (format->message) + format->message(ctx, message, indent); - fputs (format->header_start, stdout); - if (format->header) - format->header(ctx, message); - fputs (format->header_end, stdout); + fputs (format->header_start, stdout); + if (format->header) + format->header(ctx, message); + fputs (format->header_end, stdout); + + fputs (format->body_start, stdout); + } - fputs (format->body_start, stdout); if (format->part) show_message_body (notmuch_message_get_filename (message), - format); - fputs (format->body_end, stdout); + format, params); - fputs (format->message_end, stdout); + if (params->part <= 0) { + fputs (format->body_end, stdout); + + fputs (format->message_end, stdout); + } } - static void show_messages (void *ctx, const notmuch_show_format_t *format, @@ -545,7 +577,7 @@ show_messages (void *ctx, next_indent = indent; if (match || params->entire_thread) { - show_message (ctx, format, message, indent); + show_message (ctx, format, message, indent, params); next_indent = indent + 1; fputs (format->message_set_sep, stdout); @@ -588,8 +620,10 @@ do_show_single (void *ctx, return 1; } + notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, 1); + /* Special case for --format=raw of full single message, just cat out file */ - if (params->raw) { + if (params->raw && 0 == params->part) { const char *filename; FILE *file; @@ -615,6 +649,10 @@ do_show_single (void *ctx, fclose (file); + } else { + + show_message (ctx, format, message, 0, params); + } return 0; @@ -675,6 +713,7 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) params.entire_thread = 0; params.raw = 0; + params.part = -1; for (i = 0; i < argc && argv[i][0] == '-'; i++) { if (strcmp (argv[i], "--") == 0) { @@ -691,11 +730,14 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) } else if (strcmp (opt, "mbox") == 0) { format = &format_mbox; } else if (strcmp (opt, "raw") == 0) { + format = &format_raw; params.raw = 1; } else { fprintf (stderr, "Invalid value for --format: %s\n", opt); return 1; } + } else if (STRNCMP_LITERAL (argv[i], "--part=") == 0) { + params.part = atoi(argv[i] + sizeof ("--part=") - 1); } else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) { params.entire_thread = 1; } else { @@ -735,7 +777,10 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) /* If --format=raw specified without specifying part, we can only * output single message, so set part=0 */ - if (params.raw) + if (params.raw && params.part < 0) + params.part = 0; + + if (params.part >= 0) return do_show_single (ctx, query, format, ¶ms); else return do_show (ctx, query, format, ¶ms); @@ -745,78 +790,3 @@ notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[])) return 0; } - -int -notmuch_part_command (void *ctx, unused (int argc), unused (char *argv[])) -{ - notmuch_config_t *config; - notmuch_database_t *notmuch; - notmuch_query_t *query; - notmuch_messages_t *messages; - notmuch_message_t *message; - char *query_string; - int i; - int part = 0; - - for (i = 0; i < argc && argv[i][0] == '-'; i++) { - if (strcmp (argv[i], "--") == 0) { - i++; - break; - } - if (STRNCMP_LITERAL (argv[i], "--part=") == 0) { - part = atoi(argv[i] + sizeof ("--part=") - 1); - } else { - fprintf (stderr, "Unrecognized option: %s\n", argv[i]); - return 1; - } - } - - argc -= i; - argv += i; - - config = notmuch_config_open (ctx, NULL, NULL); - if (config == NULL) - return 1; - - query_string = query_string_from_args (ctx, argc, argv); - if (query_string == NULL) { - fprintf (stderr, "Out of memory\n"); - return 1; - } - - if (*query_string == '\0') { - fprintf (stderr, "Error: notmuch part requires at least one search term.\n"); - return 1; - } - - notmuch = notmuch_database_open (notmuch_config_get_database_path (config), - NOTMUCH_DATABASE_MODE_READ_ONLY); - if (notmuch == NULL) - return 1; - - query = notmuch_query_create (notmuch, query_string); - if (query == NULL) { - fprintf (stderr, "Out of memory\n"); - return 1; - } - - if (notmuch_query_count_messages (query) != 1) { - fprintf (stderr, "Error: search term did not match precisely one message.\n"); - return 1; - } - - messages = notmuch_query_search_messages (query); - message = notmuch_messages_get (messages); - - if (message == NULL) { - fprintf (stderr, "Error: cannot find matching message.\n"); - return 1; - } - - show_one_part (notmuch_message_get_filename (message), part); - - notmuch_query_destroy (query); - notmuch_database_close (notmuch); - - return 0; -} diff --git a/notmuch.1 b/notmuch.1 index 2912fcfd..76222981 100644 --- a/notmuch.1 +++ b/notmuch.1 @@ -297,8 +297,20 @@ The original, raw content of the email message is displayed. Consumers of this format should expect to implement MIME decoding and similar functions. This format must only be used with search terms matching a single message. - .RE +.RE + +.RS 4 +.TP 4 +.B \-\-part=N + +Output the single decoded MIME part N of a single message. The search +terms must match only a single message. Message parts are numbered in +a depth-first walk of the message MIME structure, and are identified +in the 'json' or +'text' output formats. +.RE + A common use of .B notmuch show is to display a single thread of email messages. For this, use a @@ -311,7 +323,8 @@ See the .B "SEARCH SYNTAX" section below for details of the supported syntax for . .RE -.TP +.RS 4 +.TP 4 .BR count " ..." Count messages matching the search terms. diff --git a/notmuch.c b/notmuch.c index 098f7335..2e98f25d 100644 --- a/notmuch.c +++ b/notmuch.c @@ -269,6 +269,15 @@ command_t commands[] = { "\t\tdecoding and similar functions. This format must only\n" "\t\tbe used with search terms matching a single message.\n" "\n" + "\t--part=N\n" + "\n" + "\t\tOutput the single decoded MIME part N of a single message.\n" + "\t\tThe search terms must match only a single message.\n" + "\t\tMessage parts are numbered in a depth-first walk of the\n" + "\t\tmessage MIME structure, and are identified in the 'json' or\n" + "\t\t'text' output formats.\n" + "\n" + "\n" "\tA common use of \"notmuch show\" is to display a single\n" "\tthread of email messages. For this, use a search term of\n" "\t\"thread:\" as can be seen in the first column\n" @@ -358,17 +367,6 @@ command_t commands[] = { "\tcontain tags only from messages that match the search-term(s).\n" "\n" "\tIn both cases the list will be alphabetically sorted." }, - { "part", notmuch_part_command, - "--part= ", - "Output a single MIME part of a message.", - "\tA single decoded MIME part, with no encoding or framing,\n" - "\tis output to stdout. The search terms must match only a single\n" - "\tmessage, otherwise this command will fail.\n" - "\n" - "\tThe part number should match the part \"id\" field output\n" - "\tby the \"--format=json\" option of \"notmuch show\". If the\n" - "\tmessage specified by the search terms does not include a\n" - "\tpart with the specified \"id\" there will be no output." }, { "config", notmuch_config_command, "[get|set]
. [value ...]", "Get or set settings in the notmuch configuration file.", diff --git a/show-message.c b/show-message.c index 4ccd4490..32bb860f 100644 --- a/show-message.c +++ b/show-message.c @@ -22,13 +22,20 @@ #include "notmuch-client.h" +typedef struct show_message_state { + int part_count; + int in_zone; +} show_message_state_t; + static void show_message_part (GMimeObject *part, - int *part_count, + show_message_state_t *state, const notmuch_show_format_t *format, + notmuch_show_params_t *params, int first) { - *part_count += 1; + int selected; + state->part_count += 1; if (! (GMIME_IS_PART (part) || GMIME_IS_MULTIPART (part) || GMIME_IS_MESSAGE_PART (part))) { fprintf (stderr, "Warning: Not displaying unknown mime part: %s.\n", @@ -36,43 +43,63 @@ show_message_part (GMimeObject *part, return; } - if (!first) - fputs (format->part_sep, stdout); + selected = (params->part <= 0 || state->part_count == params->part); - format->part (part, part_count); + if (selected || state->in_zone) { + if (!first && (params->part <= 0 || state->in_zone)) + fputs (format->part_sep, stdout); + + format->part (part, &(state->part_count)); + } if (GMIME_IS_MULTIPART (part)) { GMimeMultipart *multipart = GMIME_MULTIPART (part); int i; + if (selected) + state->in_zone = 1; + for (i = 0; i < g_mime_multipart_get_count (multipart); i++) { show_message_part (g_mime_multipart_get_part (multipart, i), - part_count, format, i == 0); + state, format, params, i == 0); } - } else if (GMIME_IS_MESSAGE_PART (part)) { - GMimeMessage *mime_message; + if (selected) + state->in_zone = 0; - mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part)); + } else if (GMIME_IS_MESSAGE_PART (part)) { + GMimeMessage *mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part)); + + if (selected) + state->in_zone = 1; show_message_part (g_mime_message_get_mime_part (mime_message), - part_count, format, first); + state, format, params, TRUE); + + if (selected) + state->in_zone = 0; } - if (format->part_end) - format->part_end (part); + if (selected || state->in_zone) { + if (format->part_end) + format->part_end (part); + } } notmuch_status_t show_message_body (const char *filename, - const notmuch_show_format_t *format) + const notmuch_show_format_t *format, + notmuch_show_params_t *params) { GMimeStream *stream = NULL; GMimeParser *parser = NULL; GMimeMessage *mime_message = NULL; notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS; FILE *file = NULL; - int part_count = 0; + show_message_state_t state; + + state.part_count = 0; + state.in_zone = 0; file = fopen (filename, "r"); if (! file) { @@ -89,8 +116,9 @@ show_message_body (const char *filename, mime_message = g_mime_parser_construct_message (parser); show_message_part (g_mime_message_get_mime_part (mime_message), - &part_count, + &state, format, + params, TRUE); DONE: @@ -108,97 +136,3 @@ show_message_body (const char *filename, return ret; } - -static void -show_one_part_output (GMimeObject *part) -{ - GMimeStream *stream_filter = NULL; - GMimeDataWrapper *wrapper; - GMimeStream *stream_stdout = g_mime_stream_file_new (stdout); - - stream_filter = g_mime_stream_filter_new(stream_stdout); - wrapper = g_mime_part_get_content_object (GMIME_PART (part)); - if (wrapper && stream_filter) - g_mime_data_wrapper_write_to_stream (wrapper, stream_filter); - if (stream_filter) - g_object_unref(stream_filter); -} - -static void -show_one_part_worker (GMimeObject *part, int *part_count, int desired_part) -{ - if (GMIME_IS_MULTIPART (part)) { - GMimeMultipart *multipart = GMIME_MULTIPART (part); - int i; - - *part_count = *part_count + 1; - - for (i = 0; i < g_mime_multipart_get_count (multipart); i++) { - show_one_part_worker (g_mime_multipart_get_part (multipart, i), - part_count, desired_part); - } - return; - } - - if (GMIME_IS_MESSAGE_PART (part)) { - GMimeMessage *mime_message; - - mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part)); - - show_one_part_worker (g_mime_message_get_mime_part (mime_message), - part_count, desired_part); - - return; - } - - if (! (GMIME_IS_PART (part))) - return; - - *part_count = *part_count + 1; - - if (*part_count == desired_part) - show_one_part_output (part); -} - -notmuch_status_t -show_one_part (const char *filename, int part) -{ - GMimeStream *stream = NULL; - GMimeParser *parser = NULL; - GMimeMessage *mime_message = NULL; - notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS; - FILE *file = NULL; - int part_count = 0; - - file = fopen (filename, "r"); - if (! file) { - fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno)); - ret = NOTMUCH_STATUS_FILE_ERROR; - goto DONE; - } - - stream = g_mime_stream_file_new (file); - g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE); - - parser = g_mime_parser_new_with_stream (stream); - - mime_message = g_mime_parser_construct_message (parser); - - show_one_part_worker (g_mime_message_get_mime_part (mime_message), - &part_count, part); - - DONE: - if (mime_message) - g_object_unref (mime_message); - - if (parser) - g_object_unref (parser); - - if (stream) - g_object_unref (stream); - - if (file) - fclose (file); - - return ret; -}