1 /* notmuch - Not much of an email program, (just index and search)
3 * Copyright © 2009 Carl Worth
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see http://www.gnu.org/licenses/ .
18 * Author: Carl Worth <cworth@cworth.org>
24 #include <sys/types.h>
32 #define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
34 typedef int (*command_function_t) (int argc, char *argv[]);
36 typedef struct command {
38 command_function_t function;
42 /* Read a line from stdin, without any line-terminator character. The
43 * return value is a newly allocated string. The caller should free()
44 * the string when finished with it.
46 * This function returns NULL if EOF is encountered before any
47 * characters are input (otherwise it returns those characters).
54 GIOStatus g_io_status;
57 GIOChannel *channel = g_io_channel_unix_new (fileno (stdin));
59 g_io_status = g_io_channel_read_line (channel, &result,
60 &length, NULL, &error);
62 if (g_io_status == EOF)
65 if (g_io_status != G_IO_STATUS_NORMAL) {
66 fprintf(stderr, "Read error: %s\n", error->message);
70 if (length && result[length - 1] == '\n')
71 result[length - 1] = '\0';
74 g_io_channel_unref (channel);
78 /* Recursively count all regular files in path and all sub-direcotries
79 * of path. The result is added to *count (which should be
80 * initialized to zero by the top-level caller before calling
83 count_files (const char *path, int *count)
86 struct dirent *entry, *e;
95 fprintf (stderr, "Warning: failed to open directory %s: %s\n",
96 path, strerror (errno));
100 entry_length = offsetof (struct dirent, d_name) +
101 pathconf (path, _PC_NAME_MAX) + 1;
102 entry = malloc (entry_length);
105 err = readdir_r (dir, entry, &e);
107 fprintf (stderr, "Error reading directory: %s\n",
116 /* Skip these special directories to avoid infinite recursion. */
117 if (strcmp (entry->d_name, ".") == 0 ||
118 strcmp (entry->d_name, "..") == 0)
123 next = g_strdup_printf ("%s/%s", path, entry->d_name);
127 if (S_ISREG (st.st_mode))
129 else if (S_ISDIR (st.st_mode))
130 count_files (next, count);
132 if (*count % 1000 == 0) {
133 printf ("Found %d files so far.\r", *count);
146 setup_command (int argc, char *argv[])
148 char *mail_directory;
151 printf ("Welcome to notmuch!\n\n");
153 printf ("The goal of notmuch is to help you manage and search your collection of\n"
154 "email, and to efficiently keep up with the flow of email as it comes in.\n\n");
156 printf ("Notmuch needs to know the top-level directory of your email archive,\n"
157 "(where you already have mail stored and where messages will be delivered\n"
158 "in the future). This directory can contain any number of sub-directories\n"
159 "but the only files it contains should be individual email messages.\n"
160 "Either maildir or mh format directories are fine, but you will want to\n"
161 "move away any auxiliary files maintained by other email programs.\n\n");
163 printf ("Mail storage that uses mbox format, (where one mbox file contains many\n"
164 "messages), will not work with notmuch. If that's how your mail is currently\n"
165 "stored, we recommend you first convert it to maildir format with a utility\n"
166 "such as mb2md. In that case, press Control-C now and run notmuch again\n"
167 "once the conversion is complete.\n\n");
169 printf ("Top-level mail directory [~/mail]: ");
172 mail_directory = read_line ();
174 if (mail_directory == NULL || strlen (mail_directory) == 0) {
178 free (mail_directory);
180 home = getenv ("HOME");
182 fprintf (stderr, "Error: No mail directory provided HOME environment variable is not set.\n");
183 fprintf (stderr, "Cowardly refusing to just guess where your mail might be.\n");
187 mail_directory = g_strdup_printf ("%s/mail", home);
190 printf ("OK. Let's take a look at the mail we can find in the directory\n");
191 printf ("%s ...\n", mail_directory);
194 count_files (mail_directory, &count);
196 printf ("Found %d total files. That's not much mail.\n", count);
198 free (mail_directory);
204 search_command (int argc, char *argv[])
206 fprintf (stderr, "Error: search is not implemented yet.\n");
211 show_command (int argc, char *argv[])
213 fprintf (stderr, "Error: show-thread is not implemented yet.\n");
217 command_t commands[] = {
218 { "setup", setup_command,
219 "Interactively setup notmuch for first use (no arguments).\n"
220 "\t\tInvoking notmuch with no command argument will run setup if\n"
221 "\t\the setup command has not previously been completed." },
222 { "search", search_command,
223 "Search for threads matching the given search terms." },
224 { "show", show_command,
225 "Show the thread with the given thread ID (see 'search')." }
234 fprintf (stderr, "Usage: notmuch <command> [args...]\n");
235 fprintf (stderr, "\n");
236 fprintf (stderr, "Where <command> is one of the following:\n");
237 fprintf (stderr, "\n");
239 for (i = 0; i < ARRAY_SIZE (commands); i++) {
240 command = &commands[i];
242 fprintf (stderr, "\t%s\t%s\n\n", command->name, command->usage);
247 main (int argc, char *argv[])
253 return setup_command (0, NULL);
255 for (i = 0; i < ARRAY_SIZE (commands); i++) {
256 command = &commands[i];
258 if (strcmp (argv[1], command->name) == 0)
259 return (command->function) (argc - 2, &argv[2]);
262 fprintf (stderr, "Error: Unknown command '%s'\n\n", argv[1]);