]> git.notmuchmail.org Git - notmuch/blobdiff - notmuch.c
notmuch: Revamp help message a bit.
[notmuch] / notmuch.c
index 73d9a9acf2483e711415ba4330a977d5c9688882..05aa52dce1d5b4e57949635b5bf2bc293f6c5174 100644 (file)
--- a/notmuch.c
+++ b/notmuch.c
  * Author: Carl Worth <cworth@cworth.org>
  */
 
+#include "notmuch.h"
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <unistd.h>
 #include <dirent.h>
 #include <errno.h>
 
-#include <glib.h>
+#include <glib.h> /* GIOChannel */
 
 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
 
@@ -75,6 +78,137 @@ read_line (void)
     return result;
 }
 
+typedef struct {
+    int total_messages;
+    int count;
+    struct timeval tv_start;
+} add_files_state_t;
+
+/* Compute the number of seconds elapsed from start to end. */
+double
+tv_elapsed (struct timeval start, struct timeval end)
+{
+    return ((end.tv_sec - start.tv_sec) +
+           (end.tv_usec - start.tv_usec) / 1e6);
+}
+
+void
+print_formatted_seconds (double seconds)
+{
+    int hours;
+    int minutes;
+
+    if (seconds > 3600) {
+       hours = (int) seconds / 3600;
+       printf ("%dh ", hours);
+       seconds -= hours * 3600;
+    }
+
+    if (seconds > 60) {
+       minutes = (int) seconds / 60;
+       printf ("%dm ", minutes);
+       seconds -= minutes * 60;
+    }
+
+    printf ("%02ds", (int) seconds);
+}
+
+void
+add_files_print_progress (add_files_state_t *state)
+{
+    struct timeval tv_now;
+    double elapsed_overall, rate_overall;
+
+    gettimeofday (&tv_now, NULL);
+
+    elapsed_overall = tv_elapsed (state->tv_start, tv_now);
+    rate_overall = (state->count) / elapsed_overall;
+
+    printf ("Added %d of %d messages (",
+           state->count, state->total_messages);
+    print_formatted_seconds ((state->total_messages - state->count) /
+                            rate_overall);
+    printf (" remaining).\r");
+
+    fflush (stdout);
+}
+
+/* Recursively find all regular files in 'path' and add them to the
+ * database. */
+void
+add_files (notmuch_database_t *notmuch, const char *path,
+          add_files_state_t *state)
+{
+    DIR *dir;
+    struct dirent *entry, *e;
+    int entry_length;
+    int err;
+    char *next;
+    struct stat st;
+    notmuch_status_t status;
+
+    dir = opendir (path);
+
+    if (dir == NULL) {
+       fprintf (stderr, "Warning: failed to open directory %s: %s\n",
+                path, strerror (errno));
+       return;
+    }
+
+    entry_length = offsetof (struct dirent, d_name) +
+       pathconf (path, _PC_NAME_MAX) + 1;
+    entry = malloc (entry_length);
+
+    while (1) {
+       err = readdir_r (dir, entry, &e);
+       if (err) {
+           fprintf (stderr, "Error reading directory: %s\n",
+                    strerror (errno));
+           free (entry);
+           return;
+       }
+
+       if (e == NULL)
+           break;
+
+       /* Ignore special directories to avoid infinite recursion.
+        * Also ignore the .notmuch directory.
+        */
+       /* XXX: Eventually we'll want more sophistication to let the
+        * user specify files to be ignored. */
+       if (strcmp (entry->d_name, ".") == 0 ||
+           strcmp (entry->d_name, "..") == 0 ||
+           strcmp (entry->d_name, ".notmuch") ==0)
+       {
+           continue;
+       }
+
+       next = g_strdup_printf ("%s/%s", path, entry->d_name);
+
+       stat (next, &st);
+
+       if (S_ISREG (st.st_mode)) {
+           status = notmuch_database_add_message (notmuch, next);
+           if (status == NOTMUCH_STATUS_FILE_NOT_EMAIL) {
+               fprintf (stderr, "Note: Ignoring non-mail file: %s\n",
+                        next);
+           } else {
+               state->count++;
+           }
+           if (state->count % 1000 == 0)
+               add_files_print_progress (state);
+       } else if (S_ISDIR (st.st_mode)) {
+           add_files (notmuch, next, state);
+       }
+
+       free (next);
+    }
+
+    free (entry);
+
+    closedir (dir);
+}
+
 /* Recursively count all regular files in path and all sub-direcotries
  * of path.  The result is added to *count (which should be
  * initialized to zero by the top-level caller before calling
@@ -113,9 +247,14 @@ count_files (const char *path, int *count)
        if (e == NULL)
            break;
 
-       /* Skip these special directories to avoid infinite recursion. */
+       /* Ignore special directories to avoid infinite recursion.
+        * Also ignore the .notmuch directory.
+        */
+       /* XXX: Eventually we'll want more sophistication to let the
+        * user specify files to be ignored. */
        if (strcmp (entry->d_name, ".") == 0 ||
-           strcmp (entry->d_name, "..") == 0)
+           strcmp (entry->d_name, "..") == 0 ||
+           strcmp (entry->d_name, ".notmuch") == 0)
        {
            continue;
        }
@@ -124,14 +263,14 @@ count_files (const char *path, int *count)
 
        stat (next, &st);
 
-       if (S_ISREG (st.st_mode))
+       if (S_ISREG (st.st_mode)) {
            *count = *count + 1;
-       else if (S_ISDIR (st.st_mode))
+           if (*count % 1000 == 0) {
+               printf ("Found %d files so far.\r", *count);
+               fflush (stdout);
+           }
+       } else if (S_ISDIR (st.st_mode)) {
            count_files (next, count);
-
-       if (*count % 1000 == 0) {
-           printf ("Found %d files so far.\r", *count);
-           fflush (stdout);
        }
 
        free (next);
@@ -145,8 +284,12 @@ count_files (const char *path, int *count)
 int
 setup_command (int argc, char *argv[])
 {
+    notmuch_database_t *notmuch;
     char *mail_directory;
     int count;
+    add_files_state_t add_files_state;
+    double elapsed;
+    struct timeval tv_now;
 
     printf ("Welcome to notmuch!\n\n");
 
@@ -156,9 +299,10 @@ setup_command (int argc, char *argv[])
     printf ("Notmuch needs to know the top-level directory of your email archive,\n"
            "(where you already have mail stored and where messages will be delivered\n"
            "in the future). This directory can contain any number of sub-directories\n"
-           "but the only files it contains should be individual email messages.\n"
-           "Either maildir or mh format directories are fine, but you will want to\n"
-           "move away any auxiliary files maintained by other email programs.\n\n");
+           "and primarily just files with indvidual email messages (eg. maildir or mh\n"
+           "archives are perfect). If there are other, non-email files (such as\n"
+           "indexes maintained by other email programs) then notmuch will do its\n"
+           "best to detect those and ignore them.\n\n");
 
     printf ("Mail storage that uses mbox format, (where one mbox file contains many\n"
            "messages), will not work with notmuch. If that's how your mail is currently\n"
@@ -187,13 +331,38 @@ setup_command (int argc, char *argv[])
        mail_directory = g_strdup_printf ("%s/mail", home);
     }
 
+    notmuch = notmuch_database_create (mail_directory);
+    if (notmuch == NULL) {
+       fprintf (stderr, "Failed to create new notmuch database at %s\n",
+                mail_directory);
+       free (mail_directory);
+       return 1;
+    }
+
     printf ("OK. Let's take a look at the mail we can find in the directory\n");
     printf ("%s ...\n", mail_directory);
 
     count = 0;
     count_files (mail_directory, &count);
 
-    printf ("Found %d total files. That's not much mail.\n", count);
+    printf ("Found %d total files. That's not much mail.\n\n", count);
+
+    printf ("Next, we'll inspect the messages and create a database of threads:\n");
+
+    add_files_state.total_messages = count;
+    add_files_state.count = 0;
+    gettimeofday (&add_files_state.tv_start, NULL);
+
+    add_files (notmuch, mail_directory, &add_files_state);
+
+    gettimeofday (&tv_now, NULL);
+    elapsed = tv_elapsed (add_files_state.tv_start,
+                         tv_now);
+    printf ("Added %d total messages in ", add_files_state.count);
+    print_formatted_seconds (elapsed);
+    printf (" (%d messages/sec.).                 \n", (int) (add_files_state.count / elapsed));
+
+    notmuch_database_close (notmuch);
 
     free (mail_directory);
     
@@ -210,19 +379,49 @@ search_command (int argc, char *argv[])
 int
 show_command (int argc, char *argv[])
 {
-    fprintf (stderr, "Error: show-thread is not implemented yet.\n");
+    fprintf (stderr, "Error: show is not implemented yet.\n");
+    return 1;
+}
+
+int
+dump_command (int argc, char *argv[])
+{
+    fprintf (stderr, "Error: dump is not implemented yet.\n");
+    return 1;
+}
+
+int
+restore_command (int argc, char *argv[])
+{
+    fprintf (stderr, "Error: restore is not implemented yet.\n");
     return 1;
 }
 
 command_t commands[] = {
     { "setup", setup_command,
-      "Interactively setup notmuch for first use (no arguments).\n"
+      "Interactively setup notmuch for first use.\n"
       "\t\tInvoking notmuch with no command argument will run setup if\n"
-      "\t\the setup command has not previously been completed." },
+      "\t\tthe setup command has not previously been completed." },
     { "search", search_command,
-      "Search for threads matching the given search terms." },
+      "<search-term> [...]\n\n"
+      "\t\tSearch for threads matching the given search terms.\n"
+      "\t\tOnce we actually implement search we'll document the\n"
+      "\t\tsyntax here." },
     { "show", show_command,
-      "Show the thread with the given thread ID (see 'search')." }
+      "<thread-id>\n\n"
+      "\t\tShow the thread with the given thread ID (see 'search')." },
+    { "dump", dump_command,
+      "[<filename>]\n\n"
+      "\t\tCreate a plain-text dump of the tags for each message\n"
+      "\t\twriting to the given filename, if any, or to stdout.\n"
+      "\t\tThese tags are the only data in the notmuch database\n"
+      "\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." },
+    { "restore", restore_command,
+      "<filename>\n\n"
+      "\t\tRestore the tags from the given dump file (see 'dump')." }
 };
 
 void
@@ -233,7 +432,7 @@ usage (void)
 
     fprintf (stderr, "Usage: notmuch <command> [args...]\n");
     fprintf (stderr, "\n");
-    fprintf (stderr, "Where <command> is one of the following:\n");
+    fprintf (stderr, "Where <command> and [args...] are as follows:\n");
     fprintf (stderr, "\n");
 
     for (i = 0; i < ARRAY_SIZE (commands); i++) {
@@ -259,7 +458,15 @@ main (int argc, char *argv[])
            return (command->function) (argc - 2, &argv[2]);
     }
 
-    fprintf (stderr, "Error: Unknown command '%s'\n\n", argv[1]);
+    /* Don't complain about "help" being an unknown command when we're
+       about to provide exactly what's wanted anyway. */
+    if (strcmp (argv[1], "help") == 0 ||
+       strcmp (argv[1], "--help") == 0)
+    {
+       fprintf (stderr, "The notmuch mail system.\n\n");
+    } else {
+       fprintf (stderr, "Error: Unknown command '%s'\n\n", argv[1]);
+    }
     usage ();
     exit (1);