notmuch/lib/config.cc
David Bremner e823d05ae6 lib: support splitting mail from database location.
Introduce a new configuration value for the mail root, and use it to
locate mail messages in preference to the database.path (which
previously implied the mail messages were also in this location.

Initially only a subset of the CLI is tested in a split
configuration. Further changes will be needed for the remainder of the
CLI to work in split configurations.
2021-03-20 07:39:12 -03:00

508 lines
13 KiB
C++

/* config.cc - API for database metadata
*
* Copyright © 2016 David Bremner
*
* 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 https://www.gnu.org/licenses/ .
*
* Author: David Bremner <david@tethera.net>
*/
#include "notmuch.h"
#include "notmuch-private.h"
#include "database-private.h"
static const std::string CONFIG_PREFIX = "C";
struct _notmuch_config_list {
notmuch_database_t *notmuch;
Xapian::TermIterator iterator;
char *current_key;
char *current_val;
};
struct _notmuch_config_values {
const char *iterator;
size_t tok_len;
const char *string;
void *children; /* talloc_context */
};
static const char *_notmuch_config_key_to_string (notmuch_config_key_t key);
static int
_notmuch_config_list_destroy (notmuch_config_list_t *list)
{
/* invoke destructor w/o deallocating memory */
list->iterator.~TermIterator();
return 0;
}
notmuch_status_t
notmuch_database_set_config (notmuch_database_t *notmuch,
const char *key,
const char *value)
{
notmuch_status_t status;
status = _notmuch_database_ensure_writable (notmuch);
if (status)
return status;
if (! notmuch->config) {
if ((status = _notmuch_config_load_from_database (notmuch)))
return status;
}
try {
notmuch->writable_xapian_db->set_metadata (CONFIG_PREFIX + key, value);
} catch (const Xapian::Error &error) {
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
notmuch->exception_reported = true;
_notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n",
error.get_msg ().c_str ());
}
if (status)
return status;
_notmuch_string_map_set (notmuch->config, key, value);
return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
_metadata_value (notmuch_database_t *notmuch,
const char *key,
std::string &value)
{
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
try {
value = notmuch->xapian_db->get_metadata (CONFIG_PREFIX + key);
} catch (const Xapian::Error &error) {
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
notmuch->exception_reported = true;
_notmuch_database_log (notmuch, "Error: A Xapian exception occurred getting metadata: %s\n",
error.get_msg ().c_str ());
}
return status;
}
notmuch_status_t
notmuch_database_get_config (notmuch_database_t *notmuch,
const char *key,
char **value)
{
const char *stored_val;
notmuch_status_t status;
if (! notmuch->config) {
if ((status = _notmuch_config_load_from_database (notmuch)))
return status;
}
if (! value)
return NOTMUCH_STATUS_NULL_POINTER;
stored_val = _notmuch_string_map_get (notmuch->config, key);
if (! stored_val) {
/* XXX in principle this API should be fixed so empty string
* is distinguished from not found */
*value = strdup ("");
} else {
*value = strdup (stored_val);
}
return NOTMUCH_STATUS_SUCCESS;
}
notmuch_status_t
notmuch_database_get_config_list (notmuch_database_t *notmuch,
const char *prefix,
notmuch_config_list_t **out)
{
notmuch_config_list_t *list = NULL;
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
list = talloc (notmuch, notmuch_config_list_t);
if (! list) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
list->notmuch = notmuch;
list->current_key = NULL;
list->current_val = NULL;
try {
new(&(list->iterator)) Xapian::TermIterator (notmuch->xapian_db->metadata_keys_begin
(CONFIG_PREFIX + (prefix ? prefix : "")));
talloc_set_destructor (list, _notmuch_config_list_destroy);
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch,
"A Xapian exception occurred getting metadata iterator: %s.\n",
error.get_msg ().c_str ());
notmuch->exception_reported = true;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
*out = list;
DONE:
if (status) {
if (list) {
talloc_free (list);
if (status != NOTMUCH_STATUS_XAPIAN_EXCEPTION)
_notmuch_config_list_destroy (list);
}
} else {
talloc_set_destructor (list, _notmuch_config_list_destroy);
}
return status;
}
notmuch_bool_t
notmuch_config_list_valid (notmuch_config_list_t *metadata)
{
if (metadata->iterator == metadata->notmuch->xapian_db->metadata_keys_end ())
return false;
return true;
}
static inline char *
_key_from_iterator (notmuch_config_list_t *list)
{
return talloc_strdup (list, (*list->iterator).c_str () + CONFIG_PREFIX.length ());
}
const char *
notmuch_config_list_key (notmuch_config_list_t *list)
{
if (list->current_key)
talloc_free (list->current_key);
list->current_key = _key_from_iterator (list);
return list->current_key;
}
const char *
notmuch_config_list_value (notmuch_config_list_t *list)
{
std::string strval;
notmuch_status_t status;
char *key = _key_from_iterator (list);
/* TODO: better error reporting?? */
status = _metadata_value (list->notmuch, key, strval);
if (status)
return NULL;
if (list->current_val)
talloc_free (list->current_val);
list->current_val = talloc_strdup (list, strval.c_str ());
talloc_free (key);
return list->current_val;
}
void
notmuch_config_list_move_to_next (notmuch_config_list_t *list)
{
list->iterator++;
}
void
notmuch_config_list_destroy (notmuch_config_list_t *list)
{
talloc_free (list);
}
notmuch_status_t
_notmuch_config_load_from_database (notmuch_database_t *notmuch)
{
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
notmuch_config_list_t *list;
if (notmuch->config == NULL)
notmuch->config = _notmuch_string_map_create (notmuch);
if (unlikely (notmuch->config == NULL))
return NOTMUCH_STATUS_OUT_OF_MEMORY;
status = notmuch_database_get_config_list (notmuch, "", &list);
if (status)
return status;
for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
_notmuch_string_map_append (notmuch->config,
notmuch_config_list_key (list),
notmuch_config_list_value (list));
}
return status;
}
notmuch_config_values_t *
notmuch_config_get_values (notmuch_database_t *notmuch, notmuch_config_key_t key)
{
notmuch_config_values_t *values = NULL;
bool ok = false;
const char *key_str = _notmuch_config_key_to_string (key);
if (! key_str)
goto DONE;
values = talloc (notmuch, notmuch_config_values_t);
if (unlikely (! values))
goto DONE;
values->children = talloc_new (values);
values->string = _notmuch_string_map_get (notmuch->config, key_str);
if (! values->string)
goto DONE;
values->iterator = strsplit_len (values->string, ';', &(values->tok_len));
ok = true;
DONE:
if (! ok) {
if (values)
talloc_free (values);
return NULL;
}
return values;
}
notmuch_bool_t
notmuch_config_values_valid (notmuch_config_values_t *values)
{
if (! values)
return false;
return (values->iterator != NULL);
}
const char *
notmuch_config_values_get (notmuch_config_values_t *values)
{
return talloc_strndup (values, values->iterator, values->tok_len);
}
void
notmuch_config_values_start (notmuch_config_values_t *values)
{
if (values == NULL)
return;
if (values->children) {
talloc_free (values->children);
}
values->children = talloc_new (values);
values->iterator = strsplit_len (values->string, ';', &(values->tok_len));
}
void
notmuch_config_values_move_to_next (notmuch_config_values_t *values)
{
values->iterator += values->tok_len;
values->iterator = strsplit_len (values->iterator, ';', &(values->tok_len));
}
void
notmuch_config_values_destroy (notmuch_config_values_t *values)
{
talloc_free (values);
}
notmuch_status_t
_notmuch_config_load_from_file (notmuch_database_t *notmuch,
GKeyFile *file)
{
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
gchar **groups = NULL, **keys, *val;
if (notmuch->config == NULL)
notmuch->config = _notmuch_string_map_create (notmuch);
if (unlikely (notmuch->config == NULL)) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
groups = g_key_file_get_groups (file, NULL);
for (gchar **grp = groups; *grp; grp++) {
keys = g_key_file_get_keys (file, *grp, NULL, NULL);
for (gchar **keys_p = keys; *keys_p; keys_p++) {
char *absolute_key = talloc_asprintf (notmuch, "%s.%s", *grp, *keys_p);
val = g_key_file_get_value (file, *grp, *keys_p, NULL);
if (! val) {
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
_notmuch_string_map_set (notmuch->config, absolute_key, val);
g_free (val);
talloc_free (absolute_key);
if (status)
goto DONE;
}
g_strfreev (keys);
}
DONE:
if (groups)
g_strfreev (groups);
return status;
}
notmuch_status_t
notmuch_config_get_bool (notmuch_database_t *notmuch, notmuch_config_key_t key, notmuch_bool_t *val)
{
const char *key_string, *val_string;
key_string = _notmuch_config_key_to_string (key);
if (! key_string) {
return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
}
val_string = _notmuch_string_map_get (notmuch->config, key_string);
if (! val_string) {
*val = FALSE;
return NOTMUCH_STATUS_SUCCESS;
}
if (strcase_equal (val_string, "false") || strcase_equal (val_string, "no"))
*val = FALSE;
else if (strcase_equal (val_string, "true") || strcase_equal (val_string, "yes"))
*val = TRUE;
else
return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
return NOTMUCH_STATUS_SUCCESS;
}
static const char *
_notmuch_config_key_to_string (notmuch_config_key_t key)
{
switch (key) {
case NOTMUCH_CONFIG_DATABASE_PATH:
return "database.path";
case NOTMUCH_CONFIG_MAIL_ROOT:
return "database.mail_root";
case NOTMUCH_CONFIG_HOOK_DIR:
return "database.hook_dir";
case NOTMUCH_CONFIG_EXCLUDE_TAGS:
return "search.exclude_tags";
case NOTMUCH_CONFIG_NEW_TAGS:
return "new.tags";
case NOTMUCH_CONFIG_NEW_IGNORE:
return "new.ignore";
case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
return "maildir.synchronize_flags";
case NOTMUCH_CONFIG_PRIMARY_EMAIL:
return "user.primary_email";
case NOTMUCH_CONFIG_OTHER_EMAIL:
return "user.other_email";
case NOTMUCH_CONFIG_USER_NAME:
return "user.name";
default:
return NULL;
}
}
static const char *
_notmuch_config_default (notmuch_database_t *notmuch, notmuch_config_key_t key)
{
char *path;
switch (key) {
case NOTMUCH_CONFIG_DATABASE_PATH:
path = getenv ("MAILDIR");
if (path)
path = talloc_strdup (notmuch, path);
else
path = talloc_asprintf (notmuch, "%s/mail",
getenv ("HOME"));
return path;
case NOTMUCH_CONFIG_MAIL_ROOT:
/* by default, mail root is the same as database path */
return notmuch_database_get_path (notmuch);
case NOTMUCH_CONFIG_EXCLUDE_TAGS:
return "";
case NOTMUCH_CONFIG_NEW_TAGS:
return "inbox;unread";
case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
return "true";
case NOTMUCH_CONFIG_HOOK_DIR:
case NOTMUCH_CONFIG_NEW_IGNORE:
case NOTMUCH_CONFIG_USER_NAME:
case NOTMUCH_CONFIG_PRIMARY_EMAIL:
case NOTMUCH_CONFIG_OTHER_EMAIL:
return NULL;
default:
case NOTMUCH_CONFIG_LAST:
INTERNAL_ERROR ("illegal key enum %d", key);
}
}
notmuch_status_t
_notmuch_config_load_defaults (notmuch_database_t *notmuch)
{
notmuch_config_key_t key;
for (key = NOTMUCH_CONFIG_FIRST;
key < NOTMUCH_CONFIG_LAST;
key = notmuch_config_key_t (key + 1)) {
const char *val = notmuch_config_get (notmuch, key);
const char *key_string = _notmuch_config_key_to_string (key);
val = _notmuch_string_map_get (notmuch->config, key_string);
if (! val) {
_notmuch_string_map_set (notmuch->config, key_string, _notmuch_config_default (notmuch,
key));
}
}
return NOTMUCH_STATUS_SUCCESS;
}
const char *
notmuch_config_get (notmuch_database_t *notmuch, notmuch_config_key_t key)
{
return _notmuch_string_map_get (notmuch->config, _notmuch_config_key_to_string (key));
}
notmuch_status_t
notmuch_config_set (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val)
{
return notmuch_database_set_config (notmuch, _notmuch_config_key_to_string (key), val);
}
void
_notmuch_config_cache (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val)
{
if (notmuch->config == NULL)
notmuch->config = _notmuch_string_map_create (notmuch);
_notmuch_string_map_set (notmuch->config, _notmuch_config_key_to_string (key), val);
}