Merge remote-tracking branch 'amdragon/eager-metadata-v4'

This commit is contained in:
Carl Worth 2011-04-25 14:26:42 -07:00
commit df91c16943
11 changed files with 346 additions and 331 deletions

View file

@ -50,6 +50,7 @@ extra_cflags += -I$(srcdir)/$(dir) -fPIC
libnotmuch_c_srcs = \ libnotmuch_c_srcs = \
$(notmuch_compat_srcs) \ $(notmuch_compat_srcs) \
$(dir)/filenames.c \ $(dir)/filenames.c \
$(dir)/string-list.c \
$(dir)/libsha1.c \ $(dir)/libsha1.c \
$(dir)/message-file.c \ $(dir)/message-file.c \
$(dir)/messages.c \ $(dir)/messages.c \

View file

@ -53,18 +53,16 @@ struct _notmuch_database {
Xapian::ValueRangeProcessor *value_range_processor; Xapian::ValueRangeProcessor *value_range_processor;
}; };
/* Convert tags from Xapian internal format to notmuch format. /* Return the list of terms from the given iterator matching a prefix.
* * The prefix will be stripped from the strings in the returned list.
* The function gets a TermIterator as argument and uses that iterator to find * The list will be allocated using ctx as the talloc context.
* all tag terms in the object. The tags are then converted to a
* notmuch_tags_t list and returned. The function needs to allocate memory for
* the resulting list and it uses the argument ctx as talloc context.
* *
* The function returns NULL on failure. * The function returns NULL on failure.
*/ */
notmuch_tags_t * notmuch_string_list_t *
_notmuch_convert_tags (void *ctx, Xapian::TermIterator &i, _notmuch_database_get_terms_with_prefix (void *ctx, Xapian::TermIterator &i,
Xapian::TermIterator &end); Xapian::TermIterator &end,
const char *prefix);
#pragma GCC visibility pop #pragma GCC visibility pop

View file

@ -1757,49 +1757,42 @@ notmuch_database_remove_message (notmuch_database_t *notmuch,
return status; return status;
} }
notmuch_tags_t * notmuch_string_list_t *
_notmuch_convert_tags (void *ctx, Xapian::TermIterator &i, _notmuch_database_get_terms_with_prefix (void *ctx, Xapian::TermIterator &i,
Xapian::TermIterator &end) Xapian::TermIterator &end,
const char *prefix)
{ {
const char *prefix = _find_prefix ("tag"); int prefix_len = strlen (prefix);
notmuch_tags_t *tags; notmuch_string_list_t *list;
std::string tag;
/* Currently this iteration is written with the assumption that list = _notmuch_string_list_create (ctx);
* "tag" has a single-character prefix. */ if (unlikely (list == NULL))
assert (strlen (prefix) == 1);
tags = _notmuch_tags_create (ctx);
if (unlikely (tags == NULL))
return NULL; return NULL;
i.skip_to (prefix); for (i.skip_to (prefix); i != end; i++) {
/* Terminate loop at first term without desired prefix. */
while (i != end) { if (strncmp ((*i).c_str (), prefix, prefix_len))
tag = *i;
if (tag.empty () || tag[0] != *prefix)
break; break;
_notmuch_tags_add_tag (tags, tag.c_str () + 1); _notmuch_string_list_append (list, (*i).c_str () + prefix_len);
i++;
} }
_notmuch_tags_prepare_iterator (tags); return list;
return tags;
} }
notmuch_tags_t * notmuch_tags_t *
notmuch_database_get_all_tags (notmuch_database_t *db) notmuch_database_get_all_tags (notmuch_database_t *db)
{ {
Xapian::TermIterator i, end; Xapian::TermIterator i, end;
notmuch_string_list_t *tags;
try { try {
i = db->xapian_db->allterms_begin(); i = db->xapian_db->allterms_begin();
end = db->xapian_db->allterms_end(); end = db->xapian_db->allterms_end();
return _notmuch_convert_tags(db, i, end); tags = _notmuch_database_get_terms_with_prefix (db, i, end,
_find_prefix ("tag"));
_notmuch_string_list_sort (tags);
return _notmuch_tags_create (db, tags);
} catch (const Xapian::Error &error) { } catch (const Xapian::Error &error) {
fprintf (stderr, "A Xapian exception occurred getting tags: %s.\n", fprintf (stderr, "A Xapian exception occurred getting tags: %s.\n",
error.get_msg().c_str()); error.get_msg().c_str());

View file

@ -23,34 +23,22 @@
/* Create an iterator to iterate over the basenames of files (or /* Create an iterator to iterate over the basenames of files (or
* directories) that all share a common parent directory. * directories) that all share a common parent directory.
*
* The code here is general enough to be reused for any case of
* iterating over the non-prefixed portion of terms sharing a common
* prefix.
*/ */
static notmuch_filenames_t * static notmuch_filenames_t *
_create_filenames_for_terms_with_prefix (void *ctx, _create_filenames_for_terms_with_prefix (void *ctx,
notmuch_database_t *notmuch, notmuch_database_t *notmuch,
const char *prefix) const char *prefix)
{ {
notmuch_filename_list_t *filename_list; notmuch_string_list_t *filename_list;
Xapian::TermIterator i, end; Xapian::TermIterator i, end;
int prefix_len = strlen (prefix);
filename_list = _notmuch_filename_list_create (ctx); i = notmuch->xapian_db->allterms_begin();
end = notmuch->xapian_db->allterms_end();
filename_list = _notmuch_database_get_terms_with_prefix (ctx, i, end,
prefix);
if (unlikely (filename_list == NULL)) if (unlikely (filename_list == NULL))
return NULL; return NULL;
end = notmuch->xapian_db->allterms_end (prefix);
for (i = notmuch->xapian_db->allterms_begin (prefix); i != end; i++)
{
std::string term = *i;
_notmuch_filename_list_add_filename (filename_list, term.c_str () +
prefix_len);
}
return _notmuch_filenames_create (ctx, filename_list); return _notmuch_filenames_create (ctx, filename_list);
} }

View file

@ -21,56 +21,14 @@
#include "notmuch-private.h" #include "notmuch-private.h"
struct _notmuch_filenames { struct _notmuch_filenames {
notmuch_filename_node_t *iterator; notmuch_string_node_t *iterator;
}; };
/* Create a new notmuch_filename_list_t object, with 'ctx' as its /* The notmuch_filenames_t iterates over a notmuch_string_list_t of
* talloc owner. * file names */
*
* This function can return NULL in case of out-of-memory.
*/
notmuch_filename_list_t *
_notmuch_filename_list_create (const void *ctx)
{
notmuch_filename_list_t *list;
list = talloc (ctx, notmuch_filename_list_t);
if (unlikely (list == NULL))
return NULL;
list->head = NULL;
list->tail = &list->head;
return list;
}
void
_notmuch_filename_list_add_filename (notmuch_filename_list_t *list,
const char *filename)
{
/* Create and initialize new node. */
notmuch_filename_node_t *node = talloc (list,
notmuch_filename_node_t);
node->filename = talloc_strdup (node, filename);
node->next = NULL;
/* Append the node to the list. */
*(list->tail) = node;
list->tail = &node->next;
}
void
_notmuch_filename_list_destroy (notmuch_filename_list_t *list)
{
talloc_free (list);
}
/* The notmuch_filenames_t is an iterator object for a
* notmuch_filename_list_t */
notmuch_filenames_t * notmuch_filenames_t *
_notmuch_filenames_create (const void *ctx, _notmuch_filenames_create (const void *ctx,
notmuch_filename_list_t *list) notmuch_string_list_t *list)
{ {
notmuch_filenames_t *filenames; notmuch_filenames_t *filenames;
@ -99,7 +57,7 @@ notmuch_filenames_get (notmuch_filenames_t *filenames)
if (filenames->iterator == NULL) if (filenames->iterator == NULL)
return NULL; return NULL;
return filenames->iterator->filename; return filenames->iterator->string;
} }
void void

View file

@ -32,7 +32,9 @@ struct _notmuch_message {
char *message_id; char *message_id;
char *thread_id; char *thread_id;
char *in_reply_to; char *in_reply_to;
notmuch_filename_list_t *filename_list; notmuch_string_list_t *tag_list;
notmuch_string_list_t *filename_term_list;
notmuch_string_list_t *filename_list;
char *author; char *author;
notmuch_message_file_t *message_file; notmuch_message_file_t *message_file;
notmuch_message_list_t *replies; notmuch_message_list_t *replies;
@ -102,6 +104,8 @@ _notmuch_message_create_for_document (const void *talloc_owner,
message->message_id = NULL; message->message_id = NULL;
message->thread_id = NULL; message->thread_id = NULL;
message->in_reply_to = NULL; message->in_reply_to = NULL;
message->tag_list = NULL;
message->filename_term_list = NULL;
message->filename_list = NULL; message->filename_list = NULL;
message->message_file = NULL; message->message_file = NULL;
message->author = NULL; message->author = NULL;
@ -256,6 +260,123 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch,
return message; return message;
} }
static char *
_notmuch_message_get_term (notmuch_message_t *message,
Xapian::TermIterator &i, Xapian::TermIterator &end,
const char *prefix)
{
int prefix_len = strlen (prefix);
const char *term = NULL;
char *value;
i.skip_to (prefix);
if (i != end)
term = (*i).c_str ();
if (!term || strncmp (term, prefix, prefix_len))
return NULL;
value = talloc_strdup (message, term + prefix_len);
#if DEBUG_DATABASE_SANITY
i++;
if (i != end && strncmp ((*i).c_str (), prefix, prefix_len) == 0) {
INTERNAL_ERROR ("Mail (doc_id: %d) has duplicate %s terms: %s and %s\n",
message->doc_id, prefix, value,
(*i).c_str () + prefix_len);
}
#endif
return value;
}
void
_notmuch_message_ensure_metadata (notmuch_message_t *message)
{
Xapian::TermIterator i, end;
const char *thread_prefix = _find_prefix ("thread"),
*tag_prefix = _find_prefix ("tag"),
*id_prefix = _find_prefix ("id"),
*filename_prefix = _find_prefix ("file-direntry"),
*replyto_prefix = _find_prefix ("replyto");
/* We do this all in a single pass because Xapian decompresses the
* term list every time you iterate over it. Thus, while this is
* slightly more costly than looking up individual fields if only
* one field of the message object is actually used, it's a huge
* win as more fields are used. */
i = message->doc.termlist_begin ();
end = message->doc.termlist_end ();
/* Get thread */
if (!message->thread_id)
message->thread_id =
_notmuch_message_get_term (message, i, end, thread_prefix);
/* Get tags */
assert (strcmp (thread_prefix, tag_prefix) < 0);
if (!message->tag_list) {
message->tag_list =
_notmuch_database_get_terms_with_prefix (message, i, end,
tag_prefix);
_notmuch_string_list_sort (message->tag_list);
}
/* Get id */
assert (strcmp (tag_prefix, id_prefix) < 0);
if (!message->message_id)
message->message_id =
_notmuch_message_get_term (message, i, end, id_prefix);
/* Get filename list. Here we get only the terms. We lazily
* expand them to full file names when needed in
* _notmuch_message_ensure_filename_list. */
assert (strcmp (id_prefix, filename_prefix) < 0);
if (!message->filename_term_list && !message->filename_list)
message->filename_term_list =
_notmuch_database_get_terms_with_prefix (message, i, end,
filename_prefix);
/* Get reply to */
assert (strcmp (filename_prefix, replyto_prefix) < 0);
if (!message->in_reply_to)
message->in_reply_to =
_notmuch_message_get_term (message, i, end, replyto_prefix);
/* It's perfectly valid for a message to have no In-Reply-To
* header. For these cases, we return an empty string. */
if (!message->in_reply_to)
message->in_reply_to = talloc_strdup (message, "");
}
static void
_notmuch_message_invalidate_metadata (notmuch_message_t *message,
const char *prefix_name)
{
if (strcmp ("thread", prefix_name) == 0) {
talloc_free (message->thread_id);
message->thread_id = NULL;
}
if (strcmp ("tag", prefix_name) == 0) {
talloc_unlink (message, message->tag_list);
message->tag_list = NULL;
}
if (strcmp ("file-direntry", prefix_name) == 0) {
talloc_free (message->filename_term_list);
talloc_free (message->filename_list);
message->filename_term_list = message->filename_list = NULL;
}
if (strcmp ("replyto", prefix_name) == 0) {
talloc_free (message->in_reply_to);
message->in_reply_to = NULL;
}
}
unsigned int unsigned int
_notmuch_message_get_doc_id (notmuch_message_t *message) _notmuch_message_get_doc_id (notmuch_message_t *message)
{ {
@ -265,32 +386,11 @@ _notmuch_message_get_doc_id (notmuch_message_t *message)
const char * const char *
notmuch_message_get_message_id (notmuch_message_t *message) notmuch_message_get_message_id (notmuch_message_t *message)
{ {
Xapian::TermIterator i; if (!message->message_id)
_notmuch_message_ensure_metadata (message);
if (message->message_id) if (!message->message_id)
return message->message_id; INTERNAL_ERROR ("Message with document ID of %u has no message ID.\n",
i = message->doc.termlist_begin ();
i.skip_to (_find_prefix ("id"));
if (i == message->doc.termlist_end ())
INTERNAL_ERROR ("Message with document ID of %d has no message ID.\n",
message->doc_id); message->doc_id);
message->message_id = talloc_strdup (message, (*i).c_str () + 1);
#if DEBUG_DATABASE_SANITY
i++;
if (i != message->doc.termlist_end () &&
strncmp ((*i).c_str (), _find_prefix ("id"),
strlen (_find_prefix ("id"))) == 0)
{
INTERNAL_ERROR ("Mail (doc_id: %d) has duplicate message IDs",
message->doc_id);
}
#endif
return message->message_id; return message->message_id;
} }
@ -329,89 +429,19 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header)
const char * const char *
_notmuch_message_get_in_reply_to (notmuch_message_t *message) _notmuch_message_get_in_reply_to (notmuch_message_t *message)
{ {
const char *prefix = _find_prefix ("replyto"); if (!message->in_reply_to)
int prefix_len = strlen (prefix); _notmuch_message_ensure_metadata (message);
Xapian::TermIterator i;
std::string in_reply_to;
if (message->in_reply_to)
return message->in_reply_to;
i = message->doc.termlist_begin ();
i.skip_to (prefix);
if (i != message->doc.termlist_end ())
in_reply_to = *i;
/* It's perfectly valid for a message to have no In-Reply-To
* header. For these cases, we return an empty string. */
if (i == message->doc.termlist_end () ||
strncmp (in_reply_to.c_str (), prefix, prefix_len))
{
message->in_reply_to = talloc_strdup (message, "");
return message->in_reply_to;
}
message->in_reply_to = talloc_strdup (message,
in_reply_to.c_str () + prefix_len);
#if DEBUG_DATABASE_SANITY
i++;
in_reply_to = *i;
if (i != message->doc.termlist_end () &&
strncmp ((*i).c_str (), prefix, prefix_len) == 0)
{
INTERNAL_ERROR ("Message %s has duplicate In-Reply-To IDs: %s and %s\n",
notmuch_message_get_message_id (message),
message->in_reply_to,
(*i).c_str () + prefix_len);
}
#endif
return message->in_reply_to; return message->in_reply_to;
} }
const char * const char *
notmuch_message_get_thread_id (notmuch_message_t *message) notmuch_message_get_thread_id (notmuch_message_t *message)
{ {
const char *prefix = _find_prefix ("thread"); if (!message->thread_id)
Xapian::TermIterator i; _notmuch_message_ensure_metadata (message);
std::string id; if (!message->thread_id)
INTERNAL_ERROR ("Message with document ID of %u has no thread ID.\n",
/* This code is written with the assumption that "thread" has a
* single-character prefix. */
assert (strlen (prefix) == 1);
if (message->thread_id)
return message->thread_id;
i = message->doc.termlist_begin ();
i.skip_to (prefix);
if (i != message->doc.termlist_end ())
id = *i;
if (i == message->doc.termlist_end () || id[0] != *prefix)
INTERNAL_ERROR ("Message with document ID of %d has no thread ID.\n",
message->doc_id); message->doc_id);
message->thread_id = talloc_strdup (message, id.c_str () + 1);
#if DEBUG_DATABASE_SANITY
i++;
id = *i;
if (i != message->doc.termlist_end () && id[0] == *prefix)
{
INTERNAL_ERROR ("Message %s has duplicate thread IDs: %s and %s\n",
notmuch_message_get_message_id (message),
message->thread_id,
id.c_str () + 1);
}
#endif
return message->thread_id; return message->thread_id;
} }
@ -444,11 +474,6 @@ _notmuch_message_add_filename (notmuch_message_t *message,
if (filename == NULL) if (filename == NULL)
INTERNAL_ERROR ("Message filename cannot be NULL."); INTERNAL_ERROR ("Message filename cannot be NULL.");
if (message->filename_list) {
_notmuch_filename_list_destroy (message->filename_list);
message->filename_list = NULL;
}
relative = _notmuch_database_relative_path (message->notmuch, filename); relative = _notmuch_database_relative_path (message->notmuch, filename);
status = _notmuch_database_split_path (local, relative, &directory, NULL); status = _notmuch_database_split_path (local, relative, &directory, NULL);
@ -496,11 +521,6 @@ _notmuch_message_remove_filename (notmuch_message_t *message,
notmuch_status_t status; notmuch_status_t status;
Xapian::TermIterator i, last; Xapian::TermIterator i, last;
if (message->filename_list) {
_notmuch_filename_list_destroy (message->filename_list);
message->filename_list = NULL;
}
status = _notmuch_database_filename_to_direntry (local, message->notmuch, status = _notmuch_database_filename_to_direntry (local, message->notmuch,
filename, &direntry); filename, &direntry);
if (status) if (status)
@ -580,21 +600,18 @@ _notmuch_message_clear_data (notmuch_message_t *message)
static void static void
_notmuch_message_ensure_filename_list (notmuch_message_t *message) _notmuch_message_ensure_filename_list (notmuch_message_t *message)
{ {
const char *prefix = _find_prefix ("file-direntry"); notmuch_string_node_t *node;
int prefix_len = strlen (prefix);
Xapian::TermIterator i;
if (message->filename_list) if (message->filename_list)
return; return;
message->filename_list = _notmuch_filename_list_create (message); if (!message->filename_term_list)
_notmuch_message_ensure_metadata (message);
i = message->doc.termlist_begin (); message->filename_list = _notmuch_string_list_create (message);
i.skip_to (prefix); node = message->filename_term_list->head;
if (i == message->doc.termlist_end () || if (!node) {
strncmp ((*i).c_str (), prefix, prefix_len))
{
/* A message document created by an old version of notmuch /* A message document created by an old version of notmuch
* (prior to rename support) will have the filename in the * (prior to rename support) will have the filename in the
* data of the document rather than as a file-direntry term. * data of the document rather than as a file-direntry term.
@ -608,24 +625,18 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message)
if (data == NULL) if (data == NULL)
INTERNAL_ERROR ("message with no filename"); INTERNAL_ERROR ("message with no filename");
_notmuch_filename_list_add_filename (message->filename_list, data); _notmuch_string_list_append (message->filename_list, data);
return; return;
} }
for (; i != message->doc.termlist_end (); i++) { for (; node; node = node->next) {
void *local = talloc_new (message); void *local = talloc_new (message);
const char *db_path, *directory, *basename, *filename; const char *db_path, *directory, *basename, *filename;
char *colon, *direntry = NULL; char *colon, *direntry = NULL;
unsigned int directory_id; unsigned int directory_id;
/* Terminate loop at first term without desired prefix. */ direntry = node->string;
if (strncmp ((*i).c_str (), prefix, prefix_len))
break;
direntry = talloc_strdup (local, (*i).c_str ());
direntry += prefix_len;
directory_id = strtol (direntry, &colon, 10); directory_id = strtol (direntry, &colon, 10);
@ -649,11 +660,13 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message)
filename = talloc_asprintf (message, "%s/%s", filename = talloc_asprintf (message, "%s/%s",
db_path, basename); db_path, basename);
_notmuch_filename_list_add_filename (message->filename_list, _notmuch_string_list_append (message->filename_list, filename);
filename);
talloc_free (local); talloc_free (local);
} }
talloc_free (message->filename_term_list);
message->filename_term_list = NULL;
} }
const char * const char *
@ -665,12 +678,12 @@ notmuch_message_get_filename (notmuch_message_t *message)
return NULL; return NULL;
if (message->filename_list->head == NULL || if (message->filename_list->head == NULL ||
message->filename_list->head->filename == NULL) message->filename_list->head->string == NULL)
{ {
INTERNAL_ERROR ("message with no filename"); INTERNAL_ERROR ("message with no filename");
} }
return message->filename_list->head->filename; return message->filename_list->head->string;
} }
notmuch_filenames_t * notmuch_filenames_t *
@ -716,10 +729,20 @@ notmuch_message_get_date (notmuch_message_t *message)
notmuch_tags_t * notmuch_tags_t *
notmuch_message_get_tags (notmuch_message_t *message) notmuch_message_get_tags (notmuch_message_t *message)
{ {
Xapian::TermIterator i, end; notmuch_tags_t *tags;
i = message->doc.termlist_begin();
end = message->doc.termlist_end(); if (!message->tag_list)
return _notmuch_convert_tags(message, i, end); _notmuch_message_ensure_metadata (message);
tags = _notmuch_tags_create (message, message->tag_list);
/* _notmuch_tags_create steals the reference to the tag_list, but
* in this case it's still used by the message, so we add an
* *additional* talloc reference to the list. As a result, it's
* possible to modify the message tags (which talloc_unlink's the
* current list from the message) while still iterating because
* the iterator will keep the current list alive. */
talloc_reference (message, message->tag_list);
return tags;
} }
const char * const char *
@ -809,6 +832,8 @@ _notmuch_message_add_term (notmuch_message_t *message,
talloc_free (term); talloc_free (term);
_notmuch_message_invalidate_metadata (message, prefix_name);
return NOTMUCH_PRIVATE_STATUS_SUCCESS; return NOTMUCH_PRIVATE_STATUS_SUCCESS;
} }
@ -874,6 +899,8 @@ _notmuch_message_remove_term (notmuch_message_t *message,
talloc_free (term); talloc_free (term);
_notmuch_message_invalidate_metadata (message, prefix_name);
return NOTMUCH_PRIVATE_STATUS_SUCCESS; return NOTMUCH_PRIVATE_STATUS_SUCCESS;
} }
@ -1283,6 +1310,7 @@ notmuch_message_remove_all_tags (notmuch_message_t *message)
if (! message->frozen) if (! message->frozen)
_notmuch_message_sync (message); _notmuch_message_sync (message);
talloc_free (tags);
return NOTMUCH_STATUS_SUCCESS; return NOTMUCH_STATUS_SUCCESS;
} }

View file

@ -146,13 +146,14 @@ notmuch_messages_destroy (notmuch_messages_t *messages)
notmuch_tags_t * notmuch_tags_t *
notmuch_messages_collect_tags (notmuch_messages_t *messages) notmuch_messages_collect_tags (notmuch_messages_t *messages)
{ {
notmuch_tags_t *tags, *msg_tags; notmuch_string_list_t *tags;
notmuch_tags_t *msg_tags;
notmuch_message_t *msg; notmuch_message_t *msg;
GHashTable *htable; GHashTable *htable;
GList *keys, *l; GList *keys, *l;
const char *tag; const char *tag;
tags = _notmuch_tags_create (messages); tags = _notmuch_string_list_create (messages);
if (tags == NULL) return NULL; if (tags == NULL) return NULL;
htable = g_hash_table_new_full (g_str_hash, g_str_equal, free, NULL); htable = g_hash_table_new_full (g_str_hash, g_str_equal, free, NULL);
@ -170,12 +171,12 @@ notmuch_messages_collect_tags (notmuch_messages_t *messages)
keys = g_hash_table_get_keys (htable); keys = g_hash_table_get_keys (htable);
for (l = keys; l; l = l->next) { for (l = keys; l; l = l->next) {
_notmuch_tags_add_tag (tags, (char *)l->data); _notmuch_string_list_append (tags, (char *)l->data);
} }
g_list_free (keys); g_list_free (keys);
g_hash_table_destroy (htable); g_hash_table_destroy (htable);
_notmuch_tags_prepare_iterator (tags); _notmuch_string_list_sort (tags);
return tags; return _notmuch_tags_create (messages, tags);
} }

View file

@ -457,48 +457,45 @@ notmuch_sha1_of_string (const char *str);
char * char *
notmuch_sha1_of_file (const char *filename); notmuch_sha1_of_file (const char *filename);
/* string-list.c */
typedef struct _notmuch_string_node {
char *string;
struct _notmuch_string_node *next;
} notmuch_string_node_t;
typedef struct _notmuch_string_list {
int length;
notmuch_string_node_t *head;
notmuch_string_node_t **tail;
} notmuch_string_list_t;
notmuch_string_list_t *
_notmuch_string_list_create (const void *ctx);
/* Add 'string' to 'list'.
*
* The list will create its own talloced copy of 'string'.
*/
void
_notmuch_string_list_append (notmuch_string_list_t *list,
const char *string);
void
_notmuch_string_list_sort (notmuch_string_list_t *list);
/* tags.c */ /* tags.c */
notmuch_tags_t * notmuch_tags_t *
_notmuch_tags_create (void *ctx); _notmuch_tags_create (const void *ctx, notmuch_string_list_t *list);
void
_notmuch_tags_add_tag (notmuch_tags_t *tags, const char *tag);
void
_notmuch_tags_prepare_iterator (notmuch_tags_t *tags);
/* filenames.c */ /* filenames.c */
typedef struct _notmuch_filename_node { /* The notmuch_filenames_t iterates over a notmuch_string_list_t of
char *filename; * file names */
struct _notmuch_filename_node *next;
} notmuch_filename_node_t;
typedef struct _notmuch_filename_list {
notmuch_filename_node_t *head;
notmuch_filename_node_t **tail;
} notmuch_filename_list_t;
notmuch_filename_list_t *
_notmuch_filename_list_create (const void *ctx);
/* Add 'filename' to 'list'.
*
* The list will create its own talloced copy of 'filename'.
*/
void
_notmuch_filename_list_add_filename (notmuch_filename_list_t *list,
const char *filename);
void
_notmuch_filename_list_destroy (notmuch_filename_list_t *list);
/* The notmuch_filenames_t is an iterator object for a
* notmuch_filename_list_t */
notmuch_filenames_t * notmuch_filenames_t *
_notmuch_filenames_create (const void *ctx, _notmuch_filenames_create (const void *ctx,
notmuch_filename_list_t *list); notmuch_string_list_t *list);
#pragma GCC visibility pop #pragma GCC visibility pop

95
lib/string-list.c Normal file
View file

@ -0,0 +1,95 @@
/* strings.c - Iterator for a list of strings
*
* Copyright © 2010 Intel Corporation
*
* 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 http://www.gnu.org/licenses/ .
*
* Author: Carl Worth <cworth@cworth.org>
* Austin Clements <aclements@csail.mit.edu>
*/
#include "notmuch-private.h"
/* Create a new notmuch_string_list_t object, with 'ctx' as its
* talloc owner.
*
* This function can return NULL in case of out-of-memory.
*/
notmuch_string_list_t *
_notmuch_string_list_create (const void *ctx)
{
notmuch_string_list_t *list;
list = talloc (ctx, notmuch_string_list_t);
if (unlikely (list == NULL))
return NULL;
list->length = 0;
list->head = NULL;
list->tail = &list->head;
return list;
}
void
_notmuch_string_list_append (notmuch_string_list_t *list,
const char *string)
{
/* Create and initialize new node. */
notmuch_string_node_t *node = talloc (list, notmuch_string_node_t);
node->string = talloc_strdup (node, string);
node->next = NULL;
/* Append the node to the list. */
*(list->tail) = node;
list->tail = &node->next;
list->length++;
}
static int
cmpnode (const void *pa, const void *pb)
{
notmuch_string_node_t *a = *(notmuch_string_node_t * const *)pa;
notmuch_string_node_t *b = *(notmuch_string_node_t * const *)pb;
return strcmp (a->string, b->string);
}
void
_notmuch_string_list_sort (notmuch_string_list_t *list)
{
notmuch_string_node_t **nodes, *node;
int i;
if (list->length == 0)
return;
nodes = talloc_array (list, notmuch_string_node_t *, list->length);
if (unlikely (nodes == NULL))
INTERNAL_ERROR ("Could not allocate memory for list sort");
for (i = 0, node = list->head; node; i++, node = node->next)
nodes[i] = node;
qsort (nodes, list->length, sizeof (*nodes), cmpnode);
for (i = 0; i < list->length - 1; ++i)
nodes[i]->next = nodes[i+1];
nodes[i]->next = NULL;
list->head = nodes[0];
list->tail = &nodes[i]->next;
talloc_free (nodes);
}

View file

@ -20,30 +20,18 @@
#include "notmuch-private.h" #include "notmuch-private.h"
#include <glib.h> /* GList */
struct _notmuch_tags { struct _notmuch_tags {
int sorted; notmuch_string_node_t *iterator;
GList *tags;
GList *iterator;
}; };
/* XXX: Should write some talloc-friendly list to avoid the need for
* this. */
static int
_notmuch_tags_destructor (notmuch_tags_t *tags)
{
g_list_free (tags->tags);
return 0;
}
/* Create a new notmuch_tags_t object, with 'ctx' as its talloc owner. /* Create a new notmuch_tags_t object, with 'ctx' as its talloc owner.
* The returned iterator will talloc_steal the 'list', since the list
* is almost always transient.
* *
* This function can return NULL in case of out-of-memory. * This function can return NULL in case of out-of-memory.
*/ */
notmuch_tags_t * notmuch_tags_t *
_notmuch_tags_create (void *ctx) _notmuch_tags_create (const void *ctx, notmuch_string_list_t *list)
{ {
notmuch_tags_t *tags; notmuch_tags_t *tags;
@ -51,44 +39,12 @@ _notmuch_tags_create (void *ctx)
if (unlikely (tags == NULL)) if (unlikely (tags == NULL))
return NULL; return NULL;
talloc_set_destructor (tags, _notmuch_tags_destructor); tags->iterator = list->head;
talloc_steal (tags, list);
tags->sorted = 1;
tags->tags = NULL;
tags->iterator = NULL;
return tags; return tags;
} }
/* Add a new tag to 'tags'. The tags object will create its own copy
* of the string.
*
* Note: The tags object will not do anything to prevent duplicate
* tags being stored, so the caller really shouldn't pass
* duplicates. */
void
_notmuch_tags_add_tag (notmuch_tags_t *tags, const char *tag)
{
tags->tags = g_list_prepend (tags->tags, talloc_strdup (tags, tag));
tags->sorted = 0;
}
/* Prepare 'tag' for iteration.
*
* The internal creator of 'tags' should call this function before
* returning 'tags' to the user to call the public functions such as
* notmuch_tags_valid, notmuch_tags_get, and
* notmuch_tags_move_to_next. */
void
_notmuch_tags_prepare_iterator (notmuch_tags_t *tags)
{
if (! tags->sorted)
tags->tags = g_list_sort (tags->tags, (GCompareFunc) strcmp);
tags->sorted = 1;
tags->iterator = tags->tags;
}
notmuch_bool_t notmuch_bool_t
notmuch_tags_valid (notmuch_tags_t *tags) notmuch_tags_valid (notmuch_tags_t *tags)
{ {
@ -101,7 +57,7 @@ notmuch_tags_get (notmuch_tags_t *tags)
if (tags->iterator == NULL) if (tags->iterator == NULL)
return NULL; return NULL;
return (char *) tags->iterator->data; return (char *) tags->iterator->string;
} }
void void

View file

@ -537,23 +537,23 @@ notmuch_thread_get_newest_date (notmuch_thread_t *thread)
notmuch_tags_t * notmuch_tags_t *
notmuch_thread_get_tags (notmuch_thread_t *thread) notmuch_thread_get_tags (notmuch_thread_t *thread)
{ {
notmuch_tags_t *tags; notmuch_string_list_t *tags;
GList *keys, *l; GList *keys, *l;
tags = _notmuch_tags_create (thread); tags = _notmuch_string_list_create (thread);
if (unlikely (tags == NULL)) if (unlikely (tags == NULL))
return NULL; return NULL;
keys = g_hash_table_get_keys (thread->tags); keys = g_hash_table_get_keys (thread->tags);
for (l = keys; l; l = l->next) for (l = keys; l; l = l->next)
_notmuch_tags_add_tag (tags, (char *) l->data); _notmuch_string_list_append (tags, (char *) l->data);
g_list_free (keys); g_list_free (keys);
_notmuch_tags_prepare_iterator (tags); _notmuch_string_list_sort (tags);
return tags; return _notmuch_tags_create (thread, tags);
} }
void void