X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=notmuch.c;h=7ee1d6bc0d4aa3258a203fe56d5b720a7f752d25;hp=4b16a71eb7352321f657fb08f8de7e6d88f0ff3a;hb=a2f0445e1b302e2559e48851775dc792ce3c6904;hpb=450b054245ffa30d447e50c3087fddf0e9716d70 diff --git a/notmuch.c b/notmuch.c index 4b16a71e..7ee1d6bc 100644 --- a/notmuch.c +++ b/notmuch.c @@ -38,6 +38,7 @@ #include #include #include +#include #include @@ -123,6 +124,14 @@ print_formatted_seconds (double seconds) printf ("%ds", (int) seconds); } +static volatile sig_atomic_t do_add_files_print_progress = 0; + +static void +handle_sigalrm (unused (int signal)) +{ + do_add_files_print_progress = 1; +} + static void add_files_print_progress (add_files_state_t *state) { @@ -298,8 +307,10 @@ add_files_recursive (notmuch_database_t *notmuch, message = NULL; } - if (state->processed_files % 1000 == 0) + if (do_add_files_print_progress) { + do_add_files_print_progress = 0; add_files_print_progress (state); + } } } else if (S_ISDIR (st->st_mode)) { status = add_files_recursive (notmuch, next, st, state); @@ -327,16 +338,17 @@ add_files_recursive (notmuch_database_t *notmuch, } /* This is the top-level entry point for add_files. It does a couple - * of error checks, and then calls into the recursive function, - * (avoiding the repeating of these error checks at every - * level---which would be useless becaues we already do a stat() at - * the level above). */ + * of error checks, sets up the progress-printing timer and then calls + * into the recursive function. */ static notmuch_status_t add_files (notmuch_database_t *notmuch, const char *path, add_files_state_t *state) { struct stat st; + notmuch_status_t status; + struct sigaction action; + struct itimerval timerval; if (stat (path, &st)) { fprintf (stderr, "Error reading directory %s: %s\n", @@ -349,7 +361,34 @@ add_files (notmuch_database_t *notmuch, return NOTMUCH_STATUS_FILE_ERROR; } - return add_files_recursive (notmuch, path, &st, state); + /* Setup our handler for SIGALRM */ + memset (&action, 0, sizeof (struct sigaction)); + action.sa_handler = handle_sigalrm; + sigemptyset (&action.sa_mask); + action.sa_flags = SA_RESTART; + sigaction (SIGALRM, &action, NULL); + + /* Then start a timer to send SIGALRM once per second. */ + timerval.it_interval.tv_sec = 1; + timerval.it_interval.tv_usec = 0; + timerval.it_value.tv_sec = 1; + timerval.it_value.tv_usec = 0; + setitimer (ITIMER_REAL, &timerval, NULL); + + status = add_files_recursive (notmuch, path, &st, state); + + /* Now stop the timer. */ + timerval.it_interval.tv_sec = 0; + timerval.it_interval.tv_usec = 0; + timerval.it_value.tv_sec = 0; + timerval.it_value.tv_usec = 0; + setitimer (ITIMER_REAL, &timerval, NULL); + + /* And disable the signal handler. */ + action.sa_handler = SIG_IGN; + sigaction (SIGALRM, &action, NULL); + + return status; } /* Recursively count all regular files in path and all sub-direcotries @@ -492,6 +531,30 @@ setup_command (unused (int argc), unused (char *argv[])) free (default_path); } + /* Coerce th directory into an absolute directory name. */ + if (*mail_directory != '/') { + char *cwd, *absolute_mail_directory; + + cwd = getcwd (NULL, 0); + if (cwd == NULL) { + fprintf (stderr, "Out of memory.\n"); + exit (1); + } + + if (asprintf (&absolute_mail_directory, "%s/%s", + cwd, mail_directory) < 0) + { + fprintf (stderr, "Out of memory.\n"); + exit (1); + } + + free (cwd); + free (mail_directory); + mail_directory = absolute_mail_directory; + + printf ("Abs: %s\n", mail_directory); + } + notmuch = notmuch_database_create (mail_directory); if (notmuch == NULL) { fprintf (stderr, "Failed to create new notmuch database at %s\n", @@ -670,6 +733,79 @@ query_string_from_args (void *ctx, int argc, char *argv[]) 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 (int argc, char *argv[]) { @@ -680,6 +816,8 @@ search_command (int argc, char *argv[]) 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); @@ -705,8 +843,12 @@ search_command (int argc, char *argv[]) thread = notmuch_thread_results_get (results); - printf ("%s %s", + 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 (" ("); @@ -734,11 +876,124 @@ search_command (int argc, char *argv[]) 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 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; + const char *filename; + FILE *file; + int ret = 0; + int c; + + const char *headers[] = { + "Subject", "From", "To", "Cc", "Bcc", "Date" + }; + const char *name, *value; + unsigned int i; + + 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\n", _get_one_line_summary (local, message)); + + for (i = 0; i < ARRAY_SIZE (headers); i++) { + name = headers[i]; + value = notmuch_message_get_header (message, name); + if (value) + printf ("%s: %s\n", name, value); + } + + printf ("%%header}\n"); + + filename = notmuch_message_get_filename (message); + + file = fopen (filename, "r"); + if (file) { + size_t header_size = notmuch_message_get_header_size (message); + fseek (file, header_size + 1, SEEK_SET); + while (1) { + c = fgetc (file); + if (c == EOF) + break; + putchar (c); + } + } + fclose (file); + + 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 @@ -1037,12 +1292,16 @@ help_command (int argc, char *argv[]); command_t commands[] = { { "setup", setup_command, "Interactively setup notmuch for first use.", + "\t\tThe setup command is the first command you will run in order\n" + "\t\tto start using notmuch. It will prompt you for the directory\n" + "\t\tcontaining your email archives, and will then proceed to build\n" + "\t\ta database to allow fast searching of that mail.\n\n" "\t\tInvoking notmuch with no command argument will run setup if\n" "\t\tthe setup command has not previously been completed." }, { "new", new_command, "Find and import any new messages.", "\t\tScans all sub-directories of the database, adding new messages\n" - "\t\tthat are found. Each new message will be tagges as both\n" + "\t\tthat are found. Each new message will be tagged as both\n" "\t\t\"inbox\" and \"unread\".\n" "\n" "\t\tNote: \"notmuch new\" will skip any read-only directories,\n" @@ -1051,16 +1310,41 @@ command_t commands[] = { { "search", search_command, " [...]\n\n" "\t\tSearch for threads matching the given search terms.", - "\t\tOnce we actually implement search we'll document the\n" - "\t\tsyntax here." }, + "\t\tNote that the individual mail messages will be matched\n" + "\t\tagainst the search terms, but the results will be the\n" + "\t\tthreads containing the matched messages.\n\n" + "\t\tCurrently, the supported search terms are as follows, (where\n" + "\t\t indicate user-supplied values):\n\n" + "\t\t\ttag:\n" + "\t\t\tid:\n" + "\t\t\tthread:\n\n" + "\t\tValid tag values include \"inbox\" and \"unread\" by default\n" + "\t\tfor new messages added by \"notmuch new\" as well as any other\n" + "\t\ttag values added manually with \"notmuch tag\".\n\n" + "\t\tMessage ID values are the literal contents of the Message-ID:\n" + "\t\theader of email messages, but without the '<','>' delimiters.\n\n" + "\t\tThread ID values are generated internally by notmuch but can\n" + "\t\tbe seen in the output of \"notmuch search\" for example.\n\n" + "\t\tIn addition to individual terms, multiple terms can be\n" + "\t\tcombined with Boolean operators (\"and\", \"or\", \"not\", etc.).\n" + "\t\tEach term in the query will be implicitly connected by a\n" + "\t\tlogical AND if no explicit operator is provided, (except\n" + "\t\tthat terms with a common prefix will be implicitly combined\n" + "\t\twith OR until we get Xapian defect #402 fixed).\n\n" + "\t\tParentheses can also be used to control the combination of\n" + "\t\tthe Boolean operators, but will have to be protected from\n" + "\t\tinterpretation by the shell, (such as by putting quotation\n" + "\t\tmarks around any parenthesized expression)." }, { "show", show_command, "\n\n" "\t\tShow the thread with the given thread ID (see 'search').", - "" }, + "\t\tThread ID values are given as the first column in the\n" + "\t\toutput of the \"notmuch search\" command. These are the\n" + "\t\trandom-looking strings of 32 characters." }, { "tag", tag_command, "+|- [...] [--] [...]\n\n" "\t\tAdd/remove tags for all messages matching the search terms.", - "\t\tThe search terms are handled texactly as in 'search' so one\n" + "\t\tThe search terms are handled exactly as in 'search' so one\n" "\t\tcan use that command first to see what will be modified.\n\n" "\t\tTags prefixed by '+' are added while those prefixed by '-' are\n" "\t\tremoved. For each message, tag removal is before tag addition.\n\n" @@ -1068,7 +1352,13 @@ command_t commands[] = { "\t\targument that begins with neither '+' nor '-'. Support for\n" "\t\tan initial search term beginning with '+' or '-' is provided\n" "\t\tby allowing the user to specify a \"--\" argument to separate\n" - "\t\tthe tags from the search terms." }, + "\t\tthe tags from the search terms.\n\n" + "\t\tNote: If you run \"notmuch new\" between reading a thread with\n" + "\t\t\"notmuch show\" and removing the \"inbox\" tag for that thread\n" + "\t\twith \"notmuch tag\" then you create the possibility of moving\n" + "\t\tsome messages from that thread out of your inbox without ever\n" + "\t\treading them. The easiest way to avoid this problem is to not\n" + "\t\trun \"notmuch new\" between reading and removing tags." }, { "dump", dump_command, "[]\n\n" "\t\tCreate a plain-text dump of the tags for each message.", @@ -1077,15 +1367,20 @@ command_t commands[] = { "\t\tthat can't be recreated from the messages themselves.\n" "\t\tThe output of notmuch dump is therefore the only\n" "\t\tcritical thing to backup (and much more friendly to\n" - "\t\tincremental backup than the native database files." }, + "\t\tincremental backup than the native database files.)" }, { "restore", restore_command, "\n\n" "\t\tRestore the tags from the given dump file (see 'dump').", - "" }, + "\t\tNote: The dump file format is specifically chosen to be\n" + "\t\tcompatible with the format of files produced by sup-dump.\n" + "\t\tSo if you've previously been using sup for mail, then the\n" + "\t\t\"notmuch restore\" command provides you a way to import\n" + "\t\tall of your tags (or labels as sup calls them)." }, { "help", help_command, "[]\n\n" "\t\tThis message, or more detailed help for the named command.", - "" } + "\t\tExcept in this case, where there's not much more detailed\n" + "\t\thelp available." } }; static void