diff options
Diffstat (limited to 'camel/camel-folder-thread.c')
-rw-r--r-- | camel/camel-folder-thread.c | 877 |
1 files changed, 0 insertions, 877 deletions
diff --git a/camel/camel-folder-thread.c b/camel/camel-folder-thread.c deleted file mode 100644 index 0d2e9b2aff..0000000000 --- a/camel/camel-folder-thread.c +++ /dev/null @@ -1,877 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2000 Ximian Inc. - * - * Authors: Michael Zucchi <notzed@ximian.com> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of version 2 of the GNU General Public - * License as published by the Free Software Foundation. - * - * 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, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -/* TODO: This could probably be made a camel object, but it isn't really required */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <sys/types.h> -#include <unistd.h> -#include <errno.h> -#include <string.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <ctype.h> -#include <glib.h> - -#include "camel-folder-thread.h" -#include "libedataserver/e-memory.h" - -#define d(x) -#define m(x) - -/*#define TIMEIT*/ - -#ifdef TIMEIT -#include <sys/time.h> -#include <unistd.h> -#endif - -static void -container_add_child(CamelFolderThreadNode *node, CamelFolderThreadNode *child) -{ - d(printf("\nAdding child %p to parent %p \n", child, node)); - child->next = node->child; - node->child = child; - child->parent = node; -} - -static void -container_parent_child(CamelFolderThreadNode *parent, CamelFolderThreadNode *child) -{ - CamelFolderThreadNode *c, *node; - - /* are we already the right parent? */ - if (child->parent == parent) - return; - - /* would this create a loop? */ - node = parent->parent; - while (node) { - if (node == child) - return; - node = node->parent; - } - - /* are we unparented? */ - if (child->parent == NULL) { - container_add_child(parent, child); - return; - } - - /* else remove child from its existing parent, and reparent */ - node = child->parent; - c = (CamelFolderThreadNode *)&node->child; - d(printf("scanning children:\n")); - while (c->next) { - d(printf(" %p\n", c)); - if (c->next==child) { - d(printf("found node %p\n", child)); - c->next = c->next->next; - child->parent = NULL; - container_add_child(parent, child); - return; - } - c = c->next; - } - - printf("DAMN, we shouldn't be here!\n"); -} - -static void -prune_empty(CamelFolderThread *thread, CamelFolderThreadNode **cp) -{ - CamelFolderThreadNode *child, *next, *c, *lastc; - - /* yes, this is intentional */ - lastc = (CamelFolderThreadNode *)cp; - while (lastc->next) { - c = lastc->next; - - d(printf("checking message %p %p (%08x%08x)\n", c, - c->message, c->message?c->message->message_id.id.part.hi:0, - c->message?c->message->message_id.id.part.lo:0)); - if (c->message == NULL) { - if (c->child == NULL) { - d(printf("removing empty node\n")); - lastc->next = c->next; - m(memset(c, 0xfe, sizeof(*c))); - e_memchunk_free(thread->node_chunks, c); - continue; - } - if (c->parent || c->child->next==0) { - d(printf("promoting child\n")); - lastc->next = c->next; /* remove us */ - child = c->child; - while (child) { - next = child->next; - - child->parent = c->parent; - child->next = lastc->next; - lastc->next = child; - - child = next; - } - continue; - } - } - prune_empty(thread, &c->child); - lastc = c; - } -} - -static void -hashloop(void *key, void *value, void *data) -{ - CamelFolderThreadNode *c = value; - CamelFolderThreadNode *tail = data; - - if (c->parent == NULL) { - c->next = tail->next; - tail->next = c; - } -} - -static char * -get_root_subject(CamelFolderThreadNode *c) -{ - char *s, *p; - CamelFolderThreadNode *scan; - - s = NULL; - c->re = FALSE; - if (c->message) - s = (char *)camel_message_info_subject(c->message); - else { - /* one of the children will always have a message */ - scan = c->child; - while (scan) { - if (scan->message) { - s = (char *)camel_message_info_subject(scan->message); - break; - } - scan = scan->next; - } - } - if (s != NULL) { - while (*s) { - while (isspace(*s)) - s++; - if (s[0] == 0) - break; - if ((s[0] == 'r' || s[0]=='R') - && (s[1] == 'e' || s[1]=='E')) { - p = s+2; - while (isdigit(*p) || (ispunct(*p) && (*p != ':'))) - p++; - if (*p==':') { - c->re = TRUE; - s = p+1; - } else - break; - } else - break; - } - if (*s) - return s; - } - return NULL; -} - -/* this can be pretty slow, but not used often */ -/* clast cannot be null */ -static void -remove_node(CamelFolderThreadNode **list, CamelFolderThreadNode *node, CamelFolderThreadNode **clast) -{ - CamelFolderThreadNode *c; - - /* this is intentional, even if it looks funny */ - /* if we have a parent, then we should remove it from the parent list, - otherwise we remove it from the root list */ - if (node->parent) { - c = (CamelFolderThreadNode *)&node->parent->child; - } else { - c = (CamelFolderThreadNode *)list; - } - while (c->next) { - if (c->next == node) { - if (*clast == c->next) - *clast = c; - c->next = c->next->next; - return; - } - c = c->next; - } - - printf("ERROR: removing node %p failed\n", node); -} - -static void -group_root_set(CamelFolderThread *thread, CamelFolderThreadNode **cp) -{ - GHashTable *subject_table = g_hash_table_new(g_str_hash, g_str_equal); - CamelFolderThreadNode *c, *clast, *scan, *container; - - /* gather subject lines */ - d(printf("gathering subject lines\n")); - clast = (CamelFolderThreadNode *)cp; - c = clast->next; - while (c) { - c->root_subject = get_root_subject(c); - if (c->root_subject) { - container = g_hash_table_lookup(subject_table, c->root_subject); - if (container == NULL - || (container->message == NULL && c->message) - || (container->re == TRUE && !c->re)) { - g_hash_table_insert(subject_table, c->root_subject, c); - } - } - c = c->next; - } - - /* merge common subjects? */ - clast = (CamelFolderThreadNode *)cp; - while (clast->next) { - c = clast->next; - d(printf("checking %p %s\n", c, c->root_subject)); - if (c->root_subject - && (container = g_hash_table_lookup(subject_table, c->root_subject)) - && (container != c)) { - d(printf(" matching %p %s\n", container, container->root_subject)); - if (c->message == NULL && container->message == NULL) { - d(printf("merge containers children\n")); - /* steal the children from c onto container, and unlink c */ - scan = (CamelFolderThreadNode *)&container->child; - while (scan->next) - scan = scan->next; - scan->next = c->child; - clast->next = c->next; - m(memset(c, 0xee, sizeof(*c))); - e_memchunk_free(thread->node_chunks, c); - continue; - } if (c->message == NULL && container->message != NULL) { - d(printf("container is non-empty parent\n")); - remove_node(cp, container, &clast); - container_add_child(c, container); - } else if (c->message != NULL && container->message == NULL) { - d(printf("container is empty child\n")); - clast->next = c->next; - container_add_child(container, c); - continue; - } else if (c->re && !container->re) { - d(printf("container is re\n")); - clast->next = c->next; - container_add_child(container, c); - continue; - } else if (!c->re && container->re) { - d(printf("container is not re\n")); - remove_node(cp, container, &clast); - container_add_child(c, container); - } else if (c->re && container->re) { - d(printf("subjects are common %p and %p\n", c, container)); - - /* build a phantom node */ - remove_node(cp, container, &clast); - remove_node(cp, c, &clast); - - scan = e_memchunk_alloc0(thread->node_chunks); - - scan->root_subject = c->root_subject; - scan->re = c->re && container->re; - scan->next = c->next; - clast->next = scan; - container_add_child(scan, c); - container_add_child(scan, container); - clast = scan; - g_hash_table_insert(subject_table, scan->root_subject, scan); - continue; - } - } - clast = c; - } - g_hash_table_destroy(subject_table); -} - -struct _tree_info { - GHashTable *visited; -}; - -static int -dump_tree_rec(struct _tree_info *info, CamelFolderThreadNode *c, int depth) -{ - char *p; - int count=0; - - p = alloca(depth*2+1); - memset(p, ' ', depth*2); - p[depth*2] = 0; - - while (c) { - if (g_hash_table_lookup(info->visited, c)) { - printf("WARNING: NODE REVISITED: %p\n", c); - } else { - g_hash_table_insert(info->visited, c, c); - } - if (c->message) { - printf("%s %p Subject: %s <%.8s>\n", p, c, camel_message_info_subject(c->message), camel_message_info_message_id(c->message)->id.hash); - count += 1; - } else { - printf("%s %p <empty>\n", p, c); - } - if (c->child) - count += dump_tree_rec(info, c->child, depth+1); - c = c->next; - } - return count; -} - -int -camel_folder_threaded_messages_dump(CamelFolderThreadNode *c) -{ - int count; - struct _tree_info info; - - info.visited = g_hash_table_new(g_direct_hash, g_direct_equal); - count = dump_tree_rec(&info, c, 0); - g_hash_table_destroy(info.visited); - return count; -} - -static int -sort_node(const void *a, const void *b) -{ - const CamelFolderThreadNode *a1 = ((CamelFolderThreadNode **)a)[0]; - const CamelFolderThreadNode *b1 = ((CamelFolderThreadNode **)b)[0]; - - /* if we have no message, it must be a dummy node, which - also means it must have a child, just use that as the - sort data (close enough?) */ - if (a1->message == NULL) - a1 = a1->child; - if (b1->message == NULL) - b1 = b1->child; - if (a1->order == b1->order) - return 0; - if (a1->order < b1->order) - return -1; - else - return 1; -} - -static void -sort_thread(CamelFolderThreadNode **cp) -{ - CamelFolderThreadNode *c, *head, **carray; - int size=0; - - c = *cp; - while (c) { - /* sort the children while we're at it */ - if (c->child) - sort_thread(&c->child); - size++; - c = c->next; - } - if (size<2) - return; - carray = alloca(size*sizeof(CamelFolderThreadNode *)); - c = *cp; - size=0; - while (c) { - carray[size] = c; - c = c->next; - size++; - } - qsort(carray, size, sizeof(CamelFolderThreadNode *), sort_node); - size--; - head = carray[size]; - head->next = NULL; - size--; - do { - c = carray[size]; - c->next = head; - head = c; - size--; - } while (size>=0); - *cp = head; -} - -static guint id_hash(void *key) -{ - CamelSummaryMessageID *id = (CamelSummaryMessageID *)key; - - return id->id.part.lo; -} - -static gint id_equal(void *a, void *b) -{ - return ((CamelSummaryMessageID *)a)->id.id == ((CamelSummaryMessageID *)b)->id.id; -} - -/* perform actual threading */ -static void -thread_summary(CamelFolderThread *thread, GPtrArray *summary) -{ - GHashTable *id_table, *no_id_table; - int i; - CamelFolderThreadNode *c, *child, *head; -#ifdef TIMEIT - struct timeval start, end; - unsigned long diff; - - gettimeofday(&start, NULL); -#endif - - id_table = g_hash_table_new((GHashFunc)id_hash, (GCompareFunc)id_equal); - no_id_table = g_hash_table_new(NULL, NULL); - for (i=0;i<summary->len;i++) { - CamelMessageInfo *mi = summary->pdata[i]; - const CamelSummaryMessageID *mid = camel_message_info_message_id(mi); - const CamelSummaryReferences *references = camel_message_info_references(mi); - - if (mid->id.id) { - c = g_hash_table_lookup(id_table, mid); - /* check for duplicate messages */ - if (c && c->order) { - /* if duplicate, just make out it is a no-id message, but try and insert it - into the right spot in the tree */ - d(printf("doing: (duplicate message id)\n")); - c = e_memchunk_alloc0(thread->node_chunks); - g_hash_table_insert(no_id_table, (void *)mi, c); - } else if (!c) { - d(printf("doing : %08x%08x (%s)\n", mid->id.part.hi, mid->id.part.lo, camel_message_info_subject(mi))); - c = e_memchunk_alloc0(thread->node_chunks); - g_hash_table_insert(id_table, (void *)mid, c); - } - } else { - d(printf("doing : (no message id)\n")); - c = e_memchunk_alloc0(thread->node_chunks); - g_hash_table_insert(no_id_table, (void *)mi, c); - } - - c->message = mi; - c->order = i+1; - child = c; - if (references) { - int j; - - d(printf("references:\n")); - for (j=0;j<references->size;j++) { - /* should never be empty, but just incase */ - if (references->references[j].id.id == 0) - continue; - - c = g_hash_table_lookup(id_table, &references->references[j]); - if (c == NULL) { - d(printf("not found\n")); - c = e_memchunk_alloc0(thread->node_chunks); - g_hash_table_insert(id_table, (void *)&references->references[j], c); - } - if (c!=child) - container_parent_child(c, child); - child = c; - } - } - } - - d(printf("\n\n")); - /* build a list of root messages (no parent) */ - head = NULL; - g_hash_table_foreach(id_table, hashloop, &head); - g_hash_table_foreach(no_id_table, hashloop, &head); - - g_hash_table_destroy(id_table); - g_hash_table_destroy(no_id_table); - - /* remove empty parent nodes */ - prune_empty(thread, &head); - - /* find any siblings which missed out - but only if we are allowing threading by subject */ - if (thread->subject) - group_root_set (thread, &head); - -#if 0 - printf("finished\n"); - i = camel_folder_thread_messages_dump(head); - printf("%d count, %d items in tree\n", uids->len, i); -#endif - - sort_thread(&head); - - /* remove any phantom nodes, this could possibly be put in group_root_set()? */ - c = (CamelFolderThreadNode *)&head; - while (c && c->next) { - CamelFolderThreadNode *scan, *newtop; - - child = c->next; - if (child->message == NULL) { - newtop = child->child; - newtop->parent = NULL; - /* unlink pseudo node */ - c->next = newtop; - - /* link its siblings onto the end of its children, fix all parent pointers */ - scan = (CamelFolderThreadNode *)&newtop->child; - while (scan->next) { - scan = scan->next; - } - scan->next = newtop->next; - while (scan->next) { - scan = scan->next; - scan->parent = newtop; - } - - /* and link the now 'real' node into the list */ - newtop->next = child->next; - c = newtop; - m(memset(child, 0xde, sizeof(*child))); - e_memchunk_free(thread->node_chunks, child); - } else { - c = child; - } - } - - /* this is only debug assertion stuff */ - c = (CamelFolderThreadNode *)&head; - while (c->next) { - c = c->next; - if (c->message == NULL) - g_warning("threading missed removing a pseudo node: %s\n", c->root_subject); - if (c->parent != NULL) - g_warning("base node has a non-null parent: %s\n", c->root_subject); - } - - thread->tree = head; - -#ifdef TIMEIT - gettimeofday(&end, NULL); - diff = end.tv_sec * 1000 + end.tv_usec/1000; - diff -= start.tv_sec * 1000 + start.tv_usec/1000; - printf("Message threading %d messages took %ld.%03ld seconds\n", - summary->len, diff / 1000, diff % 1000); -#endif -} - -/** - * camel_folder_thread_messages_new: - * @folder: - * @uids: The subset of uid's to thread. If NULL. then thread all - * uid's in @folder. - * @thread_subject: thread based on subject also - * - * Thread a (subset) of the messages in a folder. And sort the result - * in summary order. - * - * If @thread_subject is %TRUE, messages with - * related subjects will also be threaded. The default behaviour is to - * only thread based on message-id. - * - * This function is probably to be removed soon. - * - * Return value: A CamelFolderThread contianing a tree of CamelFolderThreadNode's - * which represent the threaded structure of the messages. - **/ -CamelFolderThread * -camel_folder_thread_messages_new (CamelFolder *folder, GPtrArray *uids, gboolean thread_subject) -{ - CamelFolderThread *thread; - GHashTable *wanted = NULL; - GPtrArray *summary; - GPtrArray *fsummary; - int i; - - thread = g_malloc(sizeof(*thread)); - thread->refcount = 1; - thread->subject = thread_subject; - thread->tree = NULL; - thread->node_chunks = e_memchunk_new(32, sizeof(CamelFolderThreadNode)); - thread->folder = folder; - camel_object_ref((CamelObject *)folder); - - /* get all of the summary items of interest in summary order */ - if (uids) { - wanted = g_hash_table_new(g_str_hash, g_str_equal); - for (i=0;i<uids->len;i++) - g_hash_table_insert(wanted, uids->pdata[i], uids->pdata[i]); - } - - fsummary = camel_folder_get_summary(folder); - thread->summary = summary = g_ptr_array_new(); - - for (i=0;i<fsummary->len;i++) { - CamelMessageInfo *info = fsummary->pdata[i]; - - if (wanted == NULL || g_hash_table_lookup(wanted, camel_message_info_uid(info)) != NULL) { - camel_folder_ref_message_info(folder, info); - g_ptr_array_add(summary, info); - } - } - - camel_folder_free_summary(folder, fsummary); - - thread_summary(thread, summary); - - if (wanted) - g_hash_table_destroy(wanted); - - return thread; -} - -/* add any still there, in the existing order */ -static void -add_present_rec(CamelFolderThread *thread, GHashTable *have, GPtrArray *summary, CamelFolderThreadNode *node) -{ - while (node) { - const char *uid = camel_message_info_uid(node->message); - - if (g_hash_table_lookup(have, (char *)uid)) { - g_hash_table_remove(have, (char *)camel_message_info_uid(node->message)); - g_ptr_array_add(summary, (void *) node->message); - } else { - camel_folder_free_message_info(thread->folder, (CamelMessageInfo *)node->message); - } - - if (node->child) - add_present_rec(thread, have, summary, node->child); - node = node->next; - } -} - -void -camel_folder_thread_messages_apply(CamelFolderThread *thread, GPtrArray *uids) -{ - int i; - GPtrArray *all; - GHashTable *table; - CamelMessageInfo *info; - - all = g_ptr_array_new(); - table = g_hash_table_new(g_str_hash, g_str_equal); - for (i=0;i<uids->len;i++) - g_hash_table_insert(table, uids->pdata[i], uids->pdata[i]); - - add_present_rec(thread, table, all, thread->tree); - - /* add any new ones, in supplied order */ - for (i=0;i<uids->len;i++) - if (g_hash_table_lookup(table, uids->pdata[i]) && (info = camel_folder_get_message_info(thread->folder, uids->pdata[i]))) - g_ptr_array_add(all, info); - - g_hash_table_destroy(table); - - thread->tree = NULL; - e_memchunk_destroy(thread->node_chunks); - thread->node_chunks = e_memchunk_new(32, sizeof(CamelFolderThreadNode)); - thread_summary(thread, all); - - g_ptr_array_free(thread->summary, TRUE); - thread->summary = all; -} - -void -camel_folder_thread_messages_ref(CamelFolderThread *thread) -{ - thread->refcount++; -} - -/** - * camel_folder_thread_messages_unref: - * @thread: - * - * Free all memory associated with the thread descriptor @thread. - **/ -void -camel_folder_thread_messages_unref(CamelFolderThread *thread) -{ - if (thread->refcount > 1) { - thread->refcount--; - return; - } - - if (thread->folder) { - int i; - - for (i=0;i<thread->summary->len;i++) - camel_folder_free_message_info(thread->folder, thread->summary->pdata[i]); - g_ptr_array_free(thread->summary, TRUE); - camel_object_unref((CamelObject *)thread->folder); - } - e_memchunk_destroy(thread->node_chunks); - g_free(thread); -} - -#if 0 -/** - * camel_folder_thread_messages_new_summary: - * @summary: Array of CamelMessageInfo's to thread. - * - * Thread a list of MessageInfo's. The summary must remain valid for the - * life of the CamelFolderThread created by this function, and it is upto the - * caller to ensure this. - * - * Return value: A CamelFolderThread contianing a tree of CamelFolderThreadNode's - * which represent the threaded structure of the messages. - **/ -CamelFolderThread * -camel_folder_thread_messages_new_summary(GPtrArray *summary) -{ - CamelFolderThread *thread; - -#ifdef TIMEIT - struct timeval start, end; - unsigned long diff; - - gettimeofday(&start, NULL); -#endif - - thread = g_malloc(sizeof(*thread)); - thread->refcount = 1; - thread->tree = NULL; - thread->node_chunks = e_memchunk_new(32, sizeof(CamelFolderThreadNode)); - thread->folder = NULL; - thread->summary = NULL; - - thread_summary(thread, summary); - - return thread; -} - -/* scan the list in depth-first fashion */ -static void -build_summary_rec(GHashTable *have, GPtrArray *summary, CamelFolderThreadNode *node) -{ - while (node) { - if (node->message) - g_hash_table_insert(have, (char *)camel_message_info_uid(node->message), node->message); - g_ptr_array_add(summary, node); - if (node->child) - build_summary_rec(have, summary, node->child); - node = node->next; - } -} - -void -camel_folder_thread_messages_add(CamelFolderThread *thread, GPtrArray *summary) -{ - GPtrArray *all; - int i; - GHashTable *table; - - /* Instead of working out all the complex in's and out's of - trying to do an incremental summary generation, just redo the whole - thing with the summary in the current order - so it comes out - in the same order */ - - all = g_ptr_array_new(); - table = g_hash_table_new(g_str_hash, g_str_equal); - build_summary_rec(table, all, thread->tree); - for (i=0;i<summary->len;i++) { - CamelMessageInfo *info = summary->pdata[i]; - - /* check its not already there, we dont want duplicates */ - if (g_hash_table_lookup(table, camel_message_info_uid(info)) == NULL) - g_ptr_array_add(all, info); - } - g_hash_table_destroy(table); - - /* reset the tree, and rebuild fully */ - thread->tree = NULL; - e_memchunk_empty(thread->node_chunks); - thread_summary(thread, all); -} - -static void -remove_uid_node_rec(CamelFolderThread *thread, GHashTable *table, CamelFolderThreadNode **list, CamelFolderThreadNode *parent) -{ - CamelFolderThreadNode *prev = NULL; - CamelFolderThreadNode *node, *next, *child, *rest; - - node = (CamelFolderThreadNode *)list; - next = node->next; - while (next) { - - if (next->child) - remove_uid_node_rec(thread, table, &next->child, next); - - /* do we have a node to remove? */ - if (next->message && g_hash_table_lookup(table, (char *)camel_message_info_uid(node->message))) { - child = next->child; - if (child) { - /* - node - next - child - lchild - rest - - becomes: - node - child - lchild - rest - */ - - rest = next->next; - node->next = child; - e_memchunk_free(thread->node_chunks, next); - next = child; - do { - lchild = child; - child->parent = parent; - child = child->next; - } while (child); - lchild->next = rest; - } else { - /* - node - next - rest - becomes: - node - rest */ - node->next = next->next; - e_memchunk_free(thread->node_chunks, next); - next = node->next; - } - } else { - node = next; - next = node->next; - } - } -} - -void -camel_folder_thread_messages_remove(CamelFolderThread *thread, GPtrArray *uids) -{ - GHashTable *table; - int i; - - table = g_hash_table_new(g_str_hash, g_str_equal); - for (i=0;i<uids->len;i++) - g_hash_table_insert(table, uids->pdata[i], uids->pdata[i]); - - remove_uid_node_rec(thread, table, &thread->tree, NULL); - g_hash_table_destroy(table); -} - -#endif |