]> git.notmuchmail.org Git - notmuch/blobdiff - database.cc
Trim down prefix list to things we are actually using.
[notmuch] / database.cc
index 16c514591fbe0c36fed80391604fc58096ac697e..c470cc34977c17beff7d152f78c5f1ac975cefd2 100644 (file)
 
 using namespace std;
 
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
+/* These prefix values are specifically chosen to be compatible
+ * with sup, (http://sup.rubyforge.org), written by
+ * William Morgan <wmorgan-sup@masanjin.net>, and released
+ * under the GNU GPL v2.
+ */
+
+typedef struct {
+    const char *name;
+    const char *prefix;
+} prefix_t;
+
+prefix_t BOOLEAN_PREFIX[] = {
+    { "type", "K" },
+    { "tag", "L" },
+    { "msgid", "Q" },
+    { "thread", "H" },
+    { "ref", "R" },
+    { "timestamp", "KTS" },
+};
+
+const char *
+_find_prefix (const char *name)
+{
+    unsigned int i;
+
+    for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX); i++)
+       if (strcmp (name, BOOLEAN_PREFIX[i].name) == 0)
+           return BOOLEAN_PREFIX[i].prefix;
+
+    fprintf (stderr, "Internal error: No prefix exists for '%s'\n", name);
+    exit (1);
+
+    return "";
+}
+
 const char *
 notmuch_status_to_string (notmuch_status_t status)
 {
@@ -40,10 +77,12 @@ notmuch_status_to_string (notmuch_status_t status)
        return "Something went wrong trying to read or write a file";
     case NOTMUCH_STATUS_FILE_NOT_EMAIL:
        return "File is not an email";
+    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+       return "Message ID is identical to a message in database";
     case NOTMUCH_STATUS_NULL_POINTER:
        return "Erroneous NULL pointer";
     case NOTMUCH_STATUS_TAG_TOO_LONG:
-       return "Tag value is too long";
+       return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)";
     default:
     case NOTMUCH_STATUS_LAST_STATUS:
        return "Unknown error status value";
@@ -92,12 +131,51 @@ find_doc_ids (notmuch_database_t *notmuch,
     free (term);
 }
 
+static notmuch_private_status_t
+find_unique_doc_id (notmuch_database_t *notmuch,
+                   const char *prefix_name,
+                   const char *value,
+                   unsigned int *doc_id)
+{
+    Xapian::PostingIterator i, end;
+
+    find_doc_ids (notmuch, prefix_name, value, &i, &end);
+
+    if (i == end) {
+       *doc_id = 0;
+       return NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND;
+    } else {
+       *doc_id = *i;
+       return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+    }
+}
+
 static Xapian::Document
 find_document_for_doc_id (notmuch_database_t *notmuch, unsigned doc_id)
 {
     return notmuch->xapian_db->get_document (doc_id);
 }
 
+static notmuch_private_status_t
+find_unique_document (notmuch_database_t *notmuch,
+                     const char *prefix_name,
+                     const char *value,
+                     Xapian::Document *document,
+                     unsigned int *doc_id)
+{
+    notmuch_private_status_t status;
+
+    status = find_unique_doc_id (notmuch, prefix_name, value, doc_id);
+
+    if (status) {
+       *document = Xapian::Document ();
+       return status;
+    }
+
+    *document = find_document_for_doc_id (notmuch, *doc_id);
+    return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
 static void
 insert_thread_id (GHashTable *thread_ids, Xapian::Document doc)
 {
@@ -125,14 +203,15 @@ notmuch_message_t *
 notmuch_database_find_message (notmuch_database_t *notmuch,
                               const char *message_id)
 {
-    Xapian::PostingIterator i, end;
+    notmuch_private_status_t status;
+    unsigned int doc_id;
 
-    find_doc_ids (notmuch, "msgid", message_id, &i, &end);
+    status = find_unique_doc_id (notmuch, "msgid", message_id, &doc_id);
 
-    if (i == end)
+    if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
        return NULL;
 
-    return _notmuch_message_create (notmuch, notmuch, *i);
+    return _notmuch_message_create (notmuch, notmuch, doc_id);
 }
 
 /* Return one or more thread_ids, (as a GPtrArray of strings), for the
@@ -444,6 +523,101 @@ notmuch_database_get_path (notmuch_database_t *notmuch)
     return notmuch->path;
 }
 
+notmuch_private_status_t
+find_timestamp_document (notmuch_database_t *notmuch, const char *db_key,
+                        Xapian::Document *doc, unsigned int *doc_id)
+{
+    return find_unique_document (notmuch, "timestamp", db_key, doc, doc_id);
+}
+
+/* We allow the user to use arbitrarily long keys for timestamps,
+ * (they're for filesystem paths after all, which have no limit we
+ * know about). But we have a term-length limit. So if we exceed that,
+ * we'll use the SHA-1 of the user's key as the actual key for
+ * constructing a database term.
+ *
+ * Caution: This function returns a newly allocated string which the
+ * caller should free() when finished.
+ */
+static char *
+timestamp_db_key (const char *key)
+{
+    if (strlen (key) + 1 > NOTMUCH_TERM_MAX) {
+       return notmuch_sha1_of_string (key);
+    } else {
+       return strdup (key);
+    }
+}
+
+notmuch_status_t
+notmuch_database_set_timestamp (notmuch_database_t *notmuch,
+                               const char *key, time_t timestamp)
+{
+    Xapian::Document doc;
+    unsigned int doc_id;
+    notmuch_private_status_t status;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    char *db_key = NULL;
+
+    db_key = timestamp_db_key (key);
+
+    try {
+       status = find_timestamp_document (notmuch, db_key, &doc, &doc_id);
+
+       doc.add_value (0, Xapian::sortable_serialise (timestamp));
+
+       if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+           char *term = talloc_asprintf (NULL, "%s%s",
+                                         _find_prefix ("timestamp"), db_key);
+           doc.add_term (term);
+           talloc_free (term);
+
+           notmuch->xapian_db->add_document (doc);
+       } else {
+           notmuch->xapian_db->replace_document (doc_id, doc);
+       }
+
+    } catch (Xapian::Error &error) {
+       fprintf (stderr, "A Xapian exception occurred: %s.\n",
+                error.get_msg().c_str());
+       ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+    }
+
+    if (db_key)
+       free (db_key);
+
+    return ret;
+}
+
+time_t
+notmuch_database_get_timestamp (notmuch_database_t *notmuch, const char *key)
+{
+    Xapian::Document doc;
+    unsigned int doc_id;
+    notmuch_private_status_t status;
+    char *db_key = NULL;
+    time_t ret = 0;
+
+    db_key = timestamp_db_key (key);
+
+    try {
+       status = find_timestamp_document (notmuch, db_key, &doc, &doc_id);
+
+       if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
+           goto DONE;
+
+       ret =  Xapian::sortable_unserialise (doc.get_value (0));
+    } catch (Xapian::Error &error) {
+       goto DONE;
+    }
+
+  DONE:
+    if (db_key)
+       free (db_key);
+
+    return ret;
+}
+
 notmuch_status_t
 notmuch_database_add_message (notmuch_database_t *notmuch,
                              const char *filename)
@@ -521,23 +695,8 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
        /* Has a message previously been added with the same ID? */
        old_filename = notmuch_message_get_filename (message);
        if (old_filename && strlen (old_filename)) {
-           /* XXX: This is too noisy to actually print, and what do we
-            * really expect the user to do? Go manually delete a
-            * redundant message or merge two similar messages?
-            * Instead we should handle this transparently.
-            *
-            * What we likely want to move to is adding both filenames
-            * to the database so that subsequent indexing will pick up
-            * terms from both files.
-            */
-#if 0
-           fprintf (stderr,
-                    "Note: Attempting to add a message with a duplicate message ID:\n"
-                    "Old: %s\n"   "New: %s\n",
-                    old_filename, filename);
-           fprintf (stderr, "The old filename will be used, but any new terms\n"
-                    "from the new message will added to the database.\n");
-#endif
+           ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+           goto DONE;
        } else {
            _notmuch_message_set_filename (message, filename);
            _notmuch_message_add_term (message, "type", "mail");