mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-12-22 17:34:54 +01:00
3f5809bf28
We pre-parse into a list of compiled regular expressions to avoid calling regexc on the hot (indexing) path. As explained in the code comment, this cannot be done lazily with reasonable error reporting, at least not without touching a lot of the code in index.cc.
990 lines
26 KiB
C++
990 lines
26 KiB
C++
#include <unistd.h>
|
|
#include <libgen.h>
|
|
|
|
#include "database-private.h"
|
|
#include "parse-time-vrp.h"
|
|
#include "lastmod-fp.h"
|
|
#include "path-util.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_with_config (path, mode, "", NULL,
|
|
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)
|
|
{
|
|
return notmuch_database_open_with_config (path, mode, "", NULL,
|
|
database, status_string);
|
|
}
|
|
|
|
static const char *
|
|
_xdg_dir (void *ctx,
|
|
const char *xdg_root_variable,
|
|
const char *xdg_prefix,
|
|
const char *profile_name)
|
|
{
|
|
const char *xdg_root = getenv (xdg_root_variable);
|
|
|
|
if (! xdg_root) {
|
|
const char *home = getenv ("HOME");
|
|
|
|
if (! home) return NULL;
|
|
|
|
xdg_root = talloc_asprintf (ctx,
|
|
"%s/%s",
|
|
home,
|
|
xdg_prefix);
|
|
}
|
|
|
|
if (! profile_name)
|
|
profile_name = getenv ("NOTMUCH_PROFILE");
|
|
|
|
if (! profile_name)
|
|
profile_name = "default";
|
|
|
|
return talloc_asprintf (ctx,
|
|
"%s/notmuch/%s",
|
|
xdg_root,
|
|
profile_name);
|
|
}
|
|
|
|
static notmuch_status_t
|
|
_choose_dir (notmuch_database_t *notmuch,
|
|
const char *profile,
|
|
notmuch_config_key_t key,
|
|
const char *xdg_var,
|
|
const char *xdg_subdir,
|
|
const char *subdir,
|
|
char **message = NULL)
|
|
{
|
|
const char *parent;
|
|
const char *dir;
|
|
struct stat st;
|
|
int err;
|
|
|
|
dir = notmuch_config_get (notmuch, key);
|
|
|
|
if (dir)
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
|
|
parent = _xdg_dir (notmuch, xdg_var, xdg_subdir, profile);
|
|
if (! parent)
|
|
return NOTMUCH_STATUS_PATH_ERROR;
|
|
|
|
dir = talloc_asprintf (notmuch, "%s/%s", parent, subdir);
|
|
|
|
err = stat (dir, &st);
|
|
if (err) {
|
|
if (errno == ENOENT) {
|
|
char *notmuch_path = dirname (talloc_strdup (notmuch, notmuch->xapian_path));
|
|
dir = talloc_asprintf (notmuch, "%s/%s", notmuch_path, subdir);
|
|
} else {
|
|
IGNORE_RESULT (asprintf (message, "Error: Cannot stat %s: %s.\n",
|
|
dir, strerror (errno)));
|
|
return NOTMUCH_STATUS_FILE_ERROR;
|
|
}
|
|
}
|
|
|
|
_notmuch_config_cache (notmuch, key, dir);
|
|
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
static notmuch_status_t
|
|
_load_key_file (notmuch_database_t *notmuch,
|
|
const char *path,
|
|
const char *profile,
|
|
GKeyFile **key_file)
|
|
{
|
|
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
|
|
|
|
if (path && EMPTY_STRING (path))
|
|
goto DONE;
|
|
|
|
if (! path)
|
|
path = getenv ("NOTMUCH_CONFIG");
|
|
|
|
if (path)
|
|
path = talloc_strdup (notmuch, path);
|
|
else {
|
|
const char *dir = _xdg_dir (notmuch, "XDG_CONFIG_HOME", ".config", profile);
|
|
|
|
if (dir) {
|
|
path = talloc_asprintf (notmuch, "%s/config", dir);
|
|
if (access (path, R_OK) != 0)
|
|
path = NULL;
|
|
}
|
|
}
|
|
|
|
if (! path) {
|
|
const char *home = getenv ("HOME");
|
|
|
|
path = talloc_asprintf (notmuch, "%s/.notmuch-config", home);
|
|
|
|
if (! profile)
|
|
profile = getenv ("NOTMUCH_PROFILE");
|
|
|
|
if (profile)
|
|
path = talloc_asprintf (notmuch, "%s.%s", path, profile);
|
|
}
|
|
|
|
*key_file = g_key_file_new ();
|
|
if (! g_key_file_load_from_file (*key_file, path, G_KEY_FILE_NONE, NULL)) {
|
|
status = NOTMUCH_STATUS_NO_CONFIG;
|
|
}
|
|
|
|
DONE:
|
|
if (path)
|
|
notmuch->config_path = path;
|
|
|
|
return status;
|
|
}
|
|
|
|
static notmuch_status_t
|
|
_db_dir_exists (const char *database_path, char **message)
|
|
{
|
|
struct stat st;
|
|
int err;
|
|
|
|
err = stat (database_path, &st);
|
|
if (err) {
|
|
IGNORE_RESULT (asprintf (message, "Error: Cannot open database at %s: %s.\n",
|
|
database_path, strerror (errno)));
|
|
return NOTMUCH_STATUS_FILE_ERROR;
|
|
}
|
|
|
|
if (! S_ISDIR (st.st_mode)) {
|
|
IGNORE_RESULT (asprintf (message, "Error: Cannot open database at %s: "
|
|
"Not a directory.\n",
|
|
database_path));
|
|
return NOTMUCH_STATUS_FILE_ERROR;
|
|
}
|
|
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
static notmuch_status_t
|
|
_choose_database_path (notmuch_database_t *notmuch,
|
|
const char *profile,
|
|
GKeyFile *key_file,
|
|
const char **database_path,
|
|
char **message)
|
|
{
|
|
notmuch_status_t status;
|
|
|
|
if (! *database_path) {
|
|
*database_path = getenv ("NOTMUCH_DATABASE");
|
|
}
|
|
|
|
if (! *database_path && key_file) {
|
|
char *path = g_key_file_get_string (key_file, "database", "path", NULL);
|
|
if (path) {
|
|
if (path[0] == '/')
|
|
*database_path = talloc_strdup (notmuch, path);
|
|
else
|
|
*database_path = talloc_asprintf (notmuch, "%s/%s", getenv ("HOME"), path);
|
|
g_free (path);
|
|
}
|
|
}
|
|
if (! *database_path) {
|
|
*database_path = _xdg_dir (notmuch, "XDG_DATA_HOME", ".local/share", profile);
|
|
status = _db_dir_exists (*database_path, message);
|
|
if (status) {
|
|
*database_path = NULL;
|
|
} else {
|
|
notmuch->params |= NOTMUCH_PARAM_SPLIT;
|
|
}
|
|
}
|
|
|
|
if (! *database_path) {
|
|
*database_path = getenv ("MAILDIR");
|
|
}
|
|
|
|
if (! *database_path) {
|
|
*database_path = talloc_asprintf (notmuch, "%s/mail", getenv ("HOME"));
|
|
status = _db_dir_exists (*database_path, message);
|
|
if (status) {
|
|
*database_path = NULL;
|
|
}
|
|
}
|
|
|
|
if (*database_path == NULL) {
|
|
*message = strdup ("Error: could not locate database.\n");
|
|
return NOTMUCH_STATUS_NO_DATABASE;
|
|
}
|
|
|
|
if (*database_path[0] != '/') {
|
|
*message = strdup ("Error: Database path must be absolute.\n");
|
|
return NOTMUCH_STATUS_PATH_ERROR;
|
|
}
|
|
|
|
status = _db_dir_exists (*database_path, message);
|
|
if (status) {
|
|
IGNORE_RESULT (asprintf (message,
|
|
"Error: database path '%s' does not exist or is not a directory.\n",
|
|
*database_path));
|
|
return NOTMUCH_STATUS_NO_DATABASE;
|
|
}
|
|
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
static notmuch_status_t
|
|
_mkdir (const char *path, char **message)
|
|
{
|
|
if (g_mkdir_with_parents (path, 0755)) {
|
|
IGNORE_RESULT (asprintf (message, "Error: Cannot create directory %s: %s.\n",
|
|
path, strerror (errno)));
|
|
return NOTMUCH_STATUS_FILE_ERROR;
|
|
}
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
static notmuch_status_t
|
|
_create_database_path (notmuch_database_t *notmuch,
|
|
const char *profile,
|
|
GKeyFile *key_file,
|
|
const char **database_path,
|
|
char **message)
|
|
{
|
|
notmuch_status_t status;
|
|
|
|
if (! *database_path) {
|
|
*database_path = getenv ("NOTMUCH_DATABASE");
|
|
}
|
|
|
|
if (! *database_path && key_file) {
|
|
char *path = g_key_file_get_string (key_file, "database", "path", NULL);
|
|
if (path) {
|
|
if (path[0] == '/')
|
|
*database_path = talloc_strdup (notmuch, path);
|
|
else
|
|
*database_path = talloc_asprintf (notmuch, "%s/%s", getenv ("HOME"), path);
|
|
g_free (path);
|
|
}
|
|
}
|
|
|
|
if (! *database_path) {
|
|
*database_path = _xdg_dir (notmuch, "XDG_DATA_HOME", ".local/share", profile);
|
|
notmuch->params |= NOTMUCH_PARAM_SPLIT;
|
|
}
|
|
|
|
if (*database_path[0] != '/') {
|
|
*message = strdup ("Error: Database path must be absolute.\n");
|
|
return NOTMUCH_STATUS_PATH_ERROR;
|
|
}
|
|
|
|
if ((status = _mkdir (*database_path, message)))
|
|
return status;
|
|
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
static notmuch_database_t *
|
|
_alloc_notmuch (const char *database_path, const char *config_path, const char *profile)
|
|
{
|
|
notmuch_database_t *notmuch;
|
|
|
|
notmuch = talloc_zero (NULL, notmuch_database_t);
|
|
if (! notmuch)
|
|
return NULL;
|
|
|
|
notmuch->exception_reported = false;
|
|
notmuch->status_string = NULL;
|
|
notmuch->writable_xapian_db = NULL;
|
|
notmuch->config_path = NULL;
|
|
notmuch->atomic_nesting = 0;
|
|
notmuch->transaction_count = 0;
|
|
notmuch->transaction_threshold = 0;
|
|
notmuch->view = 1;
|
|
notmuch->index_as_text = NULL;
|
|
notmuch->index_as_text_length = 0;
|
|
|
|
notmuch->params = NOTMUCH_PARAM_NONE;
|
|
if (database_path)
|
|
notmuch->params |= NOTMUCH_PARAM_DATABASE;
|
|
if (config_path)
|
|
notmuch->params |= NOTMUCH_PARAM_CONFIG;
|
|
if (profile)
|
|
notmuch->params |= NOTMUCH_PARAM_PROFILE;
|
|
|
|
return notmuch;
|
|
}
|
|
|
|
static notmuch_status_t
|
|
_trial_open (const char *xapian_path, char **message_ptr)
|
|
{
|
|
try {
|
|
Xapian::Database db (xapian_path);
|
|
} catch (const Xapian::DatabaseOpeningError &error) {
|
|
IGNORE_RESULT (asprintf (message_ptr,
|
|
"Cannot open Xapian database at %s: %s\n",
|
|
xapian_path,
|
|
error.get_msg ().c_str ()));
|
|
return NOTMUCH_STATUS_PATH_ERROR;
|
|
} catch (const Xapian::Error &error) {
|
|
IGNORE_RESULT (asprintf (message_ptr,
|
|
"A Xapian exception occurred opening database: %s\n",
|
|
error.get_msg ().c_str ()));
|
|
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
|
|
}
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
notmuch_status_t
|
|
_notmuch_choose_xapian_path (void *ctx, const char *database_path,
|
|
const char **xapian_path, char **message_ptr)
|
|
{
|
|
notmuch_status_t status;
|
|
const char *trial_path, *notmuch_path;
|
|
|
|
status = _db_dir_exists (database_path, message_ptr);
|
|
if (status)
|
|
goto DONE;
|
|
|
|
trial_path = talloc_asprintf (ctx, "%s/xapian", database_path);
|
|
status = _trial_open (trial_path, message_ptr);
|
|
if (status != NOTMUCH_STATUS_PATH_ERROR)
|
|
goto DONE;
|
|
|
|
if (*message_ptr)
|
|
free (*message_ptr);
|
|
|
|
notmuch_path = talloc_asprintf (ctx, "%s/.notmuch", database_path);
|
|
status = _db_dir_exists (notmuch_path, message_ptr);
|
|
if (status)
|
|
goto DONE;
|
|
|
|
trial_path = talloc_asprintf (ctx, "%s/xapian", notmuch_path);
|
|
status = _trial_open (trial_path, message_ptr);
|
|
|
|
DONE:
|
|
if (status == NOTMUCH_STATUS_SUCCESS)
|
|
*xapian_path = trial_path;
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
_set_database_path (notmuch_database_t *notmuch,
|
|
const char *database_path)
|
|
{
|
|
char *path = talloc_strdup (notmuch, database_path);
|
|
|
|
strip_trailing (path, '/');
|
|
|
|
_notmuch_config_cache (notmuch, NOTMUCH_CONFIG_DATABASE_PATH, path);
|
|
}
|
|
|
|
static void
|
|
_load_database_state (notmuch_database_t *notmuch)
|
|
{
|
|
std::string last_thread_id;
|
|
std::string last_mod;
|
|
|
|
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 ());
|
|
}
|
|
|
|
/* XXX This should really be done lazily, but the error reporting path in the indexing code
|
|
* would need to be redone to report any errors.
|
|
*/
|
|
notmuch_status_t
|
|
_ensure_index_as_text (notmuch_database_t *notmuch, char **message)
|
|
{
|
|
int nregex = 0;
|
|
regex_t *regexv = NULL;
|
|
|
|
if (notmuch->index_as_text)
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
|
|
for (notmuch_config_values_t *list = notmuch_config_get_values (notmuch,
|
|
NOTMUCH_CONFIG_INDEX_AS_TEXT);
|
|
notmuch_config_values_valid (list);
|
|
notmuch_config_values_move_to_next (list)) {
|
|
regex_t *new_regex;
|
|
int rerr;
|
|
const char *str = notmuch_config_values_get (list);
|
|
size_t len = strlen (str);
|
|
|
|
/* str must be non-empty, because n_c_get_values skips empty
|
|
* strings */
|
|
assert (len > 0);
|
|
|
|
regexv = talloc_realloc (notmuch, regexv, regex_t, nregex + 1);
|
|
new_regex = ®exv[nregex];
|
|
|
|
rerr = regcomp (new_regex, str, REG_EXTENDED | REG_NOSUB);
|
|
if (rerr) {
|
|
size_t error_size = regerror (rerr, new_regex, NULL, 0);
|
|
char *error = (char *) talloc_size (str, error_size);
|
|
|
|
regerror (rerr, new_regex, error, error_size);
|
|
IGNORE_RESULT (asprintf (message, "Error in index.as_text: %s: %s\n", error, str));
|
|
|
|
return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
|
|
}
|
|
nregex++;
|
|
}
|
|
|
|
notmuch->index_as_text = regexv;
|
|
notmuch->index_as_text_length = nregex;
|
|
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
static notmuch_status_t
|
|
_finish_open (notmuch_database_t *notmuch,
|
|
const char *profile,
|
|
notmuch_database_mode_t mode,
|
|
GKeyFile *key_file,
|
|
char **message_ptr)
|
|
{
|
|
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
|
|
char *incompat_features;
|
|
char *message = NULL;
|
|
const char *autocommit_str;
|
|
char *autocommit_end;
|
|
unsigned int version;
|
|
const char *database_path = notmuch_database_get_path (notmuch);
|
|
|
|
try {
|
|
|
|
if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
|
|
notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
|
|
DB_ACTION);
|
|
notmuch->xapian_db = notmuch->writable_xapian_db;
|
|
} else {
|
|
notmuch->xapian_db = new Xapian::Database (notmuch->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",
|
|
database_path, version, NOTMUCH_DATABASE_VERSION));
|
|
status = NOTMUCH_STATUS_FILE_ERROR;
|
|
goto DONE;
|
|
}
|
|
|
|
/* Check features. */
|
|
incompat_features = NULL;
|
|
notmuch->features = _notmuch_database_parse_features (
|
|
notmuch, 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",
|
|
database_path, incompat_features));
|
|
status = NOTMUCH_STATUS_FILE_ERROR;
|
|
goto DONE;
|
|
}
|
|
|
|
_load_database_state (notmuch);
|
|
|
|
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 LastModRangeProcessor (notmuch, "lastmod:");
|
|
notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
|
|
notmuch->query_parser->set_database (*notmuch->xapian_db);
|
|
notmuch->stemmer = new Xapian::Stem ("english");
|
|
notmuch->query_parser->set_stemmer (*notmuch->stemmer);
|
|
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;
|
|
|
|
if (key_file)
|
|
status = _notmuch_config_load_from_file (notmuch, key_file);
|
|
if (status)
|
|
goto DONE;
|
|
|
|
status = _choose_dir (notmuch, profile,
|
|
NOTMUCH_CONFIG_HOOK_DIR,
|
|
"XDG_CONFIG_HOME",
|
|
".config",
|
|
"hooks",
|
|
&message);
|
|
if (status)
|
|
goto DONE;
|
|
|
|
status = _choose_dir (notmuch, profile,
|
|
NOTMUCH_CONFIG_BACKUP_DIR,
|
|
"XDG_DATA_HOME",
|
|
".local/share",
|
|
"backups",
|
|
&message);
|
|
if (status)
|
|
goto DONE;
|
|
status = _notmuch_config_load_defaults (notmuch);
|
|
if (status)
|
|
goto DONE;
|
|
|
|
status = _ensure_index_as_text (notmuch, &message);
|
|
if (status)
|
|
goto DONE;
|
|
|
|
autocommit_str = notmuch_config_get (notmuch, NOTMUCH_CONFIG_AUTOCOMMIT);
|
|
if (unlikely (! autocommit_str)) {
|
|
INTERNAL_ERROR ("missing configuration for autocommit");
|
|
}
|
|
notmuch->transaction_threshold = strtoul (autocommit_str, &autocommit_end, 10);
|
|
if (*autocommit_end != '\0')
|
|
INTERNAL_ERROR ("Malformed database database.autocommit value: %s", autocommit_str);
|
|
|
|
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 ()));
|
|
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
|
|
}
|
|
DONE:
|
|
if (message_ptr)
|
|
*message_ptr = message;
|
|
return status;
|
|
}
|
|
|
|
notmuch_status_t
|
|
notmuch_database_open_with_config (const char *database_path,
|
|
notmuch_database_mode_t mode,
|
|
const char *config_path,
|
|
const char *profile,
|
|
notmuch_database_t **database,
|
|
char **status_string)
|
|
{
|
|
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
|
|
notmuch_database_t *notmuch = NULL;
|
|
char *message = NULL;
|
|
GKeyFile *key_file = NULL;
|
|
|
|
_notmuch_init ();
|
|
|
|
notmuch = _alloc_notmuch (database_path, config_path, profile);
|
|
if (! notmuch) {
|
|
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
|
|
goto DONE;
|
|
}
|
|
|
|
status = _load_key_file (notmuch, config_path, profile, &key_file);
|
|
if (status) {
|
|
message = strdup ("Error: cannot load config file.\n");
|
|
goto DONE;
|
|
}
|
|
|
|
if ((status = _choose_database_path (notmuch, profile, key_file,
|
|
&database_path,
|
|
&message)))
|
|
goto DONE;
|
|
|
|
status = _db_dir_exists (database_path, &message);
|
|
if (status)
|
|
goto DONE;
|
|
|
|
_set_database_path (notmuch, database_path);
|
|
|
|
status = _notmuch_choose_xapian_path (notmuch, database_path,
|
|
¬much->xapian_path, &message);
|
|
if (status)
|
|
goto DONE;
|
|
|
|
status = _finish_open (notmuch, profile, mode, key_file, &message);
|
|
|
|
DONE:
|
|
if (key_file)
|
|
g_key_file_free (key_file);
|
|
|
|
if (message) {
|
|
if (status_string)
|
|
*status_string = message;
|
|
else
|
|
free (message);
|
|
}
|
|
|
|
if (status && notmuch) {
|
|
notmuch_database_destroy (notmuch);
|
|
notmuch = NULL;
|
|
}
|
|
|
|
if (database)
|
|
*database = notmuch;
|
|
|
|
if (notmuch)
|
|
notmuch->open = true;
|
|
|
|
return status;
|
|
}
|
|
|
|
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)
|
|
{
|
|
return notmuch_database_create_with_config (path, "", NULL, database, status_string);
|
|
}
|
|
|
|
notmuch_status_t
|
|
notmuch_database_create_with_config (const char *database_path,
|
|
const char *config_path,
|
|
const char *profile,
|
|
notmuch_database_t **database,
|
|
char **status_string)
|
|
{
|
|
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
|
|
notmuch_database_t *notmuch = NULL;
|
|
const char *notmuch_path = NULL;
|
|
char *message = NULL;
|
|
GKeyFile *key_file = NULL;
|
|
|
|
_notmuch_init ();
|
|
|
|
notmuch = _alloc_notmuch (database_path, config_path, profile);
|
|
if (! notmuch) {
|
|
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
|
|
goto DONE;
|
|
}
|
|
|
|
status = _load_key_file (notmuch, config_path, profile, &key_file);
|
|
if (status) {
|
|
message = strdup ("Error: cannot load config file.\n");
|
|
goto DONE;
|
|
}
|
|
|
|
status = _choose_database_path (notmuch, profile, key_file,
|
|
&database_path, &message);
|
|
switch (status) {
|
|
case NOTMUCH_STATUS_SUCCESS:
|
|
break;
|
|
case NOTMUCH_STATUS_NO_DATABASE:
|
|
if ((status = _create_database_path (notmuch, profile, key_file,
|
|
&database_path, &message)))
|
|
goto DONE;
|
|
break;
|
|
default:
|
|
goto DONE;
|
|
}
|
|
|
|
_set_database_path (notmuch, database_path);
|
|
|
|
if (key_file && ! (notmuch->params & NOTMUCH_PARAM_SPLIT)) {
|
|
char *mail_root = notmuch_canonicalize_file_name (
|
|
g_key_file_get_string (key_file, "database", "mail_root", NULL));
|
|
char *db_path = notmuch_canonicalize_file_name (database_path);
|
|
|
|
if (mail_root && (0 != strcmp (mail_root, db_path)))
|
|
notmuch->params |= NOTMUCH_PARAM_SPLIT;
|
|
|
|
free (mail_root);
|
|
free (db_path);
|
|
}
|
|
|
|
if (notmuch->params & NOTMUCH_PARAM_SPLIT) {
|
|
notmuch_path = database_path;
|
|
} else {
|
|
if (! (notmuch_path = talloc_asprintf (notmuch, "%s/%s", database_path, ".notmuch"))) {
|
|
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
|
|
goto DONE;
|
|
}
|
|
|
|
status = _mkdir (notmuch_path, &message);
|
|
if (status)
|
|
goto DONE;
|
|
}
|
|
|
|
if (! (notmuch->xapian_path = talloc_asprintf (notmuch, "%s/%s", notmuch_path, "xapian"))) {
|
|
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
|
|
goto DONE;
|
|
}
|
|
|
|
status = _trial_open (notmuch->xapian_path, &message);
|
|
if (status == NOTMUCH_STATUS_SUCCESS) {
|
|
notmuch_database_destroy (notmuch);
|
|
notmuch = NULL;
|
|
status = NOTMUCH_STATUS_DATABASE_EXISTS;
|
|
goto DONE;
|
|
}
|
|
|
|
if (message)
|
|
free (message);
|
|
|
|
status = _finish_open (notmuch,
|
|
profile,
|
|
NOTMUCH_DATABASE_MODE_READ_WRITE,
|
|
key_file,
|
|
&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 (key_file)
|
|
g_key_file_free (key_file);
|
|
|
|
if (message) {
|
|
if (status_string)
|
|
*status_string = message;
|
|
else
|
|
free (message);
|
|
}
|
|
if (status && notmuch) {
|
|
notmuch_database_destroy (notmuch);
|
|
notmuch = NULL;
|
|
}
|
|
|
|
if (database)
|
|
*database = notmuch;
|
|
|
|
if (notmuch)
|
|
notmuch->open = true;
|
|
return status;
|
|
}
|
|
|
|
notmuch_status_t
|
|
notmuch_database_reopen (notmuch_database_t *notmuch,
|
|
notmuch_database_mode_t new_mode)
|
|
{
|
|
notmuch_database_mode_t cur_mode = _notmuch_database_mode (notmuch);
|
|
|
|
if (notmuch->xapian_db == NULL) {
|
|
_notmuch_database_log (notmuch, "Cannot reopen closed or nonexistent database\n");
|
|
return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
|
|
}
|
|
|
|
try {
|
|
if (cur_mode == new_mode &&
|
|
new_mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
|
|
notmuch->xapian_db->reopen ();
|
|
} else {
|
|
notmuch->xapian_db->close ();
|
|
|
|
delete notmuch->xapian_db;
|
|
notmuch->xapian_db = NULL;
|
|
/* no need to free the same object twice */
|
|
notmuch->writable_xapian_db = NULL;
|
|
|
|
if (new_mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
|
|
notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
|
|
DB_ACTION);
|
|
notmuch->xapian_db = notmuch->writable_xapian_db;
|
|
} else {
|
|
notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path,
|
|
DB_ACTION);
|
|
}
|
|
}
|
|
|
|
_load_database_state (notmuch);
|
|
} 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++;
|
|
notmuch->open = true;
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
static notmuch_status_t
|
|
_maybe_load_config_from_database (notmuch_database_t *notmuch,
|
|
GKeyFile *key_file,
|
|
const char *database_path,
|
|
const char *profile)
|
|
{
|
|
char *message; /* ignored */
|
|
|
|
if (_db_dir_exists (database_path, &message))
|
|
return NOTMUCH_STATUS_NO_DATABASE;
|
|
|
|
_set_database_path (notmuch, database_path);
|
|
|
|
if (_notmuch_choose_xapian_path (notmuch, database_path, ¬much->xapian_path, &message))
|
|
return NOTMUCH_STATUS_NO_DATABASE;
|
|
|
|
(void) _finish_open (notmuch, profile, NOTMUCH_DATABASE_MODE_READ_ONLY, key_file, &message);
|
|
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
}
|
|
|
|
notmuch_status_t
|
|
notmuch_database_load_config (const char *database_path,
|
|
const char *config_path,
|
|
const char *profile,
|
|
notmuch_database_t **database,
|
|
char **status_string)
|
|
{
|
|
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS, warning = NOTMUCH_STATUS_SUCCESS;
|
|
notmuch_database_t *notmuch = NULL;
|
|
char *message = NULL;
|
|
GKeyFile *key_file = NULL;
|
|
|
|
_notmuch_init ();
|
|
|
|
notmuch = _alloc_notmuch (database_path, config_path, profile);
|
|
if (! notmuch) {
|
|
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
|
|
goto DONE;
|
|
}
|
|
|
|
status = _load_key_file (notmuch, config_path, profile, &key_file);
|
|
switch (status) {
|
|
case NOTMUCH_STATUS_SUCCESS:
|
|
break;
|
|
case NOTMUCH_STATUS_NO_CONFIG:
|
|
warning = status;
|
|
break;
|
|
default:
|
|
message = strdup ("Error: cannot load config file.\n");
|
|
goto DONE;
|
|
}
|
|
|
|
status = _choose_database_path (notmuch, profile, key_file,
|
|
&database_path, &message);
|
|
switch (status) {
|
|
case NOTMUCH_STATUS_NO_DATABASE:
|
|
case NOTMUCH_STATUS_SUCCESS:
|
|
if (! warning)
|
|
warning = status;
|
|
break;
|
|
default:
|
|
goto DONE;
|
|
}
|
|
|
|
|
|
if (database_path) {
|
|
status = _maybe_load_config_from_database (notmuch, key_file, database_path, profile);
|
|
switch (status) {
|
|
case NOTMUCH_STATUS_NO_DATABASE:
|
|
case NOTMUCH_STATUS_SUCCESS:
|
|
if (! warning)
|
|
warning = status;
|
|
break;
|
|
default:
|
|
goto DONE;
|
|
}
|
|
}
|
|
|
|
if (key_file) {
|
|
status = _notmuch_config_load_from_file (notmuch, key_file);
|
|
if (status)
|
|
goto DONE;
|
|
}
|
|
status = _notmuch_config_load_defaults (notmuch);
|
|
if (status)
|
|
goto DONE;
|
|
|
|
DONE:
|
|
if (status_string)
|
|
*status_string = message;
|
|
|
|
if (status &&
|
|
status != NOTMUCH_STATUS_NO_DATABASE
|
|
&& status != NOTMUCH_STATUS_NO_CONFIG) {
|
|
notmuch_database_destroy (notmuch);
|
|
notmuch = NULL;
|
|
}
|
|
|
|
if (database)
|
|
*database = notmuch;
|
|
|
|
if (status)
|
|
return status;
|
|
else
|
|
return warning;
|
|
}
|