]> git.notmuchmail.org Git - notmuch/blobdiff - notmuch.c
Fix relative date formatting to not split one day into two formats.
[notmuch] / notmuch.c
index 1f5898949cac9153158bd227dcc3f0023a3c99d3..7ee1d6bc0d4aa3258a203fe56d5b720a7f752d25 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
@@ -38,6 +38,7 @@
 #include <unistd.h>
 #include <dirent.h>
 #include <errno.h>
+#include <signal.h>
 
 #include <talloc.h>
 
@@ -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
@@ -1073,14 +1328,15 @@ command_t commands[] = {
       "\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.\n\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,
       "<thread-id>\n\n"
-      "\t\tNote: The \"notmuch show\" command is not implemented yet.\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"