From a3bd931aafecd653f9a9077eb0924242a362105e Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 26 Jun 2011 13:12:40 +0300 Subject: [PATCH] * overhaul/clean-up container code, add path object; seems to work now --- src/mu-msg-threader.c | 898 +++++++++++++++++++------------- src/tests/test-mu-threads.c | 2 +- src/tests/testdir3/cur/child4.0 | 4 +- src/tests/testdir3/cur/child4.1 | 4 +- 4 files changed, 530 insertions(+), 378 deletions(-) diff --git a/src/mu-msg-threader.c b/src/mu-msg-threader.c index 7c19f649..c1471130 100644 --- a/src/mu-msg-threader.c +++ b/src/mu-msg-threader.c @@ -18,6 +18,7 @@ ** */ #include /* for log, ceil */ +#include /* for memset */ #include "mu-msg-threader.h" #include "mu-str.h" @@ -47,43 +48,75 @@ * */ - -/* Container data structure, as seen in the JWZ-doc; one differences - * is that I use GSLists for the children, rather than 'next' - * pointers - * - * */ -struct _Container { - MuMsg *_msg; - unsigned int _docid; - struct _Container *_parent; - GSList *_children; - gboolean _dup; +/* + * path data structure, to determine the thread paths mentioned + * above + * */ +struct _Path { + int *_data; + guint _len; }; -typedef struct _Container Container; +typedef struct _Path Path; + +static Path* path_new (guint initial); +static void path_destroy (Path *p); +static void path_inc (Path *p, guint index); +static gchar* path_to_string (Path *p, const char* frmt); + + + +/* Container data structure, as seen in the JWZ-doc* + * +*/ +enum _ContainerFlag { + CONTAINER_FLAG_NONE = 0, + CONTAINER_FLAG_DELETE = 1 << 0, + CONTAINER_FLAG_SPLICE = 1 << 1, + CONTAINER_FLAG_DUP = 1 << 2 +}; +typedef guint8 ContainerFlag; + +struct _Container { + struct _Container *parent, *child, *next; + ContainerFlag flags; + MuMsg *msg; + guint docid; + const char* msgid; +}; +typedef struct _Container Container; + +static Container* container_new (MuMsg *msg, guint docid, const char* msgid); +static void container_destroy (Container *c); +static void container_add_child (Container *c, Container *child); +static void container_add_sibling (Container *c, Container *sibling); +static void container_remove_child (Container *c, Container *child); + +typedef gboolean (*ContainerForeachFunc) (Container*, gpointer); +static gboolean container_foreach (Container *c, + ContainerForeachFunc func, + gpointer user_data); + +typedef void (*ContainerPathForeachFunc) (Container*, gpointer, Path*); +static void container_path_foreach (Container *c, + ContainerPathForeachFunc func, + gpointer user_data); + +static void container_splice (Container *parent, Container *child); +size_t container_child_count (Container *c); +static gboolean container_reachable (Container *haystack, Container *needle); +static void container_dump (Container *c, gboolean recursive); + + + + -static Container *container_new (MuMsg *msg, unsigned docid); -static void container_destroy (Container *c); -static void container_dump (Container *c); -/* breath-first recursive traversal */ -typedef gboolean (*ContainerTraverseFunc) (Container *c, guint level, - gpointer user_data); -static gboolean container_traverse (Container *c, - guint level, - ContainerTraverseFunc func, - gpointer user_data); -static gboolean container_add_child (Container *c, Container *child); -static void container_splice_child (Container *c, Container *child); -static gboolean container_is_root (Container *c); -static void container_dump_tree (Container *c); -static void container_remove_child (Container *c, Container *child); /* step 1 */ static GHashTable* create_containers (MuMsgIter *iter); -/* step 2 */ static Container *find_root (GHashTable *ids); +/* step 2 */ static Container *find_root_set (GHashTable *ids); static void prune_empty_containers (Container *root); /* static void group_root_set_by_subject (GSList *root_set); */ GHashTable* create_doc_id_thread_path_hash (Container *root, size_t match_num); -static void sort_by_date (Container *root); +static Container* sort_by_date (Container *root); /* msg threading algorithm, based on JWZ's algorithm, * http://www.jwz.org/doc/threading.html */ @@ -91,131 +124,166 @@ GHashTable* mu_msg_threader_calculate (MuMsgIter *iter, size_t matchnum) { GHashTable *id_table, *thread_ids; - Container *root; + Container *root_set; g_return_val_if_fail (iter, FALSE); /* step 1 */ id_table = create_containers (iter); - /* step 2 -- JWZ calls this the 'root-set'; in our case, the - * root_set is the list of children for the dummy - * root-container */ - root = find_root (id_table); + /* step 2 -- the root_set is the list of children without parent */ + root_set = find_root_set (id_table); + /* step 3: skip until the end; we still need to containers */ - + /* step 4: prune empty containers */ - prune_empty_containers (root); + prune_empty_containers (root_set); /* recalculate root set */ - sort_by_date (root); + root_set = sort_by_date (root_set); /* step 5: group root set by subject */ //group_root_set_by_subject (root_set); /* sort */ mu_msg_iter_reset (iter); /* go all the way back */ + /* finally, deliver the docid => thread-path hash */ - thread_ids = create_doc_id_thread_path_hash (root, matchnum); + thread_ids = create_doc_id_thread_path_hash (root_set, + matchnum); g_hash_table_destroy (id_table); /* step 3*/ - container_destroy (root); - return thread_ids; } -static gboolean -check_exists (Container *c, guint level, Container *c2) -{ - return c != c2; -} - - -static gboolean -already_referenced (Container *c1, Container *c2) -{ - gboolean notfound; - notfound = container_traverse ((Container*)c1, 0, - (ContainerTraverseFunc)check_exists, - c2); - - return notfound ? FALSE : TRUE; -} - - -/* find a container for the given msgid; if it does not exist yet, - * create a new one, and register it */ +/* a referred message is a message that is refered by some other message */ static Container* -find_or_create (GHashTable *id_table, const char* msgid, unsigned docid) +find_or_create_referred (GHashTable *id_table, const char *msgid) { Container *c; g_return_val_if_fail (msgid, NULL); + + c = g_hash_table_lookup (id_table, msgid); + if (!c) { + c = container_new (NULL, 0, msgid); + g_hash_table_insert (id_table, (gpointer)msgid, c); + } + + return c; +} + +/* find a container for the given msgid; if it does not exist yet, + * create a new one, and register it */ +static Container* +find_or_create (GHashTable *id_table, MuMsg *msg, guint docid) +{ + Container *c; + const char* msgid; + + g_return_val_if_fail (msg, NULL); + + msgid = mu_msg_get_msgid (msg); + if (!msgid) + msgid = mu_msg_get_path (msg); /* fake it */ c = g_hash_table_lookup (id_table, msgid); - /* case 1: there is no container for this msgid yet; create - * it */ - if (!c) { - c = container_new (NULL, docid); - g_hash_table_insert (id_table, g_strdup(msgid), c); + /* If id_table contains an empty Container for this ID: * * + * Store this message in the Container's message slot. */ + if (c && !c->msg) { + c->msg = mu_msg_ref (msg); + return c; + + } else if (!c) { /* Else: Create a new Container object holding + this message; Index the Container by + Message-ID in id_table. */ + c = container_new (msg, docid, msgid); + g_hash_table_insert (id_table, (gpointer)msgid, c); return c; - } - /* case 2: the container exists, but it did not have a docid - * yet (e.g., it was create for the messages refered to in - * References:, and now we found the actual messages */ - else if (c->_docid == 0) { - c->_docid = docid; - return c; - } - - /* case 3: the container exists, and has a docid already -- - * this means that we are seeing *another message* with a - * message-id we already saw... create this message, and mark - * it as a duplicate, and a child of the one we saw before */ - else if (docid != 0) { + } else { /* c && c->msg */ + /* special case, not in the JWZ algorithm: the + * container exists already and has a message; this + * means that we are seeing *another message* with a + * message-id we already saw... create this message, + * and mark it as a duplicate, and a child of the one + * we saw before; use its path as a fake message-id*/ Container *c2; - c2 = container_new (NULL, docid); - c2->_dup = TRUE; + c2 = container_new (msg, docid, msgid); + c2->flags = CONTAINER_FLAG_DUP; container_add_child (c, c2); g_hash_table_insert (id_table, - g_strdup_printf ("!!%s", msgid), - c2); - return c2; + (gpointer)mu_msg_get_path (msg), c2); + return NULL; /* don't process this message further */ } - - return c; } static void /* 1B */ handle_references (GHashTable *id_table, Container *c) { const GSList *refs, *cur; + Container *parent; - refs = mu_msg_get_references (c->_msg); - if (!refs) + refs = mu_msg_get_references (c->msg); + if (!refs) return; /* nothing to do */ - - /* go over over our list of refs */ - for (cur = refs; cur && cur->next; cur = g_slist_next (cur)) { - Container *c1, *c2; /* two consecutive refs in the list; - * we register them as parent, child */ + + /* For each element in the message's References field: + + Find a Container object for the given Message-ID: If + there's one in id_table use that; Otherwise, make (and + index) one with a null Message. */ - c1 = find_or_create (id_table, (gchar*)cur->data, 0); - c2 = find_or_create (id_table, (gchar*)cur->next->data, 0); + /* go over over our list of refs, until 1 before the last... */ + for (parent = NULL, cur = refs; cur; cur = g_slist_next (cur)) { - container_add_child (c1, c2); - } + Container *child; + + child = find_or_create_referred (id_table, (gchar*)cur->data); - /* now cur points to the final ref, which refers to our direct - * parent... register it */ - if (cur) { - Container *parent; - parent = find_or_create (id_table, (gchar*)cur->data, 0); - container_add_child (parent, c); + /*Link the References field's Containers together in + * the order implied by the References header. + + If they are already linked, don't change the existing + links. Do not add a link if adding that link would + introduce a loop: that is, before asserting A->B, + search down the children of B to see if A is + reachable, and also search down the children of A to + see if B is reachable. If either is already reachable + as a child of the other, don't add the link. */ + + if (parent && + !container_reachable (parent, child) && + !container_reachable (child, parent)) { + container_add_child (parent, child); + } + parent = child; } + + /* 'parent' points to the last ref: our direct parent; + + Set the parent of this message to be the last element in + References. Note that this message may have a parent + already: this can happen because we saw this ID in a + References field, and presumed a parent based on the other + entries in that field. Now that we have the actual message, + we can be more definitive, so throw away the old parent and + use this new one. Find this Container in the parent's + children list, and unlink it. + + Note that this could cause this message to now have no parent, if it + has no references field, but some message referred to it as the + non-first element of its references. (Which would have been some kind + of lie...) + + Note that at all times, the various ``parent'' and ``child'' fields + must be kept inter-consistent. */ + if (!container_reachable (parent, c) && + !container_reachable (c, parent)) { + container_add_child (parent, c); + } } @@ -227,10 +295,8 @@ create_containers (MuMsgIter *iter) GHashTable *id_table; id_table = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify)g_free, /* we copy msgid */ + NULL, (GDestroyNotify)container_destroy); - /* FIXME: copying should not be needed... we only copy because - * duplicate messages need an allocated, fake-msgid */ for (mu_msg_iter_reset (iter); !mu_msg_iter_is_done (iter); mu_msg_iter_next (iter)) { @@ -238,26 +304,16 @@ create_containers (MuMsgIter *iter) Container *c; MuMsg *msg; unsigned docid; - const char *msgid; /* 1.A */ msg = mu_msg_iter_get_msg (iter, NULL); - msgid = mu_msg_get_msgid (msg); docid = mu_msg_iter_get_docid (iter); - - if (!msgid) { - const char* path; - path = mu_msg_get_path(msg); - /* g_warning ("msg without msgid %s", path); */ - msgid = path; /* fake it... */ - } - - c = find_or_create (id_table, msgid, docid); - if (!c->_msg) - c->_msg = mu_msg_ref (msg); + c = find_or_create (id_table, msg, docid); + /* 1.B and C */ - handle_references (id_table, c); + if (c) + handle_references (id_table, c); } return id_table; @@ -266,82 +322,94 @@ create_containers (MuMsgIter *iter) static void -filter_root_set (const gchar *msgid, Container *c, Container *root) +filter_root_set (const gchar *msgid, Container *c, Container **root_set) { - if (!c->_parent) /* this *before* adding it to the dummy root */ - container_add_child (root, c); + if (c->parent) + return; + + if (*root_set == NULL) + *root_set = c; + else + container_add_sibling (*root_set, c); } -/* 2. Find the root - this is dummy container which takes the - * until-now parentless Container-objects as children. JWZ calls this - * the 'root_set' - - Walk over the elements of id_table, and gather a list of the +/* 2. Walk over the elements of id_table, and gather a list of the Container objects that have no parents, but do have children */ static Container* -find_root (GHashTable *ids) +find_root_set (GHashTable *ids) { - GSList *lst; - Container *root; - - root = container_new (NULL, 0); + Container *root_set; - lst = NULL; - g_hash_table_foreach (ids, (GHFunc)filter_root_set, root); - - return root; + root_set = NULL; + g_hash_table_foreach (ids, (GHFunc)filter_root_set, &root_set); + + return root_set; } -/* this function will mark 'containers', and the do the pruning on - * their children */ + +static gboolean +prune_maybe (Container *c) +{ + Container *cur; + + for (cur = c->child; cur; cur = cur->next) { + if (cur->flags & CONTAINER_FLAG_DELETE) + container_remove_child (c, cur); + else if (cur->flags & CONTAINER_FLAG_SPLICE) + container_splice (c, cur); + } + + /* don't touch containers with messages */ + if (c->msg) + return TRUE; + + /* A. If it is an msg-less container with no children, mark it for deletion. */ + if (!c->child) { + c->flags |= CONTAINER_FLAG_DELETE; + return TRUE; + } + + /* B. If the Container has no Message, but does have + * children, remove this container but promote its + * children to this level (that is, splice them in to + * the current child list.) + * + * Do not promote the children if doing so would + * promote them to the root set -- unless there is + * only one child, in which case, do. + */ + if (!c->parent || (!c->parent->parent && container_child_count(c) != 1)) + return TRUE; + + c->flags |= CONTAINER_FLAG_SPLICE; + + return TRUE; +} + + + static void prune_empty_containers (Container *container) { - GSList *cur; + Container *dummy, *cur; - cur = container->_children; - while (cur) { - - Container *c; - c = (Container*)cur->data; + dummy = container_new (NULL, 0, "DUMMY"); - prune_empty_containers (c); /* recurse! */ - - /* don't touch containers with messages */ - if (c->_msg) - goto next; + container_add_child (dummy, container); - /* A. If it is an msg-less container with no children, nuke it. */ - if (!c->_children && c->_parent) { - container_remove_child (c->_parent, c); - goto next; - } - /* B. If the Container has no Message, but does have - * children, remove this container but promote its - * children to this level (that is, splice them in to - * the current child list.) - * - * Do not promote the children if doing so would - * promote them to the root set -- unless there is - * only one child, in which case, do. - */ - if (container_is_root(container) && - g_slist_length(c->_children) != 1) - goto next; + container_foreach (dummy, (ContainerForeachFunc)prune_maybe, NULL); + + for (cur = dummy->child; cur; cur = cur->next) + cur->parent = NULL; - container_splice_child (container, c); - /* reiterate over the children; they may have changed */ - cur = container->_children; - continue; - - next: - cur = g_slist_next (cur); - } + container_destroy (dummy); } + + #if 0 /* 5. group root set by subject */ static void @@ -391,39 +459,59 @@ group_root_set_by_subject (GSList *root_set) #endif -static gint +G_GNUC_UNUSED static gint cmp_dates (Container *c1, Container *c2) { MuMsg *m1, *m2; - m1 = c1->_msg; - m2 = c2->_msg; + + m1 = c1->msg; + m2 = c2->msg; if (!m1) return m2 ? 1 : 0; if (!m2) - return m1 ? 0 : 1; + return m1 ? 0 : 1; return mu_msg_get_date (m1) - mu_msg_get_date (m2); } -static void -sort_by_date (Container *container) + +/* yuck ugly */ +G_GNUC_UNUSED static Container* +sort_by_date (Container *c) { - GSList *cur; + GSList *lst, *cur; - for (cur = container->_children; cur; cur = g_slist_next(cur)) { - Container *c; - c = (Container*)cur->data; - sort_by_date (c); /* recurse */ + if (!c) + return NULL; + + c->child = sort_by_date (c->child); /* recurse! */ + + for (lst = NULL; c; c = c->next) { + lst = g_slist_prepend (lst, c); } + + lst = g_slist_sort (lst, (GCompareFunc)cmp_dates); - container->_children = g_slist_sort (container->_children, - (GCompareFunc)cmp_dates); + c = (Container*)lst->data; + c->next = NULL; + cur = g_slist_next (lst); + + for (;cur; cur = g_slist_next(cur)) { + Container *sib; + sib = (Container*)cur->data; + sib->next = NULL; + container_add_sibling (c, sib); + } + + g_slist_free (lst); + return c; } + static MuMsgIterThreadInfo* thread_info_new (gchar *threadpath, gboolean root, - gboolean first_child, gboolean empty_parent, gboolean is_dup) + gboolean child, gboolean empty_parent, gboolean is_dup) { MuMsgIterThreadInfo *ti; @@ -432,7 +520,7 @@ thread_info_new (gchar *threadpath, gboolean root, ti->prop = 0; ti->prop |= root ? MU_MSG_ITER_THREAD_PROP_ROOT : 0; - ti->prop |= first_child ? MU_MSG_ITER_THREAD_PROP_FIRST_CHILD : 0; + ti->prop |= child ? MU_MSG_ITER_THREAD_PROP_FIRST_CHILD : 0; ti->prop |= empty_parent ? MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT : 0; ti->prop |= is_dup ? MU_MSG_ITER_THREAD_PROP_DUP : 0; @@ -451,54 +539,31 @@ thread_info_destroy (MuMsgIterThreadInfo *ti) struct _ThreadInfo { GHashTable *hash; - GQueue *idqueue; - unsigned prev_level; const char* format; }; typedef struct _ThreadInfo ThreadInfo; -struct _TP { - char *threadpath; - const char *frmt; -}; -typedef struct _TP TP; - - -static void -accumulate_path (int seq, TP *tp) -{ - char segm[16]; - snprintf (segm, sizeof(segm), tp->frmt, seq); - - if (tp->threadpath) { - char *path; - path = g_strdup_printf ("%s:%s", tp->threadpath, segm); - g_free (tp->threadpath); - tp->threadpath = path; - } else - tp->threadpath = g_strdup (segm); -} - static void add_to_thread_info_hash (GHashTable *thread_info_hash, Container *c, char *threadpath) { - gboolean is_root, first_child, empty_parent; + gboolean is_root, child, empty_parent, is_dup; /* 'root' means we're a child of the dummy root-container */ - is_root = container_is_root (c); + is_root = (c->parent == NULL); - first_child = is_root ? FALSE : (c->_parent->_children->data == c); - empty_parent = is_root ? FALSE : (!c->_parent->_msg); + child = is_root ? FALSE : (c->parent->child == c); + empty_parent = is_root ? FALSE : (!c->parent->msg); + is_dup = c->flags & CONTAINER_FLAG_DUP; g_hash_table_insert (thread_info_hash, - GUINT_TO_POINTER(c->_docid), + GUINT_TO_POINTER(c->docid), thread_info_new (threadpath, - container_is_root (c), - first_child, + is_root, + child, empty_parent, - c->_dup)); + is_dup)); } /* device a format string that is the minimum size to fit up to @@ -518,83 +583,49 @@ thread_segment_format_string (size_t matchnum) } static gboolean -add_thread_info (Container *c, guint level, ThreadInfo *ti) -{ - TP tp; - unsigned i; +add_thread_info (Container *c, ThreadInfo *ti, Path *path) +{ + gchar *pathstr; - /* ignore our dummy root container */ - if (!c->_parent) - return TRUE; + pathstr = path_to_string (path, ti->format); - if (level > ti->prev_level) { - for (i = ti->prev_level; i != level; ++i) - g_queue_push_tail (ti->idqueue, GUINT_TO_POINTER(0)); - } else if (level <= ti->prev_level) { - int oldseq; - for (i = level; i < ti->prev_level; ++i) - g_queue_pop_tail (ti->idqueue); - - if (g_queue_is_empty (ti->idqueue)) - g_queue_push_tail(ti->idqueue, GUINT_TO_POINTER(0)); - else { - oldseq = GPOINTER_TO_UINT(g_queue_pop_tail(ti->idqueue)); - g_queue_push_tail(ti->idqueue, GUINT_TO_POINTER(1 + oldseq)); - } - } - - ti->prev_level = level; - - if (!c->_docid) - return TRUE; /* nothing more to do - it's a 'virtual' - * message */ - - tp.threadpath = NULL; - tp.frmt = ti->format; - - g_queue_foreach (ti->idqueue, (GFunc)accumulate_path, &tp); - - add_to_thread_info_hash (ti->hash, c, tp.threadpath); + add_to_thread_info_hash (ti->hash, c, pathstr); return TRUE; } GHashTable* -create_doc_id_thread_path_hash (Container *root, size_t matchnum) +create_doc_id_thread_path_hash (Container *root_set, size_t matchnum) { ThreadInfo ti; - /* create hash docid => thread-path */ + /* create hash docid => thread-info */ ti.hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)thread_info_destroy); - ti.idqueue = g_queue_new (); - ti.prev_level = 0; ti.format = thread_segment_format_string (matchnum); + + container_path_foreach (root_set, + (ContainerPathForeachFunc)add_thread_info, + &ti); - container_traverse (root, 0, (ContainerTraverseFunc)add_thread_info, - &ti); - - g_queue_free (ti.idqueue); return ti.hash; } static Container* -container_new (MuMsg *msg, unsigned docid) +container_new (MuMsg *msg, guint docid, const char *msgid) { Container *c; + c = g_slice_new0 (Container); - c = g_slice_new (Container); - - c->_msg = msg ? mu_msg_ref (msg) : NULL; - c->_docid = docid; - c->_dup = FALSE; - c->_children = NULL; - c->_parent = NULL; + if (msg) + c->msg = mu_msg_ref (msg); + c->docid = docid; + c->msgid = msgid; return c; } @@ -605,133 +636,254 @@ container_destroy (Container *c) if (!c) return; - if (c->_msg) - mu_msg_unref (c->_msg); + if (c->msg) + mu_msg_unref (c->msg); - c->_parent = (void*)0xdeadbeef; - g_slist_free (c->_children); g_slice_free (Container, c); } -/* depth-first traverse all children, grand-children ... n-children of - * container */ -static gboolean -container_traverse (Container *c, guint level, ContainerTraverseFunc func, - gpointer user_data) +static void +container_add_sibling (Container *c, Container *sibling) { - GSList *cur; - int i; - - g_return_val_if_fail (c, FALSE); - - if (!func (c, level, user_data)) - return FALSE; - - for (i = 0, cur = c->_children; cur; cur = g_slist_next (cur)) { - if (!container_traverse ((Container*)cur->data, level + 1, - func, user_data)) - return FALSE; - - } - return TRUE; -} + Container *cur; -static gboolean -container_add_child (Container *parent, Container *child) -{ - g_return_val_if_fail (parent != child, FALSE); - g_return_val_if_fail (child, FALSE); + g_return_if_fail (c); + g_return_if_fail (sibling); + g_return_if_fail (c != sibling); - if (already_referenced(child, parent) || - already_referenced(parent, child)) { - /* g_print ("already ref'd\n"); */ - return FALSE; - } - - child->_parent = parent; - parent->_children = g_slist_prepend (parent->_children, - child); - - return TRUE; + for (cur = sibling; cur; cur = cur->next) + cur->parent = c->parent; + + for (cur = c; cur->next; cur = cur->next); + cur->next = sibling; } static void -container_splice_child (Container *c, Container *child) +container_add_child (Container *c, Container *child) { - GSList *iter; + Container *cur; - g_return_if_fail (c != child); + g_return_if_fail (c); g_return_if_fail (child); - g_return_if_fail (!child->_msg); - - for (iter = child->_children; iter; iter = g_slist_next(iter)) - ((Container*)iter->data)->_parent = c; /* reparent - * grandchildren */ - /* - * remove the old child - */ - c->_children = g_slist_remove (c->_children, child); - /* - * put child's children first, so we can use this while - * iterating over c->_children (we are already past the merged - * child's children) - */ - c->_children = g_slist_concat (child->_children, c->_children); - - child->_children = NULL; - child->_parent = NULL; + g_return_if_fail (c != child); + + for (cur = child; cur; cur = cur->next) + cur->parent = c; + + if (!c->child) + c->child = child; + else { + for (cur = c->child; cur->next; cur = cur->next); + cur->next = child; + } } static void container_remove_child (Container *c, Container *child) { - g_return_if_fail (c != child); + Container *cur, *prev; + + g_return_if_fail (c); g_return_if_fail (child); - g_return_if_fail (!child->_children); + g_return_if_fail (!child->child); + g_return_if_fail (c != child); - c->_children = g_slist_remove (c->_children, child); -} - - -static gboolean -container_is_root (Container *c) -{ - return (!c->_parent || !c->_parent->_parent); -} - -G_GNUC_UNUSED static void -container_dump (Container *c) -{ - g_print ("[%s] { %p parent=%p msg=%p docid=%u [%s] children: %d }\n", - c->_msg ? mu_msg_get_subject(c->_msg) : "", - (void*)c, - (void*)c->_parent, (void*)c->_msg, - c->_docid, - c->_msg ? mu_msg_get_msgid(c->_msg) : "", - g_slist_length (c->_children)); -} - - -static gboolean -each_container (Container *c, guint level) -{ - while (level--) - fputs (" ", stdout); + for (prev = NULL, cur = c->child; cur; cur = cur->next) { - container_dump (c); + if (cur == child) { + if (!prev) + c->child = cur->next; + else + prev->next = cur->next; + } + prev = cur; + } +} + +static void +container_path_foreach_real (Container *c, guint level, Path *path, + ContainerPathForeachFunc func, gpointer user_data) +{ + if (!c) + return; + + path_inc (path, level); + func (c, user_data, path); + + /* children */ + container_path_foreach_real (c->child, level + 1, path, func, user_data); + + /* siblings */ + container_path_foreach_real (c->next, level, path, func, user_data); +} + + +static void +container_path_foreach (Container *c, ContainerPathForeachFunc func, + gpointer user_data) +{ + Path *path; + + path = path_new (100); + + container_path_foreach_real (c, 0, path, func, user_data); + + path_destroy (path); +} + + +static gboolean +container_foreach (Container *c, ContainerForeachFunc func, gpointer user_data) +{ + if (!c) + return TRUE; + + if (!container_foreach (c->child, func, user_data)) + return FALSE; /* recurse into children */ + + /* recurse into siblings */ + if (!container_foreach (c->next, func, user_data)) + return FALSE; + + return func (c, user_data); +} + + +static void +container_splice (Container *parent, Container *child) +{ + g_return_if_fail (parent); + g_return_if_fail (child); + g_return_if_fail (parent != child); + + container_add_child (parent, child->child); + child->child = NULL; + container_remove_child (parent, child); +} + +size_t +container_child_count (Container *c) +{ + size_t count; + Container *cur; + + g_return_val_if_fail (c, 0); + + for (count = 0, cur = c->child; cur; cur = cur->next) + ++count; + + return count; +} + + +static gboolean +different_container (Container *a, Container *b) +{ + /* level == 0 so we don't compare with ourselves... */ + return a != b; +} + + +static gboolean +container_reachable (Container *haystack, Container *needle) +{ + return container_foreach (haystack, + (ContainerForeachFunc)different_container, + needle) ? FALSE : TRUE; +} + +static gboolean +dump_container (Container *c) +{ + const gchar* subject; + + if (!c) { + g_print ("\n"); + return TRUE; + } + + subject = (c->msg) ? mu_msg_get_subject (c->msg) : ""; + + g_print ("[%s][%s m:%p p:%p]\n",c->msgid, subject, (void*)c, + (void*)c->parent); return TRUE; } - G_GNUC_UNUSED static void -container_dump_tree (Container *c) -{ - container_traverse (c, 0, (ContainerTraverseFunc)each_container, NULL); +container_dump (Container *c, gboolean recursive) +{ + if (!recursive) + dump_container (c); + else + container_foreach (c, (ContainerForeachFunc)dump_container, + NULL); } +static Path* +path_new (guint initial) +{ + Path *p; + + p = g_slice_new0 (Path); + + p->_data = g_new0 (int, initial); + p->_len = initial; + + return p; +} + +static void +path_destroy (Path *p) +{ + if (!p) + return; + + g_free (p->_data); + g_slice_free (Path, p); +} + +static void +path_inc (Path *p, guint index) +{ + if (index + 1 >= p->_len) { + p->_data = g_renew (int, p->_data, 2 * p->_len); + memset (&p->_data[p->_len], 0, p->_len); + p->_len *= 2; + } + + ++p->_data[index]; + p->_data[index + 1] = 0; +} + +static gchar* +path_to_string (Path *p, const char* frmt) +{ + char *str; + guint u; + + if (!p->_data) + return NULL; + + for (u = 0, str = NULL; p->_data[u] != 0; ++u) { + + char segm[16]; + snprintf (segm, sizeof(segm), frmt, p->_data[u] - 1); + + if (!str) + str = g_strdup (segm); + else { + gchar *tmp; + tmp = g_strdup_printf ("%s:%s", str, segm); + g_free (str); + str = tmp; + } + } + + return str; +} diff --git a/src/tests/test-mu-threads.c b/src/tests/test-mu-threads.c index 5553b73f..ee2ed7d5 100644 --- a/src/tests/test-mu-threads.c +++ b/src/tests/test-mu-threads.c @@ -43,7 +43,7 @@ fill_database (const char *testdir) cmdline = g_strdup_printf ("%s index --muhome=%s --maildir=%s" " --quiet", MU_PROGRAM, tmpdir, testdir); - /* g_print ("%s\n", cmdline); */ + g_print ("%s\n", cmdline); g_assert (g_spawn_command_line_sync (cmdline, NULL, NULL, NULL, NULL)); diff --git a/src/tests/testdir3/cur/child4.0 b/src/tests/testdir3/cur/child4.0 index 4e5256c1..7d0ed79f 100644 --- a/src/tests/testdir3/cur/child4.0 +++ b/src/tests/testdir3/cur/child4.0 @@ -2,8 +2,8 @@ From: testfrom@example.com To: testto@example.com Subject: Re: child 4.0 Message-Id: -References: -In-reply-to: +References: +In-reply-to: Date: Tue, 24 Jun 2011 11:10 +0000 abc diff --git a/src/tests/testdir3/cur/child4.1 b/src/tests/testdir3/cur/child4.1 index 976fd00b..295d1b13 100644 --- a/src/tests/testdir3/cur/child4.1 +++ b/src/tests/testdir3/cur/child4.1 @@ -2,8 +2,8 @@ From: testfrom@example.com To: testto@example.com Subject: Re: child 4.1 Message-Id: -References: -In-reply-to: +References: +In-reply-to: Date: Tue, 24 Jun 2011 11:20 +0000 abc