1 /* notmuch - Not much of an email program, (just index and search)
3 * Copyright © 2009 Carl Worth
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see http://www.gnu.org/licenses/ .
18 * Author: Carl Worth <cworth@cworth.org>
22 #define _GNU_SOURCE /* for getline */
26 #include <gmime/gmime.h>
30 /* This is separate from notmuch-private.h because we're trying to
31 * keep notmuch.c from looking into any internals, (which helps us
32 * develop notmuch.h into a plausible library interface).
47 #define unused(x) x __attribute__ ((unused))
49 /* There's no point in continuing when we've detected that we've done
50 * something wrong internally (as opposed to the user passing in a
53 * Note that __location__ comes from talloc.h.
55 #define INTERNAL_ERROR(format, ...) \
58 "Internal error: " format " (%s)\n", \
59 ##__VA_ARGS__, __location__); \
63 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
65 typedef int (*command_function_t) (void *ctx, int argc, char *argv[]);
67 typedef struct command {
69 command_function_t function;
71 const char *documentation;
74 typedef void (*add_files_callback_t) (notmuch_message_t *message);
77 int ignore_read_only_directories;
78 int saw_read_only_directory;
83 struct timeval tv_start;
85 add_files_callback_t callback;
89 chomp_newline (char *str)
91 if (str && str[strlen(str)-1] == '\n')
92 str[strlen(str)-1] = '\0';
95 /* Compute the number of seconds elapsed from start to end. */
97 tv_elapsed (struct timeval start, struct timeval end)
99 return ((end.tv_sec - start.tv_sec) +
100 (end.tv_usec - start.tv_usec) / 1e6);
104 print_formatted_seconds (double seconds)
110 printf ("almost no time");
114 if (seconds > 3600) {
115 hours = (int) seconds / 3600;
116 printf ("%dh ", hours);
117 seconds -= hours * 3600;
121 minutes = (int) seconds / 60;
122 printf ("%dm ", minutes);
123 seconds -= minutes * 60;
126 printf ("%ds", (int) seconds);
129 static volatile sig_atomic_t do_add_files_print_progress = 0;
132 handle_sigalrm (unused (int signal))
134 do_add_files_print_progress = 1;
138 add_files_print_progress (add_files_state_t *state)
140 struct timeval tv_now;
141 double elapsed_overall, rate_overall;
143 gettimeofday (&tv_now, NULL);
145 elapsed_overall = tv_elapsed (state->tv_start, tv_now);
146 rate_overall = (state->processed_files) / elapsed_overall;
148 printf ("Processed %d", state->processed_files);
150 if (state->total_files) {
151 printf (" of %d files (", state->total_files);
152 print_formatted_seconds ((state->total_files - state->processed_files) /
154 printf (" remaining). \r");
156 printf (" files (%d files/sec.) \r", (int) rate_overall);
162 /* Examine 'path' recursively as follows:
164 * o Ask the filesystem for the mtime of 'path' (path_mtime)
166 * o Ask the database for its timestamp of 'path' (path_dbtime)
168 * o If 'path_mtime' > 'path_dbtime'
170 * o For each regular file in 'path' with mtime newer than the
171 * 'path_dbtime' call add_message to add the file to the
174 * o For each sub-directory of path, recursively call into this
177 * o Tell the database to update its time of 'path' to 'path_mtime'
179 * The 'struct stat *st' must point to a structure that has already
180 * been initialized for 'path' by calling stat().
182 static notmuch_status_t
183 add_files_recursive (notmuch_database_t *notmuch,
186 add_files_state_t *state)
189 struct dirent *e, *entry = NULL;
193 time_t path_mtime, path_dbtime;
194 notmuch_status_t status, ret = NOTMUCH_STATUS_SUCCESS;
195 notmuch_message_t *message = NULL, **closure;
197 /* If we're told to, we bail out on encountering a read-only
198 * directory, (with this being a clear clue from the user to
199 * Notmuch that new mail won't be arriving there and we need not
201 if (state->ignore_read_only_directories &&
202 (st->st_mode & S_IWUSR) == 0)
204 state->saw_read_only_directory = TRUE;
208 path_mtime = st->st_mtime;
210 path_dbtime = notmuch_database_get_timestamp (notmuch, path);
212 dir = opendir (path);
214 fprintf (stderr, "Error opening directory %s: %s\n",
215 path, strerror (errno));
216 ret = NOTMUCH_STATUS_FILE_ERROR;
220 entry_length = offsetof (struct dirent, d_name) +
221 pathconf (path, _PC_NAME_MAX) + 1;
222 entry = malloc (entry_length);
225 err = readdir_r (dir, entry, &e);
227 fprintf (stderr, "Error reading directory: %s\n",
229 ret = NOTMUCH_STATUS_FILE_ERROR;
236 /* If this directory hasn't been modified since the last
237 * add_files, then we only need to look further for
238 * sub-directories. */
239 if (path_mtime <= path_dbtime && entry->d_type != DT_DIR)
242 /* Ignore special directories to avoid infinite recursion.
243 * Also ignore the .notmuch directory.
245 /* XXX: Eventually we'll want more sophistication to let the
246 * user specify files to be ignored. */
247 if (strcmp (entry->d_name, ".") == 0 ||
248 strcmp (entry->d_name, "..") == 0 ||
249 strcmp (entry->d_name, ".notmuch") ==0)
254 next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
256 if (stat (next, st)) {
257 fprintf (stderr, "Error reading %s: %s\n",
258 next, strerror (errno));
259 ret = NOTMUCH_STATUS_FILE_ERROR;
263 if (S_ISREG (st->st_mode)) {
264 /* If the file hasn't been modified since the last
265 * add_files, then we need not look at it. */
266 if (st->st_mtime > path_dbtime) {
267 state->processed_files++;
274 status = notmuch_database_add_message (notmuch, next, closure);
277 case NOTMUCH_STATUS_SUCCESS:
278 state->added_messages++;
280 (state->callback) (message);
282 /* Non-fatal issues (go on to next file) */
283 case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
284 /* Stay silent on this one. */
286 case NOTMUCH_STATUS_FILE_NOT_EMAIL:
287 fprintf (stderr, "Note: Ignoring non-mail file: %s\n",
290 /* Fatal issues. Don't process anymore. */
291 case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
292 case NOTMUCH_STATUS_OUT_OF_MEMORY:
293 fprintf (stderr, "Error: %s. Halting processing.\n",
294 notmuch_status_to_string (status));
298 case NOTMUCH_STATUS_FILE_ERROR:
299 case NOTMUCH_STATUS_NULL_POINTER:
300 case NOTMUCH_STATUS_TAG_TOO_LONG:
301 case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
302 case NOTMUCH_STATUS_LAST_STATUS:
303 INTERNAL_ERROR ("add_message returned unexpected value: %d", status);
308 notmuch_message_destroy (message);
312 if (do_add_files_print_progress) {
313 do_add_files_print_progress = 0;
314 add_files_print_progress (state);
317 } else if (S_ISDIR (st->st_mode)) {
318 status = add_files_recursive (notmuch, next, st, state);
319 if (status && ret == NOTMUCH_STATUS_SUCCESS)
327 status = notmuch_database_set_timestamp (notmuch, path, path_mtime);
328 if (status && ret == NOTMUCH_STATUS_SUCCESS)
342 /* This is the top-level entry point for add_files. It does a couple
343 * of error checks, sets up the progress-printing timer and then calls
344 * into the recursive function. */
345 static notmuch_status_t
346 add_files (notmuch_database_t *notmuch,
348 add_files_state_t *state)
351 notmuch_status_t status;
352 struct sigaction action;
353 struct itimerval timerval;
355 if (stat (path, &st)) {
356 fprintf (stderr, "Error reading directory %s: %s\n",
357 path, strerror (errno));
358 return NOTMUCH_STATUS_FILE_ERROR;
361 if (! S_ISDIR (st.st_mode)) {
362 fprintf (stderr, "Error: %s is not a directory.\n", path);
363 return NOTMUCH_STATUS_FILE_ERROR;
366 /* Setup our handler for SIGALRM */
367 memset (&action, 0, sizeof (struct sigaction));
368 action.sa_handler = handle_sigalrm;
369 sigemptyset (&action.sa_mask);
370 action.sa_flags = SA_RESTART;
371 sigaction (SIGALRM, &action, NULL);
373 /* Then start a timer to send SIGALRM once per second. */
374 timerval.it_interval.tv_sec = 1;
375 timerval.it_interval.tv_usec = 0;
376 timerval.it_value.tv_sec = 1;
377 timerval.it_value.tv_usec = 0;
378 setitimer (ITIMER_REAL, &timerval, NULL);
380 status = add_files_recursive (notmuch, path, &st, state);
382 /* Now stop the timer. */
383 timerval.it_interval.tv_sec = 0;
384 timerval.it_interval.tv_usec = 0;
385 timerval.it_value.tv_sec = 0;
386 timerval.it_value.tv_usec = 0;
387 setitimer (ITIMER_REAL, &timerval, NULL);
389 /* And disable the signal handler. */
390 action.sa_handler = SIG_IGN;
391 sigaction (SIGALRM, &action, NULL);
396 /* Recursively count all regular files in path and all sub-direcotries
397 * of path. The result is added to *count (which should be
398 * initialized to zero by the top-level caller before calling
401 count_files (const char *path, int *count)
404 struct dirent *e, *entry = NULL;
410 dir = opendir (path);
413 fprintf (stderr, "Warning: failed to open directory %s: %s\n",
414 path, strerror (errno));
418 entry_length = offsetof (struct dirent, d_name) +
419 pathconf (path, _PC_NAME_MAX) + 1;
420 entry = malloc (entry_length);
423 err = readdir_r (dir, entry, &e);
425 fprintf (stderr, "Error reading directory: %s\n",
434 /* Ignore special directories to avoid infinite recursion.
435 * Also ignore the .notmuch directory.
437 /* XXX: Eventually we'll want more sophistication to let the
438 * user specify files to be ignored. */
439 if (strcmp (entry->d_name, ".") == 0 ||
440 strcmp (entry->d_name, "..") == 0 ||
441 strcmp (entry->d_name, ".notmuch") == 0)
446 if (asprintf (&next, "%s/%s", path, entry->d_name) == -1) {
448 fprintf (stderr, "Error descending from %s to %s: Out of memory\n",
449 path, entry->d_name);
455 if (S_ISREG (st.st_mode)) {
457 if (*count % 1000 == 0) {
458 printf ("Found %d files so far.\r", *count);
461 } else if (S_ISDIR (st.st_mode)) {
462 count_files (next, count);
476 setup_command (unused (void *ctx), unused (int argc), unused (char *argv[]))
478 notmuch_database_t *notmuch = NULL;
479 char *default_path, *mail_directory = NULL;
482 add_files_state_t add_files_state;
484 struct timeval tv_now;
485 notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
487 printf ("Welcome to notmuch!\n\n");
489 printf ("The goal of notmuch is to help you manage and search your collection of\n"
490 "email, and to efficiently keep up with the flow of email as it comes in.\n\n");
492 printf ("Notmuch needs to know the top-level directory of your email archive,\n"
493 "(where you already have mail stored and where messages will be delivered\n"
494 "in the future). This directory can contain any number of sub-directories\n"
495 "and primarily just files with indvidual email messages (eg. maildir or mh\n"
496 "archives are perfect). If there are other, non-email files (such as\n"
497 "indexes maintained by other email programs) then notmuch will do its\n"
498 "best to detect those and ignore them.\n\n");
500 printf ("Mail storage that uses mbox format, (where one mbox file contains many\n"
501 "messages), will not work with notmuch. If that's how your mail is currently\n"
502 "stored, we recommend you first convert it to maildir format with a utility\n"
503 "such as mb2md. In that case, press Control-C now and run notmuch again\n"
504 "once the conversion is complete.\n\n");
507 default_path = notmuch_database_default_path ();
508 printf ("Top-level mail directory [%s]: ", default_path);
511 getline (&mail_directory, &line_size, stdin);
512 chomp_newline (mail_directory);
516 if (mail_directory == NULL || strlen (mail_directory) == 0) {
518 free (mail_directory);
519 mail_directory = default_path;
521 /* XXX: Instead of telling the user to use an environment
522 * variable here, we should really be writing out a configuration
523 * file and loading that on the next run. */
524 if (strcmp (mail_directory, default_path)) {
525 printf ("Note: Since you are not using the default path, you will want to set\n"
526 "the NOTMUCH_BASE environment variable to %s so that\n"
527 "future calls to notmuch commands will know where to find your mail.\n",
529 printf ("For example, if you are using bash for your shell, add:\n\n");
530 printf ("\texport NOTMUCH_BASE=%s\n\n", mail_directory);
531 printf ("to your ~/.bashrc file.\n\n");
536 /* Coerce th directory into an absolute directory name. */
537 if (*mail_directory != '/') {
538 char *cwd, *absolute_mail_directory;
540 cwd = getcwd (NULL, 0);
542 fprintf (stderr, "Out of memory.\n");
546 if (asprintf (&absolute_mail_directory, "%s/%s",
547 cwd, mail_directory) < 0)
549 fprintf (stderr, "Out of memory.\n");
554 free (mail_directory);
555 mail_directory = absolute_mail_directory;
557 printf ("Abs: %s\n", mail_directory);
560 notmuch = notmuch_database_create (mail_directory);
561 if (notmuch == NULL) {
562 fprintf (stderr, "Failed to create new notmuch database at %s\n",
564 ret = NOTMUCH_STATUS_FILE_ERROR;
568 printf ("OK. Let's take a look at the mail we can find in the directory\n");
569 printf ("%s ...\n", mail_directory);
572 count_files (mail_directory, &count);
574 printf ("Found %d total files. That's not much mail.\n\n", count);
576 printf ("Next, we'll inspect the messages and create a database of threads:\n");
578 add_files_state.ignore_read_only_directories = FALSE;
579 add_files_state.saw_read_only_directory = FALSE;
580 add_files_state.total_files = count;
581 add_files_state.processed_files = 0;
582 add_files_state.added_messages = 0;
583 add_files_state.callback = NULL;
584 gettimeofday (&add_files_state.tv_start, NULL);
586 ret = add_files (notmuch, mail_directory, &add_files_state);
588 gettimeofday (&tv_now, NULL);
589 elapsed = tv_elapsed (add_files_state.tv_start,
591 printf ("Processed %d %s in ", add_files_state.processed_files,
592 add_files_state.processed_files == 1 ?
593 "file" : "total files");
594 print_formatted_seconds (elapsed);
596 printf (" (%d files/sec.). \n",
597 (int) (add_files_state.processed_files / elapsed));
601 if (add_files_state.added_messages) {
602 printf ("Added %d %s to the database.\n\n",
603 add_files_state.added_messages,
604 add_files_state.added_messages == 1 ?
605 "message" : "unique messages");
608 printf ("When new mail is delivered to %s in the future,\n"
609 "run \"notmuch new\" to add it to the database.\n\n",
613 printf ("Note: At least one error was encountered: %s\n",
614 notmuch_status_to_string (ret));
619 free (mail_directory);
621 notmuch_database_close (notmuch);
627 tag_inbox_and_unread (notmuch_message_t *message)
629 notmuch_message_add_tag (message, "inbox");
630 notmuch_message_add_tag (message, "unread");
634 new_command (unused (void *ctx), unused (int argc), unused (char *argv[]))
636 notmuch_database_t *notmuch;
637 const char *mail_directory;
638 add_files_state_t add_files_state;
640 struct timeval tv_now;
643 notmuch = notmuch_database_open (NULL);
644 if (notmuch == NULL) {
649 mail_directory = notmuch_database_get_path (notmuch);
651 add_files_state.ignore_read_only_directories = TRUE;
652 add_files_state.saw_read_only_directory = FALSE;
653 add_files_state.total_files = 0;
654 add_files_state.processed_files = 0;
655 add_files_state.added_messages = 0;
656 add_files_state.callback = tag_inbox_and_unread;
657 gettimeofday (&add_files_state.tv_start, NULL);
659 ret = add_files (notmuch, mail_directory, &add_files_state);
661 gettimeofday (&tv_now, NULL);
662 elapsed = tv_elapsed (add_files_state.tv_start,
664 if (add_files_state.processed_files) {
665 printf ("Processed %d %s in ", add_files_state.processed_files,
666 add_files_state.processed_files == 1 ?
667 "file" : "total files");
668 print_formatted_seconds (elapsed);
670 printf (" (%d files/sec.). \n",
671 (int) (add_files_state.processed_files / elapsed));
676 if (add_files_state.added_messages) {
677 printf ("Added %d new %s to the database (not much, really).\n",
678 add_files_state.added_messages,
679 add_files_state.added_messages == 1 ?
680 "message" : "messages");
682 printf ("No new mail---and that's not much.\n");
685 if (elapsed > 1 && ! add_files_state.saw_read_only_directory) {
686 printf ("\nTip: If you have any sub-directories that are archives (that is,\n"
687 "they will never receive new mail), marking these directores as\n"
688 "read-only (chmod u-w /path/to/dir) will make \"notmuch new\"\n"
689 "much more efficient (it won't even look in those directories).\n");
693 printf ("\nNote: At least one error was encountered: %s\n",
694 notmuch_status_to_string (ret));
699 notmuch_database_close (notmuch);
704 /* Construct a single query string from the passed arguments, using
705 * 'ctx' as the talloc owner for all allocations.
707 * Currently, the arguments are just connected with space characters,
708 * but we might do more processing in the future, (such as inserting
709 * any AND operators needed to work around Xapian QueryParser bugs).
711 * This function returns NULL in case of insufficient memory.
714 query_string_from_args (void *ctx, int argc, char *argv[])
719 query_string = talloc_strdup (ctx, "");
720 if (query_string == NULL)
723 for (i = 0; i < argc; i++) {
725 query_string = talloc_strdup_append (query_string, " ");
726 if (query_string == NULL)
730 query_string = talloc_strdup_append (query_string, argv[i]);
731 if (query_string == NULL)
738 /* Format a nice representation of 'time' relative to the current time.
742 * 5 mins. ago (For times less than 60 minutes ago)
743 * Today 12:30 (For times >60 minutes but still today)
745 * Mon. 12:30 (Before yesterday but fewer than 7 days ago)
746 * October 12 (Between 7 and 180 days ago (about 6 months))
747 * 2008-06-30 (More than 180 days ago)
750 #define HOUR (60 * MINUTE)
751 #define DAY (24 * HOUR)
752 #define RELATIVE_DATE_MAX 20
754 _format_relative_date (void *ctx, time_t then)
756 struct tm tm_now, tm_then;
757 time_t now = time(NULL);
761 localtime_r (&now, &tm_now);
762 localtime_r (&then, &tm_then);
764 result = talloc_zero_size (ctx, RELATIVE_DATE_MAX);
773 if (delta > 180 * DAY) {
774 strftime (result, RELATIVE_DATE_MAX,
775 "%F", &tm_then); /* 2008-06-30 */
780 snprintf (result, RELATIVE_DATE_MAX,
781 "%d mins. ago", (int) (delta / 60));
785 if (delta <= 7 * DAY) {
786 if (tm_then.tm_wday == tm_now.tm_wday &&
789 strftime (result, RELATIVE_DATE_MAX,
790 "Today %R", &tm_then); /* Today 12:30 */
792 } else if ((tm_now.tm_wday + 7 - tm_then.tm_wday) % 7 == 1) {
793 strftime (result, RELATIVE_DATE_MAX,
794 "Yest. %R", &tm_then); /* Yest. 12:30 */
797 if (tm_then.tm_wday != tm_now.tm_wday) {
798 strftime (result, RELATIVE_DATE_MAX,
799 "%a. %R", &tm_then); /* Mon. 12:30 */
805 strftime (result, RELATIVE_DATE_MAX,
806 "%B %d", &tm_then); /* October 12 */
814 search_command (void *ctx, int argc, char *argv[])
816 void *local = talloc_new (ctx);
817 notmuch_database_t *notmuch = NULL;
818 notmuch_query_t *query;
819 notmuch_threads_t *threads;
820 notmuch_thread_t *thread;
821 notmuch_tags_t *tags;
823 const char *relative_date;
825 notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
827 notmuch = notmuch_database_open (NULL);
828 if (notmuch == NULL) {
833 query_str = query_string_from_args (local, argc, argv);
835 query = notmuch_query_create (notmuch, query_str);
837 fprintf (stderr, "Out of memory\n");
842 for (threads = notmuch_query_search_threads (query);
843 notmuch_threads_has_more (threads);
844 notmuch_threads_advance (threads))
848 thread = notmuch_threads_get (threads);
850 date = notmuch_thread_get_oldest_date (thread);
851 relative_date = _format_relative_date (local, date);
853 printf ("thread:%s %12s %s",
854 notmuch_thread_get_thread_id (thread),
856 notmuch_thread_get_subject (thread));
859 for (tags = notmuch_thread_get_tags (thread);
860 notmuch_tags_has_more (tags);
861 notmuch_tags_advance (tags))
865 printf ("%s", notmuch_tags_get (tags));
870 notmuch_thread_destroy (thread);
873 notmuch_query_destroy (query);
877 notmuch_database_close (notmuch);
884 _get_tags_as_string (void *ctx, notmuch_message_t *message)
886 notmuch_tags_t *tags;
891 result = talloc_strdup (ctx, "");
895 for (tags = notmuch_message_get_tags (message);
896 notmuch_tags_has_more (tags);
897 notmuch_tags_advance (tags))
899 tag = notmuch_tags_get (tags);
901 result = talloc_asprintf_append (result, "%s%s",
902 first ? "" : " ", tag);
909 /* Get a nice, single-line summary of message. */
911 _get_one_line_summary (void *ctx, notmuch_message_t *message)
915 const char *relative_date;
919 from = notmuch_message_get_header (message, "from");
921 date = notmuch_message_get_date (message);
922 relative_date = _format_relative_date (ctx, date);
924 subject = notmuch_message_get_header (message, "subject");
926 tags = _get_tags_as_string (ctx, message);
928 return talloc_asprintf (ctx, "%s (%s) %s (%s)",
929 from, relative_date, subject, tags);
933 show_message_part (GMimeObject *part, int *part_count)
936 GMimeDataWrapper *wrapper;
937 GMimeContentDisposition *disposition;
938 GMimeContentType *content_type;
940 *part_count = *part_count + 1;
942 if (GMIME_IS_MULTIPART (part)) {
943 GMimeMultipart *multipart = GMIME_MULTIPART (part);
946 for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
947 if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
948 /* Don't index the signature. */
952 fprintf (stderr, "Warning: Unexpected extra parts of mutlipart/signed. Continuing.\n");
954 show_message_part (g_mime_multipart_get_part (multipart, i),
960 if (GMIME_IS_MESSAGE_PART (part)) {
961 GMimeMessage *mime_message;
963 mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
965 show_message_part (g_mime_message_get_mime_part (mime_message),
971 if (! (GMIME_IS_PART (part))) {
972 fprintf (stderr, "Warning: Not displaying unknown mime part: %s.\n",
973 g_type_name (G_OBJECT_TYPE (part)));
977 disposition = g_mime_object_get_content_disposition (part);
979 strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
981 const char *filename = g_mime_part_get_filename (GMIME_PART (part));
982 content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
984 printf ("\fattachment{ ID: %d, Content-type: %s\n",
986 g_mime_content_type_to_string (content_type));
987 printf ("Attachment: %s (%s)\n", filename,
988 g_mime_content_type_to_string (content_type));
989 printf ("\fattachment}\n");
994 content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
996 printf ("\fpart{ ID: %d, Content-type: %s\n",
998 g_mime_content_type_to_string (content_type));
1000 if (g_mime_content_type_is_type (content_type, "text", "*") &&
1001 !g_mime_content_type_is_type (content_type, "text", "html"))
1003 stream = g_mime_stream_file_new (stdout);
1004 g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
1006 wrapper = g_mime_part_get_content_object (GMIME_PART (part));
1008 g_mime_data_wrapper_write_to_stream (wrapper, stream);
1010 g_object_unref (stream);
1014 printf ("Non-text part: %s\n",
1015 g_mime_content_type_to_string (content_type));
1018 printf ("\fpart}\n");
1021 static notmuch_status_t
1022 show_message_body (const char *filename)
1024 GMimeStream *stream = NULL;
1025 GMimeParser *parser = NULL;
1026 GMimeMessage *mime_message = NULL;
1027 notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
1028 static int initialized = 0;
1032 if (! initialized) {
1037 file = fopen (filename, "r");
1039 fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
1040 ret = NOTMUCH_STATUS_FILE_ERROR;
1044 stream = g_mime_stream_file_new (file);
1045 g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
1047 parser = g_mime_parser_new_with_stream (stream);
1049 mime_message = g_mime_parser_construct_message (parser);
1051 show_message_part (g_mime_message_get_mime_part (mime_message),
1056 g_object_unref (mime_message);
1059 g_object_unref (parser);
1062 g_object_unref (stream);
1071 show_command (void *ctx, unused (int argc), unused (char *argv[]))
1073 void *local = talloc_new (ctx);
1075 notmuch_database_t *notmuch = NULL;
1076 notmuch_query_t *query = NULL;
1077 notmuch_messages_t *messages;
1078 notmuch_message_t *message;
1081 const char *headers[] = {
1082 "Subject", "From", "To", "Cc", "Bcc", "Date"
1084 const char *name, *value;
1087 notmuch = notmuch_database_open (NULL);
1088 if (notmuch == NULL) {
1093 query_string = query_string_from_args (local, argc, argv);
1094 if (query_string == NULL) {
1095 fprintf (stderr, "Out of memory\n");
1100 query = notmuch_query_create (notmuch, query_string);
1101 if (query == NULL) {
1102 fprintf (stderr, "Out of memory\n");
1107 for (messages = notmuch_query_search_messages (query);
1108 notmuch_messages_has_more (messages);
1109 notmuch_messages_advance (messages))
1111 message = notmuch_messages_get (messages);
1113 printf ("\fmessage{ id:%s filename:%s\n",
1114 notmuch_message_get_message_id (message),
1115 notmuch_message_get_filename (message));
1117 printf ("\fheader{\n");
1119 printf ("%s\n", _get_one_line_summary (local, message));
1121 for (i = 0; i < ARRAY_SIZE (headers); i++) {
1123 value = notmuch_message_get_header (message, name);
1125 printf ("%s: %s\n", name, value);
1128 printf ("\fheader}\n");
1129 printf ("\fbody{\n");
1131 show_message_body (notmuch_message_get_filename (message));
1133 printf ("\fbody}\n");
1135 printf ("\fmessage}\n");
1137 notmuch_message_destroy (message);
1142 talloc_free (local);
1145 notmuch_query_destroy (query);
1148 notmuch_database_close (notmuch);
1154 tag_command (void *ctx, unused (int argc), unused (char *argv[]))
1157 int *add_tags, *remove_tags;
1158 int add_tags_count = 0;
1159 int remove_tags_count = 0;
1161 notmuch_database_t *notmuch = NULL;
1162 notmuch_query_t *query;
1163 notmuch_messages_t *messages;
1164 notmuch_message_t *message;
1168 local = talloc_new (ctx);
1169 if (local == NULL) {
1174 add_tags = talloc_size (local, argc * sizeof (int));
1175 if (add_tags == NULL) {
1180 remove_tags = talloc_size (local, argc * sizeof (int));
1181 if (remove_tags == NULL) {
1186 for (i = 0; i < argc; i++) {
1187 if (strcmp (argv[i], "--") == 0) {
1191 if (argv[i][0] == '+') {
1192 add_tags[add_tags_count++] = i;
1193 } else if (argv[i][0] == '-') {
1194 remove_tags[remove_tags_count++] = i;
1200 if (add_tags_count == 0 && remove_tags_count == 0) {
1201 fprintf (stderr, "Error: 'notmuch tag' requires at least one tag to add or remove.\n");
1207 fprintf (stderr, "Error: 'notmuch tag' requires at least one search term.\n");
1212 notmuch = notmuch_database_open (NULL);
1213 if (notmuch == NULL) {
1218 query_string = query_string_from_args (local, argc - i, &argv[i]);
1220 query = notmuch_query_create (notmuch, query_string);
1221 if (query == NULL) {
1222 fprintf (stderr, "Out of memory.\n");
1227 for (messages = notmuch_query_search_messages (query);
1228 notmuch_messages_has_more (messages);
1229 notmuch_messages_advance (messages))
1231 message = notmuch_messages_get (messages);
1233 notmuch_message_freeze (message);
1235 for (i = 0; i < remove_tags_count; i++)
1236 notmuch_message_remove_tag (message,
1237 argv[remove_tags[i]] + 1);
1239 for (i = 0; i < add_tags_count; i++)
1240 notmuch_message_add_tag (message, argv[add_tags[i]] + 1);
1242 notmuch_message_thaw (message);
1244 notmuch_message_destroy (message);
1247 notmuch_query_destroy (query);
1251 notmuch_database_close (notmuch);
1253 talloc_free (local);
1259 dump_command (unused (void *ctx), int argc, char *argv[])
1261 FILE *output = NULL;
1262 notmuch_database_t *notmuch = NULL;
1263 notmuch_query_t *query;
1264 notmuch_messages_t *messages;
1265 notmuch_message_t *message;
1266 notmuch_tags_t *tags;
1270 output = fopen (argv[0], "w");
1271 if (output == NULL) {
1272 fprintf (stderr, "Error opening %s for writing: %s\n",
1273 argv[0], strerror (errno));
1281 notmuch = notmuch_database_open (NULL);
1282 if (notmuch == NULL) {
1287 query = notmuch_query_create (notmuch, "");
1288 if (query == NULL) {
1289 fprintf (stderr, "Out of memory\n");
1294 notmuch_query_set_sort (query, NOTMUCH_SORT_MESSAGE_ID);
1296 for (messages = notmuch_query_search_messages (query);
1297 notmuch_messages_has_more (messages);
1298 notmuch_messages_advance (messages))
1301 message = notmuch_messages_get (messages);
1304 "%s (", notmuch_message_get_message_id (message));
1306 for (tags = notmuch_message_get_tags (message);
1307 notmuch_tags_has_more (tags);
1308 notmuch_tags_advance (tags))
1311 fprintf (output, " ");
1313 fprintf (output, "%s", notmuch_tags_get (tags));
1318 fprintf (output, ")\n");
1320 notmuch_message_destroy (message);
1323 notmuch_query_destroy (query);
1327 notmuch_database_close (notmuch);
1328 if (output && output != stdout)
1335 restore_command (unused (void *ctx), int argc, char *argv[])
1338 notmuch_database_t *notmuch = NULL;
1347 input = fopen (argv[0], "r");
1348 if (input == NULL) {
1349 fprintf (stderr, "Error opening %s for reading: %s\n",
1350 argv[0], strerror (errno));
1355 printf ("No filename given. Reading dump from stdin.\n");
1359 notmuch = notmuch_database_open (NULL);
1360 if (notmuch == NULL) {
1365 /* Dump output is one line per message. We match a sequence of
1366 * non-space characters for the message-id, then one or more
1367 * spaces, then a list of space-separated tags as a sequence of
1368 * characters within literal '(' and ')'. */
1370 "^([^ ]+) \\(([^)]*)\\)$",
1373 while ((line_len = getline (&line, &line_size, input)) != -1) {
1374 regmatch_t match[3];
1375 char *message_id, *tags, *tag, *next;
1376 notmuch_message_t *message;
1377 notmuch_status_t status;
1379 chomp_newline (line);
1381 rerr = xregexec (®ex, line, 3, match, 0);
1382 if (rerr == REG_NOMATCH)
1384 fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",
1389 message_id = xstrndup (line + match[1].rm_so,
1390 match[1].rm_eo - match[1].rm_so);
1391 tags = xstrndup (line + match[2].rm_so,
1392 match[2].rm_eo - match[2].rm_so);
1394 if (strlen (tags)) {
1396 message = notmuch_database_find_message (notmuch, message_id);
1397 if (message == NULL) {
1398 fprintf (stderr, "Warning: Cannot apply tags to missing message: %s\n",
1403 notmuch_message_freeze (message);
1405 notmuch_message_remove_all_tags (message);
1409 tag = strsep (&next, " ");
1412 status = notmuch_message_add_tag (message, tag);
1415 "Error applying tag %s to message %s:\n",
1417 fprintf (stderr, "%s\n",
1418 notmuch_status_to_string (status));
1422 notmuch_message_thaw (message);
1423 notmuch_message_destroy (message);
1436 notmuch_database_close (notmuch);
1437 if (input && input != stdin)
1444 help_command (void *ctx, int argc, char *argv[]);
1446 command_t commands[] = {
1447 { "setup", setup_command,
1448 "Interactively setup notmuch for first use.",
1449 "\t\tThe setup command is the first command you will run in order\n"
1450 "\t\tto start using notmuch. It will prompt you for the directory\n"
1451 "\t\tcontaining your email archives, and will then proceed to build\n"
1452 "\t\ta database to allow fast searching of that mail.\n\n"
1453 "\t\tInvoking notmuch with no command argument will run setup if\n"
1454 "\t\tthe setup command has not previously been completed." },
1455 { "new", new_command,
1456 "Find and import any new messages.",
1457 "\t\tScans all sub-directories of the database, adding new messages\n"
1458 "\t\tthat are found. Each new message will be tagged as both\n"
1459 "\t\t\"inbox\" and \"unread\".\n"
1461 "\t\tNote: \"notmuch new\" will skip any read-only directories,\n"
1462 "\t\tso you can use that to mark directories that will not\n"
1463 "\t\treceive any new mail (and make \"notmuch new\" faster)." },
1464 { "search", search_command,
1465 "<search-term> [...]\n\n"
1466 "\t\tSearch for threads matching the given search terms.",
1467 "\t\tNote that the individual mail messages will be matched\n"
1468 "\t\tagainst the search terms, but the results will be the\n"
1469 "\t\tthreads containing the matched messages.\n\n"
1470 "\t\tCurrently, in addition to free text (and quoted phrases)\n"
1471 "\t\twhich match terms appearing anywhere within an email,\n"
1472 "\t\tthe following prefixes can be used to search specific\n"
1473 "\t\tportions of an email, (where <brackets> indicate user-\n"
1474 "\t\tsupplied values):\n\n"
1475 "\t\t\tfrom:<name-or-address>\n"
1476 "\t\t\tto:<name-or-address>\n"
1477 "\t\t\tsubject:<word-or-quoted-phrase>\n"
1479 "\t\t\tid:<message-id>\n"
1480 "\t\t\tthread:<thread-id>\n\n"
1481 "\t\tThe from: prefix is used to match the name or address of\n"
1482 "\t\tthe sender of an email message.\n\n"
1483 "\t\tThe to: prefix is used to match the names or addresses of\n"
1484 "\t\tany recipient of an email message, (whether To, Cc, or Bcc).\n\n"
1485 "\t\tAny term prefixed with subject: will match only text from\n"
1486 "\t\tthe subject of an email. Quoted phrases are supported when\n"
1487 "\t\tsearching with: subject:\"this is a phrase\".\n\n"
1488 "\t\tValid tag values include \"inbox\" and \"unread\" by default\n"
1489 "\t\tfor new messages added by \"notmuch new\" as well as any other\n"
1490 "\t\ttag values added manually with \"notmuch tag\".\n\n"
1491 "\t\tMessage ID values are the literal contents of the Message-ID:\n"
1492 "\t\theader of email messages, but without the '<','>' delimiters.\n\n"
1493 "\t\tThread ID values are generated internally by notmuch but can\n"
1494 "\t\tbe seen in the output of \"notmuch search\" for example.\n\n"
1495 "\t\tIn addition to individual terms, multiple terms can be\n"
1496 "\t\tcombined with Boolean operators (\"and\", \"or\", \"not\", etc.).\n"
1497 "\t\tEach term in the query will be implicitly connected by a\n"
1498 "\t\tlogical AND if no explicit operator is provided, (except\n"
1499 "\t\tthat terms with a common prefix will be implicitly combined\n"
1500 "\t\twith OR until we get Xapian defect #402 fixed).\n\n"
1501 "\t\tParentheses can also be used to control the combination of\n"
1502 "\t\tthe Boolean operators, but will have to be protected from\n"
1503 "\t\tinterpretation by the shell, (such as by putting quotation\n"
1504 "\t\tmarks around any parenthesized expression)." },
1505 { "show", show_command,
1506 "<search-terms> [...]\n\n"
1507 "\t\tShows all messages matching the search terms.",
1508 "\t\tSee the documentation of \"notmuch search\" for details\n"
1509 "\t\tof the supported syntax of search terms.\n\n"
1510 "\t\tA common use of \"notmuch show\" is to display a single\n"
1511 "\t\tthread of email messages. For this, use a search term of\n"
1512 "\t\t\"thread:<thread-id>\" as can be seen in the first column\n"
1513 "\t\tof output from the \"notmuch search\" command.\n\n"
1514 "\t\tAll messages will be displayed in date order. The output\n"
1515 "\t\tformat is plain-text, with all text-content MIME parts\n"
1516 "\t\tdecoded. Various components in the output, ('message',\n"
1517 "\t\t'header', 'body', 'attachment', and MIME 'part') will be\n"
1518 "\t\tdelimited by easily-parsed markers. Each marker consists\n"
1519 "\t\tof a Control-L character (ASCII decimal 12), the name of\n"
1520 "\t\tthe marker, and then either an opening or closing brace,\n"
1521 "\t\t'{' or '}' to either open or close the component."},
1522 { "tag", tag_command,
1523 "+<tag>|-<tag> [...] [--] <search-term> [...]\n\n"
1524 "\t\tAdd/remove tags for all messages matching the search terms.",
1525 "\t\tThe search terms are handled exactly as in 'search' so one\n"
1526 "\t\tcan use that command first to see what will be modified.\n\n"
1527 "\t\tTags prefixed by '+' are added while those prefixed by '-' are\n"
1528 "\t\tremoved. For each message, tag removal is before tag addition.\n\n"
1529 "\t\tThe beginning of <search-terms> is recognized by the first\n"
1530 "\t\targument that begins with neither '+' nor '-'. Support for\n"
1531 "\t\tan initial search term beginning with '+' or '-' is provided\n"
1532 "\t\tby allowing the user to specify a \"--\" argument to separate\n"
1533 "\t\tthe tags from the search terms.\n\n"
1534 "\t\tNote: If you run \"notmuch new\" between reading a thread with\n"
1535 "\t\t\"notmuch show\" and removing the \"inbox\" tag for that thread\n"
1536 "\t\twith \"notmuch tag\" then you create the possibility of moving\n"
1537 "\t\tsome messages from that thread out of your inbox without ever\n"
1538 "\t\treading them. The easiest way to avoid this problem is to not\n"
1539 "\t\trun \"notmuch new\" between reading and removing tags." },
1540 { "dump", dump_command,
1542 "\t\tCreate a plain-text dump of the tags for each message.",
1543 "\t\tOutput is to the given filename, if any, or to stdout.\n"
1544 "\t\tThese tags are the only data in the notmuch database\n"
1545 "\t\tthat can't be recreated from the messages themselves.\n"
1546 "\t\tThe output of notmuch dump is therefore the only\n"
1547 "\t\tcritical thing to backup (and much more friendly to\n"
1548 "\t\tincremental backup than the native database files.)" },
1549 { "restore", restore_command,
1551 "\t\tRestore the tags from the given dump file (see 'dump').",
1552 "\t\tNote: The dump file format is specifically chosen to be\n"
1553 "\t\tcompatible with the format of files produced by sup-dump.\n"
1554 "\t\tSo if you've previously been using sup for mail, then the\n"
1555 "\t\t\"notmuch restore\" command provides you a way to import\n"
1556 "\t\tall of your tags (or labels as sup calls them)." },
1557 { "help", help_command,
1559 "\t\tThis message, or more detailed help for the named command.",
1560 "\t\tExcept in this case, where there's not much more detailed\n"
1561 "\t\thelp available." }
1570 fprintf (stderr, "Usage: notmuch <command> [args...]\n");
1571 fprintf (stderr, "\n");
1572 fprintf (stderr, "Where <command> and [args...] are as follows:\n");
1573 fprintf (stderr, "\n");
1575 for (i = 0; i < ARRAY_SIZE (commands); i++) {
1576 command = &commands[i];
1578 fprintf (stderr, "\t%s\t%s\n\n", command->name, command->summary);
1581 fprintf (stderr, "Use \"notmuch help <command>\" for more details on each command.\n\n");
1585 help_command (unused (void *ctx), int argc, char *argv[])
1591 fprintf (stderr, "The notmuch mail system.\n\n");
1596 for (i = 0; i < ARRAY_SIZE (commands); i++) {
1597 command = &commands[i];
1599 if (strcmp (argv[0], command->name) == 0) {
1600 fprintf (stderr, "Help for \"notmuch %s\":\n\n", argv[0]);
1601 fprintf (stderr, "\t%s\t%s\n\n%s\n\n", command->name,
1602 command->summary, command->documentation);
1608 "\nSorry, %s is not a known command. There's not much I can do to help.\n\n",
1614 main (int argc, char *argv[])
1616 void *local = talloc_new (NULL);
1621 return setup_command (local, 0, NULL);
1623 for (i = 0; i < ARRAY_SIZE (commands); i++) {
1624 command = &commands[i];
1626 if (strcmp (argv[1], command->name) == 0)
1627 return (command->function) (local, argc - 2, &argv[2]);
1630 /* Don't complain about "help" being an unknown command when we're
1631 about to provide exactly what's wanted anyway. */
1632 fprintf (stderr, "Error: Unknown command '%s' (see \"notmuch help\")\n",
1635 talloc_free (local);