* 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]))
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
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;
}
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);
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");
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"
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);
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
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++) {
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);