using namespace std;
-#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
-
typedef struct {
const char *name;
const char *prefix;
notmuch->exception_reported = true;
}
+notmuch_database_mode_t
+_notmuch_database_mode (notmuch_database_t *notmuch)
+{
+ if (notmuch->writable_xapian_db)
+ return NOTMUCH_DATABASE_MODE_READ_WRITE;
+ else
+ return NOTMUCH_DATABASE_MODE_READ_ONLY;
+}
+
/* Here's the current schema for our database (for NOTMUCH_DATABASE_VERSION):
*
* We currently have three different types of documents (mail, ghost,
* same thread.
*/
-/* With these prefix values we follow the conventions published here:
- *
- * https://xapian.org/docs/omega/termprefixes.html
- *
- * as much as makes sense. Note that I took some liberty in matching
- * the reserved prefix values to notmuch concepts, (for example, 'G'
- * is documented as "newsGroup (or similar entity - e.g. a web forum
- * name)", for which I think the thread is the closest analogue in
- * notmuch. This in spite of the fact that we will eventually be
- * storing mailing-list messages where 'G' for "mailing list name"
- * might be even a closer analogue. I'm treating the single-character
- * prefixes preferentially for core notmuch concepts (which will be
- * nearly universal to all mail messages).
- */
-
-static const
-prefix_t prefix_table[] = {
- /* name term prefix flags */
- { "type", "T", NOTMUCH_FIELD_NO_FLAGS },
- { "reference", "XREFERENCE", NOTMUCH_FIELD_NO_FLAGS },
- { "replyto", "XREPLYTO", NOTMUCH_FIELD_NO_FLAGS },
- { "directory", "XDIRECTORY", NOTMUCH_FIELD_NO_FLAGS },
- { "file-direntry", "XFDIRENTRY", NOTMUCH_FIELD_NO_FLAGS },
- { "directory-direntry", "XDDIRENTRY", NOTMUCH_FIELD_NO_FLAGS },
- { "body", "", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "thread", "G", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "tag", "K", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "is", "K", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "id", "Q", NOTMUCH_FIELD_EXTERNAL },
- { "mid", "Q", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "path", "P", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "property", "XPROPERTY", NOTMUCH_FIELD_EXTERNAL },
- /*
- * Unconditionally add ':' to reduce potential ambiguity with
- * overlapping prefixes and/or terms that start with capital
- * letters. See Xapian document termprefixes.html for related
- * discussion.
- */
- { "folder", "XFOLDER:", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "date", NULL, NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "query", NULL, NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "from", "XFROM", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC |
- NOTMUCH_FIELD_PROCESSOR },
- { "to", "XTO", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "attachment", "XATTACHMENT", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "mimetype", "XMIMETYPE", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "subject", "XSUBJECT", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC |
- NOTMUCH_FIELD_PROCESSOR },
-};
-
-static void
-_setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
- if (prefix->prefix)
- notmuch->query_parser->add_prefix ("", prefix->prefix);
- if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC)
- notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
- else
- notmuch->query_parser->add_boolean_prefix (prefix->name, prefix->prefix);
-}
notmuch_string_map_iterator_t *
_notmuch_database_user_headers (notmuch_database_t *notmuch)
return _notmuch_string_map_iterator_create (notmuch->user_header, "", false);
}
-const char *
-_user_prefix (void *ctx, const char *name)
-{
- return talloc_asprintf (ctx, "XU%s:", name);
-}
-
-static notmuch_status_t
-_setup_user_query_fields (notmuch_database_t *notmuch)
-{
- notmuch_config_list_t *list;
- notmuch_status_t status;
-
- notmuch->user_prefix = _notmuch_string_map_create (notmuch);
- if (notmuch->user_prefix == NULL)
- return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
- notmuch->user_header = _notmuch_string_map_create (notmuch);
- if (notmuch->user_header == NULL)
- return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
- status = notmuch_database_get_config_list (notmuch, CONFIG_HEADER_PREFIX, &list);
- if (status)
- return status;
-
- for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
-
- prefix_t query_field;
-
- const char *key = notmuch_config_list_key (list)
- + sizeof (CONFIG_HEADER_PREFIX) - 1;
-
- _notmuch_string_map_append (notmuch->user_prefix,
- key,
- _user_prefix (notmuch, key));
-
- _notmuch_string_map_append (notmuch->user_header,
- key,
- notmuch_config_list_value (list));
-
- query_field.name = talloc_strdup (notmuch, key);
- query_field.prefix = _user_prefix (notmuch, key);
- query_field.flags = NOTMUCH_FIELD_PROBABILISTIC
- | NOTMUCH_FIELD_EXTERNAL;
-
- _setup_query_field_default (&query_field, notmuch);
- }
-
- notmuch_config_list_destroy (list);
-
- return NOTMUCH_STATUS_SUCCESS;
-}
-
-static void
-_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
- if (prefix->flags & NOTMUCH_FIELD_PROCESSOR) {
- Xapian::FieldProcessor *fp;
-
- if (STRNCMP_LITERAL (prefix->name, "date") == 0)
- fp = (new DateFieldProcessor(NOTMUCH_VALUE_TIMESTAMP))->release ();
- else if (STRNCMP_LITERAL(prefix->name, "query") == 0)
- fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
- else if (STRNCMP_LITERAL (prefix->name, "thread") == 0)
- fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
- else
- fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
- *notmuch->query_parser, notmuch))->release ();
-
- /* we treat all field-processor fields as boolean in order to get the raw input */
- if (prefix->prefix)
- notmuch->query_parser->add_prefix ("", prefix->prefix);
- notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
- } else {
- _setup_query_field_default (prefix, notmuch);
- }
-}
-
-const char *
-_find_prefix (const char *name)
-{
- unsigned int i;
-
- for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
- if (strcmp (name, prefix_table[i].name) == 0)
- return prefix_table[i].prefix;
- }
-
- INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
-
- return "";
-}
-
-/* Like find prefix, but include the possibility of user defined
- * prefixes specific to this database */
-
-const char *
-_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name)
-{
- unsigned int i;
-
- /*XXX TODO: reduce code duplication */
- for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
- if (strcmp (name, prefix_table[i].name) == 0)
- return prefix_table[i].prefix;
- }
-
- if (notmuch->user_prefix)
- return _notmuch_string_map_get (notmuch->user_prefix, name);
-
- return NULL;
-}
-
-static const struct {
- /* NOTMUCH_FEATURE_* value. */
- _notmuch_features value;
- /* Feature name as it appears in the database. This name should
- * be appropriate for displaying to the user if an older version
- * of notmuch doesn't support this feature. */
- const char *name;
- /* Compatibility flags when this feature is declared. */
- const char *flags;
-} feature_names[] = {
- { NOTMUCH_FEATURE_FILE_TERMS,
- "multiple paths per message", "rw" },
- { NOTMUCH_FEATURE_DIRECTORY_DOCS,
- "relative directory paths", "rw" },
- /* Header values are not required for reading a database because a
- * reader can just refer to the message file. */
- { NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES,
- "from/subject/message-ID in database", "w" },
- { NOTMUCH_FEATURE_BOOL_FOLDER,
- "exact folder:/path: search", "rw" },
- { NOTMUCH_FEATURE_GHOSTS,
- "mail documents for missing messages", "w" },
- /* Knowledge of the index mime-types are not required for reading
- * a database because a reader will just be unable to query
- * them. */
- { NOTMUCH_FEATURE_INDEXED_MIMETYPES,
- "indexed MIME types", "w" },
- { NOTMUCH_FEATURE_LAST_MOD,
- "modification tracking", "w" },
- /* Existing databases will work fine for all queries not involving
- * 'body:' */
- { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY,
- "index body and headers separately", "w" },
-};
-
const char *
notmuch_status_to_string (notmuch_status_t status)
{
notmuch_status_t
_notmuch_database_ensure_writable (notmuch_database_t *notmuch)
{
- if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
+ if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY) {
_notmuch_database_log (notmuch, "Cannot write to a read-only database.\n");
return NOTMUCH_STATUS_READ_ONLY_DATABASE;
}
return new_revision;
}
-/* Parse a database features string from the given database version.
- * Returns the feature bit set.
- *
- * For version < 3, this ignores the features string and returns a
- * hard-coded set of features.
- *
- * If there are unrecognized features that are required to open the
- * database in mode (which should be 'r' or 'w'), return a
- * comma-separated list of unrecognized but required features in
- * *incompat_out suitable for presenting to the user. *incompat_out
- * will be allocated from ctx.
- */
-static _notmuch_features
-_parse_features (const void *ctx, const char *features, unsigned int version,
- char mode, char **incompat_out)
-{
- _notmuch_features res = static_cast<_notmuch_features>(0);
- unsigned int namelen, i;
- size_t llen = 0;
- const char *flags;
-
- /* Prior to database version 3, features were implied by the
- * version number. */
- if (version == 0)
- return NOTMUCH_FEATURES_V0;
- else if (version == 1)
- return NOTMUCH_FEATURES_V1;
- else if (version == 2)
- return NOTMUCH_FEATURES_V2;
-
- /* Parse the features string */
- while ((features = strtok_len_c (features + llen, "\n", &llen)) != NULL) {
- flags = strchr (features, '\t');
- if (! flags || flags > features + llen)
- continue;
- namelen = flags - features;
-
- for (i = 0; i < ARRAY_SIZE (feature_names); ++i) {
- if (strlen (feature_names[i].name) == namelen &&
- strncmp (feature_names[i].name, features, namelen) == 0) {
- res |= feature_names[i].value;
- break;
- }
- }
-
- if (i == ARRAY_SIZE (feature_names) && incompat_out) {
- /* Unrecognized feature */
- const char *have = strchr (flags, mode);
- if (have && have < features + llen) {
- /* This feature is required to access this database in
- * 'mode', but we don't understand it. */
- if (! *incompat_out)
- *incompat_out = talloc_strdup (ctx, "");
- *incompat_out = talloc_asprintf_append_buffer (
- *incompat_out, "%s%.*s", **incompat_out ? ", " : "",
- namelen, features);
- }
- }
- }
-
- return res;
-}
-
-static char *
-_print_features (const void *ctx, unsigned int features)
-{
- unsigned int i;
- char *res = talloc_strdup (ctx, "");
-
- for (i = 0; i < ARRAY_SIZE (feature_names); ++i)
- if (features & feature_names[i].value)
- res = talloc_asprintf_append_buffer (
- res, "%s\t%s\n", feature_names[i].name, feature_names[i].flags);
-
- return res;
-}
-
notmuch_status_t
notmuch_database_open (const char *path,
notmuch_database_mode_t mode,
strip_trailing (notmuch->path, '/');
- notmuch->mode = mode;
+ notmuch->writable_xapian_db = NULL;
notmuch->atomic_nesting = 0;
notmuch->view = 1;
try {
string last_mod;
if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
- notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
- DB_ACTION);
+ 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 features. */
incompat_features = NULL;
- notmuch->features = _parse_features (
+ 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);
notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
- for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
- const prefix_t *prefix = &prefix_table[i];
- if (prefix->flags & NOTMUCH_FIELD_EXTERNAL) {
- _setup_query_field (prefix, notmuch);
- }
- }
- status = _setup_user_query_fields (notmuch);
+ 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 ()));
* that transaction, or may discard committed (but
* unflushed) transactions. To be certain, explicitly
* cancel any outstanding transaction before closing. */
- if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&
+ if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_WRITE &&
notmuch->atomic_nesting)
- (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))
- ->cancel_transaction ();
+ notmuch->writable_xapian_db->cancel_transaction ();
/* Close the database. This implicitly flushes
* outstanding changes. */
notmuch_status_t
_notmuch_database_reopen (notmuch_database_t *notmuch)
{
- if (notmuch->mode != NOTMUCH_DATABASE_MODE_READ_ONLY)
+ if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_ONLY)
return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
try {
{
unsigned int version;
- if (notmuch->mode != NOTMUCH_DATABASE_MODE_READ_WRITE)
+ if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_WRITE)
return FALSE;
if (NOTMUCH_FEATURES_CURRENT & ~notmuch->features)
if (status)
return status;
- db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+ db = notmuch->writable_xapian_db;
target_features = notmuch->features | NOTMUCH_FEATURES_CURRENT;
new_features = NOTMUCH_FEATURES_CURRENT & ~notmuch->features;
}
status = NOTMUCH_STATUS_SUCCESS;
- db->set_metadata ("features", _print_features (local, notmuch->features));
+ db->set_metadata ("features", _notmuch_database_print_features (local, notmuch->features));
db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION));
DONE:
notmuch_status_t
notmuch_database_begin_atomic (notmuch_database_t *notmuch)
{
- if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
+ if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY ||
notmuch->atomic_nesting > 0)
goto DONE;
return NOTMUCH_STATUS_UPGRADE_REQUIRED;
try {
- (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))->begin_transaction (false);
+ notmuch->writable_xapian_db->begin_transaction (false);
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "A Xapian exception occurred beginning transaction: %s.\n",
error.get_msg ().c_str ());
if (notmuch->atomic_nesting == 0)
return NOTMUCH_STATUS_UNBALANCED_ATOMIC;
- if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
+ if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY ||
notmuch->atomic_nesting > 1)
goto DONE;
- db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+ db = notmuch->writable_xapian_db;
try {
db->commit_transaction ();