* 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/ .
+ * along with this program. If not, see https://www.gnu.org/licenses/ .
*
* Author: Carl Worth <cworth@cworth.org>
*/
#include "notmuch-client.h"
+#include "tag-util.h"
#include <unistd.h>
_filename_node_t **tail;
} _filename_list_t;
+enum verbosity {
+ VERBOSITY_QUIET,
+ VERBOSITY_NORMAL,
+ VERBOSITY_VERBOSE,
+};
+
typedef struct {
int output_is_a_tty;
- notmuch_bool_t verbose;
+ enum verbosity verbosity;
notmuch_bool_t debug;
const char **new_tags;
size_t new_tags_length;
char *abspath;
int err, saved_errno;
-#ifdef _DIRENT_HAVE_D_TYPE
+#if HAVE_D_TYPE
/* Mapping from d_type to stat mode_t. We omit DT_LNK so that
* we'll fall through to stat and get the real file type. */
static const mode_t modes[] = {
fprintf (stderr, "Note: Ignoring non-mail file: %s\n", filename);
break;
/* Fatal issues. Don't process anymore. */
+ case NOTMUCH_STATUS_FILE_ERROR:
+ fprintf (stderr, "Unexpected error with file %s\n", filename);
+ (void) print_status_database ("add_file", notmuch, status);
+ goto DONE;
case NOTMUCH_STATUS_READ_ONLY_DATABASE:
case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
case NOTMUCH_STATUS_OUT_OF_MEMORY:
const char *path,
add_files_state_t *state)
{
- DIR *dir = NULL;
struct dirent *entry = NULL;
char *next = NULL;
time_t fs_mtime, db_mtime;
*/
if (_entry_in_ignore_list (entry->d_name, state)) {
if (state->debug)
- printf ("(D) add_files_recursive, pass 1: explicitly ignoring %s/%s\n",
+ printf ("(D) add_files, pass 1: explicitly ignoring %s/%s\n",
path, entry->d_name);
continue;
}
/* Ignore files & directories user has configured to be ignored */
if (_entry_in_ignore_list (entry->d_name, state)) {
if (state->debug)
- printf ("(D) add_files_recursive, pass 2: explicitly ignoring %s/%s\n",
- path,
- entry->d_name);
+ printf ("(D) add_files, pass 2: explicitly ignoring %s/%s\n",
+ path, entry->d_name);
continue;
}
"%s/%s", path,
notmuch_filenames_get (db_files));
+ if (state->debug)
+ printf ("(D) add_files, pass 2: queuing passed file %s for deletion from database\n",
+ absolute);
+
_filename_list_add (state->removed_files, absolute);
notmuch_filenames_move_to_next (db_files);
{
char *absolute = talloc_asprintf (state->removed_directories,
"%s/%s", path, filename);
+ if (state->debug)
+ printf ("(D) add_files, pass 2: queuing passed directory %s for deletion from database\n",
+ absolute);
_filename_list_add (state->removed_directories, absolute);
}
state->processed_files++;
- if (state->verbose) {
+ if (state->verbosity >= VERBOSITY_VERBOSE) {
if (state->output_is_a_tty)
printf("\r\033[K");
- printf ("%i/%i: %s",
- state->processed_files,
- state->total_files,
+ printf ("%i/%i: %s", state->processed_files, state->total_files,
next);
putchar((state->output_is_a_tty) ? '\r' : '\n');
char *absolute = talloc_asprintf (state->removed_files,
"%s/%s", path,
notmuch_filenames_get (db_files));
+ if (state->debug)
+ printf ("(D) add_files, pass 3: queuing leftover file %s for deletion from database\n",
+ absolute);
_filename_list_add (state->removed_files, absolute);
"%s/%s", path,
notmuch_filenames_get (db_subdirs));
+ if (state->debug)
+ printf ("(D) add_files, pass 3: queuing leftover directory %s for deletion from database\n",
+ absolute);
+
_filename_list_add (state->removed_directories, absolute);
notmuch_filenames_move_to_next (db_subdirs);
DONE:
if (next)
talloc_free (next);
- if (dir)
- closedir (dir);
if (fs_entries) {
for (i = 0; i < num_fs_entries; i++)
free (fs_entries[i]);
struct sigaction action;
struct itimerval timerval;
- /* Setup our handler for SIGALRM */
+ /* Set up our handler for SIGALRM */
memset (&action, 0, sizeof (struct sigaction));
action.sa_handler = handle_sigalrm;
sigemptyset (&action.sa_mask);
char *next;
struct dirent **fs_entries = NULL;
int num_fs_entries = scandir (path, &fs_entries, 0, dirent_sort_inode);
- int entry_type;
- int i = 0;
+ int entry_type, i;
if (num_fs_entries == -1) {
fprintf (stderr, "Warning: failed to open directory %s: %s\n",
goto DONE;
}
- while (!interrupted) {
- if (i == num_fs_entries)
- break;
-
- entry = fs_entries[i++];
+ for (i = 0; i < num_fs_entries && ! interrupted; i++) {
+ entry = fs_entries[i];
/* Ignore special directories to avoid infinite recursion.
* Also ignore the .notmuch directory and files/directories
entry_type = dirent_type (path, entry);
if (entry_type == S_IFREG) {
*count = *count + 1;
- if (*count % 1000 == 0) {
+ if (*count % 1000 == 0 && state->verbosity >= VERBOSITY_NORMAL) {
printf ("Found %d files so far.\r", *count);
fflush (stdout);
}
goto DONE;
}
+ status = notmuch_directory_delete (directory);
+
DONE:
- notmuch_directory_destroy (directory);
+ if (status)
+ notmuch_directory_destroy (directory);
return status;
}
+static void
+print_results (const add_files_state_t *state)
+{
+ double elapsed;
+ struct timeval tv_now;
+
+ gettimeofday (&tv_now, NULL);
+ elapsed = notmuch_time_elapsed (state->tv_start, tv_now);
+
+ if (state->processed_files) {
+ printf ("Processed %d %s in ", state->processed_files,
+ state->processed_files == 1 ? "file" : "total files");
+ notmuch_time_print_formatted_seconds (elapsed);
+ if (elapsed > 1)
+ printf (" (%d files/sec.).\033[K\n",
+ (int) (state->processed_files / elapsed));
+ else
+ printf (".\033[K\n");
+ }
+
+ if (state->added_messages)
+ printf ("Added %d new %s to the database.", state->added_messages,
+ state->added_messages == 1 ? "message" : "messages");
+ else
+ printf ("No new mail.");
+
+ if (state->removed_messages)
+ printf (" Removed %d %s.", state->removed_messages,
+ state->removed_messages == 1 ? "message" : "messages");
+
+ if (state->renamed_messages)
+ printf (" Detected %d file %s.", state->renamed_messages,
+ state->renamed_messages == 1 ? "rename" : "renames");
+
+ printf ("\n");
+}
+
int
notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
{
notmuch_database_t *notmuch;
- add_files_state_t add_files_state;
- double elapsed;
- struct timeval tv_now, tv_start;
+ add_files_state_t add_files_state = {
+ .verbosity = VERBOSITY_NORMAL,
+ .debug = FALSE,
+ .output_is_a_tty = isatty (fileno (stdout)),
+ };
+ struct timeval tv_start;
int ret = 0;
struct stat st;
const char *db_path;
struct sigaction action;
_filename_node_t *f;
int opt_index;
- int i;
+ unsigned int i;
notmuch_bool_t timer_is_active = FALSE;
notmuch_bool_t no_hooks = FALSE;
-
- add_files_state.verbose = FALSE;
- add_files_state.debug = FALSE;
- add_files_state.output_is_a_tty = isatty (fileno (stdout));
+ notmuch_bool_t quiet = FALSE, verbose = FALSE;
+ notmuch_status_t status;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_BOOLEAN, &add_files_state.verbose, "verbose", 'v', 0 },
+ { NOTMUCH_OPT_BOOLEAN, &quiet, "quiet", 'q', 0 },
+ { NOTMUCH_OPT_BOOLEAN, &verbose, "verbose", 'v', 0 },
{ NOTMUCH_OPT_BOOLEAN, &add_files_state.debug, "debug", 'd', 0 },
{ NOTMUCH_OPT_BOOLEAN, &no_hooks, "no-hooks", 'n', 0 },
+ { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
{ 0, 0, 0, 0, 0 }
};
if (opt_index < 0)
return EXIT_FAILURE;
+ notmuch_process_shared_options (argv[0]);
+
+ /* quiet trumps verbose */
+ if (quiet)
+ add_files_state.verbosity = VERBOSITY_QUIET;
+ else if (verbose)
+ add_files_state.verbosity = VERBOSITY_VERBOSE;
+
add_files_state.new_tags = notmuch_config_get_new_tags (config, &add_files_state.new_tags_length);
add_files_state.new_ignore = notmuch_config_get_new_ignore (config, &add_files_state.new_ignore_length);
add_files_state.synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
db_path = notmuch_config_get_database_path (config);
+ for (i = 0; i < add_files_state.new_tags_length; i++) {
+ const char *error_msg;
+
+ error_msg = illegal_tag (add_files_state.new_tags[i], FALSE);
+ if (error_msg) {
+ fprintf (stderr, "Error: tag '%s' in new.tags: %s\n",
+ add_files_state.new_tags[i], error_msg);
+ return EXIT_FAILURE;
+ }
+ }
+
if (!no_hooks) {
ret = notmuch_run_hook (db_path, "pre-new");
if (ret)
if (interrupted)
return EXIT_FAILURE;
- printf ("Found %d total files (that's not much mail).\n", count);
+ if (add_files_state.verbosity >= VERBOSITY_NORMAL)
+ printf ("Found %d total files (that's not much mail).\n", count);
if (notmuch_database_create (db_path, ¬much))
return EXIT_FAILURE;
add_files_state.total_files = count;
} else {
- if (notmuch_database_open (db_path, NOTMUCH_DATABASE_MODE_READ_WRITE,
- ¬much))
+ char *status_string = NULL;
+ if (notmuch_database_open_verbose (db_path, NOTMUCH_DATABASE_MODE_READ_WRITE,
+ ¬much, &status_string)) {
+ if (status_string) {
+ fputs (status_string, stderr);
+ free (status_string);
+ }
return EXIT_FAILURE;
+ }
+
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
if (notmuch_database_needs_upgrade (notmuch)) {
- printf ("Welcome to a new version of notmuch! Your database will now be upgraded.\n");
+ time_t now = time (NULL);
+ struct tm *gm_time = gmtime (&now);
+
+ /* since dump files are written atomically, the amount of
+ * harm from overwriting one within a second seems
+ * relatively small. */
+
+ const char *backup_name =
+ talloc_asprintf (notmuch, "%s/dump-%04d%02d%02dT%02d%02d%02d.gz",
+ dot_notmuch_path,
+ gm_time->tm_year + 1900,
+ gm_time->tm_mon + 1,
+ gm_time->tm_mday,
+ gm_time->tm_hour,
+ gm_time->tm_min,
+ gm_time->tm_sec);
+
+ if (add_files_state.verbosity >= VERBOSITY_NORMAL) {
+ printf ("Welcome to a new version of notmuch! Your database will now be upgraded.\n");
+ printf ("This process is safe to interrupt.\n");
+ printf ("Backing up tags to %s...\n", backup_name);
+ }
+
+ if (notmuch_database_dump (notmuch, backup_name, "",
+ DUMP_FORMAT_BATCH_TAG, DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS, TRUE)) {
+ fprintf (stderr, "Backup failed. Aborting upgrade.");
+ return EXIT_FAILURE;
+ }
+
gettimeofday (&add_files_state.tv_start, NULL);
- notmuch_database_upgrade (notmuch, upgrade_print_progress,
- &add_files_state);
- printf ("Your notmuch database has now been upgraded to database format version %u.\n",
- notmuch_database_get_version (notmuch));
+ status = notmuch_database_upgrade (
+ notmuch,
+ add_files_state.verbosity >= VERBOSITY_NORMAL ? upgrade_print_progress : NULL,
+ &add_files_state);
+ if (status) {
+ printf ("Upgrade failed: %s\n",
+ notmuch_status_to_string (status));
+ notmuch_database_destroy (notmuch);
+ return EXIT_FAILURE;
+ }
+ if (add_files_state.verbosity >= VERBOSITY_NORMAL)
+ printf ("Your notmuch database has now been upgraded.\n");
}
add_files_state.total_files = 0;
if (notmuch == NULL)
return EXIT_FAILURE;
- /* Setup our handler for SIGINT. We do this after having
+ /* Set up our handler for SIGINT. We do this after having
* potentially done a database upgrade we this interrupt handler
* won't support. */
memset (&action, 0, sizeof (struct sigaction));
talloc_free (dot_notmuch_path);
dot_notmuch_path = NULL;
- add_files_state.processed_files = 0;
- add_files_state.added_messages = 0;
- add_files_state.removed_messages = add_files_state.renamed_messages = 0;
gettimeofday (&add_files_state.tv_start, NULL);
add_files_state.removed_files = _filename_list_create (config);
add_files_state.removed_directories = _filename_list_create (config);
add_files_state.directory_mtimes = _filename_list_create (config);
- if (! debugger_is_active () && add_files_state.output_is_a_tty
- && ! add_files_state.verbose) {
+ if (add_files_state.verbosity == VERBOSITY_NORMAL &&
+ add_files_state.output_is_a_tty && ! debugger_is_active ()) {
setup_progress_printing_timer ();
timer_is_active = TRUE;
}
}
for (f = add_files_state.directory_mtimes->head; f && !interrupted; f = f->next) {
- notmuch_status_t status;
notmuch_directory_t *directory;
status = notmuch_database_get_directory (notmuch, f->filename, &directory);
if (status == NOTMUCH_STATUS_SUCCESS && directory) {
if (timer_is_active)
stop_progress_printing_timer ();
- 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.).\033[K\n",
- (int) (add_files_state.processed_files / elapsed));
- } else {
- printf (".\033[K\n");
- }
- }
-
- if (add_files_state.added_messages) {
- printf ("Added %d new %s to the database.",
- add_files_state.added_messages,
- add_files_state.added_messages == 1 ?
- "message" : "messages");
- } else {
- printf ("No new mail.");
- }
-
- if (add_files_state.removed_messages) {
- printf (" Removed %d %s.",
- add_files_state.removed_messages,
- add_files_state.removed_messages == 1 ? "message" : "messages");
- }
-
- if (add_files_state.renamed_messages) {
- printf (" Detected %d file %s.",
- add_files_state.renamed_messages,
- add_files_state.renamed_messages == 1 ? "rename" : "renames");
- }
-
- printf ("\n");
+ if (add_files_state.verbosity >= VERBOSITY_NORMAL)
+ print_results (&add_files_state);
if (ret)
fprintf (stderr, "Note: A fatal error was encountered: %s\n",