/* 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 */ #include "notmuch-client.h" #include notmuch_indexing_context_t *indexing_ctx; static void handle_sigalrm (unused (int signal)) { indexing_ctx->print_progress = 1; } static volatile sig_atomic_t interrupted; static void handle_sigint (unused (int sig)) { ssize_t ignored; static char msg[] = "Stopping... \n"; ignored = write(2, msg, sizeof(msg)-1); indexing_ctx->interrupted = 1; } struct priv_ctx { struct timeval tv_start; int output_is_a_tty; }; static void add_files_print_progress (notmuch_indexing_context_t *state) { struct priv_ctx *state2 = state->priv; struct timeval tv_now; double elapsed_overall, rate_overall; gettimeofday (&tv_now, NULL); elapsed_overall = notmuch_time_elapsed (state2->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); } static void add_files_print_verbose (notmuch_indexing_context_t *state, const char *filename) { struct priv_ctx *state2 = state->priv; if (state2->output_is_a_tty) printf("\r\033[K"); printf ("%i/%i: %s", state->processed_files, state->total_files, filename); putchar((state2->output_is_a_tty) ? '\r' : '\n'); fflush (stdout); } /* 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. */ static notmuch_status_t add_files (notmuch_mailstore_t *mailstore, const char *path, notmuch_indexing_context_t *state) /* FIXME: rename */ { struct priv_ctx *state2 = state->priv; notmuch_status_t status; struct sigaction action; struct itimerval timerval; notmuch_bool_t timer_is_active = FALSE; struct stat st; state->print_progress = 0; state->print_progress_cb = add_files_print_progress; state->print_verbose_cb = add_files_print_verbose; if (state2->output_is_a_tty && ! debugger_is_active () && ! state->verbose) { /* 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); timer_is_active = TRUE; } 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; } status = notmuch_mailstore_index_new (mailstore, path, state); if (timer_is_active) { /* 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; } static void upgrade_print_progress (void *closure, double progress) { notmuch_indexing_context_t *state = closure; struct priv_ctx *state2 = state->priv; printf ("Upgrading database: %.2f%% complete", progress * 100.0); if (progress > 0) { struct timeval tv_now; double elapsed, time_remaining; gettimeofday (&tv_now, NULL); elapsed = notmuch_time_elapsed (state2->tv_start, tv_now); time_remaining = (elapsed / progress) * (1.0 - progress); printf (" ("); notmuch_time_print_formatted_seconds (time_remaining); printf (" remaining)"); } printf (". \r"); fflush (stdout); } int notmuch_new_command (void *ctx, int argc, char *argv[]) { notmuch_config_t *config; notmuch_database_t *notmuch; struct priv_ctx state2; notmuch_indexing_context_t add_files_state; /* FIXME: Rename */ double elapsed; struct timeval tv_now; int ret = 0; struct stat st; const char *db_path; char *dot_notmuch_path; struct sigaction action; int i; notmuch_mailstore_t *mailstore; indexing_ctx = &add_files_state; add_files_state.verbose = 0; add_files_state.priv = &state2; state2.output_is_a_tty = isatty (fileno (stdout)); for (i = 0; i < argc && argv[i][0] == '-'; i++) { if (STRNCMP_LITERAL (argv[i], "--verbose") == 0) { add_files_state.verbose = 1; } else { fprintf (stderr, "Unrecognized option: %s\n", argv[i]); return 1; } } config = notmuch_config_open (ctx, NULL, NULL); if (config == NULL) return 1; db_path = notmuch_config_get_database_path (config); mailstore = notmuch_config_get_mailstore (config); dot_notmuch_path = talloc_asprintf (ctx, "%s/%s", db_path, ".notmuch"); if (stat (dot_notmuch_path, &st)) { int count; count = 0; notmuch_mailstore_count_files (mailstore, db_path, &count, &interrupted); if (interrupted) return 1; printf ("Found %d total files (that's not much mail).\n", count); notmuch = notmuch_database_create (db_path, mailstore); add_files_state.total_files = count; } else { notmuch = notmuch_database_open (db_path, NOTMUCH_DATABASE_MODE_READ_WRITE, mailstore); if (notmuch == NULL) return 1; if (notmuch_database_needs_upgrade (notmuch)) { printf ("Welcome to a new version of notmuch! Your database will now be upgraded.\n"); gettimeofday (&state2.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)); } add_files_state.total_files = 0; } if (notmuch == NULL) return 1; /* Setup our handler for SIGINT. We do this after having * potentially done a database upgrade we this interrupt handler * won't support. */ add_files_state.interrupted = 0; memset (&action, 0, sizeof (struct sigaction)); action.sa_handler = handle_sigint; sigemptyset (&action.sa_mask); action.sa_flags = SA_RESTART; sigaction (SIGINT, &action, NULL); talloc_free (dot_notmuch_path); dot_notmuch_path = NULL; add_files_state.processed_files = 0; add_files_state.added_messages = 0; gettimeofday (&state2.tv_start, NULL); ret = add_files (mailstore, db_path, &add_files_state); gettimeofday (&tv_now, NULL); elapsed = notmuch_time_elapsed (state2.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.", add_files_state.added_messages, add_files_state.added_messages == 1 ? "message" : "messages"); } else { printf ("No new mail."); } if (add_files_state.removed_files) { printf (" Removed %d %s.", add_files_state.removed_files, add_files_state.removed_files == 1 ? "message" : "messages"); } if (add_files_state.renamed_files) { printf (" Detected %d file %s.", add_files_state.renamed_files, add_files_state.renamed_files == 1 ? "rename" : "renames"); } printf ("\n"); if (ret) { printf ("\nNote: At least one error was encountered: %s\n", notmuch_status_to_string (ret)); } notmuch_database_close (notmuch); return ret || interrupted; }