+ notmuch_database_t *notmuch;
+ const char *mail_directory;
+ add_files_state_t add_files_state;
+ double elapsed;
+ struct timeval tv_now;
+ int ret = 0;
+
+ notmuch = notmuch_database_open (NULL);
+ if (notmuch == NULL) {
+ ret = 1;
+ goto DONE;
+ }
+
+ mail_directory = notmuch_database_get_path (notmuch);
+
+ add_files_state.ignore_read_only_directories = TRUE;
+ add_files_state.saw_read_only_directory = FALSE;
+ add_files_state.total_files = 0;
+ add_files_state.processed_files = 0;
+ add_files_state.added_messages = 0;
+ add_files_state.callback = tag_inbox_and_unread;
+ gettimeofday (&add_files_state.tv_start, NULL);
+
+ ret = add_files (notmuch, mail_directory, &add_files_state);
+
+ gettimeofday (&tv_now, NULL);
+ elapsed = tv_elapsed (add_files_state.tv_start,
+ tv_now);
+ if (add_files_state.processed_files) {
+ printf ("Processed %d %s in ", add_files_state.processed_files,
+ add_files_state.processed_files == 1 ?
+ "file" : "total files");
+ print_formatted_seconds (elapsed);
+ if (elapsed > 1) {
+ printf (" (%d files/sec.). \n",
+ (int) (add_files_state.processed_files / elapsed));
+ } else {
+ printf (". \n");
+ }
+ }
+ if (add_files_state.added_messages) {
+ printf ("Added %d new %s to the database (not much, really).\n",
+ add_files_state.added_messages,
+ add_files_state.added_messages == 1 ?
+ "message" : "messages");
+ } else {
+ printf ("No new mail---and that's not much.\n");
+ }
+
+ if (elapsed > 1 && ! add_files_state.saw_read_only_directory) {
+ printf ("\nTip: If you have any sub-directories that are archives (that is,\n"
+ "they will never receive new mail), marking these directores as\n"
+ "read-only (chmod u-w /path/to/dir) will make \"notmuch new\"\n"
+ "much more efficient (it won't even look in those directories).\n");
+ }
+
+ if (ret) {
+ printf ("\nNote: At least one error was encountered: %s\n",
+ notmuch_status_to_string (ret));
+ }
+
+ DONE:
+ if (notmuch)
+ notmuch_database_close (notmuch);
+
+ return ret;
+}
+
+/* Construct a single query string from the passed arguments, using
+ * 'ctx' as the talloc owner for all allocations.
+ *
+ * Currently, the arguments are just connected with space characters,
+ * but we might do more processing in the future, (such as inserting
+ * any AND operators needed to work around Xapian QueryParser bugs).
+ *
+ * This function returns NULL in case of insufficient memory.
+ */
+static char *
+query_string_from_args (void *ctx, int argc, char *argv[])
+{
+ char *query_string;
+ int i;
+
+ query_string = talloc_strdup (ctx, "");
+ if (query_string == NULL)
+ return NULL;
+
+ for (i = 0; i < argc; i++) {
+ if (i != 0) {
+ query_string = talloc_strdup_append (query_string, " ");
+ if (query_string == NULL)
+ return NULL;
+ }
+
+ query_string = talloc_strdup_append (query_string, argv[i]);
+ if (query_string == NULL)
+ return NULL;
+ }
+
+ return query_string;
+}
+
+/* Format a nice representation of 'time' relative to the current time.
+ *
+ * Examples include:
+ *
+ * 5 minutes ago (For times less than 60 minutes ago)
+ * 12:30 (For times >60 minutes but still today)
+ * Yesterday
+ * Monday (Before yesterday but fewer than 7 days ago)
+ * Oct. 12 (Between 7 and 180 days ago (about 6 months))
+ * 2008-06-30 (More than 180 days ago)
+ */
+#define MINUTE (60)
+#define HOUR (60 * MINUTE)
+#define DAY (24 * HOUR)
+#define RELATIVE_DATE_MAX 20
+static const char *
+_format_relative_date (void *ctx, time_t then)
+{
+ struct tm tm_now, tm_then;
+ time_t now = time(NULL);
+ time_t delta;
+ char *result;
+
+ localtime_r (&now, &tm_now);
+ localtime_r (&then, &tm_then);
+
+ result = talloc_zero_size (ctx, RELATIVE_DATE_MAX);
+ if (result == NULL)
+ return "when?";
+
+ if (then > now)
+ return "the future";
+
+ delta = now - then;
+
+ if (delta > 180 * DAY) {
+ strftime (result, RELATIVE_DATE_MAX,
+ "%F", &tm_then); /* 2008-06-30 */
+ return result;
+ }
+
+ if (delta < 3600) {
+ snprintf (result, RELATIVE_DATE_MAX,
+ "%d minutes ago", (int) (delta / 60));
+ return result;
+ }
+
+ if (delta <= 7 * DAY) {
+ if (tm_then.tm_wday == tm_now.tm_wday &&
+ delta < DAY)
+ {
+ strftime (result, RELATIVE_DATE_MAX,
+ "%R", &tm_then); /* 12:30 */
+ return result;
+ } else if ((tm_now.tm_wday + 7 - tm_then.tm_wday) % 7 == 1) {
+ return "Yesterday";
+ } else {
+ if (tm_then.tm_wday != tm_now.tm_wday) {
+ strftime (result, RELATIVE_DATE_MAX,
+ "%A", &tm_then); /* Monday */
+ return result;
+ }
+ }
+ }
+
+ strftime (result, RELATIVE_DATE_MAX,
+ "%b %d", &tm_then); /* Oct. 12 */
+ return result;
+}
+#undef MINUTE
+#undef HOUR
+#undef DAY
+
+static int
+search_command (void *ctx, int argc, char *argv[])
+{
+ void *local = talloc_new (ctx);
+ notmuch_database_t *notmuch = NULL;
+ notmuch_query_t *query;
+ notmuch_threads_t *threads;
+ notmuch_thread_t *thread;
+ notmuch_tags_t *tags;
+ char *query_str;
+ const char *relative_date;
+ time_t date;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+ notmuch = notmuch_database_open (NULL);
+ if (notmuch == NULL) {
+ ret = 1;
+ goto DONE;
+ }
+
+ query_str = query_string_from_args (local, argc, argv);
+
+ query = notmuch_query_create (notmuch, query_str);
+ if (query == NULL) {
+ fprintf (stderr, "Out of memory\n");
+ ret = 1;
+ goto DONE;
+ }
+
+ for (threads = notmuch_query_search_threads (query);
+ notmuch_threads_has_more (threads);
+ notmuch_threads_advance (threads))
+ {
+ int first = 1;
+
+ thread = notmuch_threads_get (threads);
+
+ date = notmuch_thread_get_oldest_date (thread);
+ relative_date = _format_relative_date (local, date);
+
+ printf ("%s (%s) %s",
+ notmuch_thread_get_thread_id (thread),
+ relative_date,
+ notmuch_thread_get_subject (thread));
+
+ printf (" (");
+ for (tags = notmuch_thread_get_tags (thread);
+ notmuch_tags_has_more (tags);
+ notmuch_tags_advance (tags))
+ {
+ if (! first)
+ printf (" ");
+ printf ("%s", notmuch_tags_get (tags));
+ first = 0;
+ }
+ printf (")\n");
+
+ notmuch_thread_destroy (thread);
+ }
+
+ notmuch_query_destroy (query);
+
+ DONE:
+ if (notmuch)
+ notmuch_database_close (notmuch);
+ talloc_free (local);
+
+ return ret;
+}
+
+/* Get a nice, single-line summary of message. */
+static const char *
+_get_one_line_summary (void *ctx, notmuch_message_t *message)
+{
+ const char *from;
+ time_t date;
+ const char *relative_date;
+ const char *subject;
+
+ from = notmuch_message_get_header (message, "from");
+
+ date = notmuch_message_get_date (message);
+ relative_date = _format_relative_date (ctx, date);
+
+ subject = notmuch_message_get_header (message, "subject");
+
+ return talloc_asprintf (ctx, "%s (%s) %s",
+ from, relative_date, subject);
+}
+
+static void
+show_message_part (GMimeObject *part)
+{
+ GMimeStream *stream;
+ GMimeDataWrapper *wrapper;
+ GMimeContentDisposition *disposition;
+ GMimeContentType *content_type;
+
+ if (GMIME_IS_MULTIPART (part)) {
+ GMimeMultipart *multipart = GMIME_MULTIPART (part);
+ int i;
+
+ for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
+ if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
+ /* Don't index the signature. */
+ if (i == 1)
+ continue;
+ if (i > 1)
+ fprintf (stderr, "Warning: Unexpected extra parts of mutlipart/signed. Continuing.\n");
+ }
+ show_message_part (g_mime_multipart_get_part (multipart, i));
+ }
+ return;
+ }
+
+ if (GMIME_IS_MESSAGE_PART (part)) {
+ GMimeMessage *mime_message;
+
+ mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
+
+ show_message_part (g_mime_message_get_mime_part (mime_message));
+
+ return;
+ }
+
+ if (! (GMIME_IS_PART (part))) {
+ fprintf (stderr, "Warning: Not displaying unknown mime part: %s.\n",
+ g_type_name (G_OBJECT_TYPE (part)));
+ return;
+ }
+
+ disposition = g_mime_object_get_content_disposition (part);
+ if (disposition &&
+ strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
+ {
+ const char *filename = g_mime_part_get_filename (GMIME_PART (part));
+ content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
+
+ printf ("%%attachment{ Content-type: %s\n",
+ g_mime_content_type_to_string (content_type));
+ printf ("%s\n", filename);
+ printf ("%%attachment}\n");
+
+ return;
+ }
+
+ /* Stream the MIME part out to stdout. */
+ content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
+
+ printf ("%%part{ Content-type: %s\n",
+ g_mime_content_type_to_string (content_type));
+
+ stream = g_mime_stream_file_new (stdout);
+ g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
+
+ wrapper = g_mime_part_get_content_object (GMIME_PART (part));
+ if (wrapper)
+ g_mime_data_wrapper_write_to_stream (wrapper, stream);
+
+ printf ("%%part}\n");
+
+ g_object_unref (stream);
+}
+
+static notmuch_status_t
+show_message_body (const char *filename)
+{
+ GMimeStream *stream = NULL;
+ GMimeParser *parser = NULL;
+ GMimeMessage *mime_message = NULL;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+ static int initialized = 0;
+ FILE *file = NULL;
+
+ if (! initialized) {
+ g_mime_init (0);
+ initialized = 1;
+ }
+
+ 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_message_part (g_mime_message_get_mime_part (mime_message));
+
+ 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;