mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-11-25 20:38:08 +01:00
Merge remote-tracking branch 'amdragon/eager-metadata-v4'
This commit is contained in:
commit
df91c16943
11 changed files with 346 additions and 331 deletions
|
@ -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 \
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
304
lib/message.cc
304
lib/message.cc
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
95
lib/string-list.c
Normal 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);
|
||||||
|
}
|
58
lib/tags.c
58
lib/tags.c
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue