notmuch/lib/open.cc
David Bremner 4743e87c2c lib: cache configuration information from database
The main goal is to allow configuration information to be temporarily
overridden by a separate config file. That will require further
changes not in this commit.

The performance impact is unclear, and will depend on the balance
between number of queries and number of distinct metadata items read
on the first call to n_d_get_config.
2021-02-06 18:56:05 -04:00

223 lines
6.7 KiB
C++

#include <unistd.h>
#include "database-private.h"
#include "parse-time-vrp.h"
#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
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 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);
notmuch->config = NULL;
strip_trailing (notmuch->path, '/');
notmuch->writable_xapian_db = NULL;
notmuch->atomic_nesting = 0;
notmuch->view = 1;
try {
std::string last_thread_id;
std::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);
/* Configuration information is needed to set up query parser */
status = _notmuch_config_load_from_database (notmuch);
if (status)
goto DONE;
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;
}