*/
#include "database-private.h"
-#include "parse-time-vrp.h"
-#include "query-fp.h"
-#include "thread-fp.h"
-#include "regexp-fields.h"
#include "string-util.h"
#include <iostream>
#define STRINGIFY(s) _SUB_STRINGIFY (s)
#define _SUB_STRINGIFY(s) #s
-#if HAVE_XAPIAN_DB_RETRY_LOCK
-#define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
-#else
-#define DB_ACTION Xapian::DB_CREATE_OR_OPEN
-#endif
-
#define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error)
static void
-_log_xapian_exception (const char *where, notmuch_database_t *notmuch, const Xapian::Error error) {
+_log_xapian_exception (const char *where, notmuch_database_t *notmuch, const Xapian::Error error)
+{
_notmuch_database_log (notmuch,
"A Xapian exception occurred at %s: %s\n",
where,
return "Operation requires a database upgrade";
case NOTMUCH_STATUS_PATH_ERROR:
return "Path supplied is illegal for this function";
+ case NOTMUCH_STATUS_IGNORED:
+ return "Argument was ignored";
+ case NOTMUCH_STATUS_ILLEGAL_ARGUMENT:
+ return "Illegal argument for function";
case NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL:
return "Crypto protocol missing, malformed, or unintelligible";
case NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION:
return "Crypto engine initialization failure";
case NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL:
return "Unknown crypto protocol";
+ case NOTMUCH_STATUS_NO_CONFIG:
+ return "No configuration file found";
+ case NOTMUCH_STATUS_NO_DATABASE:
+ return "No database found";
+ case NOTMUCH_STATUS_DATABASE_EXISTS:
+ return "Database exists, not recreated";
+ case NOTMUCH_STATUS_BAD_QUERY_SYNTAX:
+ return "Syntax error in query";
+ case NOTMUCH_STATUS_NO_MAIL_ROOT:
+ return "No mail root found";
default:
case NOTMUCH_STATUS_LAST_STATUS:
return "Unknown error status value";
}
}
-notmuch_status_t
-notmuch_database_create (const char *path, notmuch_database_t **database)
-{
- char *status_string = NULL;
- notmuch_status_t status;
-
- status = notmuch_database_create_verbose (path, database,
- &status_string);
-
- if (status_string) {
- fputs (status_string, stderr);
- free (status_string);
- }
-
- return status;
-}
-
-notmuch_status_t
-notmuch_database_create_verbose (const char *path,
- notmuch_database_t **database,
- char **status_string)
-{
- notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
- notmuch_database_t *notmuch = NULL;
- char *notmuch_path = NULL;
- char *message = NULL;
- struct stat st;
- int err;
-
- if (path == NULL) {
- message = strdup ("Error: Cannot create a database for a NULL path.\n");
- status = NOTMUCH_STATUS_NULL_POINTER;
- goto DONE;
- }
-
- if (path[0] != '/') {
- message = strdup ("Error: Database path must be absolute.\n");
- status = NOTMUCH_STATUS_PATH_ERROR;
- goto DONE;
- }
-
- err = stat (path, &st);
- if (err) {
- IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n",
- path, strerror (errno)));
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- if (! S_ISDIR (st.st_mode)) {
- IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: "
- "Not a directory.\n",
- path));
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- notmuch_path = talloc_asprintf (NULL, "%s/%s", path, ".notmuch");
-
- err = mkdir (notmuch_path, 0755);
-
- if (err) {
- IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
- notmuch_path, strerror (errno)));
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- status = notmuch_database_open_verbose (path,
- NOTMUCH_DATABASE_MODE_READ_WRITE,
- ¬much, &message);
- if (status)
- goto DONE;
-
- /* Upgrade doesn't add these feature to existing databases, but
- * new databases have them. */
- notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
- notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
- notmuch->features |= NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY;
-
- status = notmuch_database_upgrade (notmuch, NULL, NULL);
- if (status) {
- notmuch_database_close (notmuch);
- notmuch = NULL;
- }
-
- DONE:
- if (notmuch_path)
- talloc_free (notmuch_path);
-
- if (message) {
- if (status_string)
- *status_string = message;
- else
- free (message);
- }
- if (database)
- *database = notmuch;
- else
- talloc_free (notmuch);
- return status;
-}
-
notmuch_status_t
_notmuch_database_ensure_writable (notmuch_database_t *notmuch)
{
return NOTMUCH_STATUS_READ_ONLY_DATABASE;
}
+ if (! notmuch->open) {
+ _notmuch_database_log (notmuch, "Cannot write to a closed database.\n");
+ return NOTMUCH_STATUS_CLOSED_DATABASE;
+ }
+
return NOTMUCH_STATUS_SUCCESS;
}
return new_revision;
}
-notmuch_status_t
-notmuch_database_open (const char *path,
- notmuch_database_mode_t mode,
- notmuch_database_t **database)
-{
- char *status_string = NULL;
- notmuch_status_t status;
-
- status = notmuch_database_open_verbose (path, mode, database,
- &status_string);
-
- if (status_string) {
- fputs (status_string, stderr);
- free (status_string);
- }
-
- return status;
-}
-
-notmuch_status_t
-notmuch_database_open_verbose (const char *path,
- notmuch_database_mode_t mode,
- notmuch_database_t **database,
- char **status_string)
-{
- notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
- void *local = talloc_new (NULL);
- notmuch_database_t *notmuch = NULL;
- char *notmuch_path, *xapian_path, *incompat_features;
- char *message = NULL;
- struct stat st;
- int err;
- unsigned int i, version;
- static int initialized = 0;
-
- if (path == NULL) {
- message = strdup ("Error: Cannot open a database for a NULL path.\n");
- status = NOTMUCH_STATUS_NULL_POINTER;
- goto DONE;
- }
-
- if (path[0] != '/') {
- message = strdup ("Error: Database path must be absolute.\n");
- status = NOTMUCH_STATUS_PATH_ERROR;
- goto DONE;
- }
-
- if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
- message = strdup ("Out of memory\n");
- status = NOTMUCH_STATUS_OUT_OF_MEMORY;
- goto DONE;
- }
-
- err = stat (notmuch_path, &st);
- if (err) {
- IGNORE_RESULT (asprintf (&message, "Error opening database at %s: %s\n",
- notmuch_path, strerror (errno)));
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
- message = strdup ("Out of memory\n");
- status = NOTMUCH_STATUS_OUT_OF_MEMORY;
- goto DONE;
- }
-
- /* Initialize the GLib type system and threads */
-#if ! GLIB_CHECK_VERSION (2, 35, 1)
- g_type_init ();
-#endif
-
- /* Initialize gmime */
- if (! initialized) {
- g_mime_init ();
- initialized = 1;
- }
-
- notmuch = talloc_zero (NULL, notmuch_database_t);
- notmuch->exception_reported = false;
- notmuch->status_string = NULL;
- notmuch->path = talloc_strdup (notmuch, path);
-
- strip_trailing (notmuch->path, '/');
-
- notmuch->writable_xapian_db = NULL;
- notmuch->atomic_nesting = 0;
- notmuch->view = 1;
- try {
- string last_thread_id;
- string last_mod;
-
- if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
- notmuch->writable_xapian_db = new Xapian::WritableDatabase (xapian_path,
- DB_ACTION);
- notmuch->xapian_db = notmuch->writable_xapian_db;
- } else {
- notmuch->xapian_db = new Xapian::Database (xapian_path);
- }
-
- /* Check version. As of database version 3, we represent
- * changes in terms of features, so assume a version bump
- * means a dramatically incompatible change. */
- version = notmuch_database_get_version (notmuch);
- if (version > NOTMUCH_DATABASE_VERSION) {
- IGNORE_RESULT (asprintf (&message,
- "Error: Notmuch database at %s\n"
- " has a newer database format version (%u) than supported by this\n"
- " version of notmuch (%u).\n",
- notmuch_path, version, NOTMUCH_DATABASE_VERSION));
- notmuch_database_destroy (notmuch);
- notmuch = NULL;
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- /* Check features. */
- incompat_features = NULL;
- notmuch->features = _notmuch_database_parse_features (
- local, notmuch->xapian_db->get_metadata ("features").c_str (),
- version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
- &incompat_features);
- if (incompat_features) {
- IGNORE_RESULT (asprintf (&message,
- "Error: Notmuch database at %s\n"
- " requires features (%s)\n"
- " not supported by this version of notmuch.\n",
- notmuch_path, incompat_features));
- notmuch_database_destroy (notmuch);
- notmuch = NULL;
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
- last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
- if (last_thread_id.empty ()) {
- notmuch->last_thread_id = 0;
- } else {
- const char *str;
- char *end;
-
- str = last_thread_id.c_str ();
- notmuch->last_thread_id = strtoull (str, &end, 16);
- if (*end != '\0')
- INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
- }
-
- /* Get current highest revision number. */
- last_mod = notmuch->xapian_db->get_value_upper_bound (
- NOTMUCH_VALUE_LAST_MOD);
- if (last_mod.empty ())
- notmuch->revision = 0;
- else
- notmuch->revision = Xapian::sortable_unserialise (last_mod);
- notmuch->uuid = talloc_strdup (
- notmuch, notmuch->xapian_db->get_uuid ().c_str ());
-
- notmuch->query_parser = new Xapian::QueryParser;
- notmuch->term_gen = new Xapian::TermGenerator;
- notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
- notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
- notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP, "date:");
- notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
- notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
- notmuch->query_parser->set_database (*notmuch->xapian_db);
- notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
- notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
- notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
- notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
- notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
-
- status = _notmuch_database_setup_standard_query_fields (notmuch);
- if (status)
- goto DONE;
-
- status = _notmuch_database_setup_user_query_fields (notmuch);
- if (status)
- goto DONE;
-
- } catch (const Xapian::Error &error) {
- IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
- error.get_msg ().c_str ()));
- notmuch_database_destroy (notmuch);
- notmuch = NULL;
- status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
- }
-
- DONE:
- talloc_free (local);
-
- if (message) {
- if (status_string)
- *status_string = message;
- else
- free (message);
- }
-
- if (database)
- *database = notmuch;
- else
- talloc_free (notmuch);
-
- if (notmuch)
- notmuch->open = true;
-
- return status;
-}
-
notmuch_status_t
notmuch_database_close (notmuch_database_t *notmuch)
{
* close it. Thus, we explicitly close it here. */
if (notmuch->open) {
try {
- /* If there's an outstanding transaction, it's unclear if
- * closing the Xapian database commits everything up to
- * that transaction, or may discard committed (but
- * unflushed) transactions. To be certain, explicitly
- * cancel any outstanding transaction before closing. */
- if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_WRITE &&
- notmuch->atomic_nesting)
- notmuch->writable_xapian_db->cancel_transaction ();
-
/* Close the database. This implicitly flushes
- * outstanding changes. */
+ * outstanding changes. If there is an open (non-flushed)
+ * transaction, ALL pending changes will be discarded */
notmuch->xapian_db->close ();
} catch (const Xapian::Error &error) {
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
if (! notmuch->exception_reported) {
- _notmuch_database_log (notmuch, "Error: A Xapian exception occurred closing database: %s\n",
+ _notmuch_database_log (notmuch,
+ "Error: A Xapian exception occurred closing database: %s\n",
error.get_msg ().c_str ());
}
}
return status;
}
-notmuch_status_t
-_notmuch_database_reopen (notmuch_database_t *notmuch)
-{
- if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_ONLY)
- return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
-
- try {
- notmuch->xapian_db->reopen ();
- } catch (const Xapian::Error &error) {
- if (! notmuch->exception_reported) {
- _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
- error.get_msg ().c_str ());
- notmuch->exception_reported = true;
- }
- return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
- }
-
- notmuch->view++;
-
- return NOTMUCH_STATUS_SUCCESS;
-}
-
static int
unlink_cb (const char *path,
unused (const struct stat *sb),
notmuch_compact_status_cb_t status_cb,
void *closure)
{
- void *local;
- char *notmuch_path, *xapian_path, *compact_xapian_path;
notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
notmuch_database_t *notmuch = NULL;
- struct stat statbuf;
- bool keep_backup;
char *message = NULL;
- local = talloc_new (NULL);
- if (! local)
- return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
- ret = notmuch_database_open_verbose (path,
- NOTMUCH_DATABASE_MODE_READ_WRITE,
- ¬much,
- &message);
+ ret = notmuch_database_open_with_config (path,
+ NOTMUCH_DATABASE_MODE_READ_WRITE,
+ "",
+ NULL,
+ ¬much,
+ &message);
if (ret) {
if (status_cb) status_cb (message, closure);
- goto DONE;
+ return ret;
}
- if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
- ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
- goto DONE;
- }
+ _notmuch_config_cache (notmuch, NOTMUCH_CONFIG_DATABASE_PATH, path);
- if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
- ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+ return notmuch_database_compact_db (notmuch,
+ backup_path,
+ status_cb,
+ closure);
+}
+
+notmuch_status_t
+notmuch_database_compact_db (notmuch_database_t *notmuch,
+ const char *backup_path,
+ notmuch_compact_status_cb_t status_cb,
+ void *closure)
+{
+ void *local;
+ const char *xapian_path, *compact_xapian_path;
+ const char *path;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+ struct stat statbuf;
+ bool keep_backup;
+ char *message;
+
+ ret = _notmuch_database_ensure_writable (notmuch);
+ if (ret)
+ return ret;
+
+ path = notmuch_config_get (notmuch, NOTMUCH_CONFIG_DATABASE_PATH);
+ if (! path)
+ return NOTMUCH_STATUS_PATH_ERROR;
+
+ local = talloc_new (NULL);
+ if (! local)
+ return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+ ret = _notmuch_choose_xapian_path (local, path, &xapian_path, &message);
+ if (ret)
goto DONE;
- }
if (! (compact_xapian_path = talloc_asprintf (local, "%s.compact", xapian_path))) {
ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
try {
NotmuchCompactor compactor (status_cb, closure);
- notmuch->xapian_db->compact (compact_xapian_path, Xapian::DBCOMPACT_NO_RENUMBER, 0, compactor);
+ notmuch->xapian_db->compact (compact_xapian_path, Xapian::DBCOMPACT_NO_RENUMBER, 0,
+ compactor);
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg ().c_str ());
ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
notmuch_database_destroy (notmuch_database_t *notmuch)
{
notmuch_status_t status;
+ const char *talloc_report;
+
+ talloc_report = getenv ("NOTMUCH_TALLOC_REPORT");
+ if (talloc_report && strcmp (talloc_report, "") != 0) {
+ FILE *report = fopen (talloc_report, "a");
+ if (report) {
+ talloc_report_full (notmuch, report);
+ }
+ }
status = notmuch_database_close (notmuch);
notmuch->date_range_processor = NULL;
delete notmuch->last_mod_range_processor;
notmuch->last_mod_range_processor = NULL;
+ delete notmuch->stemmer;
+ notmuch->stemmer = NULL;
talloc_free (notmuch);
const char *
notmuch_database_get_path (notmuch_database_t *notmuch)
{
- return notmuch->path;
+ return notmuch_config_get (notmuch, NOTMUCH_CONFIG_DATABASE_PATH);
}
unsigned int
*/
notmuch_status_t
notmuch_database_upgrade (notmuch_database_t *notmuch,
- void (*progress_notify) (void *closure,
- double progress),
+ void (*progress_notify)(void *closure,
+ double progress),
void *closure)
{
void *local = talloc_new (NULL);
notmuch_query_t *query = NULL;
unsigned int count = 0, total = 0;
- status = _notmuch_database_ensure_writable (notmuch);
- if (status)
- return status;
+ if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_WRITE)
+ return NOTMUCH_STATUS_READ_ONLY_DATABASE;
db = notmuch->writable_xapian_db;
if (private_status) {
_notmuch_database_log (notmuch,
"Upgrade failed while creating ghost messages.\n");
- status = COERCE_STATUS (private_status, "Unexpected status from _notmuch_message_initialize_ghost");
+ status = COERCE_STATUS (private_status,
+ "Unexpected status from _notmuch_message_initialize_ghost");
goto DONE;
}
db = notmuch->writable_xapian_db;
try {
db->commit_transaction ();
-
- /* This is a hack for testing. Xapian never flushes on a
- * non-flushed commit, even if the flush threshold is 1.
- * However, we rely on flushing to test atomicity. */
+ notmuch->transaction_count++;
+
+ /* Xapian never flushes on a non-flushed commit, even if the
+ * flush threshold is 1. However, we rely on flushing to test
+ * atomicity. On the other hand, we can't straight replace
+ * XAPIAN_FLUSH_THRESHOLD with our autocommit counter, because
+ * the former also applies outside notmuch atomic
+ * commits. Hence the follow complicated test */
const char *thresh = getenv ("XAPIAN_FLUSH_THRESHOLD");
- if (thresh && atoi (thresh) == 1)
+ if ((notmuch->transaction_threshold > 0 &&
+ notmuch->transaction_count >= notmuch->transaction_threshold) ||
+ (thresh && atoi (thresh) == 1)) {
db->commit ();
+ notmuch->transaction_count = 0;
+ }
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n",
error.get_msg ().c_str ());
const char *db_path, *relative;
unsigned int db_path_len;
- db_path = notmuch_database_get_path (notmuch);
+ db_path = notmuch_config_get (notmuch, NOTMUCH_CONFIG_MAIL_ROOT);
db_path_len = strlen (db_path);
relative = path;
&message);
if (status == NOTMUCH_STATUS_SUCCESS && message) {
- status = _notmuch_message_remove_filename (message, filename);
+ if (notmuch_message_count_files (message) > 1) {
+ status = _notmuch_message_remove_filename (message, filename);
+ }
if (status == NOTMUCH_STATUS_SUCCESS)
- _notmuch_message_delete (message);
+ status = _notmuch_message_delete (message);
else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
_notmuch_message_sync (message);
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
}
} catch (const Xapian::Error &error) {
- _notmuch_database_log (notmuch, "Error: A Xapian exception occurred finding message by filename: %s\n",
+ _notmuch_database_log (notmuch,
+ "Error: A Xapian exception occurred finding message by filename: %s\n",
error.get_msg ().c_str ());
notmuch->exception_reported = true;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
{
return notmuch->status_string;
}
+
+bool
+_notmuch_database_indexable_as_text (notmuch_database_t *notmuch, const char *mime_string)
+{
+ for (size_t i = 0; i < notmuch->index_as_text_length; i++) {
+ if (regexec (¬much->index_as_text[i], mime_string, 0, NULL, 0) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}