]> git.notmuchmail.org Git - notmuch/blobdiff - lib/database.cc
lib/config: delay setting talloc destructor
[notmuch] / lib / database.cc
index 8ec91987753bafe4e1a60804f830bf05c7699e42..defa3062d03d84cd26640ff50a722915f4fc092f 100644 (file)
@@ -39,8 +39,6 @@
 
 using namespace std;
 
-#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
-
 typedef struct {
     const char *name;
     const char *prefix;
@@ -69,6 +67,15 @@ _log_xapian_exception (const char *where, notmuch_database_t *notmuch,  const Xa
     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,
@@ -256,80 +263,6 @@ _log_xapian_exception (const char *where, notmuch_database_t *notmuch,  const Xa
  *                     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)
@@ -337,153 +270,6 @@ _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)
 {
@@ -783,7 +569,7 @@ notmuch_database_create_verbose (const char *path,
 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;
     }
@@ -808,83 +594,6 @@ _notmuch_database_new_revision (notmuch_database_t *notmuch)
     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,
@@ -970,7 +679,7 @@ notmuch_database_open_verbose (const char *path,
 
     strip_trailing (notmuch->path, '/');
 
-    notmuch->mode = mode;
+    notmuch->writable_xapian_db = NULL;
     notmuch->atomic_nesting = 0;
     notmuch->view = 1;
     try {
@@ -978,8 +687,9 @@ notmuch_database_open_verbose (const char *path,
        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);
        }
@@ -994,7 +704,6 @@ notmuch_database_open_verbose (const char *path,
                                     "       has a newer database format version (%u) than supported by this\n"
                                     "       version of notmuch (%u).\n",
                                     notmuch_path, version, NOTMUCH_DATABASE_VERSION));
-           notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
            notmuch_database_destroy (notmuch);
            notmuch = NULL;
            status = NOTMUCH_STATUS_FILE_ERROR;
@@ -1003,7 +712,7 @@ notmuch_database_open_verbose (const char *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);
@@ -1013,7 +722,6 @@ notmuch_database_open_verbose (const char *path,
                                     "       requires features (%s)\n"
                                     "       not supported by this version of notmuch.\n",
                                     notmuch_path, incompat_features));
-           notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
            notmuch_database_destroy (notmuch);
            notmuch = NULL;
            status = NOTMUCH_STATUS_FILE_ERROR;
@@ -1058,13 +766,14 @@ notmuch_database_open_verbose (const char *path,
        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 ()));
@@ -1109,10 +818,9 @@ notmuch_database_close (notmuch_database_t *notmuch)
             * 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. */
@@ -1132,7 +840,7 @@ notmuch_database_close (notmuch_database_t *notmuch)
 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 {
@@ -1395,9 +1103,17 @@ notmuch_database_get_version (notmuch_database_t *notmuch)
 notmuch_bool_t
 notmuch_database_needs_upgrade (notmuch_database_t *notmuch)
 {
-    return notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&
-          ((NOTMUCH_FEATURES_CURRENT & ~notmuch->features) ||
-           (notmuch_database_get_version (notmuch) < NOTMUCH_DATABASE_VERSION));
+    unsigned int version;
+
+    if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_WRITE)
+       return FALSE;
+
+    if (NOTMUCH_FEATURES_CURRENT & ~notmuch->features)
+       return TRUE;
+
+    version = notmuch_database_get_version (notmuch);
+
+    return (version > 0 && version < NOTMUCH_DATABASE_VERSION);
 }
 
 static volatile sig_atomic_t do_progress_notify = 0;
@@ -1442,7 +1158,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
     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;
@@ -1597,8 +1313,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
                mtime = Xapian::sortable_unserialise (
                    document.get_value (NOTMUCH_VALUE_TIMESTAMP));
 
-               directory = _notmuch_directory_create (notmuch, term.c_str () + 10,
-                                                      NOTMUCH_FIND_CREATE, &status);
+               directory = _notmuch_directory_find_or_create (notmuch, term.c_str () + 10,
+                                                              NOTMUCH_FIND_CREATE, &status);
                notmuch_directory_set_mtime (directory, mtime);
                notmuch_directory_destroy (directory);
 
@@ -1659,7 +1375,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
     }
 
     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:
@@ -1691,7 +1407,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
 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;
 
@@ -1699,7 +1415,7 @@ notmuch_database_begin_atomic (notmuch_database_t *notmuch)
        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 ());
@@ -1720,11 +1436,11 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch)
     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 ();
 
@@ -1870,7 +1586,7 @@ _notmuch_database_find_directory_id (notmuch_database_t *notmuch,
        return NOTMUCH_STATUS_SUCCESS;
     }
 
-    directory = _notmuch_directory_create (notmuch, path, flags, &status);
+    directory = _notmuch_directory_find_or_create (notmuch, path, flags, &status);
     if (status || ! directory) {
        *directory_id = -1;
        return status;
@@ -1980,8 +1696,8 @@ notmuch_database_get_directory (notmuch_database_t *notmuch,
     *directory = NULL;
 
     try {
-       *directory = _notmuch_directory_create (notmuch, path,
-                                               NOTMUCH_FIND_LOOKUP, &status);
+       *directory = _notmuch_directory_find_or_create (notmuch, path,
+                                                       NOTMUCH_FIND_LOOKUP, &status);
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred getting directory: %s.\n",
                               error.get_msg ().c_str ());