changeset 11177:3924db2b1ca8

[gaim-migrate @ 13285] Performance optimizing the log set and screenname autocompletion code. committer: Tailor Script <tailor@pidgin.im>
author Richard Laager <rlaager@wiktel.com>
date Mon, 01 Aug 2005 04:08:27 +0000
parents 6932df31225f
children 28ac54de3024
files src/gtkrequest.c src/log.c src/log.h
diffstat 3 files changed, 248 insertions(+), 226 deletions(-) [+]
line wrap: on
line diff
--- a/src/gtkrequest.c	Sun Jul 31 15:21:31 2005 +0000
+++ b/src/gtkrequest.c	Mon Aug 01 04:08:27 2005 +0000
@@ -44,7 +44,7 @@
 	GaimRequestType type;
 
 	void *user_data;
-GtkWidget *dialog;
+	GtkWidget *dialog;
 
 	GtkWidget *ok_button;
 
@@ -647,71 +647,6 @@
 		gaim_request_fields_all_required_filled(field->group->fields_list));
 }
 
-static GList *
-get_screenname_completion_data(gboolean all)
-{
-	GList *names = NULL;
-	GaimBlistNode *gnode, *cnode, *bnode;
-	GList *log_sets;
-	GList *log_set;
-
-	for (gnode = gaim_get_blist()->root; gnode != NULL; gnode = gnode->next)
-	{
-		if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-
-		for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
-		{
-			if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
-				continue;
-
-			for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
-			{
-				GaimBuddy *buddy = (GaimBuddy *)bnode;
-
-				if (!all && !gaim_account_is_connected(buddy->account))
-					continue;
-
-#ifdef NEW_STYLE_COMPLETION
-				names = g_list_append(names, ((GaimContact *)cnode)->alias);
-				names = g_list_append(names,
-					(gpointer)gaim_buddy_get_contact_alias(buddy));
-				names = g_list_append(names, buddy->account);
-#endif /* NEW_STYLE_COMPLETION */
-
-				names = g_list_append(names, g_strdup(buddy->name));
-			}
-		}
-	}
-
-	if (all)
-	{
-		log_sets = gaim_log_get_log_sets();
-		for (log_set = log_sets ; log_set != NULL ; log_set = log_set->next) {
-			GaimLogSet *set = log_set->data;
-
-			/* 1. Don't show buddies because we will have gotten them above.
-			 * 2. Only show those with non-NULL accounts that are currently connected.
-			 * 3. The boxes that use this autocomplete code handle only IMs. */
-			if (!set->buddy &&
-			    (set->account != NULL && gaim_account_is_connected(set->account)) &&
-			    set->type != GAIM_LOG_CHAT) {
-#ifdef NEW_STYLE_COMPLETION
-					names = g_list_append(names, NULL);
-					names = g_list_append(names, NULL);
-					names = g_list_append(names, set->account);
-#endif /* NEW_STYLE_COMPLETION */
-
-					names = g_list_append(names, set->name);
-			}
-
-			g_free(set);
-		}
-	}
-
-	return names;
-}
-
 #ifndef NEW_STYLE_COMPLETION
 static gboolean
 completion_entry_event(GtkEditable *entry, GdkEventKey *event,
@@ -797,41 +732,29 @@
 static gboolean screenname_completion_match_func(GtkEntryCompletion *completion,
 		const gchar *key, GtkTreeIter *iter, gpointer user_data) {
 
-	GValue val = { 0, };
+	GValue val1 = { 0, };
+	GValue val2 = { 0, };
 	GtkTreeModel *model;
-	char *screenname = NULL;
-	char *alias = NULL;
-	char *temp;
-	gboolean ret = FALSE;
 
 	model = gtk_entry_completion_get_model (completion);
 
-	gtk_tree_model_get_value(model, iter, 1, &val);
-	temp = (gchar *)g_value_get_string(&val);
-	if (temp) {
-		temp = g_utf8_normalize(temp, -1, G_NORMALIZE_DEFAULT);
-		screenname = g_utf8_casefold(temp, -1);
-		g_free(temp);
+	gtk_tree_model_get_value(model, iter, 2, &val1);
+	if (g_str_has_prefix(g_value_get_string(&val1), key))
+	{
+		g_value_unset(&val1);
+		return TRUE;
 	}
-	g_value_unset(&val);
+	g_value_unset(&val1);
 
-	gtk_tree_model_get_value(model, iter, 2, &val);
-	temp = (gchar *)g_value_get_string(&val);
-	if (temp) {
-		temp = g_utf8_normalize(temp, -1, G_NORMALIZE_DEFAULT);
-		alias = g_utf8_casefold(temp, -1);
-		g_free(temp);
+	gtk_tree_model_get_value(model, iter, 3, &val2);
+	if (g_str_has_prefix(g_value_get_string(&val2), key))
+	{
+		g_value_unset(&val2);
+		return TRUE;
 	}
-	g_value_unset(&val);
+	g_value_unset(&val2);
 
-	if (g_str_has_prefix(screenname, key) ||
-	    g_str_has_prefix(alias, key))
-		ret = TRUE;
-
-	g_free(screenname);
-	g_free(alias);
-
-	return ret;
+	return FALSE;
 }
 
 static gboolean screenname_completion_match_selected_cb(GtkEntryCompletion *completion,
@@ -856,7 +779,7 @@
 				GaimAccount *account;
 				GtkOptionMenu *optmenu = GTK_OPTION_MENU(field->ui_data);
 
-				gtk_tree_model_get_value(model, iter, 3, &val);
+				gtk_tree_model_get_value(model, iter, 4, &val);
 				account = g_value_get_pointer(&val);
 				g_value_unset(&val);
 
@@ -886,82 +809,150 @@
 
 	return TRUE;
 }
-#endif /* !NEW_STYLE_COMPLETION */
+
+static void
+add_screenname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias,
+								  const GaimAccount *account, const char *screenname)
+{
+	GtkTreeIter iter;
+	gboolean completion_added = FALSE;
+	gchar *normalized_screenname;
+	gchar *tmp;
+
+	tmp = g_utf8_normalize(screenname, -1, G_NORMALIZE_DEFAULT);
+	normalized_screenname = g_utf8_casefold(tmp, -1);
+	g_free(tmp);
+
+	/* There's no sense listing things like: 'xxx "xxx"'
+	   when the screenname and buddy alias match. */
+	if (buddy_alias && strcmp(buddy_alias, screenname)) {
+		char *completion_entry = g_strdup_printf("%s \"%s\"", screenname, buddy_alias);
+		char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT);
+
+		tmp = g_utf8_casefold(tmp2, -1);
+		g_free(tmp2);
+
+		gtk_list_store_append(store, &iter);
+		gtk_list_store_set(store, &iter,
+				0, completion_entry,
+				1, screenname,
+				2, normalized_screenname,
+				3, tmp,
+				4, account,
+				-1);
+		g_free(completion_entry);
+		g_free(tmp);
+		completion_added = TRUE;
+	}
+
+	/* There's no sense listing things like: 'xxx "xxx"'
+	   when the screenname and contact alias match. */
+	if (contact_alias && strcmp(contact_alias, screenname)) {
+		/* We don't want duplicates when the contact and buddy alias match. */
+		if (!buddy_alias || strcmp(contact_alias, buddy_alias)) {
+			char *completion_entry = g_strdup_printf("%s \"%s\"",
+							screenname, contact_alias);
+			char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT);
+
+			tmp = g_utf8_casefold(tmp2, -1);
+			g_free(tmp2);
+
+			gtk_list_store_append(store, &iter);
+			gtk_list_store_set(store, &iter,
+					0, completion_entry,
+					1, screenname,
+					2, normalized_screenname,
+					3, tmp,
+					4, account,
+					-1);
+			g_free(completion_entry);
+			g_free(tmp);
+			completion_added = TRUE;
+		}
+	}
+
+	if (completion_added == FALSE) {
+		/* Add the buddy's screenname. */
+		gtk_list_store_append(store, &iter);
+		gtk_list_store_set(store, &iter,
+				0, screenname,
+				1, screenname,
+				2, normalized_screenname,
+				3, NULL,
+				4, account,
+				-1);
+	}
+
+	g_free(normalized_screenname);
+}
+#endif /* NEW_STYLE_COMPLETION */
+
+static void get_log_set_name(GaimLogSet *set, gpointer value, gpointer **set_hash_data)
+{
+	/* 1. Don't show buddies because we will have gotten them already.
+	 * 2. Only show those with non-NULL accounts that are currently connected.
+	 * 3. The boxes that use this autocomplete code handle only IMs. */
+	if (!set->buddy &&
+	    (GPOINTER_TO_INT(set_hash_data[1]) ||
+	     (set->account != NULL && gaim_account_is_connected(set->account))) &&
+		set->type == GAIM_LOG_IM) {
+#if NEW_STYLE_COMPLETION
+			add_screenname_autocomplete_entry((GtkListStore *)set_hash_data[0],
+											  NULL, NULL, set->account, set->name);
+#else
+			GList **items = ((GList **)set_hash_data[0]);
+			/* Steal the name for the GCompletion. */
+			*items = g_list_append(*items, set->name);
+			set->name = set->normalized_name = NULL;
+#endif /* NEW_STYLE_COMPLETION */
+	}
+}
 
 static void
 setup_screenname_autocomplete(GtkWidget *entry, GaimRequestField *field, gboolean all)
 {
 #ifdef NEW_STYLE_COMPLETION
-	GtkListStore *store;
-	GtkTreeIter iter;
+	/* Store the displayed completion value, the screenname, the UTF-8 normalized & casefolded screenname,
+	 * the UTF-8 normalized & casefolded value for comparison, and the account. */
+	GtkListStore *store = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
+
+	GaimBlistNode *gnode, *cnode, *bnode;
+	GHashTable *sets;
+	gpointer set_hash_data[] = {store, GINT_TO_POINTER(all)};
 	GtkEntryCompletion *completion;
-	GList *screenname_completion_data;
-	GList *l;
 	gpointer *data;
 
-	/* Store the displayed completion value, the screenname, the value for comparison, and the account. */
-	store = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
-
-	screenname_completion_data = get_screenname_completion_data(all);
+	for (gnode = gaim_get_blist()->root; gnode != NULL; gnode = gnode->next)
+	{
+		if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
+			continue;
 
-	/* Loop through the list four elements at a time. */
-	for (l = screenname_completion_data; l != NULL; l = l->next->next->next->next)
-	{
-		char *contact_alias = l->data;
-		char *buddy_alias = l->next->data;
-		GaimAccount *account = l->next->next->data;
-		char *screenname = l->next->next->next->data;
-		gboolean completion_added = FALSE;
+		for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
+		{
+			if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
+				continue;
 
-		/* There's no sense listing things like: 'xxx "xxx"'
-		   when the screenname and buddy alias match. */
-		if (buddy_alias && strcmp(buddy_alias, screenname)) {
-			char *completion_entry = g_strdup_printf("%s \"%s\"", screenname, buddy_alias);
-			gtk_list_store_append(store, &iter);
-			gtk_list_store_set(store, &iter,
-					0, completion_entry,
-					1, screenname,
-					2, buddy_alias,
-					3, account,
-					-1);
-			g_free(completion_entry);
-			completion_added = TRUE;
-		}
+			for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
+			{
+				GaimBuddy *buddy = (GaimBuddy *)bnode;
+
+				if (!all && !gaim_account_is_connected(buddy->account))
+					continue;
 
-		/* There's no sense listing things like: 'xxx "xxx"'
-		   when the screenname and contact alias match. */
-		if (contact_alias && strcmp(contact_alias, screenname)) {
-			/* We don't want duplicates when the contact and buddy alias match. */
-			if (strcmp(contact_alias, buddy_alias)) {
-				char *completion_entry = g_strdup_printf("%s \"%s\"",
-								screenname, contact_alias);
-				gtk_list_store_append(store, &iter);
-				gtk_list_store_set(store, &iter,
-						0, completion_entry,
-						1, screenname,
-						2, contact_alias,
-						3, account,
-						-1);
-				g_free(completion_entry);
-				completion_added = TRUE;
+				add_screenname_autocomplete_entry(store,
+												  ((GaimContact *)cnode)->alias,
+												  gaim_buddy_get_contact_alias(buddy),
+												  buddy->account,
+												  buddy->name
+												 );
 			}
 		}
-
-		if (completion_added == FALSE) {
-			/* Add the buddy's screenname. */
-			gtk_list_store_append(store, &iter);
-			gtk_list_store_set(store, &iter,
-					0, screenname,
-					1, screenname,
-					2, NULL,
-					3, account,
-					-1);
-		}
-
-		g_free(screenname);
 	}
 
-	g_list_free(screenname_completion_data);
+	sets = gaim_log_get_log_sets();
+	g_hash_table_foreach(sets, (GHFunc)get_log_set_name, &set_hash_data);
+	g_hash_table_destroy(sets);
+
 
 	/* Sort the completion list by screenname. */
 	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
@@ -986,7 +977,10 @@
 
 #else /* !NEW_STYLE_COMPLETION */
 	GaimGtkCompletionData *data;
-	GList *screennames;
+	GaimBlistNode *gnode, *cnode, *bnode;
+	GList *item = g_list_append(NULL, NULL);
+	GHashTable *sets;
+	gpointer set_hash_data[2];
 
 	data = g_new0(GaimGtkCompletionData, 1);
 
@@ -994,11 +988,38 @@
 
 	g_completion_set_compare(data->completion, g_ascii_strncasecmp);
 
-	screennames = get_screenname_completion_data(all);
+	for (gnode = gaim_get_blist()->root; gnode != NULL; gnode = gnode->next)
+	{
+		if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
+			continue;
+
+		for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
+		{
+			if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
+				continue;
+
+			for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
+			{
+				GaimBuddy *buddy = (GaimBuddy *)bnode;
 
-	g_completion_add_items(data->completion, screennames);
+				if (!all && !gaim_account_is_connected(buddy->account))
+					continue;
 
-	g_list_free(screennames);
+				item->data = g_strdup(buddy->name);
+				g_completion_add_items(data->completion, item);
+			}
+		}
+	}
+	g_list_free(item);
+
+	sets = gaim_log_get_log_sets();
+	item = NULL;
+	set_hash_data[0] = &item;
+	set_hash_data[1] = GINT_TO_POINTER(all);
+	g_hash_table_foreach(sets, (GHFunc)get_log_set_name, &set_hash_data);
+	g_hash_table_destroy(sets);
+	g_completion_add_items(data->completion, item);
+	g_list_free(item);
 
 	g_signal_connect(G_OBJECT(entry), "event",
 					 G_CALLBACK(completion_entry_event), data);
--- a/src/log.c	Sun Jul 31 15:21:31 2005 +0000
+++ b/src/log.c	Mon Aug 01 04:08:27 2005 +0000
@@ -43,7 +43,7 @@
 };
 static GHashTable *logsize_users = NULL;
 
-static GList *log_get_log_sets_common();
+static void log_get_log_sets_common(GHashTable *sets);
 
 /**************************************************************************
  * PUBLIC LOGGING FUNCTIONS ***********************************************
@@ -242,7 +242,7 @@
 				int(*size)(GaimLog*),
 				int(*total_size)(GaimLogType type, const char *name, GaimAccount *account),
 				GList*(*list_syslog)(GaimAccount *account),
-				GList*(*get_log_sets)(void))
+				void(*get_log_sets)(GaimLogSetCallback cb, GHashTable *sets))
 {
 	GaimLogLogger *logger = g_new0(GaimLogLogger, 1);
 
@@ -333,7 +333,6 @@
 	const GaimLogSet *a = y;
 	const GaimLogSet *b = z;
 	gint ret = 0;
-	char *tmp;
 
 	/* This logic seems weird at first...
 	 * If either account is NULL, we pretend the accounts are
@@ -341,14 +340,12 @@
 	 * exist if one logger knows the account and another
 	 * doesn't. */
 	if (a->account != NULL && b->account != NULL) {
-		ret = gaim_utf8_strcasecmp(gaim_account_get_username(a->account), gaim_account_get_username(b->account));
+		ret = strcmp(gaim_account_get_username(a->account), gaim_account_get_username(b->account));
 		if (ret != 0)
 			return ret;
 	}
 
-	tmp = g_strdup(gaim_normalize(a->account, a->name));
-	ret = gaim_utf8_strcasecmp(tmp, gaim_normalize(b->account, b->name));
-	g_free(tmp);
+	ret = strcmp(a->normalized_name, b->normalized_name);
 	if (ret != 0)
 		return ret;
 
@@ -373,17 +370,23 @@
 	return !gaim_log_set_compare(a, b);
 }
 
-void log_set_build_list(gpointer key, gpointer value, gpointer user_data)
+void log_add_log_set_to_hash(GHashTable *sets, GaimLogSet *set)
 {
-	*((GList **)user_data) = g_list_append(*((GList **)user_data), key);
+	GaimLogSet *existing_set = g_hash_table_lookup(sets, set);
+
+	if (existing_set == NULL)
+		g_hash_table_insert(sets, set, set);
+	else if (existing_set->account == NULL && set->account != NULL)
+		g_hash_table_replace(sets, set, set);
+	else
+		gaim_log_set_free(set);
 }
 
-GList *gaim_log_get_log_sets()
+GHashTable *gaim_log_get_log_sets(void)
 {
 	GSList *n;
-	GList *sets = NULL;
-	GList *set;
-	GHashTable *sets_ht = g_hash_table_new(log_set_hash, log_set_equal);
+	GHashTable *sets = g_hash_table_new_full(log_set_hash, log_set_equal,
+											 (GDestroyNotify)gaim_log_set_free, NULL);
 
 	/* Get the log sets from all the loggers. */
 	for (n = loggers; n; n = n->next) {
@@ -392,37 +395,23 @@
 		if (!logger->get_log_sets)
 			continue;
 
-		sets = g_list_concat(sets, logger->get_log_sets());
+		logger->get_log_sets(log_add_log_set_to_hash, sets);
 	}
 
-	/* Get the log sets for loggers that use the common logger functions. */
-	sets = g_list_concat(sets, log_get_log_sets_common());
+	log_get_log_sets_common(sets);
 
-	for (set = sets; set != NULL ; set = set->next) {
-		GaimLogSet *existing_set = g_hash_table_lookup(sets_ht, set->data);
+	/* Return the GHashTable of unique GaimLogSets. */
+	return sets;
+}
 
-		if (existing_set == NULL) {
-			g_hash_table_insert(sets_ht, set->data, set->data);
-		} else if (existing_set->account == NULL && ((GaimLogSet *)set->data)->account != NULL) {
-			/* The existing entry in the hash table has no account.
-			 * This one does. We'll delete the old one and keep this one. */
-			g_hash_table_replace(sets_ht, set->data, set->data);
-			g_free(existing_set->name);
-			g_free(existing_set);
-		} else {
-			g_free(((GaimLogSet *)set->data)->name);
-			g_free(set->data);
-		}
-	}
-	g_list_free(sets);
+void gaim_log_set_free(GaimLogSet *set)
+{
+	g_return_if_fail(set != NULL);
 
-	/* At this point, we've built a GHashTable of unique GaimLogSets.
-	 * So, we build a list of those keys and destroy the GHashTable. */
-	sets = NULL;
-	g_hash_table_foreach(sets_ht, log_set_build_list, &sets);
-	g_hash_table_destroy(sets_ht);
-
-	return g_list_sort(sets, gaim_log_set_compare);
+	g_free(set->name);
+	if (set->normalized_name != set->name)
+		g_free(set->normalized_name);
+	g_free(set);
 }
 
 GList *gaim_log_get_system_logs(GaimAccount *account)
@@ -556,16 +545,15 @@
 
 /* This will build log sets for all loggers that use the common logger
  * functions because they use the same directory structure. */
-static GList *log_get_log_sets_common()
+static void log_get_log_sets_common(GHashTable *sets)
 {
 	gchar *log_path = g_build_filename(gaim_user_dir(), "logs", NULL);
 	GDir *log_dir = g_dir_open(log_path, 0, NULL);
 	const gchar *protocol;
-	GList *sets = NULL;
 
 	if (log_dir == NULL) {
 		g_free(log_path);
-		return NULL;
+		return;
 	}
 
 	while ((protocol = g_dir_read_name(log_dir)) != NULL) {
@@ -634,6 +622,7 @@
 
 				set->account = account;
 				set->name = name;
+				set->normalized_name = g_strdup(gaim_normalize(account, name));
 
 				/* Chat for .chat or .system at the end of the name to determine the type. */
 				set->type = GAIM_LOG_IM;
@@ -653,12 +642,9 @@
 				}
 
 				/* Determine if this (account, name) combination exists as a buddy. */
-				if (gaim_find_buddy(account, name) != NULL)
-					set->buddy = TRUE;
-				else
-					set->buddy = FALSE;
+				set->buddy = (gaim_find_buddy(account, name) != NULL);
 
-				sets = g_list_append(sets, set);
+				log_add_log_set_to_hash(sets, set);
 			}
 			g_free(username_path);
 			g_dir_close(username_dir);
@@ -668,8 +654,6 @@
 	}
 	g_free(log_path);
 	g_dir_close(log_dir);
-
-	return sets;
 }
 
 #if 0 /* Maybe some other time. */
@@ -1222,17 +1206,16 @@
 	return data ? data->length : 0;
 }
 
-static GList *old_logger_get_log_sets()
+static void old_logger_get_log_sets(GaimLogSetCallback cb, GHashTable *sets)
 {
 	char *log_path = g_build_filename(gaim_user_dir(), "logs", NULL);
 	GDir *log_dir = g_dir_open(log_path, 0, NULL);
 	gchar *name;
-	GList *sets = NULL;
 	GaimBlistNode *gnode, *cnode, *bnode;
 
 	g_free(log_path);
 	if (log_dir == NULL)
-		return NULL;
+		return;
 
 	/* Don't worry about the cast, name will be filled with a dynamically allocated data shortly. */
 	while ((name = (gchar *)g_dir_read_name(log_dir)) != NULL) {
@@ -1272,7 +1255,7 @@
 			}
 		}
 
-		set->name = name;
+		set->name = set->normalized_name = name;
 
 		/* Search the buddy list to find the account and to determine if this is a buddy. */
 		for (gnode = gaim_get_blist()->root; !found && gnode != NULL; gnode = gnode->next)
@@ -1298,11 +1281,9 @@
 			}
 		}
 
-		sets = g_list_append(sets, set);
+		cb(sets, set);
 	}
 	g_dir_close(log_dir);
-
-	return sets;
 }
 
 static void old_logger_finalize(GaimLog *log)
--- a/src/log.h	Sun Jul 31 15:21:31 2005 +0000
+++ b/src/log.h	Mon Aug 01 04:08:27 2005 +0000
@@ -50,6 +50,8 @@
 #include "account.h"
 #include "conversation.h"
 
+typedef void (*GaimLogSetCallback) (GHashTable *sets, GaimLogSet *set);
+
 /**
  * A log logger.
  *
@@ -92,12 +94,15 @@
 	/** This function returns a sorted GList of available system GaimLogs */
 	GList *(*list_syslog)(GaimAccount *account);
 
-	/** Returns a list of GaimLogSets. By passing the data in the GaimLogSets
+	/** Adds GaimLogSets to a GHashTable. By passing the data in the GaimLogSets
 	 *  to list, the caller can get every available GaimLog from the logger.
 	 *  Loggers using gaim_log_common_writer() (or otherwise storing their
 	 *  logs in the same directory structure as the stock loggers) do not
-	 *  need to implement this function. */
-	GList *(*get_log_sets)(void);
+	 *  need to implement this function.
+	 *
+	 *  Loggers which implement this function must create a GaimLogSet,
+	 *  then call @a cb with @a sets and the newly created GaimLogSet. */
+	void (*get_log_sets)(GaimLogSetCallback cb, GHashTable *sets);
 };
 
 /**
@@ -143,6 +148,10 @@
 	                                           to load such logs.) */
 	gboolean buddy;                       /**< Is this (account, name) a buddy
 	                                           on the buddy list? */
+	char *normalized_name;                /**< The normalized version of
+	                                           @a name. It must be set, and
+	                                           may be set to the same pointer
+	                                           value as @a name. */
 };
 
 #ifdef __cplusplus
@@ -211,7 +220,7 @@
 GList *gaim_log_get_logs(GaimLogType type, const char *name, GaimAccount *account);
 
 /**
- * Returns a list of GaimLogSets.
+ * Returns a GHashTable of GaimLogSets.
  *
  * A "log set" here means the information necessary to gather the
  * GaimLogs for a given buddy/chat. This information would be passed
@@ -220,15 +229,19 @@
  * The primary use of this function is to get a list of everyone the
  * user has ever talked to (assuming he or she uses logging).
  *
- * @return A sorted list of all available unique GaimLogSets
+ * The GHashTable that's returned will free all log sets in it when
+ * destroyed. If a GaimLogSet is removed from the GHashTable, it
+ * must be freed with gaim_log_set_free().
+ *
+ * @return A GHashTable of all available unique GaimLogSets
  */
-GList *gaim_log_get_log_sets(void);
+GHashTable *gaim_log_get_log_sets(void);
 
 /**
  * Returns a list of all available system logs
  *
  * @param account The account
- * @return		A sorted list of GaimLogs
+ * @return        A sorted list of GaimLogs
  */
 GList *gaim_log_get_system_logs(GaimAccount *account);
 
@@ -265,9 +278,9 @@
 /**
  * Implements GCompareFunc for GaimLogs
  *
- * @param y				   A GaimLog
- * @param z				   Another GaimLog
- * @return					A value as specified by GCompareFunc
+ * @param y                   A GaimLog
+ * @param z                   Another GaimLog
+ * @return                    A value as specified by GCompareFunc
  */
 gint gaim_log_compare(gconstpointer y, gconstpointer z);
 
@@ -280,6 +293,13 @@
  */
 gint gaim_log_set_compare(gconstpointer y, gconstpointer z);
 
+/**
+ * Frees a log set
+ *
+ * @param set         The log set to destroy
+ */
+void gaim_log_set_free(GaimLogSet *set);
+
 /*@}*/
 
 /******************************************/
@@ -359,7 +379,7 @@
 				int(*size)(GaimLog*),
 				int(*total_size)(GaimLogType type, const char *name, GaimAccount *account),
 				GList*(*list_syslog)(GaimAccount *account),
-				GList*(*get_log_sets)(void));
+				void(*get_log_sets)(GaimLogSetCallback cb, GHashTable *sets));
 
 /**
  * Frees a logger