summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.local20
-rw-r--r--add-files.c291
-rw-r--r--notmuch-client.h136
-rw-r--r--notmuch-dump.c97
-rw-r--r--notmuch-new.c100
-rw-r--r--notmuch-restore.c130
-rw-r--r--notmuch-search.c91
-rw-r--r--notmuch-setup.c250
-rw-r--r--notmuch-show.c290
-rw-r--r--notmuch-tag.c126
-rw-r--r--notmuch-time.c137
-rw-r--r--notmuch.c1441
-rw-r--r--query-string.c56
13 files changed, 1733 insertions, 1432 deletions
diff --git a/Makefile.local b/Makefile.local
index 067fe51a..ef437757 100644
--- a/Makefile.local
+++ b/Makefile.local
@@ -1,6 +1,20 @@
all: notmuch
-notmuch: notmuch.o lib/notmuch.a
+notmuch_client_srcs = \
+ notmuch.c \
+ notmuch-dump.c \
+ notmuch-new.c \
+ notmuch-restore.c \
+ notmuch-search.c \
+ notmuch-setup.c \
+ notmuch-show.c \
+ notmuch-tag.c \
+ notmuch-time.c \
+ add-files.c \
+ query-string.c
+
+notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
+notmuch: $(notmuch_client_modules) lib/notmuch.a
$(CC) $(LDFLAGS) $^ -o $@
notmuch.1.gz:
@@ -12,5 +26,5 @@ install: all notmuch.1.gz
install -C -D notmuch-completion.bash \
$(DESTDIR)/etc/bash_completion.d/notmuch
-SRCS := $(SRCS) notmuch.c
-CLEAN := $(CLEAN) notmuch *.o
+SRCS := $(SRCS) $(notmuch_client_srcs)
+CLEAN := $(CLEAN) notmuch $(notmuch_client_modules)
diff --git a/add-files.c b/add-files.c
new file mode 100644
index 00000000..7b9639b7
--- /dev/null
+++ b/add-files.c
@@ -0,0 +1,291 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+
+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)
+{
+ struct timeval tv_now;
+ double elapsed_overall, rate_overall;
+
+ gettimeofday (&tv_now, NULL);
+
+ elapsed_overall = notmuch_time_elapsed (state->tv_start, tv_now);
+ rate_overall = (state->processed_files) / elapsed_overall;
+
+ printf ("Processed %d", state->processed_files);
+
+ if (state->total_files) {
+ double time_remaining;
+
+ time_remaining = ((state->total_files - state->processed_files) /
+ rate_overall);
+ printf (" of %d files (", state->total_files);
+ notmuch_time_print_formatted_seconds (time_remaining);
+ printf (" remaining). \r");
+ } else {
+ printf (" files (%d files/sec.) \r", (int) rate_overall);
+ }
+
+ fflush (stdout);
+}
+
+/* Examine 'path' recursively as follows:
+ *
+ * o Ask the filesystem for the mtime of 'path' (path_mtime)
+ *
+ * o Ask the database for its timestamp of 'path' (path_dbtime)
+ *
+ * o If 'path_mtime' > 'path_dbtime'
+ *
+ * o For each regular file in 'path' with mtime newer than the
+ * 'path_dbtime' call add_message to add the file to the
+ * database.
+ *
+ * o For each sub-directory of path, recursively call into this
+ * same function.
+ *
+ * o Tell the database to update its time of 'path' to 'path_mtime'
+ *
+ * The 'struct stat *st' must point to a structure that has already
+ * been initialized for 'path' by calling stat().
+ */
+static notmuch_status_t
+add_files_recursive (notmuch_database_t *notmuch,
+ const char *path,
+ struct stat *st,
+ add_files_state_t *state)
+{
+ DIR *dir = NULL;
+ struct dirent *e, *entry = NULL;
+ int entry_length;
+ int err;
+ char *next = NULL;
+ time_t path_mtime, path_dbtime;
+ notmuch_status_t status, ret = NOTMUCH_STATUS_SUCCESS;
+ notmuch_message_t *message = NULL, **closure;
+
+ /* If we're told to, we bail out on encountering a read-only
+ * directory, (with this being a clear clue from the user to
+ * Notmuch that new mail won't be arriving there and we need not
+ * look. */
+ if (state->ignore_read_only_directories &&
+ (st->st_mode & S_IWUSR) == 0)
+ {
+ state->saw_read_only_directory = TRUE;
+ goto DONE;
+ }
+
+ path_mtime = st->st_mtime;
+
+ path_dbtime = notmuch_database_get_timestamp (notmuch, path);
+
+ dir = opendir (path);
+ if (dir == NULL) {
+ fprintf (stderr, "Error opening directory %s: %s\n",
+ path, strerror (errno));
+ ret = NOTMUCH_STATUS_FILE_ERROR;
+ goto DONE;
+ }
+
+ 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));
+ ret = NOTMUCH_STATUS_FILE_ERROR;
+ goto DONE;
+ }
+
+ if (e == NULL)
+ break;
+
+ /* If this directory hasn't been modified since the last
+ * add_files, then we only need to look further for
+ * sub-directories. */
+ if (path_mtime <= path_dbtime && entry->d_type != DT_DIR)
+ continue;
+
+ /* 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 = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name);
+
+ if (stat (next, st)) {
+ fprintf (stderr, "Error reading %s: %s\n",
+ next, strerror (errno));
+ ret = NOTMUCH_STATUS_FILE_ERROR;
+ continue;
+ }
+
+ if (S_ISREG (st->st_mode)) {
+ /* If the file hasn't been modified since the last
+ * add_files, then we need not look at it. */
+ if (st->st_mtime > path_dbtime) {
+ state->processed_files++;
+
+ if (state->callback)
+ closure = &message;
+ else
+ closure = NULL;
+
+ status = notmuch_database_add_message (notmuch, next, closure);
+ switch (status) {
+ /* success */
+ case NOTMUCH_STATUS_SUCCESS:
+ state->added_messages++;
+ if (state->callback)
+ (state->callback) (message);
+ break;
+ /* Non-fatal issues (go on to next file) */
+ case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+ /* Stay silent on this one. */
+ break;
+ case NOTMUCH_STATUS_FILE_NOT_EMAIL:
+ fprintf (stderr, "Note: Ignoring non-mail file: %s\n",
+ next);
+ break;
+ /* Fatal issues. Don't process anymore. */
+ case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
+ case NOTMUCH_STATUS_OUT_OF_MEMORY:
+ fprintf (stderr, "Error: %s. Halting processing.\n",
+ notmuch_status_to_string (status));
+ ret = status;
+ goto DONE;
+ default:
+ case NOTMUCH_STATUS_FILE_ERROR:
+ case NOTMUCH_STATUS_NULL_POINTER:
+ case NOTMUCH_STATUS_TAG_TOO_LONG:
+ case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
+ case NOTMUCH_STATUS_LAST_STATUS:
+ INTERNAL_ERROR ("add_message returned unexpected value: %d", status);
+ goto DONE;
+ }
+
+ if (message) {
+ notmuch_message_destroy (message);
+ message = NULL;
+ }
+
+ 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);
+ if (status && ret == NOTMUCH_STATUS_SUCCESS)
+ ret = status;
+ }
+
+ talloc_free (next);
+ next = NULL;
+ }
+
+ status = notmuch_database_set_timestamp (notmuch, path, path_mtime);
+ if (status && ret == NOTMUCH_STATUS_SUCCESS)
+ ret = status;
+
+ DONE:
+ if (next)
+ talloc_free (next);
+ if (entry)
+ free (entry);
+ if (dir)
+ closedir (dir);
+
+ return ret;
+}
+
+/* This is the top-level entry point for add_files. It does a couple
+ * of error checks, sets up the progress-printing timer and then calls
+ * into the recursive function. */
+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",
+ path, strerror (errno));
+ return NOTMUCH_STATUS_FILE_ERROR;
+ }
+
+ if (! S_ISDIR (st.st_mode)) {
+ fprintf (stderr, "Error: %s is not a directory.\n", path);
+ return NOTMUCH_STATUS_FILE_ERROR;
+ }
+
+ /* 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;
+}
diff --git a/notmuch-client.h b/notmuch-client.h
new file mode 100644
index 00000000..50f31fa1
--- /dev/null
+++ b/notmuch-client.h
@@ -0,0 +1,136 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#ifndef NOTMUCH_CLIENT_H
+#define NOTMUCH_CLIENT_H
+
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* for getline */
+#endif
+#include <stdio.h>
+
+#include <gmime/gmime.h>
+
+#include "notmuch.h"
+
+/* This is separate from notmuch-private.h because we're trying to
+ * keep notmuch.c from looking into any internals, (which helps us
+ * develop notmuch.h into a plausible library interface).
+ */
+#include "xutil.h"
+
+#include <stddef.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <talloc.h>
+
+#define unused(x) x __attribute__ ((unused))
+
+/* There's no point in continuing when we've detected that we've done
+ * something wrong internally (as opposed to the user passing in a
+ * bogus value).
+ *
+ * Note that __location__ comes from talloc.h.
+ */
+#define INTERNAL_ERROR(format, ...) \
+ do { \
+ fprintf(stderr, \
+ "Internal error: " format " (%s)\n", \
+ ##__VA_ARGS__, __location__); \
+ exit (1); \
+ } while (0)
+
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
+typedef int (*command_function_t) (void *ctx, int argc, char *argv[]);
+
+typedef struct command {
+ const char *name;
+ command_function_t function;
+ const char *summary;
+ const char *documentation;
+} command_t;
+
+typedef void (*add_files_callback_t) (notmuch_message_t *message);
+
+typedef struct {
+ int ignore_read_only_directories;
+ int saw_read_only_directory;
+
+ int total_files;
+ int processed_files;
+ int added_messages;
+ struct timeval tv_start;
+
+ add_files_callback_t callback;
+} add_files_state_t;
+
+static inline void
+chomp_newline (char *str)
+{
+ if (str && str[strlen(str)-1] == '\n')
+ str[strlen(str)-1] = '\0';
+}
+
+int
+notmuch_dump_command (void *ctx, int argc, char *argv[]);
+
+int
+notmuch_new_command (void *ctx, int argc, char *argv[]);
+
+int
+notmuch_restore_command (void *ctx, int argc, char *argv[]);
+
+int
+notmuch_search_command (void *ctx, int argc, char *argv[]);
+
+int
+notmuch_setup_command (void *ctx, int argc, char *argv[]);
+
+int
+notmuch_show_command (void *ctx, int argc, char *argv[]);
+
+int
+notmuch_tag_command (void *ctx, int argc, char *argv[]);
+
+notmuch_status_t
+add_files (notmuch_database_t *notmuch, const char *path,
+ add_files_state_t *state);
+
+char *
+query_string_from_args (void *ctx, int argc, char *argv[]);
+
+const char *
+notmuch_time_relative_date (void *ctx, time_t then);
+
+void
+notmuch_time_print_formatted_seconds (double seconds);
+
+double
+notmuch_time_elapsed (struct timeval start, struct timeval end);
+
+#endif
diff --git a/notmuch-dump.c b/notmuch-dump.c
new file mode 100644
index 00000000..ae77338e
--- /dev/null
+++ b/notmuch-dump.c
@@ -0,0 +1,97 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+
+int
+notmuch_dump_command (unused (void *ctx), int argc, char *argv[])
+{
+ FILE *output = NULL;
+ notmuch_database_t *notmuch = NULL;
+ notmuch_query_t *query;
+ notmuch_messages_t *messages;
+ notmuch_message_t *message;
+ notmuch_tags_t *tags;
+ int ret = 0;
+
+ if (argc) {
+ output = fopen (argv[0], "w");
+ if (output == NULL) {
+ fprintf (stderr, "Error opening %s for writing: %s\n",
+ argv[0], strerror (errno));
+ ret = 1;
+ goto DONE;
+ }
+ } else {
+ output = stdout;
+ }
+
+ notmuch = notmuch_database_open (NULL);
+ if (notmuch == NULL) {
+ ret = 1;
+ goto DONE;
+ }
+
+ query = notmuch_query_create (notmuch, "");
+ if (query == NULL) {
+ fprintf (stderr, "Out of memory\n");
+ ret = 1;
+ goto DONE;
+ }
+
+ notmuch_query_set_sort (query, NOTMUCH_SORT_MESSAGE_ID);
+
+ for (messages = notmuch_query_search_messages (query);
+ notmuch_messages_has_more (messages);
+ notmuch_messages_advance (messages))
+ {
+ int first = 1;
+ message = notmuch_messages_get (messages);
+
+ fprintf (output,
+ "%s (", notmuch_message_get_message_id (message));
+
+ for (tags = notmuch_message_get_tags (message);
+ notmuch_tags_has_more (tags);
+ notmuch_tags_advance (tags))
+ {
+ if (! first)
+ fprintf (output, " ");
+
+ fprintf (output, "%s", notmuch_tags_get (tags));
+
+ first = 0;
+ }
+
+ fprintf (output, ")\n");
+
+ notmuch_message_destroy (message);
+ }
+
+ notmuch_query_destroy (query);
+
+ DONE:
+ if (notmuch)
+ notmuch_database_close (notmuch);
+ if (output && output != stdout)
+ fclose (output);
+
+ return ret;
+}
diff --git a/notmuch-new.c b/notmuch-new.c
new file mode 100644
index 00000000..a7bc994b
--- /dev/null
+++ b/notmuch-new.c
@@ -0,0 +1,100 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+
+static void
+tag_inbox_and_unread (notmuch_message_t *message)
+{
+ notmuch_message_add_tag (message, "inbox");
+ notmuch_message_add_tag (message, "unread");
+}
+
+int
+notmuch_new_command (unused (void *ctx),
+ unused (int argc), unused (char *argv[]))
+{
+ notmuch_database_t *notmuch;
+ const char *mail_directory;
+ add_files_state_t add_files_state;
+ double elapsed;
+ struct timeval tv_now;
+ int ret = 0;
+
+ notmuch = notmuch_database_open (NULL);
+ if (notmuch == NULL) {
+ ret = 1;
+ goto DONE;
+ }
+
+ mail_directory = notmuch_database_get_path (notmuch);
+
+ add_files_state.ignore_read_only_directories = TRUE;
+ add_files_state.saw_read_only_directory = FALSE;
+ add_files_state.total_files = 0;
+ add_files_state.processed_files = 0;
+ add_files_state.added_messages = 0;
+ add_files_state.callback = tag_inbox_and_unread;
+ gettimeofday (&add_files_state.tv_start, NULL);
+
+ ret = add_files (notmuch, mail_directory, &add_files_state);
+
+ gettimeofday (&tv_now, NULL);
+ elapsed = notmuch_time_elapsed (add_files_state.tv_start,
+ tv_now);
+ if (add_files_state.processed_files) {
+ printf ("Processed %d %s in ", add_files_state.processed_files,
+ add_files_state.processed_files == 1 ?
+ "file" : "total files");
+ notmuch_time_print_formatted_seconds (elapsed);
+ if (elapsed > 1) {
+ printf (" (%d files/sec.). \n",
+ (int) (add_files_state.processed_files / elapsed));
+ } else {
+ printf (". \n");
+ }
+ }
+ if (add_files_state.added_messages) {
+ printf ("Added %d new %s to the database (not much, really).\n",
+ add_files_state.added_messages,
+ add_files_state.added_messages == 1 ?
+ "message" : "messages");
+ } else {
+ printf ("No new mail---and that's not much.\n");
+ }
+
+ if (elapsed > 1 && ! add_files_state.saw_read_only_directory) {
+ printf ("\nTip: If you have any sub-directories that are archives (that is,\n"
+ "they will never receive new mail), marking these directores as\n"
+ "read-only (chmod u-w /path/to/dir) will make \"notmuch new\"\n"
+ "much more efficient (it won't even look in those directories).\n");
+ }
+
+ if (ret) {
+ printf ("\nNote: At least one error was encountered: %s\n",
+ notmuch_status_to_string (ret));
+ }
+
+ DONE:
+ if (notmuch)
+ notmuch_database_close (notmuch);
+
+ return ret;
+}
diff --git a/notmuch-restore.c b/notmuch-restore.c
new file mode 100644
index 00000000..87c68c1f
--- /dev/null
+++ b/notmuch-restore.c
@@ -0,0 +1,130 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+
+int
+notmuch_restore_command (unused (void *ctx), int argc, char *argv[])
+{
+ FILE *input = NULL;
+ notmuch_database_t *notmuch = NULL;
+ char *line = NULL;
+ size_t line_size;
+ ssize_t line_len;
+ regex_t regex;
+ int rerr;
+ int ret = 0;
+
+ if (argc) {
+ input = fopen (argv[0], "r");
+ if (input == NULL) {
+ fprintf (stderr, "Error opening %s for reading: %s\n",
+ argv[0], strerror (errno));
+ ret = 1;
+ goto DONE;
+ }
+ } else {
+ printf ("No filename given. Reading dump from stdin.\n");
+ input = stdin;
+ }
+
+ notmuch = notmuch_database_open (NULL);
+ if (notmuch == NULL) {
+ ret = 1;
+ goto DONE;
+ }
+
+ /* Dump output is one line per message. We match a sequence of
+ * non-space characters for the message-id, then one or more
+ * spaces, then a list of space-separated tags as a sequence of
+ * characters within literal '(' and ')'. */
+ xregcomp (&regex,
+ "^([^ ]+) \\(([^)]*)\\)$",
+ REG_EXTENDED);
+
+ while ((line_len = getline (&line, &line_size, input)) != -1) {
+ regmatch_t match[3];
+ char *message_id, *tags, *tag, *next;
+ notmuch_message_t *message;
+ notmuch_status_t status;
+
+ chomp_newline (line);
+
+ rerr = xregexec (&regex, line, 3, match, 0);
+ if (rerr == REG_NOMATCH)
+ {
+ fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",
+ line);
+ continue;
+ }
+
+ message_id = xstrndup (line + match[1].rm_so,
+ match[1].rm_eo - match[1].rm_so);
+ tags = xstrndup (line + match[2].rm_so,
+ match[2].rm_eo - match[2].rm_so);
+
+ if (strlen (tags)) {
+
+ message = notmuch_database_find_message (notmuch, message_id);
+ if (message == NULL) {
+ fprintf (stderr, "Warning: Cannot apply tags to missing message: %s\n",
+ message_id);
+ goto NEXT_LINE;
+ }
+
+ notmuch_message_freeze (message);
+
+ notmuch_message_remove_all_tags (message);
+
+ next = tags;
+ while (next) {
+ tag = strsep (&next, " ");
+ if (*tag == '\0')
+ continue;
+ status = notmuch_message_add_tag (message, tag);
+ if (status) {
+ fprintf (stderr,
+ "Error applying tag %s to message %s:\n",
+ tag, message_id);
+ fprintf (stderr, "%s\n",
+ notmuch_status_to_string (status));
+ }
+ }
+
+ notmuch_message_thaw (message);
+ notmuch_message_destroy (message);
+ }
+ NEXT_LINE:
+ free (message_id);
+ free (tags);
+ }
+
+ regfree (&regex);
+
+ DONE:
+ if (line)
+ free (line);
+ if (notmuch)
+ notmuch_database_close (notmuch);
+ if (input && input != stdin)
+ fclose (input);
+
+ return ret;
+}
diff --git a/notmuch-search.c b/notmuch-search.c
new file mode 100644
index 00000000..4d2c7361
--- /dev/null
+++ b/notmuch-search.c
@@ -0,0 +1,91 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+
+int
+notmuch_search_command (void *ctx, int argc, char *argv[])
+{
+ void *local = talloc_new (ctx);
+ notmuch_database_t *notmuch = NULL;
+ notmuch_query_t *query;
+ notmuch_threads_t *threads;
+ 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);
+ if (notmuch == NULL) {
+ ret = 1;
+ goto DONE;
+ }
+
+ query_str = query_string_from_args (local, argc, argv);
+
+ query = notmuch_query_create (notmuch, query_str);
+ if (query == NULL) {
+ fprintf (stderr, "Out of memory\n");
+ ret = 1;
+ goto DONE;
+ }
+
+ for (threads = notmuch_query_search_threads (query);
+ notmuch_threads_has_more (threads);
+ notmuch_threads_advance (threads))
+ {
+ int first = 1;
+
+ thread = notmuch_threads_get (threads);
+
+ date = notmuch_thread_get_oldest_date (thread);
+ relative_date = notmuch_time_relative_date (local, date);
+
+ printf ("thread:%s %12s %s",
+ notmuch_thread_get_thread_id (thread),
+ relative_date,
+ notmuch_thread_get_subject (thread));
+
+ printf (" (");
+ for (tags = notmuch_thread_get_tags (thread);
+ notmuch_tags_has_more (tags);
+ notmuch_tags_advance (tags))
+ {
+ if (! first)
+ printf (" ");
+ printf ("%s", notmuch_tags_get (tags));
+ first = 0;
+ }
+ printf (")\n");
+
+ notmuch_thread_destroy (thread);
+ }
+
+ notmuch_query_destroy (query);
+
+ DONE:
+ if (notmuch)
+ notmuch_database_close (notmuch);
+ talloc_free (local);
+
+ return ret;
+}
diff --git a/notmuch-setup.c b/notmuch-setup.c
new file mode 100644
index 00000000..65e97599
--- /dev/null
+++ b/notmuch-setup.c
@@ -0,0 +1,250 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2009 Carl Worth
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Carl Worth <cworth@cworth.org>
+ */
+
+#include "notmuch-client.h"
+
+/* 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
+ * count_files). */
+static void
+count_files (const char *path, int *count)
+{
+ DIR *dir;
+ struct dirent *e, *entry = NULL;
+ int entry_length;
+ int err;
+ char *next;
+ struct stat st;
+
+ dir = opendir (path);
+
+ if (dir == NULL) {
+ fprintf (stderr, "Warning: failed to open directory %s: %s\n",
+ path, strerror (errno));
+ goto DONE;
+ }
+
+ 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);
+ goto DONE;
+ }
+
+ 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;
+ }
+
+ if (asprintf (&next, "%s/%s", path, entry->d_name) == -1) {
+ next = NULL;
+ fprintf (stderr, "Error descending from %s to %s: Out of memory\n",
+ path, entry->d_name);
+ continue;
+ }
+
+ stat (next, &st);
+
+ if (S_ISREG (st.st_mode)) {
+ *count = *count + 1;
+ 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);
+ }
+
+ free (next);
+ }
+
+ DONE:
+ if (entry)
+ free (entry);
+
+ closedir (dir);
+}
+
+int
+notmuch_setup_command (unused (void *ctx),
+ unused (int argc), unused (char *argv[]))
+{
+ notmuch_database_t *notmuch = NULL;
+ char *default_path, *mail_directory = NULL;
+ size_t line_size;
+ int count;
+ add_files_state_t add_files_state;
+ double elapsed;
+ struct timeval tv_now;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+ printf ("Welcome to notmuch!\n\n");
+
+ printf ("The goal of notmuch is to help you manage and search your collection of\n"
+ "email, and to efficiently keep up with the flow of email as it comes in.\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"
+ "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"
+ "stored, we recommend you first convert it to maildir format with a utility\n"
+ "such as mb2md. In that case, press Control-C now and run notmuch again\n"
+ "once the conversion is complete.\n\n");
+
+
+ default_path = notmuch_database_default_path ();
+ printf ("Top-level mail directory [%s]: ", default_path);
+ fflush (stdout);
+
+ getline (&mail_directory, &line_size, stdin);
+ chomp_newline (mail_directory);
+
+ printf ("\n");
+
+ if (mail_directory == NULL || strlen (mail_directory) == 0) {
+ if (mail_directory)
+ free (mail_directory);
+ mail_directory = default_path;
+ } else {
+ /* XXX: Instead of telling the user to use an environment
+ * variable here, we should really be writing out a configuration
+ * file and loading that on the next run. */
+ if (strcmp (mail_directory, default_path)) {
+ printf ("Note: Since you are not using the default path, you will want to set\n"
+