#include <xapian.h>
-#include <glib.h> /* g_strdup_printf, g_free, GPtrArray, GHashTable */
+#include <glib.h> /* g_free, GPtrArray, GHashTable */
using namespace std;
*
* and has a single value:
*
- * TIMETAMPS: The time_t value from the user.
+ * TIMESTAMP: The time_t value from the user.
*/
/* With these prefix values we follow the conventions published here:
prefix_t BOOLEAN_PREFIX_INTERNAL[] = {
{ "type", "T" },
- { "thread", "G" },
{ "ref", "XREFERENCE" },
+ { "replyto", "XREPLYTO" },
{ "timestamp", "XTIMESTAMP" },
+ { "contact", "XCONTACT" }
};
prefix_t BOOLEAN_PREFIX_EXTERNAL[] = {
+ { "thread", "G" },
{ "tag", "K" },
{ "id", "Q" }
};
+prefix_t PROBABILISTIC_PREFIX[]= {
+ { "from", "XFROM" },
+ { "to", "XTO" },
+ { "attachment", "XATTACHMENT" },
+ { "subject", "XSUBJECT"}
+};
+
int
_internal_error (const char *format, ...)
{
va_start (va_args, format);
+ fprintf (stderr, "Internal error: ");
vfprintf (stderr, format, va_args);
exit (1);
if (strcmp (name, BOOLEAN_PREFIX_EXTERNAL[i].name) == 0)
return BOOLEAN_PREFIX_EXTERNAL[i].prefix;
+ for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++)
+ if (strcmp (name, PROBABILISTIC_PREFIX[i].name) == 0)
+ return PROBABILISTIC_PREFIX[i].prefix;
+
INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
return "";
switch (status) {
case NOTMUCH_STATUS_SUCCESS:
return "No error occurred";
+ case NOTMUCH_STATUS_OUT_OF_MEMORY:
+ return "Out of memory";
case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
return "A Xapian exception occurred";
case NOTMUCH_STATUS_FILE_ERROR:
return "Erroneous NULL pointer";
case NOTMUCH_STATUS_TAG_TOO_LONG:
return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)";
+ case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW:
+ return "Unblanced number of calls to notmuch_message_freeze/thaw";
default:
case NOTMUCH_STATUS_LAST_STATUS:
return "Unknown error status value";
}
}
-/* XXX: We should drop this function and convert all callers to call
- * _notmuch_message_add_term instead. */
-static void
-add_term (Xapian::Document doc,
- const char *prefix_name,
- const char *value)
-{
- const char *prefix;
- char *term;
-
- if (value == NULL)
- return;
-
- prefix = _find_prefix (prefix_name);
-
- term = g_strdup_printf ("%s%s", prefix, value);
-
- if (strlen (term) <= NOTMUCH_TERM_MAX)
- doc.add_term (term);
-
- g_free (term);
-}
-
static void
find_doc_ids (notmuch_database_t *notmuch,
const char *prefix_name,
Xapian::PostingIterator i;
char *term;
- term = g_strdup_printf ("%s%s", _find_prefix (prefix_name), value);
+ term = talloc_asprintf (notmuch, "%s%s",
+ _find_prefix (prefix_name), value);
*begin = notmuch->xapian_db->postlist_begin (term);
*end = notmuch->xapian_db->postlist_end (term);
- free (term);
+ talloc_free (term);
}
static notmuch_private_status_t
* If not NULL, then *next will be made to point to the first character
* not parsed, (possibly pointing to the final '\0' terminator.
*
- * Returns a newly allocated string which the caller should free()
- * when done with it.
+ * Returns a newly talloc'ed string belonging to 'ctx'.
*
* Returns NULL if there is any error parsing the message-id. */
static char *
-parse_message_id (const char *message_id, const char **next)
+parse_message_id (void *ctx, const char *message_id, const char **next)
{
const char *s, *end;
char *result;
if (end <= s)
return NULL;
- result = strndup (s, end - s + 1);
+ result = talloc_strndup (ctx, s, end - s + 1);
/* Finally, collapse any whitespace that is within the message-id
* itself. */
return result;
}
-/* Parse a References header value, putting a copy of each referenced
- * message-id into 'hash'. */
+/* Parse a References header value, putting a (talloc'ed under 'ctx')
+ * copy of each referenced message-id into 'hash'. */
static void
-parse_references (GHashTable *hash,
+parse_references (void *ctx,
+ GHashTable *hash,
const char *refs)
{
char *ref;
return;
while (*refs) {
- ref = parse_message_id (refs, &refs);
+ ref = parse_message_id (ctx, refs, &refs);
if (ref)
g_hash_table_insert (hash, ref, NULL);
char *
notmuch_database_default_path (void)
{
+ char *path;
+
if (getenv ("NOTMUCH_BASE"))
return strdup (getenv ("NOTMUCH_BASE"));
- return g_strdup_printf ("%s/mail", getenv ("HOME"));
+ if (asprintf (&path, "%s/mail", getenv ("HOME")) == -1) {
+ fprintf (stderr, "Out of memory.\n");
+ return xstrdup("");
+ }
+
+ return path;
}
notmuch_database_t *
goto DONE;
}
- notmuch_path = g_strdup_printf ("%s/%s", path, ".notmuch");
+ notmuch_path = talloc_asprintf (NULL, "%s/%s", path, ".notmuch");
err = mkdir (notmuch_path, 0755);
DONE:
if (notmuch_path)
- free (notmuch_path);
+ talloc_free (notmuch_path);
if (local_path)
free (local_path);
if (path == NULL)
path = local_path = notmuch_database_default_path ();
- notmuch_path = g_strdup_printf ("%s/%s", path, ".notmuch");
+ if (asprintf (¬much_path, "%s/%s", path, ".notmuch") == -1) {
+ notmuch_path = NULL;
+ fprintf (stderr, "Out of memory\n");
+ goto DONE;
+ }
err = stat (notmuch_path, &st);
if (err) {
goto DONE;
}
- xapian_path = g_strdup_printf ("%s/%s", notmuch_path, "xapian");
+ if (asprintf (&xapian_path, "%s/%s", notmuch_path, "xapian") == -1) {
+ xapian_path = NULL;
+ fprintf (stderr, "Out of memory\n");
+ goto DONE;
+ }
notmuch = talloc (NULL, notmuch_database_t);
notmuch->path = talloc_strdup (notmuch, path);
+ if (notmuch->path[strlen (notmuch->path) - 1] == '/')
+ notmuch->path[strlen (notmuch->path) - 1] = '\0';
+
try {
notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
Xapian::DB_CREATE_OR_OPEN);
notmuch->query_parser = new Xapian::QueryParser;
+ notmuch->term_gen = new Xapian::TermGenerator;
+ notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
+
notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
notmuch->query_parser->set_database (*notmuch->xapian_db);
+ notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
+ notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_EXTERNAL); i++) {
prefix_t *prefix = &BOOLEAN_PREFIX_EXTERNAL[i];
notmuch->query_parser->add_boolean_prefix (prefix->name,
prefix->prefix);
}
+
+ for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++) {
+ prefix_t *prefix = &PROBABILISTIC_PREFIX[i];
+ notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
+ }
} catch (const Xapian::Error &error) {
fprintf (stderr, "A Xapian exception occurred: %s\n",
error.get_msg().c_str());
+ notmuch = NULL;
}
DONE:
void
notmuch_database_close (notmuch_database_t *notmuch)
{
+ notmuch->xapian_db->flush ();
+
+ delete notmuch->term_gen;
delete notmuch->query_parser;
delete notmuch->xapian_db;
talloc_free (notmuch);
return notmuch->path;
}
-notmuch_private_status_t
+static notmuch_private_status_t
find_timestamp_document (notmuch_database_t *notmuch, const char *db_key,
Xapian::Document *doc, unsigned int *doc_id)
{
*
* Otherwise, returns a newly talloced string belonging to 'ctx'.
*/
-const char *
+static const char *
_resolve_message_id_to_thread_id (notmuch_database_t *notmuch,
void *ctx,
const char *message_id)
return ret;
}
+static void
+_my_talloc_free_for_g_hash (void *ptr)
+{
+ talloc_free (ptr);
+}
+
static notmuch_status_t
_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
notmuch_message_t *message,
notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
parents = g_hash_table_new_full (g_str_hash, g_str_equal,
- free, NULL);
+ _my_talloc_free_for_g_hash, NULL);
refs = notmuch_message_file_get_header (message_file, "references");
- parse_references (parents, refs);
+ parse_references (message, parents, refs);
in_reply_to = notmuch_message_file_get_header (message_file, "in-reply-to");
- parse_references (parents, in_reply_to);
+ parse_references (message, parents, in_reply_to);
+ _notmuch_message_add_term (message, "replyto",
+ parse_message_id (message, in_reply_to, NULL));
keys = g_hash_table_get_keys (parents);
for (l = keys; l; l = l->next) {
notmuch_status_t
notmuch_database_add_message (notmuch_database_t *notmuch,
- const char *filename)
+ const char *filename,
+ notmuch_message_t **message_ret)
{
notmuch_message_file_t *message_file;
notmuch_message_t *message;
notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+ notmuch_private_status_t private_status;
const char *date, *header;
- const char *from, *to, *subject, *old_filename;
+ const char *from, *to, *subject;
char *message_id;
+ if (message_ret)
+ *message_ret = NULL;
+
message_file = notmuch_message_file_open (filename);
if (message_file == NULL) {
ret = NOTMUCH_STATUS_FILE_ERROR;
(char *) NULL);
try {
- /* The first order of business is to find/create a message ID. */
+ /* Before we do any real work, (especially before doing a
+ * potential SHA-1 computation on the entire file's contents),
+ * let's make sure that what we're looking at looks like an
+ * actual email message.
+ */
+ from = notmuch_message_file_get_header (message_file, "from");
+ subject = notmuch_message_file_get_header (message_file, "subject");
+ to = notmuch_message_file_get_header (message_file, "to");
+
+ if (from == NULL &&
+ subject == NULL &&
+ to == NULL)
+ {
+ ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
+ goto DONE;
+ }
+
+ /* Now that we're sure it's mail, the first order of business
+ * is to find a message ID (or else create one ourselves). */
header = notmuch_message_file_get_header (message_file, "message-id");
if (header) {
- message_id = parse_message_id (header, NULL);
+ message_id = parse_message_id (message_file, header, NULL);
/* So the header value isn't RFC-compliant, but it's
* better than no message-id at all. */
if (message_id == NULL)
- message_id = xstrdup (header);
+ message_id = talloc_strdup (message_file, header);
} else {
/* No message-id at all, let's generate one by taking a
* hash over the file's contents. */
goto DONE;
}
- message_id = g_strdup_printf ("notmuch-sha1-%s", sha1);
+ message_id = talloc_asprintf (message_file,
+ "notmuch-sha1-%s", sha1);
free (sha1);
}
message = _notmuch_message_create_for_message_id (NULL,
notmuch,
message_id,
- &ret);
- free (message_id);
+ &private_status);
+
+ talloc_free (message_id);
if (message == NULL)
goto DONE;
- /* Has a message previously been added with the same ID? */
- old_filename = notmuch_message_get_filename (message);
- if (old_filename && strlen (old_filename)) {
- ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
- goto DONE;
- } else {
+ /* Is this a newly created message object? */
+ if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
_notmuch_message_set_filename (message, filename);
_notmuch_message_add_term (message, "type", "mail");
+ } else {
+ ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+ goto DONE;
}
ret = _notmuch_database_link_message (notmuch, message, message_file);
date = notmuch_message_file_get_header (message_file, "date");
_notmuch_message_set_date (message, date);
- from = notmuch_message_file_get_header (message_file, "from");
- subject = notmuch_message_file_get_header (message_file, "subject");
- to = notmuch_message_file_get_header (message_file, "to");
+ _notmuch_message_index_file (message, filename);
- if (from == NULL &&
- subject == NULL &&
- to == NULL)
- {
- ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
- goto DONE;
- } else {
- _notmuch_message_sync (message);
- }
+ _notmuch_message_sync (message);
} catch (const Xapian::Error &error) {
fprintf (stderr, "A Xapian exception occurred: %s.\n",
error.get_msg().c_str());
}
DONE:
- if (message)
- notmuch_message_destroy (message);
+ if (message) {
+ if (ret == NOTMUCH_STATUS_SUCCESS && message_ret)
+ *message_ret = message;
+ else
+ notmuch_message_destroy (message);
+ }
+
if (message_file)
notmuch_message_file_close (message_file);