notmuch: Ignore files that don't look like email messages.
[notmuch] / notmuch.c
index 73d9a9acf2483e711415ba4330a977d5c9688882..01000c2a823c8edc815addca4534e2a8b44ff962 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);